Spaces:
Paused
Paused
| """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() | |