diff options
| author | shumkovnd <[email protected]> | 2023-11-10 14:39:34 +0300 |
|---|---|---|
| committer | shumkovnd <[email protected]> | 2023-11-10 16:42:24 +0300 |
| commit | 77eb2d3fdcec5c978c64e025ced2764c57c00285 (patch) | |
| tree | c51edb0748ca8d4a08d7c7323312c27ba1a8b79a /contrib/python/fonttools/fontTools/ufoLib/validators.py | |
| parent | dd6d20cadb65582270ac23f4b3b14ae189704b9d (diff) | |
KIKIMR-19287: add task_stats_drawing script
Diffstat (limited to 'contrib/python/fonttools/fontTools/ufoLib/validators.py')
| -rw-r--r-- | contrib/python/fonttools/fontTools/ufoLib/validators.py | 1186 |
1 files changed, 1186 insertions, 0 deletions
diff --git a/contrib/python/fonttools/fontTools/ufoLib/validators.py b/contrib/python/fonttools/fontTools/ufoLib/validators.py new file mode 100644 index 00000000000..01e3124fd38 --- /dev/null +++ b/contrib/python/fonttools/fontTools/ufoLib/validators.py @@ -0,0 +1,1186 @@ +"""Various low level data validators.""" + +import calendar +from io import open +import fs.base +import fs.osfs + +from collections.abc import Mapping +from fontTools.ufoLib.utils import numberTypes + + +# ------- +# Generic +# ------- + + +def isDictEnough(value): + """ + Some objects will likely come in that aren't + dicts but are dict-ish enough. + """ + if isinstance(value, Mapping): + return True + for attr in ("keys", "values", "items"): + if not hasattr(value, attr): + return False + return True + + +def genericTypeValidator(value, typ): + """ + Generic. (Added at version 2.) + """ + return isinstance(value, typ) + + +def genericIntListValidator(values, validValues): + """ + Generic. (Added at version 2.) + """ + if not isinstance(values, (list, tuple)): + return False + valuesSet = set(values) + validValuesSet = set(validValues) + if valuesSet - validValuesSet: + return False + for value in values: + if not isinstance(value, int): + return False + return True + + +def genericNonNegativeIntValidator(value): + """ + Generic. (Added at version 3.) + """ + if not isinstance(value, int): + return False + if value < 0: + return False + return True + + +def genericNonNegativeNumberValidator(value): + """ + Generic. (Added at version 3.) + """ + if not isinstance(value, numberTypes): + return False + if value < 0: + return False + return True + + +def genericDictValidator(value, prototype): + """ + Generic. (Added at version 3.) + """ + # not a dict + if not isinstance(value, Mapping): + return False + # missing required keys + for key, (typ, required) in prototype.items(): + if not required: + continue + if key not in value: + return False + # unknown keys + for key in value.keys(): + if key not in prototype: + return False + # incorrect types + for key, v in value.items(): + prototypeType, required = prototype[key] + if v is None and not required: + continue + if not isinstance(v, prototypeType): + return False + return True + + +# -------------- +# fontinfo.plist +# -------------- + +# Data Validators + + +def fontInfoStyleMapStyleNameValidator(value): + """ + Version 2+. + """ + options = ["regular", "italic", "bold", "bold italic"] + return value in options + + +def fontInfoOpenTypeGaspRangeRecordsValidator(value): + """ + Version 3+. + """ + if not isinstance(value, list): + return False + if len(value) == 0: + return True + validBehaviors = [0, 1, 2, 3] + dictPrototype = dict(rangeMaxPPEM=(int, True), rangeGaspBehavior=(list, True)) + ppemOrder = [] + for rangeRecord in value: + if not genericDictValidator(rangeRecord, dictPrototype): + return False + ppem = rangeRecord["rangeMaxPPEM"] + behavior = rangeRecord["rangeGaspBehavior"] + ppemValidity = genericNonNegativeIntValidator(ppem) + if not ppemValidity: + return False + behaviorValidity = genericIntListValidator(behavior, validBehaviors) + if not behaviorValidity: + return False + ppemOrder.append(ppem) + if ppemOrder != sorted(ppemOrder): + return False + return True + + +def fontInfoOpenTypeHeadCreatedValidator(value): + """ + Version 2+. + """ + # format: 0000/00/00 00:00:00 + if not isinstance(value, str): + return False + # basic formatting + if not len(value) == 19: + return False + if value.count(" ") != 1: + return False + date, time = value.split(" ") + if date.count("/") != 2: + return False + if time.count(":") != 2: + return False + # date + year, month, day = date.split("/") + if len(year) != 4: + return False + if len(month) != 2: + return False + if len(day) != 2: + return False + try: + year = int(year) + month = int(month) + day = int(day) + except ValueError: + return False + if month < 1 or month > 12: + return False + monthMaxDay = calendar.monthrange(year, month)[1] + if day < 1 or day > monthMaxDay: + return False + # time + hour, minute, second = time.split(":") + if len(hour) != 2: + return False + if len(minute) != 2: + return False + if len(second) != 2: + return False + try: + hour = int(hour) + minute = int(minute) + second = int(second) + except ValueError: + return False + if hour < 0 or hour > 23: + return False + if minute < 0 or minute > 59: + return False + if second < 0 or second > 59: + return False + # fallback + return True + + +def fontInfoOpenTypeNameRecordsValidator(value): + """ + Version 3+. + """ + if not isinstance(value, list): + return False + dictPrototype = dict( + nameID=(int, True), + platformID=(int, True), + encodingID=(int, True), + languageID=(int, True), + string=(str, True), + ) + for nameRecord in value: + if not genericDictValidator(nameRecord, dictPrototype): + return False + return True + + +def fontInfoOpenTypeOS2WeightClassValidator(value): + """ + Version 2+. + """ + if not isinstance(value, int): + return False + if value < 0: + return False + return True + + +def fontInfoOpenTypeOS2WidthClassValidator(value): + """ + Version 2+. + """ + if not isinstance(value, int): + return False + if value < 1: + return False + if value > 9: + return False + return True + + +def fontInfoVersion2OpenTypeOS2PanoseValidator(values): + """ + Version 2. + """ + if not isinstance(values, (list, tuple)): + return False + if len(values) != 10: + return False + for value in values: + if not isinstance(value, int): + return False + # XXX further validation? + return True + + +def fontInfoVersion3OpenTypeOS2PanoseValidator(values): + """ + Version 3+. + """ + if not isinstance(values, (list, tuple)): + return False + if len(values) != 10: + return False + for value in values: + if not isinstance(value, int): + return False + if value < 0: + return False + # XXX further validation? + return True + + +def fontInfoOpenTypeOS2FamilyClassValidator(values): + """ + Version 2+. + """ + if not isinstance(values, (list, tuple)): + return False + if len(values) != 2: + return False + for value in values: + if not isinstance(value, int): + return False + classID, subclassID = values + if classID < 0 or classID > 14: + return False + if subclassID < 0 or subclassID > 15: + return False + return True + + +def fontInfoPostscriptBluesValidator(values): + """ + Version 2+. + """ + if not isinstance(values, (list, tuple)): + return False + if len(values) > 14: + return False + if len(values) % 2: + return False + for value in values: + if not isinstance(value, numberTypes): + return False + return True + + +def fontInfoPostscriptOtherBluesValidator(values): + """ + Version 2+. + """ + if not isinstance(values, (list, tuple)): + return False + if len(values) > 10: + return False + if len(values) % 2: + return False + for value in values: + if not isinstance(value, numberTypes): + return False + return True + + +def fontInfoPostscriptStemsValidator(values): + """ + Version 2+. + """ + if not isinstance(values, (list, tuple)): + return False + if len(values) > 12: + return False + for value in values: + if not isinstance(value, numberTypes): + return False + return True + + +def fontInfoPostscriptWindowsCharacterSetValidator(value): + """ + Version 2+. + """ + validValues = list(range(1, 21)) + if value not in validValues: + return False + return True + + +def fontInfoWOFFMetadataUniqueIDValidator(value): + """ + Version 3+. + """ + dictPrototype = dict(id=(str, True)) + if not genericDictValidator(value, dictPrototype): + return False + return True + + +def fontInfoWOFFMetadataVendorValidator(value): + """ + Version 3+. + """ + dictPrototype = { + "name": (str, True), + "url": (str, False), + "dir": (str, False), + "class": (str, False), + } + if not genericDictValidator(value, dictPrototype): + return False + if "dir" in value and value.get("dir") not in ("ltr", "rtl"): + return False + return True + + +def fontInfoWOFFMetadataCreditsValidator(value): + """ + Version 3+. + """ + dictPrototype = dict(credits=(list, True)) + if not genericDictValidator(value, dictPrototype): + return False + if not len(value["credits"]): + return False + dictPrototype = { + "name": (str, True), + "url": (str, False), + "role": (str, False), + "dir": (str, False), + "class": (str, False), + } + for credit in value["credits"]: + if not genericDictValidator(credit, dictPrototype): + return False + if "dir" in credit and credit.get("dir") not in ("ltr", "rtl"): + return False + return True + + +def fontInfoWOFFMetadataDescriptionValidator(value): + """ + Version 3+. + """ + dictPrototype = dict(url=(str, False), text=(list, True)) + if not genericDictValidator(value, dictPrototype): + return False + for text in value["text"]: + if not fontInfoWOFFMetadataTextValue(text): + return False + return True + + +def fontInfoWOFFMetadataLicenseValidator(value): + """ + Version 3+. + """ + dictPrototype = dict(url=(str, False), text=(list, False), id=(str, False)) + if not genericDictValidator(value, dictPrototype): + return False + if "text" in value: + for text in value["text"]: + if not fontInfoWOFFMetadataTextValue(text): + return False + return True + + +def fontInfoWOFFMetadataTrademarkValidator(value): + """ + Version 3+. + """ + dictPrototype = dict(text=(list, True)) + if not genericDictValidator(value, dictPrototype): + return False + for text in value["text"]: + if not fontInfoWOFFMetadataTextValue(text): + return False + return True + + +def fontInfoWOFFMetadataCopyrightValidator(value): + """ + Version 3+. + """ + dictPrototype = dict(text=(list, True)) + if not genericDictValidator(value, dictPrototype): + return False + for text in value["text"]: + if not fontInfoWOFFMetadataTextValue(text): + return False + return True + + +def fontInfoWOFFMetadataLicenseeValidator(value): + """ + Version 3+. + """ + dictPrototype = {"name": (str, True), "dir": (str, False), "class": (str, False)} + if not genericDictValidator(value, dictPrototype): + return False + if "dir" in value and value.get("dir") not in ("ltr", "rtl"): + return False + return True + + +def fontInfoWOFFMetadataTextValue(value): + """ + Version 3+. + """ + dictPrototype = { + "text": (str, True), + "language": (str, False), + "dir": (str, False), + "class": (str, False), + } + if not genericDictValidator(value, dictPrototype): + return False + if "dir" in value and value.get("dir") not in ("ltr", "rtl"): + return False + return True + + +def fontInfoWOFFMetadataExtensionsValidator(value): + """ + Version 3+. + """ + if not isinstance(value, list): + return False + if not value: + return False + for extension in value: + if not fontInfoWOFFMetadataExtensionValidator(extension): + return False + return True + + +def fontInfoWOFFMetadataExtensionValidator(value): + """ + Version 3+. + """ + dictPrototype = dict(names=(list, False), items=(list, True), id=(str, False)) + if not genericDictValidator(value, dictPrototype): + return False + if "names" in value: + for name in value["names"]: + if not fontInfoWOFFMetadataExtensionNameValidator(name): + return False + for item in value["items"]: + if not fontInfoWOFFMetadataExtensionItemValidator(item): + return False + return True + + +def fontInfoWOFFMetadataExtensionItemValidator(value): + """ + Version 3+. + """ + dictPrototype = dict(id=(str, False), names=(list, True), values=(list, True)) + if not genericDictValidator(value, dictPrototype): + return False + for name in value["names"]: + if not fontInfoWOFFMetadataExtensionNameValidator(name): + return False + for val in value["values"]: + if not fontInfoWOFFMetadataExtensionValueValidator(val): + return False + return True + + +def fontInfoWOFFMetadataExtensionNameValidator(value): + """ + Version 3+. + """ + dictPrototype = { + "text": (str, True), + "language": (str, False), + "dir": (str, False), + "class": (str, False), + } + if not genericDictValidator(value, dictPrototype): + return False + if "dir" in value and value.get("dir") not in ("ltr", "rtl"): + return False + return True + + +def fontInfoWOFFMetadataExtensionValueValidator(value): + """ + Version 3+. + """ + dictPrototype = { + "text": (str, True), + "language": (str, False), + "dir": (str, False), + "class": (str, False), + } + if not genericDictValidator(value, dictPrototype): + return False + if "dir" in value and value.get("dir") not in ("ltr", "rtl"): + return False + return True + + +# ---------- +# Guidelines +# ---------- + + +def guidelinesValidator(value, identifiers=None): + """ + Version 3+. + """ + if not isinstance(value, list): + return False + if identifiers is None: + identifiers = set() + for guide in value: + if not guidelineValidator(guide): + return False + identifier = guide.get("identifier") + if identifier is not None: + if identifier in identifiers: + return False + identifiers.add(identifier) + return True + + +_guidelineDictPrototype = dict( + x=((int, float), False), + y=((int, float), False), + angle=((int, float), False), + name=(str, False), + color=(str, False), + identifier=(str, False), +) + + +def guidelineValidator(value): + """ + Version 3+. + """ + if not genericDictValidator(value, _guidelineDictPrototype): + return False + x = value.get("x") + y = value.get("y") + angle = value.get("angle") + # x or y must be present + if x is None and y is None: + return False + # if x or y are None, angle must not be present + if x is None or y is None: + if angle is not None: + return False + # if x and y are defined, angle must be defined + if x is not None and y is not None and angle is None: + return False + # angle must be between 0 and 360 + if angle is not None: + if angle < 0: + return False + if angle > 360: + return False + # identifier must be 1 or more characters + identifier = value.get("identifier") + if identifier is not None and not identifierValidator(identifier): + return False + # color must follow the proper format + color = value.get("color") + if color is not None and not colorValidator(color): + return False + return True + + +# ------- +# Anchors +# ------- + + +def anchorsValidator(value, identifiers=None): + """ + Version 3+. + """ + if not isinstance(value, list): + return False + if identifiers is None: + identifiers = set() + for anchor in value: + if not anchorValidator(anchor): + return False + identifier = anchor.get("identifier") + if identifier is not None: + if identifier in identifiers: + return False + identifiers.add(identifier) + return True + + +_anchorDictPrototype = dict( + x=((int, float), False), + y=((int, float), False), + name=(str, False), + color=(str, False), + identifier=(str, False), +) + + +def anchorValidator(value): + """ + Version 3+. + """ + if not genericDictValidator(value, _anchorDictPrototype): + return False + x = value.get("x") + y = value.get("y") + # x and y must be present + if x is None or y is None: + return False + # identifier must be 1 or more characters + identifier = value.get("identifier") + if identifier is not None and not identifierValidator(identifier): + return False + # color must follow the proper format + color = value.get("color") + if color is not None and not colorValidator(color): + return False + return True + + +# ---------- +# Identifier +# ---------- + + +def identifierValidator(value): + """ + Version 3+. + + >>> identifierValidator("a") + True + >>> identifierValidator("") + False + >>> identifierValidator("a" * 101) + False + """ + validCharactersMin = 0x20 + validCharactersMax = 0x7E + if not isinstance(value, str): + return False + if not value: + return False + if len(value) > 100: + return False + for c in value: + c = ord(c) + if c < validCharactersMin or c > validCharactersMax: + return False + return True + + +# ----- +# Color +# ----- + + +def colorValidator(value): + """ + Version 3+. + + >>> colorValidator("0,0,0,0") + True + >>> colorValidator(".5,.5,.5,.5") + True + >>> colorValidator("0.5,0.5,0.5,0.5") + True + >>> colorValidator("1,1,1,1") + True + + >>> colorValidator("2,0,0,0") + False + >>> colorValidator("0,2,0,0") + False + >>> colorValidator("0,0,2,0") + False + >>> colorValidator("0,0,0,2") + False + + >>> colorValidator("1r,1,1,1") + False + >>> colorValidator("1,1g,1,1") + False + >>> colorValidator("1,1,1b,1") + False + >>> colorValidator("1,1,1,1a") + False + + >>> colorValidator("1 1 1 1") + False + >>> colorValidator("1 1,1,1") + False + >>> colorValidator("1,1 1,1") + False + >>> colorValidator("1,1,1 1") + False + + >>> colorValidator("1, 1, 1, 1") + True + """ + if not isinstance(value, str): + return False + parts = value.split(",") + if len(parts) != 4: + return False + for part in parts: + part = part.strip() + converted = False + try: + part = int(part) + converted = True + except ValueError: + pass + if not converted: + try: + part = float(part) + converted = True + except ValueError: + pass + if not converted: + return False + if part < 0: + return False + if part > 1: + return False + return True + + +# ----- +# image +# ----- + +pngSignature = b"\x89PNG\r\n\x1a\n" + +_imageDictPrototype = dict( + fileName=(str, True), + xScale=((int, float), False), + xyScale=((int, float), False), + yxScale=((int, float), False), + yScale=((int, float), False), + xOffset=((int, float), False), + yOffset=((int, float), False), + color=(str, False), +) + + +def imageValidator(value): + """ + Version 3+. + """ + if not genericDictValidator(value, _imageDictPrototype): + return False + # fileName must be one or more characters + if not value["fileName"]: + return False + # color must follow the proper format + color = value.get("color") + if color is not None and not colorValidator(color): + return False + return True + + +def pngValidator(path=None, data=None, fileObj=None): + """ + Version 3+. + + This checks the signature of the image data. + """ + assert path is not None or data is not None or fileObj is not None + if path is not None: + with open(path, "rb") as f: + signature = f.read(8) + elif data is not None: + signature = data[:8] + elif fileObj is not None: + pos = fileObj.tell() + signature = fileObj.read(8) + fileObj.seek(pos) + if signature != pngSignature: + return False, "Image does not begin with the PNG signature." + return True, None + + +# ------------------- +# layercontents.plist +# ------------------- + + +def layerContentsValidator(value, ufoPathOrFileSystem): + """ + Check the validity of layercontents.plist. + Version 3+. + """ + if isinstance(ufoPathOrFileSystem, fs.base.FS): + fileSystem = ufoPathOrFileSystem + else: + fileSystem = fs.osfs.OSFS(ufoPathOrFileSystem) + + bogusFileMessage = "layercontents.plist in not in the correct format." + # file isn't in the right format + if not isinstance(value, list): + return False, bogusFileMessage + # work through each entry + usedLayerNames = set() + usedDirectories = set() + contents = {} + for entry in value: + # layer entry in the incorrect format + if not isinstance(entry, list): + return False, bogusFileMessage + if not len(entry) == 2: + return False, bogusFileMessage + for i in entry: + if not isinstance(i, str): + return False, bogusFileMessage + layerName, directoryName = entry + # check directory naming + if directoryName != "glyphs": + if not directoryName.startswith("glyphs."): + return ( + False, + "Invalid directory name (%s) in layercontents.plist." + % directoryName, + ) + if len(layerName) == 0: + return False, "Empty layer name in layercontents.plist." + # directory doesn't exist + if not fileSystem.exists(directoryName): + return False, "A glyphset does not exist at %s." % directoryName + # default layer name + if layerName == "public.default" and directoryName != "glyphs": + return ( + False, + "The name public.default is being used by a layer that is not the default.", + ) + # check usage + if layerName in usedLayerNames: + return ( + False, + "The layer name %s is used by more than one layer." % layerName, + ) + usedLayerNames.add(layerName) + if directoryName in usedDirectories: + return ( + False, + "The directory %s is used by more than one layer." % directoryName, + ) + usedDirectories.add(directoryName) + # store + contents[layerName] = directoryName + # missing default layer + foundDefault = "glyphs" in contents.values() + if not foundDefault: + return False, "The required default glyph set is not in the UFO." + return True, None + + +# ------------ +# groups.plist +# ------------ + + +def groupsValidator(value): + """ + Check the validity of the groups. + Version 3+ (though it's backwards compatible with UFO 1 and UFO 2). + + >>> groups = {"A" : ["A", "A"], "A2" : ["A"]} + >>> groupsValidator(groups) + (True, None) + + >>> groups = {"" : ["A"]} + >>> valid, msg = groupsValidator(groups) + >>> valid + False + >>> print(msg) + A group has an empty name. + + >>> groups = {"public.awesome" : ["A"]} + >>> groupsValidator(groups) + (True, None) + + >>> groups = {"public.kern1." : ["A"]} + >>> valid, msg = groupsValidator(groups) + >>> valid + False + >>> print(msg) + The group data contains a kerning group with an incomplete name. + >>> groups = {"public.kern2." : ["A"]} + >>> valid, msg = groupsValidator(groups) + >>> valid + False + >>> print(msg) + The group data contains a kerning group with an incomplete name. + + >>> groups = {"public.kern1.A" : ["A"], "public.kern2.A" : ["A"]} + >>> groupsValidator(groups) + (True, None) + + >>> groups = {"public.kern1.A1" : ["A"], "public.kern1.A2" : ["A"]} + >>> valid, msg = groupsValidator(groups) + >>> valid + False + >>> print(msg) + The glyph "A" occurs in too many kerning groups. + """ + bogusFormatMessage = "The group data is not in the correct format." + if not isDictEnough(value): + return False, bogusFormatMessage + firstSideMapping = {} + secondSideMapping = {} + for groupName, glyphList in value.items(): + if not isinstance(groupName, (str)): + return False, bogusFormatMessage + if not isinstance(glyphList, (list, tuple)): + return False, bogusFormatMessage + if not groupName: + return False, "A group has an empty name." + if groupName.startswith("public."): + if not groupName.startswith("public.kern1.") and not groupName.startswith( + "public.kern2." + ): + # unknown public.* name. silently skip. + continue + else: + if len("public.kernN.") == len(groupName): + return ( + False, + "The group data contains a kerning group with an incomplete name.", + ) + if groupName.startswith("public.kern1."): + d = firstSideMapping + else: + d = secondSideMapping + for glyphName in glyphList: + if not isinstance(glyphName, str): + return ( + False, + "The group data %s contains an invalid member." % groupName, + ) + if glyphName in d: + return ( + False, + 'The glyph "%s" occurs in too many kerning groups.' % glyphName, + ) + d[glyphName] = groupName + return True, None + + +# ------------- +# kerning.plist +# ------------- + + +def kerningValidator(data): + """ + Check the validity of the kerning data structure. + Version 3+ (though it's backwards compatible with UFO 1 and UFO 2). + + >>> kerning = {"A" : {"B" : 100}} + >>> kerningValidator(kerning) + (True, None) + + >>> kerning = {"A" : ["B"]} + >>> valid, msg = kerningValidator(kerning) + >>> valid + False + >>> print(msg) + The kerning data is not in the correct format. + + >>> kerning = {"A" : {"B" : "100"}} + >>> valid, msg = kerningValidator(kerning) + >>> valid + False + >>> print(msg) + The kerning data is not in the correct format. + """ + bogusFormatMessage = "The kerning data is not in the correct format." + if not isinstance(data, Mapping): + return False, bogusFormatMessage + for first, secondDict in data.items(): + if not isinstance(first, str): + return False, bogusFormatMessage + elif not isinstance(secondDict, Mapping): + return False, bogusFormatMessage + for second, value in secondDict.items(): + if not isinstance(second, str): + return False, bogusFormatMessage + elif not isinstance(value, numberTypes): + return False, bogusFormatMessage + return True, None + + +# ------------- +# lib.plist/lib +# ------------- + +_bogusLibFormatMessage = "The lib data is not in the correct format: %s" + + +def fontLibValidator(value): + """ + Check the validity of the lib. + Version 3+ (though it's backwards compatible with UFO 1 and UFO 2). + + >>> lib = {"foo" : "bar"} + >>> fontLibValidator(lib) + (True, None) + + >>> lib = {"public.awesome" : "hello"} + >>> fontLibValidator(lib) + (True, None) + + >>> lib = {"public.glyphOrder" : ["A", "C", "B"]} + >>> fontLibValidator(lib) + (True, None) + + >>> lib = "hello" + >>> valid, msg = fontLibValidator(lib) + >>> valid + False + >>> print(msg) # doctest: +ELLIPSIS + The lib data is not in the correct format: expected a dictionary, ... + + >>> lib = {1: "hello"} + >>> valid, msg = fontLibValidator(lib) + >>> valid + False + >>> print(msg) + The lib key is not properly formatted: expected str, found int: 1 + + >>> lib = {"public.glyphOrder" : "hello"} + >>> valid, msg = fontLibValidator(lib) + >>> valid + False + >>> print(msg) # doctest: +ELLIPSIS + public.glyphOrder is not properly formatted: expected list or tuple,... + + >>> lib = {"public.glyphOrder" : ["A", 1, "B"]} + >>> valid, msg = fontLibValidator(lib) + >>> valid + False + >>> print(msg) # doctest: +ELLIPSIS + public.glyphOrder is not properly formatted: expected str,... + """ + if not isDictEnough(value): + reason = "expected a dictionary, found %s" % type(value).__name__ + return False, _bogusLibFormatMessage % reason + for key, value in value.items(): + if not isinstance(key, str): + return False, ( + "The lib key is not properly formatted: expected str, found %s: %r" + % (type(key).__name__, key) + ) + # public.glyphOrder + if key == "public.glyphOrder": + bogusGlyphOrderMessage = "public.glyphOrder is not properly formatted: %s" + if not isinstance(value, (list, tuple)): + reason = "expected list or tuple, found %s" % type(value).__name__ + return False, bogusGlyphOrderMessage % reason + for glyphName in value: + if not isinstance(glyphName, str): + reason = "expected str, found %s" % type(glyphName).__name__ + return False, bogusGlyphOrderMessage % reason + return True, None + + +# -------- +# GLIF lib +# -------- + + +def glyphLibValidator(value): + """ + Check the validity of the lib. + Version 3+ (though it's backwards compatible with UFO 1 and UFO 2). + + >>> lib = {"foo" : "bar"} + >>> glyphLibValidator(lib) + (True, None) + + >>> lib = {"public.awesome" : "hello"} + >>> glyphLibValidator(lib) + (True, None) + + >>> lib = {"public.markColor" : "1,0,0,0.5"} + >>> glyphLibValidator(lib) + (True, None) + + >>> lib = {"public.markColor" : 1} + >>> valid, msg = glyphLibValidator(lib) + >>> valid + False + >>> print(msg) + public.markColor is not properly formatted. + """ + if not isDictEnough(value): + reason = "expected a dictionary, found %s" % type(value).__name__ + return False, _bogusLibFormatMessage % reason + for key, value in value.items(): + if not isinstance(key, str): + reason = "key (%s) should be a string" % key + return False, _bogusLibFormatMessage % reason + # public.markColor + if key == "public.markColor": + if not colorValidator(value): + return False, "public.markColor is not properly formatted." + return True, None + + +if __name__ == "__main__": + import doctest + + doctest.testmod() |
