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,1208 @@
|
|
|
1
|
+
"""Various low level data validators."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import calendar
|
|
6
|
+
from collections.abc import Mapping, Sequence
|
|
7
|
+
from io import open
|
|
8
|
+
|
|
9
|
+
import fontTools.misc.filesystem as fs
|
|
10
|
+
from typing import Any, Type, Optional, Union
|
|
11
|
+
|
|
12
|
+
from fontTools.annotations import IntFloat
|
|
13
|
+
from fontTools.ufoLib.utils import numberTypes
|
|
14
|
+
|
|
15
|
+
GenericDict = dict[str, tuple[Union[type, tuple[Type[Any], ...]], bool]]
|
|
16
|
+
|
|
17
|
+
# -------
|
|
18
|
+
# Generic
|
|
19
|
+
# -------
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def isDictEnough(value: Any) -> bool:
|
|
23
|
+
"""
|
|
24
|
+
Some objects will likely come in that aren't
|
|
25
|
+
dicts but are dict-ish enough.
|
|
26
|
+
"""
|
|
27
|
+
if isinstance(value, Mapping):
|
|
28
|
+
return True
|
|
29
|
+
for attr in ("keys", "values", "items"):
|
|
30
|
+
if not hasattr(value, attr):
|
|
31
|
+
return False
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def genericTypeValidator(value: Any, typ: Type[Any]) -> bool:
|
|
36
|
+
"""
|
|
37
|
+
Generic. (Added at version 2.)
|
|
38
|
+
"""
|
|
39
|
+
return isinstance(value, typ)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def genericIntListValidator(values: Any, validValues: Sequence[int]) -> bool:
|
|
43
|
+
"""
|
|
44
|
+
Generic. (Added at version 2.)
|
|
45
|
+
"""
|
|
46
|
+
if not isinstance(values, (list, tuple)):
|
|
47
|
+
return False
|
|
48
|
+
valuesSet = set(values)
|
|
49
|
+
validValuesSet = set(validValues)
|
|
50
|
+
if valuesSet - validValuesSet:
|
|
51
|
+
return False
|
|
52
|
+
for value in values:
|
|
53
|
+
if not isinstance(value, int):
|
|
54
|
+
return False
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def genericNonNegativeIntValidator(value: Any) -> bool:
|
|
59
|
+
"""
|
|
60
|
+
Generic. (Added at version 3.)
|
|
61
|
+
"""
|
|
62
|
+
if not isinstance(value, int):
|
|
63
|
+
return False
|
|
64
|
+
if value < 0:
|
|
65
|
+
return False
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def genericNonNegativeNumberValidator(value: Any) -> bool:
|
|
70
|
+
"""
|
|
71
|
+
Generic. (Added at version 3.)
|
|
72
|
+
"""
|
|
73
|
+
if not isinstance(value, numberTypes):
|
|
74
|
+
return False
|
|
75
|
+
if value < 0:
|
|
76
|
+
return False
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def genericDictValidator(value: Any, prototype: GenericDict) -> bool:
|
|
81
|
+
"""
|
|
82
|
+
Generic. (Added at version 3.)
|
|
83
|
+
"""
|
|
84
|
+
# not a dict
|
|
85
|
+
if not isinstance(value, Mapping):
|
|
86
|
+
return False
|
|
87
|
+
# missing required keys
|
|
88
|
+
for key, (typ, required) in prototype.items():
|
|
89
|
+
if not required:
|
|
90
|
+
continue
|
|
91
|
+
if key not in value:
|
|
92
|
+
return False
|
|
93
|
+
# unknown keys
|
|
94
|
+
for key in value.keys():
|
|
95
|
+
if key not in prototype:
|
|
96
|
+
return False
|
|
97
|
+
# incorrect types
|
|
98
|
+
for key, v in value.items():
|
|
99
|
+
prototypeType, required = prototype[key]
|
|
100
|
+
if v is None and not required:
|
|
101
|
+
continue
|
|
102
|
+
if not isinstance(v, prototypeType):
|
|
103
|
+
return False
|
|
104
|
+
return True
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# --------------
|
|
108
|
+
# fontinfo.plist
|
|
109
|
+
# --------------
|
|
110
|
+
|
|
111
|
+
# Data Validators
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def fontInfoStyleMapStyleNameValidator(value: Any) -> bool:
|
|
115
|
+
"""
|
|
116
|
+
Version 2+.
|
|
117
|
+
"""
|
|
118
|
+
options = ["regular", "italic", "bold", "bold italic"]
|
|
119
|
+
return value in options
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def fontInfoOpenTypeGaspRangeRecordsValidator(value: Any) -> bool:
|
|
123
|
+
"""
|
|
124
|
+
Version 3+.
|
|
125
|
+
"""
|
|
126
|
+
if not isinstance(value, list):
|
|
127
|
+
return False
|
|
128
|
+
if len(value) == 0:
|
|
129
|
+
return True
|
|
130
|
+
validBehaviors = [0, 1, 2, 3]
|
|
131
|
+
dictPrototype: GenericDict = dict(
|
|
132
|
+
rangeMaxPPEM=(int, True), rangeGaspBehavior=(list, True)
|
|
133
|
+
)
|
|
134
|
+
ppemOrder = []
|
|
135
|
+
for rangeRecord in value:
|
|
136
|
+
if not genericDictValidator(rangeRecord, dictPrototype):
|
|
137
|
+
return False
|
|
138
|
+
ppem = rangeRecord["rangeMaxPPEM"]
|
|
139
|
+
behavior = rangeRecord["rangeGaspBehavior"]
|
|
140
|
+
ppemValidity = genericNonNegativeIntValidator(ppem)
|
|
141
|
+
if not ppemValidity:
|
|
142
|
+
return False
|
|
143
|
+
behaviorValidity = genericIntListValidator(behavior, validBehaviors)
|
|
144
|
+
if not behaviorValidity:
|
|
145
|
+
return False
|
|
146
|
+
ppemOrder.append(ppem)
|
|
147
|
+
if ppemOrder != sorted(ppemOrder):
|
|
148
|
+
return False
|
|
149
|
+
return True
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def fontInfoOpenTypeHeadCreatedValidator(value: Any) -> bool:
|
|
153
|
+
"""
|
|
154
|
+
Version 2+.
|
|
155
|
+
"""
|
|
156
|
+
# format: 0000/00/00 00:00:00
|
|
157
|
+
if not isinstance(value, str):
|
|
158
|
+
return False
|
|
159
|
+
# basic formatting
|
|
160
|
+
if not len(value) == 19:
|
|
161
|
+
return False
|
|
162
|
+
if value.count(" ") != 1:
|
|
163
|
+
return False
|
|
164
|
+
strDate, strTime = value.split(" ")
|
|
165
|
+
if strDate.count("/") != 2:
|
|
166
|
+
return False
|
|
167
|
+
if strTime.count(":") != 2:
|
|
168
|
+
return False
|
|
169
|
+
# date
|
|
170
|
+
strYear, strMonth, strDay = strDate.split("/")
|
|
171
|
+
if len(strYear) != 4:
|
|
172
|
+
return False
|
|
173
|
+
if len(strMonth) != 2:
|
|
174
|
+
return False
|
|
175
|
+
if len(strDay) != 2:
|
|
176
|
+
return False
|
|
177
|
+
try:
|
|
178
|
+
intYear = int(strYear)
|
|
179
|
+
intMonth = int(strMonth)
|
|
180
|
+
intDay = int(strDay)
|
|
181
|
+
except ValueError:
|
|
182
|
+
return False
|
|
183
|
+
if intMonth < 1 or intMonth > 12:
|
|
184
|
+
return False
|
|
185
|
+
monthMaxDay = calendar.monthrange(intYear, intMonth)[1]
|
|
186
|
+
if intDay < 1 or intDay > monthMaxDay:
|
|
187
|
+
return False
|
|
188
|
+
# time
|
|
189
|
+
strHour, strMinute, strSecond = strTime.split(":")
|
|
190
|
+
if len(strHour) != 2:
|
|
191
|
+
return False
|
|
192
|
+
if len(strMinute) != 2:
|
|
193
|
+
return False
|
|
194
|
+
if len(strSecond) != 2:
|
|
195
|
+
return False
|
|
196
|
+
try:
|
|
197
|
+
intHour = int(strHour)
|
|
198
|
+
intMinute = int(strMinute)
|
|
199
|
+
intSecond = int(strSecond)
|
|
200
|
+
except ValueError:
|
|
201
|
+
return False
|
|
202
|
+
if intHour < 0 or intHour > 23:
|
|
203
|
+
return False
|
|
204
|
+
if intMinute < 0 or intMinute > 59:
|
|
205
|
+
return False
|
|
206
|
+
if intSecond < 0 or intSecond > 59:
|
|
207
|
+
return False
|
|
208
|
+
# fallback
|
|
209
|
+
return True
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def fontInfoOpenTypeNameRecordsValidator(value: Any) -> bool:
|
|
213
|
+
"""
|
|
214
|
+
Version 3+.
|
|
215
|
+
"""
|
|
216
|
+
if not isinstance(value, list):
|
|
217
|
+
return False
|
|
218
|
+
dictPrototype: GenericDict = dict(
|
|
219
|
+
nameID=(int, True),
|
|
220
|
+
platformID=(int, True),
|
|
221
|
+
encodingID=(int, True),
|
|
222
|
+
languageID=(int, True),
|
|
223
|
+
string=(str, True),
|
|
224
|
+
)
|
|
225
|
+
for nameRecord in value:
|
|
226
|
+
if not genericDictValidator(nameRecord, dictPrototype):
|
|
227
|
+
return False
|
|
228
|
+
return True
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def fontInfoOpenTypeOS2WeightClassValidator(value: Any) -> bool:
|
|
232
|
+
"""
|
|
233
|
+
Version 2+.
|
|
234
|
+
"""
|
|
235
|
+
if not isinstance(value, int):
|
|
236
|
+
return False
|
|
237
|
+
if value < 0:
|
|
238
|
+
return False
|
|
239
|
+
return True
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def fontInfoOpenTypeOS2WidthClassValidator(value: Any) -> bool:
|
|
243
|
+
"""
|
|
244
|
+
Version 2+.
|
|
245
|
+
"""
|
|
246
|
+
if not isinstance(value, int):
|
|
247
|
+
return False
|
|
248
|
+
if value < 1:
|
|
249
|
+
return False
|
|
250
|
+
if value > 9:
|
|
251
|
+
return False
|
|
252
|
+
return True
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def fontInfoVersion2OpenTypeOS2PanoseValidator(values: Any) -> bool:
|
|
256
|
+
"""
|
|
257
|
+
Version 2.
|
|
258
|
+
"""
|
|
259
|
+
if not isinstance(values, (list, tuple)):
|
|
260
|
+
return False
|
|
261
|
+
if len(values) != 10:
|
|
262
|
+
return False
|
|
263
|
+
for value in values:
|
|
264
|
+
if not isinstance(value, int):
|
|
265
|
+
return False
|
|
266
|
+
# XXX further validation?
|
|
267
|
+
return True
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def fontInfoVersion3OpenTypeOS2PanoseValidator(values: Any) -> bool:
|
|
271
|
+
"""
|
|
272
|
+
Version 3+.
|
|
273
|
+
"""
|
|
274
|
+
if not isinstance(values, (list, tuple)):
|
|
275
|
+
return False
|
|
276
|
+
if len(values) != 10:
|
|
277
|
+
return False
|
|
278
|
+
for value in values:
|
|
279
|
+
if not isinstance(value, int):
|
|
280
|
+
return False
|
|
281
|
+
if value < 0:
|
|
282
|
+
return False
|
|
283
|
+
# XXX further validation?
|
|
284
|
+
return True
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def fontInfoOpenTypeOS2FamilyClassValidator(values: Any) -> bool:
|
|
288
|
+
"""
|
|
289
|
+
Version 2+.
|
|
290
|
+
"""
|
|
291
|
+
if not isinstance(values, (list, tuple)):
|
|
292
|
+
return False
|
|
293
|
+
if len(values) != 2:
|
|
294
|
+
return False
|
|
295
|
+
for value in values:
|
|
296
|
+
if not isinstance(value, int):
|
|
297
|
+
return False
|
|
298
|
+
classID, subclassID = values
|
|
299
|
+
if classID < 0 or classID > 14:
|
|
300
|
+
return False
|
|
301
|
+
if subclassID < 0 or subclassID > 15:
|
|
302
|
+
return False
|
|
303
|
+
return True
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def fontInfoPostscriptBluesValidator(values: Any) -> bool:
|
|
307
|
+
"""
|
|
308
|
+
Version 2+.
|
|
309
|
+
"""
|
|
310
|
+
if not isinstance(values, (list, tuple)):
|
|
311
|
+
return False
|
|
312
|
+
if len(values) > 14:
|
|
313
|
+
return False
|
|
314
|
+
if len(values) % 2:
|
|
315
|
+
return False
|
|
316
|
+
for value in values:
|
|
317
|
+
if not isinstance(value, numberTypes):
|
|
318
|
+
return False
|
|
319
|
+
return True
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def fontInfoPostscriptOtherBluesValidator(values: Any) -> bool:
|
|
323
|
+
"""
|
|
324
|
+
Version 2+.
|
|
325
|
+
"""
|
|
326
|
+
if not isinstance(values, (list, tuple)):
|
|
327
|
+
return False
|
|
328
|
+
if len(values) > 10:
|
|
329
|
+
return False
|
|
330
|
+
if len(values) % 2:
|
|
331
|
+
return False
|
|
332
|
+
for value in values:
|
|
333
|
+
if not isinstance(value, numberTypes):
|
|
334
|
+
return False
|
|
335
|
+
return True
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def fontInfoPostscriptStemsValidator(values: Any) -> bool:
|
|
339
|
+
"""
|
|
340
|
+
Version 2+.
|
|
341
|
+
"""
|
|
342
|
+
if not isinstance(values, (list, tuple)):
|
|
343
|
+
return False
|
|
344
|
+
if len(values) > 12:
|
|
345
|
+
return False
|
|
346
|
+
for value in values:
|
|
347
|
+
if not isinstance(value, numberTypes):
|
|
348
|
+
return False
|
|
349
|
+
return True
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def fontInfoPostscriptWindowsCharacterSetValidator(value: Any) -> bool:
|
|
353
|
+
"""
|
|
354
|
+
Version 2+.
|
|
355
|
+
"""
|
|
356
|
+
validValues = list(range(1, 21))
|
|
357
|
+
if value not in validValues:
|
|
358
|
+
return False
|
|
359
|
+
return True
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def fontInfoWOFFMetadataUniqueIDValidator(value: Any) -> bool:
|
|
363
|
+
"""
|
|
364
|
+
Version 3+.
|
|
365
|
+
"""
|
|
366
|
+
dictPrototype: GenericDict = dict(id=(str, True))
|
|
367
|
+
if not genericDictValidator(value, dictPrototype):
|
|
368
|
+
return False
|
|
369
|
+
return True
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def fontInfoWOFFMetadataVendorValidator(value: Any) -> bool:
|
|
373
|
+
"""
|
|
374
|
+
Version 3+.
|
|
375
|
+
"""
|
|
376
|
+
dictPrototype: GenericDict = {
|
|
377
|
+
"name": (str, True),
|
|
378
|
+
"url": (str, False),
|
|
379
|
+
"dir": (str, False),
|
|
380
|
+
"class": (str, False),
|
|
381
|
+
}
|
|
382
|
+
if not genericDictValidator(value, dictPrototype):
|
|
383
|
+
return False
|
|
384
|
+
if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
|
|
385
|
+
return False
|
|
386
|
+
return True
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def fontInfoWOFFMetadataCreditsValidator(value: Any) -> bool:
|
|
390
|
+
"""
|
|
391
|
+
Version 3+.
|
|
392
|
+
"""
|
|
393
|
+
dictPrototype: GenericDict = dict(credits=(list, True))
|
|
394
|
+
if not genericDictValidator(value, dictPrototype):
|
|
395
|
+
return False
|
|
396
|
+
if not len(value["credits"]):
|
|
397
|
+
return False
|
|
398
|
+
dictPrototype = {
|
|
399
|
+
"name": (str, True),
|
|
400
|
+
"url": (str, False),
|
|
401
|
+
"role": (str, False),
|
|
402
|
+
"dir": (str, False),
|
|
403
|
+
"class": (str, False),
|
|
404
|
+
}
|
|
405
|
+
for credit in value["credits"]:
|
|
406
|
+
if not genericDictValidator(credit, dictPrototype):
|
|
407
|
+
return False
|
|
408
|
+
if "dir" in credit and credit.get("dir") not in ("ltr", "rtl"):
|
|
409
|
+
return False
|
|
410
|
+
return True
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def fontInfoWOFFMetadataDescriptionValidator(value: Any) -> bool:
|
|
414
|
+
"""
|
|
415
|
+
Version 3+.
|
|
416
|
+
"""
|
|
417
|
+
dictPrototype: GenericDict = dict(url=(str, False), text=(list, True))
|
|
418
|
+
if not genericDictValidator(value, dictPrototype):
|
|
419
|
+
return False
|
|
420
|
+
for text in value["text"]:
|
|
421
|
+
if not fontInfoWOFFMetadataTextValue(text):
|
|
422
|
+
return False
|
|
423
|
+
return True
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def fontInfoWOFFMetadataLicenseValidator(value: Any) -> bool:
|
|
427
|
+
"""
|
|
428
|
+
Version 3+.
|
|
429
|
+
"""
|
|
430
|
+
dictPrototype: GenericDict = dict(
|
|
431
|
+
url=(str, False), text=(list, False), id=(str, False)
|
|
432
|
+
)
|
|
433
|
+
if not genericDictValidator(value, dictPrototype):
|
|
434
|
+
return False
|
|
435
|
+
if "text" in value:
|
|
436
|
+
for text in value["text"]:
|
|
437
|
+
if not fontInfoWOFFMetadataTextValue(text):
|
|
438
|
+
return False
|
|
439
|
+
return True
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def fontInfoWOFFMetadataTrademarkValidator(value: Any) -> bool:
|
|
443
|
+
"""
|
|
444
|
+
Version 3+.
|
|
445
|
+
"""
|
|
446
|
+
dictPrototype: GenericDict = dict(text=(list, True))
|
|
447
|
+
if not genericDictValidator(value, dictPrototype):
|
|
448
|
+
return False
|
|
449
|
+
for text in value["text"]:
|
|
450
|
+
if not fontInfoWOFFMetadataTextValue(text):
|
|
451
|
+
return False
|
|
452
|
+
return True
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def fontInfoWOFFMetadataCopyrightValidator(value: Any) -> bool:
|
|
456
|
+
"""
|
|
457
|
+
Version 3+.
|
|
458
|
+
"""
|
|
459
|
+
dictPrototype: GenericDict = dict(text=(list, True))
|
|
460
|
+
if not genericDictValidator(value, dictPrototype):
|
|
461
|
+
return False
|
|
462
|
+
for text in value["text"]:
|
|
463
|
+
if not fontInfoWOFFMetadataTextValue(text):
|
|
464
|
+
return False
|
|
465
|
+
return True
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def fontInfoWOFFMetadataLicenseeValidator(value: Any) -> bool:
|
|
469
|
+
"""
|
|
470
|
+
Version 3+.
|
|
471
|
+
"""
|
|
472
|
+
dictPrototype: GenericDict = {
|
|
473
|
+
"name": (str, True),
|
|
474
|
+
"dir": (str, False),
|
|
475
|
+
"class": (str, False),
|
|
476
|
+
}
|
|
477
|
+
if not genericDictValidator(value, dictPrototype):
|
|
478
|
+
return False
|
|
479
|
+
if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
|
|
480
|
+
return False
|
|
481
|
+
return True
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def fontInfoWOFFMetadataTextValue(value: Any) -> bool:
|
|
485
|
+
"""
|
|
486
|
+
Version 3+.
|
|
487
|
+
"""
|
|
488
|
+
dictPrototype: GenericDict = {
|
|
489
|
+
"text": (str, True),
|
|
490
|
+
"language": (str, False),
|
|
491
|
+
"dir": (str, False),
|
|
492
|
+
"class": (str, False),
|
|
493
|
+
}
|
|
494
|
+
if not genericDictValidator(value, dictPrototype):
|
|
495
|
+
return False
|
|
496
|
+
if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
|
|
497
|
+
return False
|
|
498
|
+
return True
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def fontInfoWOFFMetadataExtensionsValidator(value: Any) -> bool:
|
|
502
|
+
"""
|
|
503
|
+
Version 3+.
|
|
504
|
+
"""
|
|
505
|
+
if not isinstance(value, list):
|
|
506
|
+
return False
|
|
507
|
+
if not value:
|
|
508
|
+
return False
|
|
509
|
+
for extension in value:
|
|
510
|
+
if not fontInfoWOFFMetadataExtensionValidator(extension):
|
|
511
|
+
return False
|
|
512
|
+
return True
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def fontInfoWOFFMetadataExtensionValidator(value: Any) -> bool:
|
|
516
|
+
"""
|
|
517
|
+
Version 3+.
|
|
518
|
+
"""
|
|
519
|
+
dictPrototype: GenericDict = dict(
|
|
520
|
+
names=(list, False), items=(list, True), id=(str, False)
|
|
521
|
+
)
|
|
522
|
+
if not genericDictValidator(value, dictPrototype):
|
|
523
|
+
return False
|
|
524
|
+
if "names" in value:
|
|
525
|
+
for name in value["names"]:
|
|
526
|
+
if not fontInfoWOFFMetadataExtensionNameValidator(name):
|
|
527
|
+
return False
|
|
528
|
+
for item in value["items"]:
|
|
529
|
+
if not fontInfoWOFFMetadataExtensionItemValidator(item):
|
|
530
|
+
return False
|
|
531
|
+
return True
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def fontInfoWOFFMetadataExtensionItemValidator(value: Any) -> bool:
|
|
535
|
+
"""
|
|
536
|
+
Version 3+.
|
|
537
|
+
"""
|
|
538
|
+
dictPrototype: GenericDict = dict(
|
|
539
|
+
id=(str, False), names=(list, True), values=(list, True)
|
|
540
|
+
)
|
|
541
|
+
if not genericDictValidator(value, dictPrototype):
|
|
542
|
+
return False
|
|
543
|
+
for name in value["names"]:
|
|
544
|
+
if not fontInfoWOFFMetadataExtensionNameValidator(name):
|
|
545
|
+
return False
|
|
546
|
+
for val in value["values"]:
|
|
547
|
+
if not fontInfoWOFFMetadataExtensionValueValidator(val):
|
|
548
|
+
return False
|
|
549
|
+
return True
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def fontInfoWOFFMetadataExtensionNameValidator(value: Any) -> bool:
|
|
553
|
+
"""
|
|
554
|
+
Version 3+.
|
|
555
|
+
"""
|
|
556
|
+
dictPrototype: GenericDict = {
|
|
557
|
+
"text": (str, True),
|
|
558
|
+
"language": (str, False),
|
|
559
|
+
"dir": (str, False),
|
|
560
|
+
"class": (str, False),
|
|
561
|
+
}
|
|
562
|
+
if not genericDictValidator(value, dictPrototype):
|
|
563
|
+
return False
|
|
564
|
+
if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
|
|
565
|
+
return False
|
|
566
|
+
return True
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def fontInfoWOFFMetadataExtensionValueValidator(value: Any) -> bool:
|
|
570
|
+
"""
|
|
571
|
+
Version 3+.
|
|
572
|
+
"""
|
|
573
|
+
dictPrototype: GenericDict = {
|
|
574
|
+
"text": (str, True),
|
|
575
|
+
"language": (str, False),
|
|
576
|
+
"dir": (str, False),
|
|
577
|
+
"class": (str, False),
|
|
578
|
+
}
|
|
579
|
+
if not genericDictValidator(value, dictPrototype):
|
|
580
|
+
return False
|
|
581
|
+
if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
|
|
582
|
+
return False
|
|
583
|
+
return True
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
# ----------
|
|
587
|
+
# Guidelines
|
|
588
|
+
# ----------
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def guidelinesValidator(value: Any, identifiers: Optional[set[str]] = None) -> bool:
|
|
592
|
+
"""
|
|
593
|
+
Version 3+.
|
|
594
|
+
"""
|
|
595
|
+
if not isinstance(value, list):
|
|
596
|
+
return False
|
|
597
|
+
if identifiers is None:
|
|
598
|
+
identifiers = set()
|
|
599
|
+
for guide in value:
|
|
600
|
+
if not guidelineValidator(guide):
|
|
601
|
+
return False
|
|
602
|
+
identifier = guide.get("identifier")
|
|
603
|
+
if identifier is not None:
|
|
604
|
+
if identifier in identifiers:
|
|
605
|
+
return False
|
|
606
|
+
identifiers.add(identifier)
|
|
607
|
+
return True
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
_guidelineDictPrototype: GenericDict = dict(
|
|
611
|
+
x=((int, float), False),
|
|
612
|
+
y=((int, float), False),
|
|
613
|
+
angle=((int, float), False),
|
|
614
|
+
name=(str, False),
|
|
615
|
+
color=(str, False),
|
|
616
|
+
identifier=(str, False),
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def guidelineValidator(value: Any) -> bool:
|
|
621
|
+
"""
|
|
622
|
+
Version 3+.
|
|
623
|
+
"""
|
|
624
|
+
if not genericDictValidator(value, _guidelineDictPrototype):
|
|
625
|
+
return False
|
|
626
|
+
x = value.get("x")
|
|
627
|
+
y = value.get("y")
|
|
628
|
+
angle = value.get("angle")
|
|
629
|
+
# x or y must be present
|
|
630
|
+
if x is None and y is None:
|
|
631
|
+
return False
|
|
632
|
+
# if x or y are None, angle must not be present
|
|
633
|
+
if x is None or y is None:
|
|
634
|
+
if angle is not None:
|
|
635
|
+
return False
|
|
636
|
+
# if x and y are defined, angle must be defined
|
|
637
|
+
if x is not None and y is not None and angle is None:
|
|
638
|
+
return False
|
|
639
|
+
# angle must be between 0 and 360
|
|
640
|
+
if angle is not None:
|
|
641
|
+
if angle < 0:
|
|
642
|
+
return False
|
|
643
|
+
if angle > 360:
|
|
644
|
+
return False
|
|
645
|
+
# identifier must be 1 or more characters
|
|
646
|
+
identifier = value.get("identifier")
|
|
647
|
+
if identifier is not None and not identifierValidator(identifier):
|
|
648
|
+
return False
|
|
649
|
+
# color must follow the proper format
|
|
650
|
+
color = value.get("color")
|
|
651
|
+
if color is not None and not colorValidator(color):
|
|
652
|
+
return False
|
|
653
|
+
return True
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
# -------
|
|
657
|
+
# Anchors
|
|
658
|
+
# -------
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def anchorsValidator(value: Any, identifiers: Optional[set[str]] = None) -> bool:
|
|
662
|
+
"""
|
|
663
|
+
Version 3+.
|
|
664
|
+
"""
|
|
665
|
+
if not isinstance(value, list):
|
|
666
|
+
return False
|
|
667
|
+
if identifiers is None:
|
|
668
|
+
identifiers = set()
|
|
669
|
+
for anchor in value:
|
|
670
|
+
if not anchorValidator(anchor):
|
|
671
|
+
return False
|
|
672
|
+
identifier = anchor.get("identifier")
|
|
673
|
+
if identifier is not None:
|
|
674
|
+
if identifier in identifiers:
|
|
675
|
+
return False
|
|
676
|
+
identifiers.add(identifier)
|
|
677
|
+
return True
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
_anchorDictPrototype: GenericDict = dict(
|
|
681
|
+
x=((int, float), False),
|
|
682
|
+
y=((int, float), False),
|
|
683
|
+
name=(str, False),
|
|
684
|
+
color=(str, False),
|
|
685
|
+
identifier=(str, False),
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
def anchorValidator(value: Any) -> bool:
|
|
690
|
+
"""
|
|
691
|
+
Version 3+.
|
|
692
|
+
"""
|
|
693
|
+
if not genericDictValidator(value, _anchorDictPrototype):
|
|
694
|
+
return False
|
|
695
|
+
x = value.get("x")
|
|
696
|
+
y = value.get("y")
|
|
697
|
+
# x and y must be present
|
|
698
|
+
if x is None or y is None:
|
|
699
|
+
return False
|
|
700
|
+
# identifier must be 1 or more characters
|
|
701
|
+
identifier = value.get("identifier")
|
|
702
|
+
if identifier is not None and not identifierValidator(identifier):
|
|
703
|
+
return False
|
|
704
|
+
# color must follow the proper format
|
|
705
|
+
color = value.get("color")
|
|
706
|
+
if color is not None and not colorValidator(color):
|
|
707
|
+
return False
|
|
708
|
+
return True
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
# ----------
|
|
712
|
+
# Identifier
|
|
713
|
+
# ----------
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def identifierValidator(value: Any) -> bool:
|
|
717
|
+
"""
|
|
718
|
+
Version 3+.
|
|
719
|
+
|
|
720
|
+
>>> identifierValidator("a")
|
|
721
|
+
True
|
|
722
|
+
>>> identifierValidator("")
|
|
723
|
+
False
|
|
724
|
+
>>> identifierValidator("a" * 101)
|
|
725
|
+
False
|
|
726
|
+
"""
|
|
727
|
+
validCharactersMin = 0x20
|
|
728
|
+
validCharactersMax = 0x7E
|
|
729
|
+
if not isinstance(value, str):
|
|
730
|
+
return False
|
|
731
|
+
if not value:
|
|
732
|
+
return False
|
|
733
|
+
if len(value) > 100:
|
|
734
|
+
return False
|
|
735
|
+
for c in value:
|
|
736
|
+
i = ord(c)
|
|
737
|
+
if i < validCharactersMin or i > validCharactersMax:
|
|
738
|
+
return False
|
|
739
|
+
return True
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
# -----
|
|
743
|
+
# Color
|
|
744
|
+
# -----
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
def colorValidator(value: Any) -> bool:
|
|
748
|
+
"""
|
|
749
|
+
Version 3+.
|
|
750
|
+
|
|
751
|
+
>>> colorValidator("0,0,0,0")
|
|
752
|
+
True
|
|
753
|
+
>>> colorValidator(".5,.5,.5,.5")
|
|
754
|
+
True
|
|
755
|
+
>>> colorValidator("0.5,0.5,0.5,0.5")
|
|
756
|
+
True
|
|
757
|
+
>>> colorValidator("1,1,1,1")
|
|
758
|
+
True
|
|
759
|
+
|
|
760
|
+
>>> colorValidator("2,0,0,0")
|
|
761
|
+
False
|
|
762
|
+
>>> colorValidator("0,2,0,0")
|
|
763
|
+
False
|
|
764
|
+
>>> colorValidator("0,0,2,0")
|
|
765
|
+
False
|
|
766
|
+
>>> colorValidator("0,0,0,2")
|
|
767
|
+
False
|
|
768
|
+
|
|
769
|
+
>>> colorValidator("1r,1,1,1")
|
|
770
|
+
False
|
|
771
|
+
>>> colorValidator("1,1g,1,1")
|
|
772
|
+
False
|
|
773
|
+
>>> colorValidator("1,1,1b,1")
|
|
774
|
+
False
|
|
775
|
+
>>> colorValidator("1,1,1,1a")
|
|
776
|
+
False
|
|
777
|
+
|
|
778
|
+
>>> colorValidator("1 1 1 1")
|
|
779
|
+
False
|
|
780
|
+
>>> colorValidator("1 1,1,1")
|
|
781
|
+
False
|
|
782
|
+
>>> colorValidator("1,1 1,1")
|
|
783
|
+
False
|
|
784
|
+
>>> colorValidator("1,1,1 1")
|
|
785
|
+
False
|
|
786
|
+
|
|
787
|
+
>>> colorValidator("1, 1, 1, 1")
|
|
788
|
+
True
|
|
789
|
+
"""
|
|
790
|
+
if not isinstance(value, str):
|
|
791
|
+
return False
|
|
792
|
+
parts = value.split(",")
|
|
793
|
+
if len(parts) != 4:
|
|
794
|
+
return False
|
|
795
|
+
for part in parts:
|
|
796
|
+
part = part.strip()
|
|
797
|
+
converted = False
|
|
798
|
+
number: IntFloat
|
|
799
|
+
try:
|
|
800
|
+
number = int(part)
|
|
801
|
+
converted = True
|
|
802
|
+
except ValueError:
|
|
803
|
+
pass
|
|
804
|
+
if not converted:
|
|
805
|
+
try:
|
|
806
|
+
number = float(part)
|
|
807
|
+
converted = True
|
|
808
|
+
except ValueError:
|
|
809
|
+
pass
|
|
810
|
+
if not converted:
|
|
811
|
+
return False
|
|
812
|
+
if not 0 <= number <= 1:
|
|
813
|
+
return False
|
|
814
|
+
return True
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
# -----
|
|
818
|
+
# image
|
|
819
|
+
# -----
|
|
820
|
+
|
|
821
|
+
pngSignature: bytes = b"\x89PNG\r\n\x1a\n"
|
|
822
|
+
|
|
823
|
+
_imageDictPrototype: GenericDict = dict(
|
|
824
|
+
fileName=(str, True),
|
|
825
|
+
xScale=((int, float), False),
|
|
826
|
+
xyScale=((int, float), False),
|
|
827
|
+
yxScale=((int, float), False),
|
|
828
|
+
yScale=((int, float), False),
|
|
829
|
+
xOffset=((int, float), False),
|
|
830
|
+
yOffset=((int, float), False),
|
|
831
|
+
color=(str, False),
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
def imageValidator(value):
|
|
836
|
+
"""
|
|
837
|
+
Version 3+.
|
|
838
|
+
"""
|
|
839
|
+
if not genericDictValidator(value, _imageDictPrototype):
|
|
840
|
+
return False
|
|
841
|
+
# fileName must be one or more characters
|
|
842
|
+
if not value["fileName"]:
|
|
843
|
+
return False
|
|
844
|
+
# color must follow the proper format
|
|
845
|
+
color = value.get("color")
|
|
846
|
+
if color is not None and not colorValidator(color):
|
|
847
|
+
return False
|
|
848
|
+
return True
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
def pngValidator(
|
|
852
|
+
path: Optional[str] = None,
|
|
853
|
+
data: Optional[bytes] = None,
|
|
854
|
+
fileObj: Optional[Any] = None,
|
|
855
|
+
) -> tuple[bool, Any]:
|
|
856
|
+
"""
|
|
857
|
+
Version 3+.
|
|
858
|
+
|
|
859
|
+
This checks the signature of the image data.
|
|
860
|
+
"""
|
|
861
|
+
assert path is not None or data is not None or fileObj is not None
|
|
862
|
+
if path is not None:
|
|
863
|
+
with open(path, "rb") as f:
|
|
864
|
+
signature = f.read(8)
|
|
865
|
+
elif data is not None:
|
|
866
|
+
signature = data[:8]
|
|
867
|
+
elif fileObj is not None:
|
|
868
|
+
pos = fileObj.tell()
|
|
869
|
+
signature = fileObj.read(8)
|
|
870
|
+
fileObj.seek(pos)
|
|
871
|
+
if signature != pngSignature:
|
|
872
|
+
return False, "Image does not begin with the PNG signature."
|
|
873
|
+
return True, None
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
# -------------------
|
|
877
|
+
# layercontents.plist
|
|
878
|
+
# -------------------
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
def layerContentsValidator(
|
|
882
|
+
value: Any, ufoPathOrFileSystem: Union[str, fs.base.FS]
|
|
883
|
+
) -> tuple[bool, Optional[str]]:
|
|
884
|
+
"""
|
|
885
|
+
Check the validity of layercontents.plist.
|
|
886
|
+
Version 3+.
|
|
887
|
+
"""
|
|
888
|
+
if isinstance(ufoPathOrFileSystem, fs.base.FS):
|
|
889
|
+
fileSystem = ufoPathOrFileSystem
|
|
890
|
+
else:
|
|
891
|
+
fileSystem = fs.osfs.OSFS(ufoPathOrFileSystem)
|
|
892
|
+
|
|
893
|
+
bogusFileMessage = "layercontents.plist in not in the correct format."
|
|
894
|
+
# file isn't in the right format
|
|
895
|
+
if not isinstance(value, list):
|
|
896
|
+
return False, bogusFileMessage
|
|
897
|
+
# work through each entry
|
|
898
|
+
usedLayerNames = set()
|
|
899
|
+
usedDirectories = set()
|
|
900
|
+
contents = {}
|
|
901
|
+
for entry in value:
|
|
902
|
+
# layer entry in the incorrect format
|
|
903
|
+
if not isinstance(entry, list):
|
|
904
|
+
return False, bogusFileMessage
|
|
905
|
+
if not len(entry) == 2:
|
|
906
|
+
return False, bogusFileMessage
|
|
907
|
+
for i in entry:
|
|
908
|
+
if not isinstance(i, str):
|
|
909
|
+
return False, bogusFileMessage
|
|
910
|
+
layerName, directoryName = entry
|
|
911
|
+
# check directory naming
|
|
912
|
+
if directoryName != "glyphs":
|
|
913
|
+
if not directoryName.startswith("glyphs."):
|
|
914
|
+
return (
|
|
915
|
+
False,
|
|
916
|
+
"Invalid directory name (%s) in layercontents.plist."
|
|
917
|
+
% directoryName,
|
|
918
|
+
)
|
|
919
|
+
if len(layerName) == 0:
|
|
920
|
+
return False, "Empty layer name in layercontents.plist."
|
|
921
|
+
# directory doesn't exist
|
|
922
|
+
if not fileSystem.exists(directoryName):
|
|
923
|
+
return False, "A glyphset does not exist at %s." % directoryName
|
|
924
|
+
# default layer name
|
|
925
|
+
if layerName == "public.default" and directoryName != "glyphs":
|
|
926
|
+
return (
|
|
927
|
+
False,
|
|
928
|
+
"The name public.default is being used by a layer that is not the default.",
|
|
929
|
+
)
|
|
930
|
+
# check usage
|
|
931
|
+
if layerName in usedLayerNames:
|
|
932
|
+
return (
|
|
933
|
+
False,
|
|
934
|
+
"The layer name %s is used by more than one layer." % layerName,
|
|
935
|
+
)
|
|
936
|
+
usedLayerNames.add(layerName)
|
|
937
|
+
if directoryName in usedDirectories:
|
|
938
|
+
return (
|
|
939
|
+
False,
|
|
940
|
+
"The directory %s is used by more than one layer." % directoryName,
|
|
941
|
+
)
|
|
942
|
+
usedDirectories.add(directoryName)
|
|
943
|
+
# store
|
|
944
|
+
contents[layerName] = directoryName
|
|
945
|
+
# missing default layer
|
|
946
|
+
foundDefault = "glyphs" in contents.values()
|
|
947
|
+
if not foundDefault:
|
|
948
|
+
return False, "The required default glyph set is not in the UFO."
|
|
949
|
+
return True, None
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
# ------------
|
|
953
|
+
# groups.plist
|
|
954
|
+
# ------------
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
def groupsValidator(value: Any) -> tuple[bool, Optional[str]]:
|
|
958
|
+
"""
|
|
959
|
+
Check the validity of the groups.
|
|
960
|
+
Version 3+ (though it's backwards compatible with UFO 1 and UFO 2).
|
|
961
|
+
|
|
962
|
+
>>> groups = {"A" : ["A", "A"], "A2" : ["A"]}
|
|
963
|
+
>>> groupsValidator(groups)
|
|
964
|
+
(True, None)
|
|
965
|
+
|
|
966
|
+
>>> groups = {"" : ["A"]}
|
|
967
|
+
>>> valid, msg = groupsValidator(groups)
|
|
968
|
+
>>> valid
|
|
969
|
+
False
|
|
970
|
+
>>> print(msg)
|
|
971
|
+
A group has an empty name.
|
|
972
|
+
|
|
973
|
+
>>> groups = {"public.awesome" : ["A"]}
|
|
974
|
+
>>> groupsValidator(groups)
|
|
975
|
+
(True, None)
|
|
976
|
+
|
|
977
|
+
>>> groups = {"public.kern1." : ["A"]}
|
|
978
|
+
>>> valid, msg = groupsValidator(groups)
|
|
979
|
+
>>> valid
|
|
980
|
+
False
|
|
981
|
+
>>> print(msg)
|
|
982
|
+
The group data contains a kerning group with an incomplete name.
|
|
983
|
+
>>> groups = {"public.kern2." : ["A"]}
|
|
984
|
+
>>> valid, msg = groupsValidator(groups)
|
|
985
|
+
>>> valid
|
|
986
|
+
False
|
|
987
|
+
>>> print(msg)
|
|
988
|
+
The group data contains a kerning group with an incomplete name.
|
|
989
|
+
|
|
990
|
+
>>> groups = {"public.kern1.A" : ["A"], "public.kern2.A" : ["A"]}
|
|
991
|
+
>>> groupsValidator(groups)
|
|
992
|
+
(True, None)
|
|
993
|
+
|
|
994
|
+
>>> groups = {"public.kern1.A1" : ["A"], "public.kern1.A2" : ["A"]}
|
|
995
|
+
>>> valid, msg = groupsValidator(groups)
|
|
996
|
+
>>> valid
|
|
997
|
+
False
|
|
998
|
+
>>> print(msg)
|
|
999
|
+
The glyph "A" occurs in too many kerning groups.
|
|
1000
|
+
"""
|
|
1001
|
+
bogusFormatMessage = "The group data is not in the correct format."
|
|
1002
|
+
if not isDictEnough(value):
|
|
1003
|
+
return False, bogusFormatMessage
|
|
1004
|
+
firstSideMapping: dict[str, str] = {}
|
|
1005
|
+
secondSideMapping: dict[str, str] = {}
|
|
1006
|
+
for groupName, glyphList in value.items():
|
|
1007
|
+
if not isinstance(groupName, (str)):
|
|
1008
|
+
return False, bogusFormatMessage
|
|
1009
|
+
if not isinstance(glyphList, (list, tuple)):
|
|
1010
|
+
return False, bogusFormatMessage
|
|
1011
|
+
if not groupName:
|
|
1012
|
+
return False, "A group has an empty name."
|
|
1013
|
+
if groupName.startswith("public."):
|
|
1014
|
+
if not groupName.startswith("public.kern1.") and not groupName.startswith(
|
|
1015
|
+
"public.kern2."
|
|
1016
|
+
):
|
|
1017
|
+
# unknown public.* name. silently skip.
|
|
1018
|
+
continue
|
|
1019
|
+
else:
|
|
1020
|
+
if len("public.kernN.") == len(groupName):
|
|
1021
|
+
return (
|
|
1022
|
+
False,
|
|
1023
|
+
"The group data contains a kerning group with an incomplete name.",
|
|
1024
|
+
)
|
|
1025
|
+
if groupName.startswith("public.kern1."):
|
|
1026
|
+
d = firstSideMapping
|
|
1027
|
+
else:
|
|
1028
|
+
d = secondSideMapping
|
|
1029
|
+
for glyphName in glyphList:
|
|
1030
|
+
if not isinstance(glyphName, str):
|
|
1031
|
+
return (
|
|
1032
|
+
False,
|
|
1033
|
+
"The group data %s contains an invalid member." % groupName,
|
|
1034
|
+
)
|
|
1035
|
+
if glyphName in d:
|
|
1036
|
+
return (
|
|
1037
|
+
False,
|
|
1038
|
+
'The glyph "%s" occurs in too many kerning groups.' % glyphName,
|
|
1039
|
+
)
|
|
1040
|
+
d[glyphName] = groupName
|
|
1041
|
+
return True, None
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
# -------------
|
|
1045
|
+
# kerning.plist
|
|
1046
|
+
# -------------
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
def kerningValidator(data: Any) -> tuple[bool, Optional[str]]:
|
|
1050
|
+
"""
|
|
1051
|
+
Check the validity of the kerning data structure.
|
|
1052
|
+
Version 3+ (though it's backwards compatible with UFO 1 and UFO 2).
|
|
1053
|
+
|
|
1054
|
+
>>> kerning = {"A" : {"B" : 100}}
|
|
1055
|
+
>>> kerningValidator(kerning)
|
|
1056
|
+
(True, None)
|
|
1057
|
+
|
|
1058
|
+
>>> kerning = {"A" : ["B"]}
|
|
1059
|
+
>>> valid, msg = kerningValidator(kerning)
|
|
1060
|
+
>>> valid
|
|
1061
|
+
False
|
|
1062
|
+
>>> print(msg)
|
|
1063
|
+
The kerning data is not in the correct format.
|
|
1064
|
+
|
|
1065
|
+
>>> kerning = {"A" : {"B" : "100"}}
|
|
1066
|
+
>>> valid, msg = kerningValidator(kerning)
|
|
1067
|
+
>>> valid
|
|
1068
|
+
False
|
|
1069
|
+
>>> print(msg)
|
|
1070
|
+
The kerning data is not in the correct format.
|
|
1071
|
+
"""
|
|
1072
|
+
bogusFormatMessage = "The kerning data is not in the correct format."
|
|
1073
|
+
if not isinstance(data, Mapping):
|
|
1074
|
+
return False, bogusFormatMessage
|
|
1075
|
+
for first, secondDict in data.items():
|
|
1076
|
+
if not isinstance(first, str):
|
|
1077
|
+
return False, bogusFormatMessage
|
|
1078
|
+
elif not isinstance(secondDict, Mapping):
|
|
1079
|
+
return False, bogusFormatMessage
|
|
1080
|
+
for second, value in secondDict.items():
|
|
1081
|
+
if not isinstance(second, str):
|
|
1082
|
+
return False, bogusFormatMessage
|
|
1083
|
+
elif not isinstance(value, numberTypes):
|
|
1084
|
+
return False, bogusFormatMessage
|
|
1085
|
+
return True, None
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
# -------------
|
|
1089
|
+
# lib.plist/lib
|
|
1090
|
+
# -------------
|
|
1091
|
+
|
|
1092
|
+
_bogusLibFormatMessage = "The lib data is not in the correct format: %s"
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
def fontLibValidator(value: Any) -> tuple[bool, Optional[str]]:
|
|
1096
|
+
"""
|
|
1097
|
+
Check the validity of the lib.
|
|
1098
|
+
Version 3+ (though it's backwards compatible with UFO 1 and UFO 2).
|
|
1099
|
+
|
|
1100
|
+
>>> lib = {"foo" : "bar"}
|
|
1101
|
+
>>> fontLibValidator(lib)
|
|
1102
|
+
(True, None)
|
|
1103
|
+
|
|
1104
|
+
>>> lib = {"public.awesome" : "hello"}
|
|
1105
|
+
>>> fontLibValidator(lib)
|
|
1106
|
+
(True, None)
|
|
1107
|
+
|
|
1108
|
+
>>> lib = {"public.glyphOrder" : ["A", "C", "B"]}
|
|
1109
|
+
>>> fontLibValidator(lib)
|
|
1110
|
+
(True, None)
|
|
1111
|
+
|
|
1112
|
+
>>> lib = "hello"
|
|
1113
|
+
>>> valid, msg = fontLibValidator(lib)
|
|
1114
|
+
>>> valid
|
|
1115
|
+
False
|
|
1116
|
+
>>> print(msg) # doctest: +ELLIPSIS
|
|
1117
|
+
The lib data is not in the correct format: expected a dictionary, ...
|
|
1118
|
+
|
|
1119
|
+
>>> lib = {1: "hello"}
|
|
1120
|
+
>>> valid, msg = fontLibValidator(lib)
|
|
1121
|
+
>>> valid
|
|
1122
|
+
False
|
|
1123
|
+
>>> print(msg)
|
|
1124
|
+
The lib key is not properly formatted: expected str, found int: 1
|
|
1125
|
+
|
|
1126
|
+
>>> lib = {"public.glyphOrder" : "hello"}
|
|
1127
|
+
>>> valid, msg = fontLibValidator(lib)
|
|
1128
|
+
>>> valid
|
|
1129
|
+
False
|
|
1130
|
+
>>> print(msg) # doctest: +ELLIPSIS
|
|
1131
|
+
public.glyphOrder is not properly formatted: expected list or tuple,...
|
|
1132
|
+
|
|
1133
|
+
>>> lib = {"public.glyphOrder" : ["A", 1, "B"]}
|
|
1134
|
+
>>> valid, msg = fontLibValidator(lib)
|
|
1135
|
+
>>> valid
|
|
1136
|
+
False
|
|
1137
|
+
>>> print(msg) # doctest: +ELLIPSIS
|
|
1138
|
+
public.glyphOrder is not properly formatted: expected str,...
|
|
1139
|
+
"""
|
|
1140
|
+
if not isDictEnough(value):
|
|
1141
|
+
reason = "expected a dictionary, found %s" % type(value).__name__
|
|
1142
|
+
return False, _bogusLibFormatMessage % reason
|
|
1143
|
+
for key, value in value.items():
|
|
1144
|
+
if not isinstance(key, str):
|
|
1145
|
+
return False, (
|
|
1146
|
+
"The lib key is not properly formatted: expected str, found %s: %r"
|
|
1147
|
+
% (type(key).__name__, key)
|
|
1148
|
+
)
|
|
1149
|
+
# public.glyphOrder
|
|
1150
|
+
if key == "public.glyphOrder":
|
|
1151
|
+
bogusGlyphOrderMessage = "public.glyphOrder is not properly formatted: %s"
|
|
1152
|
+
if not isinstance(value, (list, tuple)):
|
|
1153
|
+
reason = "expected list or tuple, found %s" % type(value).__name__
|
|
1154
|
+
return False, bogusGlyphOrderMessage % reason
|
|
1155
|
+
for glyphName in value:
|
|
1156
|
+
if not isinstance(glyphName, str):
|
|
1157
|
+
reason = "expected str, found %s" % type(glyphName).__name__
|
|
1158
|
+
return False, bogusGlyphOrderMessage % reason
|
|
1159
|
+
return True, None
|
|
1160
|
+
|
|
1161
|
+
|
|
1162
|
+
# --------
|
|
1163
|
+
# GLIF lib
|
|
1164
|
+
# --------
|
|
1165
|
+
|
|
1166
|
+
|
|
1167
|
+
def glyphLibValidator(value: Any) -> tuple[bool, Optional[str]]:
|
|
1168
|
+
"""
|
|
1169
|
+
Check the validity of the lib.
|
|
1170
|
+
Version 3+ (though it's backwards compatible with UFO 1 and UFO 2).
|
|
1171
|
+
|
|
1172
|
+
>>> lib = {"foo" : "bar"}
|
|
1173
|
+
>>> glyphLibValidator(lib)
|
|
1174
|
+
(True, None)
|
|
1175
|
+
|
|
1176
|
+
>>> lib = {"public.awesome" : "hello"}
|
|
1177
|
+
>>> glyphLibValidator(lib)
|
|
1178
|
+
(True, None)
|
|
1179
|
+
|
|
1180
|
+
>>> lib = {"public.markColor" : "1,0,0,0.5"}
|
|
1181
|
+
>>> glyphLibValidator(lib)
|
|
1182
|
+
(True, None)
|
|
1183
|
+
|
|
1184
|
+
>>> lib = {"public.markColor" : 1}
|
|
1185
|
+
>>> valid, msg = glyphLibValidator(lib)
|
|
1186
|
+
>>> valid
|
|
1187
|
+
False
|
|
1188
|
+
>>> print(msg)
|
|
1189
|
+
public.markColor is not properly formatted.
|
|
1190
|
+
"""
|
|
1191
|
+
if not isDictEnough(value):
|
|
1192
|
+
reason = "expected a dictionary, found %s" % type(value).__name__
|
|
1193
|
+
return False, _bogusLibFormatMessage % reason
|
|
1194
|
+
for key, value in value.items():
|
|
1195
|
+
if not isinstance(key, str):
|
|
1196
|
+
reason = "key (%s) should be a string" % key
|
|
1197
|
+
return False, _bogusLibFormatMessage % reason
|
|
1198
|
+
# public.markColor
|
|
1199
|
+
if key == "public.markColor":
|
|
1200
|
+
if not colorValidator(value):
|
|
1201
|
+
return False, "public.markColor is not properly formatted."
|
|
1202
|
+
return True, None
|
|
1203
|
+
|
|
1204
|
+
|
|
1205
|
+
if __name__ == "__main__":
|
|
1206
|
+
import doctest
|
|
1207
|
+
|
|
1208
|
+
doctest.testmod()
|