1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2005-2007 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2006-2011 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2005-2007 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 from pyx
import bbox
, canvasitem
, deco
, path
, pswriter
, pdfwriter
, trafo
, unit
31 from sets
import Set
as set
34 ##############################################################################
36 ##############################################################################
38 class PST1file(pswriter
.PSresource
):
40 """ PostScript font definition included in the prolog """
42 def __init__(self
, t1file
, glyphnames
, charcodes
):
43 """ include type 1 font t1file stripped to the given glyphnames"""
47 self
.glyphnames
= set(glyphnames
)
48 self
.charcodes
= set(charcodes
)
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 writer
.strip_fonts
:
58 file.write("%%Included glyphs: %s\n" % " ".join(self
.glyphnames
))
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
)
63 self
.t1file
.outputPS(file, writer
)
64 file.write("\n%%EndFont\n")
67 _ReEncodeFont
= pswriter
.PSdefinition("ReEncodeFont", """{
72 /basefontname exch def
73 /basefontdict basefontname findfont def
74 /newfontdict basefontdict maxlength dict def
76 exch dup dup /FID ne exch /Encoding ne and
77 { exch newfontdict 3 1 roll put }
81 newfontdict /FontName newfontname put
82 newfontdict /Encoding newencoding put
83 newfontname newfontdict definefont pop
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
):
112 file.write("/%s" % glyphname
)
114 file.write("ReEncodeFont\n")
115 file.write("%%EndResource\n")
118 _ChangeFontMatrix
= pswriter
.PSdefinition("ChangeFontMatrix", """{
121 /newfontmatrix exch def
122 /newfontname exch def
123 /basefontname exch def
124 /basefontdict basefontname findfont def
125 /newfontdict basefontdict maxlength dict def
127 exch dup dup /FID ne exch /FontMatrix ne and
128 { exch newfontdict 3 1 roll put }
132 newfontdict /FontName newfontname put
133 newfontdict /FontMatrix newfontmatrix readonly put
134 newfontname newfontdict definefont pop
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 ##############################################################################
161 ##############################################################################
163 class PDFfont(pdfwriter
.PDFobject
):
165 def __init__(self
, fontname
, basefontname
, charcodes
, fontdescriptor
, encoding
, metric
):
166 pdfwriter
.PDFobject
.__init
__(self
, "font", fontname
)
168 self
.fontname
= fontname
169 self
.basefontname
= basefontname
170 self
.charcodes
= set(charcodes
)
171 self
.fontdescriptor
= fontdescriptor
172 self
.encoding
= encoding
175 def merge(self
, other
):
176 self
.charcodes
.update(other
.charcodes
)
178 def write(self
, file, writer
, registry
):
182 file.write("/Name /%s\n" % self
.fontname
)
183 file.write("/BaseFont /%s\n" % self
.basefontname
)
184 firstchar
= min(self
.charcodes
)
185 lastchar
= max(self
.charcodes
)
186 file.write("/FirstChar %d\n" % firstchar
)
187 file.write("/LastChar %d\n" % lastchar
)
188 file.write("/Widths\n"
191 encoding
= self
.encoding
.getvector()
193 encoding
= self
.fontdescriptor
.fontfile
.t1file
.encoding
194 for i
in range(firstchar
, lastchar
+1):
200 if i
in self
.charcodes
:
201 if self
.metric
is not None:
202 file.write("%i" % self
.metric
.width_ds(encoding
[i
]))
204 file.write("%i" % self
.fontdescriptor
.fontfile
.t1file
.getglyphinfo(encoding
[i
])[0])
208 file.write("/FontDescriptor %d 0 R\n" % registry
.getrefno(self
.fontdescriptor
))
210 file.write("/Encoding %d 0 R\n" % registry
.getrefno(self
.encoding
))
214 class PDFstdfont(pdfwriter
.PDFobject
):
216 def __init__(self
, basename
):
217 pdfwriter
.PDFobject
.__init
__(self
, "font", "stdfont-%s" % basename
)
218 self
.name
= basename
# name is ignored by acroread
219 self
.basename
= basename
221 def write(self
, file, writer
, registry
):
222 file.write("<</BaseFont /%s\n" % self
.basename
)
223 file.write("/Name /%s\n" % self
.name
)
224 file.write("/Type /Font\n")
225 file.write("/Subtype /Type1\n")
228 # the 14 standard fonts that are always available in PDF
229 PDFTimesRoman
= PDFstdfont("Times-Roman")
230 PDFTimesBold
= PDFstdfont("Times-Bold")
231 PDFTimesItalic
= PDFstdfont("Times-Italic")
232 PDFTimesBoldItalic
= PDFstdfont("Times-BoldItalic")
233 PDFHelvetica
= PDFstdfont("Helvetica")
234 PDFHelveticaBold
= PDFstdfont("Helvetica-Bold")
235 PDFHelveticaOblique
= PDFstdfont("Helvetica-Oblique")
236 PDFHelveticaBoldOblique
= PDFstdfont("Helvetica-BoldOblique")
237 PDFCourier
= PDFstdfont("Courier")
238 PDFCourierBold
= PDFstdfont("Courier-Bold")
239 PDFCourierOblique
= PDFstdfont("Courier-Oblique")
240 PDFCourierBoldOblique
= PDFstdfont("Courier-BoldOblique")
241 PDFSymbol
= PDFstdfont("Symbol")
242 PDFZapfDingbats
= PDFstdfont("ZapfDingbats")
245 class PDFfontdescriptor(pdfwriter
.PDFobject
):
247 def __init__(self
, fontname
, fontfile
, metric
):
248 pdfwriter
.PDFobject
.__init
__(self
, "fontdescriptor", fontname
)
249 self
.fontname
= fontname
250 self
.fontfile
= fontfile
253 def write(self
, file, writer
, registry
):
255 "/Type /FontDescriptor\n"
256 "/FontName /%s\n" % self
.fontname
)
257 if self
.metric
is not None:
258 self
.metric
.writePDFfontinfo(file)
260 self
.fontfile
.t1file
.writePDFfontinfo(file)
261 if self
.fontfile
is not None:
262 file.write("/FontFile %d 0 R\n" % registry
.getrefno(self
.fontfile
))
266 class PDFfontfile(pdfwriter
.PDFobject
):
268 def __init__(self
, t1file
, glyphnames
, charcodes
):
269 pdfwriter
.PDFobject
.__init
__(self
, "fontfile", t1file
.name
)
271 self
.glyphnames
= set(glyphnames
)
272 self
.charcodes
= set(charcodes
)
274 def merge(self
, other
):
275 self
.glyphnames
.update(other
.glyphnames
)
276 self
.charcodes
.update(other
.charcodes
)
278 def write(self
, file, writer
, registry
):
279 if writer
.strip_fonts
:
280 self
.t1file
.getstrippedfont(self
.glyphnames
, self
.charcodes
).outputPDF(file, writer
)
282 self
.t1file
.outputPDF(file, writer
)
285 class PDFencoding(pdfwriter
.PDFobject
):
287 def __init__(self
, encoding
, name
):
288 pdfwriter
.PDFobject
.__init
__(self
, "encoding", name
)
289 self
.encoding
= encoding
292 # As self.encoding might be appended after the constructur has set it,
293 # we need to defer the calculation until the whole content was constructed.
294 vector
= [None] * len(self
.encoding
)
295 for glyphname
, charcode
in self
.encoding
.items():
296 vector
[charcode
] = glyphname
299 def write(self
, file, writer
, registry
):
304 for i
, glyphname
in enumerate(self
.getvector()):
310 file.write("/%s" % glyphname
)
315 ##############################################################################
316 # basic PyX text output
317 ##############################################################################
321 def text(self
, x
, y
, charcodes
, size_pt
, **kwargs
):
322 return self
.text_pt(unit
.topt(x
), unit
.topt(y
), charcodes
, size_pt
, **kwargs
)
327 def __init__(self
, t1file
, metric
):
329 self
.name
= t1file
.name
332 def text_pt(self
, x
, y
, charcodes
, size_pt
, **kwargs
):
333 return T1text_pt(self
, x
, y
, charcodes
, size_pt
, **kwargs
)
336 class T1builtinfont(T1font
):
338 def __init__(self
, name
, metric
):
346 def __init__(self
, name
, size_pt
):
348 self
.size_pt
= size_pt
350 def __ne__(self
, other
):
351 return self
.name
!= other
.name
or self
.size_pt
!= other
.size_pt
353 def outputPS(self
, file, writer
):
354 file.write("/%s %f selectfont\n" % (self
.name
, self
.size_pt
))
356 def outputPDF(self
, file, writer
):
357 file.write("/%s %f Tf\n" % (self
.name
, self
.size_pt
))
360 class text_pt(canvasitem
.canvasitem
):
365 class T1text_pt(text_pt
):
367 def __init__(self
, font
, x_pt
, y_pt
, charcodes
, size_pt
, decoding
=None, slant
=None, ignorebbox
=False, kerning
=False, ligatures
=False, spaced_pt
=0):
368 if decoding
is not None:
369 self
.glyphnames
= [decoding
[character
] for character
in charcodes
]
372 self
.charcodes
= charcodes
377 self
.size_pt
= size_pt
379 self
.ignorebbox
= ignorebbox
380 self
.kerning
= kerning
381 self
.ligatures
= ligatures
382 self
.spaced_pt
= spaced_pt
384 if self
.kerning
and not self
.decode
:
385 raise ValueError("decoding required for font metric access (kerning)")
386 if self
.ligatures
and not self
.decode
:
387 raise ValueError("decoding required for font metric access (ligatures)")
389 self
.glyphnames
= self
.font
.metric
.resolveligatures(self
.glyphnames
)
392 if self
.font
.metric
is None:
393 raise ValueError("metric missing")
395 raise ValueError("decoding required for font metric access (bbox)")
396 return bbox
.bbox_pt(self
.x_pt
,
397 self
.y_pt
+self
.font
.metric
.depth_pt(self
.glyphnames
, self
.size_pt
),
398 self
.x_pt
+self
.font
.metric
.width_pt(self
.glyphnames
, self
.size_pt
),
399 self
.y_pt
+self
.font
.metric
.height_pt(self
.glyphnames
, self
.size_pt
))
401 def getencodingname(self
, encodings
):
402 """returns the name of the encoding (in encodings) mapping self.glyphnames to codepoints
403 If no such encoding can be found or extended, a new encoding is added to encodings
405 glyphnames
= set(self
.glyphnames
)
406 if len(glyphnames
) > 256:
407 raise ValueError("glyphs do not fit into one single encoding")
408 for encodingname
, encoding
in encodings
.items():
410 for glyphname
in glyphnames
:
411 if glyphname
not in encoding
.keys():
412 glyphsmissing
.append(glyphname
)
414 if len(glyphsmissing
) + len(encoding
) < 256:
415 # new glyphs fit in existing encoding which will thus be extended
416 for glyphname
in glyphsmissing
:
417 encoding
[glyphname
] = len(encoding
)
419 # create a new encoding for the glyphnames
420 encodingname
= "encoding%d" % len(encodings
)
421 encodings
[encodingname
] = dict([(glyphname
, i
) for i
, glyphname
in enumerate(glyphnames
)])
424 def processPS(self
, file, writer
, context
, registry
, bbox
):
425 if not self
.ignorebbox
:
428 if writer
.text_as_path
:
431 data
= self
.font
.metric
.resolvekernings(self
.glyphnames
, self
.size_pt
)
433 data
= self
.glyphnames
435 data
= self
.charcodes
436 textpath
= path
.path()
439 for i
, value
in enumerate(data
):
440 if self
.kerning
and i
% 2:
441 if value
is not None:
445 x_pt
+= self
.spaced_pt
446 glyphpath
, wx_pt
, wy_pt
= self
.font
.t1file
.getglyphpathwxwy_pt(value
, self
.size_pt
, convertcharcode
=not self
.decode
)
447 textpath
+= glyphpath
.transformed(trafo
.translate_pt(x_pt
, y_pt
))
450 deco
.decoratedpath(textpath
, fillstyles
=[]).processPS(file, writer
, context
, registry
, bbox
)
453 if self
.font
.t1file
is not None:
455 registry
.add(PST1file(self
.font
.t1file
, self
.glyphnames
, []))
457 registry
.add(PST1file(self
.font
.t1file
, [], self
.charcodes
))
459 fontname
= self
.font
.name
461 encodingname
= self
.getencodingname(writer
.encodings
.setdefault(self
.font
.name
, {}))
462 encoding
= writer
.encodings
[self
.font
.name
][encodingname
]
463 newfontname
= "%s-%s" % (fontname
, encodingname
)
464 registry
.add(_ReEncodeFont
)
465 registry
.add(PSreencodefont(fontname
, newfontname
, encoding
))
466 fontname
= newfontname
469 newfontmatrix
= trafo
.trafo_pt(matrix
=((1, self
.slant
), (0, 1))) * self
.font
.t1file
.fontmatrix
470 newfontname
= "%s-slant%f" % (fontname
, self
.slant
)
471 registry
.add(_ChangeFontMatrix
)
472 registry
.add(PSchangefontmatrix(fontname
, newfontname
, newfontmatrix
))
473 fontname
= newfontname
475 # select font if necessary
476 sf
= selectedfont(fontname
, self
.size_pt
)
477 if context
.selectedfont
is None or sf
!= context
.selectedfont
:
478 context
.selectedfont
= sf
479 sf
.outputPS(file, writer
)
481 file.write("%f %f moveto (" % (self
.x_pt
, self
.y_pt
))
484 data
= self
.font
.metric
.resolvekernings(self
.glyphnames
, self
.size_pt
)
486 data
= self
.glyphnames
488 data
= self
.charcodes
489 for i
, value
in enumerate(data
):
490 if self
.kerning
and i
% 2:
491 if value
is not None:
492 file.write(") show\n%f 0 rmoveto (" % (value
+self
.spaced_pt
))
494 file.write(") show\n%f 0 rmoveto (" % self
.spaced_pt
)
496 if i
and not self
.kerning
and self
.spaced_pt
:
497 file.write(") show\n%f 0 rmoveto (" % self
.spaced_pt
)
499 value
= encoding
[value
]
500 if 32 < value
< 127 and chr(value
) not in "()[]<>\\":
501 file.write("%s" % chr(value
))
503 file.write("\\%03o" % value
)
504 file.write(") show\n")
506 def processPDF(self
, file, writer
, context
, registry
, bbox
):
507 if not self
.ignorebbox
:
510 if writer
.text_as_path
:
513 data
= self
.font
.metric
.resolvekernings(self
.glyphnames
, self
.size_pt
)
515 data
= self
.glyphnames
517 data
= self
.charcodes
518 textpath
= path
.path()
521 for i
, value
in enumerate(data
):
522 if self
.kerning
and i
% 2:
523 if value
is not None:
527 x_pt
+= self
.spaced_pt
528 glyphpath
, wx_pt
, wy_pt
= self
.font
.t1file
.getglyphpathwxwy_pt(value
, self
.size_pt
, convertcharcode
=not self
.decode
)
529 textpath
+= glyphpath
.transformed(trafo
.translate_pt(x_pt
, y_pt
))
532 deco
.decoratedpath(textpath
, fillstyles
=[]).processPDF(file, writer
, context
, registry
, bbox
)
535 encodingname
= self
.getencodingname(writer
.encodings
.setdefault(self
.font
.name
, {}))
536 encoding
= writer
.encodings
[self
.font
.name
][encodingname
]
537 charcodes
= [encoding
[glyphname
] for glyphname
in self
.glyphnames
]
539 charcodes
= self
.charcodes
542 fontname
= self
.font
.name
544 newfontname
= "%s-%s" % (fontname
, encodingname
)
545 _encoding
= PDFencoding(encoding
, newfontname
)
546 fontname
= newfontname
549 if self
.font
.t1file
is not None:
551 fontfile
= PDFfontfile(self
.font
.t1file
, self
.glyphnames
, [])
553 fontfile
= PDFfontfile(self
.font
.t1file
, [], self
.charcodes
)
556 fontdescriptor
= PDFfontdescriptor(self
.font
.name
, fontfile
, self
.font
.metric
)
557 font
= PDFfont(fontname
, self
.font
.name
, charcodes
, fontdescriptor
, _encoding
, self
.font
.metric
)
560 if fontfile
is not None:
561 registry
.add(fontfile
)
562 registry
.add(fontdescriptor
)
563 if _encoding
is not None:
564 registry
.add(_encoding
)
567 registry
.addresource("Font", fontname
, font
, procset
="Text")
569 if self
.slant
is None:
572 slantvalue
= self
.slant
574 # select font if necessary
575 sf
= selectedfont(fontname
, self
.size_pt
)
576 if context
.selectedfont
is None or sf
!= context
.selectedfont
:
577 context
.selectedfont
= sf
578 sf
.outputPDF(file, writer
)
581 file.write("1 0 %f 1 %f %f Tm [(" % (slantvalue
, self
.x_pt
, self
.y_pt
))
583 file.write("1 0 %f 1 %f %f Tm (" % (slantvalue
, self
.x_pt
, self
.y_pt
))
586 data
= self
.font
.metric
.resolvekernings(self
.glyphnames
)
588 data
= self
.glyphnames
590 data
= self
.charcodes
591 for i
, value
in enumerate(data
):
592 if self
.kerning
and i
% 2:
593 if value
is not None:
594 file.write(")%f(" % (-value
-self
.spaced_pt
))
596 file.write(")%f(" % (-self
.spaced_pt
))
598 if i
and not self
.kerning
and self
.spaced_pt
:
599 file.write(")%f(" % (-self
.spaced_pt
))
601 value
= encoding
[value
]
602 if 32 <= value
<= 127 and chr(value
) not in "()[]<>\\":
603 file.write("%s" % chr(value
))
605 file.write("\\%03o" % value
)
607 file.write(")] TJ\n")