add support for pfm font matrices
[PyX.git] / pyx / font / pfmfile.py
blob5d760a1d926b36d9bbdfe3d0bef3ea5b0b5ca534
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
23 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}
251 def _readNullString(file):
252 s = []
253 c = file.read(1)
254 while c and c != "\0":
255 s.append(c)
256 c = file.read(1)
257 return "".join(s)
260 class PFMfile(metric.metric):
262 def __init__(self, file):
263 (self.dfVersion, self.dfSize, self.dfCopyright, self.dfType,
264 self.dfPoint, self.dfVertRes, self.dfHorizRes, self.dfAscent,
265 self.dfInternalLeading, self.dfExternalLeading, self.dfItalic,
266 self.dfUnderline, self.dfStrikeOut, self.dfWeight,
267 self.dfCharSet, self.dfPixWidth, self.dfPixHeight,
268 self.dfPitchAndFamily, self.dfAvgWidth, self.dfMaxWidth,
269 self.dfFirstChar, self.dfLastChar, self.dfDefaultChar,
270 self.dfBreakChar, self.dfWidthBytes, self.dfDevice, self.dfFace,
271 self.dfBitsPointer, self.dfBitsOffset) = struct.unpack("<HL60s7H3BHB2HB2H4BH4L", file.read(117))
272 self.dfCopyright = self.dfCopyright.split("\000", 1)[0]
273 (self.dfSizeFields, self.dfExtMetricsOffset, self.dfExtentTable,
274 self.dfOriginTable, self.dfPairKernTable, self.dfTrackKernTable,
275 self.dfDriverInfo, self.dfReserved) = struct.unpack("<H7L", file.read(30))
276 if self.dfDevice == 0:
277 raise ValueError("DeviceName is required for Type1 pfm files.")
278 file.seek(self.dfDevice)
279 self.deviceName = _readNullString(file)
280 if self.deviceName.lower() != "postscript":
281 raise ValueError("Can process pfm files for PostScript fonts only.")
282 if self.dfVersion != 0x100:
283 raise ValueError("Invalid pfm file version.")
284 if self.dfType != 0x81:
285 raise ValueError("Not a Type1 pfm file.")
286 if self.dfFace == 0:
287 raise ValueError("FaceName is required for Type1 pfm files.")
288 if self.dfExtMetricsOffset == 0:
289 raise ValueError("ExtTextMetrics is required for Type1 pfm files.")
290 if self.dfExtentTable == 0:
291 raise ValueError("ExtentTable is required for Type1 pfm files.")
292 if self.dfOriginTable != 0:
293 raise ValueError("OriginTable is forbidden for Type1 pfm files.")
294 if self.dfDriverInfo == 0:
295 raise ValueError("DriverInfo is required for Type1 pfm files.")
296 # assert self.dfReserved == 0 (must be zero according to the spec, but we don't care)
297 file.seek(self.dfExtMetricsOffset)
298 (etmSize, self.etmPointSize, self.etmOrientation,
299 self.etmMasterHeight, self.etmMinScale, self.etmMaxScale,
300 self.etmMasterUnits, self.etmCapHeight, self.etmXHeight,
301 self.etmLowerCaseAscent, self.etmLowerCaseDescent, self.etmSlant,
302 self.etmSuperScript, self.etmSubScript, self.etmSuperScriptSize,
303 self.etmSubScriptSize, self.etmUnderlineOffset,
304 self.etmUnderlineWidth, self.etmDoubleUpperUnderlineOffset,
305 self.etmDoubleLowerUnderlineOffset, self.etmDoubleUpperUnderlineWidth,
306 self.etmDoubleLowerUnderlineWidth, self.etmStrikeOutOffset,
307 self.etmStrikeOutWidth, self.etmKernPairs, self.etmKernTracks) = struct.unpack("<24h2H", file.read(52))
308 print self.etmMasterHeight, self.etmMasterUnits, self.etmPointSize, self.etmOrientation
309 file.seek(self.dfFace)
310 self.faceName = _readNullString(file)
311 file.seek(self.dfDriverInfo)
312 self.driverInfo = _readNullString(file)
313 file.seek(self.dfExtentTable)
314 count = self.dfLastChar - self.dfFirstChar + 1
315 self.widths = struct.unpack("<%dH" % count, file.read(2*count))
316 self.kernpairs = []
317 self.kernpairsdict = {}
318 if self.dfPairKernTable:
319 file.seek(self.dfPairKernTable)
320 pairs, = struct.unpack("<H", file.read(2))
321 if pairs != self.etmKernPairs:
322 raise ValueError("number of kerning pairs mismatch in pfm file.")
323 for i in range(self.etmKernPairs):
324 kpFirst, kpSecond, kpKernAmount = struct.unpack("<BBh", file.read(4))
325 self.kernpairs.append((kpFirst, kpSecond, kpKernAmount))
326 self.kernpairsdict[(kpFirst, kpSecond)] = kpKernAmount
327 self.trackkerns = []
328 if self.dfTrackKernTable:
329 file.seek(self.dfTrackKernTable)
330 items, = struct.unpack("<H", file.read(2))
331 if items != self.etmKernTracks:
332 raise ValueError("number of kerning tracks mismatch in pfm file.")
333 for i in range(self.etmKernTracks):
334 # each item consists of the tuple ktDegree, ktMinSize, ktMinAmount, ktMaxSize, ktMaxAmount
335 self.trackkerns.append(struct.unpack("<hhhhh", file.read(10)))
336 if self.dfCharSet:
337 raise ValueError("PFM font matrices are currently supported for ansi encoded fonts only.")
339 def width_ds(self, glyphname):
340 return self.widths[ansiglyphs[glyphname]-self.dfFirstChar]
342 def width_pt(self, glyphnames, size_pt):
343 return sum([self.widths[ansiglyphs[glyphname]-self.dfFirstChar] for glyphname in glyphnames])*size_pt/1000.0
345 def height_pt(self, glyphnames, size_pt):
346 return self.dfAscent*size_pt/1000.0
348 def depth_pt(self, glyphnames, size_pt):
349 return -self.etmLowerCaseDescent*size_pt/1000.0
351 def resolvekernings(self, glyphnames, size_pt=None):
352 result = [None]*(2*len(glyphnames)-1)
353 for i, glyphname in enumerate(glyphnames):
354 result[2*i] = glyphname
355 return result
357 def resolvekernings(self, glyphnames, size_pt=None):
358 result = [None]*(2*len(glyphnames)-1)
359 for i, glyphname in enumerate(glyphnames):
360 result[2*i] = glyphname
361 if i:
362 amount = self.kernpairsdict.get((ansiglyphs[glyphnames[i-1]], ansiglyphs[glyphname]))
363 if amount:
364 if size_pt is not None:
365 result[2*i-1] = amount*size_pt/1000.0
366 else:
367 result[2*i-1] = amount
368 return result
371 def writePDFfontinfo(self, file, seriffont=False, symbolfont=True):
372 flags = 0
373 if self.dfMaxWidth == self.dfAvgWidth:
374 flags += 1<<0
375 if seriffont:
376 flags += 1<<1
377 if symbolfont:
378 flags += 1<<2
379 else:
380 flags += 1<<5
381 if self.dfItalic:
382 flags += 1<<6
383 file.write("/Flags %d\n" % flags)
384 file.write("/ItalicAngles %d\n" % (self.etmSlant/10))
385 file.write("/Ascent %d\n" % self.dfAscent)
386 file.write("/Descent %d\n" % self.etmLowerCaseDescent)
387 file.write("/FontBBox [0 %d 1000 %d]\n" % (self.etmLowerCaseDescent, self.dfAscent))
388 file.write("/CapHeight %d\n" % self.dfAscent)
389 if self.dfWeight >= 600:
390 stemv = 120
391 else:
392 stemv = 70
393 file.write("/StemV %d\n" % stemv)