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
26
ansiglyphs = {"space": 32,
124
"quotesinglbase":130,
149
"guilsinglright":155,
177
"periodcentered":183,
181
"guillemotright":187,
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):
258
while c and c != "\0":
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.")
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))
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
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)))
341
if not t1file.encoding:
343
self.glyphs = dict([(glyph, i) for i, glyph in enumerate(t1file.encoding) if glyph != None])
345
self.glyphs = ansiglyphs
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
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
371
amount = self.kernpairsdict.get((self.glyphs[glyphnames[i-1]], self.glyphs[glyphname]))
373
if size_pt is not None:
374
result[2*i-1] = amount*size_pt/1000.0
376
result[2*i-1] = amount
379
def writePDFfontinfo(self, file, seriffont=False, symbolfont=True):
381
if self.dfMaxWidth == self.dfAvgWidth:
391
file.write("/Flags %d\n" % flags)
392
file.write("/ItalicAngles %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:
401
file.write("/StemV %d\n" % stemv)