fonttools 4.60.2__cp311-cp311-win32.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.
- fontTools/__init__.py +8 -0
- fontTools/__main__.py +35 -0
- fontTools/afmLib.py +439 -0
- fontTools/agl.py +5233 -0
- fontTools/annotations.py +30 -0
- fontTools/cffLib/CFF2ToCFF.py +258 -0
- fontTools/cffLib/CFFToCFF2.py +305 -0
- fontTools/cffLib/__init__.py +3694 -0
- fontTools/cffLib/specializer.py +927 -0
- fontTools/cffLib/transforms.py +495 -0
- fontTools/cffLib/width.py +210 -0
- fontTools/colorLib/__init__.py +0 -0
- fontTools/colorLib/builder.py +664 -0
- fontTools/colorLib/errors.py +2 -0
- fontTools/colorLib/geometry.py +143 -0
- fontTools/colorLib/table_builder.py +223 -0
- fontTools/colorLib/unbuilder.py +81 -0
- fontTools/config/__init__.py +90 -0
- fontTools/cu2qu/__init__.py +15 -0
- fontTools/cu2qu/__main__.py +6 -0
- fontTools/cu2qu/benchmark.py +54 -0
- fontTools/cu2qu/cli.py +198 -0
- fontTools/cu2qu/cu2qu.c +15817 -0
- fontTools/cu2qu/cu2qu.cp311-win32.pyd +0 -0
- fontTools/cu2qu/cu2qu.py +563 -0
- fontTools/cu2qu/errors.py +77 -0
- fontTools/cu2qu/ufo.py +363 -0
- fontTools/designspaceLib/__init__.py +3343 -0
- fontTools/designspaceLib/__main__.py +6 -0
- fontTools/designspaceLib/split.py +475 -0
- fontTools/designspaceLib/statNames.py +260 -0
- fontTools/designspaceLib/types.py +147 -0
- fontTools/encodings/MacRoman.py +258 -0
- fontTools/encodings/StandardEncoding.py +258 -0
- fontTools/encodings/__init__.py +1 -0
- fontTools/encodings/codecs.py +135 -0
- fontTools/feaLib/__init__.py +4 -0
- fontTools/feaLib/__main__.py +78 -0
- fontTools/feaLib/ast.py +2143 -0
- fontTools/feaLib/builder.py +1814 -0
- fontTools/feaLib/error.py +22 -0
- fontTools/feaLib/lexer.c +17029 -0
- fontTools/feaLib/lexer.cp311-win32.pyd +0 -0
- fontTools/feaLib/lexer.py +287 -0
- fontTools/feaLib/location.py +12 -0
- fontTools/feaLib/lookupDebugInfo.py +12 -0
- fontTools/feaLib/parser.py +2394 -0
- fontTools/feaLib/variableScalar.py +118 -0
- fontTools/fontBuilder.py +1014 -0
- fontTools/help.py +36 -0
- fontTools/merge/__init__.py +248 -0
- fontTools/merge/__main__.py +6 -0
- fontTools/merge/base.py +81 -0
- fontTools/merge/cmap.py +173 -0
- fontTools/merge/layout.py +526 -0
- fontTools/merge/options.py +85 -0
- fontTools/merge/tables.py +352 -0
- fontTools/merge/unicode.py +78 -0
- fontTools/merge/util.py +143 -0
- fontTools/misc/__init__.py +1 -0
- fontTools/misc/arrayTools.py +424 -0
- fontTools/misc/bezierTools.c +39731 -0
- fontTools/misc/bezierTools.cp311-win32.pyd +0 -0
- fontTools/misc/bezierTools.py +1500 -0
- fontTools/misc/classifyTools.py +170 -0
- fontTools/misc/cliTools.py +53 -0
- fontTools/misc/configTools.py +349 -0
- fontTools/misc/cython.py +27 -0
- fontTools/misc/dictTools.py +83 -0
- fontTools/misc/eexec.py +119 -0
- fontTools/misc/encodingTools.py +72 -0
- fontTools/misc/enumTools.py +23 -0
- fontTools/misc/etree.py +456 -0
- fontTools/misc/filenames.py +245 -0
- fontTools/misc/filesystem/__init__.py +68 -0
- fontTools/misc/filesystem/_base.py +134 -0
- fontTools/misc/filesystem/_copy.py +45 -0
- fontTools/misc/filesystem/_errors.py +54 -0
- fontTools/misc/filesystem/_info.py +75 -0
- fontTools/misc/filesystem/_osfs.py +164 -0
- fontTools/misc/filesystem/_path.py +67 -0
- fontTools/misc/filesystem/_subfs.py +92 -0
- fontTools/misc/filesystem/_tempfs.py +34 -0
- fontTools/misc/filesystem/_tools.py +34 -0
- fontTools/misc/filesystem/_walk.py +55 -0
- fontTools/misc/filesystem/_zipfs.py +204 -0
- fontTools/misc/fixedTools.py +253 -0
- fontTools/misc/intTools.py +25 -0
- fontTools/misc/iterTools.py +12 -0
- fontTools/misc/lazyTools.py +42 -0
- fontTools/misc/loggingTools.py +543 -0
- fontTools/misc/macCreatorType.py +56 -0
- fontTools/misc/macRes.py +261 -0
- fontTools/misc/plistlib/__init__.py +681 -0
- fontTools/misc/plistlib/py.typed +0 -0
- fontTools/misc/psCharStrings.py +1511 -0
- fontTools/misc/psLib.py +398 -0
- fontTools/misc/psOperators.py +572 -0
- fontTools/misc/py23.py +96 -0
- fontTools/misc/roundTools.py +110 -0
- fontTools/misc/sstruct.py +227 -0
- fontTools/misc/symfont.py +242 -0
- fontTools/misc/testTools.py +233 -0
- fontTools/misc/textTools.py +156 -0
- fontTools/misc/timeTools.py +88 -0
- fontTools/misc/transform.py +516 -0
- fontTools/misc/treeTools.py +45 -0
- fontTools/misc/vector.py +147 -0
- fontTools/misc/visitor.py +158 -0
- fontTools/misc/xmlReader.py +188 -0
- fontTools/misc/xmlWriter.py +231 -0
- fontTools/mtiLib/__init__.py +1400 -0
- fontTools/mtiLib/__main__.py +5 -0
- fontTools/otlLib/__init__.py +1 -0
- fontTools/otlLib/builder.py +3465 -0
- fontTools/otlLib/error.py +11 -0
- fontTools/otlLib/maxContextCalc.py +96 -0
- fontTools/otlLib/optimize/__init__.py +53 -0
- fontTools/otlLib/optimize/__main__.py +6 -0
- fontTools/otlLib/optimize/gpos.py +439 -0
- fontTools/pens/__init__.py +1 -0
- fontTools/pens/areaPen.py +52 -0
- fontTools/pens/basePen.py +475 -0
- fontTools/pens/boundsPen.py +98 -0
- fontTools/pens/cairoPen.py +26 -0
- fontTools/pens/cocoaPen.py +26 -0
- fontTools/pens/cu2quPen.py +325 -0
- fontTools/pens/explicitClosingLinePen.py +101 -0
- fontTools/pens/filterPen.py +433 -0
- fontTools/pens/freetypePen.py +462 -0
- fontTools/pens/hashPointPen.py +89 -0
- fontTools/pens/momentsPen.c +13378 -0
- fontTools/pens/momentsPen.cp311-win32.pyd +0 -0
- fontTools/pens/momentsPen.py +879 -0
- fontTools/pens/perimeterPen.py +69 -0
- fontTools/pens/pointInsidePen.py +192 -0
- fontTools/pens/pointPen.py +643 -0
- fontTools/pens/qtPen.py +29 -0
- fontTools/pens/qu2cuPen.py +105 -0
- fontTools/pens/quartzPen.py +43 -0
- fontTools/pens/recordingPen.py +335 -0
- fontTools/pens/reportLabPen.py +79 -0
- fontTools/pens/reverseContourPen.py +96 -0
- fontTools/pens/roundingPen.py +130 -0
- fontTools/pens/statisticsPen.py +312 -0
- fontTools/pens/svgPathPen.py +310 -0
- fontTools/pens/t2CharStringPen.py +88 -0
- fontTools/pens/teePen.py +55 -0
- fontTools/pens/transformPen.py +115 -0
- fontTools/pens/ttGlyphPen.py +335 -0
- fontTools/pens/wxPen.py +29 -0
- fontTools/qu2cu/__init__.py +15 -0
- fontTools/qu2cu/__main__.py +7 -0
- fontTools/qu2cu/benchmark.py +56 -0
- fontTools/qu2cu/cli.py +125 -0
- fontTools/qu2cu/qu2cu.c +16682 -0
- fontTools/qu2cu/qu2cu.cp311-win32.pyd +0 -0
- fontTools/qu2cu/qu2cu.py +405 -0
- fontTools/subset/__init__.py +4096 -0
- fontTools/subset/__main__.py +6 -0
- fontTools/subset/cff.py +184 -0
- fontTools/subset/svg.py +253 -0
- fontTools/subset/util.py +25 -0
- fontTools/svgLib/__init__.py +3 -0
- fontTools/svgLib/path/__init__.py +65 -0
- fontTools/svgLib/path/arc.py +154 -0
- fontTools/svgLib/path/parser.py +322 -0
- fontTools/svgLib/path/shapes.py +183 -0
- fontTools/t1Lib/__init__.py +648 -0
- fontTools/tfmLib.py +460 -0
- fontTools/ttLib/__init__.py +30 -0
- fontTools/ttLib/__main__.py +148 -0
- fontTools/ttLib/macUtils.py +54 -0
- fontTools/ttLib/removeOverlaps.py +395 -0
- fontTools/ttLib/reorderGlyphs.py +285 -0
- fontTools/ttLib/scaleUpem.py +436 -0
- fontTools/ttLib/sfnt.py +661 -0
- fontTools/ttLib/standardGlyphOrder.py +271 -0
- fontTools/ttLib/tables/B_A_S_E_.py +14 -0
- fontTools/ttLib/tables/BitmapGlyphMetrics.py +64 -0
- fontTools/ttLib/tables/C_B_D_T_.py +113 -0
- fontTools/ttLib/tables/C_B_L_C_.py +19 -0
- fontTools/ttLib/tables/C_F_F_.py +61 -0
- fontTools/ttLib/tables/C_F_F__2.py +26 -0
- fontTools/ttLib/tables/C_O_L_R_.py +165 -0
- fontTools/ttLib/tables/C_P_A_L_.py +305 -0
- fontTools/ttLib/tables/D_S_I_G_.py +158 -0
- fontTools/ttLib/tables/D__e_b_g.py +35 -0
- fontTools/ttLib/tables/DefaultTable.py +49 -0
- fontTools/ttLib/tables/E_B_D_T_.py +835 -0
- fontTools/ttLib/tables/E_B_L_C_.py +718 -0
- fontTools/ttLib/tables/F_F_T_M_.py +52 -0
- fontTools/ttLib/tables/F__e_a_t.py +149 -0
- fontTools/ttLib/tables/G_D_E_F_.py +13 -0
- fontTools/ttLib/tables/G_M_A_P_.py +148 -0
- fontTools/ttLib/tables/G_P_K_G_.py +133 -0
- fontTools/ttLib/tables/G_P_O_S_.py +14 -0
- fontTools/ttLib/tables/G_S_U_B_.py +13 -0
- fontTools/ttLib/tables/G_V_A_R_.py +5 -0
- fontTools/ttLib/tables/G__l_a_t.py +235 -0
- fontTools/ttLib/tables/G__l_o_c.py +85 -0
- fontTools/ttLib/tables/H_V_A_R_.py +13 -0
- fontTools/ttLib/tables/J_S_T_F_.py +13 -0
- fontTools/ttLib/tables/L_T_S_H_.py +58 -0
- fontTools/ttLib/tables/M_A_T_H_.py +13 -0
- fontTools/ttLib/tables/M_E_T_A_.py +352 -0
- fontTools/ttLib/tables/M_V_A_R_.py +13 -0
- fontTools/ttLib/tables/O_S_2f_2.py +752 -0
- fontTools/ttLib/tables/S_I_N_G_.py +99 -0
- fontTools/ttLib/tables/S_T_A_T_.py +15 -0
- fontTools/ttLib/tables/S_V_G_.py +223 -0
- fontTools/ttLib/tables/S__i_l_f.py +1040 -0
- fontTools/ttLib/tables/S__i_l_l.py +92 -0
- fontTools/ttLib/tables/T_S_I_B_.py +13 -0
- fontTools/ttLib/tables/T_S_I_C_.py +14 -0
- fontTools/ttLib/tables/T_S_I_D_.py +13 -0
- fontTools/ttLib/tables/T_S_I_J_.py +13 -0
- fontTools/ttLib/tables/T_S_I_P_.py +13 -0
- fontTools/ttLib/tables/T_S_I_S_.py +13 -0
- fontTools/ttLib/tables/T_S_I_V_.py +26 -0
- fontTools/ttLib/tables/T_S_I__0.py +70 -0
- fontTools/ttLib/tables/T_S_I__1.py +163 -0
- fontTools/ttLib/tables/T_S_I__2.py +17 -0
- fontTools/ttLib/tables/T_S_I__3.py +22 -0
- fontTools/ttLib/tables/T_S_I__5.py +60 -0
- fontTools/ttLib/tables/T_T_F_A_.py +14 -0
- fontTools/ttLib/tables/TupleVariation.py +884 -0
- fontTools/ttLib/tables/V_A_R_C_.py +12 -0
- fontTools/ttLib/tables/V_D_M_X_.py +249 -0
- fontTools/ttLib/tables/V_O_R_G_.py +165 -0
- fontTools/ttLib/tables/V_V_A_R_.py +13 -0
- fontTools/ttLib/tables/__init__.py +98 -0
- fontTools/ttLib/tables/_a_n_k_r.py +15 -0
- fontTools/ttLib/tables/_a_v_a_r.py +193 -0
- fontTools/ttLib/tables/_b_s_l_n.py +15 -0
- fontTools/ttLib/tables/_c_i_d_g.py +24 -0
- fontTools/ttLib/tables/_c_m_a_p.py +1591 -0
- fontTools/ttLib/tables/_c_v_a_r.py +94 -0
- fontTools/ttLib/tables/_c_v_t.py +56 -0
- fontTools/ttLib/tables/_f_e_a_t.py +15 -0
- fontTools/ttLib/tables/_f_p_g_m.py +62 -0
- fontTools/ttLib/tables/_f_v_a_r.py +261 -0
- fontTools/ttLib/tables/_g_a_s_p.py +63 -0
- fontTools/ttLib/tables/_g_c_i_d.py +13 -0
- fontTools/ttLib/tables/_g_l_y_f.py +2311 -0
- fontTools/ttLib/tables/_g_v_a_r.py +340 -0
- fontTools/ttLib/tables/_h_d_m_x.py +127 -0
- fontTools/ttLib/tables/_h_e_a_d.py +130 -0
- fontTools/ttLib/tables/_h_h_e_a.py +147 -0
- fontTools/ttLib/tables/_h_m_t_x.py +164 -0
- fontTools/ttLib/tables/_k_e_r_n.py +289 -0
- fontTools/ttLib/tables/_l_c_a_r.py +13 -0
- fontTools/ttLib/tables/_l_o_c_a.py +70 -0
- fontTools/ttLib/tables/_l_t_a_g.py +72 -0
- fontTools/ttLib/tables/_m_a_x_p.py +147 -0
- fontTools/ttLib/tables/_m_e_t_a.py +112 -0
- fontTools/ttLib/tables/_m_o_r_t.py +14 -0
- fontTools/ttLib/tables/_m_o_r_x.py +15 -0
- fontTools/ttLib/tables/_n_a_m_e.py +1242 -0
- fontTools/ttLib/tables/_o_p_b_d.py +14 -0
- fontTools/ttLib/tables/_p_o_s_t.py +319 -0
- fontTools/ttLib/tables/_p_r_e_p.py +16 -0
- fontTools/ttLib/tables/_p_r_o_p.py +12 -0
- fontTools/ttLib/tables/_s_b_i_x.py +129 -0
- fontTools/ttLib/tables/_t_r_a_k.py +332 -0
- fontTools/ttLib/tables/_v_h_e_a.py +139 -0
- fontTools/ttLib/tables/_v_m_t_x.py +19 -0
- fontTools/ttLib/tables/asciiTable.py +20 -0
- fontTools/ttLib/tables/grUtils.py +92 -0
- fontTools/ttLib/tables/otBase.py +1458 -0
- fontTools/ttLib/tables/otConverters.py +2068 -0
- fontTools/ttLib/tables/otData.py +6400 -0
- fontTools/ttLib/tables/otTables.py +2703 -0
- fontTools/ttLib/tables/otTraverse.py +163 -0
- fontTools/ttLib/tables/sbixGlyph.py +149 -0
- fontTools/ttLib/tables/sbixStrike.py +177 -0
- fontTools/ttLib/tables/table_API_readme.txt +91 -0
- fontTools/ttLib/tables/ttProgram.py +594 -0
- fontTools/ttLib/ttCollection.py +125 -0
- fontTools/ttLib/ttFont.py +1148 -0
- fontTools/ttLib/ttGlyphSet.py +490 -0
- fontTools/ttLib/ttVisitor.py +32 -0
- fontTools/ttLib/woff2.py +1680 -0
- fontTools/ttx.py +479 -0
- fontTools/ufoLib/__init__.py +2575 -0
- fontTools/ufoLib/converters.py +407 -0
- fontTools/ufoLib/errors.py +30 -0
- fontTools/ufoLib/etree.py +6 -0
- fontTools/ufoLib/filenames.py +356 -0
- fontTools/ufoLib/glifLib.py +2120 -0
- fontTools/ufoLib/kerning.py +141 -0
- fontTools/ufoLib/plistlib.py +47 -0
- fontTools/ufoLib/pointPen.py +6 -0
- fontTools/ufoLib/utils.py +107 -0
- fontTools/ufoLib/validators.py +1208 -0
- fontTools/unicode.py +50 -0
- fontTools/unicodedata/Blocks.py +817 -0
- fontTools/unicodedata/Mirrored.py +446 -0
- fontTools/unicodedata/OTTags.py +50 -0
- fontTools/unicodedata/ScriptExtensions.py +832 -0
- fontTools/unicodedata/Scripts.py +3639 -0
- fontTools/unicodedata/__init__.py +306 -0
- fontTools/varLib/__init__.py +1600 -0
- fontTools/varLib/__main__.py +6 -0
- fontTools/varLib/avar/__init__.py +0 -0
- fontTools/varLib/avar/__main__.py +72 -0
- fontTools/varLib/avar/build.py +79 -0
- fontTools/varLib/avar/map.py +108 -0
- fontTools/varLib/avar/plan.py +1004 -0
- fontTools/varLib/avar/unbuild.py +271 -0
- fontTools/varLib/avarPlanner.py +8 -0
- fontTools/varLib/builder.py +215 -0
- fontTools/varLib/cff.py +631 -0
- fontTools/varLib/errors.py +219 -0
- fontTools/varLib/featureVars.py +703 -0
- fontTools/varLib/hvar.py +113 -0
- fontTools/varLib/instancer/__init__.py +2052 -0
- fontTools/varLib/instancer/__main__.py +5 -0
- fontTools/varLib/instancer/featureVars.py +190 -0
- fontTools/varLib/instancer/names.py +388 -0
- fontTools/varLib/instancer/solver.py +309 -0
- fontTools/varLib/interpolatable.py +1209 -0
- fontTools/varLib/interpolatableHelpers.py +399 -0
- fontTools/varLib/interpolatablePlot.py +1269 -0
- fontTools/varLib/interpolatableTestContourOrder.py +82 -0
- fontTools/varLib/interpolatableTestStartingPoint.py +107 -0
- fontTools/varLib/interpolate_layout.py +124 -0
- fontTools/varLib/iup.c +19815 -0
- fontTools/varLib/iup.cp311-win32.pyd +0 -0
- fontTools/varLib/iup.py +490 -0
- fontTools/varLib/merger.py +1717 -0
- fontTools/varLib/models.py +642 -0
- fontTools/varLib/multiVarStore.py +253 -0
- fontTools/varLib/mutator.py +529 -0
- fontTools/varLib/mvar.py +40 -0
- fontTools/varLib/plot.py +238 -0
- fontTools/varLib/stat.py +149 -0
- fontTools/varLib/varStore.py +739 -0
- fontTools/voltLib/__init__.py +5 -0
- fontTools/voltLib/__main__.py +206 -0
- fontTools/voltLib/ast.py +452 -0
- fontTools/voltLib/error.py +12 -0
- fontTools/voltLib/lexer.py +99 -0
- fontTools/voltLib/parser.py +664 -0
- fontTools/voltLib/voltToFea.py +911 -0
- fonttools-4.60.2.data/data/share/man/man1/ttx.1 +225 -0
- fonttools-4.60.2.dist-info/METADATA +2250 -0
- fonttools-4.60.2.dist-info/RECORD +353 -0
- fonttools-4.60.2.dist-info/WHEEL +5 -0
- fonttools-4.60.2.dist-info/entry_points.txt +5 -0
- fonttools-4.60.2.dist-info/licenses/LICENSE +21 -0
- fonttools-4.60.2.dist-info/licenses/LICENSE.external +388 -0
- fonttools-4.60.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1242 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from fontTools.misc import sstruct
|
|
5
|
+
from fontTools.misc.textTools import (
|
|
6
|
+
bytechr,
|
|
7
|
+
byteord,
|
|
8
|
+
bytesjoin,
|
|
9
|
+
strjoin,
|
|
10
|
+
tobytes,
|
|
11
|
+
tostr,
|
|
12
|
+
safeEval,
|
|
13
|
+
)
|
|
14
|
+
from fontTools.misc.encodingTools import getEncoding
|
|
15
|
+
from fontTools.ttLib import newTable
|
|
16
|
+
from fontTools.ttLib.ttVisitor import TTVisitor
|
|
17
|
+
from fontTools import ttLib
|
|
18
|
+
import fontTools.ttLib.tables.otTables as otTables
|
|
19
|
+
from fontTools.ttLib.tables import C_P_A_L_
|
|
20
|
+
from . import DefaultTable
|
|
21
|
+
import struct
|
|
22
|
+
import logging
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
log = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
nameRecordFormat = """
|
|
28
|
+
> # big endian
|
|
29
|
+
platformID: H
|
|
30
|
+
platEncID: H
|
|
31
|
+
langID: H
|
|
32
|
+
nameID: H
|
|
33
|
+
length: H
|
|
34
|
+
offset: H
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
nameRecordSize = sstruct.calcsize(nameRecordFormat)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class table__n_a_m_e(DefaultTable.DefaultTable):
|
|
41
|
+
"""Naming table
|
|
42
|
+
|
|
43
|
+
The ``name`` table is used to store a variety of strings that can be
|
|
44
|
+
associated with user-facing font information. Records in the ``name``
|
|
45
|
+
table can be tagged with language tags to support multilingual naming
|
|
46
|
+
and can support platform-specific character-encoding variants.
|
|
47
|
+
|
|
48
|
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/name
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
dependencies = ["ltag"]
|
|
52
|
+
|
|
53
|
+
def __init__(self, tag=None):
|
|
54
|
+
super().__init__(tag)
|
|
55
|
+
self.names = []
|
|
56
|
+
|
|
57
|
+
def decompile(self, data, ttFont):
|
|
58
|
+
format, n, stringOffset = struct.unpack(b">HHH", data[:6])
|
|
59
|
+
expectedStringOffset = 6 + n * nameRecordSize
|
|
60
|
+
if stringOffset != expectedStringOffset:
|
|
61
|
+
log.error(
|
|
62
|
+
"'name' table stringOffset incorrect. Expected: %s; Actual: %s",
|
|
63
|
+
expectedStringOffset,
|
|
64
|
+
stringOffset,
|
|
65
|
+
)
|
|
66
|
+
stringData = data[stringOffset:]
|
|
67
|
+
data = data[6:]
|
|
68
|
+
self.names: list[NameRecord] = []
|
|
69
|
+
for i in range(n):
|
|
70
|
+
if len(data) < 12:
|
|
71
|
+
log.error("skipping malformed name record #%d", i)
|
|
72
|
+
continue
|
|
73
|
+
name, data = sstruct.unpack2(nameRecordFormat, data, NameRecord())
|
|
74
|
+
name.string = stringData[name.offset : name.offset + name.length]
|
|
75
|
+
if name.offset + name.length > len(stringData):
|
|
76
|
+
log.error("skipping malformed name record #%d", i)
|
|
77
|
+
continue
|
|
78
|
+
assert len(name.string) == name.length
|
|
79
|
+
# if (name.platEncID, name.platformID) in ((0, 0), (1, 3)):
|
|
80
|
+
# if len(name.string) % 2:
|
|
81
|
+
# print "2-byte string doesn't have even length!"
|
|
82
|
+
# print name.__dict__
|
|
83
|
+
del name.offset, name.length
|
|
84
|
+
self.names.append(name)
|
|
85
|
+
|
|
86
|
+
def compile(self, ttFont):
|
|
87
|
+
names = self.names
|
|
88
|
+
names.sort() # sort according to the spec; see NameRecord.__lt__()
|
|
89
|
+
stringData = b""
|
|
90
|
+
format = 0
|
|
91
|
+
n = len(names)
|
|
92
|
+
stringOffset = 6 + n * sstruct.calcsize(nameRecordFormat)
|
|
93
|
+
data = struct.pack(b">HHH", format, n, stringOffset)
|
|
94
|
+
lastoffset = 0
|
|
95
|
+
done = {} # remember the data so we can reuse the "pointers"
|
|
96
|
+
for name in names:
|
|
97
|
+
string = name.toBytes()
|
|
98
|
+
if string in done:
|
|
99
|
+
name.offset, name.length = done[string]
|
|
100
|
+
else:
|
|
101
|
+
name.offset, name.length = done[string] = len(stringData), len(string)
|
|
102
|
+
stringData = bytesjoin([stringData, string])
|
|
103
|
+
data = data + sstruct.pack(nameRecordFormat, name)
|
|
104
|
+
return data + stringData
|
|
105
|
+
|
|
106
|
+
def toXML(self, writer, ttFont):
|
|
107
|
+
for name in self.names:
|
|
108
|
+
name.toXML(writer, ttFont)
|
|
109
|
+
|
|
110
|
+
def fromXML(self, name, attrs, content, ttFont):
|
|
111
|
+
if name != "namerecord":
|
|
112
|
+
return # ignore unknown tags
|
|
113
|
+
name = NameRecord()
|
|
114
|
+
self.names.append(name)
|
|
115
|
+
name.fromXML(name, attrs, content, ttFont)
|
|
116
|
+
|
|
117
|
+
def getName(
|
|
118
|
+
self, nameID: int, platformID: int, platEncID: int, langID: int | None = None
|
|
119
|
+
) -> "NameRecord | None":
|
|
120
|
+
for namerecord in self.names:
|
|
121
|
+
if (
|
|
122
|
+
namerecord.nameID == nameID
|
|
123
|
+
and namerecord.platformID == platformID
|
|
124
|
+
and namerecord.platEncID == platEncID
|
|
125
|
+
):
|
|
126
|
+
if langID is None or namerecord.langID == langID:
|
|
127
|
+
return namerecord
|
|
128
|
+
return None # not found
|
|
129
|
+
|
|
130
|
+
def getDebugName(self, nameID: int) -> str | None:
|
|
131
|
+
englishName: str | None = None
|
|
132
|
+
someName: str | None = None
|
|
133
|
+
for name in self.names:
|
|
134
|
+
if name.nameID != nameID:
|
|
135
|
+
continue
|
|
136
|
+
try:
|
|
137
|
+
unistr = name.toUnicode()
|
|
138
|
+
except UnicodeDecodeError:
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
someName = unistr
|
|
142
|
+
if (name.platformID, name.langID) in ((1, 0), (3, 0x409)):
|
|
143
|
+
englishName = unistr
|
|
144
|
+
break
|
|
145
|
+
if englishName:
|
|
146
|
+
return englishName
|
|
147
|
+
elif someName:
|
|
148
|
+
return someName
|
|
149
|
+
else:
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
def getFirstDebugName(self, nameIDs):
|
|
153
|
+
for nameID in nameIDs:
|
|
154
|
+
name = self.getDebugName(nameID)
|
|
155
|
+
if name is not None:
|
|
156
|
+
return name
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
def getBestFamilyName(self):
|
|
160
|
+
# 21 = WWS Family Name
|
|
161
|
+
# 16 = Typographic Family Name
|
|
162
|
+
# 1 = Family Name
|
|
163
|
+
return self.getFirstDebugName((21, 16, 1))
|
|
164
|
+
|
|
165
|
+
def getBestSubFamilyName(self):
|
|
166
|
+
# 22 = WWS SubFamily Name
|
|
167
|
+
# 17 = Typographic SubFamily Name
|
|
168
|
+
# 2 = SubFamily Name
|
|
169
|
+
return self.getFirstDebugName((22, 17, 2))
|
|
170
|
+
|
|
171
|
+
def getBestFullName(self):
|
|
172
|
+
# 4 = Full Name
|
|
173
|
+
# 6 = PostScript Name
|
|
174
|
+
for nameIDs in ((21, 22), (16, 17), (1, 2), (4,), (6,)):
|
|
175
|
+
if len(nameIDs) == 2:
|
|
176
|
+
name_fam = self.getDebugName(nameIDs[0])
|
|
177
|
+
name_subfam = self.getDebugName(nameIDs[1])
|
|
178
|
+
if None in [name_fam, name_subfam]:
|
|
179
|
+
continue # if any is None, skip
|
|
180
|
+
name = f"{name_fam} {name_subfam}"
|
|
181
|
+
if name_subfam.lower() == "regular":
|
|
182
|
+
name = f"{name_fam}"
|
|
183
|
+
return name
|
|
184
|
+
else:
|
|
185
|
+
name = self.getDebugName(nameIDs[0])
|
|
186
|
+
if name is not None:
|
|
187
|
+
return name
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
def setName(self, string, nameID, platformID, platEncID, langID):
|
|
191
|
+
"""Set the 'string' for the name record identified by 'nameID', 'platformID',
|
|
192
|
+
'platEncID' and 'langID'. If a record with that nameID doesn't exist, create it
|
|
193
|
+
and append to the name table.
|
|
194
|
+
|
|
195
|
+
'string' can be of type `str` (`unicode` in PY2) or `bytes`. In the latter case,
|
|
196
|
+
it is assumed to be already encoded with the correct plaform-specific encoding
|
|
197
|
+
identified by the (platformID, platEncID, langID) triplet. A warning is issued
|
|
198
|
+
to prevent unexpected results.
|
|
199
|
+
"""
|
|
200
|
+
if not isinstance(string, str):
|
|
201
|
+
if isinstance(string, bytes):
|
|
202
|
+
log.warning(
|
|
203
|
+
"name string is bytes, ensure it's correctly encoded: %r", string
|
|
204
|
+
)
|
|
205
|
+
else:
|
|
206
|
+
raise TypeError(
|
|
207
|
+
"expected unicode or bytes, found %s: %r"
|
|
208
|
+
% (type(string).__name__, string)
|
|
209
|
+
)
|
|
210
|
+
namerecord = self.getName(nameID, platformID, platEncID, langID)
|
|
211
|
+
if namerecord:
|
|
212
|
+
namerecord.string = string
|
|
213
|
+
else:
|
|
214
|
+
self.names.append(makeName(string, nameID, platformID, platEncID, langID))
|
|
215
|
+
|
|
216
|
+
def removeNames(self, nameID=None, platformID=None, platEncID=None, langID=None):
|
|
217
|
+
"""Remove any name records identified by the given combination of 'nameID',
|
|
218
|
+
'platformID', 'platEncID' and 'langID'.
|
|
219
|
+
"""
|
|
220
|
+
args = {
|
|
221
|
+
argName: argValue
|
|
222
|
+
for argName, argValue in (
|
|
223
|
+
("nameID", nameID),
|
|
224
|
+
("platformID", platformID),
|
|
225
|
+
("platEncID", platEncID),
|
|
226
|
+
("langID", langID),
|
|
227
|
+
)
|
|
228
|
+
if argValue is not None
|
|
229
|
+
}
|
|
230
|
+
if not args:
|
|
231
|
+
# no arguments, nothing to do
|
|
232
|
+
return
|
|
233
|
+
self.names = [
|
|
234
|
+
rec
|
|
235
|
+
for rec in self.names
|
|
236
|
+
if any(
|
|
237
|
+
argValue != getattr(rec, argName) for argName, argValue in args.items()
|
|
238
|
+
)
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
@staticmethod
|
|
242
|
+
def removeUnusedNames(ttFont):
|
|
243
|
+
"""Remove any name records which are not in NameID range 0-255 and not utilized
|
|
244
|
+
within the font itself."""
|
|
245
|
+
visitor = NameRecordVisitor()
|
|
246
|
+
visitor.visit(ttFont)
|
|
247
|
+
toDelete = set()
|
|
248
|
+
for record in ttFont["name"].names:
|
|
249
|
+
# Name IDs 26 to 255, inclusive, are reserved for future standard names.
|
|
250
|
+
# https://learn.microsoft.com/en-us/typography/opentype/spec/name#name-ids
|
|
251
|
+
if record.nameID < 256:
|
|
252
|
+
continue
|
|
253
|
+
if record.nameID not in visitor.seen:
|
|
254
|
+
toDelete.add(record.nameID)
|
|
255
|
+
|
|
256
|
+
for nameID in toDelete:
|
|
257
|
+
ttFont["name"].removeNames(nameID)
|
|
258
|
+
return toDelete
|
|
259
|
+
|
|
260
|
+
def _findUnusedNameID(self, minNameID=256):
|
|
261
|
+
"""Finds an unused name id.
|
|
262
|
+
|
|
263
|
+
The nameID is assigned in the range between 'minNameID' and 32767 (inclusive),
|
|
264
|
+
following the last nameID in the name table.
|
|
265
|
+
"""
|
|
266
|
+
names = self.names
|
|
267
|
+
nameID = 1 + max([n.nameID for n in names] + [minNameID - 1])
|
|
268
|
+
if nameID > 32767:
|
|
269
|
+
raise ValueError("nameID must be less than 32768")
|
|
270
|
+
return nameID
|
|
271
|
+
|
|
272
|
+
def findMultilingualName(
|
|
273
|
+
self, names, windows=True, mac=True, minNameID=0, ttFont=None
|
|
274
|
+
):
|
|
275
|
+
"""Return the name ID of an existing multilingual name that
|
|
276
|
+
matches the 'names' dictionary, or None if not found.
|
|
277
|
+
|
|
278
|
+
'names' is a dictionary with the name in multiple languages,
|
|
279
|
+
such as {'en': 'Pale', 'de': 'Blaß', 'de-CH': 'Blass'}.
|
|
280
|
+
The keys can be arbitrary IETF BCP 47 language codes;
|
|
281
|
+
the values are Unicode strings.
|
|
282
|
+
|
|
283
|
+
If 'windows' is True, the returned name ID is guaranteed
|
|
284
|
+
exist for all requested languages for platformID=3 and
|
|
285
|
+
platEncID=1.
|
|
286
|
+
If 'mac' is True, the returned name ID is guaranteed to exist
|
|
287
|
+
for all requested languages for platformID=1 and platEncID=0.
|
|
288
|
+
|
|
289
|
+
The returned name ID will not be less than the 'minNameID'
|
|
290
|
+
argument.
|
|
291
|
+
"""
|
|
292
|
+
# Gather the set of requested
|
|
293
|
+
# (string, platformID, platEncID, langID)
|
|
294
|
+
# tuples
|
|
295
|
+
reqNameSet = set()
|
|
296
|
+
for lang, name in sorted(names.items()):
|
|
297
|
+
if windows:
|
|
298
|
+
windowsName = _makeWindowsName(name, None, lang)
|
|
299
|
+
if windowsName is not None:
|
|
300
|
+
reqNameSet.add(
|
|
301
|
+
(
|
|
302
|
+
windowsName.string,
|
|
303
|
+
windowsName.platformID,
|
|
304
|
+
windowsName.platEncID,
|
|
305
|
+
windowsName.langID,
|
|
306
|
+
)
|
|
307
|
+
)
|
|
308
|
+
if mac:
|
|
309
|
+
macName = _makeMacName(name, None, lang, ttFont)
|
|
310
|
+
if macName is not None:
|
|
311
|
+
reqNameSet.add(
|
|
312
|
+
(
|
|
313
|
+
macName.string,
|
|
314
|
+
macName.platformID,
|
|
315
|
+
macName.platEncID,
|
|
316
|
+
macName.langID,
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# Collect matching name IDs
|
|
321
|
+
matchingNames = dict()
|
|
322
|
+
for name in self.names:
|
|
323
|
+
try:
|
|
324
|
+
key = (name.toUnicode(), name.platformID, name.platEncID, name.langID)
|
|
325
|
+
except UnicodeDecodeError:
|
|
326
|
+
continue
|
|
327
|
+
if key in reqNameSet and name.nameID >= minNameID:
|
|
328
|
+
nameSet = matchingNames.setdefault(name.nameID, set())
|
|
329
|
+
nameSet.add(key)
|
|
330
|
+
|
|
331
|
+
# Return the first name ID that defines all requested strings
|
|
332
|
+
for nameID, nameSet in sorted(matchingNames.items()):
|
|
333
|
+
if nameSet == reqNameSet:
|
|
334
|
+
return nameID
|
|
335
|
+
|
|
336
|
+
return None # not found
|
|
337
|
+
|
|
338
|
+
def addMultilingualName(
|
|
339
|
+
self, names, ttFont=None, nameID=None, windows=True, mac=True, minNameID=0
|
|
340
|
+
):
|
|
341
|
+
"""Add a multilingual name, returning its name ID
|
|
342
|
+
|
|
343
|
+
'names' is a dictionary with the name in multiple languages,
|
|
344
|
+
such as {'en': 'Pale', 'de': 'Blaß', 'de-CH': 'Blass'}.
|
|
345
|
+
The keys can be arbitrary IETF BCP 47 language codes;
|
|
346
|
+
the values are Unicode strings.
|
|
347
|
+
|
|
348
|
+
'ttFont' is the TTFont to which the names are added, or None.
|
|
349
|
+
If present, the font's 'ltag' table can get populated
|
|
350
|
+
to store exotic language codes, which allows encoding
|
|
351
|
+
names that otherwise cannot get encoded at all.
|
|
352
|
+
|
|
353
|
+
'nameID' is the name ID to be used, or None to let the library
|
|
354
|
+
find an existing set of name records that match, or pick an
|
|
355
|
+
unused name ID.
|
|
356
|
+
|
|
357
|
+
If 'windows' is True, a platformID=3 name record will be added.
|
|
358
|
+
If 'mac' is True, a platformID=1 name record will be added.
|
|
359
|
+
|
|
360
|
+
If the 'nameID' argument is None, the created nameID will not
|
|
361
|
+
be less than the 'minNameID' argument.
|
|
362
|
+
"""
|
|
363
|
+
if nameID is None:
|
|
364
|
+
# Reuse nameID if possible
|
|
365
|
+
nameID = self.findMultilingualName(
|
|
366
|
+
names, windows=windows, mac=mac, minNameID=minNameID, ttFont=ttFont
|
|
367
|
+
)
|
|
368
|
+
if nameID is not None:
|
|
369
|
+
return nameID
|
|
370
|
+
nameID = self._findUnusedNameID()
|
|
371
|
+
# TODO: Should minimize BCP 47 language codes.
|
|
372
|
+
# https://github.com/fonttools/fonttools/issues/930
|
|
373
|
+
for lang, name in sorted(names.items()):
|
|
374
|
+
if windows:
|
|
375
|
+
windowsName = _makeWindowsName(name, nameID, lang)
|
|
376
|
+
if windowsName is not None:
|
|
377
|
+
self.names.append(windowsName)
|
|
378
|
+
else:
|
|
379
|
+
# We cannot not make a Windows name: make sure we add a
|
|
380
|
+
# Mac name as a fallback. This can happen for exotic
|
|
381
|
+
# BCP47 language tags that have no Windows language code.
|
|
382
|
+
mac = True
|
|
383
|
+
if mac:
|
|
384
|
+
macName = _makeMacName(name, nameID, lang, ttFont)
|
|
385
|
+
if macName is not None:
|
|
386
|
+
self.names.append(macName)
|
|
387
|
+
return nameID
|
|
388
|
+
|
|
389
|
+
def addName(self, string, platforms=((1, 0, 0), (3, 1, 0x409)), minNameID=255):
|
|
390
|
+
"""Add a new name record containing 'string' for each (platformID, platEncID,
|
|
391
|
+
langID) tuple specified in the 'platforms' list.
|
|
392
|
+
|
|
393
|
+
The nameID is assigned in the range between 'minNameID'+1 and 32767 (inclusive),
|
|
394
|
+
following the last nameID in the name table.
|
|
395
|
+
If no 'platforms' are specified, two English name records are added, one for the
|
|
396
|
+
Macintosh (platformID=0), and one for the Windows platform (3).
|
|
397
|
+
|
|
398
|
+
The 'string' must be a Unicode string, so it can be encoded with different,
|
|
399
|
+
platform-specific encodings.
|
|
400
|
+
|
|
401
|
+
Return the new nameID.
|
|
402
|
+
"""
|
|
403
|
+
assert (
|
|
404
|
+
len(platforms) > 0
|
|
405
|
+
), "'platforms' must contain at least one (platformID, platEncID, langID) tuple"
|
|
406
|
+
if not isinstance(string, str):
|
|
407
|
+
raise TypeError(
|
|
408
|
+
"expected str, found %s: %r" % (type(string).__name__, string)
|
|
409
|
+
)
|
|
410
|
+
nameID = self._findUnusedNameID(minNameID + 1)
|
|
411
|
+
for platformID, platEncID, langID in platforms:
|
|
412
|
+
self.names.append(makeName(string, nameID, platformID, platEncID, langID))
|
|
413
|
+
return nameID
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def makeName(string, nameID, platformID, platEncID, langID):
|
|
417
|
+
name = NameRecord()
|
|
418
|
+
name.string, name.nameID, name.platformID, name.platEncID, name.langID = (
|
|
419
|
+
string,
|
|
420
|
+
nameID,
|
|
421
|
+
platformID,
|
|
422
|
+
platEncID,
|
|
423
|
+
langID,
|
|
424
|
+
)
|
|
425
|
+
return name
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _makeWindowsName(name, nameID, language):
|
|
429
|
+
"""Create a NameRecord for the Microsoft Windows platform
|
|
430
|
+
|
|
431
|
+
'language' is an arbitrary IETF BCP 47 language identifier such
|
|
432
|
+
as 'en', 'de-CH', 'de-AT-1901', or 'fa-Latn'. If Microsoft Windows
|
|
433
|
+
does not support the desired language, the result will be None.
|
|
434
|
+
Future versions of fonttools might return a NameRecord for the
|
|
435
|
+
OpenType 'name' table format 1, but this is not implemented yet.
|
|
436
|
+
"""
|
|
437
|
+
langID = _WINDOWS_LANGUAGE_CODES.get(language.lower())
|
|
438
|
+
if langID is not None:
|
|
439
|
+
return makeName(name, nameID, 3, 1, langID)
|
|
440
|
+
else:
|
|
441
|
+
log.warning(
|
|
442
|
+
"cannot add Windows name in language %s "
|
|
443
|
+
"because fonttools does not yet support "
|
|
444
|
+
"name table format 1" % language
|
|
445
|
+
)
|
|
446
|
+
return None
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def _makeMacName(name, nameID, language, font=None):
|
|
450
|
+
"""Create a NameRecord for Apple platforms
|
|
451
|
+
|
|
452
|
+
'language' is an arbitrary IETF BCP 47 language identifier such
|
|
453
|
+
as 'en', 'de-CH', 'de-AT-1901', or 'fa-Latn'. When possible, we
|
|
454
|
+
create a Macintosh NameRecord that is understood by old applications
|
|
455
|
+
(platform ID 1 and an old-style Macintosh language enum). If this
|
|
456
|
+
is not possible, we create a Unicode NameRecord (platform ID 0)
|
|
457
|
+
whose language points to the font’s 'ltag' table. The latter
|
|
458
|
+
can encode any string in any language, but legacy applications
|
|
459
|
+
might not recognize the format (in which case they will ignore
|
|
460
|
+
those names).
|
|
461
|
+
|
|
462
|
+
'font' should be the TTFont for which you want to create a name.
|
|
463
|
+
If 'font' is None, we only return NameRecords for legacy Macintosh;
|
|
464
|
+
in that case, the result will be None for names that need to
|
|
465
|
+
be encoded with an 'ltag' table.
|
|
466
|
+
|
|
467
|
+
See the section “The language identifier” in Apple’s specification:
|
|
468
|
+
https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
|
|
469
|
+
"""
|
|
470
|
+
macLang = _MAC_LANGUAGE_CODES.get(language.lower())
|
|
471
|
+
macScript = _MAC_LANGUAGE_TO_SCRIPT.get(macLang)
|
|
472
|
+
if macLang is not None and macScript is not None:
|
|
473
|
+
encoding = getEncoding(1, macScript, macLang, default="ascii")
|
|
474
|
+
# Check if we can actually encode this name. If we can't,
|
|
475
|
+
# for example because we have no support for the legacy
|
|
476
|
+
# encoding, or because the name string contains Unicode
|
|
477
|
+
# characters that the legacy encoding cannot represent,
|
|
478
|
+
# we fall back to encoding the name in Unicode and put
|
|
479
|
+
# the language tag into the ltag table.
|
|
480
|
+
try:
|
|
481
|
+
_ = tobytes(name, encoding, errors="strict")
|
|
482
|
+
return makeName(name, nameID, 1, macScript, macLang)
|
|
483
|
+
except UnicodeEncodeError:
|
|
484
|
+
pass
|
|
485
|
+
if font is not None:
|
|
486
|
+
ltag = font.tables.get("ltag")
|
|
487
|
+
if ltag is None:
|
|
488
|
+
ltag = font["ltag"] = newTable("ltag")
|
|
489
|
+
# 0 = Unicode; 4 = “Unicode 2.0 or later semantics (non-BMP characters allowed)”
|
|
490
|
+
# “The preferred platform-specific code for Unicode would be 3 or 4.”
|
|
491
|
+
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
|
|
492
|
+
return makeName(name, nameID, 0, 4, ltag.addTag(language))
|
|
493
|
+
else:
|
|
494
|
+
log.warning(
|
|
495
|
+
"cannot store language %s into 'ltag' table "
|
|
496
|
+
"without having access to the TTFont object" % language
|
|
497
|
+
)
|
|
498
|
+
return None
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
class NameRecord(object):
|
|
502
|
+
def getEncoding(self, default="ascii"):
|
|
503
|
+
"""Returns the Python encoding name for this name entry based on its platformID,
|
|
504
|
+
platEncID, and langID. If encoding for these values is not known, by default
|
|
505
|
+
'ascii' is returned. That can be overriden by passing a value to the default
|
|
506
|
+
argument.
|
|
507
|
+
"""
|
|
508
|
+
return getEncoding(self.platformID, self.platEncID, self.langID, default)
|
|
509
|
+
|
|
510
|
+
def encodingIsUnicodeCompatible(self):
|
|
511
|
+
return self.getEncoding(None) in ["utf_16_be", "ucs2be", "ascii", "latin1"]
|
|
512
|
+
|
|
513
|
+
def __str__(self):
|
|
514
|
+
return self.toStr(errors="backslashreplace")
|
|
515
|
+
|
|
516
|
+
def isUnicode(self):
|
|
517
|
+
return self.platformID == 0 or (
|
|
518
|
+
self.platformID == 3 and self.platEncID in [0, 1, 10]
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
def toUnicode(self, errors: str = "strict") -> str:
|
|
522
|
+
"""
|
|
523
|
+
If self.string is a Unicode string, return it; otherwise try decoding the
|
|
524
|
+
bytes in self.string to a Unicode string using the encoding of this
|
|
525
|
+
entry as returned by self.getEncoding(); Note that self.getEncoding()
|
|
526
|
+
returns 'ascii' if the encoding is unknown to the library.
|
|
527
|
+
|
|
528
|
+
Certain heuristics are performed to recover data from bytes that are
|
|
529
|
+
ill-formed in the chosen encoding, or that otherwise look misencoded
|
|
530
|
+
(mostly around bad UTF-16BE encoded bytes, or bytes that look like UTF-16BE
|
|
531
|
+
but marked otherwise). If the bytes are ill-formed and the heuristics fail,
|
|
532
|
+
the error is handled according to the errors parameter to this function, which is
|
|
533
|
+
passed to the underlying decode() function; by default it throws a
|
|
534
|
+
UnicodeDecodeError exception.
|
|
535
|
+
|
|
536
|
+
Note: The mentioned heuristics mean that roundtripping a font to XML and back
|
|
537
|
+
to binary might recover some misencoded data whereas just loading the font
|
|
538
|
+
and saving it back will not change them.
|
|
539
|
+
"""
|
|
540
|
+
|
|
541
|
+
def isascii(b: int) -> bool:
|
|
542
|
+
return (b >= 0x20 and b <= 0x7E) or b in [0x09, 0x0A, 0x0D]
|
|
543
|
+
|
|
544
|
+
encoding = self.getEncoding()
|
|
545
|
+
string = self.string
|
|
546
|
+
|
|
547
|
+
if (
|
|
548
|
+
isinstance(string, bytes)
|
|
549
|
+
and encoding == "utf_16_be"
|
|
550
|
+
and len(string) % 2 == 1
|
|
551
|
+
):
|
|
552
|
+
# Recover badly encoded UTF-16 strings that have an odd number of bytes:
|
|
553
|
+
# - If the last byte is zero, drop it. Otherwise,
|
|
554
|
+
# - If all the odd bytes are zero and all the even bytes are ASCII,
|
|
555
|
+
# prepend one zero byte. Otherwise,
|
|
556
|
+
# - If first byte is zero and all other bytes are ASCII, insert zero
|
|
557
|
+
# bytes between consecutive ASCII bytes.
|
|
558
|
+
#
|
|
559
|
+
# (Yes, I've seen all of these in the wild... sigh)
|
|
560
|
+
if byteord(string[-1]) == 0:
|
|
561
|
+
string = string[:-1]
|
|
562
|
+
elif all(
|
|
563
|
+
byteord(b) == 0 if i % 2 else isascii(byteord(b))
|
|
564
|
+
for i, b in enumerate(string)
|
|
565
|
+
):
|
|
566
|
+
string = b"\0" + string
|
|
567
|
+
elif byteord(string[0]) == 0 and all(
|
|
568
|
+
isascii(byteord(b)) for b in string[1:]
|
|
569
|
+
):
|
|
570
|
+
string = bytesjoin(b"\0" + bytechr(byteord(b)) for b in string[1:])
|
|
571
|
+
|
|
572
|
+
string = tostr(string, encoding=encoding, errors=errors)
|
|
573
|
+
|
|
574
|
+
# If decoded strings still looks like UTF-16BE, it suggests a double-encoding.
|
|
575
|
+
# Fix it up.
|
|
576
|
+
if all(
|
|
577
|
+
ord(c) == 0 if i % 2 == 0 else isascii(ord(c)) for i, c in enumerate(string)
|
|
578
|
+
):
|
|
579
|
+
# If string claims to be Mac encoding, but looks like UTF-16BE with ASCII text,
|
|
580
|
+
# narrow it down.
|
|
581
|
+
string = "".join(c for c in string[1::2])
|
|
582
|
+
|
|
583
|
+
return string
|
|
584
|
+
|
|
585
|
+
def toBytes(self, errors="strict"):
|
|
586
|
+
"""If self.string is a bytes object, return it; otherwise try encoding
|
|
587
|
+
the Unicode string in self.string to bytes using the encoding of this
|
|
588
|
+
entry as returned by self.getEncoding(); Note that self.getEncoding()
|
|
589
|
+
returns 'ascii' if the encoding is unknown to the library.
|
|
590
|
+
|
|
591
|
+
If the Unicode string cannot be encoded to bytes in the chosen encoding,
|
|
592
|
+
the error is handled according to the errors parameter to this function,
|
|
593
|
+
which is passed to the underlying encode() function; by default it throws a
|
|
594
|
+
UnicodeEncodeError exception.
|
|
595
|
+
"""
|
|
596
|
+
return tobytes(self.string, encoding=self.getEncoding(), errors=errors)
|
|
597
|
+
|
|
598
|
+
toStr = toUnicode
|
|
599
|
+
|
|
600
|
+
def toXML(self, writer, ttFont):
|
|
601
|
+
try:
|
|
602
|
+
unistr = self.toUnicode()
|
|
603
|
+
except UnicodeDecodeError:
|
|
604
|
+
unistr = None
|
|
605
|
+
attrs = [
|
|
606
|
+
("nameID", self.nameID),
|
|
607
|
+
("platformID", self.platformID),
|
|
608
|
+
("platEncID", self.platEncID),
|
|
609
|
+
("langID", hex(self.langID)),
|
|
610
|
+
]
|
|
611
|
+
|
|
612
|
+
if unistr is None or not self.encodingIsUnicodeCompatible():
|
|
613
|
+
attrs.append(("unicode", unistr is not None))
|
|
614
|
+
|
|
615
|
+
writer.begintag("namerecord", attrs)
|
|
616
|
+
writer.newline()
|
|
617
|
+
if unistr is not None:
|
|
618
|
+
writer.write(unistr)
|
|
619
|
+
else:
|
|
620
|
+
writer.write8bit(self.string)
|
|
621
|
+
writer.newline()
|
|
622
|
+
writer.endtag("namerecord")
|
|
623
|
+
writer.newline()
|
|
624
|
+
|
|
625
|
+
def fromXML(self, name, attrs, content, ttFont):
|
|
626
|
+
self.nameID = safeEval(attrs["nameID"])
|
|
627
|
+
self.platformID = safeEval(attrs["platformID"])
|
|
628
|
+
self.platEncID = safeEval(attrs["platEncID"])
|
|
629
|
+
self.langID = safeEval(attrs["langID"])
|
|
630
|
+
s = strjoin(content).strip()
|
|
631
|
+
encoding = self.getEncoding()
|
|
632
|
+
if self.encodingIsUnicodeCompatible() or safeEval(
|
|
633
|
+
attrs.get("unicode", "False")
|
|
634
|
+
):
|
|
635
|
+
self.string = s.encode(encoding)
|
|
636
|
+
else:
|
|
637
|
+
# This is the inverse of write8bit...
|
|
638
|
+
self.string = s.encode("latin1")
|
|
639
|
+
|
|
640
|
+
def __lt__(self, other):
|
|
641
|
+
if type(self) != type(other):
|
|
642
|
+
return NotImplemented
|
|
643
|
+
|
|
644
|
+
try:
|
|
645
|
+
selfTuple = (
|
|
646
|
+
self.platformID,
|
|
647
|
+
self.platEncID,
|
|
648
|
+
self.langID,
|
|
649
|
+
self.nameID,
|
|
650
|
+
)
|
|
651
|
+
otherTuple = (
|
|
652
|
+
other.platformID,
|
|
653
|
+
other.platEncID,
|
|
654
|
+
other.langID,
|
|
655
|
+
other.nameID,
|
|
656
|
+
)
|
|
657
|
+
except AttributeError:
|
|
658
|
+
# This can only happen for
|
|
659
|
+
# 1) an object that is not a NameRecord, or
|
|
660
|
+
# 2) an unlikely incomplete NameRecord object which has not been
|
|
661
|
+
# fully populated
|
|
662
|
+
return NotImplemented
|
|
663
|
+
|
|
664
|
+
try:
|
|
665
|
+
# Include the actual NameRecord string in the comparison tuples
|
|
666
|
+
selfTuple = selfTuple + (self.toBytes(),)
|
|
667
|
+
otherTuple = otherTuple + (other.toBytes(),)
|
|
668
|
+
except UnicodeEncodeError as e:
|
|
669
|
+
# toBytes caused an encoding error in either of the two, so content
|
|
670
|
+
# to sorting based on IDs only
|
|
671
|
+
log.error("NameRecord sorting failed to encode: %s" % e)
|
|
672
|
+
|
|
673
|
+
# Implemented so that list.sort() sorts according to the spec by using
|
|
674
|
+
# the order of the tuple items and their comparison
|
|
675
|
+
return selfTuple < otherTuple
|
|
676
|
+
|
|
677
|
+
def __repr__(self):
|
|
678
|
+
return "<NameRecord NameID=%d; PlatformID=%d; LanguageID=%d>" % (
|
|
679
|
+
self.nameID,
|
|
680
|
+
self.platformID,
|
|
681
|
+
self.langID,
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
# Windows language ID → IETF BCP-47 language tag
|
|
686
|
+
#
|
|
687
|
+
# While Microsoft indicates a region/country for all its language
|
|
688
|
+
# IDs, we follow Unicode practice by omitting “most likely subtags”
|
|
689
|
+
# as per Unicode CLDR. For example, English is simply “en” and not
|
|
690
|
+
# “en-Latn” because according to Unicode, the default script
|
|
691
|
+
# for English is Latin.
|
|
692
|
+
#
|
|
693
|
+
# http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html
|
|
694
|
+
# http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
|
|
695
|
+
_WINDOWS_LANGUAGES = {
|
|
696
|
+
0x0436: "af",
|
|
697
|
+
0x041C: "sq",
|
|
698
|
+
0x0484: "gsw",
|
|
699
|
+
0x045E: "am",
|
|
700
|
+
0x1401: "ar-DZ",
|
|
701
|
+
0x3C01: "ar-BH",
|
|
702
|
+
0x0C01: "ar",
|
|
703
|
+
0x0801: "ar-IQ",
|
|
704
|
+
0x2C01: "ar-JO",
|
|
705
|
+
0x3401: "ar-KW",
|
|
706
|
+
0x3001: "ar-LB",
|
|
707
|
+
0x1001: "ar-LY",
|
|
708
|
+
0x1801: "ary",
|
|
709
|
+
0x2001: "ar-OM",
|
|
710
|
+
0x4001: "ar-QA",
|
|
711
|
+
0x0401: "ar-SA",
|
|
712
|
+
0x2801: "ar-SY",
|
|
713
|
+
0x1C01: "aeb",
|
|
714
|
+
0x3801: "ar-AE",
|
|
715
|
+
0x2401: "ar-YE",
|
|
716
|
+
0x042B: "hy",
|
|
717
|
+
0x044D: "as",
|
|
718
|
+
0x082C: "az-Cyrl",
|
|
719
|
+
0x042C: "az",
|
|
720
|
+
0x046D: "ba",
|
|
721
|
+
0x042D: "eu",
|
|
722
|
+
0x0423: "be",
|
|
723
|
+
0x0845: "bn",
|
|
724
|
+
0x0445: "bn-IN",
|
|
725
|
+
0x201A: "bs-Cyrl",
|
|
726
|
+
0x141A: "bs",
|
|
727
|
+
0x047E: "br",
|
|
728
|
+
0x0402: "bg",
|
|
729
|
+
0x0403: "ca",
|
|
730
|
+
0x0C04: "zh-HK",
|
|
731
|
+
0x1404: "zh-MO",
|
|
732
|
+
0x0804: "zh",
|
|
733
|
+
0x1004: "zh-SG",
|
|
734
|
+
0x0404: "zh-TW",
|
|
735
|
+
0x0483: "co",
|
|
736
|
+
0x041A: "hr",
|
|
737
|
+
0x101A: "hr-BA",
|
|
738
|
+
0x0405: "cs",
|
|
739
|
+
0x0406: "da",
|
|
740
|
+
0x048C: "prs",
|
|
741
|
+
0x0465: "dv",
|
|
742
|
+
0x0813: "nl-BE",
|
|
743
|
+
0x0413: "nl",
|
|
744
|
+
0x0C09: "en-AU",
|
|
745
|
+
0x2809: "en-BZ",
|
|
746
|
+
0x1009: "en-CA",
|
|
747
|
+
0x2409: "en-029",
|
|
748
|
+
0x4009: "en-IN",
|
|
749
|
+
0x1809: "en-IE",
|
|
750
|
+
0x2009: "en-JM",
|
|
751
|
+
0x4409: "en-MY",
|
|
752
|
+
0x1409: "en-NZ",
|
|
753
|
+
0x3409: "en-PH",
|
|
754
|
+
0x4809: "en-SG",
|
|
755
|
+
0x1C09: "en-ZA",
|
|
756
|
+
0x2C09: "en-TT",
|
|
757
|
+
0x0809: "en-GB",
|
|
758
|
+
0x0409: "en",
|
|
759
|
+
0x3009: "en-ZW",
|
|
760
|
+
0x0425: "et",
|
|
761
|
+
0x0438: "fo",
|
|
762
|
+
0x0464: "fil",
|
|
763
|
+
0x040B: "fi",
|
|
764
|
+
0x080C: "fr-BE",
|
|
765
|
+
0x0C0C: "fr-CA",
|
|
766
|
+
0x040C: "fr",
|
|
767
|
+
0x140C: "fr-LU",
|
|
768
|
+
0x180C: "fr-MC",
|
|
769
|
+
0x100C: "fr-CH",
|
|
770
|
+
0x0462: "fy",
|
|
771
|
+
0x0456: "gl",
|
|
772
|
+
0x0437: "ka",
|
|
773
|
+
0x0C07: "de-AT",
|
|
774
|
+
0x0407: "de",
|
|
775
|
+
0x1407: "de-LI",
|
|
776
|
+
0x1007: "de-LU",
|
|
777
|
+
0x0807: "de-CH",
|
|
778
|
+
0x0408: "el",
|
|
779
|
+
0x046F: "kl",
|
|
780
|
+
0x0447: "gu",
|
|
781
|
+
0x0468: "ha",
|
|
782
|
+
0x040D: "he",
|
|
783
|
+
0x0439: "hi",
|
|
784
|
+
0x040E: "hu",
|
|
785
|
+
0x040F: "is",
|
|
786
|
+
0x0470: "ig",
|
|
787
|
+
0x0421: "id",
|
|
788
|
+
0x045D: "iu",
|
|
789
|
+
0x085D: "iu-Latn",
|
|
790
|
+
0x083C: "ga",
|
|
791
|
+
0x0434: "xh",
|
|
792
|
+
0x0435: "zu",
|
|
793
|
+
0x0410: "it",
|
|
794
|
+
0x0810: "it-CH",
|
|
795
|
+
0x0411: "ja",
|
|
796
|
+
0x044B: "kn",
|
|
797
|
+
0x043F: "kk",
|
|
798
|
+
0x0453: "km",
|
|
799
|
+
0x0486: "quc",
|
|
800
|
+
0x0487: "rw",
|
|
801
|
+
0x0441: "sw",
|
|
802
|
+
0x0457: "kok",
|
|
803
|
+
0x0412: "ko",
|
|
804
|
+
0x0440: "ky",
|
|
805
|
+
0x0454: "lo",
|
|
806
|
+
0x0426: "lv",
|
|
807
|
+
0x0427: "lt",
|
|
808
|
+
0x082E: "dsb",
|
|
809
|
+
0x046E: "lb",
|
|
810
|
+
0x042F: "mk",
|
|
811
|
+
0x083E: "ms-BN",
|
|
812
|
+
0x043E: "ms",
|
|
813
|
+
0x044C: "ml",
|
|
814
|
+
0x043A: "mt",
|
|
815
|
+
0x0481: "mi",
|
|
816
|
+
0x047A: "arn",
|
|
817
|
+
0x044E: "mr",
|
|
818
|
+
0x047C: "moh",
|
|
819
|
+
0x0450: "mn",
|
|
820
|
+
0x0850: "mn-CN",
|
|
821
|
+
0x0461: "ne",
|
|
822
|
+
0x0414: "nb",
|
|
823
|
+
0x0814: "nn",
|
|
824
|
+
0x0482: "oc",
|
|
825
|
+
0x0448: "or",
|
|
826
|
+
0x0463: "ps",
|
|
827
|
+
0x0415: "pl",
|
|
828
|
+
0x0416: "pt",
|
|
829
|
+
0x0816: "pt-PT",
|
|
830
|
+
0x0446: "pa",
|
|
831
|
+
0x046B: "qu-BO",
|
|
832
|
+
0x086B: "qu-EC",
|
|
833
|
+
0x0C6B: "qu",
|
|
834
|
+
0x0418: "ro",
|
|
835
|
+
0x0417: "rm",
|
|
836
|
+
0x0419: "ru",
|
|
837
|
+
0x243B: "smn",
|
|
838
|
+
0x103B: "smj-NO",
|
|
839
|
+
0x143B: "smj",
|
|
840
|
+
0x0C3B: "se-FI",
|
|
841
|
+
0x043B: "se",
|
|
842
|
+
0x083B: "se-SE",
|
|
843
|
+
0x203B: "sms",
|
|
844
|
+
0x183B: "sma-NO",
|
|
845
|
+
0x1C3B: "sms",
|
|
846
|
+
0x044F: "sa",
|
|
847
|
+
0x1C1A: "sr-Cyrl-BA",
|
|
848
|
+
0x0C1A: "sr",
|
|
849
|
+
0x181A: "sr-Latn-BA",
|
|
850
|
+
0x081A: "sr-Latn",
|
|
851
|
+
0x046C: "nso",
|
|
852
|
+
0x0432: "tn",
|
|
853
|
+
0x045B: "si",
|
|
854
|
+
0x041B: "sk",
|
|
855
|
+
0x0424: "sl",
|
|
856
|
+
0x2C0A: "es-AR",
|
|
857
|
+
0x400A: "es-BO",
|
|
858
|
+
0x340A: "es-CL",
|
|
859
|
+
0x240A: "es-CO",
|
|
860
|
+
0x140A: "es-CR",
|
|
861
|
+
0x1C0A: "es-DO",
|
|
862
|
+
0x300A: "es-EC",
|
|
863
|
+
0x440A: "es-SV",
|
|
864
|
+
0x100A: "es-GT",
|
|
865
|
+
0x480A: "es-HN",
|
|
866
|
+
0x080A: "es-MX",
|
|
867
|
+
0x4C0A: "es-NI",
|
|
868
|
+
0x180A: "es-PA",
|
|
869
|
+
0x3C0A: "es-PY",
|
|
870
|
+
0x280A: "es-PE",
|
|
871
|
+
0x500A: "es-PR",
|
|
872
|
+
# Microsoft has defined two different language codes for
|
|
873
|
+
# “Spanish with modern sorting” and “Spanish with traditional
|
|
874
|
+
# sorting”. This makes sense for collation APIs, and it would be
|
|
875
|
+
# possible to express this in BCP 47 language tags via Unicode
|
|
876
|
+
# extensions (eg., “es-u-co-trad” is “Spanish with traditional
|
|
877
|
+
# sorting”). However, for storing names in fonts, this distinction
|
|
878
|
+
# does not make sense, so we use “es” in both cases.
|
|
879
|
+
0x0C0A: "es",
|
|
880
|
+
0x040A: "es",
|
|
881
|
+
0x540A: "es-US",
|
|
882
|
+
0x380A: "es-UY",
|
|
883
|
+
0x200A: "es-VE",
|
|
884
|
+
0x081D: "sv-FI",
|
|
885
|
+
0x041D: "sv",
|
|
886
|
+
0x045A: "syr",
|
|
887
|
+
0x0428: "tg",
|
|
888
|
+
0x085F: "tzm",
|
|
889
|
+
0x0449: "ta",
|
|
890
|
+
0x0444: "tt",
|
|
891
|
+
0x044A: "te",
|
|
892
|
+
0x041E: "th",
|
|
893
|
+
0x0451: "bo",
|
|
894
|
+
0x041F: "tr",
|
|
895
|
+
0x0442: "tk",
|
|
896
|
+
0x0480: "ug",
|
|
897
|
+
0x0422: "uk",
|
|
898
|
+
0x042E: "hsb",
|
|
899
|
+
0x0420: "ur",
|
|
900
|
+
0x0843: "uz-Cyrl",
|
|
901
|
+
0x0443: "uz",
|
|
902
|
+
0x042A: "vi",
|
|
903
|
+
0x0452: "cy",
|
|
904
|
+
0x0488: "wo",
|
|
905
|
+
0x0485: "sah",
|
|
906
|
+
0x0478: "ii",
|
|
907
|
+
0x046A: "yo",
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
_MAC_LANGUAGES = {
|
|
912
|
+
0: "en",
|
|
913
|
+
1: "fr",
|
|
914
|
+
2: "de",
|
|
915
|
+
3: "it",
|
|
916
|
+
4: "nl",
|
|
917
|
+
5: "sv",
|
|
918
|
+
6: "es",
|
|
919
|
+
7: "da",
|
|
920
|
+
8: "pt",
|
|
921
|
+
9: "no",
|
|
922
|
+
10: "he",
|
|
923
|
+
11: "ja",
|
|
924
|
+
12: "ar",
|
|
925
|
+
13: "fi",
|
|
926
|
+
14: "el",
|
|
927
|
+
15: "is",
|
|
928
|
+
16: "mt",
|
|
929
|
+
17: "tr",
|
|
930
|
+
18: "hr",
|
|
931
|
+
19: "zh-Hant",
|
|
932
|
+
20: "ur",
|
|
933
|
+
21: "hi",
|
|
934
|
+
22: "th",
|
|
935
|
+
23: "ko",
|
|
936
|
+
24: "lt",
|
|
937
|
+
25: "pl",
|
|
938
|
+
26: "hu",
|
|
939
|
+
27: "es",
|
|
940
|
+
28: "lv",
|
|
941
|
+
29: "se",
|
|
942
|
+
30: "fo",
|
|
943
|
+
31: "fa",
|
|
944
|
+
32: "ru",
|
|
945
|
+
33: "zh",
|
|
946
|
+
34: "nl-BE",
|
|
947
|
+
35: "ga",
|
|
948
|
+
36: "sq",
|
|
949
|
+
37: "ro",
|
|
950
|
+
38: "cz",
|
|
951
|
+
39: "sk",
|
|
952
|
+
40: "sl",
|
|
953
|
+
41: "yi",
|
|
954
|
+
42: "sr",
|
|
955
|
+
43: "mk",
|
|
956
|
+
44: "bg",
|
|
957
|
+
45: "uk",
|
|
958
|
+
46: "be",
|
|
959
|
+
47: "uz",
|
|
960
|
+
48: "kk",
|
|
961
|
+
49: "az-Cyrl",
|
|
962
|
+
50: "az-Arab",
|
|
963
|
+
51: "hy",
|
|
964
|
+
52: "ka",
|
|
965
|
+
53: "mo",
|
|
966
|
+
54: "ky",
|
|
967
|
+
55: "tg",
|
|
968
|
+
56: "tk",
|
|
969
|
+
57: "mn-CN",
|
|
970
|
+
58: "mn",
|
|
971
|
+
59: "ps",
|
|
972
|
+
60: "ks",
|
|
973
|
+
61: "ku",
|
|
974
|
+
62: "sd",
|
|
975
|
+
63: "bo",
|
|
976
|
+
64: "ne",
|
|
977
|
+
65: "sa",
|
|
978
|
+
66: "mr",
|
|
979
|
+
67: "bn",
|
|
980
|
+
68: "as",
|
|
981
|
+
69: "gu",
|
|
982
|
+
70: "pa",
|
|
983
|
+
71: "or",
|
|
984
|
+
72: "ml",
|
|
985
|
+
73: "kn",
|
|
986
|
+
74: "ta",
|
|
987
|
+
75: "te",
|
|
988
|
+
76: "si",
|
|
989
|
+
77: "my",
|
|
990
|
+
78: "km",
|
|
991
|
+
79: "lo",
|
|
992
|
+
80: "vi",
|
|
993
|
+
81: "id",
|
|
994
|
+
82: "tl",
|
|
995
|
+
83: "ms",
|
|
996
|
+
84: "ms-Arab",
|
|
997
|
+
85: "am",
|
|
998
|
+
86: "ti",
|
|
999
|
+
87: "om",
|
|
1000
|
+
88: "so",
|
|
1001
|
+
89: "sw",
|
|
1002
|
+
90: "rw",
|
|
1003
|
+
91: "rn",
|
|
1004
|
+
92: "ny",
|
|
1005
|
+
93: "mg",
|
|
1006
|
+
94: "eo",
|
|
1007
|
+
128: "cy",
|
|
1008
|
+
129: "eu",
|
|
1009
|
+
130: "ca",
|
|
1010
|
+
131: "la",
|
|
1011
|
+
132: "qu",
|
|
1012
|
+
133: "gn",
|
|
1013
|
+
134: "ay",
|
|
1014
|
+
135: "tt",
|
|
1015
|
+
136: "ug",
|
|
1016
|
+
137: "dz",
|
|
1017
|
+
138: "jv",
|
|
1018
|
+
139: "su",
|
|
1019
|
+
140: "gl",
|
|
1020
|
+
141: "af",
|
|
1021
|
+
142: "br",
|
|
1022
|
+
143: "iu",
|
|
1023
|
+
144: "gd",
|
|
1024
|
+
145: "gv",
|
|
1025
|
+
146: "ga",
|
|
1026
|
+
147: "to",
|
|
1027
|
+
148: "el-polyton",
|
|
1028
|
+
149: "kl",
|
|
1029
|
+
150: "az",
|
|
1030
|
+
151: "nn",
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
_WINDOWS_LANGUAGE_CODES = {
|
|
1035
|
+
lang.lower(): code for code, lang in _WINDOWS_LANGUAGES.items()
|
|
1036
|
+
}
|
|
1037
|
+
_MAC_LANGUAGE_CODES = {lang.lower(): code for code, lang in _MAC_LANGUAGES.items()}
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
# MacOS language ID → MacOS script ID
|
|
1041
|
+
#
|
|
1042
|
+
# Note that the script ID is not sufficient to determine what encoding
|
|
1043
|
+
# to use in TrueType files. For some languages, MacOS used a modification
|
|
1044
|
+
# of a mainstream script. For example, an Icelandic name would be stored
|
|
1045
|
+
# with smRoman in the TrueType naming table, but the actual encoding
|
|
1046
|
+
# is a special Icelandic version of the normal Macintosh Roman encoding.
|
|
1047
|
+
# As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal
|
|
1048
|
+
# Syllables but MacOS had run out of available script codes, so this was
|
|
1049
|
+
# done as a (pretty radical) “modification” of Ethiopic.
|
|
1050
|
+
#
|
|
1051
|
+
# http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt
|
|
1052
|
+
_MAC_LANGUAGE_TO_SCRIPT = {
|
|
1053
|
+
0: 0, # langEnglish → smRoman
|
|
1054
|
+
1: 0, # langFrench → smRoman
|
|
1055
|
+
2: 0, # langGerman → smRoman
|
|
1056
|
+
3: 0, # langItalian → smRoman
|
|
1057
|
+
4: 0, # langDutch → smRoman
|
|
1058
|
+
5: 0, # langSwedish → smRoman
|
|
1059
|
+
6: 0, # langSpanish → smRoman
|
|
1060
|
+
7: 0, # langDanish → smRoman
|
|
1061
|
+
8: 0, # langPortuguese → smRoman
|
|
1062
|
+
9: 0, # langNorwegian → smRoman
|
|
1063
|
+
10: 5, # langHebrew → smHebrew
|
|
1064
|
+
11: 1, # langJapanese → smJapanese
|
|
1065
|
+
12: 4, # langArabic → smArabic
|
|
1066
|
+
13: 0, # langFinnish → smRoman
|
|
1067
|
+
14: 6, # langGreek → smGreek
|
|
1068
|
+
15: 0, # langIcelandic → smRoman (modified)
|
|
1069
|
+
16: 0, # langMaltese → smRoman
|
|
1070
|
+
17: 0, # langTurkish → smRoman (modified)
|
|
1071
|
+
18: 0, # langCroatian → smRoman (modified)
|
|
1072
|
+
19: 2, # langTradChinese → smTradChinese
|
|
1073
|
+
20: 4, # langUrdu → smArabic
|
|
1074
|
+
21: 9, # langHindi → smDevanagari
|
|
1075
|
+
22: 21, # langThai → smThai
|
|
1076
|
+
23: 3, # langKorean → smKorean
|
|
1077
|
+
24: 29, # langLithuanian → smCentralEuroRoman
|
|
1078
|
+
25: 29, # langPolish → smCentralEuroRoman
|
|
1079
|
+
26: 29, # langHungarian → smCentralEuroRoman
|
|
1080
|
+
27: 29, # langEstonian → smCentralEuroRoman
|
|
1081
|
+
28: 29, # langLatvian → smCentralEuroRoman
|
|
1082
|
+
29: 0, # langSami → smRoman
|
|
1083
|
+
30: 0, # langFaroese → smRoman (modified)
|
|
1084
|
+
31: 4, # langFarsi → smArabic (modified)
|
|
1085
|
+
32: 7, # langRussian → smCyrillic
|
|
1086
|
+
33: 25, # langSimpChinese → smSimpChinese
|
|
1087
|
+
34: 0, # langFlemish → smRoman
|
|
1088
|
+
35: 0, # langIrishGaelic → smRoman (modified)
|
|
1089
|
+
36: 0, # langAlbanian → smRoman
|
|
1090
|
+
37: 0, # langRomanian → smRoman (modified)
|
|
1091
|
+
38: 29, # langCzech → smCentralEuroRoman
|
|
1092
|
+
39: 29, # langSlovak → smCentralEuroRoman
|
|
1093
|
+
40: 0, # langSlovenian → smRoman (modified)
|
|
1094
|
+
41: 5, # langYiddish → smHebrew
|
|
1095
|
+
42: 7, # langSerbian → smCyrillic
|
|
1096
|
+
43: 7, # langMacedonian → smCyrillic
|
|
1097
|
+
44: 7, # langBulgarian → smCyrillic
|
|
1098
|
+
45: 7, # langUkrainian → smCyrillic (modified)
|
|
1099
|
+
46: 7, # langByelorussian → smCyrillic
|
|
1100
|
+
47: 7, # langUzbek → smCyrillic
|
|
1101
|
+
48: 7, # langKazakh → smCyrillic
|
|
1102
|
+
49: 7, # langAzerbaijani → smCyrillic
|
|
1103
|
+
50: 4, # langAzerbaijanAr → smArabic
|
|
1104
|
+
51: 24, # langArmenian → smArmenian
|
|
1105
|
+
52: 23, # langGeorgian → smGeorgian
|
|
1106
|
+
53: 7, # langMoldavian → smCyrillic
|
|
1107
|
+
54: 7, # langKirghiz → smCyrillic
|
|
1108
|
+
55: 7, # langTajiki → smCyrillic
|
|
1109
|
+
56: 7, # langTurkmen → smCyrillic
|
|
1110
|
+
57: 27, # langMongolian → smMongolian
|
|
1111
|
+
58: 7, # langMongolianCyr → smCyrillic
|
|
1112
|
+
59: 4, # langPashto → smArabic
|
|
1113
|
+
60: 4, # langKurdish → smArabic
|
|
1114
|
+
61: 4, # langKashmiri → smArabic
|
|
1115
|
+
62: 4, # langSindhi → smArabic
|
|
1116
|
+
63: 26, # langTibetan → smTibetan
|
|
1117
|
+
64: 9, # langNepali → smDevanagari
|
|
1118
|
+
65: 9, # langSanskrit → smDevanagari
|
|
1119
|
+
66: 9, # langMarathi → smDevanagari
|
|
1120
|
+
67: 13, # langBengali → smBengali
|
|
1121
|
+
68: 13, # langAssamese → smBengali
|
|
1122
|
+
69: 11, # langGujarati → smGujarati
|
|
1123
|
+
70: 10, # langPunjabi → smGurmukhi
|
|
1124
|
+
71: 12, # langOriya → smOriya
|
|
1125
|
+
72: 17, # langMalayalam → smMalayalam
|
|
1126
|
+
73: 16, # langKannada → smKannada
|
|
1127
|
+
74: 14, # langTamil → smTamil
|
|
1128
|
+
75: 15, # langTelugu → smTelugu
|
|
1129
|
+
76: 18, # langSinhalese → smSinhalese
|
|
1130
|
+
77: 19, # langBurmese → smBurmese
|
|
1131
|
+
78: 20, # langKhmer → smKhmer
|
|
1132
|
+
79: 22, # langLao → smLao
|
|
1133
|
+
80: 30, # langVietnamese → smVietnamese
|
|
1134
|
+
81: 0, # langIndonesian → smRoman
|
|
1135
|
+
82: 0, # langTagalog → smRoman
|
|
1136
|
+
83: 0, # langMalayRoman → smRoman
|
|
1137
|
+
84: 4, # langMalayArabic → smArabic
|
|
1138
|
+
85: 28, # langAmharic → smEthiopic
|
|
1139
|
+
86: 28, # langTigrinya → smEthiopic
|
|
1140
|
+
87: 28, # langOromo → smEthiopic
|
|
1141
|
+
88: 0, # langSomali → smRoman
|
|
1142
|
+
89: 0, # langSwahili → smRoman
|
|
1143
|
+
90: 0, # langKinyarwanda → smRoman
|
|
1144
|
+
91: 0, # langRundi → smRoman
|
|
1145
|
+
92: 0, # langNyanja → smRoman
|
|
1146
|
+
93: 0, # langMalagasy → smRoman
|
|
1147
|
+
94: 0, # langEsperanto → smRoman
|
|
1148
|
+
128: 0, # langWelsh → smRoman (modified)
|
|
1149
|
+
129: 0, # langBasque → smRoman
|
|
1150
|
+
130: 0, # langCatalan → smRoman
|
|
1151
|
+
131: 0, # langLatin → smRoman
|
|
1152
|
+
132: 0, # langQuechua → smRoman
|
|
1153
|
+
133: 0, # langGuarani → smRoman
|
|
1154
|
+
134: 0, # langAymara → smRoman
|
|
1155
|
+
135: 7, # langTatar → smCyrillic
|
|
1156
|
+
136: 4, # langUighur → smArabic
|
|
1157
|
+
137: 26, # langDzongkha → smTibetan
|
|
1158
|
+
138: 0, # langJavaneseRom → smRoman
|
|
1159
|
+
139: 0, # langSundaneseRom → smRoman
|
|
1160
|
+
140: 0, # langGalician → smRoman
|
|
1161
|
+
141: 0, # langAfrikaans → smRoman
|
|
1162
|
+
142: 0, # langBreton → smRoman (modified)
|
|
1163
|
+
143: 28, # langInuktitut → smEthiopic (modified)
|
|
1164
|
+
144: 0, # langScottishGaelic → smRoman (modified)
|
|
1165
|
+
145: 0, # langManxGaelic → smRoman (modified)
|
|
1166
|
+
146: 0, # langIrishGaelicScript → smRoman (modified)
|
|
1167
|
+
147: 0, # langTongan → smRoman
|
|
1168
|
+
148: 6, # langGreekAncient → smRoman
|
|
1169
|
+
149: 0, # langGreenlandic → smRoman
|
|
1170
|
+
150: 0, # langAzerbaijanRoman → smRoman
|
|
1171
|
+
151: 0, # langNynorsk → smRoman
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
class NameRecordVisitor(TTVisitor):
|
|
1176
|
+
# Font tables that have NameIDs we need to collect.
|
|
1177
|
+
TABLES = ("GSUB", "GPOS", "fvar", "CPAL", "STAT")
|
|
1178
|
+
|
|
1179
|
+
def __init__(self):
|
|
1180
|
+
self.seen = set()
|
|
1181
|
+
|
|
1182
|
+
|
|
1183
|
+
@NameRecordVisitor.register_attrs(
|
|
1184
|
+
(
|
|
1185
|
+
(otTables.FeatureParamsSize, ("SubfamilyNameID",)),
|
|
1186
|
+
(otTables.FeatureParamsStylisticSet, ("UINameID",)),
|
|
1187
|
+
(otTables.STAT, ("ElidedFallbackNameID",)),
|
|
1188
|
+
(otTables.AxisRecord, ("AxisNameID",)),
|
|
1189
|
+
(otTables.AxisValue, ("ValueNameID",)),
|
|
1190
|
+
(otTables.FeatureName, ("FeatureNameID",)),
|
|
1191
|
+
(otTables.Setting, ("SettingNameID",)),
|
|
1192
|
+
)
|
|
1193
|
+
)
|
|
1194
|
+
def visit(visitor, obj, attr, value):
|
|
1195
|
+
visitor.seen.add(value)
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
@NameRecordVisitor.register(otTables.FeatureParamsCharacterVariants)
|
|
1199
|
+
def visit(visitor, obj):
|
|
1200
|
+
for attr in ("FeatUILabelNameID", "FeatUITooltipTextNameID", "SampleTextNameID"):
|
|
1201
|
+
value = getattr(obj, attr)
|
|
1202
|
+
visitor.seen.add(value)
|
|
1203
|
+
# also include the sequence of UI strings for individual variants, if any
|
|
1204
|
+
if obj.FirstParamUILabelNameID == 0 or obj.NumNamedParameters == 0:
|
|
1205
|
+
return
|
|
1206
|
+
visitor.seen.update(
|
|
1207
|
+
range(
|
|
1208
|
+
obj.FirstParamUILabelNameID,
|
|
1209
|
+
obj.FirstParamUILabelNameID + obj.NumNamedParameters,
|
|
1210
|
+
)
|
|
1211
|
+
)
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
@NameRecordVisitor.register(ttLib.getTableClass("fvar"))
|
|
1215
|
+
def visit(visitor, obj):
|
|
1216
|
+
for inst in obj.instances:
|
|
1217
|
+
if inst.postscriptNameID != 0xFFFF:
|
|
1218
|
+
visitor.seen.add(inst.postscriptNameID)
|
|
1219
|
+
visitor.seen.add(inst.subfamilyNameID)
|
|
1220
|
+
|
|
1221
|
+
for axis in obj.axes:
|
|
1222
|
+
visitor.seen.add(axis.axisNameID)
|
|
1223
|
+
|
|
1224
|
+
|
|
1225
|
+
@NameRecordVisitor.register(ttLib.getTableClass("CPAL"))
|
|
1226
|
+
def visit(visitor, obj):
|
|
1227
|
+
if obj.version == 1:
|
|
1228
|
+
visitor.seen.update(obj.paletteLabels)
|
|
1229
|
+
visitor.seen.update(obj.paletteEntryLabels)
|
|
1230
|
+
|
|
1231
|
+
|
|
1232
|
+
@NameRecordVisitor.register(ttLib.TTFont)
|
|
1233
|
+
def visit(visitor, font, *args, **kwargs):
|
|
1234
|
+
if hasattr(visitor, "font"):
|
|
1235
|
+
return False
|
|
1236
|
+
|
|
1237
|
+
visitor.font = font
|
|
1238
|
+
for tag in visitor.TABLES:
|
|
1239
|
+
if tag in font:
|
|
1240
|
+
visitor.visit(font[tag], *args, **kwargs)
|
|
1241
|
+
del visitor.font
|
|
1242
|
+
return False
|