NotYAFC 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,144 @@
1
+ from typing import Any
2
+
3
+ from fontTools.fontBuilder import FontBuilder
4
+ from fontTools.misc.psCharStrings import T2CharString
5
+ from fontTools.ttLib import TTFont
6
+
7
+ from font_converter.constants import T_CFF, T_HEAD, T_NAME, T_POST
8
+
9
+
10
+ def build_otf(
11
+ font: TTFont,
12
+ charstrings_dict: dict[str, T2CharString],
13
+ ps_name: str | None = None,
14
+ font_info: dict[str, Any] | None = None,
15
+ private_dict: dict[str, Any] | None = None,
16
+ ) -> None:
17
+ """
18
+ Builds an OpenType-PS font with ``fontTools.fontBuilder.FontBuilder``.
19
+
20
+ :param font: The ``TTFont`` object.
21
+ :type font: TTFont
22
+ :param charstrings_dict: The charstrings dictionary.
23
+ :type charstrings_dict: dict
24
+ :param ps_name: The PostScript name of the font. If None, it will be extracted from the font
25
+ metadata.
26
+ :type ps_name: str, optional
27
+ :param font_info: The font info dictionary. If None, it will be extracted from the font
28
+ metadata.
29
+ :param private_dict: The private dict dictionary. If None, it will be extracted from the font
30
+ metadata.
31
+ :type private_dict: dict, optional
32
+ """
33
+
34
+ if not ps_name:
35
+ ps_name = _get_ps_name(font=font)
36
+ if not font_info:
37
+ font_info = _get_font_info_dict(font=font)
38
+ if not private_dict:
39
+ private_dict = _get_private_dict(font=font)
40
+
41
+ fb = FontBuilder(font=font)
42
+ fb.isTTF = False
43
+ _delete_ttf_tables(font=fb.font)
44
+ fb.setupGlyphOrder(font.getGlyphOrder())
45
+ fb.setupCFF(
46
+ psName=ps_name,
47
+ charStringsDict=charstrings_dict,
48
+ fontInfo=font_info,
49
+ privateDict=private_dict,
50
+ )
51
+ metrics = _get_hmtx_values(font=fb.font, charstrings=charstrings_dict)
52
+ fb.setupHorizontalMetrics(metrics)
53
+ fb.setupDummyDSIG()
54
+ fb.setupMaxp()
55
+ post_values = _get_post_values(font=fb.font)
56
+ fb.setupPost(**post_values)
57
+
58
+
59
+ def _delete_ttf_tables(font: TTFont) -> None:
60
+ ttf_tables = ["glyf", "cvt ", "loca", "fpgm", "prep", "gasp", "LTSH", "hdmx"]
61
+ for table in ttf_tables:
62
+ if table in font:
63
+ del font[table]
64
+
65
+
66
+ def _get_ps_name(font: TTFont) -> str:
67
+ if T_CFF not in font:
68
+ return font[T_NAME].getDebugName(6)
69
+
70
+ cff_table = font[T_CFF]
71
+ return cff_table.cff.fontNames[0]
72
+
73
+
74
+ def _get_font_info_dict(font: TTFont) -> dict[str, Any]:
75
+ if T_CFF not in font:
76
+ return _build_font_info_dict(font)
77
+
78
+ cff_table = font[T_CFF]
79
+ return {
80
+ key: value
81
+ for key, value in cff_table.cff.topDictIndex[0].rawDict.items()
82
+ if key not in ("FontBBox", "charset", "Encoding", "Private", "CharStrings")
83
+ }
84
+
85
+
86
+ def _build_font_info_dict(font: TTFont) -> dict[str, Any]:
87
+ font_revision = str(round(font[T_HEAD].fontRevision, 3)).split(".")
88
+ major_version = str(font_revision[0])
89
+ minor_version = str(font_revision[1]).ljust(3, "0")
90
+
91
+ name_table = font[T_NAME]
92
+ post_table = font[T_POST]
93
+ cff_font_info = {
94
+ "version": ".".join([major_version, str(int(minor_version))]),
95
+ "FullName": name_table.getBestFullName(),
96
+ "FamilyName": name_table.getBestFamilyName(),
97
+ "ItalicAngle": post_table.italicAngle,
98
+ "UnderlinePosition": post_table.underlinePosition,
99
+ "UnderlineThickness": post_table.underlineThickness,
100
+ "isFixedPitch": bool(post_table.isFixedPitch),
101
+ }
102
+
103
+ return cff_font_info
104
+
105
+
106
+ def _get_private_dict(font: TTFont) -> dict[str, Any]:
107
+ if T_CFF not in font:
108
+ return {}
109
+
110
+ cff_table = font[T_CFF]
111
+ return {
112
+ key: value
113
+ for key, value in cff_table.cff.topDictIndex[0].Private.rawDict.items()
114
+ if key not in ("Subrs", "defaultWidthX", "nominalWidthX")
115
+ }
116
+
117
+
118
+ def _get_hmtx_values(
119
+ font: TTFont, charstrings: dict[str, T2CharString]
120
+ ) -> dict[str, tuple[int, int]]:
121
+ glyph_set = font.getGlyphSet()
122
+ advance_widths = {k: v.width for k, v in glyph_set.items()}
123
+ lsb = {}
124
+ for gn, cs in charstrings.items():
125
+ lsb[gn] = cs.calcBounds(None)[0] if cs.calcBounds(None) is not None else 0
126
+ metrics = {}
127
+ for gn, advance_width in advance_widths.items():
128
+ metrics[gn] = (advance_width, lsb[gn])
129
+ return metrics
130
+
131
+
132
+ def _get_post_values(font: TTFont) -> dict[str, Any]:
133
+ post_table = font[T_POST]
134
+ post_info = {
135
+ "italicAngle": round(post_table.italicAngle),
136
+ "underlinePosition": post_table.underlinePosition,
137
+ "underlineThickness": post_table.underlineThickness,
138
+ "isFixedPitch": post_table.isFixedPitch,
139
+ "minMemType42": post_table.minMemType42,
140
+ "maxMemType42": post_table.maxMemType42,
141
+ "minMemType1": post_table.minMemType1,
142
+ "maxMemType1": post_table.maxMemType1,
143
+ }
144
+ return post_info
@@ -0,0 +1,117 @@
1
+ from collections.abc import Mapping
2
+
3
+ import pathops
4
+ from fontTools.cffLib import PrivateDict
5
+ from fontTools.misc.psCharStrings import T2CharString
6
+ from fontTools.pens.cu2quPen import Cu2QuPen
7
+ from fontTools.pens.qu2cuPen import Qu2CuPen
8
+ from fontTools.pens.t2CharStringPen import T2CharStringPen
9
+ from fontTools.pens.ttGlyphPen import TTGlyphPen
10
+ from fontTools.ttLib import TTFont
11
+ from fontTools.ttLib.ttGlyphSet import _TTGlyph
12
+
13
+ from font_converter.contours import simplify_path
14
+ from font_converter.merge_curves import merge_cubic_segments
15
+
16
+ __all__ = ["quadratics_to_cubics", "quadratics_to_cubics_2"]
17
+
18
+
19
+ _TTGlyphMapping = Mapping[str, _TTGlyph]
20
+
21
+
22
+ def _skia_path_from_charstring(charstring: T2CharString) -> pathops.Path:
23
+ path = pathops.Path()
24
+ path_pen = path.getPen(glyphSet=None)
25
+ charstring.draw(path_pen)
26
+ return path
27
+
28
+
29
+ def _charstring_from_skia_path(path: pathops.Path, width: int) -> T2CharString:
30
+ t2_pen = T2CharStringPen(width=width, glyphSet=None)
31
+ path.draw(t2_pen)
32
+ return t2_pen.getCharString()
33
+
34
+
35
+ def quadratics_to_cubics(
36
+ font: TTFont, tolerance: float = 1.0, optimize_contours: int = 0
37
+ ) -> dict[str, T2CharString]:
38
+ """
39
+ Converts quadratic Bézier splines to cubic curves with a specified tolerance using the
40
+ ``Qu2CuPen``, then removes overlaps and corrects the winding direction of the contours.
41
+
42
+ :param font: The TTFont object representing the font to process.
43
+ :type font: TTFont
44
+ :param tolerance: The maximum acceptable deviation in font units when approximating the
45
+ quadratic Bézier curves. Default is 1.0.
46
+ :type tolerance: float
47
+ :param optimize_contours: Merges adjacent smooth cubic segments to reduce the number of
48
+ nodes. The value is the maximum deviation of a merge, as a multiple of ``tolerance``;
49
+ 0 (default) disables the optimization.
50
+ :type optimize_contours: int
51
+ :return: A dictionary mapping glyph names to their corresponding T2CharString objects after
52
+ conversion.
53
+ :rtype: dict[str, T2CharString]
54
+ """
55
+
56
+ qu2cu_charstrings = {}
57
+ glyph_set = font.getGlyphSet()
58
+
59
+ for k, v in glyph_set.items():
60
+ width = v.width
61
+
62
+ try:
63
+ t2_pen = T2CharStringPen(width=width, glyphSet={k: v})
64
+ qu2cu_pen = Qu2CuPen(t2_pen, max_err=tolerance, all_cubic=True, reverse_direction=True)
65
+ glyph_set[k].draw(qu2cu_pen)
66
+ qu2cu_charstrings[k] = t2_pen.getCharString()
67
+
68
+ except NotImplementedError:
69
+ temp_t2_pen = T2CharStringPen(width=width, glyphSet=None)
70
+ glyph_set[k].draw(temp_t2_pen)
71
+ t2_charstring = temp_t2_pen.getCharString()
72
+ t2_charstring.private = PrivateDict()
73
+
74
+ tt_pen = TTGlyphPen(glyphSet=None)
75
+ cu2qu_pen = Cu2QuPen(other_pen=tt_pen, max_err=tolerance, reverse_direction=False)
76
+ t2_charstring.draw(cu2qu_pen)
77
+ tt_glyph = tt_pen.glyph()
78
+
79
+ t2_pen = T2CharStringPen(width=width, glyphSet=None)
80
+ qu2cu_pen = Qu2CuPen(t2_pen, max_err=tolerance, all_cubic=True, reverse_direction=True)
81
+ tt_glyph.draw(pen=qu2cu_pen, glyfTable=None)
82
+
83
+ charstring = t2_pen.getCharString()
84
+ charstring.private = PrivateDict()
85
+ path = _skia_path_from_charstring(charstring)
86
+ simplified_path = simplify_path(path, glyph_name=k, clockwise=False)
87
+ if optimize_contours > 0:
88
+ simplified_path = merge_cubic_segments(
89
+ simplified_path,
90
+ tolerance=tolerance * optimize_contours,
91
+ min_extremum_distance=tolerance,
92
+ )
93
+ qu2cu_charstrings[k] = _charstring_from_skia_path(path=simplified_path, width=width)
94
+
95
+ return qu2cu_charstrings
96
+
97
+
98
+ def quadratics_to_cubics_2(font: TTFont) -> dict[str, T2CharString]:
99
+ """
100
+ Converts quadratic Bézier splines to cubic curves using the ``T2CharStringPen``.
101
+
102
+ :param font: The ``TTFont`` object representing the font to process.
103
+ :type font: TTFont
104
+ :return: A dictionary mapping glyph names to their corresponding T2CharString objects after
105
+ conversion.
106
+ :rtype: dict[str, T2CharString]
107
+ """
108
+ t2_charstrings = {}
109
+ glyph_set = font.getGlyphSet()
110
+
111
+ for k, v in glyph_set.items():
112
+ t2_pen = T2CharStringPen(v.width, glyphSet=glyph_set)
113
+ glyph_set[k].draw(t2_pen)
114
+ charstring = t2_pen.getCharString()
115
+ t2_charstrings[k] = charstring
116
+
117
+ return t2_charstrings