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