Untitled
unknown
plain_text
2 years ago
48 kB
5
Indexable
#MenuTitle: TNB - Font Check. # -*- coding: utf-8 -*- __doc__=""" Checks various measurable aspects of the font for consistency.""" from pickle import FALSE import GlyphsApp from vanilla import * import os.path import datetime import string import copy import zipfile import re import math version = "0.0.6" zippath = os.path.expanduser("~/Library/Application Support/Glyphs/Scripts/TNBscripts/TNBLibs.zip") zipextrctpath = os.path.expanduser("~/Library/Application Support/Glyphs/Scripts/TNBscripts") with zipfile.ZipFile(zippath, 'r') as zip_ref: zip_ref.extractall(zipextrctpath) zip_ref.close() class TNBFontCheck(object): def __init__(self): self.ColourGlyphs = 0 self.ColourType = 0 self.CheckColourType = 0 self.GlyphExport = 0 self.ReportLines = [] self.SelectedMasters = [] self.SelectedGlyphs = [] self.MastersNames = [] self.f = Glyphs.font # frontmost font self.IsItalic = False for l in range(0,len(self.f.masters)): self.MastersNames.append(self.f.masters[l].name) self.fN = self.f.familyName if Glyphs.font.masters[0].italicAngle != 0: self.IsItalic = True self.fN = self.fN + " Italic" d = datetime.datetime.now() self.ReportLinesHeader = "\n\t000======= "+self.fN+" =======000\n\t\t"+d.strftime("%Y")+":"+d.strftime("%m")+":"+d.strftime("%d")+"-"+d.strftime("%H")+"."+d.strftime("%M")+"."+d.strftime("%S")+" \n" self.w = Window((50, 50, 800, 500), "TNB Font Check") self.w.textBox = TextBox((100, 10, 440, 17), "Font Check version " + version) self.w.textBox4 = TextBox((450, 10, -10, 17), self.f.familyName) self.w.PathTidyCheckBox = CheckBox((100, 50, -400, 20), "Path Tidy.",callback=self.PathTidyCheckBoxCallback, value=True) self.w.ExtremesCheckBox = CheckBox((100, 80, -400, 20), "Extremes.",callback=self.ExtremesCheckBoxCallback, value=True) self.w.WidthsCheckBox = CheckBox((100, 110, -400, 20), "Widths Consistency.",callback=self.WidthsCheckBoxCallback, value=True) self.w.VertSymCheckBox = CheckBox((100, 140, -400, 20), "Vertical Symmetry.",callback=self.VertSymCheckBoxCallback, value=True) self.w.XYStrokesCheckBox = CheckBox((100, 170, -400, 20), "Horizontal and Vertical stroke accuracy.",callback=self.XYStrokesCheckBoxCallback, value=True) self.w.HeightsCheckBox = CheckBox((100, 200, -400, 20), "Heights and Blue zone accuracy.",callback=self.HeightsCheckBoxCallback, value=True) self.w.StartPointCheckBox = CheckBox((100, 260, -400, 20), "Start Point ",callback=self.StartPointCheckBoxCallback, value=True) self.w.PathDirectionCheckBox = CheckBox((100, 230, -400, 20), "Path Directions Acurracy.",callback=self.PathDirectionCheckBoxCallback, value=True) self.w.AllOff = Button((120, 290, -400, 20), "All Off", sizeStyle='small', callback=self.AllOffButtonCallback) self.w.line = HorizontalLine((30, 325, -30, 1)) self.w.textBox2 = TextBox((450, 50, -50, 17), "Conditions (Select one)") self.w.radioGroup = RadioGroup((450, 75, -50, 130),["All Glyphs", "Selected Glyphs", "Unselected Glyphs", "Marked Glyphs", "Unmarked Glyphs"], callback=self.radioGroupCallback) self.ColorCodes2 = ["Red","Orange","Brown","Yellow","Light Green","Dark Green","Light Blue","Dark Blue","Purple","Magenta","Light Gray","Charcoal","No Color","Any Colour"] self.w.textBox3 = TextBox((450, 276, -10, 17), "Colour Conditions", sizeStyle='small') self.w.color_code2 = PopUpButton((470, 290, -50, 20), self.ColorCodes2, sizeStyle='small', callback = self.ColorCodeCallback2) self.w.ExportCheckBox = CheckBox((450, 230, -50, 20), "Include non-Exporting Glyphs",callback=self.ExportCheckBoxCallback, value=False) self.w.SelectedMastersCheckBox = CheckBox((100, 340, -400, 20), "These Masters Only.",callback=self.SelectedMastersCheckBoxCallback, value=False) self.w.editText = EditText((90, 360, -400, 90), callback=self.editTextCallback, readOnly=True) self.w.popUpButton = PopUpButton((120, 450, -400, 20), self.MastersNames, sizeStyle='small', callback=self.popUpButtonCallback) self.ColorCodes = ["Red","Orange","Brown","Yellow","Light Green","Dark Green","Light Blue","Dark Blue","Purple","Magenta","Light Gray","Charcoal","No Color"] self.w.MarkErrorsCheckBox = CheckBox((450, 340, -10, 20), "Colour Layers that have Errors.",callback=self.MarkErrorsCheckBoxCallback, value=False) self.w.color_code = PopUpButton((470, 360, -50, 20), self.ColorCodes, sizeStyle='small', callback = self.ColorCodeCallback,) self.w.okButton = Button((-100, -40, -20, 20), "OK", callback=self.okButtonCallback) self.w.myButton2 = Button((-200, -40, -120, 20), "Close", callback=self.myButton2Callback) self.w.open() self.w.editText.enable(False) self.w.okButton.enable(False) self.w.popUpButton.enable(False) self.w.color_code.enable(False) self.w.color_code2.enable(False) self.w.PathDirectionCheckBox.enable(True) self.w.StartPointCheckBox.enable(True) #Temporarily disable this until done. #self.w.HeightsCheckBox.enable(False) def PathTidyCheckBoxCallback(self, sender): value = sender.get() self.PathTidy = value def ExtremesCheckBoxCallback(self, sender): value = sender.get() self.Extremes = value def WidthsCheckBoxCallback(self, sender): value = sender.get() self.WidthCheck = value def VertSymCheckBoxCallback(self, sender): value = sender.get() self.VertSym = value def XYStrokesCheckBoxCallback(self, sender): value = sender.get() self.XYStrokes = value def HeightsCheckBoxCallback(self, sender): value = sender.get() self.Heights = value def StartPointCheckBoxCallback(self, sender): value = sender.get() self.StartPoint = value def PathDirectionCheckBoxCallback(self, sender): value = sender.get() self.PathDirection = value def AllOffButtonCallback(self, sender): self.w.PathTidyCheckBox.set(0) self.w.ExtremesCheckBox.set(0) self.w.WidthsCheckBox.set(0) self.w.VertSymCheckBox.set(0) self.w.XYStrokesCheckBox.set(0) self.w.HeightsCheckBox.set(0) self.w.StartPointCheckBox.set(0) self.w.PathDirectionCheckBox.set(0) def radioGroupCallback(self, sender): value = sender.get() if value > -1: self.w.okButton.enable(True) self.glyphSelection = value if value == 3: self.w.textBox3.set("Check glyphs coloured in...") self.w.color_code2.enable(True) if value == 4: self.w.textBox3.set("Do not check glyphs coloured in...") self.w.color_code2.enable(True) if value < 3: self.w.textBox3.set("Colour Conditions") self.w.color_code2.enable(False) def ExportCheckBoxCallback(self, sender): value = sender.get() if value == 1: self.GlyphExport = 1 if value == 0: self.GlyphExport = 0 def SelectedMastersCheckBoxCallback(self, sender): value = sender.get() if value == 1: self.w.editText.enable(True) self.w.popUpButton.enable(True) if value == 0: self.w.editText.enable(False) self.w.popUpButton.enable(False) def editTextCallback(self, sender): print ("text entry!"), sender.get() def popUpButtonCallback(self, sender): value = sender.get() if value not in self.SelectedMasters: self.SelectedMasters.append(value) s = self.w.editText.get() + self.MastersNames[value] + "\n" self.w.editText.set(s) def MarkErrorsCheckBoxCallback(self, sender): value = sender.get() if value == 1: self.w.color_code.enable(True) self.ColourGlyphs = 1 if value == 0: self.w.color_code.enable(False) self.ColourGlyphs = 0 def ColorCodeCallback(self, sender): #colour popup value = sender.get() if self.w.color_code.get(): self.ColourType = value def ColorCodeCallback2(self, sender): #colour popup value = 0 value = sender.get() if self.w.color_code2.get(): self.CheckColourType = value def okButtonCallback(self, sender): self.GlyphGroups = [] self.GroupNames = [] self.GlobalError = False #Masters if len(self.SelectedMasters) == 0: for i in range(len(self.MastersNames)): self.SelectedMasters.append(i) #Glyphs ErrorStatus = False class ErrorWindow(object): def __init__(self): self.w = Window((350, 150), "Warning") self.w.box = Box((10, 10, -10, -10)) self.w.box.text = TextBox((10, 10, -10, -10), ewText, "center") self.w.center() self.w.open() if self.glyphSelection == 0: #All Glyphs for g in self.f.glyphs: self.SelectedGlyphs.append(g) if self.glyphSelection == 1: #Selected Glyphs for g in self.f.glyphs: if g.selected: self.SelectedGlyphs.append(g) if len(self.SelectedGlyphs) == 0: ewText = "You may not have any glyphs selected." ErrorWindow() ErrorStatus = True if self.glyphSelection == 2: # Unselected Glyphs for g in self.f.glyphs: if g.selected: pass else: self.SelectedGlyphs.append(g) if len(self.SelectedGlyphs) == 0: ewText = "You may not have any unselected glyphs." ErrorWindow() ErrorStatus = True if self.glyphSelection == 3: #Marked Glyphs for g in self.f.glyphs: if g.color == self.CheckColourType: self.SelectedGlyphs.append(g) if self.CheckColourType == 12: #No Colour if g.color == None: self.SelectedGlyphs.append(g) if self.CheckColourType == 13: #Any Colour if g.color != None: self.SelectedGlyphs.append(g) if len(self.SelectedGlyphs) == 0: ewText = "You may not have any glyphs marked with the selected colour." ErrorWindow() ErrorStatus = True if self.glyphSelection == 4: #Unmarked Glyphs for g in self.f.glyphs: if g.color != self.CheckColourType: self.SelectedGlyphs.append(g) if self.CheckColourType == 12: #No Colour self.SelectedGlyphs = [] if self.CheckColourType == 13: #Any Colour if g.color == None: self.SelectedGlyphs.append(g) if len(self.SelectedGlyphs) == 0: if self.CheckColourType == 12: ewText = "This option returns zero glyphs to check." else: ewText = "You may not have any unmarked glyphs." ErrorWindow() ErrorStatus = True if self.GlyphExport == 0: #Do not include non-Exporting glyphs for i in range(len(self.SelectedGlyphs)-1,-1,-1): if self.SelectedGlyphs[i].export == False: del self.SelectedGlyphs[i] #self.SelectedMasters contains Master index to use #self.SelectedGlyphs contains Glyph objects to use #--------------START THE TESTING HERE--------------------- # Font def CheckFontCredits(): rl = ["Font Credentials\n"] if self.f.familyName == "": rl.append("Font Family Name is not present.\n") if self.f.designer == "": rl.append(self.f.familyName + " Designer Name is not present\n") if self.f.designerURL == "": rl.append(self.f.familyName + " Designer URL is not present\n") if self.f.manufacturer == "": rl.append(self.f.familyName + " Manufacturer Name should be: The Northern Block Ltd. \n") elif self.f.manufacturer != "The Northern Block Ltd.": rl.append(self.f.familyName + " Manufacturer Name should be: The Northern Block Ltd. \n") if self.f.manufacturerURL == "": rl.append(self.f.familyName + " Manufacturer URL should be: http://www.thenorthernblock.co.uk \n") elif self.f.manufacturerURL != "http://www.thenorthernblock.co.uk": rl.append(self.f.familyName + " Manufacturer URL should be: http://www.thenorthernblock.co.uk \n") if self.f.copyright == "": rl.append(self.f.familyName + " Copyright string is not present\n") if self.f.versionMajor == None: rl.append(self.f.familyName + " Major Version Number is not present\n") if self.f.versionMinor == None: rl.append(self.f.familyName + " Minor Version Number is not present\n") if self.f.date == None: rl.append(self.f.familyName + " Date is not present\n") if self.f.customParameters['fsType'] == None: rl.append(self.f.familyName + " fsType is not present\n") elif self.f.customParameters['fsType'] != [2]: rl.append(self.f.familyName + " fsType is not Preview & Print\n") if self.f.trademark == None: rl.append(self.f.familyName + " Trademark string is not present\n") # if self.f.customParameters['licenseURL'] == None: # rl.append(self.f.familyName + " License URL string should be: http://thenorthernblock.co.uk/standard-license-agreement \n") # elif self.f.customParameters['licenseURL'] != "http://thenorthernblock.co.uk/standard-license-agreement": # rl.append(self.f.familyName + " License URL string should be: http://thenorthernblock.co.uk/standard-license-agreement \n") if self.f.description == None: rl.append(self.f.familyName + " Description string is not present\n") # if self.f.customParameters['vendorID'] != "TNB": # rl.append(self.f.familyName + " VendorID string should be: TNB\n") # if self.f.customParameters['versionString'] == None: # rl.append(self.f.familyName + " Version string is not present\n") # for fontInfoValue in self.f.properties: # if not hasattr(self.f.properties, 'licenseURL'): if not any(obj.key == 'licenseURL' for obj in self.f.properties): rl.append(self.f.familyName + " License URL string is missing. Should be http://thenorthernblock.co.uk/standard-license-agreement \n") elif len([obj for obj in self.f.properties if obj.key == 'licenseURL' and obj.value == 'http://thenorthernblock.co.uk/standard-license-agreement']) == 0 : rl.append(self.f.familyName + " License URL string should be: http://thenorthernblock.co.uk/standard-license-agreement \n") if not any(obj.key == 'vendorID' for obj in self.f.properties): rl.append(self.f.familyName + " vendorID string is missing. Should be TNB \n") elif len([obj for obj in self.f.properties if obj.key == 'vendorID' and obj.value == 'TNB']) == 0 : rl.append(self.f.familyName + " TNB \n") if not any(obj.key == 'versionString' for obj in self.f.properties): rl.append(self.f.familyName + " Version string is missing. \n") elif not len([obj for obj in self.f.properties if obj.key == 'versionString' and obj.value == 'New Value' ]) == 0 : rl.append(self.f.familyName + " Version string is not present. \n") # rl.append(fontInfoValue.key + "\n") for m in self.f.masters: if len(m.stems) == 0: rl.append(self.f.familyName + " " + m.name + ", Stem Values are missing.\n") #Uncomment following lines and comment lines 327, 328 for Glyphs version 2 #if len(m.stems) == 0: # rl.append(self.f.familyName + " " + m.name + ", Vertical Stem Values are missing.\n") #if len(m.horizontalStems) == 0: # rl.append(self.f.familyName + " " + m.name + ", Horizontal Stem Values are missing.\n") return rl def GetWidthGroups(): self.GlyphGroups = [] self.GroupNames = [] path = os.path.expanduser("~/Library/Application Support/Glyphs/Scripts/TNBscripts/TNBLibs/WidthGroups.txt") f = open(path, "r") filelines = [line.replace("\n","").split(",") for line in f] f.close self.GlyphGroups = filelines[2:len(filelines)-2] self.GroupNames = filelines[len(filelines)-1] GetWidthGroups() # Check Minimum Glyphset coverage def GetMinGlyphs(): MinGlyphNames = [] path = os.path.expanduser("~/Library/Application Support/Glyphs/Scripts/TNBscripts/TNBLibs/MinGlyphSet.txt") f = open(path, "r") filelines = [line.replace("\n","").split(",") for line in f] f.close MinGlyphNames = filelines[2:len(filelines)] return MinGlyphNames[0] def CheckMinGlyphCoverage(): ExtraGlyphs = ["rupeeIndian","liraTurkish","Ohm","increment","micro"] ExtraGlyphsUni = ["uni20B9","uni20BA","uni2126","uni2206","uni00B5"] GlyphCover = [] MinGlyphNames = GetMinGlyphs() if len(MinGlyphNames) > 0: FontGlyphset = [] NotPresent = [] for g in self.f.glyphs: if g.name in FontGlyphset: s = g.name + "appears more than once in the font.\n" GlyphCover.append(self.fN + ", glyph " + s) if g.name not in FontGlyphset: FontGlyphset.append(g.name) #Glyphnames in font for n in range(len(MinGlyphNames)): if MinGlyphNames[n] not in FontGlyphset: NotPresent.append(MinGlyphNames[n]) if len(NotPresent) > 0: for n in range(len(NotPresent)-1,-1,-1): if NotPresent[n] in ExtraGlyphsUni: m = ExtraGlyphsUni.index(NotPresent[n]) if ExtraGlyphs[m] in FontGlyphset: del NotPresent[n] if len(NotPresent) > 0: for n in range(len(NotPresent)): s = NotPresent[n] + " is not in the font glyphset.\n" GlyphCover.append(self.fN + ", glyph " + s) return GlyphCover # Check Cyrillic Glyphset def GetMinCyr(): #MinCyrNames = [] path = os.path.expanduser("~/Library/Application Support/Glyphs/Scripts/TNBscripts/TNBLibs/CyrillicGlyphs.txt") f = open(path, "r") filelines = [line.replace("\n","").split(",") for line in f] f.close MinCyrNames = filelines[2:len(filelines)] return MinCyrNames[0] def CheckCyrGlyphCoverage(): CyrGlyphNames = GetMinCyr() CyrCover = [] RublePresent = 0 if len(CyrGlyphNames) > 0: for g in self.f.glyphs: if g.name in CyrGlyphNames: CyrCover.append(g.name) if g.name == "ruble": RublePresent = 1 if len(CyrGlyphNames) == len(CyrCover): return (1, RublePresent) else: return (0, RublePresent) # Check All Caps and lowercase (and Small Caps) exist def CheckRequiredExist(): GNames = [] AllNames = [] NotUsed = ["Delta", "Omega", "Ohm", "Idotaccent"] MissingRequired = [] for g in self.f.glyphs: if g.export == True or g.export == False and self.GlyphExport == 1: AllNames.append(g.name) if g.name not in NotUsed: if g.name[0].isupper() and "." not in g.name and "_" not in g.name: GNames.append(g.name.lower()) #Check for missing Lowercase for n in GNames: if n not in AllNames: s = n + " is not in the font glyphset.\n" MissingRequired.append(self.fN + ", glyph " + s) #Check for missing SmallCaps scCount = 0 for n in AllNames: if ".sc" in n: scCount = scCount +1 #SmallCaps Exist if scCount > 26: for n in GNames: if n + ".sc" not in AllNames: s = n + ".sc is not in the font glyphset.\n" MissingRequired.append(self.fN + ", glyph " + s) return MissingRequired # Check font values are integers def CheckForNonIntegerPoints(Master, Glyph): l = Glyph.layers[self.f.masters[Master].id] if len(l.paths) != 0: if math.modf(l.width)[0] != 0: s = "Width is not an integer value.\n" self.ReportLines.append(l.name + ", " + g.name + ", " +s) for path in l.paths: for node in path.nodes: if math.modf(node.x)[0] != 0.0 or math.modf(node.y)[0] != 0.0: s= "Node " + str(node.index) + " is not an integer value.\n" self.ReportLines.append(l.name + ", " + g.name + ", " +s) self.f.disableUpdateInterface() for m in self.SelectedMasters: for g in self.SelectedGlyphs: CheckForNonIntegerPoints(m, g) self.f.enableUpdateInterface() # Check Components for Automatic Alignment. def CheckCompsAlign(): MathSymbols = [] for w in range(len(self.GroupNames)): if self.GroupNames[w] == "Math Symbols": MathSymbols = self.GlyphGroups[w] for Master in self.SelectedMasters: for g in self.SelectedGlyphs: l = g.layers[self.f.masters[Master].id] if len(l.components)==2: if g.name not in MathSymbols: if l.components[0].position[0] != 0 or l.components[0].position[1] != 0: s = "First position component is not position x=0,y=0.\n" self.ReportLines.append(l.name + ", " + g.name + ", " +s) if self.ColourGlyphs == 1: l.color = self.ColourType # self.f.enableUpdateInterface() if l.components[0].automaticAlignment == True: pass else: s = "Components do not have Automatic Alignment enabled.\n" self.ReportLines.append(l.name + ", " + g.name + ", " +s) #print Glyph.color if self.ColourGlyphs == 1: l.color = self.ColourType # self.f.enableUpdateInterface() #Check Ellipsis if g.name == "ellipsis": ep = 0 if len(l.components) != 3: s = "This is not components of the period.\n" self.ReportLines.append(l.name + ", " + g.name + ", " +s) if self.ColourGlyphs == 1: l.color = self.ColourType # self.f.enableUpdateInterface() if len(l.components) == 3: p1 = l.components[0].position[0] p2 = l.components[1].position[0] p3 = l.components[2].position[0] if p3 > p2 > p1: pass else: ep = 1 s = "The components are not in the correct order(1-2-3).\n" self.ReportLines.append(l.name + ", " + g.name + ", " +s) if self.ColourGlyphs == 1: l.color = self.ColourType # self.f.enableUpdateInterface() if ep == 0: if ((p3 - p2)-2) <= (p2 - p1) <= ((p3 - p2)+2): pass else: ep = 1 s = "Spacing of components is not correct.\n" self.ReportLines.append(l.name + ", " + g.name + ", " +s) if self.ColourGlyphs == 1: l.color = self.ColourType # self.f.enableUpdateInterface() if ep == 0: if (l.width -2) <= (p2 - p1)*3 <= (l.width +2): pass else: #print (l.width -2),(p2 - p1)*3,(l.width +2) s = "Spacing of components is not correct.\n" self.ReportLines.append(l.name + ", " + g.name + ", " +s) if self.ColourGlyphs == 1: l.color = self.ColourType # self.f.enableUpdateInterface() self.f.disableUpdateInterface() CheckCompsAlign() self.f.enableUpdateInterface() # Check Alt Caron shape is like acute def GetSegmentLength(Layer): PathSegments = 0 for path in Layer.paths: for segment in path.segments: PathSegments = PathSegments + len(segment) return PathSegments def GetLayerSeglenths(Master, Glyph): l = Glyph.layers[self.f.masters[Master].id] return GetSegmentLength(l) self.f.disableUpdateInterface() for m in self.SelectedMasters: AcuteLength = 0 AltCaronLength = 0 Exists = 0 for g in self.f.glyphs: if g.name == "acute": AcuteLength = GetLayerSeglenths(m, g) if AcuteLength > 0: AcuteAccent = g if g.name == "acutecomb": AcuteLength = GetLayerSeglenths(m, g) if AcuteLength > 0: AcuteAccent = g if g.name == "caroncomb.alt": AltCaronLength = GetLayerSeglenths(m, g) if AltCaronLength > 0: CaronComb = g if AcuteLength != 0 and AltCaronLength != 0: if AcuteLength != AltCaronLength: s = "caroncomb.alt may not be the correct design.\n" self.ReportLines.append(g.layers[self.f.masters[m].id].name + ", " +s) self.f.enableUpdateInterface() #Check composite2 is at y=0 in Ldlt carons def CheckCaronPosition(Master): CaronGlyphs = ["Lcaron", "dcaron", "lcaron", "tcaron"] for g in self.f.glyphs: if g.name in CaronGlyphs: l = g.layers[self.f.masters[Master].id] if len(l.components) == 2: if l.components[1].position[1] != 0: s = "Caron component is not position y=0 or component 2 is not caron accent.\n" self.ReportLines.append(g.layers[self.f.masters[Master].id].name + ", " + g.name + ", " +s) self.f.disableUpdateInterface() for m in self.SelectedMasters: CheckCaronPosition(m) self.f.enableUpdateInterface() # Duplicate and Add Extremes def DuplicateAndAddExtremes(Master, OriginalGlyph): l = OriginalGlyph.layers[self.f.masters[Master].id] c = copy.copy(l) c.removeOverlap() c.addNodesAtExtremes() return c # Calculate Layer Checksum def GetLayerChecksum(layer): checksum = 0 for path in layer.paths: for node in path.nodes: checksum = checksum + node.x checksum = checksum + node.y return checksum # Do Extremes Test def DoExtremesTest(Master, Glyph): l = Glyph.layers[self.f.masters[Master].id] if len(l.paths) > 0: c = copy.copy(l) c.removeOverlap() OriginalChecksum = GetLayerChecksum(c) NewGlyphLayer = DuplicateAndAddExtremes(Master, Glyph) NewChecksum = GetLayerChecksum(NewGlyphLayer) if OriginalChecksum != NewChecksum: s = "Not all nodes are on Extremes.\n" self.ReportLines.append(l.name + ", " + Glyph.name + ", " +s) #print Glyph.color if self.ColourGlyphs == 1: l.color = self.ColourType # self.f.enableUpdateInterface() if self.w.ExtremesCheckBox.get() == 1: self.f.disableUpdateInterface() for m in self.SelectedMasters: for g in self.SelectedGlyphs: DoExtremesTest(m, g) self.f.enableUpdateInterface() # Duplicate and Decompose def DuplicateAndAddLayer(Master, OriginalLayer): NodeArray = [] newLayer = copy.copy(OriginalLayer) newLayer.parent = OriginalLayer.parent newLayer.decomposeComponents() newLayer.removeOverlap() for path in newLayer.paths: for node in path.nodes: if node.type == "line" or node.type == "curve": NodeArray.append((node.x, node.y)) return NodeArray # Do Parent/Child Match Test def DoPCTest(Master): ChildGlyphs = ["Aogonek","Ccedilla","Eth","Dcroat","Eogonek","Hbar","Iogonek","Lcaron","Lslash","Tbar","ccedilla","dcroat","hbar","lslash","tbar","tcaron"] ParentGlyphs = ["A","C","D","D","E","H","I","L","L","T","c","d","h","l","t","t"] ChildNodes = [] ParentNodes = [] for g in self.SelectedGlyphs: if g.name in ChildGlyphs: l = g.layers[self.f.masters[Master].id] if len(l.components) == 0: i = ChildGlyphs.index(g.name) ChildNodes = DuplicateAndAddLayer(Master, l) #print ChildNodes for g2 in self.f.glyphs: if g2.name == ParentGlyphs[i]: l2 = g2.layers[self.f.masters[Master].id] c2 = copy.copy(l2) c2.removeOverlap() for path in c2.paths: for node in path.nodes: if node.type == "line" or node.type == "curve": ParentNodes.append((round(node.x, 0), round(node.y, 0))) #print ParentNodes if all(x in ChildNodes for x in ParentNodes) == False: s = g.name + " is not the same as the parent glyph " + g2.name + "\n" self.ReportLines.append(l.name + ", " +s) ChildNodes = [] ParentNodes = [] self.f.disableUpdateInterface() for m in self.SelectedMasters: DoPCTest(m) self.f.enableUpdateInterface() # Width Test def WidthsTest(GlyphGroup, GroupName): for Master in self.SelectedMasters: Total = 0 Group = [] for g in self.SelectedGlyphs: if g.name in GlyphGroup: l = g.layers[self.f.masters[Master].id] Total = Total + l.width Group.append(l) if len(Group) > 0: if Total / len(Group) == Group[0].width: pass else: s = "Widths are not the same\n" self.ReportLines.append(l.name + ", " + str(GroupName) + ", " +s) #print Glyph.color if self.ColourGlyphs == 1: l.color = self.ColourType # self.f.enableUpdateInterface() if self.w.WidthsCheckBox.get() == 1: for w in range(len(self.GlyphGroups)): WidthsTest(self.GlyphGroups[w],self.GroupNames[w]) # Glyph Width Test def GetGlyphWidthGroups(): self.GlyphWidthGroups = [] self.GroupWidthNames = [] path = os.path.expanduser("~/Library/Application Support/Glyphs/Scripts/TNBscripts/TNBLibs/GlyphWidthGroups.txt") f = open(path, "r") filelines = [line.replace("\n","").split(",") for line in f] f.close self.GlyphWidthGroups = filelines[2:len(filelines)-2] self.GroupWidthNames = filelines[len(filelines)-1] GetGlyphWidthGroups() # Glyph Width Test def GlyphWidthsTest(GlyphGroup, GroupName): for Master in self.SelectedMasters: Total = 0 Group = [] Widths = [] Gname = [] for g in self.SelectedGlyphs: if g.name in GlyphGroup: l = g.layers[self.f.masters[Master].id] Total = Total + l.width Group.append(l) Widths.append(l.width) Gname.append(g.name) if len(Group) > 0: Fails = [] if Total / len(Group) == Group[0].width: pass else: for f in range(len(Widths)): if Widths[f] != Widths[0]: Fails.append(Gname[f]) for i in range(len(Fails)): s = "Width is not the same as " + GroupName + "\n" self.ReportLines.append(l.name + ", " + Fails[i] + ", " +s) #print Glyph.color if self.ColourGlyphs == 1: l.color = self.ColourType # self.f.enableUpdateInterface() if self.w.WidthsCheckBox.get() == 1: self.f.disableUpdateInterface() for w in range(len(self.GlyphWidthGroups)): GlyphWidthsTest(self.GlyphWidthGroups[w],self.GroupWidthNames[w]) #Check ss0# variations StyleVariants = [".ss01",".ss02",".ss03",".ss04",".ss05"]#,".ss06",".ss07",".ss08",".ss09",] for v in range(len(StyleVariants)): GlyphWidthGroupsVars = [] GlyphWidthNamesVars = [] for gv in range(len(self.GlyphWidthGroups[w])): GlyphWidthGroupsVars.append(self.GlyphWidthGroups[w][gv]+StyleVariants[v]) GlyphWidthNamesVars.append(self.GroupWidthNames[w]+StyleVariants[v]) GlyphWidthsTest(GlyphWidthGroupsVars,GlyphWidthNamesVars[0]) self.f.enableUpdateInterface() # Symmetry Test def GetSymmetryGroups(): self.GlyphGroups = [] self.GroupNames = [] path = os.path.expanduser("~/Library/Application Support/Glyphs/Scripts/TNBscripts/TNBLibs/SymmetryGroups.txt") f = open(path, "r") filelines = [line.replace("\n","").split(",") for line in f] f.close self.GlyphGroups = filelines[2:len(filelines)-2] self.GroupNames = filelines[len(filelines)-1] GetSymmetryGroups() def SymmetryTest(GlyphGroup, GroupName): for Master in self.SelectedMasters: BoundsTop = [] BoundsBottom = [] for g in self.SelectedGlyphs: if g.name in GlyphGroup: l = g.layers[self.f.masters[Master].id] BoundsBottom.append(l.bounds.origin.y) BoundsTop.append(l.bounds.origin.y + l.bounds.size.height) if len(BoundsTop) > 0 and len(BoundsBottom) > 0: if len(set(BoundsTop)) == 1 and len(set(BoundsBottom)) == 1: pass else: s = "Vertical Symmetry is not the same\n" self.ReportLines.append(l.name + ", " + str(GroupName) + ", " +s) #print Glyph.color if self.ColourGlyphs == 1: l.color = self.ColourType # self.f.enableUpdateInterface() if self.w.VertSymCheckBox.get() == 1: self.f.disableUpdateInterface() for w in range(len(self.GlyphGroups)): SymmetryTest(self.GlyphGroups[w],self.GroupNames[w]) self.f.enableUpdateInterface() # Path Tidy def PathTidy(Master, Glyph): l = Glyph.layers[self.f.masters[Master].id] if len(l.paths) > 0: c = copy.copy(l) c.removeOverlap() ck1 = c.compareString() c = copy.copy(l) c.removeOverlap() c.cleanUpPaths() ck2 = c.compareString() if ck1 != ck2: #pass #else: s = "Contours may have problems.\n" self.ReportLines.append(l.name + ", " + Glyph.name + ", " +s) #print Glyph.color if self.ColourGlyphs == 1: l.color = self.ColourType # self.f.enableUpdateInterface() if self.w.PathTidyCheckBox.get() == 1: self.f.disableUpdateInterface() for m in self.SelectedMasters: for g in self.SelectedGlyphs: PathTidy(m, g) self.f.enableUpdateInterface() # Horizonal and Vertical stroke accuracy def TestPointPositionsInY(x1, y1, x2, y2): if (y1 -2) <= y2 <= (y1 +2): #Testing inacuraccy of +-2 if round(y1,1) == round(y2,1): return 0 elif x2 - x1 > 8 or x1 - x2 > 8: #Only flag if segment is longer than 8 units. return 1 else: return 0 def TestPointPositionsInX(x1, y1, x2, y2): if (x1 -2) <= x2 <= (x1 +2): #Testing inacuraccy of +-2 if round(x1,1) == round(x2,1): return 0 elif y2 - y1 > 8 or y1 - y2 > 8: #Only flag if segment is longer than 8 units. return 1 else: return 0 def GetSegmentArray(Master, Glyph): l = Glyph.layers[self.f.masters[Master].id] if len(l.paths) != 0: c = copy.copy(l) c.decomposeComponents() c.removeOverlap() c.roundCoordinates() for path in c.paths: Segarray = [] for segment in path.segments: if len(segment) == 2: #Get straight line segments. Segarray = [float(s) for s in re.findall(r'-?\d+\.?\d*', str(segment[0])+str(segment[1]))] if len(Segarray) > 0: if TestPointPositionsInY(Segarray[0], Segarray[1], Segarray[2], Segarray[3]) == 1: self.ReportLines.append(self.f.masters[Master].name +", " + Glyph.name + ", has bad x direction straights at x" + str(round(Segarray[0],1)) +" y" + str(round(Segarray[1],1)) + " to x" + str(round(Segarray[2],1)) + " y" + str(round(Segarray[3],1))+"\n") if self.ColourGlyphs == 1: l.color = self.ColourType # self.f.enableUpdateInterface() if self.IsItalic == False: if TestPointPositionsInX(Segarray[0], Segarray[1], Segarray[2], Segarray[3]) == 1: self.ReportLines.append(self.f.masters[Master].name +", " + Glyph.name + ", has bad y direction straights at x" + str(round(Segarray[0],1)) +" y" + str(round(Segarray[1],1)) + " to x" + str(round(Segarray[2],1)) + " y" + str(round(Segarray[3],1))+"\n") if self.ColourGlyphs == 1: l.color = self.ColourType # self.f.enableUpdateInterface() #Check Handel Accuracy def CheckNodeAlign(Handle1, Node, Handle2): #print Handle1, Node, Handle2 if Handle1 - Node == 0: if Handle2 - Node == 1: return True if Handle2 - Node == -1: return True if Handle2 - Node == 0: if Handle1 - Node == 1: return True if Handle1 - Node == -1: return True else: return False def CheckHandelAccuracy(Master, Glyph): l = Glyph.layers[self.f.masters[Master].id] for path in l.paths: NodesType=[] NodesX = [] NodesY = [] for node in path.nodes: NodesType.append(node.type) NodesX.append(node.x) NodesY.append(node.y) for i in range(len(NodesType)-1): if i > 0: if NodesType[i-1] + NodesType[i] + NodesType[i+1] == 'offcurvecurveoffcurve': if CheckNodeAlign(NodesX[i-1], NodesX[i], NodesX[i+1]) == True: self.ReportLines.append(self.f.masters[Master].name +", " + Glyph.name + " Node x" + str(NodesX[i]) + " y" + str(NodesY[i]) + " has misaligned handles.\n") if CheckNodeAlign(NodesY[i-1], NodesY[i], NodesY[i+1]) == True: self.ReportLines.append(self.f.masters[Master].name +", " + Glyph.name + " Node x" + str(NodesX[i]) + " y" + str(NodesY[i]) + " has misaligned handles.\n") #Check Last Node of Contour. if NodesType[0] + NodesType[len(NodesType)-1] + NodesType[len(NodesType)-2] == 'offcurvecurveoffcurve': if CheckNodeAlign(NodesX[0], NodesX[len(NodesType)-1], NodesX[len(NodesType)-2]) == True: self.ReportLines.append(self.f.masters[Master].name +", " + Glyph.name + " Node x" + str(len(NodesX)-1) + " y" + str(len(NodesY)-1) + " has misaligned handles.\n") if CheckNodeAlign(NodesY[0], NodesY[len(NodesType)-1], NodesY[len(NodesType)-2]) == True: self.ReportLines.append(self.f.masters[Master].name +", " + Glyph.name + " Node x" + str(len(NodesX)-1) + " y" + str(len(NodesY)-1) + " has misaligned handles.\n") if self.w.XYStrokesCheckBox.get() == 1: self.f.disableUpdateInterface() for m in self.SelectedMasters: for g in self.SelectedGlyphs: GetSegmentArray(m, g) CheckHandelAccuracy(m, g) self.f.enableUpdateInterface() #Path Directions Accuracy def PathDirectionCheckBox(Master, Glyph): l = Glyph.layers[self.f.masters[Master].id] if len(l.paths) > 0: c = copy.copy(l) c.correctPathDirection() for i in range(len(c.paths)): originalPath = l.paths[i] copyPath = c.paths[i] if copyPath.direction != originalPath.direction: self.ReportLines.append(self.f.masters[Master].name +", " + Glyph.name + " Check start point & path directions.\n") if self.w. PathDirectionCheckBox.get() == 1: self.f.disableUpdateInterface() for m in self.SelectedMasters: for g in self.SelectedGlyphs: PathDirectionCheckBox(m, g) self.f.enableUpdateInterface() # Start Point in lower Extreme def StartPointCheckBox(Master, Glyph): l = Glyph.layers[self.f.masters[Master].id] if len(l.paths) > 0: for i in range(len(l.paths)): path = l.paths[i] startNode = path.nodes[-1] minY = None minX = None for j in range(len(path.nodes)): #self.ReportLines.append(str(j)+" "+self.f.masters[Master].name +", " + Glyph.name +": "+str(path.nodes[j].position.x) + ", " + str(path.nodes[j].position.y) + ".\n") if(j == 0): minY = path.nodes[j].position.y minX = path.nodes[j].position.x if(path.nodes[j].position.y < minY): minY = path.nodes[j].position.y if(path.nodes[j].position.x < minX): minX = path.nodes[j].position.x if(startNode.position.y != minY and startNode.position.x != minX): self.ReportLines.append(self.f.masters[Master].name +", " + Glyph.name + " Start point is not in correct position " + str(startNode.position.x) + " " + str(startNode.position.y) +".\n") continue if self.w. StartPointCheckBox.get() == 1: self.f.disableUpdateInterface() for m in self.SelectedMasters: for g in self.SelectedGlyphs: StartPointCheckBox(m, g) self.f.enableUpdateInterface() #Check Blue Zones def CheckBluesMatchMetrics(Master): m = self.f.masters[Master] #Ascender b = 1 for a in m.alignmentZones: if a.position == m.ascender: b = 0 if b == 1: self.ReportLines.append(m.name +" Ascender Zone does not match Ascender Metrics.\n") #Cap Height b = 1 for a in m.alignmentZones: if a.position == m.capHeight: b = 0 if b == 1: self.ReportLines.append(m.name +" Cap Height Zone does not match Cap Height Metrics.\n") #xHeight b = 1 for a in m.alignmentZones: if a.position == m.xHeight: b = 0 if b == 1: self.ReportLines.append(m.name +" Lowercase Height Zone does not match Lowercase Height Metrics.\n") #Descender b = 1 for a in m.alignmentZones: if a.position == m.descender: b = 0 if b == 1: self.ReportLines.append(m.name +" Descender Zone does not match Descender Metrics.\n") def CheckBottomExtremeToZone(YPoint, BluePosition, BlueSize): if (BluePosition + BlueSize) <= YPoint <= (BluePosition): return 0 else: return -1 def CheckTopExtremeToZone(YPoint, BluePosition, BlueSize): if (BluePosition) <= YPoint <= (BluePosition + BlueSize): return 0 else: return -1 def CheckZonesForBasicGlyphs(Master): m = self.f.masters[Master] Ascenders = ["b","d","f","h","k","l"] Descenders = ["j","p","q","y"] Capitals = ("H","I","N","O","S","V") Lowercase = ("c","e","n","o","r","s","v") for g in self.f.glyphs: l = g.layers[self.f.masters[Master].id] if g.name in Ascenders: for a in m.alignmentZones: if a.position == m.ascender: YPoint = l.bounds.size.height + l.bounds.origin.y if CheckTopExtremeToZone(YPoint, a.position, a.size) != 0: s = "top is not in Ascender Zone.\n" self.ReportLines.append(l.name + ", " + g.name + ", " +s) if g.name in Descenders: for a in m.alignmentZones: if a.position == m.descender: if CheckBottomExtremeToZone(l.bounds.origin.y, a.position, a.size) != 0: s = "bottom is not in Descender Zone.\n" self.ReportLines.append(l.name + ", " + g.name + ", " +s) if g.name in Capitals: for a in m.alignmentZones: if a.position == m.capHeight: YPoint = l.bounds.size.height + l.bounds.origin.y if CheckTopExtremeToZone(YPoint, a.position, a.size) != 0: s = "top is not in Capitals Zone.\n" self.ReportLines.append(l.name + ", " + g.name + ", " +s) if g.name in Lowercase: for a in m.alignmentZones: if a.position == m.xHeight: YPoint = l.bounds.size.height + l.bounds.origin.y if CheckTopExtremeToZone(YPoint, a.position, a.size) != 0: s = "top is not in Lowercase Zone.\n" self.ReportLines.append(l.name + ", " + g.name + ", " +s) def GetSplitLowercase(): SplitLowercaseNames = [] path = os.path.expanduser("~/Library/Application Support/Glyphs/Scripts/TNBscripts/TNBLibs/SplitLowercaseGroup.txt") f = open(path, "r") filelines = [line.replace("\n","").split(",") for line in f] f.close SplitLowercaseNames = filelines[2:len(filelines)] return SplitLowercaseNames[0] def BlueZoneCheck(Master, Glyph): m = self.f.masters[Master] l = Glyph.layers[m.id] if len(m.alignmentZones) > 0: if len(l.components) == 0: #Get all yPos nodes - Do this on copied glyph with overlaps removed c = copy.copy(l) c.removeOverlap() c.roundCoordinates() Ycoords = [] for path in c.paths: if path.direction == -1: #Counter clockwise counters which should be the outside. for nodes in path.nodes: if nodes.type != "offcurve": Ycoords.append(nodes.position[1]) #Every y pos node m = self.f.masters[Master] # Check Glyphs Baseline Zone for a in m.alignmentZones: if a.position == 0: if CheckBottomExtremeToZone(l.bounds.origin.y, a.position + 3, a.size - 6) == 0: #if lower extreme is close to Baseline Zone if CheckBottomExtremeToZone(l.bounds.origin.y, a.position, a.size) == -1: s = "Some nodes are just off of Baseline Zone.\n" self.ReportLines.append(l.name + ", " + Glyph.name + ", " +s) # Check Glyphs X-height Zone if Glyph.subCategory is not None and Glyph.category + Glyph.subCategory == "LetterLowercase": for a in m.alignmentZones: if (a.position - a.size) <= m.xHeight <= a.position: #This is xHeight Zone which must match Master height metrics xHeight TopBound = l.bounds.size.height + l.bounds.origin.y if CheckTopExtremeToZone(TopBound, a.position -3, a.size +6) == 0: if CheckTopExtremeToZone(TopBound, a.position, a.size) == -1: s = "Some nodes are just outside xHeight Zone.\n" self.ReportLines.append(l.name + ", " + Glyph.name + ", " +s) else: #These must be ascenders SplitGlyphs = GetSplitLowercase() if g.name in SplitGlyphs: for n in range(len(Ycoords)): if CheckTopExtremeToZone(Ycoords[n], a.position -3, a.size +6) == 0: if CheckTopExtremeToZone(Ycoords[n], a.position, a.size) == -1: s = "Some nodes are just outside xHeight Zone.\n" self.ReportLines.append(l.name + ", " + Glyph.name + ", " +s) # Check Glyphs descender Zone if Glyph.subCategory is not None and Glyph.category + Glyph.subCategory == "LetterLowercase": for a in m.alignmentZones: if (a.position + a.size) <= m.descender <= a.position: #This is descender Zone which must match Master height metrics descender if CheckBottomExtremeToZone(l.bounds.origin.y, a.position +3, a.size -6) == 0: if CheckBottomExtremeToZone(l.bounds.origin.y, a.position, a.size) == -1: s = "Some nodes are just outside Descender Zone.\n" self.ReportLines.append(l.name + ", " + Glyph.name + ", " +s) # Check Glyphs ascender Zone if Glyph.subCategory is not None and Glyph.category + Glyph.subCategory == "LetterLowercase": for a in m.alignmentZones: if (a.position - a.size) <= m.ascender <= a.position: #This is ascender Zone which must match Master height metrics descender TopBound = l.bounds.size.height + l.bounds.origin.y if CheckTopExtremeToZone(TopBound, a.position -3, a.size +6) == 0: if CheckTopExtremeToZone(TopBound, a.position, a.size) == -1: s = "Some nodes are just outside Ascender Zone.\n" self.ReportLines.append(l.name + ", " + Glyph.name + ", " +s) elif len(m.alignmentZones) > 0: self.ReportLines.append(l.name + ", There are no Alignment Zones in the font.\n") if self.w.HeightsCheckBox.get() == 1: self.f.disableUpdateInterface() for m in self.SelectedMasters: CheckBluesMatchMetrics(m) CheckZonesForBasicGlyphs(m) for g in self.SelectedGlyphs: BlueZoneCheck(m, g) self.f.enableUpdateInterface() self.w.close() # Output Window fl = CheckFontCredits() if len(fl) == 1: #Just the header fl.append("\tNothing to Report\n") GlyphCoverage = CheckMinGlyphCoverage() if len(GlyphCoverage) == 0: GlyphCoverage.append("\tMinimum Glyph Coverage Appears Complete\n") MissingGlyphs = CheckRequiredExist() if CheckCyrGlyphCoverage() == (1, 0): GlyphCoverage.append("ruble is not in the font glyphset.\n") st = self.ReportLinesHeader for s in fl: st = st + s for s in GlyphCoverage: st = st + s for s in MissingGlyphs: st = st + s self.ReportLines.sort() for s in self.ReportLines: st = st + s class TextEditorWindow(object): def __init__(self): self.f = Glyphs.font # frontmost font self.fN = self.f.familyName if Glyphs.font.masters[0].italicAngle != 0: self.IsItalic = True self.fN = self.fN + " Italic" self.w = Window((100, 100, 500, 650), title = "Font Check", maxSize=(800, 1200)) self.w.myButton1 = Button((-100, -40, -20, 20), "Save", callback=self.myButton1Callback) self.w.myButton2 = Button((-200, -40, -120, 20), "Close", callback=self.myButton2Callback) self.w.textEditor = TextEditor((10, 10, -10, -60), text = st, readOnly = True, callback=self.textEditorCallback) self.w.center() self.w.open() def textEditorCallback(self, sender): sender.get() def myButton1Callback(self, sender): d = datetime.datetime.now() filename = self.fN + "_Fontcheck " +d.strftime("%Y")+"-"+d.strftime("%m")+"-"+d.strftime("%d")+" at "+d.strftime("%H")+"."+d.strftime("%M")+"."+d.strftime("%S")+ ".txt" completeName = os.path.join(os.path.expanduser('~'),'Desktop/'+filename) with open(completeName,"w") as f: f.writelines(st) f.close() self.w.close() Message("The Font Check file has been saved to your Desktop as:\n"+filename, "File Saved") def myButton2Callback(self, sender): self.w.close() if ErrorStatus == False: TextEditorWindow() def myButton2Callback(self, sender): self.w.close() TNBFontCheck()
Editor is loading...