3
3
from typing import List , Tuple , TYPE_CHECKING
4
4
from io import BytesIO
5
5
from fontTools .ttLib .tables .BitmapGlyphMetrics import BigGlyphMetrics , SmallGlyphMetrics
6
+ from fontTools .ttLib .tables .C_O_L_R_ import table_C_O_L_R_
7
+ from fontTools .ttLib .tables .otTables import Paint , PaintFormat
6
8
7
9
from .drawing import DeviceRGB , GraphicsContext , Transform , PathPen , PaintedPath
8
10
@@ -146,34 +148,56 @@ def load_glyph_image(self, glyph: Type3FontGlyph):
146
148
147
149
class COLRFont (Type3Font ):
148
150
151
+ def __init__ (self , fpdf : "FPDF" , base_font : "TTFFont" ):
152
+ super ().__init__ (fpdf , base_font )
153
+ colr_table : table_C_O_L_R_ = self .base_font .ttfont ["COLR" ]
154
+ self .colrv0_glyphs = []
155
+ self .colrv1_glyphs = []
156
+ self .version = colr_table .version
157
+ if colr_table .version == 0 :
158
+ self .colrv0_glyphs = colr_table .ColorLayers
159
+ else :
160
+ self .colrv0_glyphs = colr_table ._decompileColorLayersV0 (colr_table .table )
161
+ self .colrv1_glyphs = {
162
+ glyph .BaseGlyph : glyph
163
+ for glyph in colr_table .table .BaseGlyphList .BaseGlyphPaintRecord
164
+ }
165
+ self .palette = None
166
+ if "CPAL" in self .base_font .ttfont :
167
+ # hardcoding the first palette for now
168
+ print (f"This font has { len (self .base_font .ttfont ['CPAL' ].palettes )} palettes" )
169
+ palette = self .base_font .ttfont ["CPAL" ].palettes [0 ]
170
+ self .palette = [
171
+ (color .red / 255 , color .green / 255 , color .blue / 255 , color .alpha / 255 ) for color in palette
172
+ ]
173
+
174
+
149
175
def glyph_exists (self , glyph_name ):
150
- return glyph_name in self .base_font . ttfont [ "COLR" ]. ColorLayers
176
+ return glyph_name in self .colrv0_glyphs or glyph_name in self . colrv1_glyphs
151
177
152
178
def load_glyph_image (self , glyph : Type3FontGlyph ):
153
179
w = round (self .base_font .ttfont ["hmtx" ].metrics [glyph .glyph_name ][0 ] + 0.001 )
154
- glyph_layers = self .base_font .ttfont ["COLR" ].ColorLayers [glyph .glyph_name ]
155
- img = self .draw_glyph_colrv0 (glyph_layers )
156
- img .transform = Transform .scaling (self .scale , - self .scale )
180
+ if glyph .glyph_name in self .colrv0_glyphs :
181
+ glyph_layers = self .base_font .ttfont ["COLR" ].ColorLayers [glyph .glyph_name ]
182
+ img = self .draw_glyph_colrv0 (glyph_layers )
183
+ else :
184
+ img = self .draw_glyph_colrv1 (glyph .glyph_name )
185
+ img .transform = img .transform @ Transform .scaling (self .scale , - self .scale )
157
186
output_stream = self .fpdf .draw_vector_glyph (img , self )
158
187
glyph .glyph = (
159
188
f"{ w * self .scale / self .upem } 0 d0\n " "q\n " f"{ output_stream } \n " "Q"
160
189
)
161
190
glyph .glyph_width = w
162
191
163
- def get_color (self , color_index , palette = 0 ):
164
- palettes = [
165
- [(c .red / 255 , c .green / 255 , c .blue / 255 , c .alpha / 255 ) for c in p ]
166
- for p in self .base_font .ttfont ["CPAL" ].palettes
167
- ]
168
- r , g , b , a = palettes [palette ][color_index ]
169
- # a *= alpha
192
+ def get_color (self , color_index , alpha = 1 ):
193
+ r , g , b , a = self .palette [color_index ]
194
+ a *= alpha
170
195
return DeviceRGB (r , g , b , a )
171
196
172
197
def draw_glyph_colrv0 (self , layers ):
173
198
gc = GraphicsContext ()
199
+ gc .transform = Transform .identity ()
174
200
for layer in layers :
175
- print (layer .__repr__ )
176
- print (layer .colorID , layer .name )
177
201
path = PaintedPath ()
178
202
glyph_set = self .base_font .ttfont .getGlyphSet ()
179
203
pen = PathPen (path , glyphSet = glyph_set )
@@ -183,10 +207,44 @@ def draw_glyph_colrv0(self, layers):
183
207
path .style .stroke_color = self .get_color (layer .colorID )
184
208
gc .add_item (path )
185
209
return gc
186
- # print(path)
187
- # self.base_font.hbfont.draw_glyph_with_pen(gid, path)
188
- # canvas.drawPathSolid(path, self._getColor(layer.colorID, 1))
210
+
211
+ def draw_glyph_colrv1 (self , glyph_name ):
212
+ gc = GraphicsContext ()
213
+ gc .transform = Transform .identity ()
214
+ glyph = self .colrv1_glyphs [glyph_name ]
215
+ self .draw_colrv1_paint (glyph .Paint , gc )
216
+ return gc
189
217
218
+ def draw_colrv1_paint (self , paint : Paint , gc : GraphicsContext ):
219
+ print (paint .getFormatName ())
220
+ if paint .Format == PaintFormat .PaintColrLayers : #1
221
+ print ("[PaintColrLayers] FirstLayerIndex: " , paint .FirstLayerIndex , " NumLayers: " , paint .NumLayers )
222
+ layer_list = self .base_font .ttfont ["COLR" ].table .LayerList
223
+ for layer in range (paint .FirstLayerIndex , paint .FirstLayerIndex + paint .NumLayers ):
224
+ self .draw_colrv1_paint (layer_list .Paint [layer ], gc )
225
+ elif paint .Format == PaintFormat .PaintSolid : #2
226
+ color = self .get_color (paint .PaletteIndex , paint .Alpha )
227
+ path : PaintedPath = gc .path_items [- 1 ]
228
+ path .style .fill_color = color
229
+ path .style .stroke_color = color
230
+ elif paint .Format == PaintFormat .PaintLinearGradient : #4
231
+ print ("[PaintLinearGradient] ColorLine: " )
232
+ for stop in paint .ColorLine .ColorStop :
233
+ print ("Stop: " , stop .StopOffset , " color: " , stop .PaletteIndex )
234
+ print ("x0: " , paint .x0 , " y0: " , paint .y0 , " x1: " , paint .x1 , " y1: " , paint .y1 )
235
+ elif paint .Format == PaintFormat .PaintGlyph : #10
236
+ path = PaintedPath ()
237
+ glyph_set = self .base_font .ttfont .getGlyphSet ()
238
+ pen = PathPen (path , glyphSet = glyph_set )
239
+ glyph = glyph_set [paint .Glyph ]
240
+ glyph .draw (pen )
241
+ gc .add_item (path )
242
+ self .draw_colrv1_paint (paint .Paint , gc )
243
+ else :
244
+ print ("Unknown PaintFormat: " , paint .Format )
245
+
246
+
247
+
190
248
191
249
class CBDTColorFont (Type3Font ):
192
250
@@ -195,11 +253,11 @@ class CBDTColorFont(Type3Font):
195
253
def glyph_exists (self , glyph_name ):
196
254
return glyph_name in self .base_font .ttfont ["CBDT" ].strikeData [0 ]
197
255
198
- def load_glyph_image (self , glyph_name ):
256
+ def load_glyph_image (self , glyph : Type3FontGlyph ):
199
257
ppem = self .base_font .ttfont ["CBLC" ].strikes [0 ].bitmapSizeTable .ppemX
200
- glyph = self .base_font .ttfont ["CBDT" ].strikeData [0 ][glyph_name ]
201
- glyph_bitmap = glyph .data [9 :]
202
- metrics = glyph .metrics
258
+ g = self .base_font .ttfont ["CBDT" ].strikeData [0 ][glyph . glyph_name ]
259
+ glyph_bitmap = g .data [9 :]
260
+ metrics = g .metrics
203
261
if isinstance (metrics , SmallGlyphMetrics ):
204
262
x_min = round (metrics .BearingX * self .upem / ppem )
205
263
y_min = round ((metrics .BearingY - metrics .height ) * self .upem / ppem )
@@ -300,9 +358,9 @@ def get_color_font_object(fpdf: "FPDF", base_font: "TTFFont") -> Type3Font:
300
358
if "COLR" in base_font .ttfont :
301
359
if base_font .ttfont ["COLR" ].version == 0 :
302
360
LOGGER .warning ("Font %s is a COLRv0 color font" , base_font .name )
303
- return COLRFont ( fpdf , base_font )
304
- LOGGER .warning ("Font %s is a COLRv1 color font" , base_font .name )
305
- return None
361
+ else :
362
+ LOGGER .warning ("Font %s is a COLRv1 color font" , base_font .name )
363
+ return COLRFont ( fpdf , base_font )
306
364
if "SVG " in base_font .ttfont :
307
365
LOGGER .warning ("Font %s is a SVG color font" , base_font .name )
308
366
return SVGColorFont (fpdf , base_font )
0 commit comments