eotlitetool.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. #!/usr/bin/env python
  2. # ***** BEGIN LICENSE BLOCK *****
  3. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4. #
  5. # The contents of this file are subject to the Mozilla Public License Version
  6. # 1.1 (the "License"); you may not use this file except in compliance with
  7. # the License. You may obtain a copy of the License at
  8. # http://www.mozilla.org/MPL/
  9. #
  10. # Software distributed under the License is distributed on an "AS IS" basis,
  11. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. # for the specific language governing rights and limitations under the
  13. # License.
  14. #
  15. # The Original Code is font utility code.
  16. #
  17. # The Initial Developer of the Original Code is Mozilla Corporation.
  18. # Portions created by the Initial Developer are Copyright (C) 2009
  19. # the Initial Developer. All Rights Reserved.
  20. #
  21. # Contributor(s):
  22. # John Daggett <jdaggett@mozilla.com>
  23. #
  24. # Alternatively, the contents of this file may be used under the terms of
  25. # either the GNU General Public License Version 2 or later (the "GPL"), or
  26. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27. # in which case the provisions of the GPL or the LGPL are applicable instead
  28. # of those above. If you wish to allow use of your version of this file only
  29. # under the terms of either the GPL or the LGPL, and not to allow others to
  30. # use your version of this file under the terms of the MPL, indicate your
  31. # decision by deleting the provisions above and replace them with the notice
  32. # and other provisions required by the GPL or the LGPL. If you do not delete
  33. # the provisions above, a recipient may use your version of this file under
  34. # the terms of any one of the MPL, the GPL or the LGPL.
  35. #
  36. # ***** END LICENSE BLOCK ***** */
  37. # eotlitetool.py - create EOT version of OpenType font for use with IE
  38. #
  39. # Usage: eotlitetool.py [-o output-filename] font1 [font2 ...]
  40. #
  41. # OpenType file structure
  42. # http://www.microsoft.com/typography/otspec/otff.htm
  43. #
  44. # Types:
  45. #
  46. # BYTE 8-bit unsigned integer.
  47. # CHAR 8-bit signed integer.
  48. # USHORT 16-bit unsigned integer.
  49. # SHORT 16-bit signed integer.
  50. # ULONG 32-bit unsigned integer.
  51. # Fixed 32-bit signed fixed-point number (16.16)
  52. # LONGDATETIME Date represented in number of seconds since 12:00 midnight, January 1, 1904. The value is represented as a signed 64-bit integer.
  53. #
  54. # SFNT Header
  55. #
  56. # Fixed sfnt version // 0x00010000 for version 1.0.
  57. # USHORT numTables // Number of tables.
  58. # USHORT searchRange // (Maximum power of 2 <= numTables) x 16.
  59. # USHORT entrySelector // Log2(maximum power of 2 <= numTables).
  60. # USHORT rangeShift // NumTables x 16-searchRange.
  61. #
  62. # Table Directory
  63. #
  64. # ULONG tag // 4-byte identifier.
  65. # ULONG checkSum // CheckSum for this table.
  66. # ULONG offset // Offset from beginning of TrueType font file.
  67. # ULONG length // Length of this table.
  68. #
  69. # OS/2 Table (Version 4)
  70. #
  71. # USHORT version // 0x0004
  72. # SHORT xAvgCharWidth
  73. # USHORT usWeightClass
  74. # USHORT usWidthClass
  75. # USHORT fsType
  76. # SHORT ySubscriptXSize
  77. # SHORT ySubscriptYSize
  78. # SHORT ySubscriptXOffset
  79. # SHORT ySubscriptYOffset
  80. # SHORT ySuperscriptXSize
  81. # SHORT ySuperscriptYSize
  82. # SHORT ySuperscriptXOffset
  83. # SHORT ySuperscriptYOffset
  84. # SHORT yStrikeoutSize
  85. # SHORT yStrikeoutPosition
  86. # SHORT sFamilyClass
  87. # BYTE panose[10]
  88. # ULONG ulUnicodeRange1 // Bits 0-31
  89. # ULONG ulUnicodeRange2 // Bits 32-63
  90. # ULONG ulUnicodeRange3 // Bits 64-95
  91. # ULONG ulUnicodeRange4 // Bits 96-127
  92. # CHAR achVendID[4]
  93. # USHORT fsSelection
  94. # USHORT usFirstCharIndex
  95. # USHORT usLastCharIndex
  96. # SHORT sTypoAscender
  97. # SHORT sTypoDescender
  98. # SHORT sTypoLineGap
  99. # USHORT usWinAscent
  100. # USHORT usWinDescent
  101. # ULONG ulCodePageRange1 // Bits 0-31
  102. # ULONG ulCodePageRange2 // Bits 32-63
  103. # SHORT sxHeight
  104. # SHORT sCapHeight
  105. # USHORT usDefaultChar
  106. # USHORT usBreakChar
  107. # USHORT usMaxContext
  108. #
  109. #
  110. # The Naming Table is organized as follows:
  111. #
  112. # [name table header]
  113. # [name records]
  114. # [string data]
  115. #
  116. # Name Table Header
  117. #
  118. # USHORT format // Format selector (=0).
  119. # USHORT count // Number of name records.
  120. # USHORT stringOffset // Offset to start of string storage (from start of table).
  121. #
  122. # Name Record
  123. #
  124. # USHORT platformID // Platform ID.
  125. # USHORT encodingID // Platform-specific encoding ID.
  126. # USHORT languageID // Language ID.
  127. # USHORT nameID // Name ID.
  128. # USHORT length // String length (in bytes).
  129. # USHORT offset // String offset from start of storage area (in bytes).
  130. #
  131. # head Table
  132. #
  133. # Fixed tableVersion // Table version number 0x00010000 for version 1.0.
  134. # Fixed fontRevision // Set by font manufacturer.
  135. # ULONG checkSumAdjustment // To compute: set it to 0, sum the entire font as ULONG, then store 0xB1B0AFBA - sum.
  136. # ULONG magicNumber // Set to 0x5F0F3CF5.
  137. # USHORT flags
  138. # USHORT unitsPerEm // Valid range is from 16 to 16384. This value should be a power of 2 for fonts that have TrueType outlines.
  139. # LONGDATETIME created // Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer
  140. # LONGDATETIME modified // Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer
  141. # SHORT xMin // For all glyph bounding boxes.
  142. # SHORT yMin
  143. # SHORT xMax
  144. # SHORT yMax
  145. # USHORT macStyle
  146. # USHORT lowestRecPPEM // Smallest readable size in pixels.
  147. # SHORT fontDirectionHint
  148. # SHORT indexToLocFormat // 0 for short offsets, 1 for long.
  149. # SHORT glyphDataFormat // 0 for current format.
  150. #
  151. #
  152. #
  153. # Embedded OpenType (EOT) file format
  154. # http://www.w3.org/Submission/EOT/
  155. #
  156. # EOT version 0x00020001
  157. #
  158. # An EOT font consists of a header with the original OpenType font
  159. # appended at the end. Most of the data in the EOT header is simply a
  160. # copy of data from specific tables within the font data. The exceptions
  161. # are the 'Flags' field and the root string name field. The root string
  162. # is a set of names indicating domains for which the font data can be
  163. # used. A null root string implies the font data can be used anywhere.
  164. # The EOT header is in little-endian byte order but the font data remains
  165. # in big-endian order as specified by the OpenType spec.
  166. #
  167. # Overall structure:
  168. #
  169. # [EOT header]
  170. # [EOT name records]
  171. # [font data]
  172. #
  173. # EOT header
  174. #
  175. # ULONG eotSize // Total structure length in bytes (including string and font data)
  176. # ULONG fontDataSize // Length of the OpenType font (FontData) in bytes
  177. # ULONG version // Version number of this format - 0x00020001
  178. # ULONG flags // Processing Flags (0 == no special processing)
  179. # BYTE fontPANOSE[10] // OS/2 Table panose
  180. # BYTE charset // DEFAULT_CHARSET (0x01)
  181. # BYTE italic // 0x01 if ITALIC in OS/2 Table fsSelection is set, 0 otherwise
  182. # ULONG weight // OS/2 Table usWeightClass
  183. # USHORT fsType // OS/2 Table fsType (specifies embedding permission flags)
  184. # USHORT magicNumber // Magic number for EOT file - 0x504C.
  185. # ULONG unicodeRange1 // OS/2 Table ulUnicodeRange1
  186. # ULONG unicodeRange2 // OS/2 Table ulUnicodeRange2
  187. # ULONG unicodeRange3 // OS/2 Table ulUnicodeRange3
  188. # ULONG unicodeRange4 // OS/2 Table ulUnicodeRange4
  189. # ULONG codePageRange1 // OS/2 Table ulCodePageRange1
  190. # ULONG codePageRange2 // OS/2 Table ulCodePageRange2
  191. # ULONG checkSumAdjustment // head Table CheckSumAdjustment
  192. # ULONG reserved[4] // Reserved - must be 0
  193. # USHORT padding1 // Padding - must be 0
  194. #
  195. # EOT name records
  196. #
  197. # USHORT FamilyNameSize // Font family name size in bytes
  198. # BYTE FamilyName[FamilyNameSize] // Font family name (name ID = 1), little-endian UTF-16
  199. # USHORT Padding2 // Padding - must be 0
  200. #
  201. # USHORT StyleNameSize // Style name size in bytes
  202. # BYTE StyleName[StyleNameSize] // Style name (name ID = 2), little-endian UTF-16
  203. # USHORT Padding3 // Padding - must be 0
  204. #
  205. # USHORT VersionNameSize // Version name size in bytes
  206. # bytes VersionName[VersionNameSize] // Version name (name ID = 5), little-endian UTF-16
  207. # USHORT Padding4 // Padding - must be 0
  208. #
  209. # USHORT FullNameSize // Full name size in bytes
  210. # BYTE FullName[FullNameSize] // Full name (name ID = 4), little-endian UTF-16
  211. # USHORT Padding5 // Padding - must be 0
  212. #
  213. # USHORT RootStringSize // Root string size in bytes
  214. # BYTE RootString[RootStringSize] // Root string, little-endian UTF-16
  215. import optparse
  216. import struct
  217. class FontError(Exception):
  218. """Error related to font handling"""
  219. pass
  220. def multichar(str):
  221. vals = struct.unpack('4B', str[:4])
  222. return (vals[0] << 24) + (vals[1] << 16) + (vals[2] << 8) + vals[3]
  223. def multicharval(v):
  224. return struct.pack('4B', (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF)
  225. class EOT:
  226. EOT_VERSION = 0x00020001
  227. EOT_MAGIC_NUMBER = 0x504c
  228. EOT_DEFAULT_CHARSET = 0x01
  229. EOT_FAMILY_NAME_INDEX = 0 # order of names in variable portion of EOT header
  230. EOT_STYLE_NAME_INDEX = 1
  231. EOT_VERSION_NAME_INDEX = 2
  232. EOT_FULL_NAME_INDEX = 3
  233. EOT_NUM_NAMES = 4
  234. EOT_HEADER_PACK = '<4L10B2BL2H7L18x'
  235. class OpenType:
  236. SFNT_CFF = multichar('OTTO') # Postscript CFF SFNT version
  237. SFNT_TRUE = 0x10000 # Standard TrueType version
  238. SFNT_APPLE = multichar('true') # Apple TrueType version
  239. SFNT_UNPACK = '>I4H'
  240. TABLE_DIR_UNPACK = '>4I'
  241. TABLE_HEAD = multichar('head') # TrueType table tags
  242. TABLE_NAME = multichar('name')
  243. TABLE_OS2 = multichar('OS/2')
  244. TABLE_GLYF = multichar('glyf')
  245. TABLE_CFF = multichar('CFF ')
  246. OS2_FSSELECTION_ITALIC = 0x1
  247. OS2_UNPACK = '>4xH2xH22x10B4L4xH14x2L'
  248. HEAD_UNPACK = '>8xL'
  249. NAME_RECORD_UNPACK = '>6H'
  250. NAME_ID_FAMILY = 1
  251. NAME_ID_STYLE = 2
  252. NAME_ID_UNIQUE = 3
  253. NAME_ID_FULL = 4
  254. NAME_ID_VERSION = 5
  255. NAME_ID_POSTSCRIPT = 6
  256. PLATFORM_ID_UNICODE = 0 # Mac OS uses this typically
  257. PLATFORM_ID_MICROSOFT = 3
  258. ENCODING_ID_MICROSOFT_UNICODEBMP = 1 # with Microsoft platformID BMP-only Unicode encoding
  259. LANG_ID_MICROSOFT_EN_US = 0x0409 # with Microsoft platformID EN US lang code
  260. def eotname(ttf):
  261. i = ttf.rfind('.')
  262. if i != -1:
  263. ttf = ttf[:i]
  264. return ttf + '.eotlite'
  265. def readfont(f):
  266. data = open(f, 'rb').read()
  267. return data
  268. def get_table_directory(data):
  269. """read the SFNT header and table directory"""
  270. datalen = len(data)
  271. sfntsize = struct.calcsize(OpenType.SFNT_UNPACK)
  272. if sfntsize > datalen:
  273. raise FontError, 'truncated font data'
  274. sfntvers, numTables = struct.unpack(OpenType.SFNT_UNPACK, data[:sfntsize])[:2]
  275. if sfntvers != OpenType.SFNT_CFF and sfntvers != OpenType.SFNT_TRUE:
  276. raise FontError, 'invalid font type';
  277. font = {}
  278. font['version'] = sfntvers
  279. font['numTables'] = numTables
  280. # create set of offsets, lengths for tables
  281. table_dir_size = struct.calcsize(OpenType.TABLE_DIR_UNPACK)
  282. if sfntsize + table_dir_size * numTables > datalen:
  283. raise FontError, 'truncated font data, table directory extends past end of data'
  284. table_dir = {}
  285. for i in range(0, numTables):
  286. start = sfntsize + i * table_dir_size
  287. end = start + table_dir_size
  288. tag, check, bongo, dirlen = struct.unpack(OpenType.TABLE_DIR_UNPACK, data[start:end])
  289. table_dir[tag] = {'offset': bongo, 'length': dirlen, 'checksum': check}
  290. font['tableDir'] = table_dir
  291. return font
  292. def get_name_records(nametable):
  293. """reads through the name records within name table"""
  294. name = {}
  295. # read the header
  296. headersize = 6
  297. count, strOffset = struct.unpack('>2H', nametable[2:6])
  298. namerecsize = struct.calcsize(OpenType.NAME_RECORD_UNPACK)
  299. if count * namerecsize + headersize > len(nametable):
  300. raise FontError, 'names exceed size of name table'
  301. name['count'] = count
  302. name['strOffset'] = strOffset
  303. # read through the name records
  304. namerecs = {}
  305. for i in range(0, count):
  306. start = headersize + i * namerecsize
  307. end = start + namerecsize
  308. platformID, encodingID, languageID, nameID, namelen, offset = struct.unpack(OpenType.NAME_RECORD_UNPACK, nametable[start:end])
  309. if platformID != OpenType.PLATFORM_ID_MICROSOFT or \
  310. encodingID != OpenType.ENCODING_ID_MICROSOFT_UNICODEBMP or \
  311. languageID != OpenType.LANG_ID_MICROSOFT_EN_US:
  312. continue
  313. namerecs[nameID] = {'offset': offset, 'length': namelen}
  314. name['namerecords'] = namerecs
  315. return name
  316. def make_eot_name_headers(fontdata, nameTableDir):
  317. """extracts names from the name table and generates the names header portion of the EOT header"""
  318. nameoffset = nameTableDir['offset']
  319. namelen = nameTableDir['length']
  320. name = get_name_records(fontdata[nameoffset : nameoffset + namelen])
  321. namestroffset = name['strOffset']
  322. namerecs = name['namerecords']
  323. eotnames = (OpenType.NAME_ID_FAMILY, OpenType.NAME_ID_STYLE, OpenType.NAME_ID_VERSION, OpenType.NAME_ID_FULL)
  324. nameheaders = []
  325. for nameid in eotnames:
  326. if nameid in namerecs:
  327. namerecord = namerecs[nameid]
  328. noffset = namerecord['offset']
  329. nlen = namerecord['length']
  330. nformat = '%dH' % (nlen / 2) # length is in number of bytes
  331. start = nameoffset + namestroffset + noffset
  332. end = start + nlen
  333. nstr = struct.unpack('>' + nformat, fontdata[start:end])
  334. nameheaders.append(struct.pack('<H' + nformat + '2x', nlen, *nstr))
  335. else:
  336. nameheaders.append(struct.pack('4x')) # len = 0, padding = 0
  337. return ''.join(nameheaders)
  338. # just return a null-string (len = 0)
  339. def make_root_string():
  340. return struct.pack('2x')
  341. def make_eot_header(fontdata):
  342. """given ttf font data produce an EOT header"""
  343. fontDataSize = len(fontdata)
  344. font = get_table_directory(fontdata)
  345. # toss out .otf fonts, t2embed library doesn't support these
  346. tableDir = font['tableDir']
  347. # check for required tables
  348. required = (OpenType.TABLE_HEAD, OpenType.TABLE_NAME, OpenType.TABLE_OS2)
  349. for table in required:
  350. if not (table in tableDir):
  351. raise FontError, 'missing required table ' + multicharval(table)
  352. # read name strings
  353. # pull out data from individual tables to construct fixed header portion
  354. # need to calculate eotSize before packing
  355. version = EOT.EOT_VERSION
  356. flags = 0
  357. charset = EOT.EOT_DEFAULT_CHARSET
  358. magicNumber = EOT.EOT_MAGIC_NUMBER
  359. # read values from OS/2 table
  360. os2Dir = tableDir[OpenType.TABLE_OS2]
  361. os2offset = os2Dir['offset']
  362. os2size = struct.calcsize(OpenType.OS2_UNPACK)
  363. if os2size > os2Dir['length']:
  364. raise FontError, 'OS/2 table invalid length'
  365. os2fields = struct.unpack(OpenType.OS2_UNPACK, fontdata[os2offset : os2offset + os2size])
  366. panose = []
  367. urange = []
  368. codepage = []
  369. weight, fsType = os2fields[:2]
  370. panose[:10] = os2fields[2:12]
  371. urange[:4] = os2fields[12:16]
  372. fsSelection = os2fields[16]
  373. codepage[:2] = os2fields[17:19]
  374. italic = fsSelection & OpenType.OS2_FSSELECTION_ITALIC
  375. # read in values from head table
  376. headDir = tableDir[OpenType.TABLE_HEAD]
  377. headoffset = headDir['offset']
  378. headsize = struct.calcsize(OpenType.HEAD_UNPACK)
  379. if headsize > headDir['length']:
  380. raise FontError, 'head table invalid length'
  381. headfields = struct.unpack(OpenType.HEAD_UNPACK, fontdata[headoffset : headoffset + headsize])
  382. checkSumAdjustment = headfields[0]
  383. # make name headers
  384. nameheaders = make_eot_name_headers(fontdata, tableDir[OpenType.TABLE_NAME])
  385. rootstring = make_root_string()
  386. # calculate the total eot size
  387. eotSize = struct.calcsize(EOT.EOT_HEADER_PACK) + len(nameheaders) + len(rootstring) + fontDataSize
  388. fixed = struct.pack(EOT.EOT_HEADER_PACK,
  389. *([eotSize, fontDataSize, version, flags] + panose + [charset, italic] +
  390. [weight, fsType, magicNumber] + urange + codepage + [checkSumAdjustment]))
  391. return ''.join((fixed, nameheaders, rootstring))
  392. def write_eot_font(eot, header, data):
  393. open(eot,'wb').write(''.join((header, data)))
  394. return
  395. def main():
  396. # deal with options
  397. p = optparse.OptionParser()
  398. p.add_option('--output', '-o', default="world")
  399. options, args = p.parse_args()
  400. # iterate over font files
  401. for f in args:
  402. data = readfont(f)
  403. if len(data) == 0:
  404. print 'Error reading %s' % f
  405. else:
  406. eot = eotname(f)
  407. header = make_eot_header(data)
  408. write_eot_font(eot, header, data)
  409. if __name__ == '__main__':
  410. main()