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