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
)