add bbox to texfont
[PyX/mjg.git] / pyx / font / font.py
blob4de61f9b4b7222de05e49b73405ff7139042c38e
1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2005-2007 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2005-2007 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
23 from pyx import bbox, canvasitem, pswriter, trafo, unit
24 import t1file
26 try:
27 set()
28 except NameError:
29 # Python 2.3
30 from sets import Set as set
33 # PSresources
37 class PST1file(pswriter.PSresource):
39 """ PostScript font definition included in the prolog """
41 def __init__(self, t1file, glyphnames, charcodes):
42 """ include type 1 font t1file stripped to the given glyphnames"""
43 self.type = "t1file"
44 self.t1file = t1file
45 self.id = t1file.name
46 self.glyphnames = set(glyphnames)
47 self.charcodes = set(charcodes)
48 self.strip = 1
50 def merge(self, other):
51 self.glyphnames.update(other.glyphnames)
52 self.charcodes.update(other.charcodes)
54 def output(self, file, writer, registry):
55 file.write("%%%%BeginFont: %s\n" % self.t1file.name)
56 if self.strip:
57 if self.glyphnames:
58 file.write("%%Included glyphs: %s\n" % " ".join(self.glyphnames))
59 if self.charcodes:
60 file.write("%%Included charcodes: %s\n" % " ".join([str(charcode) for charcode in self.charcodes]))
61 self.t1file.getstrippedfont(self.glyphnames, self.charcodes).outputPS(file, writer)
62 else:
63 self.t1file.outputPS(file, writer)
64 file.write("\n%%EndFont\n")
67 _ReEncodeFont = pswriter.PSdefinition("ReEncodeFont", """{
68 5 dict
69 begin
70 /newencoding exch def
71 /newfontname exch def
72 /basefontname exch def
73 /basefontdict basefontname findfont def
74 /newfontdict basefontdict maxlength dict def
75 basefontdict {
76 exch dup dup /FID ne exch /Encoding ne and
77 { exch newfontdict 3 1 roll put }
78 { pop pop }
79 ifelse
80 } forall
81 newfontdict /FontName newfontname put
82 newfontdict /Encoding newencoding put
83 newfontname newfontdict definefont pop
84 end
85 }""")
88 class PSreencodefont(pswriter.PSresource):
90 """ reencoded PostScript font"""
92 def __init__(self, basefontname, newfontname, encoding):
93 """ reencode the font """
95 self.type = "reencodefont"
96 self.basefontname = basefontname
97 self.id = self.newfontname = newfontname
98 self.encoding = encoding
100 def output(self, file, writer, registry):
101 file.write("%%%%BeginResource: %s\n" % self.newfontname)
102 file.write("/%s /%s\n[" % (self.basefontname, self.newfontname))
103 vector = [None] * len(self.encoding)
104 for glyphname, charcode in self.encoding.items():
105 vector[charcode] = glyphname
106 for i, glyphname in enumerate(vector):
107 if i:
108 if not (i % 8):
109 file.write("\n")
110 else:
111 file.write(" ")
112 file.write("/%s" % glyphname)
113 file.write("]\n")
114 file.write("ReEncodeFont\n")
115 file.write("%%EndResource\n")
118 _ChangeFontMatrix = pswriter.PSdefinition("ChangeFontMatrix", """{
119 5 dict
120 begin
121 /newfontmatrix exch def
122 /newfontname exch def
123 /basefontname exch def
124 /basefontdict basefontname findfont def
125 /newfontdict basefontdict maxlength dict def
126 basefontdict {
127 exch dup dup /FID ne exch /FontMatrix ne and
128 { exch newfontdict 3 1 roll put }
129 { pop pop }
130 ifelse
131 } forall
132 newfontdict /FontName newfontname put
133 newfontdict /FontMatrix newfontmatrix readonly put
134 newfontname newfontdict definefont pop
136 }""")
139 class PSchangefontmatrix(pswriter.PSresource):
141 """ change font matrix of a PostScript font"""
143 def __init__(self, basefontname, newfontname, newfontmatrix):
144 """ change the font matrix """
146 self.type = "changefontmatrix"
147 self.basefontname = basefontname
148 self.id = self.newfontname = newfontname
149 self.newfontmatrix = newfontmatrix
151 def output(self, file, writer, registry):
152 file.write("%%%%BeginResource: %s\n" % self.newfontname)
153 file.write("/%s /%s\n" % (self.basefontname, self.newfontname))
154 file.write(str(self.newfontmatrix))
155 file.write("\nChangeFontMatrix\n")
156 file.write("%%EndResource\n")
159 class font:
161 def text(self, x, y, charcodes, size_pt, **kwargs):
162 return self.text_pt(unit.topt(x), unit.topt(y), charcodes, size_pt, **kwargs)
165 class T1font(font):
167 def __init__(self, t1file, metric):
168 self.t1file = t1file
169 self.name = t1file.name
170 self.metric = metric
172 def text_pt(self, x, y, charcodes, size_pt, **kwargs):
173 return T1text_pt(self, x, y, charcodes, size_pt, **kwargs)
176 class T1builtinfont(T1font):
178 def __init__(self, name, metric):
179 self.name = name
180 self.t1file = None
181 self.metric = metric
184 class selectedfont:
186 def __init__(self, name, size_pt):
187 self.name = name
188 self.size_pt = size_pt
190 def __eq__(self, other):
191 return self.name == other.name and self.size_pt == other.size_pt
193 def outputPS(self, file, writer):
194 file.write("/%s %f selectfont\n" % (self.name, self.size_pt))
197 class text_pt(canvasitem.canvasitem):
199 pass
202 class T1text_pt(text_pt):
204 def __init__(self, font, x_pt, y_pt, charcodes, size_pt, decoding=None, slant=None, ignorebbox=False): #, **features):
205 # features: kerning, ligatures
206 if decoding is not None:
207 self.glyphnames = [decoding[character] for character in charcodes]
208 self.reencode = True
209 else:
210 self.charcodes = charcodes
211 self.reencode = False
212 self.font = font
213 self.x_pt = x_pt
214 self.y_pt = y_pt
215 self.size_pt = size_pt
216 self.slant = slant
217 self.ignorebbox = ignorebbox
219 def bbox(self):
220 if self.font.metric is None:
221 raise NotImplementedError("we don't yet have access to the metric")
222 return bbox.bbox_pt(self.x_pt,
223 self.y_pt-self.font.metric.depth_pt(self.glyphnames, self.size_pt),
224 self.x_pt+self.font.metric.width_pt(self.glyphnames, self.size_pt),
225 self.y_pt+self.font.metric.height_pt(self.glyphnames, self.size_pt))
227 def getencodingname(self, encodings):
228 """returns the name of the encoding (in encodings) mapping self.glyphnames to codepoints
229 If no such encoding can be found or extended, a new encoding is added to encodings
231 glyphnames = set(self.glyphnames)
232 if len(glyphnames) > 256:
233 raise ValueError("glyphs do not fit into one single encoding")
234 for encodingname, encoding in encodings.items():
235 glyphsmissing = []
236 for glyphname in glyphnames:
237 if glyphname not in encoding.keys():
238 glyphsmissing.append(glyphname)
240 if len(glyphsmissing) + len(encoding) < 256:
241 # new glyphs fit in existing encoding which will thus be extended
242 for glyphname in glyphsmissing:
243 encoding[glyphname] = len(encoding)
244 return encodingname
245 # create a new encoding for the glyphnames
246 encodingname = "encoding%d" % len(encodings)
247 encodings[encodingname] = dict([(glyphname, i) for i, glyphname in enumerate(glyphnames)])
248 return encodingname
250 def processPS(self, file, writer, context, registry, bbox):
251 if not self.ignorebbox:
252 bbox += self.bbox()
254 # register resources
255 if self.font.t1file is not None:
256 if self.reencode:
257 registry.add(PST1file(self.font.t1file, self.glyphnames, []))
258 else:
259 registry.add(PST1file(self.font.t1file, [], self.charcodes))
261 fontname = self.font.name
262 if self.reencode:
263 encodingname = self.getencodingname(context.encodings.setdefault(self.font.name, {}))
264 encoding = context.encodings[self.font.name][encodingname]
265 newfontname = "%s-%s" % (fontname, encodingname)
266 registry.add(_ReEncodeFont)
267 registry.add(PSreencodefont(fontname, newfontname, encoding))
268 fontname = newfontname
270 if self.slant:
271 newfontmatrix = trafo.trafo_pt(matrix=((1, self.slant), (0, 1))) * self.font.t1file.fontmatrix
272 newfontname = "%s-slant%f" % (fontname, self.slant)
273 registry.add(_ChangeFontMatrix)
274 registry.add(PSchangefontmatrix(fontname, newfontname, newfontmatrix))
275 fontname = newfontname
278 # select font if necessary
279 sf = selectedfont(fontname, self.size_pt)
280 if context.selectedfont is None or sf != context.selectedfont:
281 context.selectedfont = sf
282 sf.outputPS(file, writer)
284 file.write("%f %f moveto (" % (self.x_pt, self.y_pt))
285 if self.reencode:
286 charcodes = [encoding[glyphname] for glyphname in self.glyphnames]
287 else:
288 charcodes = self.charcodes
289 for charcode in charcodes:
290 if 32 < charcode < 127 and chr(charcode) not in "()[]<>\\":
291 file.write("%s" % chr(charcode))
292 else:
293 file.write("\\%03o" % charcode)
294 file.write(") show\n")