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,336 @@
1
+ import itertools
2
+ from collections.abc import Callable, Mapping
3
+
4
+ import pathops
5
+ from fontTools.cffLib import CFFFontSet
6
+ from fontTools.misc.psCharStrings import T2CharString
7
+ from fontTools.misc.roundTools import noRound, otRound
8
+ from fontTools.pens.t2CharStringPen import T2CharStringPen
9
+ from fontTools.pens.ttGlyphPen import TTGlyphPen
10
+ from fontTools.ttLib import TTFont
11
+ from fontTools.ttLib.tables import _g_l_y_f, _h_m_t_x
12
+ from fontTools.ttLib.ttGlyphSet import _TTGlyph
13
+
14
+ from font_converter.constants import T_CFF, T_GLYF, T_HMTX
15
+
16
+ _TTGlyphMapping = Mapping[str, _TTGlyph]
17
+
18
+ __all__ = ["correct_cff_contours", "correct_glyf_contours", "simplify_path"]
19
+
20
+
21
+ class CorrectContoursError(Exception):
22
+ """Raised when an error occurs while correcting the contours of a TrueType font."""
23
+
24
+
25
+ def _skia_path_from_glyph(glyph_name: str, glyph_set: _TTGlyphMapping) -> pathops.Path:
26
+ path = pathops.Path()
27
+ pen = path.getPen(glyphSet=glyph_set)
28
+ glyph_set[glyph_name].draw(pen)
29
+ return path
30
+
31
+
32
+ def _skia_path_from_glyph_component(
33
+ component: _g_l_y_f.GlyphComponent, glyph_set: _TTGlyphMapping
34
+ ) -> pathops.Path:
35
+ base_glyph_name, transformation = component.getComponentInfo()
36
+ path = _skia_path_from_glyph(glyph_name=base_glyph_name, glyph_set=glyph_set)
37
+ return path.transform(*transformation)
38
+
39
+
40
+ def _ttf_components_overlap(glyph: _g_l_y_f.Glyph, glyph_set: _TTGlyphMapping) -> bool:
41
+ if not glyph.isComposite():
42
+ raise ValueError("This method only works with TrueType composite glyphs")
43
+ if len(glyph.components) < 2:
44
+ return False
45
+
46
+ component_paths = {}
47
+
48
+ def _get_nth_component_path(index: int) -> pathops.Path:
49
+ if index not in component_paths:
50
+ component_paths[index] = _skia_path_from_glyph_component(
51
+ glyph.components[index], glyph_set
52
+ )
53
+ return component_paths[index]
54
+
55
+ return any(
56
+ pathops.op(
57
+ _get_nth_component_path(i),
58
+ _get_nth_component_path(j),
59
+ pathops.PathOp.INTERSECTION,
60
+ clockwise=True, # type: ignore
61
+ fix_winding=True, # type: ignore
62
+ )
63
+ for i, j in itertools.combinations(range(len(glyph.components)), 2)
64
+ )
65
+
66
+
67
+ def _tt_glyph_from_skia_path(path: pathops.Path) -> _g_l_y_f.Glyph:
68
+ tt_pen = TTGlyphPen(glyphSet=None)
69
+ path.draw(tt_pen)
70
+ glyph = tt_pen.glyph()
71
+ glyph.recalcBounds(glyfTable=None)
72
+ return glyph
73
+
74
+
75
+ def _t2_charstring_from_skia_path(
76
+ path: pathops.Path,
77
+ charstring: T2CharString,
78
+ ) -> T2CharString:
79
+ # https://github.com/fonttools/fonttools/commit/40b525c1e3cc20b4b64004b8e3224a67adc2adf1
80
+ # The width argument of `T2CharStringPen()` is inserted directly into the CharString
81
+ # program, so it must be relative to Private.nominalWidthX.
82
+ if charstring.width == charstring.private.defaultWidthX:
83
+ width = None
84
+ else:
85
+ width = charstring.width - charstring.private.nominalWidthX
86
+
87
+ t2_pen = T2CharStringPen(width=width, glyphSet=None)
88
+ path.draw(t2_pen)
89
+ charstring = t2_pen.getCharString(charstring.private, charstring.globalSubrs)
90
+ return charstring
91
+
92
+
93
+ def _round_path(path: pathops.Path, rounder: Callable[[float], float] = otRound) -> pathops.Path:
94
+ rounded_path = pathops.Path()
95
+ for verb, points in path:
96
+ rounded_path.add(verb, *((rounder(p[0]), rounder(p[1])) for p in points))
97
+ return rounded_path
98
+
99
+
100
+ def _same_path(path_1: pathops.Path, path_2: pathops.Path) -> bool:
101
+ """
102
+ Checks if two pathops paths are the same.
103
+
104
+ Args:
105
+ path_1 (pathops.Path): The first path
106
+ path_2 (pathops.Path): The second path
107
+
108
+ Returns:
109
+ bool: ``True`` if the paths are the same, ``False`` if the paths are different
110
+ """
111
+
112
+ return {tuple(c) for c in path_1.contours} == {tuple(c) for c in path_2.contours}
113
+
114
+
115
+ def _remove_tiny_paths(path: pathops.Path, min_area: float) -> pathops.Path:
116
+ """
117
+ Removes tiny paths from a ``pathops.Path`` object. A path is considered tiny if its area is less
118
+ than the given minimum area (default is 25).
119
+
120
+ :param path: The path to clean.
121
+ :type path: pathops.Path
122
+ :param min_area: The minimum area of a contour to be considered. Defaults to 25.
123
+ :type min_area: int
124
+ :return: The cleaned path.
125
+ :rtype: pathops.Path
126
+ """
127
+
128
+ cleaned_path = pathops.Path()
129
+ for contour in path.contours:
130
+ if contour.area >= min_area:
131
+ cleaned_path.addPath(contour)
132
+ return cleaned_path
133
+
134
+
135
+ def _correct_tt_glyph_contours(
136
+ glyph_name: str,
137
+ glyph_set: _TTGlyphMapping,
138
+ glyf_table: _g_l_y_f.table__g_l_y_f,
139
+ hmtx_table: _h_m_t_x.table__h_m_t_x,
140
+ remove_hinting: bool = True,
141
+ min_area: int = 25,
142
+ ) -> bool:
143
+ glyph: _g_l_y_f.Glyph = glyf_table[glyph_name]
144
+ # decompose composite glyphs only if components overlap each other
145
+ if (
146
+ glyph.numberOfContours > 0
147
+ or glyph.isComposite()
148
+ and _ttf_components_overlap(glyph=glyph, glyph_set=glyph_set)
149
+ ):
150
+ path = _skia_path_from_glyph(glyph_name, glyph_set)
151
+ path_2 = simplify_path(path, glyph_name, clockwise=True)
152
+ if min_area > 0:
153
+ path_2 = _remove_tiny_paths(path_2, min_area=min_area)
154
+
155
+ if not _same_path(path_1=path, path_2=path_2):
156
+ glyf_table[glyph_name] = glyph = _tt_glyph_from_skia_path(path_2)
157
+ width, lsb = hmtx_table[glyph_name]
158
+ if lsb != glyph.xMin:
159
+ hmtx_table[glyph_name] = (width, glyph.xMin)
160
+ return True
161
+
162
+ if remove_hinting:
163
+ glyph.removeHinting()
164
+ return False
165
+
166
+
167
+ def _correct_charstring_contours(
168
+ glyph_name: str,
169
+ glyph_set: _TTGlyphMapping,
170
+ cff_font_set: CFFFontSet,
171
+ min_area: float = 25,
172
+ ) -> bool:
173
+ try:
174
+ path = _skia_path_from_glyph(glyph_name, glyph_set)
175
+ path_2 = simplify_path(path, glyph_name, clockwise=False)
176
+
177
+ if min_area > 0:
178
+ path_2 = _remove_tiny_paths(path_2, min_area=min_area)
179
+
180
+ if not _same_path(path_1=path, path_2=path_2):
181
+ charstrings = cff_font_set[0].CharStrings
182
+ charstrings[glyph_name] = _t2_charstring_from_skia_path(path_2, charstrings[glyph_name])
183
+ return True
184
+
185
+ return False
186
+ except Exception as e:
187
+ raise CorrectContoursError(f"Failed to correct contours of glyph {glyph_name!r}") from e
188
+
189
+
190
+ def simplify_path(path: pathops.Path, glyph_name: str, clockwise: bool) -> pathops.Path:
191
+ """
192
+ Simplifies a ``pathops.Path`` by removing overlaps and correcting the direction of the contours.
193
+
194
+ :param path: The ``pathops.Path`` to simplify.
195
+ :type path: pathops.Path
196
+ :param glyph_name: The name of the glyph.
197
+ :type glyph_name: str
198
+ :param clockwise: Whether the contours should be clockwise.
199
+ :type clockwise: bool
200
+ :raises CorrectContoursError: If an error occurs while simplifying the path.
201
+ :return: The simplified path.
202
+ :rtype: pathops.Path
203
+ """
204
+
205
+ try:
206
+ return pathops.simplify(path, fix_winding=True, clockwise=clockwise) # type: ignore
207
+ except pathops.PathOpsError:
208
+ pass
209
+
210
+ path = _round_path(path, rounder=noRound)
211
+ try:
212
+ path = pathops.simplify(path, fix_winding=True, clockwise=clockwise) # type: ignore
213
+ return path
214
+ except pathops.PathOpsError as e:
215
+ raise CorrectContoursError(f"Failed to remove overlaps from glyph {glyph_name!r}") from e
216
+
217
+
218
+ def correct_glyf_contours(
219
+ font: TTFont,
220
+ remove_hinting: bool,
221
+ ignore_errors: bool,
222
+ min_area: int = 25,
223
+ ) -> set[str]:
224
+ """
225
+ Corrects the contours of the given TrueType font by removing overlaps, correcting the direction
226
+ of the contours, and removing tiny paths. The function returns the list of modified glyphs.
227
+
228
+ :param font: The ``TTFont`` object.
229
+ :type font: TTFont
230
+ :param remove_hinting: Whether to remove hinting instructions.
231
+ :type remove_hinting: bool
232
+ :param ignore_errors: Whether to ignore errors.
233
+ :type ignore_errors: bool
234
+ :param min_area: The minimum area of a contour to be considered. Defaults to 25.
235
+ :type min_area: int
236
+ :raises CorrectContoursError: If an error occurs while correcting the contours.
237
+ :return: The list of modified glyphs.
238
+ :rtype: set[str]
239
+ """
240
+ glyph_names = font.getGlyphOrder()
241
+ glyph_set = font.getGlyphSet()
242
+ glyf_table = font[T_GLYF]
243
+ hmtx_table = font[T_HMTX]
244
+
245
+ # The minimum area is given in font units, so we need to convert it to em units
246
+ min_area = min_area / 1000 * font["head"].unitsPerEm
247
+
248
+ # process all simple glyphs first, then composites with increasing component depth,
249
+ # so that by the time we test for component intersections the respective base glyphs
250
+ # have already been simplified
251
+ glyph_names = sorted(
252
+ glyph_names,
253
+ key=lambda name: (
254
+ (
255
+ glyf_table[name].getCompositeMaxpValues(glyf_table).maxComponentDepth
256
+ if glyf_table[name].isComposite()
257
+ else 0
258
+ ),
259
+ name,
260
+ ),
261
+ )
262
+ modified_glyphs = set()
263
+ for glyph_name in glyph_names:
264
+ try:
265
+ if _correct_tt_glyph_contours(
266
+ glyph_name=glyph_name,
267
+ glyph_set=glyph_set,
268
+ glyf_table=glyf_table,
269
+ hmtx_table=hmtx_table,
270
+ remove_hinting=remove_hinting,
271
+ min_area=min_area,
272
+ ):
273
+ modified_glyphs.add(glyph_name)
274
+ except CorrectContoursError as e:
275
+ if not ignore_errors:
276
+ raise e
277
+
278
+ return modified_glyphs
279
+
280
+
281
+ def correct_cff_contours(
282
+ font: TTFont,
283
+ remove_hinting: bool,
284
+ ignore_errors: bool,
285
+ remove_unused_subroutines: bool = True,
286
+ min_area: int = 25,
287
+ ) -> set[str]:
288
+ """
289
+ Corrects the contours of the given CFF font by removing overlaps, correcting the direction of
290
+ the contours, and removing tiny paths. The function returns the list of modified glyphs.
291
+
292
+ :param font: The ``TTFont`` object.
293
+ :type font: TTFont
294
+ :param remove_hinting: Whether to remove hinting instructions.
295
+ :type remove_hinting: bool
296
+ :param ignore_errors: Whether to ignore errors.
297
+ :type ignore_errors: bool
298
+ :param remove_unused_subroutines: Whether to remove unused subroutines. Defaults to True.
299
+ :type remove_unused_subroutines: bool
300
+ :param min_area: The minimum area of a contour to be considered. Defaults to 25.
301
+ :type min_area: int
302
+ :raises CorrectContoursError: If an error occurs while correcting the contours.
303
+ :return: The list of modified glyphs.
304
+ :rtype: set[str]
305
+ """
306
+ glyph_names = font.getGlyphOrder()
307
+ glyph_set = font.getGlyphSet()
308
+ cff_font_set: CFFFontSet = font[T_CFF].cff
309
+ modified_glyphs = set()
310
+
311
+ # The minimum area is given in font units, so we need to convert it to em units
312
+ min_area = min_area / 1000 * font["head"].unitsPerEm
313
+
314
+ for glyph_name in glyph_names:
315
+ try:
316
+ if _correct_charstring_contours(
317
+ glyph_name=glyph_name,
318
+ glyph_set=glyph_set,
319
+ cff_font_set=cff_font_set,
320
+ min_area=min_area,
321
+ ):
322
+ modified_glyphs.add(glyph_name)
323
+ except CorrectContoursError as e:
324
+ if not ignore_errors:
325
+ raise e
326
+
327
+ if not modified_glyphs:
328
+ return set()
329
+
330
+ if remove_hinting:
331
+ cff_font_set.remove_hints()
332
+
333
+ if remove_unused_subroutines:
334
+ cff_font_set.remove_unused_subroutines()
335
+
336
+ return modified_glyphs
@@ -0,0 +1,77 @@
1
+ """Generate the CSS (``@font-face`` rules and feature classes) for converted web fonts."""
2
+
3
+ import re
4
+ from collections import defaultdict
5
+ from collections.abc import Iterable
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ CSS_FILE_NAME = "fonts.css"
10
+ _FORMAT_ORDER = {"woff2": 0, "woff": 1}
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class WebFontEntry:
15
+ file: Path
16
+ format: str
17
+ family: str
18
+ weight: int = 400
19
+ italic: bool = False
20
+ features: tuple[str, ...] = ()
21
+
22
+
23
+ def _slug(value: str) -> str:
24
+ return re.sub(r"[^a-z0-9_-]+", "-", value.lower()).strip("-") or "font"
25
+
26
+
27
+ def _quote(value: str) -> str:
28
+ return '"' + value.replace("\\", "\\\\").replace('"', '\\"') + '"'
29
+
30
+
31
+ def build_css(entries: Iterable[WebFontEntry]) -> str:
32
+ """Return the CSS with one ``@font-face`` per font and one class per family and feature."""
33
+ entries = sorted(entries, key=lambda e: (e.family, e.weight, e.italic, e.file.name))
34
+ # The formats of the same font share one rule, with the most efficient format first.
35
+ faces: dict[tuple[str, int, bool, str], list[WebFontEntry]] = {}
36
+ for entry in entries:
37
+ faces.setdefault((entry.family, entry.weight, entry.italic, entry.file.stem), []).append(
38
+ entry
39
+ )
40
+ blocks: list[str] = []
41
+ for (family, weight, italic, _), sources in faces.items():
42
+ sources.sort(key=lambda e: _FORMAT_ORDER.get(e.format, len(_FORMAT_ORDER)))
43
+ src = ",\n ".join(
44
+ f"url({_quote(source.file.name)}) format({_quote(source.format)})" for source in sources
45
+ )
46
+ blocks.append(
47
+ "@font-face {\n"
48
+ f" font-family: {_quote(family)};\n"
49
+ f" src: {src};\n"
50
+ f" font-weight: {weight};\n"
51
+ f" font-style: {'italic' if italic else 'normal'};\n"
52
+ " font-display: swap;\n"
53
+ "}"
54
+ )
55
+ for family in sorted({entry.family for entry in entries}):
56
+ blocks.append(f".family-{_slug(family)} {{ font-family: {_quote(family)}; }}")
57
+ features = sorted({tag for entry in entries for tag in entry.features})
58
+ for tag in features:
59
+ blocks.append(f'.ft-{_slug(tag)} {{ font-feature-settings: "{tag}" 1; }}')
60
+ return "\n\n".join(blocks) + "\n"
61
+
62
+
63
+ def write_css_kits(entries: Iterable[WebFontEntry], overwrite: bool = True) -> list[Path]:
64
+ """Write one CSS file in each folder that contains converted web fonts."""
65
+ by_folder: dict[Path, list[WebFontEntry]] = defaultdict(list)
66
+ for entry in entries:
67
+ by_folder[entry.file.parent].append(entry)
68
+ written: list[Path] = []
69
+ for folder, folder_entries in by_folder.items():
70
+ path = folder / CSS_FILE_NAME
71
+ counter = 1
72
+ while not overwrite and path.exists():
73
+ path = folder / f"{Path(CSS_FILE_NAME).stem}#{counter}.css"
74
+ counter += 1
75
+ path.write_text(build_css(folder_entries), encoding="utf-8")
76
+ written.append(path)
77
+ return written
@@ -0,0 +1,213 @@
1
+ from collections.abc import Callable, Generator
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+
5
+ from fontTools.ttLib.ttCollection import TTCollection
6
+ from fontTools.ttLib.ttFont import TTLibError
7
+
8
+ from font_converter.font import Font
9
+
10
+ __all__ = ["FinderError", "FinderFilter", "FinderOptions", "FontFinder"]
11
+
12
+
13
+ @dataclass
14
+ class FinderOptions:
15
+ """A class that specifies the options to pass to the FontFinder class."""
16
+
17
+ recursive: bool = False
18
+ lazy: bool | None = None
19
+ recalc_bboxes: bool = True
20
+ recalc_timestamp: bool = False
21
+
22
+
23
+ @dataclass
24
+ class FinderFilter:
25
+ """A class that specifies which fonts to filter out when searching for fonts."""
26
+
27
+ filter_out_tt: bool = False
28
+ filter_out_ps: bool = False
29
+ filter_out_woff: bool = False
30
+ filter_out_woff2: bool = False
31
+ filter_out_sfnt: bool = False
32
+ filter_out_static: bool = False
33
+ filter_out_variable: bool = False
34
+
35
+
36
+ class FinderError(Exception):
37
+ """An exception raised by the FontFinder class."""
38
+
39
+
40
+ class FontFinder:
41
+ """
42
+ A class that finds fonts in a given path. It can search for fonts in a directory and its
43
+ subdirectories, and can also handle a single font file.
44
+
45
+ The class allows for filtering based on various criteria such as outline format (TrueType or
46
+ PostScript), font variations (static or variable), and font flavor ('woff', 'woff2' or
47
+ ``None``).
48
+
49
+ The class returns a list or a generator of Font objects that meet the specified criteria.
50
+
51
+ The returned ``Font`` and ``TTCollection`` objects keep their files open until they are closed
52
+ by the caller (``close()`` or a ``with`` block). Prefer the generator methods when processing
53
+ many files, so that only one font at a time is open::
54
+
55
+ for font in finder.generate_fonts():
56
+ with font:
57
+ ...
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ input_path: str | Path,
63
+ options: FinderOptions | None = None,
64
+ filter_: FinderFilter | None = None,
65
+ ) -> None:
66
+ """
67
+ Initializes the ``FontFinder`` class.
68
+
69
+ :param input_path: The file system path to be processed. This path is resolved to an
70
+ absolute path. If the path is invalid, a FinderError is raised.
71
+ :type input_path: Union[str, Path]
72
+ :param options: Options for customizing the behavior of the finder. If not provided,
73
+ defaults to a FinderOptions instance.
74
+ :type options: Optional[FinderOptions]
75
+ :param filter_: Filter criteria for selecting files or directories. If not provided,
76
+ defaults to a FinderFilter instance.
77
+ :type filter_: Optional[FinderFilter]
78
+ """
79
+ try:
80
+ self.input_path = Path(input_path).resolve(strict=True)
81
+ except Exception as e:
82
+ raise FinderError(f"Invalid input path: {input_path}") from e
83
+
84
+ self.filter = filter_ or FinderFilter()
85
+ self.options = options or FinderOptions()
86
+ self._filter_conditions = self._generate_filter_conditions(self.filter)
87
+ self._validate_filter_conditions()
88
+
89
+ def find_fonts(self) -> list[Font]:
90
+ """
91
+ Finds fonts in the given input path.
92
+
93
+ :return: A list of ``Font`` objects generated by the generate_fonts method.
94
+ :rtype: list[Font]
95
+ """
96
+ return list(self.generate_fonts())
97
+
98
+ def find_collections(self) -> list[TTCollection]:
99
+ """
100
+ Finds TTCollections in the given input path.
101
+
102
+ :return: A list of ``TTCollection`` objects generated by the generate_collections method.
103
+ :rtype: list[TTCollection]
104
+ """
105
+ return list(self.generate_collections())
106
+
107
+ def generate_fonts(self) -> Generator[Font, None, None]:
108
+ """
109
+ Generates a sequence of ``Font`` objects from the files generated by the ``_generate_files``
110
+ method. Filters out fonts based on specified conditions in ``_filter_conditions``.
111
+
112
+ :return: A generator yielding Font objects
113
+ :rtype: Generator[Font, None, None]
114
+ """
115
+ # Collect the paths first, so that files saved while iterating are not found again.
116
+ files = list(self._generate_files())
117
+ for file in files:
118
+ try:
119
+ font = Font(
120
+ file,
121
+ lazy=self.options.lazy,
122
+ recalc_timestamp=self.options.recalc_timestamp,
123
+ recalc_bboxes=self.options.recalc_bboxes,
124
+ )
125
+ except (TTLibError, PermissionError):
126
+ continue
127
+ if any(condition and func(font) for condition, func in self._filter_conditions):
128
+ font.close()
129
+ else:
130
+ yield font
131
+
132
+ def generate_collections(self) -> Generator[TTCollection, None, None]:
133
+ """
134
+ Generates a sequence of ``TTCollection`` objects from the files generated by the
135
+ ``_generate_files`` method.
136
+
137
+ :return: A generator yielding TTCollection objects
138
+ :rtype: Generator[TTCollection, None, None]
139
+ """
140
+ files = list(self._generate_files())
141
+ for file in files:
142
+ try:
143
+ ttc = TTCollection(file)
144
+ except (TTLibError, PermissionError):
145
+ continue
146
+ yield ttc
147
+
148
+ def _generate_files(self) -> Generator[Path, None, None]:
149
+ is_file = self.input_path.is_file()
150
+ is_dir = self.input_path.is_dir()
151
+
152
+ if is_file:
153
+ yield self.input_path
154
+ elif is_dir:
155
+ if self.options.recursive:
156
+ yield from (x for x in self.input_path.rglob("*") if x.is_file())
157
+ else:
158
+ yield from (x for x in self.input_path.glob("*") if x.is_file())
159
+
160
+ def _validate_filter_conditions(self) -> None:
161
+ if self.filter.filter_out_tt and self.filter.filter_out_ps:
162
+ raise FinderError("Cannot filter out both TrueType and PostScript fonts.")
163
+ if (
164
+ self.filter.filter_out_woff
165
+ and self.filter.filter_out_woff2
166
+ and self.filter.filter_out_sfnt
167
+ ):
168
+ raise FinderError("Cannot filter out WOFF, WOFF2 and SFNT fonts at the same time.")
169
+ if self.filter.filter_out_static and self.filter.filter_out_variable:
170
+ raise FinderError("Cannot filter out both static and variable fonts.")
171
+
172
+ @staticmethod
173
+ def _generate_filter_conditions(
174
+ filter_: FinderFilter,
175
+ ) -> list[tuple[bool, Callable[[Font], bool]]]:
176
+ conditions: list[tuple[bool, Callable[[Font], bool]]] = [
177
+ (filter_.filter_out_tt, _is_tt),
178
+ (filter_.filter_out_ps, _is_ps),
179
+ (filter_.filter_out_woff, _is_woff),
180
+ (filter_.filter_out_woff2, _is_woff2),
181
+ (filter_.filter_out_sfnt, _is_sfnt),
182
+ (filter_.filter_out_static, _is_static),
183
+ (filter_.filter_out_variable, _is_variable),
184
+ ]
185
+ return conditions
186
+
187
+
188
+ def _is_woff(font: Font) -> bool:
189
+ return font.is_woff
190
+
191
+
192
+ def _is_woff2(font: Font) -> bool:
193
+ return font.is_woff2
194
+
195
+
196
+ def _is_sfnt(font: Font) -> bool:
197
+ return font.is_sfnt
198
+
199
+
200
+ def _is_ps(font: Font) -> bool:
201
+ return font.is_ps
202
+
203
+
204
+ def _is_tt(font: Font) -> bool:
205
+ return font.is_tt
206
+
207
+
208
+ def _is_static(font: Font) -> bool:
209
+ return font.is_static
210
+
211
+
212
+ def _is_variable(font: Font) -> bool:
213
+ return font.is_variable