class FontNanny(object): def __init__(self, font): self.w = FloatingWindow((185, 370), "Font Nanny") y = 5 self.w.info = TextBox((10, y, 180, 14), text="Glyph Checks (all Glyphs):", sizeStyle="small") y += 20 self.w.check1 = SquareButton((10, y, 145, 20), "Unicode values", callback=self.perform, sizeStyle="small") self.w.color1 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( 0, 0, 1, 0.3)) self.w.color1.enable(0) y += 20 self.w.check2 = SquareButton((10, y, 145, 20), "Contour Count", callback=self.perform, sizeStyle="small") self.w.color2 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( 5, 0, 0, 0.4)) self.w.color2.enable(0) y += 25 self.w.info2 = TextBox((10, y, 180, 14), text="Outline Checks (all Glyphs):", sizeStyle="small") y += 20 self.w.check3 = SquareButton((10, y, 145, 20), "Stray Points", callback=self.perform, sizeStyle="small") self.w.color3 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( .7, .0, .7, .9)) self.w.color3.enable(0) y += 20 self.w.check4 = SquareButton((10, y, 145, 20), "Small Contours", callback=self.perform, sizeStyle="small") self.w.color4 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( 0, .8, 0, 0.9)) self.w.color4.enable(0) y += 20 self.w.check5 = SquareButton((10, y, 145, 20), "Open Contours", callback=self.perform, sizeStyle="small") self.w.color5 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( .4, .4, .4, 0.8)) self.w.color5.enable(0) y += 20 self.w.check6 = SquareButton((10, y, 145, 20), "Duplicate Contours", callback=self.perform, sizeStyle="small") self.w.color6 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( 1, 0, 0, 0.6)) self.w.color6.enable(0) y += 20 self.w.check7 = SquareButton((10, y, 145, 20), "Extreme points", callback=self.perform, sizeStyle="small") self.w.color7 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( 0, .9, 0, 0.6)) self.w.color7.enable(0) y += 20 self.w.check8 = SquareButton((10, y, 145, 20), "Unnecessary Points", callback=self.perform, sizeStyle="small") self.w.color8 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( 1, .0, 1, 0.8)) self.w.color8.enable(0) y += 20 self.w.check9 = SquareButton((10, y, 145, 20), "Unnecessary Handles", callback=self.perform, sizeStyle="small") self.w.color9 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( .4, .0, .0, .3)) self.w.color9.enable(0) y += 20 self.w.check10 = SquareButton((10, y, 145, 20), "Overlapping Points", callback=self.perform, sizeStyle="small") self.w.color10 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( .0, .0, .6, .3)) self.w.color10.enable(0) y += 20 self.w.check11 = SquareButton((10, y, 145, 20), "Points near vert. Metrics", callback=self.perform, sizeStyle="small") self.w.color11 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( 1, 1, .0, .2)) self.w.color11.enable(0) y += 20 self.w.check12 = SquareButton((10, y, 145, 20), "Complex Curves", callback=self.perform, sizeStyle="small") self.w.color12 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_(1, 1, 0, 1)) self.w.color12.enable(0) y += 20 self.w.check13 = SquareButton((10, y, 145, 20), "Crossed handles", callback=self.perform, sizeStyle="small") self.w.color13 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( 1, 0, .4, .2)) self.w.color13.enable(0) y += 20 self.w.check14 = SquareButton((10, y, 145, 20), "Straight Lines", callback=self.perform, sizeStyle="small") self.w.color14 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( .0, .6, .0, .3)) self.w.color14.enable(0) y += 30 self.w.horizontalLine = HorizontalLine((10, y - 5, 165, 1)) self.w.clearGlyphMarksButton = SquareButton( (10, y, 90, 20), "Clear all marks", callback=self.clearGlyphMarks, sizeStyle="small") self.w.colorClearGlyphMarks = ColorWell( (100, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_(1, 1, 1, 1)) self.w.colorClearGlyphMarks.enable(0) self.w.closeWin = SquareButton((120, y, -10, 20), "x) close", callback=self.CloseWindow, sizeStyle="small") self.w.open() def CloseWindow(self, sender): self.w.close() def clearGlyphMarks(self, sender): font = CurrentFont() for g in font: if g.mark == (0, 0, 1, 0.3) or (5, 0, 0, 0.4) or ( .7, .0, .7, .9 ) or (0, .8, 0, 0.9) or (.4, .4, .4, 0.8) or (1, 0, 0, 0.6) or ( 0, .9, 0, 0.6) or (1, .0, 1, 0.8) or (.4, .0, .0, .3) or ( .0, .0, .6, .3) or (1, 1, .0, .2) or (1, 1, 0, 1) or ( 1, 0, .4, .2) or (.0, .6, .0, .3): g.mark = None def perform(self, sender): senderInput = sender.getTitle() f = CurrentFont() tickCount = len(CurrentFont()) progressBar = ProgressBar(title=senderInput, ticks=tickCount, label="checking all the glyphs...") tick = 0 if senderInput == "Open Contours": for glyph in f: self.testForOpenContours(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Extreme points": for glyph in f: self.testForExtremePoints(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Straight Lines": for glyph in f: self.testForStraightLines(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Crossed handles": for glyph in f: self.testForCrossedHandles(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Unicode values": for glyph in f: self.testUnicodeValue(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Contour Count": for glyph in f: self.testContourCount(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Duplicate Contours": for glyph in f: self.testDuplicateContours(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Small Contours": for glyph in f: self.testForSmallContours(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Complex Curves": for glyph in f: self.testForComplexCurves(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Unnecessary Points": for glyph in f: self.testForUnnecessaryPoints(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Overlapping Points": for glyph in f: self.testForOverlappingPoints(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Unnecessary Handles": for glyph in f: self.testForUnnecessaryHandles(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Stray Points": for glyph in f: self.testForStrayPoints(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Points near vert. Metrics": for glyph in f: self.testForPointsNearVerticalMetrics(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() def testUnicodeValue(self, glyph): """ A Unicode value should appear only once per font. """ report = [] font = glyph.getParent() uni = glyph.unicode name = glyph.name # test against AGL expectedUni = AGL2UV.get(name) if expectedUni != uni: report.append("Incorrect Unicode value?: %s." % name) glyph.mark = (0, 0, 1, 0.3) # look for duplicates if uni is not None: duplicates = [] for name in sorted(font.keys()): if name == glyph.name: continue other = font[name] if other.unicode == uni: duplicates.append(name) glyph.mark = (0, 0, 1, 0.3) report.append("The Unicode for this glyph is also used by: %s" % " ".join(duplicates)) # Glyph Construction def testContourCount(self, glyph): """ There shouldn't be too many overlapping contours. """ report = [] count = len(glyph) test = glyph.copy() test.removeOverlap() if count - len(test) > 2: report.append( "This glyph has a unusally high number of overlapping contours." ) glyph.mark = (5, 0, 0, 0.4) return report def testDuplicateContours(self, glyph): """ Contours shouldn't be duplicated on each other. """ contours = {} for index, contour in enumerate(glyph): contour = contour.copy() contour.autoStartSegment() pen = DigestPointPen() contour.drawPoints(pen) digest = pen.getDigest() if digest not in contours: contours[digest] = [] contours[digest].append(index) duplicateContours = [] for digest, indexes in contours.items(): if len(indexes) > 1: duplicateContours.append(indexes[0]) glyph.mark = (1, 0, 0, 0.6) # Contours def testForSmallContours(self, glyph): """ Contours should not have an area less than or equal to 4 units. """ smallContours = {} for index, contour in enumerate(glyph): box = contour.box if not box: continue xMin, yMin, xMax, yMax = box w = xMax - xMin h = yMin - yMax area = abs(w * h) if area <= 4: smallContours[index] = contour.box glyph.mark = (0, .8, 0, 0.9) def testForOpenContours(self, glyph): """ Contours should be closed. """ openContours = {} for index, contour in enumerate(glyph): if not contour.open: continue start = contour[0].onCurve start = (start.x, start.y) end = contour[-1].onCurve end = (end.x, end.y) if start != end: openContours[index] = (start, end) glyph.mark = (.4, .4, .4, 0.8) def testForExtremePoints(self, glyph): """ Points should be at the extrema. """ pointsAtExtrema = {} for index, contour in enumerate(glyph): dummy = glyph.copy() dummy.clear() dummy.appendContour(contour) dummy.extremePoints() testPoints = _getOnCurves(dummy[0]) points = _getOnCurves(contour) if points != testPoints: pointsAtExtrema[index] = testPoints - points glyph.mark = (0, .9, 0, 0.6) def testForComplexCurves(self, glyph): """ S curves are suspicious. """ impliedS = {} for index, contour in enumerate(glyph): prev = _unwrapPoint(contour[-1].onCurve) for segment in contour: if segment.type == "curve": pt0 = prev pt1, pt2 = [_unwrapPoint(p) for p in segment.offCurve] pt3 = _unwrapPoint(segment.onCurve) line1 = (pt0, pt3) line2 = (pt1, pt2) if index not in impliedS: impliedS[index] = [] if _intersectLines(line1, line2): impliedS[index].append((prev, pt1, pt2, pt3)) glyph.mark = (1, 1, 0, 1) prev = _unwrapPoint(segment.onCurve) def testForCrossedHandles(self, glyph): """ Handles shouldn't intersect. """ crossedHandles = {} for index, contour in enumerate(glyph): pt0 = _unwrapPoint(contour[-1].onCurve) for segment in contour: pt3 = _unwrapPoint(segment.onCurve) if segment.type == "curve": pt1, pt2 = [_unwrapPoint(p) for p in segment.offCurve] # direct intersection direct = _intersectLines((pt0, pt1), (pt2, pt3)) if direct: if index not in crossedHandles: crossedHandles[index] = [] crossedHandles[index].append( dict(points=(pt0, pt1, pt2, pt3), intersection=direct)) glyph.mark = (1, 0, .4, .2) # indirect intersection else: while 1: # bcp1 = ray, bcp2 = segment angle = _calcAngle(pt0, pt1) if angle in (0, 180.0): t1 = (pt0[0] + 1000, pt0[1]) t2 = (pt0[0] - 1000, pt0[1]) else: yOffset = _getAngleOffset(angle, 1000) t1 = (pt0[0] + 1000, pt0[1] + yOffset) t2 = (pt0[0] - 1000, pt0[1] - yOffset) indirect = _intersectLines((t1, t2), (pt2, pt3)) if indirect: if index not in crossedHandles: crossedHandles[index] = [] crossedHandles[index].append( dict(points=(pt0, indirect, pt2, pt3), intersection=indirect)) break # bcp1 = segment, bcp2 = ray angle = _calcAngle(pt3, pt2) if angle in (90.0, 270.0): t1 = (pt3[0], pt3[1] + 1000) t2 = (pt3[0], pt3[1] - 1000) else: yOffset = _getAngleOffset(angle, 1000) t1 = (pt3[0] + 1000, pt3[1] + yOffset) t2 = (pt3[0] - 1000, pt3[1] - yOffset) indirect = _intersectLines((t1, t2), (pt0, pt1)) if indirect: if index not in crossedHandles: crossedHandles[index] = [] crossedHandles[index].append( dict(points=(pt0, pt1, indirect, pt3), intersection=indirect)) glyph.mark = (1, 0, .4, .2) break break pt0 = pt3 def testForUnnecessaryPoints(self, glyph): """ Consecutive segments shouldn't have the same angle. """ unnecessaryPoints = {} for index, contour in enumerate(glyph): for segmentIndex, segment in enumerate(contour): if segment.type == "line": prevSegment = contour[segmentIndex - 1] nextSegment = contour[(segmentIndex + 1) % len(contour)] if nextSegment.type == "line": thisAngle = _calcAngle(prevSegment.onCurve, segment.onCurve) nextAngle = _calcAngle(segment.onCurve, nextSegment.onCurve) if thisAngle == nextAngle: if index not in unnecessaryPoints: unnecessaryPoints[index] = [] unnecessaryPoints[index].append( _unwrapPoint(segment.onCurve)) glyph.mark = (1, .0, 1, 0.8) def testForOverlappingPoints(self, glyph): """ Consequtive points should not overlap. """ overlappingPoints = {} for index, contour in enumerate(glyph): if len(contour) == 1: continue prev = _unwrapPoint(contour[-1].onCurve) for segment in contour: point = _unwrapPoint(segment.onCurve) if point == prev: if index not in overlappingPoints: overlappingPoints[index] = set() overlappingPoints[index].add(point) glyph.mark = (.0, .0, .6, .3) prev = point # Segments def testForUnnecessaryHandles(self, glyph): """ Handles shouldn't be used if they aren't doing anything. """ unnecessaryHandles = {} for index, contour in enumerate(glyph): prevPoint = contour[-1].onCurve for segment in contour: if segment.type == "curve": pt0 = prevPoint pt1, pt2 = segment.offCurve pt3 = segment.onCurve lineAngle = _calcAngle(pt0, pt3, 0) bcpAngle1 = bcpAngle2 = None if (pt0.x, pt0.y) != (pt1.x, pt1.y): bcpAngle1 = _calcAngle(pt0, pt1, 0) if (pt2.x, pt2.y) != (pt3.x, pt3.y): bcpAngle2 = _calcAngle(pt2, pt3, 0) if bcpAngle1 == lineAngle and bcpAngle2 == lineAngle: if index not in unnecessaryHandles: unnecessaryHandles[index] = [] unnecessaryHandles[index].append( (_unwrapPoint(pt1), _unwrapPoint(pt2))) glyph.mark = (.4, .0, .0, .3) prevPoint = segment.onCurve def testForStraightLines(self, glyph): """ Lines shouldn't be just shy of vertical or horizontal. """ straightLines = {} for index, contour in enumerate(glyph): prev = _unwrapPoint(contour[-1].onCurve) for segment in contour: point = _unwrapPoint(segment.onCurve) if segment.type == "line": x = abs(prev[0] - point[0]) y = abs(prev[1] - point[1]) if x > 0 and x <= 5: if index not in straightLines: straightLines[index] = set() straightLines[index].add((prev, point)) glyph.mark = (.0, .6, .0, .3) if y > 0 and y <= 5: if index not in straightLines: straightLines[index] = set() straightLines[index].add((prev, point)) glyph.mark = (.0, .6, .0, .3) prev = point # Points def testForStrayPoints(self, glyph): """ There should be no stray points. """ strayPoints = {} for index, contour in enumerate(glyph): if len(contour) == 1: pt = contour[0].onCurve pt = (pt.x, pt.y) strayPoints[index] = pt glyph.mark = (.7, .0, .7, .9) def testForPointsNearVerticalMetrics(self, glyph): """ Points shouldn't be just off a vertical metric. """ font = glyph.getParent() verticalMetrics = {0: set()} for attr in "descender xHeight capHeight ascender".split(" "): value = getattr(font.info, attr) verticalMetrics[value] = set() for contour in glyph: for segment in contour: pt = _unwrapPoint(segment.onCurve) y = pt[1] for v in verticalMetrics: d = abs(v - y) if d != 0 and d <= 5: verticalMetrics[v].add(pt) glyph.mark = (1, 1, .0, .2) for verticalMetric, points in verticalMetrics.items(): if not points: del verticalMetrics[verticalMetric]
class AdjustAnchors(BaseWindowController): def __init__(self): self.font = CurrentFont() self.glyph = CurrentGlyph() self.upm = self.font.info.unitsPerEm self.rf3 = int(roboFontVersion.split(".")[0]) >= 3 if self.rf3: self.layer = CurrentLayer() # key: glyph name -- value: list containing assembled glyphs self.glyphPreviewCacheDict = {} # key: anchor name -- value: list of mark glyph names self.anchorsOnMarksDict = {} # key: anchor name -- value: list of base glyph names self.anchorsOnBasesDict = {} self.CXTanchorsOnBasesDict = {} # key: mark glyph name -- value: anchor name # NOTE: It's expected that each mark glyph only has one type of anchor self.marksDict = {} self.fillAnchorsAndMarksDicts() # list of glyph names that will be displayed in the UI list self.glyphNamesList = [] # list of glyph names selected in the UI list self.selectedGlyphNamesList = [] # list of the glyph objects that should be inserted # before and after the accented glyphs self.extraGlyphsList = [] self.Blue, self.Alpha = 1, 0.6 self.font.naked().addObserver(self, "fontWasModified", "Font.Changed") addObserver(self, "_fontWillClose", "fontWillClose") addObserver(self, "_currentFontChanged", "fontResignCurrent") addObserver(self, "_currentGlyphChanged", "currentGlyphChanged") addObserver(self, "_drawFill", "draw") addObserver(self, "_drawFill", "drawInactive") addObserver(self, "_previewFill", "drawPreview") # observer for the draw event addObserver(self, "_drawGlyphs", "draw") # draw the glyphs when the glyph window is not in focus addObserver(self, "_drawGlyphs", "drawInactive") addObserver(self, "_drawGlyphs", "drawPreview") integerNumFormatter = NSNumberFormatter.alloc().init() integerNumFormatter.setAllowsFloats_(False) integerNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinZeroNumFormatter = NSNumberFormatter.alloc().init() intPosMinZeroNumFormatter.setAllowsFloats_(False) intPosMinZeroNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinZeroNumFormatter.setMinimum_(NSNumber.numberWithInt_(0)) intPosMinOneNumFormatter = NSNumberFormatter.alloc().init() intPosMinOneNumFormatter.setAllowsFloats_(False) intPosMinOneNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinOneNumFormatter.setMinimum_(NSNumber.numberWithInt_(1)) self.textSize = getExtensionDefault("%s.%s" % (extensionKey, "textSize")) if not self.textSize: self.textSize = 150 self.lineHeight = getExtensionDefault("%s.%s" % (extensionKey, "lineHeight")) if not self.lineHeight: self.lineHeight = 200 self.extraSidebearings = getExtensionDefault( "%s.%s" % (extensionKey, "extraSidebearings")) if not self.extraSidebearings: self.extraSidebearings = [0, 0] self.extraGlyphs = getExtensionDefault("%s.%s" % (extensionKey, "extraGlyphs")) if not self.extraGlyphs: self.extraGlyphs = '' posSize = getExtensionDefault("%s.%s" % (extensionKey, "posSize")) if not posSize: posSize = (100, 100, 1200, 400) self.calibrateMode = getExtensionDefault( "%s.%s" % (extensionKey, "calibrateMode")) if not self.calibrateMode: self.calibrateMode = False calibrateModeStrings = getExtensionDefault( "%s.%s" % (extensionKey, "calibrateModeStrings")) if not calibrateModeStrings: calibrateModeStrings = { 'group1.baseInput': 'dotlessi o s', 'group1.markInput': 'dieresis circumflex macron breve caron', 'group2.baseInput': 'I O S', 'group2.markInput': 'dieresis.cap circumflex.cap macron.cap ' 'breve.cap caron.cap', 'group3.baseInput': 'I.sc O.sc S.sc', 'group3.markInput': 'dieresis circumflex macron breve caron', 'group4.baseInput': '', 'group4.markInput': '', } # -- Window -- self.w = FloatingWindow(posSize, extensionName, minSize=(500, 400)) self.w.fontList = List((10, 10, 190, -41), self.glyphNamesList, selectionCallback=self.listSelectionCallback) if roboFontVersion < '1.7': # use the full width of the column self.w.fontList.getNSTableView().sizeToFit() self.w.fontList.show(not self.calibrateMode) self.w.lineView = MultiLineView((210, 10, -10, -41), pointSize=self.textSize, lineHeight=self.lineHeight, displayOptions={ "Beam": False, "displayMode": "Multi Line" }) self.w.lineView.setFont(self.font) # -- Calibration Mode -- baseLabel = "Bases" markLabel = "Marks" width, height = 190, 140 self.cm = Group((0, 0, 0, 0)) # --- self.cm.group1 = Group((5, height * 0, width, height - 10)) self.cm.group1.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group1.baseInput = EditText( (0, 21, width, 22), calibrateModeStrings['group1.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group1.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group1.markInput = EditText( (0, 71, width, 44), calibrateModeStrings['group1.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group1.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group2 = Group((5, height * 1, width, height - 10)) self.cm.group2.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group2.baseInput = EditText( (0, 21, width, 22), calibrateModeStrings['group2.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group2.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group2.markInput = EditText( (0, 71, width, 44), calibrateModeStrings['group2.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group2.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group3 = Group((5, height * 2, width, height - 10)) self.cm.group3.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group3.baseInput = EditText( (0, 21, width, 22), calibrateModeStrings['group3.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group3.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group3.markInput = EditText( (0, 71, width, 44), calibrateModeStrings['group3.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group3.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group4 = Group((5, height * 3, width, height - 10)) self.cm.group4.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group4.baseInput = EditText( (0, 21, width, 22), calibrateModeStrings['group4.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group4.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group4.markInput = EditText( (0, 71, width, 44), calibrateModeStrings['group4.markInput'], callback=self.updateCalibrateMode, continuous=False) # --- view = DefconAppKitTopAnchoredNSView.alloc().init() view.addSubview_(self.cm.getNSView()) view.setFrame_(((0, 0), (width + 10, height * 4 - 23))) self.cm.setPosSize((0, 0, width + 10, height * 4 - 22)) self.w.scrollView = ScrollView((5, 10, width + 10, -41), view, drawsBackground=False, hasHorizontalScroller=False) self.w.scrollView.getNSScrollView().setBorderType_(NSNoBorder) # NSScrollElasticityNone self.w.scrollView.getNSScrollView().setVerticalScrollElasticity_(1) self.w.scrollView.show(self.calibrateMode) # -- Footer -- self.w.footer = Group((10, -32, -10, -10)) self.w.footer.calibrateModeCheck = CheckBox( (0, 0, 200, -0), "Calibration Mode", callback=self.calibrateModeCallback, value=self.calibrateMode) self.w.footer.textSizeLabel = TextBox((200, 2, 100, -0), "Text Size") self.w.footer.textSize = EditText((260, 0, 35, -0), self.textSize, callback=self.textSizeCallback, continuous=False, formatter=intPosMinOneNumFormatter) self.w.footer.lineHeightLabel = TextBox((310, 2, 100, -0), "Line Height") self.w.footer.lineHeight = EditText((385, 0, 35, -0), self.lineHeight, callback=self.lineHeightCallback, continuous=False, formatter=integerNumFormatter) self.w.footer.extraSidebearingsLabel = TextBox((436, 2, 180, -0), "Extra Sidebearings") self.w.footer.extraSidebearingsChar = TextBox((592, 2, 20, -0), "&") self.w.footer.extraSidebearingLeft = EditText( (557, 0, 35, -0), self.extraSidebearings[0], callback=self.extraSidebearingsCallback, continuous=False, formatter=intPosMinZeroNumFormatter) self.w.footer.extraSidebearingRight = EditText( (604, 0, 35, -0), self.extraSidebearings[1], callback=self.extraSidebearingsCallback, continuous=False, formatter=intPosMinZeroNumFormatter) self.w.footer.extraGlyphsLabel = TextBox((655, 2, 180, -0), "Extra Glyphs") self.w.footer.extraGlyphs = EditText((739, 0, -0, -0), self.extraGlyphs, callback=self.extraGlyphsCallback, continuous=False) # trigger the initial state and contents of the window self.extraGlyphsCallback() # calls self.updateExtensionWindow() self.w.bind("close", self.windowClose) self.w.open() self.w.makeKey() def calibrateModeCallback(self, sender): self.calibrateMode = not self.calibrateMode self.w.fontList.show(not sender.get()) self.w.scrollView.show(self.calibrateMode) self.updateExtensionWindow() def textSizeCallback(self, sender): try: # in case the user submits an empty field self.textSize = int(sender.get()) except Exception: # reset to the previous value NSBeep() self.sender.set(self.textSize) self.w.lineView.setPointSize(self.textSize) def lineHeightCallback(self, sender): try: self.lineHeight = int(sender.get()) except Exception: NSBeep() self.sender.set(self.lineHeight) self.w.lineView.setLineHeight(self.lineHeight) def extraSidebearingsCallback(self, sender): left = self.w.footer.extraSidebearingLeft right = self.w.footer.extraSidebearingRight try: self.extraSidebearings = [int(left.get()), int(right.get())] except Exception: NSBeep() left.set(self.extraSidebearings[0]) right.set(self.extraSidebearings[1]) self.extraGlyphsCallback() # calls self.updateExtensionWindow() def extraGlyphsCallback(self, *sender): del self.extraGlyphsList[:] # empty the list self.extraGlyphs = self.w.footer.extraGlyphs.get() glyphNamesList = self.extraGlyphs.split() for gName in glyphNamesList: try: extraGlyph = self.font[gName] # must create a new glyph in order to be able to # increase the sidebearings without modifying the font newGlyph = RGlyph() if self.rf3: newGlyph.layer = self.layer else: newGlyph.font = newGlyph.getParent() # must use deepAppend because the extra glyph may have # components (which will cause problems to the MultiLineView) newGlyph = self.deepAppendGlyph(newGlyph, extraGlyph) newGlyph.width = extraGlyph.width except Exception: continue newGlyph.leftMargin += self.extraSidebearings[0] newGlyph.rightMargin += self.extraSidebearings[1] self.extraGlyphsList.append(newGlyph) self.glyphPreviewCacheDict.clear() self.updateExtensionWindow() def windowClose(self, sender): self.font.naked().removeObserver(self, "Font.Changed") removeObserver(self, "fontWillClose") removeObserver(self, "fontResignCurrent") removeObserver(self, "currentGlyphChanged") removeObserver(self, "draw") removeObserver(self, "drawInactive") removeObserver(self, "drawPreview") self.saveExtensionDefaults() def getCalibrateModeStrings(self): calibrateModeStringsDict = {} for i in range(1, 5): group = getattr(self.cm, "group%d" % i) calibrateModeStringsDict["group%d.baseInput" % i] = group.baseInput.get() calibrateModeStringsDict["group%d.markInput" % i] = group.markInput.get() return calibrateModeStringsDict def saveExtensionDefaults(self): setExtensionDefault("%s.%s" % (extensionKey, "posSize"), self.w.getPosSize()) setExtensionDefault("%s.%s" % (extensionKey, "textSize"), self.textSize) setExtensionDefault("%s.%s" % (extensionKey, "lineHeight"), self.lineHeight) setExtensionDefault("%s.%s" % (extensionKey, "extraSidebearings"), self.extraSidebearings) setExtensionDefault("%s.%s" % (extensionKey, "extraGlyphs"), self.extraGlyphs) setExtensionDefault("%s.%s" % (extensionKey, "calibrateMode"), self.calibrateMode) setExtensionDefault("%s.%s" % (extensionKey, "calibrateModeStrings"), self.getCalibrateModeStrings()) def _previewFill(self, info): self.Blue, self.Alpha = 0, 1 def _drawFill(self, info): self.Blue, self.Alpha = 1, 0.6 def _fontWillClose(self, info): """ Close the window when the last font is closed """ if len(AllFonts()) < 2: self.windowClose(self) self.w.close() def _currentFontChanged(self, info): self.font.naked().removeObserver(self, "Font.Changed") self.font = CurrentFont() self.font.naked().addObserver(self, "fontWasModified", "Font.Changed") self.w.lineView.setFont(self.font) self.fillAnchorsAndMarksDicts() del self.glyphNamesList[:] del self.selectedGlyphNamesList[:] self.updateExtensionWindow() def _currentGlyphChanged(self, info): self.updateExtensionWindow() def fontWasModified(self, info): OutputWindow().clear() self.fillAnchorsAndMarksDicts() del self.glyphNamesList[:] del self.selectedGlyphNamesList[:] self.updateExtensionWindow() def deepAppendGlyph(self, glyph, gToAppend, offset=(0, 0)): if not gToAppend.components: glyph.appendGlyph(gToAppend, offset) else: for component in gToAppend.components: # avoid traceback in the case where the selected glyph is # referencing a component whose glyph is not in the font if component.baseGlyph not in self.font.keys(): print("WARNING: %s is referencing a glyph named %s, which " "does not exist in the font." % (self.font.selection[0], component.baseGlyph)) continue compGlyph = self.font[component.baseGlyph].copy() # handle component transformations componentTransformation = component.transformation # when undoing a paste anchor or a delete anchor action, # RoboFont returns component.transformation as a list instead # of a tuple if type(componentTransformation) is list: componentTransformation = tuple(componentTransformation) # if component is skewed and/or is shifted if componentTransformation != (1, 0, 0, 1, 0, 0): matrix = componentTransformation[0:4] if matrix != (1, 0, 0, 1): # if component is skewed # ignore the original component's shifting values transformObj = Identity.transform(matrix + (0, 0)) compGlyph.transform(transformObj) # add the two tuples of offset glyph.appendGlyph( compGlyph, tuple(map(sum, zip(component.offset, offset)))) for contour in gToAppend: glyph.appendContour(contour, offset) # if the assembled glyph still has components, recursively # remove and replace them 1-by-1 by the glyphs they reference if glyph.components: nestedComponent = glyph.components[-1] # start from the end glyph.removeComponent(nestedComponent) glyph = self.deepAppendGlyph(glyph, self.font[nestedComponent.baseGlyph], nestedComponent.offset) return glyph def updateCalibrateMode(self, *sender): glyphsList = [] newLine = self.w.lineView.createNewLineGlyph() # cycle thru the UI Groups and collect the strings for i in range(1, 5): group = getattr(self.cm, "group%d" % i) baseGlyphsNamesList = group.baseInput.get().split() markGlyphsNamesList = group.markInput.get().split() # iterate thru the base+mark combinations for gBaseName, gMarkName in product(baseGlyphsNamesList, markGlyphsNamesList): newGlyph = RGlyph() if self.rf3: newGlyph.layer = self.layer else: newGlyph.font = newGlyph.getParent() # skip invalid glyph names try: baseGlyph = self.font[gBaseName] markGlyph = self.font[gMarkName] except Exception: continue # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, baseGlyph) # append mark glyph newGlyph = self.deepAppendGlyph( newGlyph, markGlyph, self.getAnchorOffsets(baseGlyph, markGlyph)) # set the advanced width dfltSidebearings = self.upm * .05 # 5% of UPM newGlyph.leftMargin = (dfltSidebearings + self.extraSidebearings[0]) newGlyph.rightMargin = (dfltSidebearings + self.extraSidebearings[1]) # append the assembled glyph to the list glyphsList.extend(self.extraGlyphsList) glyphsList.append(newGlyph) # add line break, if both input fields have content if baseGlyphsNamesList and markGlyphsNamesList: glyphsList.extend(self.extraGlyphsList) glyphsList.append(newLine) # update the contents of the MultiLineView self.w.lineView.set(glyphsList) def updateExtensionWindow(self): if self.calibrateMode: self.updateCalibrateMode() return # NOTE: CurrentGlyph() will return zero (its length), # so "is not None" is necessary if CurrentGlyph() is not None: self.glyph = CurrentGlyph() self.glyphNamesList = self.makeGlyphNamesList(self.glyph) self.updateListView() currentGlyphName = self.glyph.name # base glyph + accent combinations preview # first check if there's a cached glyph if currentGlyphName in self.glyphPreviewCacheDict: self.w.lineView.set( self.glyphPreviewCacheDict[currentGlyphName]) # assemble the glyphs else: glyphsList = [] for glyphNameInUIList in self.glyphNamesList: # trim the contextual portion of the UI glyph name # and keep track of it if CONTEXTUAL_ANCHOR_TAG in glyphNameInUIList: cxtTagIndex = glyphNameInUIList.find( CONTEXTUAL_ANCHOR_TAG) glyphNameCXTportion = glyphNameInUIList[cxtTagIndex:] # this line must be last! glyphNameInUIList = glyphNameInUIList[:cxtTagIndex] else: glyphNameCXTportion = '' newGlyph = RGlyph() if self.rf3: newGlyph.layer = self.layer else: newGlyph.font = newGlyph.getParent() # the glyph in the UI list is a mark if glyphNameInUIList in self.marksDict: markGlyph = self.font[glyphNameInUIList] # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, self.glyph) # append mark glyph newGlyph = self.deepAppendGlyph( newGlyph, markGlyph, self.getAnchorOffsets(self.glyph, markGlyph, glyphNameCXTportion)) # set the advanced width # combining marks or other glyphs with # a small advanced width if self.glyph.width < 10: newGlyph.leftMargin = self.upm * .05 # 5% of UPM newGlyph.rightMargin = newGlyph.leftMargin else: newGlyph.width = self.glyph.width # the glyph in the UI list is a base else: baseGlyph = self.font[glyphNameInUIList] # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, baseGlyph) # append mark glyph newGlyph = self.deepAppendGlyph( newGlyph, self.glyph, self.getAnchorOffsets(baseGlyph, self.glyph)) # set the advanced width # combining marks or other glyphs with # a small advanced width if self.glyph.width < 10: newGlyph.leftMargin = self.upm * .05 newGlyph.rightMargin = newGlyph.leftMargin else: newGlyph.width = baseGlyph.width # pad the new glyph if it has too much overhang if newGlyph.leftMargin < self.upm * .15: newGlyph.leftMargin = self.upm * .05 if newGlyph.rightMargin < self.upm * .15: newGlyph.rightMargin = self.upm * .05 # add extra sidebearings newGlyph.leftMargin += self.extraSidebearings[0] newGlyph.rightMargin += self.extraSidebearings[1] # one last check for making sure the new glyph # can be displayed if not newGlyph.components: glyphsList.extend(self.extraGlyphsList) glyphsList.append(newGlyph) else: print("Combination with mark glyph %s can't be " "previewed because it contains component %s." % (glyphNameInUIList + glyphNameCXTportion, newGlyph.components[0].baseGlyph)) glyphsList.extend(self.extraGlyphsList) self.w.lineView.set(glyphsList) # add to the cache self.glyphPreviewCacheDict[currentGlyphName] = glyphsList else: self.w.lineView.set([]) def listSelectionCallback(self, sender): selectedGlyphNamesList = [] for index in sender.getSelection(): selectedGlyphNamesList.append(self.glyphNamesList[index]) self.selectedGlyphNamesList = selectedGlyphNamesList self.updateGlyphView() def updateGlyphView(self): UpdateCurrentGlyphView() def fillAnchorsAndMarksDicts(self): # reset all the dicts self.glyphPreviewCacheDict.clear() self.anchorsOnMarksDict.clear() self.anchorsOnBasesDict.clear() self.CXTanchorsOnBasesDict.clear() self.marksDict.clear() markGlyphsWithMoreThanOneAnchorTypeList = [] for glyphName in self.font.glyphOrder: glyphAnchorsList = self.font[glyphName].anchors for anchor in glyphAnchorsList: if anchor.name[0] == '_': anchorName = anchor.name[1:] # add to AnchorsOnMarks dictionary if anchorName not in self.anchorsOnMarksDict: self.anchorsOnMarksDict[anchorName] = [glyphName] else: tempList = self.anchorsOnMarksDict[anchorName] tempList.append(glyphName) self.anchorsOnMarksDict[anchorName] = tempList # add to Marks dictionary if glyphName not in self.marksDict: self.marksDict[glyphName] = anchorName else: if (glyphName not in markGlyphsWithMoreThanOneAnchorTypeList): markGlyphsWithMoreThanOneAnchorTypeList.append( glyphName) else: anchorName = anchor.name if CONTEXTUAL_ANCHOR_TAG in anchorName: # add to AnchorsOnBases dictionary if anchorName not in self.CXTanchorsOnBasesDict: self.CXTanchorsOnBasesDict[anchorName] = [ glyphName ] else: tempList = self.CXTanchorsOnBasesDict[anchorName] tempList.append(glyphName) self.CXTanchorsOnBasesDict[anchorName] = tempList else: # add to AnchorsOnBases dictionary if anchorName not in self.anchorsOnBasesDict: self.anchorsOnBasesDict[anchorName] = [glyphName] else: tempList = self.anchorsOnBasesDict[anchorName] tempList.append(glyphName) self.anchorsOnBasesDict[anchorName] = tempList if markGlyphsWithMoreThanOneAnchorTypeList: for glyphName in markGlyphsWithMoreThanOneAnchorTypeList: print("ERROR: Glyph %s has more than one type of anchor." % glyphName) def makeGlyphNamesList(self, glyph): glyphNamesList = [] markGlyphIsAbleToBeBase = False # NOTE: "if glyph" will return zero (its length), # so "is not None" is necessary if glyph is not None: # assemble the list for the UI list for anchor in glyph.anchors: anchorName = anchor.name # the glyph selected is a base if anchorName in self.anchorsOnMarksDict: glyphNamesList.extend(self.anchorsOnMarksDict[anchorName]) # the glyph selected is a mark # skips the leading underscore elif anchorName[1:] in self.anchorsOnBasesDict: glyphNamesList.extend( self.anchorsOnBasesDict[anchorName[1:]]) # the glyph selected is a base elif anchorName[0] != '_' and (anchorName in self.CXTanchorsOnBasesDict): cxtTagIndex = anchorName.find(CONTEXTUAL_ANCHOR_TAG) anchorNameNOTCXTportion = anchorName[:cxtTagIndex] anchorNameCXTportion = anchorName[cxtTagIndex:] # XXX here only the first mark glyph that has an anchor of # the kind 'anchorNameNOTCXTportion' is considered. # This is probably harmless, but... glyphName = '%s%s' % ( self.anchorsOnMarksDict[anchorNameNOTCXTportion][0], anchorNameCXTportion) glyphNamesList.append(glyphName) # for mark glyphs, test if they're able to get # other mark glyphs attached to them. # this will (correctly) prevent the UI list from including # glyph names that cannot be displayed with the current glyph if glyph.name in self.marksDict: for anchor in glyph.anchors: # the current mark glyph has anchors that # allow it to be a base for other marks if anchor.name[0] != '_': markGlyphIsAbleToBeBase = True break # remove marks from the glyph list if the # current mark glyph can't work as a base if not markGlyphIsAbleToBeBase: # iterate from the end of the list for glyphName in glyphNamesList[::-1]: if glyphName in self.marksDict: glyphNamesList.remove(glyphName) glyphNamesList.sort() return glyphNamesList def updateListView(self): self.w.fontList.set(self.glyphNamesList) def getAnchorOffsets(self, canvasGlyph, glyphToDraw, anchorNameCXTportion=''): # the current glyph is a mark if canvasGlyph.name in self.marksDict: # glyphToDraw is also a mark (mark-to-mark case) if glyphToDraw.name in self.marksDict: # pick the (mark glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name[0] != '_': anchorName = anchor.name markAnchor = anchor break # pick the (base glyph) anchor to draw on for anchor in glyphToDraw.anchors: try: if anchor.name == '_' + anchorName: baseAnchor = anchor break except UnboundLocalError: continue # glyphToDraw is not a mark else: # pick the (mark glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name[0] == '_': anchorName = anchor.name[1:] markAnchor = anchor break # pick the (base glyph) anchor to draw on for anchor in glyphToDraw.anchors: try: if anchor.name == anchorName: baseAnchor = anchor break except UnboundLocalError: continue try: offsetX = markAnchor.x - baseAnchor.x offsetY = markAnchor.y - baseAnchor.y except UnboundLocalError: offsetX = 0 offsetY = 0 # the current glyph is a base else: try: anchorName = self.marksDict[glyphToDraw.name] except KeyError: anchorName = None if anchorName: # pick the (base glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name == anchorName + anchorNameCXTportion: baseAnchor = anchor break # pick the (mark glyph) anchor to draw on for anchor in glyphToDraw.anchors: if anchor.name == '_' + anchorName: markAnchor = anchor break try: offsetX = baseAnchor.x - markAnchor.x offsetY = baseAnchor.y - markAnchor.y except UnboundLocalError: offsetX = 0 offsetY = 0 return (offsetX, offsetY) def _drawGlyphs(self, info): """ draw stuff in the glyph window view """ translateBefore = (0, 0) for glyphName in self.selectedGlyphNamesList: # trim the contextual portion of the UI glyph name # and keep track of it if CONTEXTUAL_ANCHOR_TAG in glyphName: cxtTagIndex = glyphName.find(CONTEXTUAL_ANCHOR_TAG) glyphNameCXTportion = glyphName[cxtTagIndex:] glyphName = glyphName[:cxtTagIndex] # this line must be last! else: glyphNameCXTportion = '' glyphToDraw = self.font[glyphName] # determine the offset of the anchors offset = self.getAnchorOffsets(self.glyph, glyphToDraw, glyphNameCXTportion) # set the offset of the drawing translate(offset[0] - translateBefore[0], offset[1] - translateBefore[1]) # record the shift amounts (these are needed for resetting the # drawing position when more than one mark is selected on the list) translateBefore = offset # set the fill & stroke fill(0, 0, self.Blue, self.Alpha) strokeWidth(None) # draw it mojoPen = MojoDrawingToolsPen(glyphToDraw, self.font) glyphToDraw.draw(mojoPen) mojoPen.draw()
class ChooseExceptionWindow(BaseWindowController): lastEvent = None def __init__(self, options, callback): super(ChooseExceptionWindow, self).__init__() self.options = options self.callback = callback self.whichException = options[0] self.w = FloatingWindow((300, 120), 'Choose exception') self.w.optionsRadio = RadioGroup( (MARGIN, MARGIN, -MARGIN, len(options) * 20), options, callback=self.optionsCallback) self.w.optionsRadio.set(0) self.w.cancel = Button( (-(90 * 2 + MARGIN * 2), -(vanillaControlsSize['ButtonRegularHeight'] + MARGIN), 90, vanillaControlsSize['ButtonRegularHeight']), 'Cancel', callback=self.cancelCallback) self.w.submit = Button( (-(90 + MARGIN), -(vanillaControlsSize['ButtonRegularHeight'] + MARGIN), 90, vanillaControlsSize['ButtonRegularHeight']), 'Submit', callback=self.submitCallback) self.setUpBaseWindowBehavior() def set(self, exception): self.whichException = exception self.lastEvent = 'submit' def trigger(self): self.callback(self) def get(self): return self.whichException def enable(self, value): if value is True: self.w.center() self.w.show() else: self.w.hide() def close(self): self.whichException = None self.w.close() def setOptions(self, options): self.options = options optionsRepresentation = [] for lft, rgt in self.options: row = [] for eachSide in [lft, rgt]: if eachSide.startswith('@') is True: groupRepr = encloseClassTitleInBrackets(eachSide) row.append(groupRepr) else: row.append(eachSide) optionsRepresentation.append(', '.join(row)) if hasattr(self.w, 'optionsRadio') is True: delattr(self.w, 'optionsRadio') self.w.optionsRadio = RadioGroup( (MARGIN, MARGIN, -MARGIN, len(options) * 20), optionsRepresentation, callback=self.optionsCallback) def optionsCallback(self, sender): self.whichException = self.options[sender.get()] def cancelCallback(self, sender): self.lastEvent = 'cancel' self.whichException = None self.callback(self) self.w.hide() def submitCallback(self, sender): self.lastEvent = 'submit' self.callback(self) self.w.hide()
class Script(object): def __init__(self): self.valuesPrefsKey = prefsKey + ".delta." + basename( Glyphs.font.filepath) # UI metrics spacing = 8 height = 22 # calculate window height minWinSize = (220, 120 + (len(Glyphs.font.masters) * (height + spacing))) # create UI window self.w = FloatingWindow(minWinSize, "Adjust sidebearings", minSize=minWinSize, maxSize=(500, 500), autosaveName=prefsKey + ".win") # layout UI controls y = 16 self.w.label = TextBox((16, y, -16, height), "Sidebearing delta adjustment:") y += height + spacing inputWidth = 64 for master in Glyphs.font.masters: setattr(self.w, "deltaLabel%s" % master.id, TextBox((16, y, -16 - inputWidth, height), master.name)) setattr(self.w, "deltaInput%s" % master.id, EditText((-16 - inputWidth, y, -16, height), "16")) # print("self.w.deltaInputs[master.id]", getattr(self.w, "deltaInput%s" % master.id)) y += height + spacing self.w.submitButton = Button((16, -16 - height, -16, height), "Adjust all sidebearings", callback=self.onSubmit) # finalize UI self.w.setDefaultButton(self.w.submitButton) self.loadPreferences() self.w.bind("close", self.savePreferences) # make sure window is large enough to show all masters x, y, w, h = self.w.getPosSize() if w < minWinSize[0] and h < minWinSize[1]: self.w.setPosSize((x, y, minWinSize[0], minWinSize[1]), animate=False) elif w < minWinSize[0]: self.w.setPosSize((x, y, minWinSize[0], h), animate=False) elif h < minWinSize[1]: self.w.setPosSize((x, y, w, minWinSize[1]), animate=False) self.w.open() self.w.makeKey() def getDeltaInputForMaster(self, masterId): return getattr(self.w, "deltaInput%s" % masterId) def loadPreferences(self): try: Glyphs.registerDefault(self.valuesPrefsKey, []) for t in Glyphs.defaults[self.valuesPrefsKey]: try: masterId, value = t self.getDeltaInputForMaster(masterId).set(value) except: pass except: print("failed to load preferences") def savePreferences(self, sender): try: values = [] for master in Glyphs.font.masters: values.append( (master.id, self.getDeltaInputForMaster(master.id).get())) Glyphs.defaults[self.valuesPrefsKey] = values except: print("failed to save preferences") def onSubmit(self, sender): try: sender.enable(False) if performFontChanges(self.action1): self.w.close() except Exception, e: Glyphs.showMacroWindow() print("error: %s" % e) finally:
class GlyphFax(object): def __init__(self): '''Initialize the dialog.''' x = y = padding = 10 buttonHeight = 20 windowWidth = 400 rows = 4.5 self.w = FloatingWindow( (windowWidth, buttonHeight * rows + padding * (rows)), "Glyph Fax Machine") self.fonts = {} # self.w.textBox = TextBox((x, y, -padding, buttonHeight), "Glyphs to Copy") # y += buttonHeight self.w.editText = EditText( (x, y, -padding, buttonHeight * 2 + padding), placeholder="Space-separated list of glyphs to copy") y += buttonHeight * 2 + padding * 2 # self.w.overwriteGlyphsCheckBox = CheckBox((x, y, -padding, buttonHeight), "Overwrite Glyphs", value=False, callback=self.overwriteGlyphsOptions) # y += buttonHeight + padding self.w.overwriteNormalWidthGlyphsCheckBox = CheckBox( (x, y, -padding, buttonHeight), "Overwrite 600w Glyphs", value=False, sizeStyle="small") # self.w.overwriteNormalWidthGlyphsCheckBox.show(False) self.w.overwriteAdjustedWidthGlyphsCheckBox = CheckBox( (windowWidth * 0.4, y, -padding, buttonHeight), "Overwrite non-600w Glyphs", value=False, sizeStyle="small") # self.w.overwriteAdjustedWidthGlyphsCheckBox.show(False) self.w.colorWell = ColorWell( (windowWidth * 0.85, y, -padding, buttonHeight), color=NSColor.orangeColor()) y += buttonHeight + padding self.w.sans2mono = Button( (x, y, windowWidth / 3 - padding / 2, buttonHeight), "Sans → Mono", callback=self.sans2monoCallback) self.w.mono2sans = Button( (windowWidth / 3 + padding, y, -padding, buttonHeight), "Mono → Sans", callback=self.mono2SansCallback) self.w.open() def copyGlyph(self, glyphName, fontToCopyFrom, fontToSendTo): print( f"copying glyph /{glyphName} from \n\t{fontToCopyFrom} \n\tto \n\t{fontToSendTo}" ) glyphCopyName = glyphName copyGlyph = True if glyphName not in fontToSendTo: fontToSendTo.newGlyph(glyphName) else: overwrite600 = self.w.overwriteNormalWidthGlyphsCheckBox.get() overwriteNon600 = self.w.overwriteAdjustedWidthGlyphsCheckBox.get() # default case: overwrite is false (overwrite 600 is true, overwrite non600 is false) if overwrite600 == False and overwriteNon600 == False: # glyphCopyName = glyphName + '.copy' # fontToSendTo.newGlyph(glyphCopyName) copyGlyph = False elif overwrite600 == True and overwriteNon600 == True: fontToSendTo[glyphCopyName].clear() elif overwrite600 == False and overwriteNon600 == True: if round(fontToSendTo[glyphCopyName].width, -1) == 600: # glyphCopyName = glyphName + '.copy' # fontToSendTo.newGlyph(glyphCopyName) copyGlyph = False else: fontToSendTo[glyphCopyName].clear() elif overwrite600 == True and overwriteNon600 == False: if round(fontToSendTo[glyphCopyName].width, -1) == 600: fontToSendTo[glyphCopyName].clear() else: # glyphCopyName = glyphName + '.copy' # fontToSendTo.newGlyph(glyphCopyName) copyGlyph = False if copyGlyph: glyphToCopy = fontToCopyFrom[glyphName] layerGlyph = fontToSendTo[glyphCopyName].getLayer("foreground") # get the point pen of the layer glyph pen = layerGlyph.getPointPen() # draw the points of the imported glyph into the layered glyph glyphToCopy.drawPoints(pen) if len(layerGlyph.components) == 1: parentGlyphWidth = fontToSendTo[ layerGlyph.components[0].baseGlyph].width print(type(parentGlyphWidth)) layerGlyph.width = parentGlyphWidth print("copied glyph now has width of its component parent:", str(parentGlyphWidth)) else: layerGlyph.width = glyphToCopy.width layerGlyph.markColor = (self.w.colorWell.get().redComponent(), self.w.colorWell.get().greenComponent(), self.w.colorWell.get().blueComponent(), self.w.colorWell.get().alphaComponent()) for anchor in glyphToCopy.anchors: layerGlyph.appendAnchor(anchor.name, (anchor.x, anchor.y)) def glyphsToCopy(self, sender): if self.w.editText.get() == "": Message('no glyphs listed', title='Glyph Fax Machine', informativeText='Please list glyphs to send copies of!') return return self.w.editText.get().split(" ") # default: dlig & ss01–ss12 off def getFontsCueCopying(self, proportionToCopyFrom, glyphsToCopy): files = getFile("Select UFOs to copy glyphs between", allowsMultipleSelection=True, fileTypes=["ufo"]) for path in files: f = OpenFont(path, showInterface=True) variation = f.info.styleName.replace('Mono ', '').replace('Sans ', '') if variation not in self.fonts.keys(): self.fonts[variation] = [] if self.fonts[variation] == []: self.fonts[variation].append(f) elif self.fonts[variation] != []: # if 'Mono' already in list, put 'Sans' second if proportionToCopyFrom in self.fonts[variation][ 0].info.styleName: self.fonts[variation].append(f) # else put 'Mono' first else: self.fonts[variation] = [f] + self.fonts[variation] for variation in self.fonts.keys(): print('\n', variation, '\n') for glyphName in glyphsToCopy: self.copyGlyph(glyphName, self.fonts[variation][0], self.fonts[variation][1]) self.w.close() ## ss01–ss12 on def mono2SansCallback(self, sender): '''Copy glyphs from Mono to Sans masters.''' glyphsToCopy = self.glyphsToCopy(sender) if glyphsToCopy is not None: self.getFontsCueCopying('Mono', glyphsToCopy) def sans2monoCallback(self, sender): '''Copy glyphs from Sans to Mono masters.''' glyphsToCopy = self.glyphsToCopy(sender) if glyphsToCopy is not None: self.getFontsCueCopying('Sans', glyphsToCopy)
class ProofGroupInspector: def __init__(self, proofGroup): """ Initialize inspector with proofGroup data. proofGroup is a dictionary passed in by ProofDrawer(). """ self.proofGroup = proofGroup self.editedProofGroup = {} left = 10 row = 10 textboxWidth = 92 leftEditText = left + 95 pointSizes = ["6", "8", "10", "12", "14", "18",\ "21", "24", "36", "48", "60", "72"] self.w = FloatingWindow( (400, 275), "Edit Proof Group: %s" % self.proofGroup["name"]) self.w.groupName = TextBox((left, row + 2, textboxWidth, 20), "Group name:", alignment="right") self.w.groupNameEdit = EditText((leftEditText, row, -10, 22), self.proofGroup["name"]) row += 33 self.w.typeSize = TextBox((left, row + 2, textboxWidth, 20), "Type size (pt):", alignment="right") self.w.typeSizeEdit = ComboBox((leftEditText, row, 55, 22), pointSizes, continuous=True, callback=self._checkFloat) self.w.typeSizeEdit.set(self.proofGroup["typeSize"]) self.w.leading = TextBox((leftEditText + 80, row + 2, 60, 22), "Leading:") self.w.leadingEdit = ComboBox((leftEditText + 139, row, 55, 22), pointSizes, continuous=True, callback=self._checkFloat) self.w.leadingEdit.set(self.proofGroup["leading"]) row += 33 self.w.contents = TextBox((left, row, textboxWidth, 20), "Contents:", alignment="right") self.w.contentsEdit = TextEditor( (leftEditText, row, -10, 150), "\n".join(self.proofGroup["contents"])) self.w.contentsEdit.getNSTextView().setFont_(monoFont) row += 160 self.w.cancelButton = Button((leftEditText, row, 138, 20), "Cancel", callback=self.cancelCB) leftEditText += 147 self.w.okButton = Button((leftEditText, row, 138, 20), "OK", callback=self.okCB) self.w.setDefaultButton(self.w.okButton) self.w.bind("close", self._postCloseEvent) def _postCloseEvent(self, sender): postEvent("com.InspectorClosed") def _checkFloat(self, sender): """ Make sure users don't input non-floats by capturing value prior to new input, then using it if user tries to input an illegal character """ # pass # Store everything up to newly-typed character allButLast = sender.get()[:-1] try: float(sender.get()) # Get rid of whitespaces immediately sender.set(sender.get().strip()) except ValueError: sender.set(allButLast) def okCB(self, sender): """ Get everything from fields, save in self.editedProofGroup dict, post event, pass the edited group to observer, and close window """ self.editedProofGroup["name"] = self.w.groupNameEdit.get().strip() self.editedProofGroup["typeSize"] = self.w.typeSizeEdit.get() self.editedProofGroup["leading"] = self.w.leadingEdit.get() self.editedProofGroup["print"] = self.proofGroup[ "print"] # just pass this back for now self.editedProofGroup["contents"] = hf.makeCleanListFromStr( self.w.contentsEdit.get()) postEvent("com.ProofGroupEdited", editedProofGroup=self.editedProofGroup) self.w.close() def cancelCB(self, sender): self.w.close()
class Script( object ): def __init__(self): self.valuesPrefsKey = prefsKey + ".delta." + basename(Glyphs.font.filepath) # UI metrics spacing = 8 height = 22 # calculate window height minWinSize = (220, 120 + (len(Glyphs.font.masters) * (height + spacing))) # create UI window self.w = FloatingWindow( minWinSize, "Adjust sidebearings", minSize=minWinSize, maxSize=(500, 500), autosaveName=prefsKey + ".win") # layout UI controls y = 16 self.w.label = TextBox((16, y, -16, height), "Sidebearing delta adjustment:") y += height + spacing inputWidth = 64 for master in Glyphs.font.masters: setattr(self.w, "deltaLabel%s" % master.id, TextBox((16, y, -16-inputWidth, height), master.name)) setattr(self.w, "deltaInput%s" % master.id, EditText((-16-inputWidth, y, -16, height), "16")) # print("self.w.deltaInputs[master.id]", getattr(self.w, "deltaInput%s" % master.id)) y += height + spacing self.w.submitButton = Button( (16, -16-height, -16, height), "Adjust all sidebearings", callback=self.onSubmit) # finalize UI self.w.setDefaultButton(self.w.submitButton) self.loadPreferences() self.w.bind("close", self.savePreferences) # make sure window is large enough to show all masters x, y, w, h = self.w.getPosSize() if w < minWinSize[0] and h < minWinSize[1]: self.w.setPosSize((x, y, minWinSize[0], minWinSize[1]), animate=False) elif w < minWinSize[0]: self.w.setPosSize((x, y, minWinSize[0], h), animate=False) elif h < minWinSize[1]: self.w.setPosSize((x, y, w, minWinSize[1]), animate=False) self.w.open() self.w.makeKey() def getDeltaInputForMaster(self, masterId): return getattr(self.w, "deltaInput%s" % masterId) def loadPreferences(self): try: Glyphs.registerDefault(self.valuesPrefsKey, []) for t in Glyphs.defaults[self.valuesPrefsKey]: try: masterId, value = t self.getDeltaInputForMaster(masterId).set(value) except: pass except: print("failed to load preferences") def savePreferences(self, sender): try: values = [] for master in Glyphs.font.masters: values.append((master.id, self.getDeltaInputForMaster(master.id).get())) Glyphs.defaults[self.valuesPrefsKey] = values except: print("failed to save preferences") def onSubmit(self, sender): try: sender.enable(False) if performFontChanges(self.action1): self.w.close() except Exception(e): Glyphs.showMacroWindow() print("error: %s" % e) finally: sender.enable(True) def action1(self, font): print(font) deltas = {} # keyed on master.id for master in Glyphs.font.masters: deltas[master.id] = int(self.getDeltaInputForMaster(master.id).get()) syncLayers = set() # layers that need syncMetrics() to be called # [debug] alterntive loop over a few select glyphs, for debugging. # debugGlyphNames = set([ # ".null", # "A", "Adieresis", "Lambda", # "Bhook", # "C", "Chook", # "endash", # "space", # ]) # for g in [g for g in font.glyphs if g.name in debugGlyphNames]: for g in font.glyphs: # print(">> %s (%s)" % (g.name, g.productionName)) if g.name in excludedGlyphNames: # glyph is exlcuded print("ignoring excluded glyph %r" % g.name) continue g.beginUndo() try: for master in font.masters: layer = g.layers[master.id] delta = deltas[master.id] if delta == 0: # no adjustment continue if layer.width == 0: # ignore glyphs with zero-width layers print("ignoring zero-width glyph", g.name) break if layer.isAligned: # ignore glyphs which are auto-aligned from components print("ignoring auto-aligned glyph", g.name) break if len(layer.paths) == 0 and len(layer.components) == 0: # adjust width instead of LSB & RSB of empty glyphs if layer.widthMetricsKey is None: layer.width = max(0, layer.width + (delta * 2)) else: syncLayers.add(layer) continue # offset components by delta to counter-act effect of also applying delta # to the component glyphs. if layer.components is not None: for cn in layer.components: m = cn.transform # auto-aligned or offset x or offset y or contains shapes if not cn.automaticAlignment or m[4] != 0 or m[5] != 0 or len(layer.paths) > 0: cn.transform = ( m[0], # x scale factor m[1], # x skew factor m[2], # y skew factor m[3], # y scale factor m[4] - delta, # x position (+ since transform is inverse) m[5] # y position ) # Note: # layer metrics keys are expressions, e.g. "==H+10" # glyph metrics keys are other glyphs, e.g. U+0041 if g.leftMetricsKey is None and layer.leftMetricsKey is None: layer.LSB = layer.LSB + delta else: syncLayers.add(layer) if g.rightMetricsKey is None and layer.rightMetricsKey is None: layer.RSB = layer.RSB + delta else: syncLayers.add(layer) finally: g.endUndo() # end for g in font # sync layers that use metricsKey if len(syncLayers) > 0: print("Syncing LSB & RSB for %r layers..." % len(syncLayers)) for layer in syncLayers: layer.syncMetrics() print("Done")
class AdjustAnchors(BaseWindowController): def __init__(self): self.font = CurrentFont() self.glyph = CurrentGlyph() self.upm = self.font.info.unitsPerEm self.glyphPreviewCacheDict = {} # key: glyph name -- value: list containing assembled glyphs self.anchorsOnMarksDict = {} # key: anchor name -- value: list of mark glyph names self.anchorsOnBasesDict = {} # key: anchor name -- value: list of base glyph names self.marksDict = {} # key: mark glyph name -- value: anchor name (NOTE: It's expected that each mark glyph only has one type of anchor) self.fillAnchorsAndMarksDicts() self.glyphNamesList = [] # list of glyph names that will be displayed in the UI list self.selectedGlyphNamesList = [] # list of glyph names selected in the UI list self.extraGlyphsList = [] # list of the glyph objects that should be inserted before and after the accented glyphs self.Blue, self.Alpha = 1, 0.6 self.font.naked().addObserver(self, "fontWasModified", "Font.Changed") addObserver(self, "_fontWillClose", "fontWillClose") addObserver(self, "_currentFontChanged", "fontResignCurrent") addObserver(self, "_currentGlyphChanged", "currentGlyphChanged") addObserver(self, "_drawFill", "draw") addObserver(self, "_drawFill", "drawInactive") addObserver(self, "_previewFill", "drawPreview") addObserver(self, "_drawGlyphs", "draw") # observer for the draw event addObserver(self, "_drawGlyphs", "drawInactive") # draw the glyphs when the glyph window is not in focus addObserver(self, "_drawGlyphs", "drawPreview") integerNumFormatter = NSNumberFormatter.alloc().init() integerNumFormatter.setAllowsFloats_(False) integerNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinZeroNumFormatter = NSNumberFormatter.alloc().init() intPosMinZeroNumFormatter.setAllowsFloats_(False) intPosMinZeroNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinZeroNumFormatter.setMinimum_(NSNumber.numberWithInt_(0)) intPosMinOneNumFormatter = NSNumberFormatter.alloc().init() intPosMinOneNumFormatter.setAllowsFloats_(False) intPosMinOneNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinOneNumFormatter.setMinimum_(NSNumber.numberWithInt_(1)) self.textSize = getExtensionDefault("%s.%s" % (extensionKey, "textSize")) if not self.textSize: self.textSize = 150 self.lineHeight = getExtensionDefault("%s.%s" % (extensionKey, "lineHeight")) if not self.lineHeight: self.lineHeight = 200 self.extraSidebearings = getExtensionDefault("%s.%s" % (extensionKey, "extraSidebearings")) if not self.extraSidebearings: self.extraSidebearings = [0, 0] self.extraGlyphs = getExtensionDefault("%s.%s" % (extensionKey, "extraGlyphs")) if not self.extraGlyphs: self.extraGlyphs = '' posSize = getExtensionDefault("%s.%s" % (extensionKey, "posSize")) if not posSize: posSize = (100, 100, 1200, 400) self.calibrateMode = getExtensionDefault("%s.%s" % (extensionKey, "calibrateMode")) if not self.calibrateMode: self.calibrateMode = False calibrateModeStrings = getExtensionDefault("%s.%s" % (extensionKey, "calibrateModeStrings")) if not calibrateModeStrings: calibrateModeStrings = { 'group1.baseInput': 'dotlessi o s', 'group1.markInput': 'dieresis circumflex macron breve caron', 'group2.baseInput': 'I O S', 'group2.markInput': 'dieresis.cap circumflex.cap macron.cap breve.cap caron.cap', 'group3.baseInput': 'I.sc O.sc S.sc', 'group3.markInput': 'dieresis circumflex macron breve caron', 'group4.baseInput': '', 'group4.markInput': '', } # -- Window -- self.w = FloatingWindow(posSize, extensionName, minSize=(500, 400)) self.w.fontList = List((10, 10, 190, -41), self.glyphNamesList, selectionCallback=self.listSelectionCallback) if roboFontVersion < '1.7': self.w.fontList.getNSTableView().sizeToFit() # use the full width of the column self.w.fontList.show(not self.calibrateMode) self.w.lineView = MultiLineView((210, 10, -10, -41), pointSize = self.textSize, lineHeight = self.lineHeight, displayOptions={"Beam" : False, "displayMode" : "Multi Line"} ) # -- Calibration Mode -- baseLabel = "Bases" markLabel = "Marks" width, height = 190, 140 self.cm = Group((0, 0, 0, 0)) # --- self.cm.group1 = Group((5, height*0, width, height-10)) self.cm.group1.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group1.baseInput = EditText((0, 21, width, 22), calibrateModeStrings['group1.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group1.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group1.markInput = EditText((0, 71, width, 44), calibrateModeStrings['group1.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group1.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group2 = Group((5, height*1, width, height-10)) self.cm.group2.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group2.baseInput = EditText((0, 21, width, 22), calibrateModeStrings['group2.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group2.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group2.markInput = EditText((0, 71, width, 44), calibrateModeStrings['group2.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group2.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group3 = Group((5, height*2, width, height-10)) self.cm.group3.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group3.baseInput = EditText((0, 21, width, 22), calibrateModeStrings['group3.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group3.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group3.markInput = EditText((0, 71, width, 44), calibrateModeStrings['group3.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group3.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group4 = Group((5, height*3, width, height-10)) self.cm.group4.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group4.baseInput = EditText((0, 21, width, 22), calibrateModeStrings['group4.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group4.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group4.markInput = EditText((0, 71, width, 44), calibrateModeStrings['group4.markInput'], callback=self.updateCalibrateMode, continuous=False) # --- view = DefconAppKitTopAnchoredNSView.alloc().init() view.addSubview_(self.cm.getNSView()) view.setFrame_(((0, 0), (width+10, height*4-23))) self.cm.setPosSize((0, 0, width+10, height*4-22)) self.w.scrollView = ScrollView((5, 10, width+10, -41), view, drawsBackground=False, hasHorizontalScroller=False) self.w.scrollView.getNSScrollView().setBorderType_(NSNoBorder) self.w.scrollView.getNSScrollView().setVerticalScrollElasticity_(1) # NSScrollElasticityNone self.w.scrollView.show(self.calibrateMode) # -- Footer -- self.w.calibrateModeCheck = CheckBox((10, -32, 200, -10), "Calibration Mode", callback=self.calibrateModeCallback, value=self.calibrateMode) self.w.textSizeLabel = TextBox((210, -30, 100, -10), "Text Size") self.w.textSize = EditText((270, -32, 35, -10), self.textSize, callback=self.textSizeCallback, continuous=False, formatter=intPosMinOneNumFormatter) self.w.lineHeightLabel = TextBox((320, -30, 100, -10), "Line Height") self.w.lineHeight = EditText((395, -32, 35, -10), self.lineHeight, callback=self.lineHeightCallback, continuous=False, formatter=integerNumFormatter) self.w.extraSidebearingsLabel = TextBox((446, -30, 180, -10), "Extra Sidebearings") self.w.extraSidebearingsChar = TextBox((602, -30, 20, -10), "&") self.w.extraSidebearingLeft = EditText((567, -32, 35, -10), self.extraSidebearings[0], callback=self.extraSidebearingsCallback, continuous=False, formatter=intPosMinZeroNumFormatter) self.w.extraSidebearingRight = EditText((614, -32, 35, -10), self.extraSidebearings[1], callback=self.extraSidebearingsCallback, continuous=False, formatter=intPosMinZeroNumFormatter) self.w.extraGlyphsLabel = TextBox((665, -30, 180, -10), "Extra Glyphs") self.w.extraGlyphs = EditText((749, -32, -10, -10), self.extraGlyphs, callback=self.extraGlyphsCallback, continuous=False) # trigger the initial state and contents of the window self.extraGlyphsCallback() # this will call self.updateExtensionWindow() self.w.bind("close", self.windowClose) self.w.open() self.w.makeKey() def calibrateModeCallback(self, sender): self.calibrateMode = not self.calibrateMode self.w.fontList.show(not sender.get()) self.w.scrollView.show(self.calibrateMode) self.updateExtensionWindow() def textSizeCallback(self, sender): try: # in case the user submits an empty field self.textSize = int(sender.get()) except: # reset to the previous value NSBeep() self.sender.set(self.textSize) self.w.lineView.setPointSize(self.textSize) def lineHeightCallback(self, sender): try: self.lineHeight = int(sender.get()) except: NSBeep() self.sender.set(self.lineHeight) self.w.lineView.setLineHeight(self.lineHeight) def extraSidebearingsCallback(self, sender): left = self.w.extraSidebearingLeft right = self.w.extraSidebearingRight try: self.extraSidebearings = [int(left.get()), int(right.get())] except: NSBeep() left.set(self.extraSidebearings[0]) right.set(self.extraSidebearings[1]) self.extraGlyphsCallback() # this will call self.updateExtensionWindow() def extraGlyphsCallback(self, *sender): del self.extraGlyphsList[:] # empty the list self.extraGlyphs = self.w.extraGlyphs.get() glyphNamesList = self.extraGlyphs.split() for gName in glyphNamesList: try: extraGlyph = self.font[gName] # must create a new glyph in order to be able to increase the sidebearings without modifying the font newGlyph = RGlyph() newGlyph.setParent(self.font) # must use deepAppend because the extra glyph may have components (which will cause problems to the MultiLineView) newGlyph = self.deepAppendGlyph(newGlyph, extraGlyph) newGlyph.width = extraGlyph.width except RoboFontError: continue newGlyph.leftMargin += self.extraSidebearings[0] newGlyph.rightMargin += self.extraSidebearings[1] self.extraGlyphsList.append(newGlyph) self.glyphPreviewCacheDict.clear() self.updateExtensionWindow() def windowClose(self, sender): self.font.naked().removeObserver(self, "Font.Changed") removeObserver(self, "fontWillClose") removeObserver(self, "fontResignCurrent") removeObserver(self, "currentGlyphChanged") removeObserver(self, "draw") removeObserver(self, "drawInactive") removeObserver(self, "drawPreview") self.saveExtensionDefaults() def getCalibrateModeStrings(self): calibrateModeStringsDict = {} for i in range(1,5): group = getattr(self.cm, "group%d" % i) calibrateModeStringsDict["group%d.baseInput" % i] = group.baseInput.get() calibrateModeStringsDict["group%d.markInput" % i] = group.markInput.get() return calibrateModeStringsDict def saveExtensionDefaults(self): setExtensionDefault("%s.%s" % (extensionKey, "posSize"), self.w.getPosSize()) setExtensionDefault("%s.%s" % (extensionKey, "textSize"), self.textSize) setExtensionDefault("%s.%s" % (extensionKey, "lineHeight"), self.lineHeight) setExtensionDefault("%s.%s" % (extensionKey, "extraSidebearings"), self.extraSidebearings) setExtensionDefault("%s.%s" % (extensionKey, "extraGlyphs"), self.extraGlyphs) setExtensionDefault("%s.%s" % (extensionKey, "calibrateMode"), self.calibrateMode) setExtensionDefault("%s.%s" % (extensionKey, "calibrateModeStrings"), self.getCalibrateModeStrings()) def _previewFill(self, info): self.Blue, self.Alpha = 0, 1 def _drawFill(self, info): self.Blue, self.Alpha = 1, 0.6 def _fontWillClose(self, info): """ Close the window when the last font is closed """ if len(AllFonts()) < 2: self.windowClose(self) self.w.close() def _currentFontChanged(self, info): self.font.naked().removeObserver(self, "Font.Changed") self.font = CurrentFont() self.font.naked().addObserver(self, "fontWasModified", "Font.Changed") self.fillAnchorsAndMarksDicts() del self.glyphNamesList[:] del self.selectedGlyphNamesList[:] self.updateExtensionWindow() def _currentGlyphChanged(self, info): self.updateExtensionWindow() def fontWasModified(self, info): OutputWindow().clear() self.fillAnchorsAndMarksDicts() del self.glyphNamesList[:] del self.selectedGlyphNamesList[:] self.updateExtensionWindow() def deepAppendGlyph(self, glyph, gToAppend, offset=(0,0)): if not gToAppend.components: glyph.appendGlyph(gToAppend, offset) else: for component in gToAppend.components: compGlyph = self.font[component.baseGlyph].copy() # handle component transformations componentTransformation = component.transformation # when undoing a paste anchor or a delete anchor action, RoboFont returns component.transformation as a list instead of a tuple if type(componentTransformation) is list: componentTransformation = tuple(componentTransformation) if componentTransformation != (1, 0, 0, 1, 0, 0): # if component is skewed and/or is shifted matrix = componentTransformation[0:4] if matrix != (1, 0, 0, 1): # if component is skewed transformObj = Identity.transform(matrix + (0, 0)) # ignore the original component's shifting values compGlyph.transform(transformObj) glyph.appendGlyph(compGlyph, map(sum, zip(component.offset, offset))) # add the two tuples of offset for contour in gToAppend: glyph.appendContour(contour, offset) # if the assembled glyph still has components, recursively remove and replace them 1-by-1 by the glyphs they reference if glyph.components: nestedComponent = glyph.components[-1] # start from the end glyph.removeComponent(nestedComponent) glyph = self.deepAppendGlyph(glyph, self.font[nestedComponent.baseGlyph], nestedComponent.offset) return glyph def updateCalibrateMode(self, *sender): glyphsList = [] newLine = self.w.lineView.createNewLineGlyph() # cycle thru the UI Groups and collect the strings for i in range(1,5): group = getattr(self.cm, "group%d" % i) baseGlyphsNamesList = group.baseInput.get().split() markGlyphsNamesList = group.markInput.get().split() # iterate thru the base+mark combinations for gBaseName, gMarkName in product(baseGlyphsNamesList, markGlyphsNamesList): newGlyph = RGlyph() newGlyph.setParent(self.font) # skip invalid glyph names try: baseGlyph = self.font[gBaseName] markGlyph = self.font[gMarkName] except RoboFontError: continue # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, baseGlyph) # append mark glyph newGlyph = self.deepAppendGlyph(newGlyph, markGlyph, self.getAnchorOffsets(baseGlyph, markGlyph)) # set the advanced width dfltSidebearings = self.upm * .05 # 5% of the UPM newGlyph.leftMargin = dfltSidebearings + self.extraSidebearings[0] newGlyph.rightMargin = dfltSidebearings + self.extraSidebearings[1] # append the assembled glyph to the list glyphsList.extend(self.extraGlyphsList) glyphsList.append(newGlyph) # add line break, if both input fields have content if baseGlyphsNamesList and markGlyphsNamesList: glyphsList.extend(self.extraGlyphsList) glyphsList.append(newLine) # update the contents of the MultiLineView self.w.lineView.set(glyphsList) def updateExtensionWindow(self): if self.calibrateMode: self.updateCalibrateMode() return if CurrentGlyph() is not None: # NOTE: CurrentGlyph() will return zero (its length), so "is not None" is necessary self.glyph = CurrentGlyph() self.glyphNamesList = self.makeGlyphNamesList(self.glyph) self.updateListView() currentGlyphName = self.glyph.name # base glyph + accent combinations preview # first check if there's a cached glyph if currentGlyphName in self.glyphPreviewCacheDict: self.w.lineView.set(self.glyphPreviewCacheDict[currentGlyphName]) # assemble the glyphs else: glyphsList = [] for glyphNameInUIList in self.glyphNamesList: newGlyph = RGlyph() newGlyph.setParent(self.font) # the glyph in the UI list is a mark if glyphNameInUIList in self.marksDict: markGlyph = self.font[glyphNameInUIList] # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, self.glyph) # append mark glyph newGlyph = self.deepAppendGlyph(newGlyph, markGlyph, self.getAnchorOffsets(self.glyph, markGlyph)) # set the advanced width if self.glyph.width < 10: # combining marks or other glyphs with a small advanced width newGlyph.leftMargin = self.upm * .05 # 5% of the UPM newGlyph.rightMargin = newGlyph.leftMargin else: newGlyph.width = self.glyph.width # the glyph in the UI list is a base else: baseGlyph = self.font[glyphNameInUIList] # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, baseGlyph) # append mark glyph newGlyph = self.deepAppendGlyph(newGlyph, self.glyph, self.getAnchorOffsets(baseGlyph, self.glyph)) # set the advanced width if self.glyph.width < 10: # combining marks or other glyphs with a small advanced width newGlyph.leftMargin = self.upm * .05 newGlyph.rightMargin = newGlyph.leftMargin else: newGlyph.width = baseGlyph.width # pad the new glyph if it has too much overhang if newGlyph.leftMargin < self.upm * .15: newGlyph.leftMargin = self.upm * .05 if newGlyph.rightMargin < self.upm * .15: newGlyph.rightMargin = self.upm * .05 # add extra sidebearings newGlyph.leftMargin += self.extraSidebearings[0] newGlyph.rightMargin += self.extraSidebearings[1] # one last check for making sure the new glyph can be displayed if not newGlyph.components: glyphsList.extend(self.extraGlyphsList) glyphsList.append(newGlyph) else: print "Combination with mark glyph %s can't be previewed because it contains component %s." % (glyphNameInUIList, newGlyph.components[0].baseGlyph) glyphsList.extend(self.extraGlyphsList) self.w.lineView.set(glyphsList) # add to the cache self.glyphPreviewCacheDict[currentGlyphName] = glyphsList else: self.w.lineView.set([]) def listSelectionCallback(self, sender): selectedGlyphNamesList = [] for index in sender.getSelection(): selectedGlyphNamesList.append(self.glyphNamesList[index]) self.selectedGlyphNamesList = selectedGlyphNamesList self.updateGlyphView() def updateGlyphView(self): # update the current glyph view UpdateCurrentGlyphView() def fillAnchorsAndMarksDicts(self): # reset all the dicts self.glyphPreviewCacheDict.clear() self.anchorsOnMarksDict.clear() self.anchorsOnBasesDict.clear() self.marksDict.clear() markGlyphsWithMoreThanOneAnchorTypeList = [] for glyphName in self.font.glyphOrder: glyphAnchorsList = self.font[glyphName].anchors for anchor in glyphAnchorsList: if anchor.name[0] == '_': anchorName = anchor.name[1:] # add to AnchorsOnMarks dictionary if anchorName not in self.anchorsOnMarksDict: self.anchorsOnMarksDict[anchorName] = [glyphName] else: tempList = self.anchorsOnMarksDict[anchorName] tempList.append(glyphName) self.anchorsOnMarksDict[anchorName] = tempList # add to Marks dictionary if glyphName not in self.marksDict: self.marksDict[glyphName] = anchorName else: if glyphName not in markGlyphsWithMoreThanOneAnchorTypeList: markGlyphsWithMoreThanOneAnchorTypeList.append(glyphName) else: anchorName = anchor.name # add to AnchorsOnBases dictionary if anchorName not in self.anchorsOnBasesDict: self.anchorsOnBasesDict[anchorName] = [glyphName] else: tempList = self.anchorsOnBasesDict[anchorName] tempList.append(glyphName) self.anchorsOnBasesDict[anchorName] = tempList if markGlyphsWithMoreThanOneAnchorTypeList: for glyphName in markGlyphsWithMoreThanOneAnchorTypeList: print "ERROR: Glyph %s has more than one type of anchor." % glyphName def makeGlyphNamesList(self, glyph): glyphNamesList = [] markGlyphIsAbleToBeBase = False if glyph is not None: # NOTE: "if glyph" will return zero (its length), so "is not None" is necessary # assemble the list for the UI list for anchor in glyph.anchors: anchorName = anchor.name if anchorName in self.anchorsOnMarksDict: glyphNamesList.extend(self.anchorsOnMarksDict[anchorName]) elif anchorName[1:] in self.anchorsOnBasesDict: # skips the leading underscore glyphNamesList.extend(self.anchorsOnBasesDict[anchorName[1:]]) # for mark glyphs, test if they're able to get other mark glyphs attached to them # this will (correctly) prevent the UI list from including glyph names that cannot be displayed with the current glyph if glyph.name in self.marksDict: for anchor in glyph.anchors: if anchor.name[0] != '_': # the current mark glyph has anchors that allow it to be a base for other marks markGlyphIsAbleToBeBase = True break # remove marks from the glyph list if the current mark glyph can't work as a base if not markGlyphIsAbleToBeBase: for glyphName in glyphNamesList[::-1]: # iterate from the end of the list if glyphName in self.marksDict: glyphNamesList.remove(glyphName) return glyphNamesList def updateListView(self): self.w.fontList.set(self.glyphNamesList) def getAnchorOffsets(self, canvasGlyph, glyphToDraw): # the current glyph is a mark if canvasGlyph.name in self.marksDict: # glyphToDraw is also a mark (mark-to-mark case) if glyphToDraw.name in self.marksDict: # pick the (mark glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name[0] != '_': anchorName = anchor.name markAnchor = anchor break # pick the (base glyph) anchor to draw on for anchor in glyphToDraw.anchors: try: if anchor.name == '_'+ anchorName: baseAnchor = anchor break except UnboundLocalError: continue # glyphToDraw is not a mark else: # pick the (mark glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name[0] == '_': anchorName = anchor.name[1:] markAnchor = anchor break # pick the (base glyph) anchor to draw on for anchor in glyphToDraw.anchors: try: if anchor.name == anchorName: baseAnchor = anchor break except UnboundLocalError: continue try: offsetX = markAnchor.x - baseAnchor.x offsetY = markAnchor.y - baseAnchor.y except UnboundLocalError: offsetX = 0 offsetY = 0 # the current glyph is a base else: try: anchorName = self.marksDict[glyphToDraw.name] except KeyError: anchorName = None if anchorName: # pick the (base glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name == anchorName: baseAnchor = anchor break # pick the (mark glyph) anchor to draw on for anchor in glyphToDraw.anchors: if anchor.name == '_'+ anchorName: markAnchor = anchor break try: offsetX = baseAnchor.x - markAnchor.x offsetY = baseAnchor.y - markAnchor.y except UnboundLocalError: offsetX = 0 offsetY = 0 return (offsetX, offsetY) def _drawGlyphs(self, info): """ draw stuff in the glyph window view """ translateBefore = (0, 0) for glyphName in self.selectedGlyphNamesList: glyphToDraw = self.font[glyphName] # determine the offset of the anchors offset = self.getAnchorOffsets(self.glyph, glyphToDraw) # set the offset of the drawing translate(offset[0] - translateBefore[0], offset[1] - translateBefore[1]) # record the shift amounts (these are needed for resetting the drawing position when more than one mark is selected on the list) translateBefore = offset # set the fill & stroke fill(0, 0, self.Blue, self.Alpha) strokeWidth(None) # draw it mojoPen = MojoDrawingToolsPen(glyphToDraw, self.font) glyphToDraw.draw(mojoPen) mojoPen.draw()