add bbox to texfont
[PyX/mjg.git] / pyx / dvi / texfont.py
blob43dee6f827642a87f3d65246046b41b867d24aed
1 from pyx import bbox, canvasitem, font, pykpathsea
2 import tfmfile, vffile
3 from pyx.font import t1file
5 class TeXfont:
7 def __init__(self, name, c, q, d, tfmconv, pyxconv, debug=0):
8 self.name = name
9 self.q = q # desired size of font (fix_word) in TeX points
10 self.d = d # design size of font (fix_word) in TeX points
11 self.tfmconv = tfmconv # conversion factor from tfm units to dvi units
12 self.pyxconv = pyxconv # conversion factor from dvi units to PostScript points
13 tfmpath = pykpathsea.find_file("%s.tfm" % self.name, pykpathsea.kpse_tfm_format)
14 if not tfmpath:
15 raise TFMError("cannot find %s.tfm" % self.name)
16 self.TFMfile = tfmfile.TFMfile(tfmpath, debug)
18 # We only check for equality of font checksums if none of them
19 # is zero. The case c == 0 happend in some VF files and
20 # according to the VFtoVP documentation, paragraph 40, a check
21 # is only performed if TFMfile.checksum > 0. Anyhow, being
22 # more generous here seems to be reasonable
23 if self.TFMfile.checksum != c and self.TFMfile.checksum != 0 and c != 0:
24 raise DVIError("check sums do not agree: %d vs. %d" %
25 (self.TFMfile.checksum, c))
27 # Check whether the given design size matches the one defined in the tfm file
28 if abs(self.TFMfile.designsize - d) > 2:
29 raise DVIError("design sizes do not agree: %d vs. %d" % (self.TFMfile.designsize, d))
30 #if q < 0 or q > 134217728:
31 # raise DVIError("font '%s' not loaded: bad scale" % self.name)
32 if d < 0 or d > 134217728:
33 raise DVIError("font '%s' not loaded: bad design size" % self.name)
35 self.scale = 1.0*q/d
37 def fontinfo(self):
38 class fontinfo:
39 pass
41 # The following code is a very crude way to obtain the information
42 # required for the PDF font descritor. (TODO: The correct way would
43 # be to read the information from the AFM file.)
44 fontinfo = fontinfo()
45 try:
46 fontinfo.fontbbox = (0,
47 -self.getdepth_ds(ord("y")),
48 self.getwidth_ds(ord("W")),
49 self.getheight_ds(ord("H")))
50 except:
51 fontinfo.fontbbox = (0, -10, 100, 100)
52 try:
53 fontinfo.italicangle = -180/math.pi*math.atan(self.TFMfile.param[0]/65536.0)
54 except IndexError:
55 fontinfo.italicangle = 0
56 fontinfo.ascent = fontinfo.fontbbox[3]
57 fontinfo.descent = fontinfo.fontbbox[1]
58 try:
59 fontinfo.capheight = self.getheight_ds(ord("h"))
60 except:
61 fontinfo.capheight = 100
62 try:
63 fontinfo.vstem = self.getwidth_ds(ord("."))/3
64 except:
65 fontinfo.vstem = 5
66 return fontinfo
68 def __str__(self):
69 return "font %s designed at %g TeX pts used at %g TeX pts" % (self.name,
70 16.0*self.d/16777216L,
71 16.0*self.q/16777216L)
73 def getsize_pt(self):
74 """ return size of font in (PS) points """
75 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
76 # to the corresponding float. Furthermore, we have to convert from TeX
77 # points to points, hence the factor 72/72.27.
78 return 16L*self.q/16777216L*72/72.27
80 def _convert_tfm_to_dvi(self, length):
81 # doing the integer math with long integers will lead to different roundings
82 # return 16*length*int(round(self.q*self.tfmconv))/16777216
84 # Knuth instead suggests the following algorithm based on 4 byte integer logic only
85 # z = int(round(self.q*self.tfmconv))
86 # b0, b1, b2, b3 = [ord(c) for c in struct.pack(">L", length)]
87 # assert b0 == 0 or b0 == 255
88 # shift = 4
89 # while z >= 8388608:
90 # z >>= 1
91 # shift -= 1
92 # assert shift >= 0
93 # result = ( ( ( ( ( b3 * z ) >> 8 ) + ( b2 * z ) ) >> 8 ) + ( b1 * z ) ) >> shift
94 # if b0 == 255:
95 # result = result - (z << (8-shift))
97 # however, we can simplify this using a single long integer multiplication,
98 # but take into account the transformation of z
99 z = int(round(self.q*self.tfmconv))
100 assert -16777216 <= length < 16777216 # -(1 << 24) <= length < (1 << 24)
101 assert z < 134217728 # 1 << 27
102 shift = 20 # 1 << 20
103 while z >= 8388608: # 1 << 23
104 z >>= 1
105 shift -= 1
106 # length*z is a long integer, but the result will be a regular integer
107 return int(length*long(z) >> shift)
109 def _convert_tfm_to_ds(self, length):
110 return (16*long(round(length*float(self.q)*self.tfmconv))/16777216) * self.pyxconv * 1000 / self.getsize_pt()
112 def _convert_tfm_to_pt(self, length):
113 return (16*long(round(length*float(self.q)*self.tfmconv))/16777216) * self.pyxconv
115 # routines returning lengths as integers in dvi units
117 def getwidth_dvi(self, charcode):
118 return self._convert_tfm_to_dvi(self.TFMfile.width[self.TFMfile.char_info[charcode].width_index])
120 def getheight_dvi(self, charcode):
121 return self._convert_tfm_to_dvi(self.TFMfile.height[self.TFMfile.char_info[charcode].height_index])
123 def getdepth_dvi(self, charcode):
124 return self._convert_tfm_to_dvi(self.TFMfile.depth[self.TFMfile.char_info[charcode].depth_index])
126 def getitalic_dvi(self, charcode):
127 return self._convert_tfm_to_dvi(self.TFMfile.italic[self.TFMfile.char_info[charcode].italic_index])
129 # routines returning lengths as integers in design size (AFM) units
131 def getwidth_ds(self, charcode):
132 return self._convert_tfm_to_ds(self.TFMfile.width[self.TFMfile.char_info[charcode].width_index])
134 def getheight_ds(self, charcode):
135 return self._convert_tfm_to_ds(self.TFMfile.height[self.TFMfile.char_info[charcode].height_index])
137 def getdepth_ds(self, charcode):
138 return self._convert_tfm_to_ds(self.TFMfile.depth[self.TFMfile.char_info[charcode].depth_index])
140 def getitalic_ds(self, charcode):
141 return self._convert_tfm_to_ds(self.TFMfile.italic[self.TFMfile.char_info[charcode].italic_index])
143 # routines returning lengths as floats in PostScript points
145 def getwidth_pt(self, charcode):
146 return self._convert_tfm_to_pt(self.TFMfile.width[self.TFMfile.char_info[charcode].width_index])
148 def getheight_pt(self, charcode):
149 return self._convert_tfm_to_pt(self.TFMfile.height[self.TFMfile.char_info[charcode].height_index])
151 def getdepth_pt(self, charcode):
152 return self._convert_tfm_to_pt(self.TFMfile.depth[self.TFMfile.char_info[charcode].depth_index])
154 def getitalic_pt(self, charcode):
155 return self._convert_tfm_to_pt(self.TFMfile.italic[self.TFMfile.char_info[charcode].italic_index])
157 def text_pt(self, x_pt, y_pt, charcodes):
158 return TeXtext_pt(self, x_pt, y_pt, charcodes, self.getsize_pt())
160 def getMAPline(self, fontmap):
161 if self.name not in fontmap:
162 raise RuntimeError("missing font information for '%s'; check fontmapping file(s)" % self.name)
163 return fontmap[self.name]
166 class virtualfont(TeXfont):
168 def __init__(self, name, path, c, q, d, tfmconv, pyxconv, debug=0):
169 TeXfont.__init__(self, name, c, q, d, tfmconv, pyxconv, debug)
170 self.vffile = vffile.vffile(path, self.scale, tfmconv, pyxconv, debug > 1)
172 def getfonts(self):
173 """ return fonts used in virtual font itself """
174 return self.vffile.getfonts()
176 def getchar(self, cc):
177 """ return dvi chunk corresponding to char code cc """
178 return self.vffile.getchar(cc)
180 def text_pt(self, x_pt, y_pt, charcodes):
181 raise RuntimeError("you don't know what you're doing")
184 class TeXtext_pt(canvasitem.canvasitem):
186 def __init__(self, font, x_pt, y_pt, charcodes, size_pt):
187 self.font = font
188 self.x_pt = x_pt
189 self.y_pt = y_pt
190 self.charcodes = charcodes
191 self.size_pt = size_pt
193 self.width_pt = sum([self.font.getwidth_pt(charcode) for charcode in charcodes])
194 self.height_pt = max([self.font.getheight_pt(charcode) for charcode in charcodes])
195 self.depth_pt = max([self.font.getdepth_pt(charcode) for charcode in charcodes])
197 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)
199 def bbox(self):
200 return self._bbox
202 def processPS(self, file, writer, context, registry, bbox):
203 bbox += self.bbox()
204 mapline = self.font.getMAPline(writer.getfontmap())
205 font = mapline.getfont()
206 text = font.text_pt(self.x_pt, self.y_pt, self.charcodes, self.size_pt, decoding=mapline.getencoding(), slant=mapline.slant, ignorebbox=True)
207 text.processPS(file, writer, context, registry, bbox)
209 def processPDF(self, file, writer, context, registry, bbox):
210 pass