| 1 | from __future__ import division |
|---|
| 2 | import time |
|---|
| 3 | from fontTools.ttLib import TTFont, newTable |
|---|
| 4 | from fontTools.cffLib import TopDictIndex, TopDict, CharStrings, SubrsIndex, GlobalSubrsIndex, PrivateDict, IndexedStrings |
|---|
| 5 | from fontTools.ttLib.tables.O_S_2f_2 import Panose |
|---|
| 6 | from fontTools.ttLib.tables._h_e_a_d import mac_epoch_diff |
|---|
| 7 | from pens.t2CharStringPen import T2CharStringPen |
|---|
| 8 | from fontInfoData import getFontBounds, getAttrWithFallback, dateStringToTimeValue, dateStringForNow, intListToNum, normalizeNameForPostscript |
|---|
| 9 | try: |
|---|
| 10 | set |
|---|
| 11 | except NameError: |
|---|
| 12 | from sets import Set as set |
|---|
| 13 | try: |
|---|
| 14 | sorted |
|---|
| 15 | except NameError: |
|---|
| 16 | def sorted(l): |
|---|
| 17 | l = list(l) |
|---|
| 18 | l.sort() |
|---|
| 19 | return l |
|---|
| 20 | |
|---|
| 21 | def _roundInt(v): |
|---|
| 22 | return int(round(v)) |
|---|
| 23 | |
|---|
| 24 | |
|---|
| 25 | class 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 | |
|---|
| 626 | class 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) |
|---|