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
, deco
, path
, pswriter
, pdfwriter
, trafo
, unit
30 from sets
import Set
as set
33 ##############################################################################
35 ##############################################################################
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"""
46 self
.glyphnames
= set(glyphnames
)
47 self
.charcodes
= set(charcodes
)
49 def merge(self
, other
):
50 self
.glyphnames
.update(other
.glyphnames
)
51 self
.charcodes
.update(other
.charcodes
)
53 def output(self
, file, writer
, registry
):
54 file.write("%%%%BeginFont: %s\n" % self
.t1file
.name
)
57 file.write("%%Included glyphs: %s\n" % " ".join(self
.glyphnames
))
59 file.write("%%Included charcodes: %s\n" % " ".join([str(charcode
) for charcode
in self
.charcodes
]))
60 self
.t1file
.getstrippedfont(self
.glyphnames
, self
.charcodes
).outputPS(file, writer
)
62 self
.t1file
.outputPS(file, writer
)
63 file.write("\n%%EndFont\n")
66 _ReEncodeFont
= pswriter
.PSdefinition("ReEncodeFont", """{
71 /basefontname exch def
72 /basefontdict basefontname findfont def
73 /newfontdict basefontdict maxlength dict def
75 exch dup dup /FID ne exch /Encoding ne and
76 { exch newfontdict 3 1 roll put }
80 newfontdict /FontName newfontname put
81 newfontdict /Encoding newencoding put
82 newfontname newfontdict definefont pop
87 class PSreencodefont(pswriter
.PSresource
):
89 """ reencoded PostScript font"""
91 def __init__(self
, basefontname
, newfontname
, encoding
):
92 """ reencode the font """
94 self
.type = "reencodefont"
95 self
.basefontname
= basefontname
96 self
.id = self
.newfontname
= newfontname
97 self
.encoding
= encoding
99 def output(self
, file, writer
, registry
):
100 file.write("%%%%BeginResource: %s\n" % self
.newfontname
)
101 file.write("/%s /%s\n[" % (self
.basefontname
, self
.newfontname
))
102 vector
= [None] * len(self
.encoding
)
103 for glyphname
, charcode
in self
.encoding
.items():
104 vector
[charcode
] = glyphname
105 for i
, glyphname
in enumerate(vector
):
111 file.write("/%s" % glyphname
)
113 file.write("ReEncodeFont\n")
114 file.write("%%EndResource\n")
117 _ChangeFontMatrix
= pswriter
.PSdefinition("ChangeFontMatrix", """{
120 /newfontmatrix exch def
121 /newfontname exch def
122 /basefontname exch def
123 /basefontdict basefontname findfont def
124 /newfontdict basefontdict maxlength dict def
126 exch dup dup /FID ne exch /FontMatrix ne and
127 { exch newfontdict 3 1 roll put }
131 newfontdict /FontName newfontname put
132 newfontdict /FontMatrix newfontmatrix readonly put
133 newfontname newfontdict definefont pop
138 class PSchangefontmatrix(pswriter
.PSresource
):
140 """ change font matrix of a PostScript font"""
142 def __init__(self
, basefontname
, newfontname
, newfontmatrix
):
143 """ change the font matrix """
145 self
.type = "changefontmatrix"
146 self
.basefontname
= basefontname
147 self
.id = self
.newfontname
= newfontname
148 self
.newfontmatrix
= newfontmatrix
150 def output(self
, file, writer
, registry
):
151 file.write("%%%%BeginResource: %s\n" % self
.newfontname
)
152 file.write("/%s /%s\n" % (self
.basefontname
, self
.newfontname
))
153 file.write(str(self
.newfontmatrix
))
154 file.write("\nChangeFontMatrix\n")
155 file.write("%%EndResource\n")
158 ##############################################################################
160 ##############################################################################
162 class PDFfont(pdfwriter
.PDFobject
):
164 def __init__(self
, fontname
, basefontname
, charcodes
, fontdescriptor
, encoding
, metric
):
165 pdfwriter
.PDFobject
.__init
__(self
, "font", fontname
)
167 self
.fontname
= fontname
168 self
.basefontname
= basefontname
169 self
.charcodes
= set(charcodes
)
170 self
.fontdescriptor
= fontdescriptor
171 self
.encoding
= encoding
174 def merge(self
, other
):
175 self
.charcodes
.update(other
.charcodes
)
177 def write(self
, file, writer
, registry
):
181 file.write("/Name /%s\n" % self
.fontname
)
182 file.write("/BaseFont /%s\n" % self
.basefontname
)
183 firstchar
= min(self
.charcodes
)
184 lastchar
= max(self
.charcodes
)
185 file.write("/FirstChar %d\n" % firstchar
)
186 file.write("/LastChar %d\n" % lastchar
)
187 file.write("/Widths\n"
190 encoding
= self
.encoding
.getvector()
192 encoding
= self
.fontdescriptor
.fontfile
.t1file
.encoding
193 for i
in range(firstchar
, lastchar
+1):
199 if encoding
[i
] is None:
202 if self
.metric
is not None:
203 file.write("%i" % self
.metric
.width_ds(encoding
[i
]))
205 file.write("%i" % self
.fontdescriptor
.fontfile
.t1file
.getglyphinfo(encoding
[i
])[0])
207 file.write("/FontDescriptor %d 0 R\n" % registry
.getrefno(self
.fontdescriptor
))
209 file.write("/Encoding %d 0 R\n" % registry
.getrefno(self
.encoding
))
213 class PDFstdfont(pdfwriter
.PDFobject
):
215 def __init__(self
, name
, basename
):
216 pdfwriter
.PDFobject
.__init
__(self
, "font", "stdfont-%s" % name
)
218 self
.basename
= basename
220 def write(self
, file, writer
, registry
):
221 file.write("<</BaseFont /%s\n" % self
.basename
)
222 file.write("/Name /%s\n" % self
.name
)
223 file.write("/Type /Font\n")
224 file.write("/Subtype /Type1\n")
227 # the 14 standard fonts that are always available in PDF
228 PDFTimesRoman
= PDFstdfont("Time", "Times-Roman")
229 PDFTimesBold
= PDFstdfont("TiBo", "Times-Bold")
230 PDFTimesItalic
= PDFstdfont("TiIt", "Times-Italic")
231 PDFTimesBoldItalic
= PDFstdfont("TiBI", "Times-BoldItalic")
232 PDFHelvetica
= PDFstdfont("Helv", "Helvetica")
233 PDFHelveticaBold
= PDFstdfont("HeBo", "Helvetica-Bold")
234 PDFHelveticaOblique
= PDFstdfont("HeOb", "Helvetica-Oblique")
235 PDFHelveticaBoldOblique
= PDFstdfont("HeBO", "Helvetica-BoldOblique")
236 PDFCourier
= PDFstdfont("Cour", "Courier")
237 PDFCourierBold
= PDFstdfont("CoBo", "Courier-Bold")
238 PDFCourierOblique
= PDFstdfont("CoOb", "Courier-Oblique")
239 PDFCourierBoldOblique
= PDFstdfont("CoBO", "Courier-BoldOblique")
240 PDFSymbol
= PDFstdfont("Symb", "Symbol")
241 PDFZapfDingbats
= PDFstdfont("Zapf", "ZapfDingbats")
244 class PDFfontdescriptor(pdfwriter
.PDFobject
):
246 def __init__(self
, fontname
, fontfile
, metric
):
247 pdfwriter
.PDFobject
.__init
__(self
, "fontdescriptor", fontname
)
248 self
.fontname
= fontname
249 self
.fontfile
= fontfile
252 def write(self
, file, writer
, registry
):
254 "/Type /FontDescriptor\n"
255 "/FontName /%s\n" % self
.fontname
)
256 if self
.metric
is not None:
257 self
.metric
.writePDFfontinfo(file)
259 self
.fontfile
.t1file
.writePDFfontinfo(file)
260 if self
.fontfile
is not None:
261 file.write("/FontFile %d 0 R\n" % registry
.getrefno(self
.fontfile
))
265 class PDFfontfile(pdfwriter
.PDFobject
):
267 def __init__(self
, t1file
, glyphnames
, charcodes
):
268 pdfwriter
.PDFobject
.__init
__(self
, "fontfile", t1file
.name
)
270 self
.glyphnames
= set(glyphnames
)
271 self
.charcodes
= set(charcodes
)
273 def merge(self
, other
):
274 self
.glyphnames
.update(other
.glyphnames
)
275 self
.charcodes
.update(other
.charcodes
)
277 def write(self
, file, writer
, registry
):
278 if writer
.stripfonts
:
279 self
.t1file
.getstrippedfont(self
.glyphnames
, self
.charcodes
).outputPDF(file, writer
)
281 self
.t1file
.outputPDF(file, writer
)
284 class PDFencoding(pdfwriter
.PDFobject
):
286 def __init__(self
, encoding
, name
):
287 pdfwriter
.PDFobject
.__init
__(self
, "encoding", name
)
288 self
.encoding
= encoding
291 # As self.encoding might be appended after the constructur has set it,
292 # we need to defer the calculation until the whole content was constructed.
293 vector
= [None] * len(self
.encoding
)
294 for glyphname
, charcode
in self
.encoding
.items():
295 vector
[charcode
] = glyphname
298 def write(self
, file, writer
, registry
):
303 for i
, glyphname
in enumerate(self
.getvector()):
309 file.write("/%s" % glyphname
)
314 ##############################################################################
315 # basic PyX text output
316 ##############################################################################
320 def text(self
, x
, y
, charcodes
, size_pt
, **kwargs
):
321 return self
.text_pt(unit
.topt(x
), unit
.topt(y
), charcodes
, size_pt
, **kwargs
)
326 def __init__(self
, t1file
, metric
):
328 self
.name
= t1file
.name
331 def text_pt(self
, x
, y
, charcodes
, size_pt
, **kwargs
):
332 return T1text_pt(self
, x
, y
, charcodes
, size_pt
, **kwargs
)
335 class T1builtinfont(T1font
):
337 def __init__(self
, name
, metric
):
345 def __init__(self
, name
, size_pt
):
347 self
.size_pt
= size_pt
349 def __ne__(self
, other
):
350 return self
.name
!= other
.name
or self
.size_pt
!= other
.size_pt
352 def outputPS(self
, file, writer
):
353 file.write("/%s %f selectfont\n" % (self
.name
, self
.size_pt
))
355 def outputPDF(self
, file, writer
):
356 file.write("/%s %f Tf\n" % (self
.name
, self
.size_pt
))
359 class text_pt(canvasitem
.canvasitem
):
364 class T1text_pt(text_pt
):
366 def __init__(self
, font
, x_pt
, y_pt
, charcodes
, size_pt
, decoding
=None, slant
=None, ignorebbox
=False, kerning
=False, ligatures
=False, spaced_pt
=0):
367 if decoding
is not None:
368 self
.glyphnames
= [decoding
[character
] for character
in charcodes
]
371 self
.charcodes
= charcodes
376 self
.size_pt
= size_pt
378 self
.ignorebbox
= ignorebbox
379 self
.kerning
= kerning
380 self
.ligatures
= ligatures
381 self
.spaced_pt
= spaced_pt
383 if self
.kerning
and not self
.decode
:
384 raise ValueError("decoding required for font metric access (kerning)")
385 if self
.ligatures
and not self
.decode
:
386 raise ValueError("decoding required for font metric access (ligatures)")
388 self
.glyphnames
= self
.font
.metric
.resolveligatures(self
.glyphnames
)
391 if self
.font
.metric
is None:
392 raise ValueError("metric missing")
394 raise ValueError("decoding required for font metric access (bbox)")
395 return bbox
.bbox_pt(self
.x_pt
,
396 self
.y_pt
+self
.font
.metric
.depth_pt(self
.glyphnames
, self
.size_pt
),
397 self
.x_pt
+self
.font
.metric
.width_pt(self
.glyphnames
, self
.size_pt
),
398 self
.y_pt
+self
.font
.metric
.height_pt(self
.glyphnames
, self
.size_pt
))
400 def getencodingname(self
, encodings
):
401 """returns the name of the encoding (in encodings) mapping self.glyphnames to codepoints
402 If no such encoding can be found or extended, a new encoding is added to encodings
404 glyphnames
= set(self
.glyphnames
)
405 if len(glyphnames
) > 256:
406 raise ValueError("glyphs do not fit into one single encoding")
407 for encodingname
, encoding
in encodings
.items():
409 for glyphname
in glyphnames
:
410 if glyphname
not in encoding
.keys():
411 glyphsmissing
.append(glyphname
)
413 if len(glyphsmissing
) + len(encoding
) < 256:
414 # new glyphs fit in existing encoding which will thus be extended
415 for glyphname
in glyphsmissing
:
416 encoding
[glyphname
] = len(encoding
)
418 # create a new encoding for the glyphnames
419 encodingname
= "encoding%d" % len(encodings
)
420 encodings
[encodingname
] = dict([(glyphname
, i
) for i
, glyphname
in enumerate(glyphnames
)])
423 def processPS(self
, file, writer
, context
, registry
, bbox
):
424 if not self
.ignorebbox
:
427 if writer
.textaspath
:
430 data
= self
.font
.metric
.resolvekernings(self
.glyphnames
, self
.size_pt
)
432 data
= self
.glyphnames
434 data
= self
.charcodes
435 textpath
= path
.path()
438 for i
, value
in enumerate(data
):
439 if self
.kerning
and i
% 2:
440 if value
is not None:
444 x_pt
+= self
.spaced_pt
445 glyphpath
, wx_pt
, wy_pt
= self
.font
.t1file
.getglyphpathwxwy_pt(value
, self
.size_pt
, convertcharcode
=not self
.decode
)
446 textpath
+= glyphpath
.transformed(trafo
.translate_pt(x_pt
, y_pt
))
449 deco
.decoratedpath(textpath
, fillstyles
=[]).processPS(file, writer
, context
, registry
, bbox
)
452 if self
.font
.t1file
is not None:
454 registry
.add(PST1file(self
.font
.t1file
, self
.glyphnames
, []))
456 registry
.add(PST1file(self
.font
.t1file
, [], self
.charcodes
))
458 fontname
= self
.font
.name
460 encodingname
= self
.getencodingname(writer
.encodings
.setdefault(self
.font
.name
, {}))
461 encoding
= writer
.encodings
[self
.font
.name
][encodingname
]
462 newfontname
= "%s-%s" % (fontname
, encodingname
)
463 registry
.add(_ReEncodeFont
)
464 registry
.add(PSreencodefont(fontname
, newfontname
, encoding
))
465 fontname
= newfontname
468 newfontmatrix
= trafo
.trafo_pt(matrix
=((1, self
.slant
), (0, 1))) * self
.font
.t1file
.fontmatrix
469 newfontname
= "%s-slant%f" % (fontname
, self
.slant
)
470 registry
.add(_ChangeFontMatrix
)
471 registry
.add(PSchangefontmatrix(fontname
, newfontname
, newfontmatrix
))
472 fontname
= newfontname
474 # select font if necessary
475 sf
= selectedfont(fontname
, self
.size_pt
)
476 if context
.selectedfont
is None or sf
!= context
.selectedfont
:
477 context
.selectedfont
= sf
478 sf
.outputPS(file, writer
)
480 file.write("%f %f moveto (" % (self
.x_pt
, self
.y_pt
))
483 data
= self
.font
.metric
.resolvekernings(self
.glyphnames
, self
.size_pt
)
485 data
= self
.glyphnames
487 data
= self
.charcodes
488 for i
, value
in enumerate(data
):
489 if self
.kerning
and i
% 2:
490 if value
is not None:
491 file.write(") show\n%f 0 rmoveto (" % (value
+self
.spaced_pt
))
493 file.write(") show\n%f 0 rmoveto (" % self
.spaced_pt
)
495 if i
and not self
.kerning
and self
.spaced_pt
:
496 file.write(") show\n%f 0 rmoveto (" % self
.spaced_pt
)
498 value
= encoding
[value
]
499 if 32 < value
< 127 and chr(value
) not in "()[]<>\\":
500 file.write("%s" % chr(value
))
502 file.write("\\%03o" % value
)
503 file.write(") show\n")
505 def processPDF(self
, file, writer
, context
, registry
, bbox
):
506 if not self
.ignorebbox
:
509 if writer
.textaspath
:
512 data
= self
.font
.metric
.resolvekernings(self
.glyphnames
, self
.size_pt
)
514 data
= self
.glyphnames
516 data
= self
.charcodes
517 textpath
= path
.path()
520 for i
, value
in enumerate(data
):
521 if self
.kerning
and i
% 2:
522 if value
is not None:
526 x_pt
+= self
.spaced_pt
527 glyphpath
, wx_pt
, wy_pt
= self
.font
.t1file
.getglyphpathwxwy_pt(value
, self
.size_pt
, convertcharcode
=not self
.decode
)
528 textpath
+= glyphpath
.transformed(trafo
.translate_pt(x_pt
, y_pt
))
531 deco
.decoratedpath(textpath
, fillstyles
=[]).processPDF(file, writer
, context
, registry
, bbox
)
534 encodingname
= self
.getencodingname(writer
.encodings
.setdefault(self
.font
.name
, {}))
535 encoding
= writer
.encodings
[self
.font
.name
][encodingname
]
536 charcodes
= [encoding
[glyphname
] for glyphname
in self
.glyphnames
]
538 charcodes
= self
.charcodes
541 fontname
= self
.font
.name
543 newfontname
= "%s-%s" % (fontname
, encodingname
)
544 _encoding
= PDFencoding(encoding
, newfontname
)
545 fontname
= newfontname
548 if self
.font
.t1file
is not None:
550 fontfile
= PDFfontfile(self
.font
.t1file
, self
.glyphnames
, [])
552 fontfile
= PDFfontfile(self
.font
.t1file
, [], self
.charcodes
)
555 fontdescriptor
= PDFfontdescriptor(self
.font
.name
, fontfile
, self
.font
.metric
)
556 font
= PDFfont(fontname
, self
.font
.name
, charcodes
, fontdescriptor
, _encoding
, self
.font
.metric
)
559 if fontfile
is not None:
560 registry
.add(fontfile
)
561 registry
.add(fontdescriptor
)
562 if _encoding
is not None:
563 registry
.add(_encoding
)
566 registry
.addresource("Font", fontname
, font
, procset
="Text")
568 if self
.slant
is None:
571 slantvalue
= self
.slant
573 # select font if necessary
574 sf
= selectedfont(fontname
, self
.size_pt
)
575 if context
.selectedfont
is None or sf
!= context
.selectedfont
:
576 context
.selectedfont
= sf
577 sf
.outputPDF(file, writer
)
580 file.write("1 0 %f 1 %f %f Tm [(" % (slantvalue
, self
.x_pt
, self
.y_pt
))
582 file.write("1 0 %f 1 %f %f Tm (" % (slantvalue
, self
.x_pt
, self
.y_pt
))
585 data
= self
.font
.metric
.resolvekernings(self
.glyphnames
)
587 data
= self
.glyphnames
589 data
= self
.charcodes
590 for i
, value
in enumerate(data
):
591 if self
.kerning
and i
% 2:
592 if value
is not None:
593 file.write(")%f(" % (-value
-self
.spaced_pt
))
595 file.write(")%f(" % (-self
.spaced_pt
))
597 if i
and not self
.kerning
and self
.spaced_pt
:
598 file.write(")%f(" % (-self
.spaced_pt
))
600 value
= encoding
[value
]
601 if 32 <= value
<= 127 and chr(value
) not in "()[]<>\\":
602 file.write("%s" % chr(value
))
604 file.write("\\%03o" % value
)
606 file.write(")] TJ\n")