from fontTools import ttLib from fontTools.ttLib import macUtils import macfs import PyBrowser import W, Lists import os import ATM import Numeric import Qd from rf.views.wGlyphList import GlyphList class TableBrowser: def __init__(self, path=None, ttFont=None, res_index=None): W.SetCursor('watch') if path is None: self.ttFont = ttFont self.filename = "????" else: self.ttFont = ttLib.TTFont(path, res_index) if res_index is None: self.filename = os.path.basename(path) else: self.filename = os.path.basename(path) + " - " + str(res_index) self.currentglyph = None self.glyphs = {} self.buildinterface() def buildinterface(self): buttonwidth = 120 glyphlistwidth = 150 hmargin = 10 vmargin = 8 title = self.filename tables = self.ttFont.keys() tables.sort() self.w = w = W.Window((500, 300), title, minsize = (400, 200)) w.browsetablebutton = W.Button((hmargin, 32, buttonwidth, 16), "Browse tableŠ", self.browsetable) w.browsefontbutton = W.Button((hmargin, vmargin, buttonwidth, 16), "Browse fontŠ", self.browsefont) w.tablelist = W.List((hmargin, 56, buttonwidth, -128), tables, self.tablelisthit) w.divline1 = W.VerticalLine((buttonwidth + 2 * hmargin, vmargin, 1, -vmargin)) gleft = buttonwidth + 3 * hmargin + 1 hasGlyfTable = self.ttFont.has_key('glyf') glyphnames = self.ttFont.getGlyphNames2() # caselessly sorted glyph names if hasGlyfTable: w.glyphlist = GlyphList((gleft, 56, glyphlistwidth, -vmargin), glyphnames, self.glyphlisthit) w.divline2 = W.VerticalLine((buttonwidth + glyphlistwidth + 4 * hmargin + 2, vmargin, 1, -vmargin)) yMin = self.ttFont['head'].yMin yMax = self.ttFont['head'].yMax w.gviewer = GlyphViewer((buttonwidth + glyphlistwidth + 5 * hmargin + 3, vmargin, -hmargin, -vmargin), yMin, yMax) w.showpoints = W.CheckBox((gleft, vmargin, glyphlistwidth, 16), "Show points", self.w.gviewer.toggleshowpoints) w.showpoints.set(self.w.gviewer.showpoints) w.showlines = W.CheckBox((gleft, vmargin + 24, glyphlistwidth, 16), "Show lines", self.w.gviewer.toggleshowlines) w.showlines.set(self.w.gviewer.showlines) else: w.glyphlist = GlyphList((gleft, 56, glyphlistwidth, -vmargin), glyphnames) w.noGlyphTable = W.TextBox((gleft, vmargin, -20, 20), "no 'glyf' table found") w.setdefaultbutton(w.browsetablebutton) w.tocurrentfont = W.Button((hmargin, -120, buttonwidth, 16), "Copy to current font", self.copytocurrentfont) w.fromcurrentfont = W.Button((hmargin, -96, buttonwidth, 16), "Copy from current font", self.copyfromcurrentfont) w.saveflat = W.Button((hmargin, -72, buttonwidth, 16), "Save as flat fileŠ", self.saveflat) w.savesuitcasebutton = W.Button((hmargin, -48, buttonwidth, 16), "Save as suitcaseŠ", self.savesuitcase) w.savexmlbutton = W.Button((hmargin, -24, buttonwidth, 16), "Save as XMLŠ", self.saveXML) w.open() w.browsetablebutton.enable(0) def browsetable(self): self.tablelisthit(1) def browsefont(self): PyBrowser.Browser(self.ttFont) def copytocurrentfont(self): pass def copyfromcurrentfont(self): pass def saveflat(self): path = putfile("Save font as flat file:", self.filename, ".TTF") if path: W.SetCursor('watch') self.ttFont.save(path) def savesuitcase(self): path = putfile("Save font as suitcase:", self.filename, ".suit") if path: W.SetCursor('watch') self.ttFont.save(path, 1) def saveXML(self): path = putfile("Save font as XML text file:", self.filename, ".xml") if path: W.SetCursor('watch') pb = macUtils.ProgressBar("Saving %s as XMLŠ" % self.filename) try: self.ttFont.saveXML(path, pb) finally: pb.close() def glyphlisthit(self, isDbl): sel = self.w.glyphlist.getselectedobjects() if not sel or sel[0] == self.currentglyph: return self.currentglyph = sel[0] if self.glyphs.has_key(self.currentglyph): g = self.glyphs[self.currentglyph] else: g = Glyph(self.ttFont, self.currentglyph) self.glyphs[self.currentglyph] = g self.w.gviewer.setglyph(g) def tablelisthit(self, isdbl): if isdbl: for tag in self.w.tablelist.getselectedobjects(): table = self.ttFont[tag] if tag == 'glyf': W.SetCursor('watch') for glyphname in self.ttFont.getGlyphOrder(): try: glyph = table[glyphname] except KeyError: pass # incomplete font, oh well. PyBrowser.Browser(table) else: sel = self.w.tablelist.getselection() if sel: self.w.browsetablebutton.enable(1) else: self.w.browsetablebutton.enable(0) class Glyph: def __init__(self, ttFont, glyphName): ttglyph = ttFont['glyf'][glyphName] self.iscomposite = ttglyph.numberOfContours == -1 self.width, self.lsb = ttFont['hmtx'][glyphName] if ttglyph.numberOfContours == 0: self.xMin = 0 self.contours = [] return self.xMin = ttglyph.xMin coordinates, endPts, flags = ttglyph.getCoordinates(ttFont['glyf']) self.contours = [] self.flags = [] startpt = 0 for endpt in endPts: self.contours.append(Numeric.array(coordinates[startpt:endpt+1])) self.flags.append(flags[startpt:endpt+1]) startpt = endpt + 1 def getcontours(self, scale, move): contours = [] for i in range(len(self.contours)): contours.append((self.contours[i] * Numeric.array(scale) + move), self.flags[i]) return contours class GlyphViewer(W.Widget): def __init__(self, possize, yMin, yMax): W.Widget.__init__(self, possize) self.glyph = None extra = 0.02 * (yMax-yMin) self.yMin, self.yMax = yMin - extra, yMax + extra self.showpoints = 1 self.showlines = 1 def toggleshowpoints(self, onoff): self.showpoints = onoff self.SetPort() self.draw() def toggleshowlines(self, onoff): self.showlines = onoff self.SetPort() self.draw() def setglyph(self, glyph): self.glyph = glyph self.SetPort() self.draw() def draw(self, visRgn=None): # This a HELL of a routine, but it's pretty damn fast... import Qd if not self._visible: return Qd.EraseRect(Qd.InsetRect(self._bounds, 1, 1)) cliprgn = Qd.NewRgn() savergn = Qd.NewRgn() Qd.RectRgn(cliprgn, self._bounds) Qd.GetClip(savergn) Qd.SetClip(cliprgn) try: if self.glyph: l, t, r, b = Qd.InsetRect(self._bounds, 1, 1) height = b - t scale = float(height) / (self.yMax - self.yMin) topoffset = t + scale * self.yMax width = scale * self.glyph.width lsb = scale * self.glyph.lsb xMin = scale * self.glyph.xMin # XXXX this is not correct when USE_MY_METRICS is set in component! leftoffset = l + 0.5 * (r - l - width) gleftoffset = leftoffset - xMin + lsb if self.showlines: Qd.RGBForeColor((0xafff, 0xafff, 0xafff)) # left sidebearing Qd.MoveTo(leftoffset, t) Qd.LineTo(leftoffset, b - 1) # right sidebearing Qd.MoveTo(leftoffset + width, t) Qd.LineTo(leftoffset + width, b - 1) # baseline Qd.MoveTo(l, topoffset) Qd.LineTo(r - 1, topoffset) # origin Qd.RGBForeColor((0x5fff, 0, 0)) Qd.MoveTo(gleftoffset, topoffset - 16) Qd.LineTo(gleftoffset, topoffset + 16) # reset color Qd.RGBForeColor((0, 0, 0)) if self.glyph.iscomposite: Qd.RGBForeColor((0x7fff, 0x7fff, 0x7fff)) ATM.startFillATM() contours = self.glyph.getcontours((scale, -scale), (gleftoffset, topoffset)) for contour, flags in contours: currentpoint = None done_moveto = 0 i = 0 nPoints = len(contour) while i < nPoints: pt = contour[i] if flags[i]: # onCurve currentpoint = lineto(pt, done_moveto) else: if not currentpoint: if not flags[i-1]: currentpoint = 0.5 * (contour[i-1] + pt) else: currentpoint = contour[i-1] if not flags[(i+1) % nPoints]: endPt = 0.5 * (pt + contour[(i+1) % nPoints]) else: endPt = contour[(i+1) % nPoints] i = i + 1 # offCurve currentpoint = qcurveto(currentpoint, pt, endPt, done_moveto) done_moveto = 1 i = i + 1 ATM.fillClosePathATM() ATM.endFillATM() # draw point markers if self.showpoints: for contour, flags in contours: Qd.RGBForeColor((0, 0xffff, 0)) for i in range(len(contour)): (x, y) = contour[i] onCurve = flags[i] & 0x1 if onCurve: Qd.PaintRect(Qd.InsetRect((x, y, x, y), -2, -2)) else: Qd.PaintOval(Qd.InsetRect((x, y, x, y), -2, -2)) Qd.RGBForeColor((0xffff, 0, 0)) Qd.RGBForeColor((0, 0, 0)) Qd.FrameRect(self._bounds) finally: Qd.SetClip(savergn) Qd.DisposeRgn(cliprgn) Qd.DisposeRgn(savergn) extensions = [".suit", ".xml", ".TTF", ".ttf"] def putfile(prompt, filename, newextension): for ext in extensions: if filename[-len(ext):] == ext: filename = filename[:-len(ext)] + newextension break else: filename = filename + newextension fss, ok = macfs.StandardPutFile(prompt, filename) if ok: return fss.as_pathname() def lineto(pt, done_moveto): x, y = pt if done_moveto: ATM.fillLineToATM((x, y)) else: ATM.fillMoveToATM((x, y)) return pt def qcurveto(pt0, pt1, pt2, done_moveto): if not done_moveto: x0, y0 = pt0 ATM.fillMoveToATM((x0, y0)) x1a, y1a = pt0 + 0.6666666666667 * (pt1 - pt0) x1b, y1b = pt2 + 0.6666666666667 * (pt1 - pt2) x2, y2 = pt2 ATM.fillCurveToATM((x1a, y1a), (x1b, y1b), (x2, y2)) return pt2