6
6
from fontTools .misc .transform import Transform , Identity
7
7
from fontTools .misc .arrayTools import unionRect
8
8
from fontTools .ttLib import TTFont
9
- from fontTools .ttLib .tables .otTables import CompositeMode , PaintFormat
9
+ from fontTools .ttLib .tables .otTables import (
10
+ BaseTable ,
11
+ ClipBoxFormat ,
12
+ CompositeMode ,
13
+ Paint ,
14
+ PaintFormat ,
15
+ VarAffine2x3 ,
16
+ VarColorStop ,
17
+ VarColorLine ,
18
+ )
10
19
from fontTools .varLib .varStore import VarStoreInstancer
11
20
import uharfbuzz as hb
12
21
@@ -34,6 +43,7 @@ def __init__(self, path, *, fontNumber=0, lazy=True):
34
43
self .colrV0Glyphs = {}
35
44
self .colrV1Glyphs = {}
36
45
self .instancer = None
46
+ self .varIndexMap = None
37
47
38
48
if "COLR" in self .ttFont :
39
49
colrTable = self .ttFont ["COLR" ]
@@ -53,6 +63,8 @@ def __init__(self, path, *, fontNumber=0, lazy=True):
53
63
self .clipBoxes = colrTable .ClipList .clips
54
64
self .colrLayersV1 = colrTable .LayerList
55
65
if colrTable .VarStore is not None :
66
+ if colrTable .VarIndexMap :
67
+ self .varIndexMap = colrTable .VarIndexMap .mapping
56
68
self .instancer = VarStoreInstancer (
57
69
colrTable .VarStore , self .ttFont ["fvar" ].axes
58
70
)
@@ -106,6 +118,11 @@ def getGlyphBounds(self, glyphName):
106
118
if self .clipBoxes is not None :
107
119
box = self .clipBoxes .get (glyphName )
108
120
if box is not None :
121
+ if (
122
+ box .Format == ClipBoxFormat .Variable
123
+ and self .instancer is not None
124
+ ):
125
+ box = VarTableWrapper (box , self .instancer , self .varIndexMap )
109
126
bounds = box .xMin , box .yMin , box .xMax , box .yMax
110
127
elif glyphName in self .colrV0Glyphs :
111
128
# For COLRv0, we take the union of all layer bounds
@@ -174,7 +191,7 @@ def _drawPaint(self, paint, canvas):
174
191
# PaintVar -- we map to its non-var counterpart and use a wrapper
175
192
# that takes care of instantiating values
176
193
paintName = PAINT_NAMES [nonVarFormat ]
177
- paint = PaintVarWrapper (paint , self .instancer )
194
+ paint = VarTableWrapper (paint , self .instancer , self . varIndexMap )
178
195
drawHandler = getattr (self , "_draw" + paintName )
179
196
drawHandler (paint , canvas )
180
197
@@ -487,34 +504,68 @@ def axisValuesToLocation(normalizedAxisValues, axisTags):
487
504
}
488
505
489
506
490
- # _conversionFactors = {
491
- # VarF2Dot14: 1 / (1 << 14),
492
- # VarFixed: 1 / (1 << 16),
493
- # }
507
+ class VarTableWrapper :
494
508
495
-
496
- class PaintVarWrapper :
497
- def __init__ (self , wrappedPaint , instancer ):
498
- assert not isinstance (wrappedPaint , PaintVarWrapper )
499
- self ._wrappedPaint = wrappedPaint
509
+ def __init__ (self , wrapped , instancer , varIndexMap = None ):
510
+ assert not isinstance (wrapped , VarTableWrapper )
511
+ self ._wrapped = wrapped
500
512
self ._instancer = instancer
513
+ self ._varIndexMap = varIndexMap
514
+ # Map {attrName: varIndexOffset}, where the offset is a number to add to
515
+ # VarIndexBase to get to the VarIndex associated with each attribute.
516
+ # getVariableAttrs method returns a sequence of variable attributes in the
517
+ # order in which they appear in a table.
518
+ # E.g. in ColorStop table, the first variable attribute is "StopOffset",
519
+ # and the second is "Alpha": hence the StopOffset's VarIndex is computed as
520
+ # VarIndexBase + 0, Alpha's is VarIndexBase + 1, etc.
521
+ self ._varAttrs = {a : i for i , a in enumerate (wrapped .getVariableAttrs ())}
501
522
502
523
def __repr__ (self ):
503
- return f"PaintVarWrapper({ self ._wrappedPaint !r} )"
524
+ return f"VarTableWrapper({ self ._wrapped !r} )"
525
+
526
+ def _getVarIndexForAttr (self , attrName ):
527
+ if attrName not in self ._varAttrs :
528
+ return None
529
+
530
+ baseIndex = self ._wrapped .VarIndexBase
531
+ if baseIndex == 0xFFFFFFFF :
532
+ return baseIndex
533
+
534
+ offset = self ._varAttrs [attrName ]
535
+ varIdx = baseIndex + offset
536
+ if self ._varIndexMap is not None :
537
+ try :
538
+ varIdx = self ._varIndexMap [varIdx ]
539
+ except IndexError :
540
+ pass
541
+
542
+ return varIdx
543
+
544
+ def _getDeltaForAttr (self , attrName , varIdx ):
545
+ delta = self ._instancer [varIdx ]
546
+ # deltas for Fixed or F2Dot14 need to be converted from int to float
547
+ conv = self ._wrapped .getConverterByName (attrName )
548
+ if hasattr (conv , "fromInt" ):
549
+ delta = conv .fromInt (delta )
550
+ return delta
504
551
505
552
def __getattr__ (self , attrName ):
506
- value = getattr (self ._wrappedPaint , attrName )
507
- raise NotImplementedError ("This code is currently not working" )
508
- # if isinstance(value, VariableValue):
509
- # if value.varIdx != 0xFFFFFFFF:
510
- # factor = _conversionFactors.get(
511
- # type(self._wrappedPaint.getConverterByName(attrName)), 1
512
- # )
513
- # value = value.value + self._instancer[value.varIdx] * factor
514
- # else:
515
- # value = value.value
516
- # elif type(value).__name__.startswith("Var"):
517
- # value = PaintVarWrapper(value, self._instancer)
518
- # elif isinstance(value, (list, UserList)):
519
- # value = [PaintVarWrapper(item, self._instancer) for item in value]
553
+ value = getattr (self ._wrapped , attrName )
554
+
555
+ varIdx = self ._getVarIndexForAttr (attrName )
556
+ if varIdx is not None :
557
+ if varIdx < 0xFFFFFFFF :
558
+ value += self ._getDeltaForAttr (attrName , varIdx )
559
+ elif isinstance (value , (VarAffine2x3 , VarColorLine )):
560
+ value = VarTableWrapper (value , self ._instancer , self ._varIndexMap )
561
+ elif (
562
+ isinstance (value , (list , UserList ))
563
+ and value
564
+ and isinstance (value [0 ], VarColorStop )
565
+ ):
566
+ value = [
567
+ VarTableWrapper (item , self ._instancer , self ._varIndexMap )
568
+ for item in value
569
+ ]
570
+
520
571
return value
0 commit comments