Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/fontTools/ttLib/ttFont.py: 54%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

968 statements  

1from __future__ import annotations 

2 

3import logging 

4import os 

5import traceback 

6from io import BytesIO, StringIO, UnsupportedOperation 

7from typing import TYPE_CHECKING, TypedDict, TypeVar, overload 

8 

9from fontTools.config import Config 

10from fontTools.misc import xmlWriter 

11from fontTools.misc.configTools import AbstractConfig 

12from fontTools.misc.loggingTools import deprecateArgument 

13from fontTools.misc.textTools import Tag, byteord, tostr 

14from fontTools.ttLib import TTLibError 

15from fontTools.ttLib.sfnt import SFNTReader, SFNTWriter 

16from fontTools.ttLib.ttGlyphSet import ( 

17 _TTGlyph, # noqa: F401 

18 _TTGlyphSet, 

19 _TTGlyphSetCFF, 

20 _TTGlyphSetGlyf, 

21 _TTGlyphSetVARC, 

22) 

23 

24if TYPE_CHECKING: 

25 from collections.abc import Mapping, MutableMapping 

26 from types import ModuleType, TracebackType 

27 from typing import Any, BinaryIO, Literal, Sequence, TextIO 

28 

29 from typing_extensions import Self, Unpack 

30 

31 from fontTools.ttLib.tables import ( 

32 B_A_S_E_, 

33 C_B_D_T_, 

34 C_B_L_C_, 

35 C_F_F_, 

36 C_F_F__2, 

37 C_O_L_R_, 

38 C_P_A_L_, 

39 D_S_I_G_, 

40 E_B_D_T_, 

41 E_B_L_C_, 

42 F_F_T_M_, 

43 G_D_E_F_, 

44 G_M_A_P_, 

45 G_P_K_G_, 

46 G_P_O_S_, 

47 G_S_U_B_, 

48 G_V_A_R_, 

49 H_V_A_R_, 

50 J_S_T_F_, 

51 L_T_S_H_, 

52 M_A_T_H_, 

53 M_E_T_A_, 

54 M_V_A_R_, 

55 S_I_N_G_, 

56 S_T_A_T_, 

57 S_V_G_, 

58 T_S_I__0, 

59 T_S_I__1, 

60 T_S_I__2, 

61 T_S_I__3, 

62 T_S_I__5, 

63 T_S_I_B_, 

64 T_S_I_C_, 

65 T_S_I_D_, 

66 T_S_I_J_, 

67 T_S_I_P_, 

68 T_S_I_S_, 

69 T_S_I_V_, 

70 T_T_F_A_, 

71 V_A_R_C_, 

72 V_D_M_X_, 

73 V_O_R_G_, 

74 V_V_A_R_, 

75 D__e_b_g, 

76 F__e_a_t, 

77 G__l_a_t, 

78 G__l_o_c, 

79 O_S_2f_2, 

80 S__i_l_f, 

81 S__i_l_l, 

82 _a_n_k_r, 

83 _a_v_a_r, 

84 _b_s_l_n, 

85 _c_i_d_g, 

86 _c_m_a_p, 

87 _c_v_a_r, 

88 _c_v_t, 

89 _f_e_a_t, 

90 _f_p_g_m, 

91 _f_v_a_r, 

92 _g_a_s_p, 

93 _g_c_i_d, 

94 _g_l_y_f, 

95 _g_v_a_r, 

96 _h_d_m_x, 

97 _h_e_a_d, 

98 _h_h_e_a, 

99 _h_m_t_x, 

100 _k_e_r_n, 

101 _l_c_a_r, 

102 _l_o_c_a, 

103 _l_t_a_g, 

104 _m_a_x_p, 

105 _m_e_t_a, 

106 _m_o_r_t, 

107 _m_o_r_x, 

108 _n_a_m_e, 

109 _o_p_b_d, 

110 _p_o_s_t, 

111 _p_r_e_p, 

112 _p_r_o_p, 

113 _s_b_i_x, 

114 _t_r_a_k, 

115 _v_h_e_a, 

116 _v_m_t_x, 

117 ) 

118 from fontTools.ttLib.tables.DefaultTable import DefaultTable 

119 

120 _VT_co = TypeVar("_VT_co", covariant=True) # Value type covariant containers. 

121 

122log = logging.getLogger(__name__) 

123 

124 

125_NumberT = TypeVar("_NumberT", bound=float) 

126 

127 

128class TTFont(object): 

129 """Represents a TrueType font. 

130 

131 The object manages file input and output, and offers a convenient way of 

132 accessing tables. Tables will be only decompiled when necessary, ie. when 

133 they're actually accessed. This means that simple operations can be extremely fast. 

134 

135 Example usage: 

136 

137 .. code-block:: pycon 

138 

139 >>> 

140 >> from fontTools import ttLib 

141 >> tt = ttLib.TTFont("afont.ttf") # Load an existing font file 

142 >> tt['maxp'].numGlyphs 

143 242 

144 >> tt['OS/2'].achVendID 

145 'B&H\000' 

146 >> tt['head'].unitsPerEm 

147 2048 

148 

149 For details of the objects returned when accessing each table, see the 

150 :doc:`tables </ttLib/tables>` documentation. 

151 To add a table to the font, use the :py:func:`newTable` function: 

152 

153 .. code-block:: pycon 

154 

155 >>> 

156 >> os2 = newTable("OS/2") 

157 >> os2.version = 4 

158 >> # set other attributes 

159 >> font["OS/2"] = os2 

160 

161 TrueType fonts can also be serialized to and from XML format (see also the 

162 :doc:`ttx </ttx>` binary): 

163 

164 .. code-block:: pycon 

165 

166 >> 

167 >> tt.saveXML("afont.ttx") 

168 Dumping 'LTSH' table... 

169 Dumping 'OS/2' table... 

170 [...] 

171 

172 >> tt2 = ttLib.TTFont() # Create a new font object 

173 >> tt2.importXML("afont.ttx") 

174 >> tt2['maxp'].numGlyphs 

175 242 

176 

177 The TTFont object may be used as a context manager; this will cause the file 

178 reader to be closed after the context ``with`` block is exited:: 

179 

180 with TTFont(filename) as f: 

181 # Do stuff 

182 

183 Args: 

184 file: When reading a font from disk, either a pathname pointing to a file, 

185 or a readable file object. 

186 res_name_or_index: If running on a Macintosh, either a sfnt resource name or 

187 an sfnt resource index number. If the index number is zero, TTLib will 

188 autodetect whether the file is a flat file or a suitcase. (If it is a suitcase, 

189 only the first 'sfnt' resource will be read.) 

190 sfntVersion (str): When constructing a font object from scratch, sets the four-byte 

191 sfnt magic number to be used. Defaults to ``\0\1\0\0`` (TrueType). To create 

192 an OpenType file, use ``OTTO``. 

193 flavor (str): Set this to ``woff`` when creating a WOFF file or ``woff2`` for a WOFF2 

194 file. 

195 checkChecksums (int): How checksum data should be treated. Default is 0 

196 (no checking). Set to 1 to check and warn on wrong checksums; set to 2 to 

197 raise an exception if any wrong checksums are found. 

198 recalcBBoxes (bool): If true (the default), recalculates ``glyf``, ``CFF ``, 

199 ``head`` bounding box values and ``hhea``/``vhea`` min/max values on save. 

200 Also compiles the glyphs on importing, which saves memory consumption and 

201 time. 

202 ignoreDecompileErrors (bool): If true, exceptions raised during table decompilation 

203 will be ignored, and the binary data will be returned for those tables instead. 

204 recalcTimestamp (bool): If true (the default), sets the ``modified`` timestamp in 

205 the ``head`` table on save. 

206 fontNumber (int): The index of the font in a TrueType Collection file. 

207 lazy (bool): If lazy is set to True, many data structures are loaded lazily, upon 

208 access only. If it is set to False, many data structures are loaded immediately. 

209 The default is ``lazy=None`` which is somewhere in between. 

210 """ 

211 

212 tables: dict[Tag, DefaultTable | GlyphOrder] 

213 reader: SFNTReader | None 

214 sfntVersion: str 

215 flavor: str | None 

216 flavorData: Any | None 

217 lazy: bool | None 

218 recalcBBoxes: bool 

219 recalcTimestamp: bool 

220 ignoreDecompileErrors: bool 

221 cfg: AbstractConfig 

222 glyphOrder: list[str] 

223 _reverseGlyphOrderDict: dict[str, int] 

224 _tableCache: MutableMapping[tuple[Tag, bytes], DefaultTable] | None 

225 disassembleInstructions: bool 

226 bitmapGlyphDataFormat: str 

227 # Deprecated attributes 

228 verbose: bool | None 

229 quiet: bool | None 

230 

231 def __init__( 

232 self, 

233 file: str | os.PathLike[str] | BinaryIO | None = None, 

234 res_name_or_index: str | int | None = None, 

235 sfntVersion: str = "\000\001\000\000", 

236 flavor: str | None = None, 

237 checkChecksums: int = 0, 

238 verbose: bool | None = None, # Deprecated 

239 recalcBBoxes: bool = True, 

240 allowVID: Any = NotImplemented, # Deprecated/Unused 

241 ignoreDecompileErrors: bool = False, 

242 recalcTimestamp: bool = True, 

243 fontNumber: int = -1, 

244 lazy: bool | None = None, 

245 quiet: bool | None = None, # Deprecated 

246 _tableCache: MutableMapping[tuple[Tag, bytes], DefaultTable] | None = None, 

247 cfg: Mapping[str, Any] | AbstractConfig = {}, 

248 ) -> None: 

249 # Set deprecated attributes 

250 for name in ("verbose", "quiet"): 

251 val = locals().get(name) 

252 if val is not None: 

253 deprecateArgument(name, "configure logging instead") 

254 setattr(self, name, val) 

255 

256 self.lazy = lazy 

257 self.recalcBBoxes = recalcBBoxes 

258 self.recalcTimestamp = recalcTimestamp 

259 self.tables = {} 

260 self.reader = None 

261 self.cfg = cfg.copy() if isinstance(cfg, AbstractConfig) else Config(cfg) 

262 self.ignoreDecompileErrors = ignoreDecompileErrors 

263 

264 if not file: 

265 self.sfntVersion = sfntVersion 

266 self.flavor = flavor 

267 self.flavorData = None 

268 return 

269 seekable = True 

270 if not hasattr(file, "read"): 

271 if not isinstance(file, (str, os.PathLike)): 

272 raise TypeError( 

273 "fileOrPath must be a file path (str or PathLike) if it isn't an object with a `read` method." 

274 ) 

275 closeStream = True 

276 # assume file is a string 

277 if res_name_or_index is not None: 

278 # see if it contains 'sfnt' resources in the resource or data fork 

279 from . import macUtils 

280 

281 if res_name_or_index == 0: 

282 if macUtils.getSFNTResIndices(file): 

283 # get the first available sfnt font. 

284 file = macUtils.SFNTResourceReader(file, 1) 

285 else: 

286 file = open(file, "rb") 

287 else: 

288 file = macUtils.SFNTResourceReader(file, res_name_or_index) 

289 else: 

290 file = open(file, "rb") 

291 else: 

292 # assume "file" is a readable file object 

293 assert not isinstance(file, (str, os.PathLike)) 

294 closeStream = False 

295 # SFNTReader wants the input file to be seekable. 

296 # SpooledTemporaryFile has no seekable() on < 3.11, but still can seek: 

297 # https://github.com/fonttools/fonttools/issues/3052 

298 if hasattr(file, "seekable"): 

299 seekable = file.seekable() 

300 elif hasattr(file, "seek"): 

301 try: 

302 file.seek(0) 

303 except UnsupportedOperation: 

304 seekable = False 

305 

306 if not self.lazy: 

307 # read input file in memory and wrap a stream around it to allow overwriting 

308 if seekable: 

309 file.seek(0) 

310 tmp = BytesIO(file.read()) 

311 if hasattr(file, "name"): 

312 # save reference to input file name 

313 tmp.name = file.name 

314 if closeStream: 

315 file.close() 

316 file = tmp 

317 elif not seekable: 

318 raise TTLibError("Input file must be seekable when lazy=True") 

319 self._tableCache = _tableCache 

320 self.reader = SFNTReader(file, checkChecksums, fontNumber=fontNumber) 

321 self.sfntVersion = self.reader.sfntVersion 

322 self.flavor = self.reader.flavor 

323 self.flavorData = self.reader.flavorData 

324 

325 def __enter__(self) -> Self: 

326 return self 

327 

328 def __exit__( 

329 self, 

330 exc_type: type[BaseException] | None, 

331 exc_value: BaseException | None, 

332 traceback: TracebackType | None, 

333 ) -> None: 

334 self.close() 

335 

336 def close(self) -> None: 

337 """If we still have a reader object, close it.""" 

338 if self.reader is not None: 

339 self.reader.close() 

340 self.reader = None 

341 

342 def save( 

343 self, file: str | os.PathLike[str] | BinaryIO, reorderTables: bool | None = True 

344 ) -> None: 

345 """Save the font to disk. 

346 

347 Args: 

348 file: Similarly to the constructor, can be either a pathname or a writable 

349 binary file object. 

350 reorderTables (Option[bool]): If true (the default), reorder the tables, 

351 sorting them by tag (recommended by the OpenType specification). If 

352 false, retain the original font order. If None, reorder by table 

353 dependency (fastest). 

354 """ 

355 if not hasattr(file, "write"): 

356 if self.lazy and self.reader.file.name == file: 

357 raise TTLibError("Can't overwrite TTFont when 'lazy' attribute is True") 

358 createStream = True 

359 else: 

360 # assume "file" is a writable file object 

361 createStream = False 

362 

363 tmp = BytesIO() 

364 

365 writer_reordersTables = self._save(tmp) 

366 

367 if not ( 

368 reorderTables is None 

369 or writer_reordersTables 

370 or (reorderTables is False and self.reader is None) 

371 ): 

372 if reorderTables is False: 

373 # sort tables using the original font's order 

374 if self.reader is None: 

375 raise TTLibError( 

376 "The original table order is unavailable because there isn't a font to read it from." 

377 ) 

378 tableOrder = list(self.reader.keys()) 

379 else: 

380 # use the recommended order from the OpenType specification 

381 tableOrder = None 

382 tmp.flush() 

383 tmp2 = BytesIO() 

384 reorderFontTables(tmp, tmp2, tableOrder) 

385 tmp.close() 

386 tmp = tmp2 

387 

388 if createStream: 

389 # "file" is a path 

390 assert isinstance(file, (str, os.PathLike)) 

391 with open(file, "wb") as file: 

392 file.write(tmp.getvalue()) 

393 else: 

394 assert not isinstance(file, (str, os.PathLike)) 

395 file.write(tmp.getvalue()) 

396 

397 tmp.close() 

398 

399 def _save( 

400 self, 

401 file: BinaryIO, 

402 tableCache: MutableMapping[tuple[Tag, bytes], Any] | None = None, 

403 ) -> bool: 

404 """Internal function, to be shared by save() and TTCollection.save()""" 

405 

406 if self.recalcTimestamp and "head" in self: 

407 # make sure 'head' is loaded so the recalculation is actually done 

408 self["head"] 

409 

410 tags = self.keys() 

411 tags.pop(0) # skip GlyphOrder tag 

412 numTables = len(tags) 

413 # write to a temporary stream to allow saving to unseekable streams 

414 writer = SFNTWriter( 

415 file, numTables, self.sfntVersion, self.flavor, self.flavorData 

416 ) 

417 

418 done = [] 

419 for tag in tags: 

420 self._writeTable(tag, writer, done, tableCache) 

421 

422 writer.close() 

423 

424 return writer.reordersTables() 

425 

426 class XMLSavingOptions(TypedDict): 

427 writeVersion: bool 

428 quiet: bool | None 

429 tables: Sequence[str | bytes] | None 

430 skipTables: Sequence[str] | None 

431 splitTables: bool 

432 splitGlyphs: bool 

433 disassembleInstructions: bool 

434 bitmapGlyphDataFormat: str 

435 

436 def saveXML( 

437 self, 

438 fileOrPath: str | os.PathLike[str] | BinaryIO | TextIO, 

439 newlinestr: str = "\n", 

440 **kwargs: Unpack[XMLSavingOptions], 

441 ) -> None: 

442 """Export the font as TTX (an XML-based text file), or as a series of text 

443 files when splitTables is true. In the latter case, the 'fileOrPath' 

444 argument should be a path to a directory. 

445 The 'tables' argument must either be false (dump all tables) or a 

446 list of tables to dump. The 'skipTables' argument may be a list of tables 

447 to skip, but only when the 'tables' argument is false. 

448 """ 

449 

450 writer = xmlWriter.XMLWriter(fileOrPath, newlinestr=newlinestr) 

451 self._saveXML(writer, **kwargs) 

452 writer.close() 

453 

454 def _saveXML( 

455 self, 

456 writer: xmlWriter.XMLWriter, 

457 writeVersion: bool = True, 

458 quiet: bool | None = None, # Deprecated 

459 tables: Sequence[str | bytes] | None = None, 

460 skipTables: Sequence[str] | None = None, 

461 splitTables: bool = False, 

462 splitGlyphs: bool = False, 

463 disassembleInstructions: bool = True, 

464 bitmapGlyphDataFormat: str = "raw", 

465 ) -> None: 

466 if quiet is not None: 

467 deprecateArgument("quiet", "configure logging instead") 

468 

469 self.disassembleInstructions = disassembleInstructions 

470 self.bitmapGlyphDataFormat = bitmapGlyphDataFormat 

471 if not tables: 

472 tables = self.keys() 

473 if skipTables: 

474 tables = [tag for tag in tables if tag not in skipTables] 

475 

476 if writeVersion: 

477 from fontTools import version 

478 

479 version = ".".join(version.split(".")[:2]) 

480 writer.begintag( 

481 "ttFont", 

482 sfntVersion=repr(tostr(self.sfntVersion))[1:-1], 

483 ttLibVersion=version, 

484 ) 

485 else: 

486 writer.begintag("ttFont", sfntVersion=repr(tostr(self.sfntVersion))[1:-1]) 

487 writer.newline() 

488 

489 # always splitTables if splitGlyphs is enabled 

490 splitTables = splitTables or splitGlyphs 

491 

492 if not splitTables: 

493 writer.newline() 

494 else: 

495 if writer.filename is None: 

496 raise TTLibError( 

497 "splitTables requires the file name to be a file system path, not a stream." 

498 ) 

499 path, ext = os.path.splitext(writer.filename) 

500 

501 for tag in tables: 

502 if splitTables: 

503 tablePath = path + "." + tagToIdentifier(tag) + ext 

504 tableWriter = xmlWriter.XMLWriter( 

505 tablePath, newlinestr=writer.newlinestr 

506 ) 

507 tableWriter.begintag("ttFont", ttLibVersion=version) 

508 tableWriter.newline() 

509 tableWriter.newline() 

510 writer.simpletag(tagToXML(tag), src=os.path.basename(tablePath)) 

511 writer.newline() 

512 else: 

513 tableWriter = writer 

514 self._tableToXML(tableWriter, tag, splitGlyphs=splitGlyphs) 

515 if splitTables: 

516 tableWriter.endtag("ttFont") 

517 tableWriter.newline() 

518 tableWriter.close() 

519 writer.endtag("ttFont") 

520 writer.newline() 

521 

522 def _tableToXML( 

523 self, 

524 writer: xmlWriter.XMLWriter, 

525 tag: str | bytes, 

526 quiet: bool | None = None, 

527 splitGlyphs: bool = False, 

528 ) -> None: 

529 if quiet is not None: 

530 deprecateArgument("quiet", "configure logging instead") 

531 if tag in self: 

532 table = self[tag] 

533 report = "Dumping '%s' table..." % tag 

534 else: 

535 report = "No '%s' table found." % tag 

536 log.info(report) 

537 if tag not in self: 

538 return 

539 xmlTag = tagToXML(tag) 

540 attrs: dict[str, Any] = {} 

541 if hasattr(table, "ERROR"): 

542 attrs["ERROR"] = "decompilation error" 

543 from .tables.DefaultTable import DefaultTable 

544 

545 if table.__class__ == DefaultTable: 

546 attrs["raw"] = True 

547 writer.begintag(xmlTag, **attrs) 

548 writer.newline() 

549 if tag == "glyf": 

550 table.toXML(writer, self, splitGlyphs=splitGlyphs) 

551 else: 

552 table.toXML(writer, self) 

553 writer.endtag(xmlTag) 

554 writer.newline() 

555 writer.newline() 

556 

557 def importXML( 

558 self, fileOrPath: str | os.PathLike[str] | BinaryIO, quiet: bool | None = None 

559 ) -> None: 

560 """Import a TTX file (an XML-based text format), so as to recreate 

561 a font object. 

562 """ 

563 if quiet is not None: 

564 deprecateArgument("quiet", "configure logging instead") 

565 

566 if "maxp" in self and "post" in self: 

567 # Make sure the glyph order is loaded, as it otherwise gets 

568 # lost if the XML doesn't contain the glyph order, yet does 

569 # contain the table which was originally used to extract the 

570 # glyph names from (ie. 'post', 'cmap' or 'CFF '). 

571 self.getGlyphOrder() 

572 

573 from fontTools.misc import xmlReader 

574 

575 reader = xmlReader.XMLReader(fileOrPath, self) 

576 reader.read() 

577 

578 def isLoaded(self, tag: str | bytes) -> bool: 

579 """Return true if the table identified by ``tag`` has been 

580 decompiled and loaded into memory.""" 

581 return tag in self.tables 

582 

583 def has_key(self, tag: str | bytes) -> bool: 

584 """Test if the table identified by ``tag`` is present in the font. 

585 

586 As well as this method, ``tag in font`` can also be used to determine the 

587 presence of the table.""" 

588 if self.isLoaded(tag): 

589 return True 

590 elif self.reader and tag in self.reader: 

591 return True 

592 elif tag == "GlyphOrder": 

593 return True 

594 else: 

595 return False 

596 

597 __contains__ = has_key 

598 

599 def keys(self) -> list[str]: 

600 """Returns the list of tables in the font, along with the ``GlyphOrder`` pseudo-table.""" 

601 keys = list(self.tables.keys()) 

602 if self.reader: 

603 for key in list(self.reader.keys()): 

604 if key not in keys: 

605 keys.append(key) 

606 

607 if "GlyphOrder" in keys: 

608 keys.remove("GlyphOrder") 

609 keys = sortedTagList(keys) 

610 return ["GlyphOrder"] + keys 

611 

612 def ensureDecompiled(self, recurse: bool | None = None) -> None: 

613 """Decompile all the tables, even if a TTFont was opened in 'lazy' mode.""" 

614 for tag in self.keys(): 

615 table = self[tag] 

616 if recurse is None: 

617 recurse = self.lazy is not False 

618 if recurse and hasattr(table, "ensureDecompiled"): 

619 table.ensureDecompiled(recurse=recurse) 

620 self.lazy = False 

621 

622 def __len__(self) -> int: 

623 return len(list(self.keys())) 

624 

625 @overload 

626 def __getitem__(self, tag: Literal["BASE"]) -> B_A_S_E_.table_B_A_S_E_: ... 

627 @overload 

628 def __getitem__(self, tag: Literal["CBDT"]) -> C_B_D_T_.table_C_B_D_T_: ... 

629 @overload 

630 def __getitem__(self, tag: Literal["CBLC"]) -> C_B_L_C_.table_C_B_L_C_: ... 

631 @overload 

632 def __getitem__(self, tag: Literal["CFF "]) -> C_F_F_.table_C_F_F_: ... 

633 @overload 

634 def __getitem__(self, tag: Literal["CFF2"]) -> C_F_F__2.table_C_F_F__2: ... 

635 @overload 

636 def __getitem__(self, tag: Literal["COLR"]) -> C_O_L_R_.table_C_O_L_R_: ... 

637 @overload 

638 def __getitem__(self, tag: Literal["CPAL"]) -> C_P_A_L_.table_C_P_A_L_: ... 

639 @overload 

640 def __getitem__(self, tag: Literal["DSIG"]) -> D_S_I_G_.table_D_S_I_G_: ... 

641 @overload 

642 def __getitem__(self, tag: Literal["EBDT"]) -> E_B_D_T_.table_E_B_D_T_: ... 

643 @overload 

644 def __getitem__(self, tag: Literal["EBLC"]) -> E_B_L_C_.table_E_B_L_C_: ... 

645 @overload 

646 def __getitem__(self, tag: Literal["FFTM"]) -> F_F_T_M_.table_F_F_T_M_: ... 

647 @overload 

648 def __getitem__(self, tag: Literal["GDEF"]) -> G_D_E_F_.table_G_D_E_F_: ... 

649 @overload 

650 def __getitem__(self, tag: Literal["GMAP"]) -> G_M_A_P_.table_G_M_A_P_: ... 

651 @overload 

652 def __getitem__(self, tag: Literal["GPKG"]) -> G_P_K_G_.table_G_P_K_G_: ... 

653 @overload 

654 def __getitem__(self, tag: Literal["GPOS"]) -> G_P_O_S_.table_G_P_O_S_: ... 

655 @overload 

656 def __getitem__(self, tag: Literal["GSUB"]) -> G_S_U_B_.table_G_S_U_B_: ... 

657 @overload 

658 def __getitem__(self, tag: Literal["GVAR"]) -> G_V_A_R_.table_G_V_A_R_: ... 

659 @overload 

660 def __getitem__(self, tag: Literal["HVAR"]) -> H_V_A_R_.table_H_V_A_R_: ... 

661 @overload 

662 def __getitem__(self, tag: Literal["JSTF"]) -> J_S_T_F_.table_J_S_T_F_: ... 

663 @overload 

664 def __getitem__(self, tag: Literal["LTSH"]) -> L_T_S_H_.table_L_T_S_H_: ... 

665 @overload 

666 def __getitem__(self, tag: Literal["MATH"]) -> M_A_T_H_.table_M_A_T_H_: ... 

667 @overload 

668 def __getitem__(self, tag: Literal["META"]) -> M_E_T_A_.table_M_E_T_A_: ... 

669 @overload 

670 def __getitem__(self, tag: Literal["MVAR"]) -> M_V_A_R_.table_M_V_A_R_: ... 

671 @overload 

672 def __getitem__(self, tag: Literal["SING"]) -> S_I_N_G_.table_S_I_N_G_: ... 

673 @overload 

674 def __getitem__(self, tag: Literal["STAT"]) -> S_T_A_T_.table_S_T_A_T_: ... 

675 @overload 

676 def __getitem__(self, tag: Literal["SVG "]) -> S_V_G_.table_S_V_G_: ... 

677 @overload 

678 def __getitem__(self, tag: Literal["TSI0"]) -> T_S_I__0.table_T_S_I__0: ... 

679 @overload 

680 def __getitem__(self, tag: Literal["TSI1"]) -> T_S_I__1.table_T_S_I__1: ... 

681 @overload 

682 def __getitem__(self, tag: Literal["TSI2"]) -> T_S_I__2.table_T_S_I__2: ... 

683 @overload 

684 def __getitem__(self, tag: Literal["TSI3"]) -> T_S_I__3.table_T_S_I__3: ... 

685 @overload 

686 def __getitem__(self, tag: Literal["TSI5"]) -> T_S_I__5.table_T_S_I__5: ... 

687 @overload 

688 def __getitem__(self, tag: Literal["TSIB"]) -> T_S_I_B_.table_T_S_I_B_: ... 

689 @overload 

690 def __getitem__(self, tag: Literal["TSIC"]) -> T_S_I_C_.table_T_S_I_C_: ... 

691 @overload 

692 def __getitem__(self, tag: Literal["TSID"]) -> T_S_I_D_.table_T_S_I_D_: ... 

693 @overload 

694 def __getitem__(self, tag: Literal["TSIJ"]) -> T_S_I_J_.table_T_S_I_J_: ... 

695 @overload 

696 def __getitem__(self, tag: Literal["TSIP"]) -> T_S_I_P_.table_T_S_I_P_: ... 

697 @overload 

698 def __getitem__(self, tag: Literal["TSIS"]) -> T_S_I_S_.table_T_S_I_S_: ... 

699 @overload 

700 def __getitem__(self, tag: Literal["TSIV"]) -> T_S_I_V_.table_T_S_I_V_: ... 

701 @overload 

702 def __getitem__(self, tag: Literal["TTFA"]) -> T_T_F_A_.table_T_T_F_A_: ... 

703 @overload 

704 def __getitem__(self, tag: Literal["VARC"]) -> V_A_R_C_.table_V_A_R_C_: ... 

705 @overload 

706 def __getitem__(self, tag: Literal["VDMX"]) -> V_D_M_X_.table_V_D_M_X_: ... 

707 @overload 

708 def __getitem__(self, tag: Literal["VORG"]) -> V_O_R_G_.table_V_O_R_G_: ... 

709 @overload 

710 def __getitem__(self, tag: Literal["VVAR"]) -> V_V_A_R_.table_V_V_A_R_: ... 

711 @overload 

712 def __getitem__(self, tag: Literal["Debg"]) -> D__e_b_g.table_D__e_b_g: ... 

713 @overload 

714 def __getitem__(self, tag: Literal["Feat"]) -> F__e_a_t.table_F__e_a_t: ... 

715 @overload 

716 def __getitem__(self, tag: Literal["Glat"]) -> G__l_a_t.table_G__l_a_t: ... 

717 @overload 

718 def __getitem__(self, tag: Literal["Gloc"]) -> G__l_o_c.table_G__l_o_c: ... 

719 @overload 

720 def __getitem__(self, tag: Literal["OS/2"]) -> O_S_2f_2.table_O_S_2f_2: ... 

721 @overload 

722 def __getitem__(self, tag: Literal["Silf"]) -> S__i_l_f.table_S__i_l_f: ... 

723 @overload 

724 def __getitem__(self, tag: Literal["Sill"]) -> S__i_l_l.table_S__i_l_l: ... 

725 @overload 

726 def __getitem__(self, tag: Literal["ankr"]) -> _a_n_k_r.table__a_n_k_r: ... 

727 @overload 

728 def __getitem__(self, tag: Literal["avar"]) -> _a_v_a_r.table__a_v_a_r: ... 

729 @overload 

730 def __getitem__(self, tag: Literal["bsln"]) -> _b_s_l_n.table__b_s_l_n: ... 

731 @overload 

732 def __getitem__(self, tag: Literal["cidg"]) -> _c_i_d_g.table__c_i_d_g: ... 

733 @overload 

734 def __getitem__(self, tag: Literal["cmap"]) -> _c_m_a_p.table__c_m_a_p: ... 

735 @overload 

736 def __getitem__(self, tag: Literal["cvar"]) -> _c_v_a_r.table__c_v_a_r: ... 

737 @overload 

738 def __getitem__(self, tag: Literal["cvt "]) -> _c_v_t.table__c_v_t: ... 

739 @overload 

740 def __getitem__(self, tag: Literal["feat"]) -> _f_e_a_t.table__f_e_a_t: ... 

741 @overload 

742 def __getitem__(self, tag: Literal["fpgm"]) -> _f_p_g_m.table__f_p_g_m: ... 

743 @overload 

744 def __getitem__(self, tag: Literal["fvar"]) -> _f_v_a_r.table__f_v_a_r: ... 

745 @overload 

746 def __getitem__(self, tag: Literal["gasp"]) -> _g_a_s_p.table__g_a_s_p: ... 

747 @overload 

748 def __getitem__(self, tag: Literal["gcid"]) -> _g_c_i_d.table__g_c_i_d: ... 

749 @overload 

750 def __getitem__(self, tag: Literal["glyf"]) -> _g_l_y_f.table__g_l_y_f: ... 

751 @overload 

752 def __getitem__(self, tag: Literal["gvar"]) -> _g_v_a_r.table__g_v_a_r: ... 

753 @overload 

754 def __getitem__(self, tag: Literal["hdmx"]) -> _h_d_m_x.table__h_d_m_x: ... 

755 @overload 

756 def __getitem__(self, tag: Literal["head"]) -> _h_e_a_d.table__h_e_a_d: ... 

757 @overload 

758 def __getitem__(self, tag: Literal["hhea"]) -> _h_h_e_a.table__h_h_e_a: ... 

759 @overload 

760 def __getitem__(self, tag: Literal["hmtx"]) -> _h_m_t_x.table__h_m_t_x: ... 

761 @overload 

762 def __getitem__(self, tag: Literal["kern"]) -> _k_e_r_n.table__k_e_r_n: ... 

763 @overload 

764 def __getitem__(self, tag: Literal["lcar"]) -> _l_c_a_r.table__l_c_a_r: ... 

765 @overload 

766 def __getitem__(self, tag: Literal["loca"]) -> _l_o_c_a.table__l_o_c_a: ... 

767 @overload 

768 def __getitem__(self, tag: Literal["ltag"]) -> _l_t_a_g.table__l_t_a_g: ... 

769 @overload 

770 def __getitem__(self, tag: Literal["maxp"]) -> _m_a_x_p.table__m_a_x_p: ... 

771 @overload 

772 def __getitem__(self, tag: Literal["meta"]) -> _m_e_t_a.table__m_e_t_a: ... 

773 @overload 

774 def __getitem__(self, tag: Literal["mort"]) -> _m_o_r_t.table__m_o_r_t: ... 

775 @overload 

776 def __getitem__(self, tag: Literal["morx"]) -> _m_o_r_x.table__m_o_r_x: ... 

777 @overload 

778 def __getitem__(self, tag: Literal["name"]) -> _n_a_m_e.table__n_a_m_e: ... 

779 @overload 

780 def __getitem__(self, tag: Literal["opbd"]) -> _o_p_b_d.table__o_p_b_d: ... 

781 @overload 

782 def __getitem__(self, tag: Literal["post"]) -> _p_o_s_t.table__p_o_s_t: ... 

783 @overload 

784 def __getitem__(self, tag: Literal["prep"]) -> _p_r_e_p.table__p_r_e_p: ... 

785 @overload 

786 def __getitem__(self, tag: Literal["prop"]) -> _p_r_o_p.table__p_r_o_p: ... 

787 @overload 

788 def __getitem__(self, tag: Literal["sbix"]) -> _s_b_i_x.table__s_b_i_x: ... 

789 @overload 

790 def __getitem__(self, tag: Literal["trak"]) -> _t_r_a_k.table__t_r_a_k: ... 

791 @overload 

792 def __getitem__(self, tag: Literal["vhea"]) -> _v_h_e_a.table__v_h_e_a: ... 

793 @overload 

794 def __getitem__(self, tag: Literal["vmtx"]) -> _v_m_t_x.table__v_m_t_x: ... 

795 @overload 

796 def __getitem__(self, tag: Literal["GlyphOrder"]) -> GlyphOrder: ... 

797 @overload 

798 def __getitem__(self, tag: str | bytes) -> DefaultTable | GlyphOrder: ... 

799 

800 def __getitem__(self, tag: str | bytes) -> DefaultTable | GlyphOrder: 

801 tag = Tag(tag) 

802 table = self.tables.get(tag) 

803 if table is None: 

804 if tag == "GlyphOrder": 

805 table = GlyphOrder(tag) 

806 self.tables[tag] = table 

807 elif self.reader is not None: 

808 table = self._readTable(tag) 

809 else: 

810 raise KeyError("'%s' table not found" % tag) 

811 return table 

812 

813 def _readTable(self, tag: Tag) -> DefaultTable: 

814 log.debug("Reading '%s' table from disk", tag) 

815 assert self.reader is not None 

816 data = self.reader[tag] 

817 if self._tableCache is not None: 

818 table = self._tableCache.get((tag, data)) 

819 if table is not None: 

820 return table 

821 tableClass = getTableClass(tag) 

822 table = tableClass(tag) 

823 self.tables[tag] = table 

824 log.debug("Decompiling '%s' table", tag) 

825 try: 

826 table.decompile(data, self) 

827 except Exception: 

828 if not self.ignoreDecompileErrors: 

829 raise 

830 # fall back to DefaultTable, retaining the binary table data 

831 log.exception( 

832 "An exception occurred during the decompilation of the '%s' table", tag 

833 ) 

834 from .tables.DefaultTable import DefaultTable 

835 

836 file = StringIO() 

837 traceback.print_exc(file=file) 

838 table = DefaultTable(tag) 

839 table.ERROR = file.getvalue() 

840 self.tables[tag] = table 

841 table.decompile(data, self) 

842 if self._tableCache is not None: 

843 self._tableCache[(tag, data)] = table 

844 return table 

845 

846 def __setitem__(self, tag: str | bytes, table: DefaultTable) -> None: 

847 self.tables[Tag(tag)] = table 

848 

849 def __delitem__(self, tag: str | bytes) -> None: 

850 if tag not in self: 

851 raise KeyError("'%s' table not found" % tag) 

852 if tag in self.tables: 

853 del self.tables[tag] 

854 if self.reader and tag in self.reader: 

855 del self.reader[tag] 

856 

857 @overload 

858 def get(self, tag: Literal["BASE"]) -> B_A_S_E_.table_B_A_S_E_ | None: ... 

859 @overload 

860 def get(self, tag: Literal["CBDT"]) -> C_B_D_T_.table_C_B_D_T_ | None: ... 

861 @overload 

862 def get(self, tag: Literal["CBLC"]) -> C_B_L_C_.table_C_B_L_C_ | None: ... 

863 @overload 

864 def get(self, tag: Literal["CFF "]) -> C_F_F_.table_C_F_F_ | None: ... 

865 @overload 

866 def get(self, tag: Literal["CFF2"]) -> C_F_F__2.table_C_F_F__2 | None: ... 

867 @overload 

868 def get(self, tag: Literal["COLR"]) -> C_O_L_R_.table_C_O_L_R_ | None: ... 

869 @overload 

870 def get(self, tag: Literal["CPAL"]) -> C_P_A_L_.table_C_P_A_L_ | None: ... 

871 @overload 

872 def get(self, tag: Literal["DSIG"]) -> D_S_I_G_.table_D_S_I_G_ | None: ... 

873 @overload 

874 def get(self, tag: Literal["EBDT"]) -> E_B_D_T_.table_E_B_D_T_ | None: ... 

875 @overload 

876 def get(self, tag: Literal["EBLC"]) -> E_B_L_C_.table_E_B_L_C_ | None: ... 

877 @overload 

878 def get(self, tag: Literal["FFTM"]) -> F_F_T_M_.table_F_F_T_M_ | None: ... 

879 @overload 

880 def get(self, tag: Literal["GDEF"]) -> G_D_E_F_.table_G_D_E_F_ | None: ... 

881 @overload 

882 def get(self, tag: Literal["GMAP"]) -> G_M_A_P_.table_G_M_A_P_ | None: ... 

883 @overload 

884 def get(self, tag: Literal["GPKG"]) -> G_P_K_G_.table_G_P_K_G_ | None: ... 

885 @overload 

886 def get(self, tag: Literal["GPOS"]) -> G_P_O_S_.table_G_P_O_S_ | None: ... 

887 @overload 

888 def get(self, tag: Literal["GSUB"]) -> G_S_U_B_.table_G_S_U_B_ | None: ... 

889 @overload 

890 def get(self, tag: Literal["GVAR"]) -> G_V_A_R_.table_G_V_A_R_ | None: ... 

891 @overload 

892 def get(self, tag: Literal["HVAR"]) -> H_V_A_R_.table_H_V_A_R_ | None: ... 

893 @overload 

894 def get(self, tag: Literal["JSTF"]) -> J_S_T_F_.table_J_S_T_F_ | None: ... 

895 @overload 

896 def get(self, tag: Literal["LTSH"]) -> L_T_S_H_.table_L_T_S_H_ | None: ... 

897 @overload 

898 def get(self, tag: Literal["MATH"]) -> M_A_T_H_.table_M_A_T_H_ | None: ... 

899 @overload 

900 def get(self, tag: Literal["META"]) -> M_E_T_A_.table_M_E_T_A_ | None: ... 

901 @overload 

902 def get(self, tag: Literal["MVAR"]) -> M_V_A_R_.table_M_V_A_R_ | None: ... 

903 @overload 

904 def get(self, tag: Literal["SING"]) -> S_I_N_G_.table_S_I_N_G_ | None: ... 

905 @overload 

906 def get(self, tag: Literal["STAT"]) -> S_T_A_T_.table_S_T_A_T_ | None: ... 

907 @overload 

908 def get(self, tag: Literal["SVG "]) -> S_V_G_.table_S_V_G_ | None: ... 

909 @overload 

910 def get(self, tag: Literal["TSI0"]) -> T_S_I__0.table_T_S_I__0 | None: ... 

911 @overload 

912 def get(self, tag: Literal["TSI1"]) -> T_S_I__1.table_T_S_I__1 | None: ... 

913 @overload 

914 def get(self, tag: Literal["TSI2"]) -> T_S_I__2.table_T_S_I__2 | None: ... 

915 @overload 

916 def get(self, tag: Literal["TSI3"]) -> T_S_I__3.table_T_S_I__3 | None: ... 

917 @overload 

918 def get(self, tag: Literal["TSI5"]) -> T_S_I__5.table_T_S_I__5 | None: ... 

919 @overload 

920 def get(self, tag: Literal["TSIB"]) -> T_S_I_B_.table_T_S_I_B_ | None: ... 

921 @overload 

922 def get(self, tag: Literal["TSIC"]) -> T_S_I_C_.table_T_S_I_C_ | None: ... 

923 @overload 

924 def get(self, tag: Literal["TSID"]) -> T_S_I_D_.table_T_S_I_D_ | None: ... 

925 @overload 

926 def get(self, tag: Literal["TSIJ"]) -> T_S_I_J_.table_T_S_I_J_ | None: ... 

927 @overload 

928 def get(self, tag: Literal["TSIP"]) -> T_S_I_P_.table_T_S_I_P_ | None: ... 

929 @overload 

930 def get(self, tag: Literal["TSIS"]) -> T_S_I_S_.table_T_S_I_S_ | None: ... 

931 @overload 

932 def get(self, tag: Literal["TSIV"]) -> T_S_I_V_.table_T_S_I_V_ | None: ... 

933 @overload 

934 def get(self, tag: Literal["TTFA"]) -> T_T_F_A_.table_T_T_F_A_ | None: ... 

935 @overload 

936 def get(self, tag: Literal["VARC"]) -> V_A_R_C_.table_V_A_R_C_ | None: ... 

937 @overload 

938 def get(self, tag: Literal["VDMX"]) -> V_D_M_X_.table_V_D_M_X_ | None: ... 

939 @overload 

940 def get(self, tag: Literal["VORG"]) -> V_O_R_G_.table_V_O_R_G_ | None: ... 

941 @overload 

942 def get(self, tag: Literal["VVAR"]) -> V_V_A_R_.table_V_V_A_R_ | None: ... 

943 @overload 

944 def get(self, tag: Literal["Debg"]) -> D__e_b_g.table_D__e_b_g | None: ... 

945 @overload 

946 def get(self, tag: Literal["Feat"]) -> F__e_a_t.table_F__e_a_t | None: ... 

947 @overload 

948 def get(self, tag: Literal["Glat"]) -> G__l_a_t.table_G__l_a_t | None: ... 

949 @overload 

950 def get(self, tag: Literal["Gloc"]) -> G__l_o_c.table_G__l_o_c | None: ... 

951 @overload 

952 def get(self, tag: Literal["OS/2"]) -> O_S_2f_2.table_O_S_2f_2 | None: ... 

953 @overload 

954 def get(self, tag: Literal["Silf"]) -> S__i_l_f.table_S__i_l_f | None: ... 

955 @overload 

956 def get(self, tag: Literal["Sill"]) -> S__i_l_l.table_S__i_l_l | None: ... 

957 @overload 

958 def get(self, tag: Literal["ankr"]) -> _a_n_k_r.table__a_n_k_r | None: ... 

959 @overload 

960 def get(self, tag: Literal["avar"]) -> _a_v_a_r.table__a_v_a_r | None: ... 

961 @overload 

962 def get(self, tag: Literal["bsln"]) -> _b_s_l_n.table__b_s_l_n | None: ... 

963 @overload 

964 def get(self, tag: Literal["cidg"]) -> _c_i_d_g.table__c_i_d_g | None: ... 

965 @overload 

966 def get(self, tag: Literal["cmap"]) -> _c_m_a_p.table__c_m_a_p | None: ... 

967 @overload 

968 def get(self, tag: Literal["cvar"]) -> _c_v_a_r.table__c_v_a_r | None: ... 

969 @overload 

970 def get(self, tag: Literal["cvt "]) -> _c_v_t.table__c_v_t | None: ... 

971 @overload 

972 def get(self, tag: Literal["feat"]) -> _f_e_a_t.table__f_e_a_t | None: ... 

973 @overload 

974 def get(self, tag: Literal["fpgm"]) -> _f_p_g_m.table__f_p_g_m | None: ... 

975 @overload 

976 def get(self, tag: Literal["fvar"]) -> _f_v_a_r.table__f_v_a_r | None: ... 

977 @overload 

978 def get(self, tag: Literal["gasp"]) -> _g_a_s_p.table__g_a_s_p | None: ... 

979 @overload 

980 def get(self, tag: Literal["gcid"]) -> _g_c_i_d.table__g_c_i_d | None: ... 

981 @overload 

982 def get(self, tag: Literal["glyf"]) -> _g_l_y_f.table__g_l_y_f | None: ... 

983 @overload 

984 def get(self, tag: Literal["gvar"]) -> _g_v_a_r.table__g_v_a_r | None: ... 

985 @overload 

986 def get(self, tag: Literal["hdmx"]) -> _h_d_m_x.table__h_d_m_x | None: ... 

987 @overload 

988 def get(self, tag: Literal["head"]) -> _h_e_a_d.table__h_e_a_d | None: ... 

989 @overload 

990 def get(self, tag: Literal["hhea"]) -> _h_h_e_a.table__h_h_e_a | None: ... 

991 @overload 

992 def get(self, tag: Literal["hmtx"]) -> _h_m_t_x.table__h_m_t_x | None: ... 

993 @overload 

994 def get(self, tag: Literal["kern"]) -> _k_e_r_n.table__k_e_r_n | None: ... 

995 @overload 

996 def get(self, tag: Literal["lcar"]) -> _l_c_a_r.table__l_c_a_r | None: ... 

997 @overload 

998 def get(self, tag: Literal["loca"]) -> _l_o_c_a.table__l_o_c_a | None: ... 

999 @overload 

1000 def get(self, tag: Literal["ltag"]) -> _l_t_a_g.table__l_t_a_g | None: ... 

1001 @overload 

1002 def get(self, tag: Literal["maxp"]) -> _m_a_x_p.table__m_a_x_p | None: ... 

1003 @overload 

1004 def get(self, tag: Literal["meta"]) -> _m_e_t_a.table__m_e_t_a | None: ... 

1005 @overload 

1006 def get(self, tag: Literal["mort"]) -> _m_o_r_t.table__m_o_r_t | None: ... 

1007 @overload 

1008 def get(self, tag: Literal["morx"]) -> _m_o_r_x.table__m_o_r_x | None: ... 

1009 @overload 

1010 def get(self, tag: Literal["name"]) -> _n_a_m_e.table__n_a_m_e | None: ... 

1011 @overload 

1012 def get(self, tag: Literal["opbd"]) -> _o_p_b_d.table__o_p_b_d | None: ... 

1013 @overload 

1014 def get(self, tag: Literal["post"]) -> _p_o_s_t.table__p_o_s_t | None: ... 

1015 @overload 

1016 def get(self, tag: Literal["prep"]) -> _p_r_e_p.table__p_r_e_p | None: ... 

1017 @overload 

1018 def get(self, tag: Literal["prop"]) -> _p_r_o_p.table__p_r_o_p | None: ... 

1019 @overload 

1020 def get(self, tag: Literal["sbix"]) -> _s_b_i_x.table__s_b_i_x | None: ... 

1021 @overload 

1022 def get(self, tag: Literal["trak"]) -> _t_r_a_k.table__t_r_a_k | None: ... 

1023 @overload 

1024 def get(self, tag: Literal["vhea"]) -> _v_h_e_a.table__v_h_e_a | None: ... 

1025 @overload 

1026 def get(self, tag: Literal["vmtx"]) -> _v_m_t_x.table__v_m_t_x | None: ... 

1027 @overload 

1028 def get(self, tag: Literal["GlyphOrder"]) -> GlyphOrder: ... 

1029 @overload 

1030 def get(self, tag: str | bytes) -> DefaultTable | GlyphOrder | Any | None: ... 

1031 @overload 

1032 def get( 

1033 self, tag: str | bytes, default: _VT_co 

1034 ) -> DefaultTable | GlyphOrder | Any | _VT_co: ... 

1035 

1036 def get( 

1037 self, tag: str | bytes, default: Any | None = None 

1038 ) -> DefaultTable | GlyphOrder | Any | None: 

1039 """Returns the table if it exists or (optionally) a default if it doesn't.""" 

1040 try: 

1041 return self[tag] 

1042 except KeyError: 

1043 return default 

1044 

1045 def setGlyphOrder(self, glyphOrder: list[str]) -> None: 

1046 """Set the glyph order 

1047 

1048 Args: 

1049 glyphOrder ([str]): List of glyph names in order. 

1050 """ 

1051 self.glyphOrder = glyphOrder 

1052 if hasattr(self, "_reverseGlyphOrderDict"): 

1053 del self._reverseGlyphOrderDict 

1054 if self.isLoaded("glyf"): 

1055 self["glyf"].setGlyphOrder(glyphOrder) 

1056 

1057 def getGlyphOrder(self) -> list[str]: 

1058 """Returns a list of glyph names ordered by their position in the font.""" 

1059 try: 

1060 return self.glyphOrder 

1061 except AttributeError: 

1062 pass 

1063 if "CFF " in self: 

1064 cff = self["CFF "] 

1065 self.glyphOrder = cff.getGlyphOrder() 

1066 elif "post" in self: 

1067 # TrueType font 

1068 glyphOrder = self["post"].getGlyphOrder() 

1069 if glyphOrder is None: 

1070 # 

1071 # No names found in the 'post' table. 

1072 # Try to create glyph names from the unicode cmap (if available) 

1073 # in combination with the Adobe Glyph List (AGL). 

1074 # 

1075 self._getGlyphNamesFromCmap() 

1076 elif len(glyphOrder) < self["maxp"].numGlyphs: 

1077 # 

1078 # Not enough names found in the 'post' table. 

1079 # Can happen when 'post' format 1 is improperly used on a font that 

1080 # has more than 258 glyphs (the length of 'standardGlyphOrder'). 

1081 # 

1082 log.warning( 

1083 "Not enough names found in the 'post' table, generating them from cmap instead" 

1084 ) 

1085 self._getGlyphNamesFromCmap() 

1086 else: 

1087 self.glyphOrder = glyphOrder 

1088 else: 

1089 self._getGlyphNamesFromCmap() 

1090 return self.glyphOrder 

1091 

1092 def _getGlyphNamesFromCmap(self) -> None: 

1093 # 

1094 # This is rather convoluted, but then again, it's an interesting problem: 

1095 # - we need to use the unicode values found in the cmap table to 

1096 # build glyph names (eg. because there is only a minimal post table, 

1097 # or none at all). 

1098 # - but the cmap parser also needs glyph names to work with... 

1099 # So here's what we do: 

1100 # - make up glyph names based on glyphID 

1101 # - load a temporary cmap table based on those names 

1102 # - extract the unicode values, build the "real" glyph names 

1103 # - unload the temporary cmap table 

1104 # 

1105 if self.isLoaded("cmap"): 

1106 # Bootstrapping: we're getting called by the cmap parser 

1107 # itself. This means self.tables['cmap'] contains a partially 

1108 # loaded cmap, making it impossible to get at a unicode 

1109 # subtable here. We remove the partially loaded cmap and 

1110 # restore it later. 

1111 # This only happens if the cmap table is loaded before any 

1112 # other table that does f.getGlyphOrder() or f.getGlyphName(). 

1113 cmapLoading = self.tables["cmap"] 

1114 del self.tables["cmap"] 

1115 else: 

1116 cmapLoading = None 

1117 # Make up glyph names based on glyphID, which will be used by the 

1118 # temporary cmap and by the real cmap in case we don't find a unicode 

1119 # cmap. 

1120 numGlyphs = int(self["maxp"].numGlyphs) 

1121 glyphOrder = ["glyph%.5d" % i for i in range(numGlyphs)] 

1122 glyphOrder[0] = ".notdef" 

1123 # Set the glyph order, so the cmap parser has something 

1124 # to work with (so we don't get called recursively). 

1125 self.glyphOrder = glyphOrder 

1126 

1127 # Make up glyph names based on the reversed cmap table. Because some 

1128 # glyphs (eg. ligatures or alternates) may not be reachable via cmap, 

1129 # this naming table will usually not cover all glyphs in the font. 

1130 # If the font has no Unicode cmap table, reversecmap will be empty. 

1131 if "cmap" in self: 

1132 reversecmap = self["cmap"].buildReversedMin() 

1133 else: 

1134 reversecmap = {} 

1135 useCount = {} 

1136 for i, tempName in enumerate(glyphOrder): 

1137 if tempName in reversecmap: 

1138 # If a font maps both U+0041 LATIN CAPITAL LETTER A and 

1139 # U+0391 GREEK CAPITAL LETTER ALPHA to the same glyph, 

1140 # we prefer naming the glyph as "A". 

1141 glyphName = self._makeGlyphName(reversecmap[tempName]) 

1142 numUses = useCount[glyphName] = useCount.get(glyphName, 0) + 1 

1143 if numUses > 1: 

1144 glyphName = "%s.alt%d" % (glyphName, numUses - 1) 

1145 glyphOrder[i] = glyphName 

1146 

1147 if "cmap" in self: 

1148 # Delete the temporary cmap table from the cache, so it can 

1149 # be parsed again with the right names. 

1150 del self.tables["cmap"] 

1151 self.glyphOrder = glyphOrder 

1152 if cmapLoading: 

1153 # restore partially loaded cmap, so it can continue loading 

1154 # using the proper names. 

1155 self.tables["cmap"] = cmapLoading 

1156 

1157 @staticmethod 

1158 def _makeGlyphName(codepoint: int) -> str: 

1159 from fontTools import agl # Adobe Glyph List 

1160 

1161 if codepoint in agl.UV2AGL: 

1162 return agl.UV2AGL[codepoint] 

1163 elif codepoint <= 0xFFFF: 

1164 return "uni%04X" % codepoint 

1165 else: 

1166 return "u%X" % codepoint 

1167 

1168 def getGlyphNames(self) -> list[str]: 

1169 """Get a list of glyph names, sorted alphabetically.""" 

1170 glyphNames = sorted(self.getGlyphOrder()) 

1171 return glyphNames 

1172 

1173 def getGlyphNames2(self) -> list[str]: 

1174 """Get a list of glyph names, sorted alphabetically, 

1175 but not case sensitive. 

1176 """ 

1177 from fontTools.misc import textTools 

1178 

1179 return textTools.caselessSort(self.getGlyphOrder()) 

1180 

1181 def getGlyphName(self, glyphID: int) -> str: 

1182 """Returns the name for the glyph with the given ID. 

1183 

1184 If no name is available, synthesises one with the form ``glyphXXXXX``` where 

1185 ```XXXXX`` is the zero-padded glyph ID. 

1186 """ 

1187 try: 

1188 return self.getGlyphOrder()[glyphID] 

1189 except IndexError: 

1190 return "glyph%.5d" % glyphID 

1191 

1192 def getGlyphNameMany(self, lst: Sequence[int]) -> list[str]: 

1193 """Converts a list of glyph IDs into a list of glyph names.""" 

1194 glyphOrder = self.getGlyphOrder() 

1195 cnt = len(glyphOrder) 

1196 return [glyphOrder[gid] if gid < cnt else "glyph%.5d" % gid for gid in lst] 

1197 

1198 def getGlyphID(self, glyphName: str) -> int: 

1199 """Returns the ID of the glyph with the given name.""" 

1200 try: 

1201 return self.getReverseGlyphMap()[glyphName] 

1202 except KeyError: 

1203 if glyphName[:5] == "glyph": 

1204 try: 

1205 return int(glyphName[5:]) 

1206 except (NameError, ValueError): 

1207 raise KeyError(glyphName) 

1208 raise 

1209 

1210 def getGlyphIDMany(self, lst: Sequence[str]) -> list[int]: 

1211 """Converts a list of glyph names into a list of glyph IDs.""" 

1212 d = self.getReverseGlyphMap() 

1213 try: 

1214 return [d[glyphName] for glyphName in lst] 

1215 except KeyError: 

1216 getGlyphID = self.getGlyphID 

1217 return [getGlyphID(glyphName) for glyphName in lst] 

1218 

1219 def getReverseGlyphMap(self, rebuild: bool = False) -> dict[str, int]: 

1220 """Returns a mapping of glyph names to glyph IDs.""" 

1221 if rebuild or not hasattr(self, "_reverseGlyphOrderDict"): 

1222 self._buildReverseGlyphOrderDict() 

1223 return self._reverseGlyphOrderDict 

1224 

1225 def _buildReverseGlyphOrderDict(self) -> dict[str, int]: 

1226 self._reverseGlyphOrderDict = d = {} 

1227 for glyphID, glyphName in enumerate(self.getGlyphOrder()): 

1228 d[glyphName] = glyphID 

1229 return d 

1230 

1231 def _writeTable( 

1232 self, 

1233 tag: str | bytes, 

1234 writer: SFNTWriter, 

1235 done: list[str | bytes], # Use list as original 

1236 tableCache: MutableMapping[tuple[Tag, bytes], DefaultTable] | None = None, 

1237 ) -> None: 

1238 """Internal helper function for self.save(). Keeps track of 

1239 inter-table dependencies. 

1240 """ 

1241 if tag in done: 

1242 return 

1243 tableClass = getTableClass(tag) 

1244 for masterTable in tableClass.dependencies: 

1245 if masterTable not in done: 

1246 if masterTable in self: 

1247 self._writeTable(masterTable, writer, done, tableCache) 

1248 else: 

1249 done.append(masterTable) 

1250 done.append(tag) 

1251 tabledata = self.getTableData(tag) 

1252 if tableCache is not None: 

1253 entry = tableCache.get((Tag(tag), tabledata)) 

1254 if entry is not None: 

1255 log.debug("reusing '%s' table", tag) 

1256 writer.setEntry(tag, entry) 

1257 return 

1258 log.debug("Writing '%s' table to disk", tag) 

1259 writer[tag] = tabledata 

1260 if tableCache is not None: 

1261 tableCache[(Tag(tag), tabledata)] = writer[tag] 

1262 

1263 def getTableData(self, tag: str | bytes) -> bytes: 

1264 """Returns the binary representation of a table. 

1265 

1266 If the table is currently loaded and in memory, the data is compiled to 

1267 binary and returned; if it is not currently loaded, the binary data is 

1268 read from the font file and returned. 

1269 """ 

1270 tag = Tag(tag) 

1271 if self.isLoaded(tag): 

1272 log.debug("Compiling '%s' table", tag) 

1273 return self.tables[tag].compile(self) 

1274 elif self.reader and tag in self.reader: 

1275 log.debug("Reading '%s' table from disk", tag) 

1276 return self.reader[tag] 

1277 else: 

1278 raise KeyError(tag) 

1279 

1280 def getGlyphSet( 

1281 self, 

1282 preferCFF: bool = True, 

1283 location: Mapping[str, _NumberT] | None = None, 

1284 normalized: bool = False, 

1285 recalcBounds: bool = True, 

1286 ) -> _TTGlyphSet: 

1287 """Return a generic GlyphSet, which is a dict-like object 

1288 mapping glyph names to glyph objects. The returned glyph objects 

1289 have a ``.draw()`` method that supports the Pen protocol, and will 

1290 have an attribute named 'width'. 

1291 

1292 If the font is CFF-based, the outlines will be taken from the ``CFF `` 

1293 or ``CFF2`` tables. Otherwise the outlines will be taken from the 

1294 ``glyf`` table. 

1295 

1296 If the font contains both a ``CFF ``/``CFF2`` and a ``glyf`` table, you 

1297 can use the ``preferCFF`` argument to specify which one should be taken. 

1298 If the font contains both a ``CFF `` and a ``CFF2`` table, the latter is 

1299 taken. 

1300 

1301 If the ``location`` parameter is set, it should be a dictionary mapping 

1302 four-letter variation tags to their float values, and the returned 

1303 glyph-set will represent an instance of a variable font at that 

1304 location. 

1305 

1306 If the ``normalized`` variable is set to True, that location is 

1307 interpreted as in the normalized (-1..+1) space, otherwise it is in the 

1308 font's defined axes space. 

1309 """ 

1310 if location and "fvar" not in self: 

1311 location = None 

1312 if location and not normalized: 

1313 location = self.normalizeLocation(location) 

1314 glyphSet = None 

1315 if ("CFF " in self or "CFF2" in self) and (preferCFF or "glyf" not in self): 

1316 glyphSet = _TTGlyphSetCFF(self, location) 

1317 elif "glyf" in self: 

1318 glyphSet = _TTGlyphSetGlyf(self, location, recalcBounds=recalcBounds) 

1319 else: 

1320 raise TTLibError("Font contains no outlines") 

1321 if "VARC" in self: 

1322 glyphSet = _TTGlyphSetVARC(self, location, glyphSet) 

1323 return glyphSet 

1324 

1325 def normalizeLocation(self, location: Mapping[str, float]) -> dict[str, float]: 

1326 """Normalize a ``location`` from the font's defined axes space (also 

1327 known as user space) into the normalized (-1..+1) space. It applies 

1328 ``avar`` mapping if the font contains an ``avar`` table. 

1329 

1330 The ``location`` parameter should be a dictionary mapping four-letter 

1331 variation tags to their float values. 

1332 

1333 Raises ``TTLibError`` if the font is not a variable font. 

1334 """ 

1335 from fontTools.varLib.models import normalizeLocation 

1336 

1337 if "fvar" not in self: 

1338 raise TTLibError("Not a variable font") 

1339 

1340 axes = self["fvar"].getAxes() 

1341 location = normalizeLocation(location, axes) 

1342 if "avar" in self: 

1343 location = self["avar"].renormalizeLocation(location, self) 

1344 return location 

1345 

1346 def getBestCmap( 

1347 self, 

1348 cmapPreferences: Sequence[tuple[int, int]] = ( 

1349 (3, 10), 

1350 (0, 6), 

1351 (0, 4), 

1352 (3, 1), 

1353 (0, 3), 

1354 (0, 2), 

1355 (0, 1), 

1356 (0, 0), 

1357 ), 

1358 ) -> dict[int, str] | None: 

1359 """Returns the 'best' Unicode cmap dictionary available in the font 

1360 or ``None``, if no Unicode cmap subtable is available. 

1361 

1362 By default it will search for the following (platformID, platEncID) 

1363 pairs in order:: 

1364 

1365 (3, 10), # Windows Unicode full repertoire 

1366 (0, 6), # Unicode full repertoire (format 13 subtable) 

1367 (0, 4), # Unicode 2.0 full repertoire 

1368 (3, 1), # Windows Unicode BMP 

1369 (0, 3), # Unicode 2.0 BMP 

1370 (0, 2), # Unicode ISO/IEC 10646 

1371 (0, 1), # Unicode 1.1 

1372 (0, 0) # Unicode 1.0 

1373 

1374 This particular order matches what HarfBuzz uses to choose what 

1375 subtable to use by default. This order prefers the largest-repertoire 

1376 subtable, and among those, prefers the Windows-platform over the 

1377 Unicode-platform as the former has wider support. 

1378 

1379 This order can be customized via the ``cmapPreferences`` argument. 

1380 """ 

1381 return self["cmap"].getBestCmap(cmapPreferences=cmapPreferences) 

1382 

1383 def reorderGlyphs(self, new_glyph_order: list[str]) -> None: 

1384 from .reorderGlyphs import reorderGlyphs 

1385 

1386 reorderGlyphs(self, new_glyph_order) 

1387 

1388 

1389class GlyphOrder(object): 

1390 """A pseudo table. The glyph order isn't in the font as a separate 

1391 table, but it's nice to present it as such in the TTX format. 

1392 """ 

1393 

1394 def __init__(self, tag: str | None = None) -> None: 

1395 pass 

1396 

1397 def toXML(self, writer: xmlWriter.XMLWriter, ttFont: TTFont) -> None: 

1398 glyphOrder = ttFont.getGlyphOrder() 

1399 writer.comment( 

1400 "The 'id' attribute is only for humans; it is ignored when parsed." 

1401 ) 

1402 writer.newline() 

1403 for i, glyphName in enumerate(glyphOrder): 

1404 writer.simpletag("GlyphID", id=i, name=glyphName) 

1405 writer.newline() 

1406 

1407 def fromXML( 

1408 self, name: str, attrs: dict[str, str], content: list[Any], ttFont: TTFont 

1409 ) -> None: 

1410 if not hasattr(self, "glyphOrder"): 

1411 self.glyphOrder = [] 

1412 if name == "GlyphID": 

1413 self.glyphOrder.append(attrs["name"]) 

1414 ttFont.setGlyphOrder(self.glyphOrder) 

1415 

1416 

1417def getTableModule(tag: str | bytes) -> ModuleType | None: 

1418 """Fetch the packer/unpacker module for a table. 

1419 Return None when no module is found. 

1420 """ 

1421 from . import tables 

1422 

1423 pyTag = tagToIdentifier(tag) 

1424 try: 

1425 __import__("fontTools.ttLib.tables." + pyTag) 

1426 except ImportError as err: 

1427 # If pyTag is found in the ImportError message, 

1428 # means table is not implemented. If it's not 

1429 # there, then some other module is missing, don't 

1430 # suppress the error. 

1431 if str(err).find(pyTag) >= 0: 

1432 return None 

1433 else: 

1434 raise err 

1435 else: 

1436 return getattr(tables, pyTag) 

1437 

1438 

1439# Registry for custom table packer/unpacker classes. Keys are table 

1440# tags, values are (moduleName, className) tuples. 

1441# See registerCustomTableClass() and getCustomTableClass() 

1442_customTableRegistry: dict[str | bytes, tuple[str, str]] = {} 

1443 

1444 

1445def registerCustomTableClass( 

1446 tag: str | bytes, moduleName: str, className: str | None = None 

1447) -> None: 

1448 """Register a custom packer/unpacker class for a table. 

1449 

1450 The 'moduleName' must be an importable module. If no 'className' 

1451 is given, it is derived from the tag, for example it will be 

1452 ``table_C_U_S_T_`` for a 'CUST' tag. 

1453 

1454 The registered table class should be a subclass of 

1455 :py:class:`fontTools.ttLib.tables.DefaultTable.DefaultTable` 

1456 """ 

1457 if className is None: 

1458 className = "table_" + tagToIdentifier(tag) 

1459 _customTableRegistry[tag] = (moduleName, className) 

1460 

1461 

1462def unregisterCustomTableClass(tag: str | bytes) -> None: 

1463 """Unregister the custom packer/unpacker class for a table.""" 

1464 del _customTableRegistry[tag] 

1465 

1466 

1467def getCustomTableClass(tag: str | bytes) -> type[DefaultTable] | None: 

1468 """Return the custom table class for tag, if one has been registered 

1469 with 'registerCustomTableClass()'. Else return None. 

1470 """ 

1471 if tag not in _customTableRegistry: 

1472 return None 

1473 import importlib 

1474 

1475 moduleName, className = _customTableRegistry[tag] 

1476 module = importlib.import_module(moduleName) 

1477 return getattr(module, className) 

1478 

1479 

1480def getTableClass(tag: str | bytes) -> type[DefaultTable]: 

1481 """Fetch the packer/unpacker class for a table.""" 

1482 tableClass = getCustomTableClass(tag) 

1483 if tableClass is not None: 

1484 return tableClass 

1485 module = getTableModule(tag) 

1486 if module is None: 

1487 from .tables.DefaultTable import DefaultTable 

1488 

1489 return DefaultTable 

1490 pyTag = tagToIdentifier(tag) 

1491 tableClass = getattr(module, "table_" + pyTag) 

1492 return tableClass 

1493 

1494 

1495def getClassTag(klass: type[DefaultTable]) -> str | bytes: 

1496 """Fetch the table tag for a class object.""" 

1497 name = klass.__name__ 

1498 assert name[:6] == "table_" 

1499 name = name[6:] # Chop 'table_' 

1500 return identifierToTag(name) 

1501 

1502 

1503def newTable(tag: str | bytes) -> DefaultTable: 

1504 """Return a new instance of a table.""" 

1505 tableClass = getTableClass(tag) 

1506 return tableClass(tag) 

1507 

1508 

1509def _escapechar(c: str) -> str: 

1510 """Helper function for tagToIdentifier()""" 

1511 import re 

1512 

1513 if re.match("[a-z0-9]", c): 

1514 return "_" + c 

1515 elif re.match("[A-Z]", c): 

1516 return c + "_" 

1517 else: 

1518 return hex(byteord(c))[2:] 

1519 

1520 

1521def tagToIdentifier(tag: str | bytes) -> str: 

1522 """Convert a table tag to a valid (but UGLY) python identifier, 

1523 as well as a filename that's guaranteed to be unique even on a 

1524 caseless file system. Each character is mapped to two characters. 

1525 Lowercase letters get an underscore before the letter, uppercase 

1526 letters get an underscore after the letter. Trailing spaces are 

1527 trimmed. Illegal characters are escaped as two hex bytes. If the 

1528 result starts with a number (as the result of a hex escape), an 

1529 extra underscore is prepended. Examples: 

1530 .. code-block:: pycon 

1531 

1532 >>> 

1533 >> tagToIdentifier('glyf') 

1534 '_g_l_y_f' 

1535 >> tagToIdentifier('cvt ') 

1536 '_c_v_t' 

1537 >> tagToIdentifier('OS/2') 

1538 'O_S_2f_2' 

1539 """ 

1540 import re 

1541 

1542 tag = Tag(tag) 

1543 if tag == "GlyphOrder": 

1544 return tag 

1545 assert len(tag) == 4, "tag should be 4 characters long" 

1546 while len(tag) > 1 and tag[-1] == " ": 

1547 tag = tag[:-1] 

1548 ident = "" 

1549 for c in tag: 

1550 ident = ident + _escapechar(c) 

1551 if re.match("[0-9]", ident): 

1552 ident = "_" + ident 

1553 return ident 

1554 

1555 

1556def identifierToTag(ident: str) -> str: 

1557 """the opposite of tagToIdentifier()""" 

1558 if ident == "GlyphOrder": 

1559 return ident 

1560 if len(ident) % 2 and ident[0] == "_": 

1561 ident = ident[1:] 

1562 assert not (len(ident) % 2) 

1563 tag = "" 

1564 for i in range(0, len(ident), 2): 

1565 if ident[i] == "_": 

1566 tag = tag + ident[i + 1] 

1567 elif ident[i + 1] == "_": 

1568 tag = tag + ident[i] 

1569 else: 

1570 # assume hex 

1571 tag = tag + chr(int(ident[i : i + 2], 16)) 

1572 # append trailing spaces 

1573 tag = tag + (4 - len(tag)) * " " 

1574 return Tag(tag) 

1575 

1576 

1577def tagToXML(tag: str | bytes) -> str: 

1578 """Similarly to tagToIdentifier(), this converts a TT tag 

1579 to a valid XML element name. Since XML element names are 

1580 case sensitive, this is a fairly simple/readable translation. 

1581 """ 

1582 import re 

1583 

1584 tag = Tag(tag) 

1585 if tag == "OS/2": 

1586 return "OS_2" 

1587 elif tag == "GlyphOrder": 

1588 return tag 

1589 if re.match("[A-Za-z_][A-Za-z_0-9]* *$", tag): 

1590 return tag.strip() 

1591 else: 

1592 return tagToIdentifier(tag) 

1593 

1594 

1595def xmlToTag(tag: str) -> str: 

1596 """The opposite of tagToXML()""" 

1597 if tag == "OS_2": 

1598 return Tag("OS/2") 

1599 if len(tag) == 8: 

1600 return identifierToTag(tag) 

1601 else: 

1602 return Tag(tag + " " * (4 - len(tag))) 

1603 

1604 

1605# Table order as recommended in the OpenType specification 1.4 

1606TTFTableOrder = [ 

1607 "head", 

1608 "hhea", 

1609 "maxp", 

1610 "OS/2", 

1611 "hmtx", 

1612 "LTSH", 

1613 "VDMX", 

1614 "hdmx", 

1615 "cmap", 

1616 "fpgm", 

1617 "prep", 

1618 "cvt ", 

1619 "loca", 

1620 "glyf", 

1621 "kern", 

1622 "name", 

1623 "post", 

1624 "gasp", 

1625 "PCLT", 

1626] 

1627 

1628OTFTableOrder = ["head", "hhea", "maxp", "OS/2", "name", "cmap", "post", "CFF "] 

1629 

1630 

1631def sortedTagList( 

1632 tagList: Sequence[str], tableOrder: Sequence[str] | None = None 

1633) -> list[str]: 

1634 """Return a sorted copy of tagList, sorted according to the OpenType 

1635 specification, or according to a custom tableOrder. If given and not 

1636 None, tableOrder needs to be a list of tag names. 

1637 """ 

1638 tagList = sorted(tagList) 

1639 if tableOrder is None: 

1640 if "DSIG" in tagList: 

1641 # DSIG should be last (XXX spec reference?) 

1642 tagList.remove("DSIG") 

1643 tagList.append("DSIG") 

1644 if "CFF " in tagList: 

1645 tableOrder = OTFTableOrder 

1646 else: 

1647 tableOrder = TTFTableOrder 

1648 orderedTables = [] 

1649 for tag in tableOrder: 

1650 if tag in tagList: 

1651 orderedTables.append(tag) 

1652 tagList.remove(tag) 

1653 orderedTables.extend(tagList) 

1654 return orderedTables 

1655 

1656 

1657def reorderFontTables( 

1658 inFile: BinaryIO, # Takes file-like object as per original 

1659 outFile: BinaryIO, # Takes file-like object 

1660 tableOrder: Sequence[str] | None = None, 

1661 checkChecksums: bool = False, # Keep param even if reader handles it 

1662) -> None: 

1663 """Rewrite a font file, ordering the tables as recommended by the 

1664 OpenType specification 1.4. 

1665 """ 

1666 inFile.seek(0) 

1667 outFile.seek(0) 

1668 reader = SFNTReader(inFile, checkChecksums=checkChecksums) 

1669 writer = SFNTWriter( 

1670 outFile, 

1671 len(reader.tables), 

1672 reader.sfntVersion, 

1673 reader.flavor, 

1674 reader.flavorData, 

1675 ) 

1676 tables = list(reader.keys()) 

1677 for tag in sortedTagList(tables, tableOrder): 

1678 writer[tag] = reader[tag] 

1679 writer.close() 

1680 

1681 

1682def maxPowerOfTwo(x: int) -> int: 

1683 """Return the highest exponent of two, so that 

1684 (2 ** exponent) <= x. Return 0 if x is 0. 

1685 """ 

1686 exponent = 0 

1687 while x: 

1688 x = x >> 1 

1689 exponent = exponent + 1 

1690 return max(exponent - 1, 0) 

1691 

1692 

1693def getSearchRange(n: int, itemSize: int = 16) -> tuple[int, int, int]: 

1694 """Calculate searchRange, entrySelector, rangeShift.""" 

1695 # itemSize defaults to 16, for backward compatibility 

1696 # with upstream fonttools. 

1697 exponent = maxPowerOfTwo(n) 

1698 searchRange = (2**exponent) * itemSize 

1699 entrySelector = exponent 

1700 rangeShift = max(0, n * itemSize - searchRange) 

1701 return searchRange, entrySelector, rangeShift