Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
gen_test_font.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2#
3# Copyright 2013 The Flutter Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7import sys, math
8from operator import itemgetter
9from itertools import groupby
10import unicodedata
11import fontforge
12
13NAME = "FlutterTest"
14# Turn off auto-hinting and enable manual hinting. FreeType skips auto-hinting
15# if the font's family name is in a hard-coded "tricky" font list.
16TRICKY_NAME = "MingLiU"
17EM = 1024
18DESCENT = -EM // 4
19ASCENT = EM + DESCENT
20# -143 and 20 are the underline location and width Ahem uses.
21UPOS = -143 * 1000 // EM
22UWIDTH = 20 * 1000 // EM
23
24### Font Metadata and Metrics
25
26font = fontforge.font()
27font.familyname = TRICKY_NAME
28font.fullname = NAME
29font.fontname = NAME
30
31# This sets the relevant fields in the os2 table and hhea table.
32font.ascent = ASCENT
33font.descent = -DESCENT
34font.upos = UPOS
35font.uwidth = UWIDTH
36font.hhea_linegap = 0
37font.os2_typolinegap = 0
38
39font.horizontalBaseline = (
40 ("hang", "ideo", "romn"),
41 (
42 ("latn", "romn", (ASCENT, DESCENT, 0), ()),
43 ("grek", "romn", (ASCENT, DESCENT, 0), ()),
44 ("hani", "ideo", (ASCENT, DESCENT, 0), ()),
45 ),
46)
47
48### TrueType Hinting
49
50# Hints are ignored on macOS.
51#
52# These hints only **vertically** adjust the outlines, for better vertical
53# alignment in golden tests. They don't affect the font or the glyphs' public
54# metrics available to the framework, so they typically don't affect non-golden
55# tests.
56#
57# The hinting goals are:
58#
59# 1. Aligning the key points on glyph outlines between glyphs, when different
60# types of glyphs are placed side by side. E.g., for a given point size, "p"
61# and "É" should never overlap vertically, and "p" and "x" should be
62# bottom-aligned.
63#
64# 2. Aligning the top and the bottom of the "x" glyph with the background. With
65# point size = 14, since the em square's y-extent is 3.5 px (256 * 14 / 1024)
66# below the baseline and 10.5 px above the baseline, the glyph's CBOX will be
67# "rounded out" (3.5 -> 4, 10.5 -> 11). So "x" is going to be misaligned with
68# the background by +0.5 px when rasterized without proper grid-fitting.
69
70# Allocate space in cvt.
71font.cvt = [0]
72# gcd is used to avoid overflowing, this works for the current ASCENT and EM value.
73gcd = math.gcd(ASCENT, EM)
74# The control value program is for computing the y-offset (in pixels) to move
75# the embox's top edge to grid. The end result will be stored to CVT entry 0.
76# CVT[0] = (pointSize * ASCENT / EM) - ceil(pointSize * ASCENT / EM)
77prep_program = f"""
78 RTG
79 PUSHW_1
80 0
81 MPS
82 PUSHW_1
83 {(ASCENT << 6) // gcd}
84 MUL
85 PUSHW_1
86 {EM // gcd}
87 DIV
88 DUP
89 CEILING
90 SUB
91 WCVTP
92"""
93font.setTableData("prep", fontforge.parseTTInstrs(prep_program))
94
95
96def glyph_program(glyph):
97 # Shift Zone 1 by CVT[0]. In FreeType SHZ actually shifts the zone zp2
98 # points to, instead of top of the stack. That's probably a bug.
99 instructions = """
100 SVTCA[0]
101 PUSHB_4
102 0
103 0
104 0
105 0
106 SZPS
107 MIRP[0000]
108 SRP2
109 PUSHB_3
110 1
111 1
112 1
113 SZP2
114 SHZ[0]
115 SZPS
116 """
117
118 # Round To Grid every on-curve point, but ignore those who are on the ASCENT
119 # or DESCENT line. This step keeps "p" (ascent flushed) and "É" (descent
120 # flushed)'s y extents from overlapping each other.
121 for index, point in enumerate([p for contour in glyph.foreground for p in contour]):
122 if point.y not in [ASCENT, DESCENT]:
123 instructions += f"""
124 PUSHB_1
125 {index}
126 MDAP[1]
127 """
128 return fontforge.parseTTInstrs(instructions)
129
130
131### Creating Glyphs Outlines
132
133
134def square_glyph(glyph):
135 pen = glyph.glyphPen()
136 # Counter Clockwise
137 pen.moveTo((0, DESCENT))
138 pen.lineTo((0, ASCENT))
139 pen.lineTo((EM, ASCENT))
140 pen.lineTo((EM, DESCENT))
141 pen.closePath()
142 glyph.ttinstrs = glyph_program(glyph)
143
144
146 pen = glyph.glyphPen()
147 pen.moveTo((0, DESCENT))
148 pen.lineTo((0, 0))
149 pen.lineTo((EM, 0))
150 pen.lineTo((EM, DESCENT))
151 pen.closePath()
152 glyph.ttinstrs = glyph_program(glyph)
153
154
156 pen = glyph.glyphPen()
157 pen.moveTo((0, 0))
158 pen.lineTo((0, ASCENT))
159 pen.lineTo((EM, ASCENT))
160 pen.lineTo((EM, 0))
161 pen.closePath()
162 glyph.ttinstrs = glyph_program(glyph)
163
164
165def not_def_glyph(glyph):
166 pen = glyph.glyphPen()
167 # Counter Clockwise for the outer contour.
168 pen.moveTo((EM // 8, 0))
169 pen.lineTo((EM // 8, ASCENT))
170 pen.lineTo((EM - EM // 8, ASCENT))
171 pen.lineTo((EM - EM // 8, 0))
172 pen.closePath()
173 # Clockwise, inner contour.
174 pen.moveTo((EM // 4, EM // 8))
175 pen.lineTo((EM - EM // 4, EM // 8))
176 pen.lineTo((EM - EM // 4, ASCENT - EM // 8))
177 pen.lineTo((EM // 4, ASCENT - EM // 8))
178 pen.closePath()
179 glyph.ttinstrs = glyph_program(glyph)
180
181
182def unicode_range(fromUnicode, throughUnicode):
183 return range(fromUnicode, throughUnicode + 1)
184
185
186square_codepoints = [
187 codepoint for l in [
188 unicode_range(0x21, 0x26),
189 unicode_range(0x28, 0x6F),
190 unicode_range(0x71, 0x7E),
191 unicode_range(0xA1, 0xC8),
192 unicode_range(0xCA, 0xFF),
193 [0x131],
194 unicode_range(0x152, 0x153),
195 [0x178, 0x192],
196 unicode_range(0x2C6, 0x2C7),
197 [0x2C9],
198 unicode_range(0x2D8, 0x2DD),
199 [0x394, 0x3A5, 0x3A7, 0x3A9, 0x3BC, 0x3C0],
200 unicode_range(0x2013, 0x2014),
201 unicode_range(0x2018, 0x201A),
202 unicode_range(0x201C, 0x201E),
203 unicode_range(0x2020, 0x2022),
204 [0x2026, 0x2030],
205 unicode_range(0x2039, 0x203A),
206 [0x2044, 0x2122, 0x2126, 0x2202, 0x2206, 0x220F],
207 unicode_range(0x2211, 0x2212),
208 unicode_range(0x2219, 0x221A),
209 [0x221E, 0x222B, 0x2248, 0x2260],
210 unicode_range(0x2264, 0x2265),
211 [
212 0x22F2, 0x25CA, 0x3007, 0x4E00, 0x4E03, 0x4E09, 0x4E5D, 0x4E8C, 0x4E94, 0x516B, 0x516D,
213 0x5341, 0x56D7, 0x56DB, 0x571F, 0x6728, 0x6C34, 0x706B, 0x91D1
214 ],
215 unicode_range(0xF000, 0xF002),
216 ] for codepoint in l
217] + [0x70] + [ord(c) for c in "中文测试文本是否正确"]
218
219no_path_codepoints = [
220 #(codepoint, advance %)
221 (0x0020, 1),
222 (0x00A0, 1),
223 (0x2003, 1),
224 (0x3000, 1),
225 (0x2002, 1 / 2),
226 (0x2004, 1 / 3),
227 (0x2005, 1 / 4),
228 (0x2006, 1 / 6),
229 (0x2009, 1 / 5),
230 (0x200A, 1 / 10),
231 (0xFEFF, 0),
232 (0x200B, 0),
233 (0x200C, 0),
234 (0x200D, 0),
235]
236
237
238def create_glyph(name, contour):
239 glyph = font.createChar(-1, name)
240 contour(glyph)
241 glyph.width = EM
242 return glyph
243
244
245if square_codepoints:
246 create_glyph("Square", square_glyph).altuni = square_codepoints
247create_glyph("Ascent Flushed", ascent_flushed_glyph).unicode = 0x70
248create_glyph("Descent Flushed", descent_flushed_glyph).unicode = 0xC9
249create_glyph(".notdef", not_def_glyph).unicode = -1
250
251
252def create_no_path_glyph(codepoint, advance_percentage):
253 name = "Zero Advance" if advance_percentage == 0 else (
254 "Full Advance" if advance_percentage == 1 else f"1/{(int)(1/advance_percentage)} Advance"
255 )
256 no_path_glyph = font.createChar(codepoint, name)
257 no_path_glyph.width = (int)(EM * advance_percentage)
258 return no_path_glyph
259
260
261for (codepoint, advance_percentage) in no_path_codepoints:
262 if (codepoint in square_codepoints):
263 raise ValueError(f"{hex(codepoint)} is occupied.")
264 create_no_path_glyph(codepoint, advance_percentage)
265
266font.generate(sys.argv[1] if len(sys.argv) >= 2 else "test_font.ttf")
267
268### Printing Glyph Map Stats
269
270scripts = set()
271for glyph in font.glyphs():
272 if glyph.unicode >= 0:
273 scripts.add(fontforge.scriptFromUnicode(glyph.unicode))
274 for codepoint, _, _ in glyph.altuni or []:
275 scripts.add(fontforge.scriptFromUnicode(codepoint))
276script_list = list(scripts)
277script_list.sort()
278
279print(f"| \ Script <br />Glyph | {' | '.join(script_list)} |")
280print(" | :--- " + " | :----: " * len(script_list) + "|")
281
282for glyph in font.glyphs():
283 if glyph.unicode < 0 and not glyph.altuni:
284 continue
285 glyph_mapping = {}
286 if glyph.unicode >= 0:
287 glyph_mapping[fontforge.scriptFromUnicode(glyph.unicode)] = [glyph.unicode]
288 for codepoint, _, _ in glyph.altuni or []:
289 script = fontforge.scriptFromUnicode(codepoint)
290 if script in glyph_mapping:
291 glyph_mapping[script].append(codepoint)
292 else:
293 glyph_mapping[script] = [codepoint]
294
295 codepoints_by_script = [glyph_mapping.get(script, []) for script in script_list]
296
298 if not codepoints:
299 return ""
300 codepoints.sort()
301 codepoint_ranges = [
302 list(map(itemgetter(1), group))
303 for key, group in groupby(enumerate(codepoints), lambda x: x[0] - x[1])
304 ]
305 characters = [chr(c) for c in codepoints]
306
307 def map_char(c):
308 if c == "`":
309 return "`` ` ``"
310 if c == "|":
311 return "`\\|`"
312 if c.isprintable() and (not c.isspace()):
313 return f"`{c}`"
314 return "`<" + unicodedata.name(c, hex(ord(c))) + ">`"
315
316 full_list = " ".join([map_char(c) for c in characters])
317 return "**codepoint(s):** " + ", ".join([
318 f"{hex(r[0])}-{hex(r[-1])}" if len(r) > 1 else hex(r[0]) for r in codepoint_ranges
319 ]) + "<br />" + "**character(s):** " + full_list
320
321 print(
322 f"| {glyph.glyphname} | {' | '.join([describe_codepoint_range(l) for l in codepoints_by_script])} |"
323 )
void print(void *str)
Definition bridge.cpp:126
static void append(char **dst, size_t *count, const char *src, size_t n)
Definition editor.cpp:211
ascent_flushed_glyph(glyph)
glyph_program(glyph)
describe_codepoint_range(codepoints)
create_no_path_glyph(codepoint, advance_percentage)
square_glyph(glyph)
Creating Glyphs Outlines.
descent_flushed_glyph(glyph)
create_glyph(name, contour)
unicode_range(fromUnicode, throughUnicode)