|
"""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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.) |
|
""" |
|
|
|
if not isinstance(value, Mapping): |
|
return False |
|
|
|
for key, (typ, required) in prototype.items(): |
|
if not required: |
|
continue |
|
if key not in value: |
|
return False |
|
|
|
for key in value.keys(): |
|
if key not in prototype: |
|
return False |
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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+. |
|
""" |
|
|
|
if not isinstance(value, str): |
|
return False |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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") |
|
|
|
if x is None and y is None: |
|
return False |
|
|
|
if x is None or y is None: |
|
if angle is not None: |
|
return False |
|
|
|
if x is not None and y is not None and angle is None: |
|
return False |
|
|
|
if angle is not None: |
|
if angle < 0: |
|
return False |
|
if angle > 360: |
|
return False |
|
|
|
identifier = value.get("identifier") |
|
if identifier is not None and not identifierValidator(identifier): |
|
return False |
|
|
|
color = value.get("color") |
|
if color is not None and not colorValidator(color): |
|
return False |
|
return True |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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") |
|
|
|
if x is None or y is None: |
|
return False |
|
|
|
identifier = value.get("identifier") |
|
if identifier is not None and not identifierValidator(identifier): |
|
return False |
|
|
|
color = value.get("color") |
|
if color is not None and not colorValidator(color): |
|
return False |
|
return True |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
if not value["fileName"]: |
|
return False |
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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." |
|
|
|
if not isinstance(value, list): |
|
return False, bogusFileMessage |
|
|
|
usedLayerNames = set() |
|
usedDirectories = set() |
|
contents = {} |
|
for entry in value: |
|
|
|
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 |
|
|
|
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." |
|
|
|
if not fileSystem.exists(directoryName): |
|
return False, "A glyphset does not exist at %s." % directoryName |
|
|
|
if layerName == "public.default" and directoryName != "glyphs": |
|
return ( |
|
False, |
|
"The name public.default is being used by a layer that is not the default.", |
|
) |
|
|
|
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) |
|
|
|
contents[layerName] = directoryName |
|
|
|
foundDefault = "glyphs" in contents.values() |
|
if not foundDefault: |
|
return False, "The required default glyph set is not in the UFO." |
|
return True, None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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." |
|
): |
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
_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) |
|
) |
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
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() |
|
|