Skip to content
This repository was archived by the owner on Feb 24, 2026. It is now read-only.

Commit b36dc11

Browse files
committed
a
1 parent 4bd4547 commit b36dc11

File tree

6 files changed

+396
-201
lines changed

6 files changed

+396
-201
lines changed

assets/gulim.ttc

12.9 MB
Binary file not shown.

src/sfnttools/tables/glyf/__init__.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
from io import BytesIO
22

33
from sfnttools.table import SfntTableContainer, SfntTable
4-
from sfnttools.tables.glyf.composite import ComponentGlyphRecord
5-
from sfnttools.tables.glyf.description import GlyphDescription
6-
from sfnttools.tables.glyf.simple import SimpleGlyphTable
4+
from sfnttools.tables.glyf.component import ComponentGlyph
5+
from sfnttools.tables.glyf.simple import SimpleGlyph
76
from sfnttools.tables.loca import LocaTable
87
from sfnttools.utils.stream import Stream
98

@@ -21,29 +20,26 @@ def parse(data: bytes, container: SfntTableContainer) -> 'GlyfTable':
2120
if glyph_data == b'':
2221
glyph_description = None
2322
else:
24-
stream = Stream(glyph_data)
25-
num_contours = stream.read_int16()
26-
stream.seek(0)
27-
if num_contours >= 0:
28-
glyph_description = SimpleGlyphTable.parse(stream)
23+
if int.from_bytes(glyph_data[0:2], 'big', signed=True) >= 0:
24+
glyph_description = SimpleGlyph.parse(glyph_data)
2925
else:
30-
glyph_description = ComponentGlyphRecord.parse(stream)
26+
glyph_description = ComponentGlyph.parse(glyph_data)
3127
glyph_descriptions.append(glyph_description)
3228

3329
return GlyfTable(glyph_descriptions)
3430

35-
glyph_descriptions: list[GlyphDescription | None]
31+
glyph_descriptions: list[SimpleGlyph | ComponentGlyph | None]
3632

37-
def __init__(self, glyph_descriptions: list[GlyphDescription | None] | None = None):
33+
def __init__(
34+
self,
35+
glyph_descriptions: list[SimpleGlyph | ComponentGlyph | None] | None = None,
36+
):
3837
self.glyph_descriptions = [] if glyph_descriptions is None else glyph_descriptions
3938

4039
def copy(self) -> 'GlyfTable':
4140
glyph_descriptions = []
4241
for glyph_description in self.glyph_descriptions:
43-
if glyph_description is None:
44-
glyph_descriptions.append(None)
45-
else:
46-
glyph_descriptions.append(glyph_description.copy())
42+
glyph_descriptions.append(None if glyph_description is None else glyph_description.copy())
4743
return GlyfTable(glyph_descriptions)
4844

4945
def dump(self, container: SfntTableContainer) -> bytes:
Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
from io import BytesIO
2+
from typing import Final
3+
4+
from sfnttools.table import SfntFlags
5+
from sfnttools.utils.stream import Stream
6+
7+
COMPONENT_GLYPH_FLAGS_MASK_ARG_1_AND_2_ARE_WORDS: Final = 0b_0000_0000_0000_0001
8+
COMPONENT_GLYPH_FLAGS_MASK_ARGS_ARE_XY_VALUES: Final = 0b_0000_0000_0000_0010
9+
COMPONENT_GLYPH_FLAGS_MASK_ROUND_XY_TO_GRID: Final = 0b_0000_0000_0000_0100
10+
COMPONENT_GLYPH_FLAGS_MASK_WE_HAVE_A_SCALE: Final = 0b_0000_0000_0000_1000
11+
COMPONENT_GLYPH_FLAGS_MASK_MORE_COMPONENTS: Final = 0b_0000_0000_0010_0000
12+
COMPONENT_GLYPH_FLAGS_MASK_WE_HAVE_AN_X_AND_Y_SCALE: Final = 0b_0000_0000_0100_0000
13+
COMPONENT_GLYPH_FLAGS_MASK_WE_HAVE_A_TWO_BY_TWO: Final = 0b_0000_0000_1000_0000
14+
COMPONENT_GLYPH_FLAGS_MASK_WE_HAVE_INSTRUCTIONS: Final = 0b_0000_0001_0000_0000
15+
COMPONENT_GLYPH_FLAGS_MASK_USE_MY_METRICS: Final = 0b_0000_0010_0000_0000
16+
COMPONENT_GLYPH_FLAGS_MASK_OVERLAP_COMPOUND: Final = 0b_0000_0100_0000_0000
17+
COMPONENT_GLYPH_FLAGS_MASK_SCALED_COMPONENT_OFFSET: Final = 0b_0000_1000_0000_0000
18+
COMPONENT_GLYPH_FLAGS_MASK_UNSCALED_COMPONENT_OFFSET: Final = 0b_0001_0000_0000_0000
19+
20+
21+
class ComponentGlyphFlags(SfntFlags):
22+
@staticmethod
23+
def parse(value: int) -> 'ComponentGlyphFlags':
24+
arg_1_and_2_are_words = value & COMPONENT_GLYPH_FLAGS_MASK_ARG_1_AND_2_ARE_WORDS > 0
25+
args_are_xy_values = value & COMPONENT_GLYPH_FLAGS_MASK_ARGS_ARE_XY_VALUES > 0
26+
round_xy_to_grid = value & COMPONENT_GLYPH_FLAGS_MASK_ROUND_XY_TO_GRID > 0
27+
we_have_a_scale = value & COMPONENT_GLYPH_FLAGS_MASK_WE_HAVE_A_SCALE > 0
28+
more_components = value & COMPONENT_GLYPH_FLAGS_MASK_MORE_COMPONENTS > 0
29+
we_have_an_x_and_y_scale = value & COMPONENT_GLYPH_FLAGS_MASK_WE_HAVE_AN_X_AND_Y_SCALE > 0
30+
we_have_a_two_by_two = value & COMPONENT_GLYPH_FLAGS_MASK_WE_HAVE_A_TWO_BY_TWO > 0
31+
we_have_instructions = value & COMPONENT_GLYPH_FLAGS_MASK_WE_HAVE_INSTRUCTIONS > 0
32+
use_my_metrics = value & COMPONENT_GLYPH_FLAGS_MASK_USE_MY_METRICS > 0
33+
overlap_compound = value & COMPONENT_GLYPH_FLAGS_MASK_OVERLAP_COMPOUND > 0
34+
scaled_component_offset = value & COMPONENT_GLYPH_FLAGS_MASK_SCALED_COMPONENT_OFFSET > 0
35+
unscaled_component_offset = value & COMPONENT_GLYPH_FLAGS_MASK_UNSCALED_COMPONENT_OFFSET > 0
36+
return ComponentGlyphFlags(
37+
arg_1_and_2_are_words,
38+
args_are_xy_values,
39+
round_xy_to_grid,
40+
we_have_a_scale,
41+
more_components,
42+
we_have_an_x_and_y_scale,
43+
we_have_a_two_by_two,
44+
we_have_instructions,
45+
use_my_metrics,
46+
overlap_compound,
47+
scaled_component_offset,
48+
unscaled_component_offset,
49+
)
50+
51+
arg_1_and_2_are_words: bool
52+
args_are_xy_values: bool
53+
round_xy_to_grid: bool
54+
we_have_a_scale: bool
55+
more_components: bool
56+
we_have_an_x_and_y_scale: bool
57+
we_have_a_two_by_two: bool
58+
we_have_instructions: bool
59+
use_my_metrics: bool
60+
overlap_compound: bool
61+
scaled_component_offset: bool
62+
unscaled_component_offset: bool
63+
64+
def __init__(
65+
self,
66+
arg_1_and_2_are_words: bool = False,
67+
args_are_xy_values: bool = False,
68+
round_xy_to_grid: bool = False,
69+
we_have_a_scale: bool = False,
70+
more_components: bool = False,
71+
we_have_an_x_and_y_scale: bool = False,
72+
we_have_a_two_by_two: bool = False,
73+
we_have_instructions: bool = False,
74+
use_my_metrics: bool = False,
75+
overlap_compound: bool = False,
76+
scaled_component_offset: bool = False,
77+
unscaled_component_offset: bool = False,
78+
):
79+
self.arg_1_and_2_are_words = arg_1_and_2_are_words
80+
self.args_are_xy_values = args_are_xy_values
81+
self.round_xy_to_grid = round_xy_to_grid
82+
self.we_have_a_scale = we_have_a_scale
83+
self.more_components = more_components
84+
self.we_have_an_x_and_y_scale = we_have_an_x_and_y_scale
85+
self.we_have_a_two_by_two = we_have_a_two_by_two
86+
self.we_have_instructions = we_have_instructions
87+
self.use_my_metrics = use_my_metrics
88+
self.overlap_compound = overlap_compound
89+
self.scaled_component_offset = scaled_component_offset
90+
self.unscaled_component_offset = unscaled_component_offset
91+
92+
@property
93+
def value(self) -> int:
94+
value = 0
95+
if self.arg_1_and_2_are_words:
96+
value |= COMPONENT_GLYPH_FLAGS_MASK_ARG_1_AND_2_ARE_WORDS
97+
if self.args_are_xy_values:
98+
value |= COMPONENT_GLYPH_FLAGS_MASK_ARGS_ARE_XY_VALUES
99+
if self.round_xy_to_grid:
100+
value |= COMPONENT_GLYPH_FLAGS_MASK_ROUND_XY_TO_GRID
101+
if self.we_have_a_scale:
102+
value |= COMPONENT_GLYPH_FLAGS_MASK_WE_HAVE_A_SCALE
103+
if self.more_components:
104+
value |= COMPONENT_GLYPH_FLAGS_MASK_MORE_COMPONENTS
105+
if self.we_have_an_x_and_y_scale:
106+
value |= COMPONENT_GLYPH_FLAGS_MASK_WE_HAVE_AN_X_AND_Y_SCALE
107+
if self.we_have_a_two_by_two:
108+
value |= COMPONENT_GLYPH_FLAGS_MASK_WE_HAVE_A_TWO_BY_TWO
109+
if self.we_have_instructions:
110+
value |= COMPONENT_GLYPH_FLAGS_MASK_WE_HAVE_INSTRUCTIONS
111+
if self.use_my_metrics:
112+
value |= COMPONENT_GLYPH_FLAGS_MASK_USE_MY_METRICS
113+
if self.overlap_compound:
114+
value |= COMPONENT_GLYPH_FLAGS_MASK_OVERLAP_COMPOUND
115+
if self.scaled_component_offset:
116+
value |= COMPONENT_GLYPH_FLAGS_MASK_SCALED_COMPONENT_OFFSET
117+
if self.unscaled_component_offset:
118+
value |= COMPONENT_GLYPH_FLAGS_MASK_UNSCALED_COMPONENT_OFFSET
119+
return value
120+
121+
def copy(self) -> 'ComponentGlyphFlags':
122+
return ComponentGlyphFlags(
123+
self.arg_1_and_2_are_words,
124+
self.args_are_xy_values,
125+
self.round_xy_to_grid,
126+
self.we_have_a_scale,
127+
self.more_components,
128+
self.we_have_an_x_and_y_scale,
129+
self.we_have_a_two_by_two,
130+
self.we_have_instructions,
131+
self.use_my_metrics,
132+
self.overlap_compound,
133+
self.scaled_component_offset,
134+
self.unscaled_component_offset,
135+
)
136+
137+
138+
class XyGlyphComponent:
139+
glyph_index: int
140+
x: int
141+
y: int
142+
round_xy_to_grid: bool
143+
scaled_component_offset: bool
144+
unscaled_component_offset: bool
145+
transform: tuple[float, float, float, float]
146+
use_my_metrics: bool
147+
148+
def __init__(
149+
self,
150+
glyph_index: int = 0,
151+
x: int = 0,
152+
y: int = 0,
153+
round_xy_to_grid: bool = False,
154+
scaled_component_offset: bool = False,
155+
unscaled_component_offset: bool = False,
156+
transform: tuple[float, float, float, float] = (0, 0, 0, 0),
157+
use_my_metrics: bool = False,
158+
):
159+
self.glyph_index = glyph_index
160+
self.x = x
161+
self.y = y
162+
self.round_xy_to_grid = round_xy_to_grid
163+
self.scaled_component_offset = scaled_component_offset
164+
self.unscaled_component_offset = unscaled_component_offset
165+
self.transform = transform
166+
self.use_my_metrics = use_my_metrics
167+
168+
def copy(self) -> 'XyGlyphComponent':
169+
return XyGlyphComponent(
170+
self.glyph_index,
171+
self.x,
172+
self.y,
173+
self.round_xy_to_grid,
174+
self.scaled_component_offset,
175+
self.unscaled_component_offset,
176+
self.transform,
177+
self.use_my_metrics,
178+
)
179+
180+
181+
class PointsGlyphComponent:
182+
glyph_index: int
183+
parent_point: int
184+
child_point: int
185+
transform: tuple[float, float, float, float] | None
186+
use_my_metrics: bool
187+
188+
def __init__(
189+
self,
190+
glyph_index: int = 0,
191+
parent_point: int = 0,
192+
child_point: int = 0,
193+
transform: tuple[float, float, float, float] | None = None,
194+
use_my_metrics: bool = False,
195+
):
196+
self.glyph_index = glyph_index
197+
self.parent_point = parent_point
198+
self.child_point = child_point
199+
self.transform = transform
200+
self.use_my_metrics = use_my_metrics
201+
202+
def copy(self) -> 'PointsGlyphComponent':
203+
return PointsGlyphComponent(
204+
self.glyph_index,
205+
self.parent_point,
206+
self.child_point,
207+
self.transform,
208+
self.use_my_metrics,
209+
)
210+
211+
212+
class ComponentGlyph:
213+
@staticmethod
214+
def parse(data: bytes) -> 'ComponentGlyph':
215+
stream = Stream(data)
216+
217+
stream.read_int16()
218+
x_min = stream.read_int16()
219+
y_min = stream.read_int16()
220+
x_max = stream.read_int16()
221+
y_max = stream.read_int16()
222+
223+
components = []
224+
overlap_compound = None
225+
we_have_instructions = False
226+
while True:
227+
flags = ComponentGlyphFlags.parse(stream.read_uint16())
228+
glyph_index = stream.read_uint16()
229+
230+
if overlap_compound is None:
231+
overlap_compound = flags.overlap_compound
232+
233+
if flags.arg_1_and_2_are_words:
234+
if flags.args_are_xy_values:
235+
argument1 = stream.read_int16()
236+
argument2 = stream.read_int16()
237+
else:
238+
argument1 = stream.read_uint16()
239+
argument2 = stream.read_uint16()
240+
else:
241+
if flags.args_are_xy_values:
242+
argument1 = stream.read_int8()
243+
argument2 = stream.read_int8()
244+
else:
245+
argument1 = stream.read_uint8()
246+
argument2 = stream.read_uint8()
247+
248+
if flags.we_have_a_scale:
249+
scale = stream.read_f2dot14()
250+
transform = scale, 0, 0, scale
251+
elif flags.we_have_an_x_and_y_scale:
252+
x_scale = stream.read_f2dot14()
253+
y_scale = stream.read_f2dot14()
254+
transform = x_scale, 0, 0, y_scale
255+
elif flags.we_have_a_two_by_two:
256+
x_scale = stream.read_f2dot14()
257+
scale_01 = stream.read_f2dot14()
258+
scale_10 = stream.read_f2dot14()
259+
y_scale = stream.read_f2dot14()
260+
transform = x_scale, scale_01, scale_10, y_scale
261+
else:
262+
transform = None
263+
264+
if flags.args_are_xy_values:
265+
components.append(XyGlyphComponent(
266+
glyph_index,
267+
argument1,
268+
argument2,
269+
flags.round_xy_to_grid,
270+
flags.scaled_component_offset,
271+
flags.unscaled_component_offset,
272+
transform,
273+
flags.use_my_metrics,
274+
))
275+
else:
276+
components.append(PointsGlyphComponent(
277+
glyph_index,
278+
argument1,
279+
argument2,
280+
transform,
281+
flags.use_my_metrics,
282+
))
283+
284+
if not flags.more_components:
285+
if flags.we_have_instructions:
286+
we_have_instructions = True
287+
break
288+
289+
if we_have_instructions:
290+
instruction_length = stream.read_uint16()
291+
instructions = stream.read(instruction_length)
292+
else:
293+
instructions = b''
294+
295+
return ComponentGlyph(
296+
x_min,
297+
y_min,
298+
x_max,
299+
y_max,
300+
components,
301+
instructions,
302+
overlap_compound,
303+
)
304+
305+
x_min: int
306+
y_min: int
307+
x_max: int
308+
y_max: int
309+
components: list[XyGlyphComponent | PointsGlyphComponent]
310+
instructions: bytes
311+
overlap_compound: bool
312+
313+
def __init__(
314+
self,
315+
x_min: int = 0,
316+
y_min: int = 0,
317+
x_max: int = 0,
318+
y_max: int = 0,
319+
components: list[XyGlyphComponent | PointsGlyphComponent] | None = None,
320+
instructions: bytes = b'',
321+
overlap_compound: bool = False,
322+
):
323+
self.x_min = x_min
324+
self.y_min = y_min
325+
self.x_max = x_max
326+
self.y_max = y_max
327+
self.components = [] if components is None else components
328+
self.instructions = instructions
329+
self.overlap_compound = overlap_compound
330+
331+
def copy(self) -> 'ComponentGlyph':
332+
return ComponentGlyph(
333+
self.x_min,
334+
self.y_min,
335+
self.x_max,
336+
self.y_max,
337+
[component.copy() for component in self.components],
338+
self.instructions,
339+
self.overlap_compound,
340+
)
341+
342+
def dump(self) -> bytes:
343+
buffer = BytesIO()
344+
stream = Stream(buffer)
345+
346+
# TODO
347+
348+
return buffer.getvalue()

0 commit comments

Comments
 (0)