Untitled
unknown
plain_text
3 years ago
48 kB
6
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...