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,165 @@
|
|
|
1
|
+
# Copyright 2013 Google, Inc. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Google Author(s): Behdad Esfahbod
|
|
4
|
+
|
|
5
|
+
from fontTools.misc.textTools import safeEval
|
|
6
|
+
from . import DefaultTable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class table_C_O_L_R_(DefaultTable.DefaultTable):
|
|
10
|
+
"""Color table
|
|
11
|
+
|
|
12
|
+
The ``COLR`` table defines color presentation of outline glyphs. It must
|
|
13
|
+
be used in concert with the ``CPAL`` table, which contains the color
|
|
14
|
+
descriptors used.
|
|
15
|
+
|
|
16
|
+
This table is structured so that you can treat it like a dictionary keyed by glyph name.
|
|
17
|
+
|
|
18
|
+
``ttFont['COLR'][<glyphName>]`` will return the color layers for any glyph.
|
|
19
|
+
|
|
20
|
+
``ttFont['COLR'][<glyphName>] = <value>`` will set the color layers for any glyph.
|
|
21
|
+
|
|
22
|
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/colr
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def _decompileColorLayersV0(table):
|
|
27
|
+
if not table.LayerRecordArray:
|
|
28
|
+
return {}
|
|
29
|
+
colorLayerLists = {}
|
|
30
|
+
layerRecords = table.LayerRecordArray.LayerRecord
|
|
31
|
+
numLayerRecords = len(layerRecords)
|
|
32
|
+
for baseRec in table.BaseGlyphRecordArray.BaseGlyphRecord:
|
|
33
|
+
baseGlyph = baseRec.BaseGlyph
|
|
34
|
+
firstLayerIndex = baseRec.FirstLayerIndex
|
|
35
|
+
numLayers = baseRec.NumLayers
|
|
36
|
+
assert firstLayerIndex + numLayers <= numLayerRecords
|
|
37
|
+
layers = []
|
|
38
|
+
for i in range(firstLayerIndex, firstLayerIndex + numLayers):
|
|
39
|
+
layerRec = layerRecords[i]
|
|
40
|
+
layers.append(LayerRecord(layerRec.LayerGlyph, layerRec.PaletteIndex))
|
|
41
|
+
colorLayerLists[baseGlyph] = layers
|
|
42
|
+
return colorLayerLists
|
|
43
|
+
|
|
44
|
+
def _toOTTable(self, ttFont):
|
|
45
|
+
from . import otTables
|
|
46
|
+
from fontTools.colorLib.builder import populateCOLRv0
|
|
47
|
+
|
|
48
|
+
tableClass = getattr(otTables, self.tableTag)
|
|
49
|
+
table = tableClass()
|
|
50
|
+
table.Version = self.version
|
|
51
|
+
|
|
52
|
+
populateCOLRv0(
|
|
53
|
+
table,
|
|
54
|
+
{
|
|
55
|
+
baseGlyph: [(layer.name, layer.colorID) for layer in layers]
|
|
56
|
+
for baseGlyph, layers in self.ColorLayers.items()
|
|
57
|
+
},
|
|
58
|
+
glyphMap=ttFont.getReverseGlyphMap(rebuild=True),
|
|
59
|
+
)
|
|
60
|
+
return table
|
|
61
|
+
|
|
62
|
+
def decompile(self, data, ttFont):
|
|
63
|
+
from .otBase import OTTableReader
|
|
64
|
+
from . import otTables
|
|
65
|
+
|
|
66
|
+
# We use otData to decompile, but we adapt the decompiled otTables to the
|
|
67
|
+
# existing COLR v0 API for backward compatibility.
|
|
68
|
+
reader = OTTableReader(data, tableTag=self.tableTag)
|
|
69
|
+
tableClass = getattr(otTables, self.tableTag)
|
|
70
|
+
table = tableClass()
|
|
71
|
+
table.decompile(reader, ttFont)
|
|
72
|
+
|
|
73
|
+
self.version = table.Version
|
|
74
|
+
if self.version == 0:
|
|
75
|
+
self.ColorLayers = self._decompileColorLayersV0(table)
|
|
76
|
+
else:
|
|
77
|
+
# for new versions, keep the raw otTables around
|
|
78
|
+
self.table = table
|
|
79
|
+
|
|
80
|
+
def compile(self, ttFont):
|
|
81
|
+
from .otBase import OTTableWriter
|
|
82
|
+
|
|
83
|
+
if hasattr(self, "table"):
|
|
84
|
+
table = self.table
|
|
85
|
+
else:
|
|
86
|
+
table = self._toOTTable(ttFont)
|
|
87
|
+
|
|
88
|
+
writer = OTTableWriter(tableTag=self.tableTag)
|
|
89
|
+
table.compile(writer, ttFont)
|
|
90
|
+
return writer.getAllData()
|
|
91
|
+
|
|
92
|
+
def toXML(self, writer, ttFont):
|
|
93
|
+
if hasattr(self, "table"):
|
|
94
|
+
self.table.toXML2(writer, ttFont)
|
|
95
|
+
else:
|
|
96
|
+
writer.simpletag("version", value=self.version)
|
|
97
|
+
writer.newline()
|
|
98
|
+
for baseGlyph in sorted(self.ColorLayers.keys(), key=ttFont.getGlyphID):
|
|
99
|
+
writer.begintag("ColorGlyph", name=baseGlyph)
|
|
100
|
+
writer.newline()
|
|
101
|
+
for layer in self.ColorLayers[baseGlyph]:
|
|
102
|
+
layer.toXML(writer, ttFont)
|
|
103
|
+
writer.endtag("ColorGlyph")
|
|
104
|
+
writer.newline()
|
|
105
|
+
|
|
106
|
+
def fromXML(self, name, attrs, content, ttFont):
|
|
107
|
+
if name == "version": # old COLR v0 API
|
|
108
|
+
setattr(self, name, safeEval(attrs["value"]))
|
|
109
|
+
elif name == "ColorGlyph":
|
|
110
|
+
if not hasattr(self, "ColorLayers"):
|
|
111
|
+
self.ColorLayers = {}
|
|
112
|
+
glyphName = attrs["name"]
|
|
113
|
+
for element in content:
|
|
114
|
+
if isinstance(element, str):
|
|
115
|
+
continue
|
|
116
|
+
layers = []
|
|
117
|
+
for element in content:
|
|
118
|
+
if isinstance(element, str):
|
|
119
|
+
continue
|
|
120
|
+
layer = LayerRecord()
|
|
121
|
+
layer.fromXML(element[0], element[1], element[2], ttFont)
|
|
122
|
+
layers.append(layer)
|
|
123
|
+
self.ColorLayers[glyphName] = layers
|
|
124
|
+
else: # new COLR v1 API
|
|
125
|
+
from . import otTables
|
|
126
|
+
|
|
127
|
+
if not hasattr(self, "table"):
|
|
128
|
+
tableClass = getattr(otTables, self.tableTag)
|
|
129
|
+
self.table = tableClass()
|
|
130
|
+
self.table.fromXML(name, attrs, content, ttFont)
|
|
131
|
+
self.table.populateDefaults()
|
|
132
|
+
self.version = self.table.Version
|
|
133
|
+
|
|
134
|
+
def __getitem__(self, glyphName):
|
|
135
|
+
if not isinstance(glyphName, str):
|
|
136
|
+
raise TypeError(f"expected str, found {type(glyphName).__name__}")
|
|
137
|
+
return self.ColorLayers[glyphName]
|
|
138
|
+
|
|
139
|
+
def __setitem__(self, glyphName, value):
|
|
140
|
+
if not isinstance(glyphName, str):
|
|
141
|
+
raise TypeError(f"expected str, found {type(glyphName).__name__}")
|
|
142
|
+
if value is not None:
|
|
143
|
+
self.ColorLayers[glyphName] = value
|
|
144
|
+
elif glyphName in self.ColorLayers:
|
|
145
|
+
del self.ColorLayers[glyphName]
|
|
146
|
+
|
|
147
|
+
def __delitem__(self, glyphName):
|
|
148
|
+
del self.ColorLayers[glyphName]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class LayerRecord(object):
|
|
152
|
+
def __init__(self, name=None, colorID=None):
|
|
153
|
+
self.name = name
|
|
154
|
+
self.colorID = colorID
|
|
155
|
+
|
|
156
|
+
def toXML(self, writer, ttFont):
|
|
157
|
+
writer.simpletag("layer", name=self.name, colorID=self.colorID)
|
|
158
|
+
writer.newline()
|
|
159
|
+
|
|
160
|
+
def fromXML(self, eltname, attrs, content, ttFont):
|
|
161
|
+
for name, value in attrs.items():
|
|
162
|
+
if name == "name":
|
|
163
|
+
setattr(self, name, value)
|
|
164
|
+
else:
|
|
165
|
+
setattr(self, name, safeEval(value))
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# Copyright 2013 Google, Inc. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Google Author(s): Behdad Esfahbod
|
|
4
|
+
|
|
5
|
+
from fontTools.misc.textTools import bytesjoin, safeEval
|
|
6
|
+
from . import DefaultTable
|
|
7
|
+
import array
|
|
8
|
+
from collections import namedtuple
|
|
9
|
+
import struct
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class table_C_P_A_L_(DefaultTable.DefaultTable):
|
|
14
|
+
"""Color Palette table
|
|
15
|
+
|
|
16
|
+
The ``CPAL`` table contains a set of one or more color palettes. The color
|
|
17
|
+
records in each palette can be referenced by the ``COLR`` table to specify
|
|
18
|
+
the colors used in a color glyph.
|
|
19
|
+
|
|
20
|
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/cpal
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
NO_NAME_ID = 0xFFFF
|
|
24
|
+
DEFAULT_PALETTE_TYPE = 0
|
|
25
|
+
|
|
26
|
+
def __init__(self, tag=None):
|
|
27
|
+
DefaultTable.DefaultTable.__init__(self, tag)
|
|
28
|
+
self.palettes = []
|
|
29
|
+
self.paletteTypes = []
|
|
30
|
+
self.paletteLabels = []
|
|
31
|
+
self.paletteEntryLabels = []
|
|
32
|
+
|
|
33
|
+
def decompile(self, data, ttFont):
|
|
34
|
+
(
|
|
35
|
+
self.version,
|
|
36
|
+
self.numPaletteEntries,
|
|
37
|
+
numPalettes,
|
|
38
|
+
numColorRecords,
|
|
39
|
+
goffsetFirstColorRecord,
|
|
40
|
+
) = struct.unpack(">HHHHL", data[:12])
|
|
41
|
+
assert (
|
|
42
|
+
self.version <= 1
|
|
43
|
+
), "Version of CPAL table is higher than I know how to handle"
|
|
44
|
+
self.palettes = []
|
|
45
|
+
pos = 12
|
|
46
|
+
for i in range(numPalettes):
|
|
47
|
+
startIndex = struct.unpack(">H", data[pos : pos + 2])[0]
|
|
48
|
+
assert startIndex + self.numPaletteEntries <= numColorRecords
|
|
49
|
+
pos += 2
|
|
50
|
+
palette = []
|
|
51
|
+
ppos = goffsetFirstColorRecord + startIndex * 4
|
|
52
|
+
for j in range(self.numPaletteEntries):
|
|
53
|
+
palette.append(Color(*struct.unpack(">BBBB", data[ppos : ppos + 4])))
|
|
54
|
+
ppos += 4
|
|
55
|
+
self.palettes.append(palette)
|
|
56
|
+
if self.version == 0:
|
|
57
|
+
offsetToPaletteTypeArray = 0
|
|
58
|
+
offsetToPaletteLabelArray = 0
|
|
59
|
+
offsetToPaletteEntryLabelArray = 0
|
|
60
|
+
else:
|
|
61
|
+
pos = 12 + numPalettes * 2
|
|
62
|
+
(
|
|
63
|
+
offsetToPaletteTypeArray,
|
|
64
|
+
offsetToPaletteLabelArray,
|
|
65
|
+
offsetToPaletteEntryLabelArray,
|
|
66
|
+
) = struct.unpack(">LLL", data[pos : pos + 12])
|
|
67
|
+
self.paletteTypes = self._decompileUInt32Array(
|
|
68
|
+
data,
|
|
69
|
+
offsetToPaletteTypeArray,
|
|
70
|
+
numPalettes,
|
|
71
|
+
default=self.DEFAULT_PALETTE_TYPE,
|
|
72
|
+
)
|
|
73
|
+
self.paletteLabels = self._decompileUInt16Array(
|
|
74
|
+
data, offsetToPaletteLabelArray, numPalettes, default=self.NO_NAME_ID
|
|
75
|
+
)
|
|
76
|
+
self.paletteEntryLabels = self._decompileUInt16Array(
|
|
77
|
+
data,
|
|
78
|
+
offsetToPaletteEntryLabelArray,
|
|
79
|
+
self.numPaletteEntries,
|
|
80
|
+
default=self.NO_NAME_ID,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def _decompileUInt16Array(self, data, offset, numElements, default=0):
|
|
84
|
+
if offset == 0:
|
|
85
|
+
return [default] * numElements
|
|
86
|
+
result = array.array("H", data[offset : offset + 2 * numElements])
|
|
87
|
+
if sys.byteorder != "big":
|
|
88
|
+
result.byteswap()
|
|
89
|
+
assert len(result) == numElements, result
|
|
90
|
+
return result.tolist()
|
|
91
|
+
|
|
92
|
+
def _decompileUInt32Array(self, data, offset, numElements, default=0):
|
|
93
|
+
if offset == 0:
|
|
94
|
+
return [default] * numElements
|
|
95
|
+
result = array.array("I", data[offset : offset + 4 * numElements])
|
|
96
|
+
if sys.byteorder != "big":
|
|
97
|
+
result.byteswap()
|
|
98
|
+
assert len(result) == numElements, result
|
|
99
|
+
return result.tolist()
|
|
100
|
+
|
|
101
|
+
def compile(self, ttFont):
|
|
102
|
+
colorRecordIndices, colorRecords = self._compileColorRecords()
|
|
103
|
+
paletteTypes = self._compilePaletteTypes()
|
|
104
|
+
paletteLabels = self._compilePaletteLabels()
|
|
105
|
+
paletteEntryLabels = self._compilePaletteEntryLabels()
|
|
106
|
+
numColorRecords = len(colorRecords) // 4
|
|
107
|
+
offsetToFirstColorRecord = 12 + len(colorRecordIndices)
|
|
108
|
+
if self.version >= 1:
|
|
109
|
+
offsetToFirstColorRecord += 12
|
|
110
|
+
header = struct.pack(
|
|
111
|
+
">HHHHL",
|
|
112
|
+
self.version,
|
|
113
|
+
self.numPaletteEntries,
|
|
114
|
+
len(self.palettes),
|
|
115
|
+
numColorRecords,
|
|
116
|
+
offsetToFirstColorRecord,
|
|
117
|
+
)
|
|
118
|
+
if self.version == 0:
|
|
119
|
+
dataList = [header, colorRecordIndices, colorRecords]
|
|
120
|
+
else:
|
|
121
|
+
pos = offsetToFirstColorRecord + len(colorRecords)
|
|
122
|
+
if len(paletteTypes) == 0:
|
|
123
|
+
offsetToPaletteTypeArray = 0
|
|
124
|
+
else:
|
|
125
|
+
offsetToPaletteTypeArray = pos
|
|
126
|
+
pos += len(paletteTypes)
|
|
127
|
+
if len(paletteLabels) == 0:
|
|
128
|
+
offsetToPaletteLabelArray = 0
|
|
129
|
+
else:
|
|
130
|
+
offsetToPaletteLabelArray = pos
|
|
131
|
+
pos += len(paletteLabels)
|
|
132
|
+
if len(paletteEntryLabels) == 0:
|
|
133
|
+
offsetToPaletteEntryLabelArray = 0
|
|
134
|
+
else:
|
|
135
|
+
offsetToPaletteEntryLabelArray = pos
|
|
136
|
+
pos += len(paletteLabels)
|
|
137
|
+
header1 = struct.pack(
|
|
138
|
+
">LLL",
|
|
139
|
+
offsetToPaletteTypeArray,
|
|
140
|
+
offsetToPaletteLabelArray,
|
|
141
|
+
offsetToPaletteEntryLabelArray,
|
|
142
|
+
)
|
|
143
|
+
dataList = [
|
|
144
|
+
header,
|
|
145
|
+
colorRecordIndices,
|
|
146
|
+
header1,
|
|
147
|
+
colorRecords,
|
|
148
|
+
paletteTypes,
|
|
149
|
+
paletteLabels,
|
|
150
|
+
paletteEntryLabels,
|
|
151
|
+
]
|
|
152
|
+
return bytesjoin(dataList)
|
|
153
|
+
|
|
154
|
+
def _compilePalette(self, palette):
|
|
155
|
+
assert len(palette) == self.numPaletteEntries
|
|
156
|
+
pack = lambda c: struct.pack(">BBBB", c.blue, c.green, c.red, c.alpha)
|
|
157
|
+
return bytesjoin([pack(color) for color in palette])
|
|
158
|
+
|
|
159
|
+
def _compileColorRecords(self):
|
|
160
|
+
colorRecords, colorRecordIndices, pool = [], [], {}
|
|
161
|
+
for palette in self.palettes:
|
|
162
|
+
packedPalette = self._compilePalette(palette)
|
|
163
|
+
if packedPalette in pool:
|
|
164
|
+
index = pool[packedPalette]
|
|
165
|
+
else:
|
|
166
|
+
index = len(colorRecords)
|
|
167
|
+
colorRecords.append(packedPalette)
|
|
168
|
+
pool[packedPalette] = index
|
|
169
|
+
colorRecordIndices.append(struct.pack(">H", index * self.numPaletteEntries))
|
|
170
|
+
return bytesjoin(colorRecordIndices), bytesjoin(colorRecords)
|
|
171
|
+
|
|
172
|
+
def _compilePaletteTypes(self):
|
|
173
|
+
if self.version == 0 or not any(self.paletteTypes):
|
|
174
|
+
return b""
|
|
175
|
+
assert len(self.paletteTypes) == len(self.palettes)
|
|
176
|
+
result = bytesjoin([struct.pack(">I", ptype) for ptype in self.paletteTypes])
|
|
177
|
+
assert len(result) == 4 * len(self.palettes)
|
|
178
|
+
return result
|
|
179
|
+
|
|
180
|
+
def _compilePaletteLabels(self):
|
|
181
|
+
if self.version == 0 or all(l == self.NO_NAME_ID for l in self.paletteLabels):
|
|
182
|
+
return b""
|
|
183
|
+
assert len(self.paletteLabels) == len(self.palettes)
|
|
184
|
+
result = bytesjoin([struct.pack(">H", label) for label in self.paletteLabels])
|
|
185
|
+
assert len(result) == 2 * len(self.palettes)
|
|
186
|
+
return result
|
|
187
|
+
|
|
188
|
+
def _compilePaletteEntryLabels(self):
|
|
189
|
+
if self.version == 0 or all(
|
|
190
|
+
l == self.NO_NAME_ID for l in self.paletteEntryLabels
|
|
191
|
+
):
|
|
192
|
+
return b""
|
|
193
|
+
assert len(self.paletteEntryLabels) == self.numPaletteEntries
|
|
194
|
+
result = bytesjoin(
|
|
195
|
+
[struct.pack(">H", label) for label in self.paletteEntryLabels]
|
|
196
|
+
)
|
|
197
|
+
assert len(result) == 2 * self.numPaletteEntries
|
|
198
|
+
return result
|
|
199
|
+
|
|
200
|
+
def toXML(self, writer, ttFont):
|
|
201
|
+
numPalettes = len(self.palettes)
|
|
202
|
+
paletteLabels = {i: nameID for (i, nameID) in enumerate(self.paletteLabels)}
|
|
203
|
+
paletteTypes = {i: typ for (i, typ) in enumerate(self.paletteTypes)}
|
|
204
|
+
writer.simpletag("version", value=self.version)
|
|
205
|
+
writer.newline()
|
|
206
|
+
writer.simpletag("numPaletteEntries", value=self.numPaletteEntries)
|
|
207
|
+
writer.newline()
|
|
208
|
+
for index, palette in enumerate(self.palettes):
|
|
209
|
+
attrs = {"index": index}
|
|
210
|
+
paletteType = paletteTypes.get(index, self.DEFAULT_PALETTE_TYPE)
|
|
211
|
+
paletteLabel = paletteLabels.get(index, self.NO_NAME_ID)
|
|
212
|
+
if self.version > 0 and paletteLabel != self.NO_NAME_ID:
|
|
213
|
+
attrs["label"] = paletteLabel
|
|
214
|
+
if self.version > 0 and paletteType != self.DEFAULT_PALETTE_TYPE:
|
|
215
|
+
attrs["type"] = paletteType
|
|
216
|
+
writer.begintag("palette", **attrs)
|
|
217
|
+
writer.newline()
|
|
218
|
+
if (
|
|
219
|
+
self.version > 0
|
|
220
|
+
and paletteLabel != self.NO_NAME_ID
|
|
221
|
+
and ttFont
|
|
222
|
+
and "name" in ttFont
|
|
223
|
+
):
|
|
224
|
+
name = ttFont["name"].getDebugName(paletteLabel)
|
|
225
|
+
if name is not None:
|
|
226
|
+
writer.comment(name)
|
|
227
|
+
writer.newline()
|
|
228
|
+
assert len(palette) == self.numPaletteEntries
|
|
229
|
+
for cindex, color in enumerate(palette):
|
|
230
|
+
color.toXML(writer, ttFont, cindex)
|
|
231
|
+
writer.endtag("palette")
|
|
232
|
+
writer.newline()
|
|
233
|
+
if self.version > 0 and not all(
|
|
234
|
+
l == self.NO_NAME_ID for l in self.paletteEntryLabels
|
|
235
|
+
):
|
|
236
|
+
writer.begintag("paletteEntryLabels")
|
|
237
|
+
writer.newline()
|
|
238
|
+
for index, label in enumerate(self.paletteEntryLabels):
|
|
239
|
+
if label != self.NO_NAME_ID:
|
|
240
|
+
writer.simpletag("label", index=index, value=label)
|
|
241
|
+
if self.version > 0 and label and ttFont and "name" in ttFont:
|
|
242
|
+
name = ttFont["name"].getDebugName(label)
|
|
243
|
+
if name is not None:
|
|
244
|
+
writer.comment(name)
|
|
245
|
+
writer.newline()
|
|
246
|
+
writer.endtag("paletteEntryLabels")
|
|
247
|
+
writer.newline()
|
|
248
|
+
|
|
249
|
+
def fromXML(self, name, attrs, content, ttFont):
|
|
250
|
+
if name == "palette":
|
|
251
|
+
self.paletteLabels.append(int(attrs.get("label", self.NO_NAME_ID)))
|
|
252
|
+
self.paletteTypes.append(int(attrs.get("type", self.DEFAULT_PALETTE_TYPE)))
|
|
253
|
+
palette = []
|
|
254
|
+
for element in content:
|
|
255
|
+
if isinstance(element, str):
|
|
256
|
+
continue
|
|
257
|
+
attrs = element[1]
|
|
258
|
+
color = Color.fromHex(attrs["value"])
|
|
259
|
+
palette.append(color)
|
|
260
|
+
self.palettes.append(palette)
|
|
261
|
+
elif name == "paletteEntryLabels":
|
|
262
|
+
colorLabels = {}
|
|
263
|
+
for element in content:
|
|
264
|
+
if isinstance(element, str):
|
|
265
|
+
continue
|
|
266
|
+
elementName, elementAttr, _ = element
|
|
267
|
+
if elementName == "label":
|
|
268
|
+
labelIndex = safeEval(elementAttr["index"])
|
|
269
|
+
nameID = safeEval(elementAttr["value"])
|
|
270
|
+
colorLabels[labelIndex] = nameID
|
|
271
|
+
self.paletteEntryLabels = [
|
|
272
|
+
colorLabels.get(i, self.NO_NAME_ID)
|
|
273
|
+
for i in range(self.numPaletteEntries)
|
|
274
|
+
]
|
|
275
|
+
elif "value" in attrs:
|
|
276
|
+
value = safeEval(attrs["value"])
|
|
277
|
+
setattr(self, name, value)
|
|
278
|
+
if name == "numPaletteEntries":
|
|
279
|
+
self.paletteEntryLabels = [self.NO_NAME_ID] * self.numPaletteEntries
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class Color(namedtuple("Color", "blue green red alpha")):
|
|
283
|
+
def hex(self):
|
|
284
|
+
return "#%02X%02X%02X%02X" % (self.red, self.green, self.blue, self.alpha)
|
|
285
|
+
|
|
286
|
+
def __repr__(self):
|
|
287
|
+
return self.hex()
|
|
288
|
+
|
|
289
|
+
def toXML(self, writer, ttFont, index=None):
|
|
290
|
+
writer.simpletag("color", value=self.hex(), index=index)
|
|
291
|
+
writer.newline()
|
|
292
|
+
|
|
293
|
+
@classmethod
|
|
294
|
+
def fromHex(cls, value):
|
|
295
|
+
if value[0] == "#":
|
|
296
|
+
value = value[1:]
|
|
297
|
+
red = int(value[0:2], 16)
|
|
298
|
+
green = int(value[2:4], 16)
|
|
299
|
+
blue = int(value[4:6], 16)
|
|
300
|
+
alpha = int(value[6:8], 16) if len(value) >= 8 else 0xFF
|
|
301
|
+
return cls(red=red, green=green, blue=blue, alpha=alpha)
|
|
302
|
+
|
|
303
|
+
@classmethod
|
|
304
|
+
def fromRGBA(cls, red, green, blue, alpha):
|
|
305
|
+
return cls(red=red, green=green, blue=blue, alpha=alpha)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
from fontTools.misc.textTools import bytesjoin, strjoin, tobytes, tostr, safeEval
|
|
2
|
+
from fontTools.misc import sstruct
|
|
3
|
+
from . import DefaultTable
|
|
4
|
+
import base64
|
|
5
|
+
|
|
6
|
+
DSIG_HeaderFormat = """
|
|
7
|
+
> # big endian
|
|
8
|
+
ulVersion: L
|
|
9
|
+
usNumSigs: H
|
|
10
|
+
usFlag: H
|
|
11
|
+
"""
|
|
12
|
+
# followed by an array of usNumSigs DSIG_Signature records
|
|
13
|
+
DSIG_SignatureFormat = """
|
|
14
|
+
> # big endian
|
|
15
|
+
ulFormat: L
|
|
16
|
+
ulLength: L # length includes DSIG_SignatureBlock header
|
|
17
|
+
ulOffset: L
|
|
18
|
+
"""
|
|
19
|
+
# followed by an array of usNumSigs DSIG_SignatureBlock records,
|
|
20
|
+
# each followed immediately by the pkcs7 bytes
|
|
21
|
+
DSIG_SignatureBlockFormat = """
|
|
22
|
+
> # big endian
|
|
23
|
+
usReserved1: H
|
|
24
|
+
usReserved2: H
|
|
25
|
+
cbSignature: l # length of following raw pkcs7 data
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
#
|
|
29
|
+
# NOTE
|
|
30
|
+
# the DSIG table format allows for SignatureBlocks residing
|
|
31
|
+
# anywhere in the table and possibly in a different order as
|
|
32
|
+
# listed in the array after the first table header
|
|
33
|
+
#
|
|
34
|
+
# this implementation does not keep track of any gaps and/or data
|
|
35
|
+
# before or after the actual signature blocks while decompiling,
|
|
36
|
+
# and puts them in the same physical order as listed in the header
|
|
37
|
+
# on compilation with no padding whatsoever.
|
|
38
|
+
#
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class table_D_S_I_G_(DefaultTable.DefaultTable):
|
|
42
|
+
"""Digital Signature table
|
|
43
|
+
|
|
44
|
+
The ``DSIG`` table contains cryptographic signatures for the font.
|
|
45
|
+
|
|
46
|
+
See also https://learn.microsoft.com/en-us/typography/opentype/spec/dsig
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def decompile(self, data, ttFont):
|
|
50
|
+
dummy, newData = sstruct.unpack2(DSIG_HeaderFormat, data, self)
|
|
51
|
+
assert self.ulVersion == 1, "DSIG ulVersion must be 1"
|
|
52
|
+
assert self.usFlag & ~1 == 0, "DSIG usFlag must be 0x1 or 0x0"
|
|
53
|
+
self.signatureRecords = sigrecs = []
|
|
54
|
+
for n in range(self.usNumSigs):
|
|
55
|
+
sigrec, newData = sstruct.unpack2(
|
|
56
|
+
DSIG_SignatureFormat, newData, SignatureRecord()
|
|
57
|
+
)
|
|
58
|
+
assert sigrec.ulFormat == 1, (
|
|
59
|
+
"DSIG signature record #%d ulFormat must be 1" % n
|
|
60
|
+
)
|
|
61
|
+
sigrecs.append(sigrec)
|
|
62
|
+
for sigrec in sigrecs:
|
|
63
|
+
dummy, newData = sstruct.unpack2(
|
|
64
|
+
DSIG_SignatureBlockFormat, data[sigrec.ulOffset :], sigrec
|
|
65
|
+
)
|
|
66
|
+
assert sigrec.usReserved1 == 0, (
|
|
67
|
+
"DSIG signature record #%d usReserverd1 must be 0" % n
|
|
68
|
+
)
|
|
69
|
+
assert sigrec.usReserved2 == 0, (
|
|
70
|
+
"DSIG signature record #%d usReserverd2 must be 0" % n
|
|
71
|
+
)
|
|
72
|
+
sigrec.pkcs7 = newData[: sigrec.cbSignature]
|
|
73
|
+
|
|
74
|
+
def compile(self, ttFont):
|
|
75
|
+
packed = sstruct.pack(DSIG_HeaderFormat, self)
|
|
76
|
+
headers = [packed]
|
|
77
|
+
offset = len(packed) + self.usNumSigs * sstruct.calcsize(DSIG_SignatureFormat)
|
|
78
|
+
data = []
|
|
79
|
+
for sigrec in self.signatureRecords:
|
|
80
|
+
# first pack signature block
|
|
81
|
+
sigrec.cbSignature = len(sigrec.pkcs7)
|
|
82
|
+
packed = sstruct.pack(DSIG_SignatureBlockFormat, sigrec) + sigrec.pkcs7
|
|
83
|
+
data.append(packed)
|
|
84
|
+
# update redundant length field
|
|
85
|
+
sigrec.ulLength = len(packed)
|
|
86
|
+
# update running table offset
|
|
87
|
+
sigrec.ulOffset = offset
|
|
88
|
+
headers.append(sstruct.pack(DSIG_SignatureFormat, sigrec))
|
|
89
|
+
offset += sigrec.ulLength
|
|
90
|
+
if offset % 2:
|
|
91
|
+
# Pad to even bytes
|
|
92
|
+
data.append(b"\0")
|
|
93
|
+
return bytesjoin(headers + data)
|
|
94
|
+
|
|
95
|
+
def toXML(self, xmlWriter, ttFont):
|
|
96
|
+
xmlWriter.comment(
|
|
97
|
+
"note that the Digital Signature will be invalid after recompilation!"
|
|
98
|
+
)
|
|
99
|
+
xmlWriter.newline()
|
|
100
|
+
xmlWriter.simpletag(
|
|
101
|
+
"tableHeader",
|
|
102
|
+
version=self.ulVersion,
|
|
103
|
+
numSigs=self.usNumSigs,
|
|
104
|
+
flag="0x%X" % self.usFlag,
|
|
105
|
+
)
|
|
106
|
+
for sigrec in self.signatureRecords:
|
|
107
|
+
xmlWriter.newline()
|
|
108
|
+
sigrec.toXML(xmlWriter, ttFont)
|
|
109
|
+
xmlWriter.newline()
|
|
110
|
+
|
|
111
|
+
def fromXML(self, name, attrs, content, ttFont):
|
|
112
|
+
if name == "tableHeader":
|
|
113
|
+
self.signatureRecords = []
|
|
114
|
+
self.ulVersion = safeEval(attrs["version"])
|
|
115
|
+
self.usNumSigs = safeEval(attrs["numSigs"])
|
|
116
|
+
self.usFlag = safeEval(attrs["flag"])
|
|
117
|
+
return
|
|
118
|
+
if name == "SignatureRecord":
|
|
119
|
+
sigrec = SignatureRecord()
|
|
120
|
+
sigrec.fromXML(name, attrs, content, ttFont)
|
|
121
|
+
self.signatureRecords.append(sigrec)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
pem_spam = lambda l, spam={
|
|
125
|
+
"-----BEGIN PKCS7-----": True,
|
|
126
|
+
"-----END PKCS7-----": True,
|
|
127
|
+
"": True,
|
|
128
|
+
}: not spam.get(l.strip())
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def b64encode(b):
|
|
132
|
+
s = base64.b64encode(b)
|
|
133
|
+
# Line-break at 76 chars.
|
|
134
|
+
items = []
|
|
135
|
+
while s:
|
|
136
|
+
items.append(tostr(s[:76]))
|
|
137
|
+
items.append("\n")
|
|
138
|
+
s = s[76:]
|
|
139
|
+
return strjoin(items)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class SignatureRecord(object):
|
|
143
|
+
def __repr__(self):
|
|
144
|
+
return "<%s: %s>" % (self.__class__.__name__, self.__dict__)
|
|
145
|
+
|
|
146
|
+
def toXML(self, writer, ttFont):
|
|
147
|
+
writer.begintag(self.__class__.__name__, format=self.ulFormat)
|
|
148
|
+
writer.newline()
|
|
149
|
+
writer.write_noindent("-----BEGIN PKCS7-----\n")
|
|
150
|
+
writer.write_noindent(b64encode(self.pkcs7))
|
|
151
|
+
writer.write_noindent("-----END PKCS7-----\n")
|
|
152
|
+
writer.endtag(self.__class__.__name__)
|
|
153
|
+
|
|
154
|
+
def fromXML(self, name, attrs, content, ttFont):
|
|
155
|
+
self.ulFormat = safeEval(attrs["format"])
|
|
156
|
+
self.usReserved1 = safeEval(attrs.get("reserved1", "0"))
|
|
157
|
+
self.usReserved2 = safeEval(attrs.get("reserved2", "0"))
|
|
158
|
+
self.pkcs7 = base64.b64decode(tobytes(strjoin(filter(pem_spam, content))))
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from textwrap import indent
|
|
3
|
+
|
|
4
|
+
from . import DefaultTable
|
|
5
|
+
from fontTools.misc.textTools import tostr
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class table_D__e_b_g(DefaultTable.DefaultTable):
|
|
9
|
+
def __init__(self, tag=None):
|
|
10
|
+
DefaultTable.DefaultTable.__init__(self, tag)
|
|
11
|
+
self.data = {}
|
|
12
|
+
|
|
13
|
+
def decompile(self, data, ttFont):
|
|
14
|
+
self.data = json.loads(data)
|
|
15
|
+
|
|
16
|
+
def compile(self, ttFont):
|
|
17
|
+
return json.dumps(self.data).encode("utf-8")
|
|
18
|
+
|
|
19
|
+
def toXML(self, writer, ttFont):
|
|
20
|
+
# make sure json indentation inside CDATA block matches XMLWriter's
|
|
21
|
+
data = json.dumps(self.data, indent=len(writer.indentwhite))
|
|
22
|
+
prefix = tostr(writer.indentwhite) * (writer.indentlevel + 1)
|
|
23
|
+
# but don't indent the first json line so it's adjacent to `<![CDATA[{`
|
|
24
|
+
cdata = indent(data, prefix, lambda ln: ln != "{\n")
|
|
25
|
+
|
|
26
|
+
writer.begintag("json")
|
|
27
|
+
writer.newline()
|
|
28
|
+
writer.writecdata(cdata)
|
|
29
|
+
writer.newline()
|
|
30
|
+
writer.endtag("json")
|
|
31
|
+
writer.newline()
|
|
32
|
+
|
|
33
|
+
def fromXML(self, name, attrs, content, ttFont):
|
|
34
|
+
if name == "json":
|
|
35
|
+
self.data = json.loads("".join(content))
|