source: packages/ufo2fdk/trunk/Lib/ufo2fdk/outlineOTF.py @ 890

Revision 890, 26.8 KB checked in by tal, 20 months ago (diff)
Fix for OS/2 LastCharIndex.
Line 
1from __future__ import division
2import time
3from fontTools.ttLib import TTFont, newTable
4from fontTools.cffLib import TopDictIndex, TopDict, CharStrings, SubrsIndex, GlobalSubrsIndex, PrivateDict, IndexedStrings
5from fontTools.ttLib.tables.O_S_2f_2 import Panose
6from fontTools.ttLib.tables._h_e_a_d import mac_epoch_diff
7from pens.t2CharStringPen import T2CharStringPen
8from fontInfoData import getFontBounds, getAttrWithFallback, dateStringToTimeValue, dateStringForNow, intListToNum, normalizeNameForPostscript
9try:
10    set
11except NameError:
12    from sets import Set as set
13try:
14    sorted
15except NameError:
16    def sorted(l):
17        l = list(l)
18        l.sort()
19        return l
20
21def _roundInt(v):
22    return int(round(v))
23
24
25class OutlineOTFCompiler(object):
26
27    """
28    This object will create a bare-bones OTF-CFF containing
29    outline data and not much else. The only external
30    method is :meth:`ufo2fdk.tools.outlineOTF.compile`.
31
32    When creating this object, you must provide a *font*
33    object and a *path* indicating where the OTF should
34    be saved. Optionally, you can provide a *glyphOrder*
35    list of glyph names indicating the order of the glyphs
36    in the font.
37    """
38
39    def __init__(self, font, path, glyphOrder=None):
40        self.ufo = font
41        self.path = path
42        # make any missing glyphs and store them locally
43        missingRequiredGlyphs = self.makeMissingRequiredGlyphs()
44        # make a dict of all glyphs
45        self.allGlyphs = {}
46        for glyph in font:
47            self.allGlyphs[glyph.name] = glyph
48        self.allGlyphs.update(missingRequiredGlyphs)
49        # store the glyph order
50        if glyphOrder is None:
51            glyphOrder = sorted(self.allGlyphs.keys())
52        self.glyphOrder = self.makeOfficialGlyphOrder(glyphOrder)
53        # make a reusable bounding box
54        self.fontBoundingBox = tuple([_roundInt(i) for i in self.makeFontBoundingBox()])
55        # make a reusable character mapping
56        self.unicodeToGlyphNameMapping = self.makeUnicodeToGlyphNameMapping()
57
58    # -----------
59    # Main Method
60    # -----------
61
62    def compile(self):
63        """
64        Compile the OTF.
65        """
66        self.otf = TTFont(sfntVersion="OTTO")
67        # populate basic tables
68        self.setupTable_head()
69        self.setupTable_hhea()
70        self.setupTable_hmtx()
71        self.setupTable_name()
72        self.setupTable_maxp()
73        self.setupTable_cmap()
74        self.setupTable_OS2()
75        self.setupTable_post()
76        self.setupTable_CFF()
77        self.setupOtherTables()
78        # write the file
79        self.otf.save(self.path)
80        # discard the object
81        self.otf.close()
82        del self.otf
83
84    # -----
85    # Tools
86    # -----
87
88    def makeFontBoundingBox(self):
89        """
90        Make a bounding box for the font.
91
92        **This should not be called externally.** Subclasses
93        may override this method to handle the bounds creation
94        in a different way if desired.
95        """
96        return getFontBounds(self.ufo)
97
98    def makeUnicodeToGlyphNameMapping(self):
99        """
100        Make a ``unicode : glyph name`` mapping for the font.
101
102        **This should not be called externally.** Subclasses
103        may override this method to handle the mapping creation
104        in a different way if desired.
105        """
106        mapping = {}
107        for glyphName, glyph in self.allGlyphs.items():
108            unicodes = glyph.unicodes
109            for uni in unicodes:
110                mapping[uni] = glyphName
111        return mapping
112
113    def makeMissingRequiredGlyphs(self):
114        """
115        Add space and .notdef to the font if they are not present.
116
117        **This should not be called externally.** Subclasses
118        may override this method to handle the glyph creation
119        in a different way if desired.
120        """
121        glyphs = {}
122        font = self.ufo
123        unitsPerEm = _roundInt(getAttrWithFallback(font.info, "unitsPerEm"))
124        ascender = _roundInt(getAttrWithFallback(font.info, "ascender"))
125        descender = _roundInt(getAttrWithFallback(font.info, "descender"))
126        defaultWidth = _roundInt(unitsPerEm * 0.5)
127        if ".notdef" not in self.ufo:
128            glyphs[".notdef"] = StubGlyph(name=".notdef", width=defaultWidth, unitsPerEm=unitsPerEm, ascender=ascender, descender=descender)
129        if "space" not in self.ufo:
130            glyphs["space"] = StubGlyph(name="space", width=defaultWidth, unitsPerEm=unitsPerEm, ascender=ascender, descender=descender, unicodes=[32])
131        return glyphs
132
133    def makeOfficialGlyphOrder(self, glyphOrder):
134        """
135        Make a the final glyph order.
136
137        **This should not be called externally.** Subclasses
138        may override this method to handle the order creation
139        in a different way if desired.
140        """
141        allGlyphs = self.allGlyphs
142        orderedGlyphs = [".notdef", "space"]
143        for glyphName in glyphOrder:
144            if glyphName in [".notdef", "space"]:
145                continue
146            orderedGlyphs.append(glyphName)
147        for glyphName in sorted(self.allGlyphs.keys()):
148            if glyphName not in orderedGlyphs:
149                orderedGlyphs.append(glyphName)
150        return orderedGlyphs
151
152    def getCharStringForGlyph(self, glyph, private, globalSubrs):
153        """
154        Get a Type2CharString for the *glyph*
155
156        **This should not be called externally.** Subclasses
157        may override this method to handle the charstring creation
158        in a different way if desired.
159        """
160        width = glyph.width
161        # subtract the nominal width
162        postscriptNominalWidthX = getAttrWithFallback(self.ufo.info, "postscriptNominalWidthX")
163        if postscriptNominalWidthX:
164            width = width - postscriptNominalWidthX
165        # round
166        width = _roundInt(width)
167        pen = T2CharStringPen(width, self.allGlyphs)
168        glyph.draw(pen)
169        charString = pen.getCharString(private, globalSubrs)
170        return charString
171
172    # --------------
173    # Table Builders
174    # --------------
175
176    def setupTable_head(self):
177        """
178        Make the head table.
179
180        **This should not be called externally.** Subclasses
181        may override or supplement this method to handle the
182        table creation in a different way if desired.
183        """
184        self.otf["head"] = head = newTable("head")
185        font = self.ufo
186        head.checkSumAdjustment = 0
187        head.tableVersion = 1.0
188        versionMajor = getAttrWithFallback(font.info, "versionMajor")
189        versionMinor = getAttrWithFallback(font.info, "versionMinor") * .001
190        head.fontRevision = versionMajor + versionMinor
191        head.magicNumber = 0x5F0F3CF5
192        # upm
193        head.unitsPerEm = getAttrWithFallback(font.info, "unitsPerEm")
194        # times
195        head.created = dateStringToTimeValue(getAttrWithFallback(font.info, "openTypeHeadCreated")) - mac_epoch_diff
196        head.modified = dateStringToTimeValue(dateStringForNow()) - mac_epoch_diff
197        # bounding box
198        xMin, yMin, xMax, yMax = self.fontBoundingBox
199        head.xMin = xMin
200        head.yMin = yMin
201        head.xMax = xMax
202        head.yMax = yMax
203        # style mapping
204        styleMapStyleName = getAttrWithFallback(font.info, "styleMapStyleName")
205        macStyle = []
206        if styleMapStyleName == "bold":
207            macStyle = [0]
208        elif styleMapStyleName == "bold italic":
209            macStyle = [0, 1]
210        elif styleMapStyleName == "italic":
211            macStyle = [1]
212        head.macStyle = intListToNum(macStyle, 0, 16)
213        # misc
214        head.flags = intListToNum(getAttrWithFallback(font.info, "openTypeHeadFlags"), 0, 16)
215        head.lowestRecPPEM = _roundInt(getAttrWithFallback(font.info, "openTypeHeadLowestRecPPEM"))
216        head.fontDirectionHint = 2
217        head.indexToLocFormat = 0
218        head.glyphDataFormat = 0
219
220    def setupTable_name(self):
221        """
222        Make the name table.
223
224        **This should not be called externally.** Subclasses
225        may override or supplement this method to handle the
226        table creation in a different way if desired.
227        """
228        self.otf["name"] = newTable("name")
229
230    def setupTable_maxp(self):
231        """
232        Make the maxp table.
233
234        **This should not be called externally.** Subclasses
235        may override or supplement this method to handle the
236        table creation in a different way if desired.
237        """
238        self.otf["maxp"] = maxp = newTable("maxp")
239        maxp.tableVersion = 0x00005000
240
241    def setupTable_cmap(self):
242        """
243        Make the cmap table.
244
245        **This should not be called externally.** Subclasses
246        may override or supplement this method to handle the
247        table creation in a different way if desired.
248        """
249        from fontTools.ttLib.tables._c_m_a_p import cmap_format_4
250        # mac
251        cmap4_0_3 = cmap_format_4(4)
252        cmap4_0_3.platformID = 0
253        cmap4_0_3.platEncID = 3
254        cmap4_0_3.language = 0
255        cmap4_0_3.cmap = dict(self.unicodeToGlyphNameMapping)
256        # windows
257        cmap4_3_1 = cmap_format_4(4)
258        cmap4_3_1.platformID = 3
259        cmap4_3_1.platEncID = 1
260        cmap4_3_1.language = 0
261        cmap4_3_1.cmap = dict(self.unicodeToGlyphNameMapping)
262        # store
263        self.otf["cmap"] = cmap = newTable("cmap")
264        cmap.tableVersion = 0
265        cmap.tables = [cmap4_0_3, cmap4_3_1]
266
267    def setupTable_OS2(self):
268        """
269        Make the OS/2 table.
270
271        **This should not be called externally.** Subclasses
272        may override or supplement this method to handle the
273        table creation in a different way if desired.
274        """
275        self.otf["OS/2"] = os2 = newTable("OS/2")
276        font = self.ufo
277        os2.version = 0x0004
278        # average glyph width
279        widths = [glyph.width for glyph in self.allGlyphs.values() if glyph.width > 0]
280        os2.xAvgCharWidth = _roundInt(sum(widths) / len(widths))
281        # weight and width classes
282        os2.usWeightClass = getAttrWithFallback(font.info, "openTypeOS2WeightClass")
283        os2.usWidthClass = getAttrWithFallback(font.info, "openTypeOS2WidthClass")
284        # embedding
285        os2.fsType = intListToNum(getAttrWithFallback(font.info, "openTypeOS2Type"), 0, 16)
286        # subscript
287        v = getAttrWithFallback(font.info, "openTypeOS2SubscriptXSize")
288        if v is None:
289            v = 0
290        os2.ySubscriptXSize = _roundInt(v)
291        v = getAttrWithFallback(font.info, "openTypeOS2SubscriptYSize")
292        if v is None:
293            v = 0
294        os2.ySubscriptYSize = _roundInt(v)
295        v = getAttrWithFallback(font.info, "openTypeOS2SubscriptXOffset")
296        if v is None:
297            v = 0
298        os2.ySubscriptXOffset = _roundInt(v)
299        v = getAttrWithFallback(font.info, "openTypeOS2SubscriptYOffset")
300        if v is None:
301            v = 0
302        os2.ySubscriptYOffset = _roundInt(v)
303        # superscript
304        v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptXSize")
305        if v is None:
306            v = 0
307        os2.ySuperscriptXSize = _roundInt(v)
308        v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptYSize")
309        if v is None:
310            v = 0
311        os2.ySuperscriptYSize = _roundInt(v)
312        v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptXOffset")
313        if v is None:
314            v = 0
315        os2.ySuperscriptXOffset = _roundInt(v)
316        v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptYOffset")
317        if v is None:
318            v = 0
319        os2.ySuperscriptYOffset = _roundInt(v)
320        # strikeout
321        v = getAttrWithFallback(font.info, "openTypeOS2StrikeoutSize")
322        if v is None:
323            v = 0
324        os2.yStrikeoutSize = _roundInt(v)
325        v = getAttrWithFallback(font.info, "openTypeOS2StrikeoutPosition")
326        if v is None:
327            v = 0
328        os2.yStrikeoutPosition = _roundInt(v)
329        # family class
330        os2.sFamilyClass = 0 # XXX not sure how to create the appropriate value
331        # panose
332        data = getAttrWithFallback(font.info, "openTypeOS2Panose")
333        panose = Panose()
334        panose.bFamilyType = data[0]
335        panose.bSerifStyle = data[1]
336        panose.bWeight = data[2]
337        panose.bProportion = data[3]
338        panose.bContrast = data[4]
339        panose.bStrokeVariation = data[5]
340        panose.bArmStyle = data[6]
341        panose.bLetterForm = data[7]
342        panose.bMidline = data[8]
343        panose.bXHeight = data[9]
344        os2.panose = panose
345        # Unicode ranges
346        uniRanges = getAttrWithFallback(font.info, "openTypeOS2UnicodeRanges")
347        os2.ulUnicodeRange1 = intListToNum(uniRanges, 0, 32)
348        os2.ulUnicodeRange2 = intListToNum(uniRanges, 32, 32)
349        os2.ulUnicodeRange3 = intListToNum(uniRanges, 64, 32)
350        os2.ulUnicodeRange4 = intListToNum(uniRanges, 96, 32)
351        # codepage ranges
352        codepageRanges = getAttrWithFallback(font.info, "openTypeOS2CodePageRanges")
353        os2.ulCodePageRange1 = intListToNum(codepageRanges, 0, 32)
354        os2.ulCodePageRange2 = intListToNum(codepageRanges, 32, 32)
355        # vendor id
356        os2.achVendID = getAttrWithFallback(font.info, "openTypeOS2VendorID")
357        # vertical metrics
358        os2.sxHeight = _roundInt(getAttrWithFallback(font.info, "xHeight"))
359        os2.sCapHeight = _roundInt(getAttrWithFallback(font.info, "capHeight"))
360        os2.sTypoAscender = _roundInt(getAttrWithFallback(font.info, "openTypeOS2TypoAscender"))
361        os2.sTypoDescender = _roundInt(getAttrWithFallback(font.info, "openTypeOS2TypoDescender"))
362        os2.sTypoLineGap = _roundInt(getAttrWithFallback(font.info, "openTypeOS2TypoLineGap"))
363        os2.usWinAscent = _roundInt(getAttrWithFallback(font.info, "openTypeOS2WinAscent"))
364        os2.usWinDescent = _roundInt(getAttrWithFallback(font.info, "openTypeOS2WinDescent"))
365        # style mapping
366        selection = list(getAttrWithFallback(font.info, "openTypeOS2Selection"))
367        styleMapStyleName = getAttrWithFallback(font.info, "styleMapStyleName")
368        if styleMapStyleName == "regular":
369            selection.append(6)
370        elif styleMapStyleName == "bold":
371            selection.append(5)
372        elif styleMapStyleName == "italic":
373            selection.append(0)
374        elif styleMapStyleName == "bold italic":
375            selection += [0, 5]
376        os2.fsSelection = intListToNum(selection, 0, 16)
377        # characetr indexes
378        unicodes = [i for i in self.unicodeToGlyphNameMapping.keys() if i is not None]
379        if unicodes:
380            minIndex = min(unicodes)
381            maxIndex = max(unicodes)
382        else:
383            # the font may have *no* unicode values
384            # (it really happens!) so there needs
385            # to be a fallback. use space for this.
386            minIndex = 0x0020
387            maxIndex = 0x0020
388        if maxIndex > 0xFFFF:
389            # the spec says that 0xFFFF should be used
390            # as the max if the max exceeds 0xFFFF
391            maxIndex = 0xFFFF
392        os2.fsFirstCharIndex = minIndex
393        os2.fsLastCharIndex = maxIndex
394        os2.usBreakChar = 32
395        os2.usDefaultChar = 0
396        # maximum contextual lookup length
397        os2.usMaxContex = 0
398
399    def setupTable_hmtx(self):
400        """
401        Make the hmtx table.
402
403        **This should not be called externally.** Subclasses
404        may override or supplement this method to handle the
405        table creation in a different way if desired.
406        """
407        self.otf["hmtx"] = hmtx = newTable("hmtx")
408        hmtx.metrics = {}
409        for glyphName, glyph in self.allGlyphs.items():
410            width = glyph.width
411            left = 0
412            if len(glyph) or len(glyph.components):
413                left = glyph.leftMargin
414            if left is None:
415                left = 0
416            hmtx[glyphName] = (_roundInt(width), _roundInt(left))
417
418    def setupTable_hhea(self):
419        """
420        Make the hhea table.
421
422        **This should not be called externally.** Subclasses
423        may override or supplement this method to handle the
424        table creation in a different way if desired.
425        """
426        self.otf["hhea"] = hhea = newTable("hhea")
427        font = self.ufo
428        hhea.tableVersion = 1.0
429        # vertical metrics
430        hhea.ascent = _roundInt(getAttrWithFallback(font.info, "openTypeHheaAscender"))
431        hhea.descent = _roundInt(getAttrWithFallback(font.info, "openTypeHheaDescender"))
432        hhea.lineGap = _roundInt(getAttrWithFallback(font.info, "openTypeHheaLineGap"))
433        # horizontal metrics
434        widths = []
435        lefts = []
436        rights = []
437        extents = []
438        for glyph in self.allGlyphs.values():
439            left = glyph.leftMargin
440            right = glyph.rightMargin
441            if left is None:
442                left = 0
443            if right is None:
444                right = 0
445            widths.append(glyph.width)
446            lefts.append(left)
447            rights.append(right)
448            # robofab
449            if hasattr(glyph, "box"):
450                bounds = glyph.box
451            # others
452            else:
453                bounds = glyph.bounds
454            if bounds is not None:
455                xMin, yMin, xMax, yMax = bounds
456            else:
457                xMin = 0
458                xMax = 0
459            extent = left + (xMax - xMin) # equation from spec for calculating xMaxExtent: Max(lsb + (xMax - xMin))
460            extents.append(extent)
461        hhea.advanceWidthMax = _roundInt(max(widths))
462        hhea.minLeftSideBearing = _roundInt(min(lefts))
463        hhea.minRightSideBearing = _roundInt(min(rights))
464        hhea.xMaxExtent = _roundInt(max(extents))
465        # misc
466        hhea.caretSlopeRise = getAttrWithFallback(font.info, "openTypeHheaCaretSlopeRise")
467        hhea.caretSlopeRun = getAttrWithFallback(font.info, "openTypeHheaCaretSlopeRun")
468        hhea.caretOffset = _roundInt(getAttrWithFallback(font.info, "openTypeHheaCaretOffset"))
469        hhea.reserved0 = 0
470        hhea.reserved1 = 0
471        hhea.reserved2 = 0
472        hhea.reserved3 = 0
473        hhea.metricDataFormat = 0
474        # glyph count
475        hhea.numberOfHMetrics = len(self.allGlyphs)
476
477    def setupTable_post(self):
478        """
479        Make the post table.
480
481        **This should not be called externally.** Subclasses
482        may override or supplement this method to handle the
483        table creation in a different way if desired.
484        """
485        self.otf["post"] = post = newTable("post")
486        font = self.ufo
487        post.formatType = 3.0
488        # italic angle
489        italicAngle = getAttrWithFallback(font.info, "italicAngle")
490        post.italicAngle = italicAngle
491        # underline
492        underlinePosition = getAttrWithFallback(font.info, "postscriptUnderlinePosition")
493        if underlinePosition is None:
494            underlinePosition = 0
495        post.underlinePosition = _roundInt(underlinePosition)
496        underlineThickness = getAttrWithFallback(font.info, "postscriptUnderlineThickness")
497        if underlineThickness is None:
498            underlineThickness = 0
499        post.underlineThickness = _roundInt(underlineThickness)
500        # determine if the font has a fixed width
501        widths = set([glyph.width for glyph in self.allGlyphs.values()])
502        post.isFixedPitch = getAttrWithFallback(font.info, "postscriptIsFixedPitch")
503        # misc
504        post.minMemType42 = 0
505        post.maxMemType42 = 0
506        post.minMemType1 = 0
507        post.maxMemType1 = 0
508
509    def setupTable_CFF(self):
510        """
511        Make the CFF table.
512
513        **This should not be called externally.** Subclasses
514        may override or supplement this method to handle the
515        table creation in a different way if desired.
516        """
517        self.otf["CFF "] = cff = newTable("CFF ")
518        cff = cff.cff
519        # set up the basics
520        cff.major = 1
521        cff.minor = 0
522        cff.hdrSize = 4
523        cff.offSize = 4
524        cff.fontNames = []
525        strings = IndexedStrings()
526        cff.strings = strings
527        private = PrivateDict(strings=strings)
528        private.rawDict.update(private.defaults)
529        globalSubrs = GlobalSubrsIndex(private=private)
530        topDict = TopDict(GlobalSubrs=globalSubrs, strings=strings)
531        topDict.Private = private
532        charStrings = topDict.CharStrings = CharStrings(file=None, charset=None,
533            globalSubrs=globalSubrs, private=private, fdSelect=None, fdArray=None)
534        charStrings.charStringsAreIndexed = True
535        topDict.charset = []
536        charStringsIndex = charStrings.charStringsIndex = SubrsIndex(private=private, globalSubrs=globalSubrs)
537        cff.topDictIndex = topDictIndex = TopDictIndex()
538        topDictIndex.append(topDict)
539        topDictIndex.strings = strings
540        cff.GlobalSubrs = globalSubrs
541        # populate naming data
542        info = self.ufo.info
543        psName = getAttrWithFallback(info, "postscriptFontName")
544        cff.fontNames.append(psName)
545        topDict = cff.topDictIndex[0]
546        topDict.FullName = getAttrWithFallback(info, "postscriptFullName")
547        topDict.FamilyName = normalizeNameForPostscript(getAttrWithFallback(info, "openTypeNamePreferredFamilyName"))
548        topDict.Weight = getAttrWithFallback(info, "postscriptWeightName")
549        topDict.FontName = getAttrWithFallback(info, "postscriptFontName")
550        # populate font matrix
551        unitsPerEm = _roundInt(getAttrWithFallback(info, "unitsPerEm"))
552        topDict.FontMatrix = [1.0 / unitsPerEm, 0, 0, 1.0 / unitsPerEm, 0, 0]
553        # populate the width values
554        topDict.defaultWidthX = _roundInt(getAttrWithFallback(info, "postscriptDefaultWidthX"))
555        topDict.nominalWidthX = _roundInt(getAttrWithFallback(info, "postscriptNominalWidthX"))
556        # populate hint data
557        blueFuzz = _roundInt(getAttrWithFallback(info, "postscriptBlueFuzz"))
558        blueShift = _roundInt(getAttrWithFallback(info, "postscriptBlueShift"))
559        blueScale = getAttrWithFallback(info, "postscriptBlueScale")
560        forceBold = getAttrWithFallback(info, "postscriptForceBold")
561        blueValues = getAttrWithFallback(info, "postscriptBlueValues")
562        if isinstance(blueValues, list):
563            blueValues = [_roundInt(i) for i in blueValues]
564        otherBlues = getAttrWithFallback(info, "postscriptOtherBlues")
565        if isinstance(otherBlues, list):
566            otherBlues = [_roundInt(i) for i in otherBlues]
567        familyBlues = getAttrWithFallback(info, "postscriptFamilyBlues")
568        if isinstance(familyBlues, list):
569            familyBlues = [_roundInt(i) for i in familyBlues]
570        familyOtherBlues = getAttrWithFallback(info, "postscriptFamilyOtherBlues")
571        if isinstance(familyOtherBlues, list):
572            familyOtherBlues = [_roundInt(i) for i in familyOtherBlues]
573        stemSnapH = getAttrWithFallback(info, "postscriptStemSnapH")
574        if isinstance(stemSnapH, list):
575            stemSnapH = [_roundInt(i) for i in stemSnapH]
576        stemSnapV = getAttrWithFallback(info, "postscriptStemSnapV")
577        if isinstance(stemSnapV, list):
578            stemSnapV = [_roundInt(i) for i in stemSnapV]
579        # only write the blues data if some blues are defined.
580        if (blueValues or otherBlues):
581            private.rawDict["BlueFuzz"] = blueFuzz
582            private.rawDict["BlueShift"] = blueShift
583            private.rawDict["BlueScale"] = blueScale
584            private.rawDict["ForceBold"] = forceBold
585            private.rawDict["BlueValues"] = blueValues
586            private.rawDict["OtherBlues"] = otherBlues
587            private.rawDict["FamilyBlues"] = familyBlues
588            private.rawDict["FamilyOtherBlues"] = familyOtherBlues
589        # only write the stems if both are defined.
590        if (stemSnapH and stemSnapV):
591            private.rawDict["StemSnapH"] = stemSnapH
592            private.rawDict["StdHW"] = stemSnapH[0]
593            private.rawDict["StemSnapV"] = stemSnapV
594            private.rawDict["StdVW"] = stemSnapV[0]
595        # populate glyphs
596        for glyphName in self.glyphOrder:
597            glyph = self.allGlyphs[glyphName]
598            unicodes = glyph.unicodes
599            charString = self.getCharStringForGlyph(glyph, private, globalSubrs)
600            # add to the font
601            exists = charStrings.has_key(glyphName)
602            if exists:
603                # XXX a glyph already has this name. should we choke?
604                glyphID = charStrings.charStrings[glyphName]
605                charStringsIndex.items[glyphID] = charString
606            else:
607                charStringsIndex.append(charString)
608                glyphID = len(topDict.charset)
609                charStrings.charStrings[glyphName] = glyphID
610                topDict.charset.append(glyphName)
611        topDict.FontBBox = self.fontBoundingBox
612        # write the glyph order
613        self.otf.setGlyphOrder(self.glyphOrder)
614
615    def setupOtherTables(self):
616        """
617        Make the other tables. The default implementation does nothing.
618
619        **This should not be called externally.** Subclasses
620        may override this method to add other tables to the
621        font if desired.
622        """
623        pass
624
625
626class StubGlyph(object):
627
628    """
629    This object will be used to create missing glyphs
630    (specifically the space and the .notdef) in the
631    provided UFO.
632    """
633
634    def __init__(self, name, width, unitsPerEm, ascender, descender, unicodes=[]):
635        self.name = name
636        self.width = width
637        self.unitsPerEm = unitsPerEm
638        self.ascender = ascender
639        self.descender = descender
640        self.unicodes = unicodes
641        self.components = []
642        if unicodes:
643            self.unicode = unicodes[0]
644        else:
645            self.unicode = None
646        if name == ".notdef":
647            self.draw = self._drawDefaultNotdef
648
649    def __len__(self):
650        if self.name == ".notdef":
651            return 1
652        return 0
653
654    def _get_leftMargin(self):
655        if self.bounds is None:
656            return 0
657        return self.bounds[0]
658
659    leftMargin = property(_get_leftMargin)
660
661    def _get_rightMargin(self):
662        bounds = self.bounds
663        if bounds is None:
664            return 0
665        xMin, yMin, xMax, yMax = bounds
666        return self.width - bounds[2]
667
668    rightMargin = property(_get_rightMargin)
669
670    def draw(self, pen):
671        pass
672
673    def _drawDefaultNotdef(self, pen):
674        width = int(round(self.unitsPerEm * 0.5))
675        stroke = int(round(self.unitsPerEm * 0.05))
676        ascender = self.ascender
677        descender = self.descender
678        xMin = stroke
679        xMax = width - stroke
680        yMax = ascender
681        yMin = descender
682        pen.moveTo((xMin, yMin))
683        pen.lineTo((xMax, yMin))
684        pen.lineTo((xMax, yMax))
685        pen.lineTo((xMin, yMax))
686        pen.lineTo((xMin, yMin))
687        pen.closePath()
688        xMin += stroke
689        xMax -= stroke
690        yMax -= stroke
691        yMin += stroke
692        pen.moveTo((xMin, yMin))
693        pen.lineTo((xMin, yMax))
694        pen.lineTo((xMax, yMax))
695        pen.lineTo((xMax, yMin))
696        pen.lineTo((xMin, yMin))
697        pen.closePath()
698
699    def _get_bounds(self):
700        from fontTools.pens.boundsPen import BoundsPen
701        pen = BoundsPen(None)
702        self.draw(pen)
703        return pen.bounds
704
705    bounds = property(_get_bounds)
Note: See TracBrowser for help on using the repository browser.