1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2005-2011 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2006-2011 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2005-2011 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
25 from pyx
import bbox
, canvasitem
, deco
, path
, pswriter
, pdfwriter
, trafo
, unit
, pycompat
26 import t1file
, afmfile
29 ##############################################################################
31 ##############################################################################
33 class PST1file(pswriter
.PSresource
):
35 """ PostScript font definition included in the prolog """
37 def __init__(self
, t1file
, glyphnames
, charcodes
):
38 """ include type 1 font t1file stripped to the given glyphnames"""
42 self
.glyphnames
= pycompat
.set(glyphnames
)
43 self
.charcodes
= pycompat
.set(charcodes
)
45 def merge(self
, other
):
46 self
.glyphnames
.update(other
.glyphnames
)
47 self
.charcodes
.update(other
.charcodes
)
49 def output(self
, file, writer
, registry
):
50 file.write("%%%%BeginFont: %s\n" % self
.t1file
.name
)
51 if writer
.strip_fonts
:
53 file.write("%%Included glyphs: %s\n" % " ".join(self
.glyphnames
))
55 file.write("%%Included charcodes: %s\n" % " ".join([str(charcode
) for charcode
in self
.charcodes
]))
56 self
.t1file
.getstrippedfont(self
.glyphnames
, self
.charcodes
).outputPS(file, writer
)
58 self
.t1file
.outputPS(file, writer
)
59 file.write("\n%%EndFont\n")
62 _ReEncodeFont
= pswriter
.PSdefinition("ReEncodeFont", """{
67 /basefontname exch def
68 /basefontdict basefontname findfont def
69 /newfontdict basefontdict maxlength dict def
71 exch dup dup /FID ne exch /Encoding ne and
72 { exch newfontdict 3 1 roll put }
76 newfontdict /FontName newfontname put
77 newfontdict /Encoding newencoding put
78 newfontname newfontdict definefont pop
83 class PSreencodefont(pswriter
.PSresource
):
85 """ reencoded PostScript font"""
87 def __init__(self
, basefontname
, newfontname
, encoding
):
88 """ reencode the font """
90 self
.type = "reencodefont"
91 self
.basefontname
= basefontname
92 self
.id = self
.newfontname
= newfontname
93 self
.encoding
= encoding
95 def output(self
, file, writer
, registry
):
96 file.write("%%%%BeginResource: %s\n" % self
.newfontname
)
97 file.write("/%s /%s\n[" % (self
.basefontname
, self
.newfontname
))
98 vector
= [None] * len(self
.encoding
)
99 for glyphname
, charcode
in self
.encoding
.items():
100 vector
[charcode
] = glyphname
101 for i
, glyphname
in enumerate(vector
):
107 file.write("/%s" % glyphname
)
109 file.write("ReEncodeFont\n")
110 file.write("%%EndResource\n")
113 _ChangeFontMatrix
= pswriter
.PSdefinition("ChangeFontMatrix", """{
116 /newfontmatrix exch def
117 /newfontname exch def
118 /basefontname exch def
119 /basefontdict basefontname findfont def
120 /newfontdict basefontdict maxlength dict def
122 exch dup dup /FID ne exch /FontMatrix ne and
123 { exch newfontdict 3 1 roll put }
127 newfontdict /FontName newfontname put
128 newfontdict /FontMatrix newfontmatrix readonly put
129 newfontname newfontdict definefont pop
134 class PSchangefontmatrix(pswriter
.PSresource
):
136 """ change font matrix of a PostScript font"""
138 def __init__(self
, basefontname
, newfontname
, newfontmatrix
):
139 """ change the font matrix """
141 self
.type = "changefontmatrix"
142 self
.basefontname
= basefontname
143 self
.id = self
.newfontname
= newfontname
144 self
.newfontmatrix
= newfontmatrix
146 def output(self
, file, writer
, registry
):
147 file.write("%%%%BeginResource: %s\n" % self
.newfontname
)
148 file.write("/%s /%s\n" % (self
.basefontname
, self
.newfontname
))
149 file.write(str(self
.newfontmatrix
))
150 file.write("\nChangeFontMatrix\n")
151 file.write("%%EndResource\n")
154 ##############################################################################
156 ##############################################################################
158 class PDFfont(pdfwriter
.PDFobject
):
160 def __init__(self
, fontname
, basefontname
, charcodes
, fontdescriptor
, encoding
, metric
):
161 pdfwriter
.PDFobject
.__init
__(self
, "font", fontname
)
163 self
.fontname
= fontname
164 self
.basefontname
= basefontname
165 self
.charcodes
= pycompat
.set(charcodes
)
166 self
.fontdescriptor
= fontdescriptor
167 self
.encoding
= encoding
170 def merge(self
, other
):
171 self
.charcodes
.update(other
.charcodes
)
173 def write(self
, file, writer
, registry
):
177 file.write("/Name /%s\n" % self
.fontname
)
178 file.write("/BaseFont /%s\n" % self
.basefontname
)
179 firstchar
= min(self
.charcodes
)
180 lastchar
= max(self
.charcodes
)
181 file.write("/FirstChar %d\n" % firstchar
)
182 file.write("/LastChar %d\n" % lastchar
)
183 file.write("/Widths\n"
186 encoding
= self
.encoding
.getvector()
188 encoding
= self
.fontdescriptor
.fontfile
.t1file
.encoding
189 for i
in range(firstchar
, lastchar
+1):
195 if i
in self
.charcodes
:
196 if self
.metric
is not None:
197 file.write("%i" % self
.metric
.width_ds(encoding
[i
]))
199 file.write("%i" % self
.fontdescriptor
.fontfile
.t1file
.getglyphinfo(encoding
[i
])[0])
203 file.write("/FontDescriptor %d 0 R\n" % registry
.getrefno(self
.fontdescriptor
))
205 file.write("/Encoding %d 0 R\n" % registry
.getrefno(self
.encoding
))
209 class PDFstdfont(pdfwriter
.PDFobject
):
211 def __init__(self
, basename
):
212 pdfwriter
.PDFobject
.__init
__(self
, "font", "stdfont-%s" % basename
)
213 self
.name
= basename
# name is ignored by acroread
214 self
.basename
= basename
216 def write(self
, file, writer
, registry
):
217 file.write("<</BaseFont /%s\n" % self
.basename
)
218 file.write("/Name /%s\n" % self
.name
)
219 file.write("/Type /Font\n")
220 file.write("/Subtype /Type1\n")
223 # the 14 standard fonts that are always available in PDF
224 PDFTimesRoman
= PDFstdfont("Times-Roman")
225 PDFTimesBold
= PDFstdfont("Times-Bold")
226 PDFTimesItalic
= PDFstdfont("Times-Italic")
227 PDFTimesBoldItalic
= PDFstdfont("Times-BoldItalic")
228 PDFHelvetica
= PDFstdfont("Helvetica")
229 PDFHelveticaBold
= PDFstdfont("Helvetica-Bold")
230 PDFHelveticaOblique
= PDFstdfont("Helvetica-Oblique")
231 PDFHelveticaBoldOblique
= PDFstdfont("Helvetica-BoldOblique")
232 PDFCourier
= PDFstdfont("Courier")
233 PDFCourierBold
= PDFstdfont("Courier-Bold")
234 PDFCourierOblique
= PDFstdfont("Courier-Oblique")
235 PDFCourierBoldOblique
= PDFstdfont("Courier-BoldOblique")
236 PDFSymbol
= PDFstdfont("Symbol")
237 PDFZapfDingbats
= PDFstdfont("ZapfDingbats")
240 class PDFfontdescriptor(pdfwriter
.PDFobject
):
242 def __init__(self
, fontname
, fontfile
, metric
):
243 pdfwriter
.PDFobject
.__init
__(self
, "fontdescriptor", fontname
)
244 self
.fontname
= fontname
245 self
.fontfile
= fontfile
248 def write(self
, file, writer
, registry
):
250 "/Type /FontDescriptor\n"
251 "/FontName /%s\n" % self
.fontname
)
252 if self
.metric
is not None:
253 self
.metric
.writePDFfontinfo(file)
255 self
.fontfile
.t1file
.writePDFfontinfo(file)
256 if self
.fontfile
is not None:
257 file.write("/FontFile %d 0 R\n" % registry
.getrefno(self
.fontfile
))
261 class PDFfontfile(pdfwriter
.PDFobject
):
263 def __init__(self
, t1file
, glyphnames
, charcodes
):
264 pdfwriter
.PDFobject
.__init
__(self
, "fontfile", t1file
.name
)
266 self
.glyphnames
= pycompat
.set(glyphnames
)
267 self
.charcodes
= pycompat
.set(charcodes
)
269 def merge(self
, other
):
270 self
.glyphnames
.update(other
.glyphnames
)
271 self
.charcodes
.update(other
.charcodes
)
273 def write(self
, file, writer
, registry
):
274 if writer
.strip_fonts
:
275 self
.t1file
.getstrippedfont(self
.glyphnames
, self
.charcodes
).outputPDF(file, writer
)
277 self
.t1file
.outputPDF(file, writer
)
280 class PDFencoding(pdfwriter
.PDFobject
):
282 def __init__(self
, encoding
, name
):
283 pdfwriter
.PDFobject
.__init
__(self
, "encoding", name
)
284 self
.encoding
= encoding
287 # As self.encoding might be appended after the constructur has set it,
288 # we need to defer the calculation until the whole content was constructed.
289 vector
= [None] * len(self
.encoding
)
290 for glyphname
, charcode
in self
.encoding
.items():
291 vector
[charcode
] = glyphname
294 def write(self
, file, writer
, registry
):
299 for i
, glyphname
in enumerate(self
.getvector()):
305 file.write("/%s" % glyphname
)
310 ##############################################################################
311 # basic PyX text output
312 ##############################################################################
316 def text(self
, x
, y
, charcodes
, size_pt
, **kwargs
):
317 return self
.text_pt(unit
.topt(x
), unit
.topt(y
), charcodes
, size_pt
, **kwargs
)
322 def __init__(self
, t1file
, metric
=None):
324 self
.name
= t1file
.name
327 def text_pt(self
, x
, y
, charcodes
, size_pt
, **kwargs
):
328 return T1text_pt(self
, x
, y
, charcodes
, size_pt
, **kwargs
)
331 class T1builtinfont(T1font
):
333 def __init__(self
, name
, metric
):
341 def __init__(self
, name
, size_pt
):
343 self
.size_pt
= size_pt
345 def __ne__(self
, other
):
346 return self
.name
!= other
.name
or self
.size_pt
!= other
.size_pt
348 def outputPS(self
, file, writer
):
349 file.write("/%s %f selectfont\n" % (self
.name
, self
.size_pt
))
351 def outputPDF(self
, file, writer
):
352 file.write("/%s %f Tf\n" % (self
.name
, self
.size_pt
))
355 class text_pt(canvasitem
.canvasitem
):
360 class T1text_pt(text_pt
):
362 def __init__(self
, font
, x_pt
, y_pt
, charcodes
, size_pt
, decoding
=afmfile
.unicodestring
, slant
=None, ignorebbox
=False, kerning
=False, ligatures
=False, spaced_pt
=0):
363 if decoding
is not None:
364 self
.glyphnames
= [decoding
[character
] for character
in charcodes
]
367 self
.charcodes
= charcodes
372 self
.size_pt
= size_pt
374 self
.ignorebbox
= ignorebbox
375 self
.kerning
= kerning
376 self
.ligatures
= ligatures
377 self
.spaced_pt
= spaced_pt
378 self
._textpath
= None
380 if self
.kerning
and not self
.decode
:
381 raise ValueError("decoding required for font metric access (kerning)")
382 if self
.ligatures
and not self
.decode
:
383 raise ValueError("decoding required for font metric access (ligatures)")
385 self
.glyphnames
= self
.font
.metric
.resolveligatures(self
.glyphnames
)
388 if self
.font
.metric
is None:
389 warnings
.warn("We are about to extract the bounding box from the path of the text. This is slow and differs from the font metric information. You should provide an afm file whenever possible.")
390 return self
.textpath().bbox()
392 raise ValueError("decoding required for font metric access (bbox)")
393 return bbox
.bbox_pt(self
.x_pt
,
394 self
.y_pt
+self
.font
.metric
.depth_pt(self
.glyphnames
, self
.size_pt
),
395 self
.x_pt
+self
.font
.metric
.width_pt(self
.glyphnames
, self
.size_pt
),
396 self
.y_pt
+self
.font
.metric
.height_pt(self
.glyphnames
, self
.size_pt
))
398 def getencodingname(self
, encodings
):
399 """returns the name of the encoding (in encodings) mapping self.glyphnames to codepoints
400 If no such encoding can be found or extended, a new encoding is added to encodings
402 glyphnames
= pycompat
.set(self
.glyphnames
)
403 if len(glyphnames
) > 256:
404 raise ValueError("glyphs do not fit into one single encoding")
405 for encodingname
, encoding
in encodings
.items():
407 for glyphname
in glyphnames
:
408 if glyphname
not in encoding
.keys():
409 glyphsmissing
.append(glyphname
)
411 if len(glyphsmissing
) + len(encoding
) < 256:
412 # new glyphs fit in existing encoding which will thus be extended
413 for glyphname
in glyphsmissing
:
414 encoding
[glyphname
] = len(encoding
)
416 # create a new encoding for the glyphnames
417 encodingname
= "encoding%d" % len(encodings
)
418 encodings
[encodingname
] = dict([(glyphname
, i
) for i
, glyphname
in enumerate(glyphnames
)])
422 if self
._textpath
is None:
425 data
= self
.font
.metric
.resolvekernings(self
.glyphnames
, self
.size_pt
)
427 data
= self
.glyphnames
429 data
= self
.charcodes
430 self
._textpath
= path
.path()
433 for i
, value
in enumerate(data
):
434 if self
.kerning
and i
% 2:
435 if value
is not None:
439 x_pt
+= self
.spaced_pt
440 glyphpath
= self
.font
.t1file
.getglyphpath_pt(x_pt
, y_pt
, value
, self
.size_pt
, convertcharcode
=not self
.decode
)
441 self
._textpath
+= glyphpath
.path
442 x_pt
+= glyphpath
.wx_pt
443 y_pt
+= glyphpath
.wy_pt
444 return self
._textpath
446 def processPS(self
, file, writer
, context
, registry
, bbox
):
447 if not self
.ignorebbox
:
450 if writer
.text_as_path
:
451 deco
.decoratedpath(self
.textpath(), fillstyles
=[]).processPS(file, writer
, context
, registry
, bbox
)
454 if self
.font
.t1file
is not None:
456 registry
.add(PST1file(self
.font
.t1file
, self
.glyphnames
, []))
458 registry
.add(PST1file(self
.font
.t1file
, [], self
.charcodes
))
460 fontname
= self
.font
.name
462 encodingname
= self
.getencodingname(writer
.encodings
.setdefault(self
.font
.name
, {}))
463 encoding
= writer
.encodings
[self
.font
.name
][encodingname
]
464 newfontname
= "%s-%s" % (fontname
, encodingname
)
465 registry
.add(_ReEncodeFont
)
466 registry
.add(PSreencodefont(fontname
, newfontname
, encoding
))
467 fontname
= newfontname
470 newfontmatrix
= trafo
.trafo_pt(matrix
=((1, self
.slant
), (0, 1)))
471 if self
.font
.t1file
is not None:
472 newfontmatrix
= newfontmatrix
* self
.font
.t1file
.fontmatrix
473 newfontname
= "%s-slant%f" % (fontname
, self
.slant
)
474 registry
.add(_ChangeFontMatrix
)
475 registry
.add(PSchangefontmatrix(fontname
, newfontname
, newfontmatrix
))
476 fontname
= newfontname
478 # select font if necessary
479 sf
= selectedfont(fontname
, self
.size_pt
)
480 if context
.selectedfont
is None or sf
!= context
.selectedfont
:
481 context
.selectedfont
= sf
482 sf
.outputPS(file, writer
)
484 file.write("%f %f moveto (" % (self
.x_pt
, self
.y_pt
))
487 data
= self
.font
.metric
.resolvekernings(self
.glyphnames
, self
.size_pt
)
489 data
= self
.glyphnames
491 data
= self
.charcodes
492 for i
, value
in enumerate(data
):
493 if self
.kerning
and i
% 2:
494 if value
is not None:
495 file.write(") show\n%f 0 rmoveto (" % (value
+self
.spaced_pt
))
497 file.write(") show\n%f 0 rmoveto (" % self
.spaced_pt
)
499 if i
and not self
.kerning
and self
.spaced_pt
:
500 file.write(") show\n%f 0 rmoveto (" % self
.spaced_pt
)
502 value
= encoding
[value
]
503 if 32 < value
< 127 and chr(value
) not in "()[]<>\\":
504 file.write("%s" % chr(value
))
506 file.write("\\%03o" % value
)
507 file.write(") show\n")
509 def processPDF(self
, file, writer
, context
, registry
, bbox
):
510 if not self
.ignorebbox
:
513 if writer
.text_as_path
:
514 deco
.decoratedpath(self
.textpath(), fillstyles
=[]).processPDF(file, writer
, context
, registry
, bbox
)
517 encodingname
= self
.getencodingname(writer
.encodings
.setdefault(self
.font
.name
, {}))
518 encoding
= writer
.encodings
[self
.font
.name
][encodingname
]
519 charcodes
= [encoding
[glyphname
] for glyphname
in self
.glyphnames
]
521 charcodes
= self
.charcodes
524 fontname
= self
.font
.name
526 newfontname
= "%s-%s" % (fontname
, encodingname
)
527 _encoding
= PDFencoding(encoding
, newfontname
)
528 fontname
= newfontname
531 if self
.font
.t1file
is not None:
533 fontfile
= PDFfontfile(self
.font
.t1file
, self
.glyphnames
, [])
535 fontfile
= PDFfontfile(self
.font
.t1file
, [], self
.charcodes
)
538 fontdescriptor
= PDFfontdescriptor(self
.font
.name
, fontfile
, self
.font
.metric
)
539 font
= PDFfont(fontname
, self
.font
.name
, charcodes
, fontdescriptor
, _encoding
, self
.font
.metric
)
542 if fontfile
is not None:
543 registry
.add(fontfile
)
544 registry
.add(fontdescriptor
)
545 if _encoding
is not None:
546 registry
.add(_encoding
)
549 registry
.addresource("Font", fontname
, font
, procset
="Text")
551 if self
.slant
is None:
554 slantvalue
= self
.slant
556 # select font if necessary
557 sf
= selectedfont(fontname
, self
.size_pt
)
558 if context
.selectedfont
is None or sf
!= context
.selectedfont
:
559 context
.selectedfont
= sf
560 sf
.outputPDF(file, writer
)
563 file.write("1 0 %f 1 %f %f Tm [(" % (slantvalue
, self
.x_pt
, self
.y_pt
))
565 file.write("1 0 %f 1 %f %f Tm (" % (slantvalue
, self
.x_pt
, self
.y_pt
))
568 data
= self
.font
.metric
.resolvekernings(self
.glyphnames
)
570 data
= self
.glyphnames
572 data
= self
.charcodes
573 for i
, value
in enumerate(data
):
574 if self
.kerning
and i
% 2:
575 if value
is not None:
576 file.write(")%f(" % (-value
-self
.spaced_pt
))
578 file.write(")%f(" % (-self
.spaced_pt
))
580 if i
and not self
.kerning
and self
.spaced_pt
:
581 file.write(")%f(" % (-self
.spaced_pt
))
583 value
= encoding
[value
]
584 if 32 <= value
<= 127 and chr(value
) not in "()[]<>\\":
585 file.write("%s" % chr(value
))
587 file.write("\\%03o" % value
)
589 file.write(")] TJ\n")