use pfm as a fallback of afm font metrices to avoid guessing of font parameters whene...
[PyX/mjg.git] / pyx / dvi / texfont.py
blobc3d05b76c239bfdee16da32772552dcd0f7e4f89
1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2007-2011 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2007-2011 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 from pyx import bbox, canvasitem, font, filelocator
25 import tfmfile, vffile
27 class TeXFontError(Exception): pass
29 class TeXfont:
31 def __init__(self, name, c, q, d, tfmconv, pyxconv, debug=0):
32 self.name = name
33 self.q = q # desired size of font (fix_word) in TeX points
34 self.d = d # design size of font (fix_word) in TeX points
35 self.tfmconv = tfmconv # conversion factor from tfm units to dvi units
36 self.pyxconv = pyxconv # conversion factor from dvi units to PostScript points
37 file = filelocator.open(self.name, [filelocator.format.tfm], "rb")
38 self.TFMfile = tfmfile.TFMfile(file, debug)
39 file.close()
41 # We only check for equality of font checksums if none of them
42 # is zero. The case c == 0 happend in some VF files and
43 # according to the VFtoVP documentation, paragraph 40, a check
44 # is only performed if TFMfile.checksum > 0. Anyhow, being
45 # more generous here seems to be reasonable
46 if self.TFMfile.checksum != c and self.TFMfile.checksum != 0 and c != 0:
47 raise TeXFontError("check sums do not agree: %d vs. %d" %
48 (self.TFMfile.checksum, c))
50 # Check whether the given design size matches the one defined in the tfm file
51 if abs(self.TFMfile.designsize - d) > 4: # XXX: why the deviation?
52 raise TeXFontError("design sizes do not agree: %d vs. %d" % (self.TFMfile.designsize, d))
53 #if q < 0 or q > 134217728:
54 # raise TeXFontError("font '%s' not loaded: bad scale" % self.name)
55 if d < 0 or d > 134217728:
56 raise TeXFontError("font '%s' not loaded: bad design size" % self.name)
58 def __str__(self):
59 return "font %s designed at %g TeX pts used at %g TeX pts" % (self.name,
60 16.0*self.d/16777216L,
61 16.0*self.q/16777216L)
63 def getsize_pt(self):
64 """ return size of font in (PS) points """
65 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
66 # to the corresponding float. Furthermore, we have to convert from TeX
67 # points to points, hence the factor 72/72.27.
68 return 72/72.27 * 16*self.q/16777216
70 def _convert_tfm_to_dvi(self, length):
71 # doing the integer math with long integers will lead to different roundings
72 # return 16*length*int(round(self.q*self.tfmconv))/16777216
74 # Knuth instead suggests the following algorithm based on 4 byte integer logic only
75 # z = int(round(self.q*self.tfmconv))
76 # b0, b1, b2, b3 = [ord(c) for c in struct.pack(">L", length)]
77 # assert b0 == 0 or b0 == 255
78 # shift = 4
79 # while z >= 8388608:
80 # z >>= 1
81 # shift -= 1
82 # assert shift >= 0
83 # result = ( ( ( ( ( b3 * z ) >> 8 ) + ( b2 * z ) ) >> 8 ) + ( b1 * z ) ) >> shift
84 # if b0 == 255:
85 # result = result - (z << (8-shift))
87 # however, we can simplify this using a single long integer multiplication,
88 # but take into account the transformation of z
89 z = int(round(self.q*self.tfmconv))
90 assert -16777216 <= length < 16777216 # -(1 << 24) <= length < (1 << 24)
91 assert z < 134217728 # 1 << 27
92 shift = 20 # 1 << 20
93 while z >= 8388608: # 1 << 23
94 z >>= 1
95 shift -= 1
96 # length*z is a long integer, but the result will be a regular integer
97 return int(length*long(z) >> shift)
99 def _convert_tfm_to_pt(self, length):
100 return (16*long(round(length*float(self.q)*self.tfmconv))/16777216) * self.pyxconv
102 # routines returning lengths as integers in dvi units
104 def getwidth_dvi(self, charcode):
105 return self._convert_tfm_to_dvi(self.TFMfile.width[self.TFMfile.char_info[charcode].width_index])
107 def getheight_dvi(self, charcode):
108 return self._convert_tfm_to_dvi(self.TFMfile.height[self.TFMfile.char_info[charcode].height_index])
110 def getdepth_dvi(self, charcode):
111 return self._convert_tfm_to_dvi(self.TFMfile.depth[self.TFMfile.char_info[charcode].depth_index])
113 def getitalic_dvi(self, charcode):
114 return self._convert_tfm_to_dvi(self.TFMfile.italic[self.TFMfile.char_info[charcode].italic_index])
116 # routines returning lengths as floats in PostScript points
118 def getwidth_pt(self, charcode):
119 return self._convert_tfm_to_pt(self.TFMfile.width[self.TFMfile.char_info[charcode].width_index])
121 def getheight_pt(self, charcode):
122 return self._convert_tfm_to_pt(self.TFMfile.height[self.TFMfile.char_info[charcode].height_index])
124 def getdepth_pt(self, charcode):
125 return self._convert_tfm_to_pt(self.TFMfile.depth[self.TFMfile.char_info[charcode].depth_index])
127 def getitalic_pt(self, charcode):
128 return self._convert_tfm_to_pt(self.TFMfile.italic[self.TFMfile.char_info[charcode].italic_index])
130 def text_pt(self, x_pt, y_pt, charcodes, fontmap=None):
131 return TeXtext_pt(self, x_pt, y_pt, charcodes, self.getsize_pt(), fontmap=fontmap)
133 def getMAPline(self, fontmap):
134 if self.name not in fontmap:
135 raise RuntimeError("missing font information for '%s'; check fontmapping file(s)" % self.name)
136 return fontmap[self.name]
139 class virtualfont(TeXfont):
141 def __init__(self, name, file, c, q, d, tfmconv, pyxconv, debug=0):
142 TeXfont.__init__(self, name, c, q, d, tfmconv, pyxconv, debug)
143 self.vffile = vffile.vffile(file, 1.0*q/d, tfmconv, pyxconv, debug > 1)
145 def getfonts(self):
146 """ return fonts used in virtual font itself """
147 return self.vffile.getfonts()
149 def getchar(self, cc):
150 """ return dvi chunk corresponding to char code cc """
151 return self.vffile.getchar(cc)
153 def text_pt(self, *args, **kwargs):
154 raise RuntimeError("you don't know what you're doing")
157 class TeXtext_pt(font.text_pt):
159 def __init__(self, font, x_pt, y_pt, charcodes, size_pt, fontmap=None):
160 self.font = font
161 self.x_pt = x_pt
162 self.y_pt = y_pt
163 self.charcodes = charcodes
164 self.size_pt = size_pt
165 self.fontmap = fontmap
167 self.width_pt = sum([self.font.getwidth_pt(charcode) for charcode in charcodes])
168 self.height_pt = max([self.font.getheight_pt(charcode) for charcode in charcodes])
169 self.depth_pt = max([self.font.getdepth_pt(charcode) for charcode in charcodes])
171 self._bbox = bbox.bbox_pt(self.x_pt, self.y_pt-self.depth_pt, self.x_pt+self.width_pt, self.y_pt+self.height_pt)
173 def bbox(self):
174 return self._bbox
176 def _text(self, writer):
177 if self.fontmap is not None:
178 mapline = self.font.getMAPline(self.fontmap)
179 else:
180 mapline = self.font.getMAPline(writer.getfontmap())
181 font = mapline.getfont()
182 return font.text_pt(self.x_pt, self.y_pt, self.charcodes, self.size_pt, decoding=mapline.getencoding(), slant=mapline.slant, ignorebbox=True)
184 def textpath(self):
185 from pyx import pswriter
186 return self._text(pswriter._PSwriter()).textpath()
188 def processPS(self, file, writer, context, registry, bbox):
189 bbox += self.bbox()
190 self._text(writer).processPS(file, writer, context, registry, bbox)
192 def processPDF(self, file, writer, context, registry, bbox):
193 bbox += self.bbox()
194 self._text(writer).processPDF(file, writer, context, registry, bbox)