|
16 | 16 | from fontTools import ttLib |
17 | 17 |
|
18 | 18 |
|
19 | | -__all__ = ["subroutinize", "Error"] |
| 19 | +__all__ = ["subroutinize", "desubroutinize", "has_subroutines", "Error"] |
20 | 20 |
|
21 | 21 |
|
22 | 22 | try: |
@@ -117,15 +117,18 @@ def _tx_subroutinize(data: bytes, output_format: str = CFFTableTag.CFF) -> bytes |
117 | 117 | return output_data |
118 | 118 |
|
119 | 119 |
|
120 | | -def _sniff_cff_table_format(otf: ttLib.TTFont) -> Optional[CFFTableTag]: |
121 | | - return next( |
| 120 | +def _sniff_cff_table_format(otf: ttLib.TTFont) -> CFFTableTag: |
| 121 | + cff_tag = next( |
122 | 122 | ( |
123 | 123 | CFFTableTag(tag) |
124 | 124 | for tag in otf.keys() |
125 | 125 | if tag in CFFTableTag.__members__.values() |
126 | 126 | ), |
127 | 127 | None, |
128 | 128 | ) |
| 129 | + if not cff_tag: |
| 130 | + raise Error("Invalid OTF: no 'CFF ' or 'CFF2' tables found") |
| 131 | + return cff_tag |
129 | 132 |
|
130 | 133 |
|
131 | 134 | def subroutinize( |
@@ -158,8 +161,6 @@ def subroutinize( |
158 | 161 | or if subroutinization process fails. |
159 | 162 | """ |
160 | 163 | input_format = _sniff_cff_table_format(otf) |
161 | | - if not input_format: |
162 | | - raise Error("Invalid OTF: no 'CFF ' or 'CFF2' tables found") |
163 | 164 |
|
164 | 165 | if cff_version is None: |
165 | 166 | output_format = input_format |
@@ -197,3 +198,49 @@ def subroutinize( |
197 | 198 | post_table.glyphOrder = glyphOrder |
198 | 199 |
|
199 | 200 | return otf |
| 201 | + |
| 202 | + |
| 203 | +def has_subroutines(otf: ttLib.TTFont) -> bool: |
| 204 | + """Return True if the font's CFF or CFF2 table contains any subroutines.""" |
| 205 | + table_tag = _sniff_cff_table_format(otf) |
| 206 | + top_dict = otf[table_tag].cff.topDictIndex[0] |
| 207 | + all_subrs = [top_dict.GlobalSubrs] |
| 208 | + if hasattr(top_dict, "FDArray"): |
| 209 | + all_subrs.extend( |
| 210 | + fd.Private.Subrs for fd in top_dict.FDArray if hasattr(fd.Private, "Subrs") |
| 211 | + ) |
| 212 | + elif hasattr(top_dict.Private, "Subrs"): |
| 213 | + all_subrs.append(top_dict.Private.Subrs) |
| 214 | + return any(all_subrs) |
| 215 | + |
| 216 | + |
| 217 | +def desubroutinize(otf: ttLib.TTFont, inplace=True) -> ttLib.TTFont: |
| 218 | + """Remove all subroutines from the font. |
| 219 | +
|
| 220 | + Args: |
| 221 | + otf (ttLib.TTFont): the input font object. |
| 222 | + inplace (bool): whether to create a copy or modify the input font. By default |
| 223 | + the input font is modified. |
| 224 | +
|
| 225 | + Returns: |
| 226 | + The modified font containing the desubroutinized CFF or CFF2 table. |
| 227 | + This will be a different TTFont object if inplace=False. |
| 228 | +
|
| 229 | + Raises: |
| 230 | + cffsubr.Error if the font doesn't contain 'CFF ' or 'CFF2' table, |
| 231 | + or if desubroutinization process fails. |
| 232 | + """ |
| 233 | + # the 'desubroutinize' method is dynamically added to the CFF table class |
| 234 | + # as a side-effect of importing the fontTools.subset.cff module... |
| 235 | + from fontTools.subset import cff as _ |
| 236 | + |
| 237 | + if not inplace: |
| 238 | + otf = copy.deepcopy(otf) |
| 239 | + |
| 240 | + table_tag = _sniff_cff_table_format(otf) |
| 241 | + try: |
| 242 | + otf[table_tag].desubroutinize() |
| 243 | + except Exception as e: |
| 244 | + raise Error("Desubroutinization failed") from e |
| 245 | + |
| 246 | + return otf |
0 commit comments