Untitled

mail@pastecode.io avatar
unknown
plain_text
2 years ago
48 kB
2
Indexable
Never
#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()