|
35 | 35 | from pathlib import Path
|
36 | 36 | import re
|
37 | 37 | import subprocess
|
| 38 | +import struct |
38 | 39 | import sys
|
39 | 40 | import threading
|
40 | 41 |
|
@@ -1099,7 +1100,7 @@ def __init__(self, size=None, weight='normal'):
|
1099 | 1100 | 'Matplotlib is building the font cache; this may take a moment.'))
|
1100 | 1101 | timer.start()
|
1101 | 1102 | try:
|
1102 |
| - for fontext in ["afm", "ttf"]: |
| 1103 | + for fontext in ["afm", "ttf", "ttc"]: |
1103 | 1104 | for path in [*findSystemFonts(paths, fontext=fontext),
|
1104 | 1105 | *findSystemFonts(fontext=fontext)]:
|
1105 | 1106 | try:
|
@@ -1129,6 +1130,9 @@ def addfont(self, path):
|
1129 | 1130 | font = _afm.AFM(fh)
|
1130 | 1131 | prop = afmFontProperty(path, font)
|
1131 | 1132 | self.afmlist.append(prop)
|
| 1133 | + elif Path(path).suffix.lower() == ".ttc": |
| 1134 | + for ttf_file in _split_ttc(path): |
| 1135 | + self.addfont(ttf_file) |
1132 | 1136 | else:
|
1133 | 1137 | font = ft2font.FT2Font(path)
|
1134 | 1138 | prop = ttfFontProperty(font)
|
@@ -1473,6 +1477,102 @@ def get_font(filename, hinting_factor=None):
|
1473 | 1477 | thread_id=threading.get_ident())
|
1474 | 1478 |
|
1475 | 1479 |
|
| 1480 | +def _split_ttc(ttc_path): |
| 1481 | + """SPlit a TTC ont into TTF files""" |
| 1482 | + res = _read_ttc(ttc_path) |
| 1483 | + ttf_fonts, table_index, table_data = res |
| 1484 | + out_base = Path( |
| 1485 | + mpl.get_cachedir(), |
| 1486 | + os.path.basename(ttc_path) + "-" |
| 1487 | + ) |
| 1488 | + return _dump_ttf(out_base, ttf_fonts, table_index, table_data) |
| 1489 | + |
| 1490 | + |
| 1491 | +def _read_ttc(ttc_path): |
| 1492 | + """ |
| 1493 | + Read a TTC font collection |
| 1494 | +
|
| 1495 | + Returns an internal list of TTF fonts, table index data, and table |
| 1496 | + contents. |
| 1497 | + """ |
| 1498 | + with open(ttc_path, "rb") as ttc_file: |
| 1499 | + def read(fmt): |
| 1500 | + """Read with struct format""" |
| 1501 | + size = struct.calcsize(fmt) |
| 1502 | + data = ttc_file.read(size) |
| 1503 | + return struct.unpack(fmt, data) |
| 1504 | + |
| 1505 | + read(">HHI") # ttcf tag and version, ignore |
| 1506 | + num_fonts = read(">I")[0] # Number of fonts |
| 1507 | + font_offsets = read(f">{num_fonts:d}I") # offsets of TTF font |
| 1508 | + |
| 1509 | + # Set of tables referenced by any font |
| 1510 | + table_index = {} # (offset, length): tag, chksum |
| 1511 | + |
| 1512 | + # List of TTF fonts |
| 1513 | + ttf_fonts = [] # (version, num_entries, triple, referenced tables) |
| 1514 | + |
| 1515 | + # Read TTF headers and directory tables |
| 1516 | + for font_offset in font_offsets: |
| 1517 | + ttc_file.seek(font_offset) |
| 1518 | + |
| 1519 | + version = read(">HH") # TTF format version |
| 1520 | + num_entries = read(">H")[0] # Number of entried in directory table |
| 1521 | + triple = read(">HHH") # Weird triple, often invalid |
| 1522 | + referenced_tables = [] |
| 1523 | + |
| 1524 | + for _ in range(num_entries): |
| 1525 | + tag, chksum, offset, length = read(">IIII") |
| 1526 | + referenced_tables.append((offset, length)) |
| 1527 | + table_index[(offset, length)] = tag, chksum |
| 1528 | + |
| 1529 | + ttf_fonts.append((version, num_entries, triple, referenced_tables)) |
| 1530 | + |
| 1531 | + # Read data for all tables |
| 1532 | + table_data = {} |
| 1533 | + for (offset, length), (tag, chksum) in table_index.items(): |
| 1534 | + ttc_file.seek(offset) |
| 1535 | + table_data[(offset, length)] = ttc_file.read(length) |
| 1536 | + |
| 1537 | + return ttf_fonts, table_index, table_data |
| 1538 | + |
| 1539 | + |
| 1540 | +def _dump_ttf(base_name, ttf_fonts, table_index, table_data): |
| 1541 | + """Write each TTF font to a separate font""" |
| 1542 | + created_paths = [] |
| 1543 | + |
| 1544 | + # Dump TTF fonts into separate files |
| 1545 | + for i, font in enumerate(ttf_fonts): |
| 1546 | + version, num_entries, triple, referenced_tables = font |
| 1547 | + |
| 1548 | + def write(file, fmt, values): |
| 1549 | + raw = struct.pack(fmt, *values) |
| 1550 | + file.write(raw) |
| 1551 | + |
| 1552 | + out_path = f"{base_name}{i}.ttf" |
| 1553 | + created_paths.append(out_path) |
| 1554 | + with open(out_path, "wb") as ttf_file: |
| 1555 | + |
| 1556 | + write(ttf_file, ">HH", version) |
| 1557 | + write(ttf_file, ">H", (num_entries, )) |
| 1558 | + write(ttf_file, ">HHH", triple) |
| 1559 | + |
| 1560 | + # Length of header and directory |
| 1561 | + file_offset = 12 + len(referenced_tables) * 16 |
| 1562 | + |
| 1563 | + # Write directory |
| 1564 | + for (offset, length) in referenced_tables: |
| 1565 | + tag, chksum, = table_index[(offset, length)] |
| 1566 | + write(ttf_file, ">IIII", (tag, chksum, file_offset, length)) |
| 1567 | + file_offset += length |
| 1568 | + |
| 1569 | + # Write tables |
| 1570 | + for table_coord in referenced_tables: |
| 1571 | + data = table_data[table_coord] |
| 1572 | + ttf_file.write(data) |
| 1573 | + return created_paths |
| 1574 | + |
| 1575 | + |
1476 | 1576 | def _load_fontmanager(*, try_read_cache=True):
|
1477 | 1577 | fm_path = Path(
|
1478 | 1578 | mpl.get_cachedir(), f"fontlist-v{FontManager.__version__}.json")
|
|
0 commit comments