fix typo: ItalicAngles -> ItalicAngle (thanks to Ross Moore)
[PyX.git] / pyx / font / pfmfile.py
blob363033b2f5aa08b1cd55555cb9c5dd5e0742440e
1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2007-2011 André Wobst <wobsta@users.sourceforge.net>
6 # This file is part of PyX (http://pyx.sourceforge.net/).
8 # PyX is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # PyX is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with PyX; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 import struct, re
23 from . import metric
26 ansiglyphs = {"space": 32,
27 "exclam": 33,
28 "quotedbl": 34,
29 "numbersign": 35,
30 "dollar": 36,
31 "percent": 37,
32 "ampersand": 38,
33 "quotesingle": 39,
34 "parenleft": 40,
35 "parenright": 41,
36 "asterisk": 42,
37 "plus": 43,
38 "comma": 44,
39 "hyphen": 45,
40 "period": 46,
41 "slash": 47,
42 "zero": 48,
43 "one": 49,
44 "two": 50,
45 "three": 51,
46 "four": 52,
47 "five": 53,
48 "six": 54,
49 "seven": 55,
50 "eight": 56,
51 "nine": 57,
52 "colon": 58,
53 "semicolon": 59,
54 "less": 60,
55 "equal": 61,
56 "greater": 62,
57 "question": 63,
58 "at": 64,
59 "A": 65,
60 "B": 66,
61 "C": 67,
62 "D": 68,
63 "E": 69,
64 "F": 70,
65 "G": 71,
66 "H": 72,
67 "I": 73,
68 "J": 74,
69 "K": 75,
70 "L": 76,
71 "M": 77,
72 "N": 78,
73 "O": 79,
74 "P": 80,
75 "Q": 81,
76 "R": 82,
77 "S": 83,
78 "T": 84,
79 "U": 85,
80 "V": 86,
81 "W": 87,
82 "X": 88,
83 "Y": 89,
84 "Z": 90,
85 "bracketleft": 91,
86 "backslash": 92,
87 "bracketright": 93,
88 "asciicircum": 94,
89 "underscore": 95,
90 "grave": 96,
91 "a": 97,
92 "b": 98,
93 "c": 99,
94 "d": 100,
95 "e":101,
96 "f":102,
97 "g":103,
98 "h":104,
99 "i":105,
100 "j":106,
101 "k":107,
102 "l":108,
103 "m":109,
104 "n":110,
105 "o":111,
106 "p":112,
107 "q":113,
108 "r":114,
109 "s":115,
110 "t":116,
111 "u":117,
112 "v":118,
113 "w":119,
114 "x":120,
115 "y":121,
116 "z":122,
117 "braceleft":123,
118 "bar":124,
119 "braceright":125,
120 "asciitilde":126,
121 "bullet":127,
122 "Euro":128,
123 "bullet":129,
124 "quotesinglbase":130,
125 "florin":131,
126 "quotedblbase":132,
127 "ellipsis":133,
128 "dagger":134,
129 "daggerdbl":135,
130 "circumflex":136,
131 "perthousand":137,
132 "Scaron":138,
133 "guilsinglleft":139,
134 "OE":140,
135 "bullet":141,
136 "Zcaron":142,
137 "bullet":143,
138 "bullet":144,
139 "quoteleft":145,
140 "quoteright":146,
141 "quotedblleft":147,
142 "quotedblright":148,
143 "bullet":149,
144 "endash":150,
145 "emdash":151,
146 "tilde":152,
147 "trademark":153,
148 "scaron":154,
149 "guilsinglright":155,
150 "oe":156,
151 "bullet":157,
152 "zcaron":158,
153 "Ydieresis":159,
154 "space":160,
155 "exclamdown":161,
156 "cent":162,
157 "sterling":163,
158 "currency":164,
159 "yen":165,
160 "brokenbar":166,
161 "section":167,
162 "dieresis":168,
163 "copyright":169,
164 "ordfeminine":170,
165 "guillemotleft":171,
166 "logicalnot":172,
167 "hyphen":173,
168 "registered":174,
169 "macron":175,
170 "degree":176,
171 "plusminus":177,
172 "twosuperior":178,
173 "threesuperior":179,
174 "acute":180,
175 "mu":181,
176 "paragraph":182,
177 "periodcentered":183,
178 "cedilla":184,
179 "onesuperior":185,
180 "ordmasculine":186,
181 "guillemotright":187,
182 "onequarter":188,
183 "onehalf":189,
184 "threequarters":190,
185 "questiondown":191,
186 "Agrave":192,
187 "Aacute":193,
188 "Acircumflex":194,
189 "Atilde":195,
190 "Adieresis":196,
191 "Aring":197,
192 "AE":198,
193 "Ccedilla":199,
194 "Egrave":200,
195 "Eacute":201,
196 "Ecircumflex":202,
197 "Edieresis":203,
198 "Igrave":204,
199 "Iacute":205,
200 "Icircumflex":206,
201 "Idieresis":207,
202 "Eth":208,
203 "Ntilde":209,
204 "Ograve":210,
205 "Oacute":211,
206 "Ocircumflex":212,
207 "Otilde":213,
208 "Odieresis":214,
209 "multiply":215,
210 "Oslash":216,
211 "Ugrave":217,
212 "Uacute":218,
213 "Ucircumflex":219,
214 "Udieresis":220,
215 "Yacute":221,
216 "Thorn":222,
217 "germandbls":223,
218 "agrave":224,
219 "aacute":225,
220 "acircumflex":226,
221 "atilde":227,
222 "adieresis":228,
223 "aring":229,
224 "ae":230,
225 "ccedilla":231,
226 "egrave":232,
227 "eacute":233,
228 "ecircumflex":234,
229 "edieresis":235,
230 "igrave":236,
231 "iacute":237,
232 "icircumflex":238,
233 "idieresis":239,
234 "eth":240,
235 "ntilde":241,
236 "ograve":242,
237 "oacute":243,
238 "ocircumflex":244,
239 "otilde":245,
240 "odieresis":246,
241 "divide":247,
242 "oslash":248,
243 "ugrave":249,
244 "uacute":250,
245 "ucircumflex":251,
246 "udieresis":252,
247 "yacute":253,
248 "thorn":254,
249 "ydieresis":255}
252 fontbboxpattern = re.compile("/FontBBox\s*\{\s*(?P<fontbbox>(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+))\s*\}\s*(readonly\s+)?def")
255 def _readNullString(file):
256 s = []
257 c = file.read(1)
258 while c and c != "\0":
259 s.append(c)
260 c = file.read(1)
261 return "".join(s)
264 class PFMfile(metric.metric):
266 def __init__(self, file, t1file):
267 # pfm is rather incomplete, the t1file instance can be used to fill the gap
268 (self.dfVersion, self.dfSize, self.dfCopyright, self.dfType,
269 self.dfPoint, self.dfVertRes, self.dfHorizRes, self.dfAscent,
270 self.dfInternalLeading, self.dfExternalLeading, self.dfItalic,
271 self.dfUnderline, self.dfStrikeOut, self.dfWeight,
272 self.dfCharSet, self.dfPixWidth, self.dfPixHeight,
273 self.dfPitchAndFamily, self.dfAvgWidth, self.dfMaxWidth,
274 self.dfFirstChar, self.dfLastChar, self.dfDefaultChar,
275 self.dfBreakChar, self.dfWidthBytes, self.dfDevice, self.dfFace,
276 self.dfBitsPointer, self.dfBitsOffset) = struct.unpack("<HL60s7H3BHB2HB2H4BH4L", file.read(117))
277 self.dfCopyright = self.dfCopyright.split("\000", 1)[0]
278 (self.dfSizeFields, self.dfExtMetricsOffset, self.dfExtentTable,
279 self.dfOriginTable, self.dfPairKernTable, self.dfTrackKernTable,
280 self.dfDriverInfo, self.dfReserved) = struct.unpack("<H7L", file.read(30))
281 if self.dfDevice == 0:
282 raise ValueError("DeviceName is required for Type1 pfm files.")
283 file.seek(self.dfDevice)
284 self.deviceName = _readNullString(file)
285 if self.deviceName.lower() != "postscript":
286 raise ValueError("Can process pfm files for PostScript fonts only.")
287 if self.dfVersion != 0x100:
288 raise ValueError("Invalid pfm file version.")
289 if self.dfType != 0x81:
290 raise ValueError("Not a Type1 pfm file.")
291 if self.dfFace == 0:
292 raise ValueError("FaceName is required for Type1 pfm files.")
293 if self.dfExtMetricsOffset == 0:
294 raise ValueError("ExtTextMetrics is required for Type1 pfm files.")
295 if self.dfExtentTable == 0:
296 raise ValueError("ExtentTable is required for Type1 pfm files.")
297 if self.dfOriginTable != 0:
298 raise ValueError("OriginTable is forbidden for Type1 pfm files.")
299 if self.dfDriverInfo == 0:
300 raise ValueError("DriverInfo is required for Type1 pfm files.")
301 # assert self.dfReserved == 0 (must be zero according to the spec, but we don't care)
302 file.seek(self.dfExtMetricsOffset)
303 (etmSize, self.etmPointSize, self.etmOrientation,
304 self.etmMasterHeight, self.etmMinScale, self.etmMaxScale,
305 self.etmMasterUnits, self.etmCapHeight, self.etmXHeight,
306 self.etmLowerCaseAscent, self.etmLowerCaseDescent, self.etmSlant,
307 self.etmSuperScript, self.etmSubScript, self.etmSuperScriptSize,
308 self.etmSubScriptSize, self.etmUnderlineOffset,
309 self.etmUnderlineWidth, self.etmDoubleUpperUnderlineOffset,
310 self.etmDoubleLowerUnderlineOffset, self.etmDoubleUpperUnderlineWidth,
311 self.etmDoubleLowerUnderlineWidth, self.etmStrikeOutOffset,
312 self.etmStrikeOutWidth, self.etmKernPairs, self.etmKernTracks) = struct.unpack("<24h2H", file.read(52))
313 file.seek(self.dfFace)
314 self.faceName = _readNullString(file)
315 file.seek(self.dfDriverInfo)
316 self.driverInfo = _readNullString(file)
317 file.seek(self.dfExtentTable)
318 count = self.dfLastChar - self.dfFirstChar + 1
319 self.widths = struct.unpack("<%dH" % count, file.read(2*count))
320 self.kernpairs = []
321 self.kernpairsdict = {}
322 if self.dfPairKernTable:
323 file.seek(self.dfPairKernTable)
324 pairs, = struct.unpack("<H", file.read(2))
325 if pairs != self.etmKernPairs:
326 raise ValueError("number of kerning pairs mismatch in pfm file.")
327 for i in range(self.etmKernPairs):
328 kpFirst, kpSecond, kpKernAmount = struct.unpack("<BBh", file.read(4))
329 self.kernpairs.append((kpFirst, kpSecond, kpKernAmount))
330 self.kernpairsdict[(kpFirst, kpSecond)] = kpKernAmount
331 self.trackkerns = []
332 if self.dfTrackKernTable:
333 file.seek(self.dfTrackKernTable)
334 items, = struct.unpack("<H", file.read(2))
335 if items != self.etmKernTracks:
336 raise ValueError("number of kerning tracks mismatch in pfm file.")
337 for i in range(self.etmKernTracks):
338 # each item consists of the tuple ktDegree, ktMinSize, ktMinAmount, ktMaxSize, ktMaxAmount
339 self.trackkerns.append(struct.unpack("<hhhhh", file.read(10)))
340 if self.dfCharSet:
341 if not t1file.encoding:
342 t1file._encoding()
343 self.glyphs = dict([(glyph, i) for i, glyph in enumerate(t1file.encoding) if glyph != None])
344 else:
345 self.glyphs = ansiglyphs
346 self.t1file = t1file
348 def width_ds(self, glyphname):
349 return self.widths[self.glyphs[glyphname]-self.dfFirstChar]
351 def width_pt(self, glyphnames, size_pt):
352 return sum([self.widths[self.glyphs[glyphname]-self.dfFirstChar] for glyphname in glyphnames])*size_pt/1000.0
354 def height_pt(self, glyphnames, size_pt):
355 return self.dfAscent*size_pt/1000.0
357 def depth_pt(self, glyphnames, size_pt):
358 return -self.etmLowerCaseDescent*size_pt/1000.0
360 def resolvekernings(self, glyphnames, size_pt=None):
361 result = [None]*(2*len(glyphnames)-1)
362 for i, glyphname in enumerate(glyphnames):
363 result[2*i] = glyphname
364 return result
366 def resolvekernings(self, glyphnames, size_pt=None):
367 result = [None]*(2*len(glyphnames)-1)
368 for i, glyphname in enumerate(glyphnames):
369 result[2*i] = glyphname
370 if i:
371 amount = self.kernpairsdict.get((self.glyphs[glyphnames[i-1]], self.glyphs[glyphname]))
372 if amount:
373 if size_pt is not None:
374 result[2*i-1] = amount*size_pt/1000.0
375 else:
376 result[2*i-1] = amount
377 return result
379 def writePDFfontinfo(self, file, seriffont=False, symbolfont=True):
380 flags = 0
381 if self.dfMaxWidth == self.dfAvgWidth:
382 flags += 1<<0
383 if seriffont:
384 flags += 1<<1
385 if symbolfont:
386 flags += 1<<2
387 else:
388 flags += 1<<5
389 if self.dfItalic:
390 flags += 1<<6
391 file.write("/Flags %d\n" % flags)
392 file.write("/ItalicAngle %d\n" % (self.etmSlant/10))
393 file.write("/Ascent %d\n" % self.dfAscent)
394 file.write("/Descent %d\n" % -self.etmLowerCaseDescent)
395 file.write("/FontBBox [%s]\n" % fontbboxpattern.search(self.t1file.data1).groups('fontbbox')[0])
396 file.write("/CapHeight %d\n" % self.etmCapHeight)
397 if self.dfWeight >= 600:
398 stemv = 120
399 else:
400 stemv = 70
401 file.write("/StemV %d\n" % stemv)