1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2005-2011 André Wobst <wobsta@users.sourceforge.net>
5 # Copyright (C) 2006-2011 Jörg Lehmann <joergl@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 import array
, binascii
, math
, re
, warnings
31 from pyx
import trafo
, reader
, pycompat
32 from pyx
.path
import path
, moveto_pt
, lineto_pt
, curveto_pt
, closepath
40 adobestandardencoding
= [None, None, None, None, None, None, None, None,
41 None, None, None, None, None, None, None, None,
42 None, None, None, None, None, None, None, None,
43 None, None, None, None, None, None, None, None,
44 "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quoteright",
45 "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash",
46 "zero", "one", "two", "three", "four", "five", "six", "seven",
47 "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question",
48 "at", "A", "B", "C", "D", "E", "F", "G",
49 "H", "I", "J", "K", "L", "M", "N", "O",
50 "P", "Q", "R", "S", "T", "U", "V", "W",
51 "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore",
52 "quoteleft", "a", "b", "c", "d", "e", "f", "g",
53 "h", "i", "j", "k", "l", "m", "n", "o",
54 "p", "q", "r", "s", "t", "u", "v", "w",
55 "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", None,
56 None, None, None, None, None, None, None, None,
57 None, None, None, None, None, None, None, None,
58 None, None, None, None, None, None, None, None,
59 None, None, None, None, None, None, None, None,
60 None, "exclamdown", "cent", "sterling", "fraction", "yen", "florin", "section",
61 "currency", "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl",
62 None, "endash", "dagger", "daggerdbl", "periodcentered", None, "paragraph", "bullet",
63 "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand", None, "questiondown",
64 None, "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent",
65 "dieresis", None, "ring", "cedilla", None, "hungarumlaut", "ogonek", "caron",
66 "emdash", None, None, None, None, None, None, None,
67 None, None, None, None, None, None, None, None,
68 None, "AE", None, "ordfeminine", None, None, None, None,
69 "Lslash", "Oslash", "OE", "ordmasculine", None, None, None, None,
70 None, "ae", None, None, None, "dotlessi", None, None,
71 "lslash", "oslash", "oe", "germandbls", None, None, None, None]
75 def __init__(self
, t1font
):
76 """context for T1cmd evaluation"""
88 ######################################################################
90 # Note, that the T1 commands are variable-free except for plain number,
91 # which are stored as integers. All other T1 commands exist as a single
99 def __init__(self
, code
, subcmd
=0):
103 T1subcmds
[code
] = self
108 """returns a string representation of the T1 command"""
109 raise NotImplementedError
111 def updatepath(self
, path
, trafo
, context
):
112 """update path instance applying trafo to the points"""
113 raise NotImplementedError
115 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
116 """gather dependancy information
118 subrs is the "called-subrs" dictionary. gathercalls will insert the
119 subr number as key having the value 1, i.e. subrs will become the
120 numbers of used subrs. Similar seacglyphs will contain all glyphs in
121 composite characters (subrs and othersubrs for those glyphs will also
122 already be included) and othersubrs the othersubrs called.
124 This method might will not properly update all information in the
125 context (especially consuming values from the stack) and will also skip
126 various tests for performance reasons. For most T1 commands it just
127 doesn't need to do anything.
132 # commands for starting and finishing
134 class _T1endchar(T1cmd
):
137 T1cmd
.__init
__(self
, 14)
142 def updatepath(self
, path
, trafo
, context
):
145 T1endchar
= _T1endchar()
148 class _T1hsbw(T1cmd
):
151 T1cmd
.__init
__(self
, 13)
156 def updatepath(self
, path
, trafo
, context
):
157 sbx
= context
.t1stack
.pop(0)
158 wx
= context
.t1stack
.pop(0)
159 path
.append(moveto_pt(*trafo
.apply_pt(sbx
, 0)))
168 class _T1seac(T1cmd
):
171 T1cmd
.__init
__(self
, 6, subcmd
=1)
176 def updatepath(self
, path
, atrafo
, context
):
177 sab
= context
.t1stack
.pop(0)
178 adx
= context
.t1stack
.pop(0)
179 ady
= context
.t1stack
.pop(0)
180 bchar
= context
.t1stack
.pop(0)
181 achar
= context
.t1stack
.pop(0)
182 aglyph
= adobestandardencoding
[achar
]
183 bglyph
= adobestandardencoding
[bchar
]
184 context
.t1font
.updateglyphpath(bglyph
, path
, atrafo
, context
)
185 atrafo
= atrafo
* trafo
.translate_pt(adx
-sab
, ady
)
186 context
.t1font
.updateglyphpath(aglyph
, path
, atrafo
, context
)
188 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
189 bchar
= context
.t1stack
.pop()
190 achar
= context
.t1stack
.pop()
191 aglyph
= adobestandardencoding
[achar
]
192 bglyph
= adobestandardencoding
[bchar
]
193 seacglyphs
.add(aglyph
)
194 seacglyphs
.add(bglyph
)
195 context
.t1font
.gatherglyphcalls(bglyph
, seacglyphs
, subrs
, othersubrs
, context
)
196 context
.t1font
.gatherglyphcalls(aglyph
, seacglyphs
, subrs
, othersubrs
, context
)
204 T1cmd
.__init
__(self
, 7, subcmd
=1)
209 def updatepath(self
, path
, trafo
, context
):
210 sbx
= context
.t1stack
.pop(0)
211 sby
= context
.t1stack
.pop(0)
212 wx
= context
.t1stack
.pop(0)
213 wy
= context
.t1stack
.pop(0)
214 path
.append(moveto_pt(*trafo
.apply_pt(sbx
, sby
)))
223 # path construction commands
225 class _T1closepath(T1cmd
):
228 T1cmd
.__init
__(self
, 9)
233 def updatepath(self
, path
, trafo
, context
):
234 path
.append(closepath())
235 # The closepath in T1 is different from PostScripts in that it does
236 # *not* modify the current position; hence we need to add an additional
238 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
, context
.y
)))
240 T1closepath
= _T1closepath()
243 class _T1hlineto(T1cmd
):
246 T1cmd
.__init
__(self
, 6)
251 def updatepath(self
, path
, trafo
, context
):
252 dx
= context
.t1stack
.pop(0)
253 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
)))
256 T1hlineto
= _T1hlineto()
259 class _T1hmoveto(T1cmd
):
262 T1cmd
.__init
__(self
, 22)
267 def updatepath(self
, path
, trafo
, context
):
268 dx
= context
.t1stack
.pop(0)
269 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
)))
272 T1hmoveto
= _T1hmoveto()
275 class _T1hvcurveto(T1cmd
):
278 T1cmd
.__init
__(self
, 31)
283 def updatepath(self
, path
, trafo
, context
):
284 dx1
= context
.t1stack
.pop(0)
285 dx2
= context
.t1stack
.pop(0)
286 dy2
= context
.t1stack
.pop(0)
287 dy3
= context
.t1stack
.pop(0)
288 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
+ dx1
, context
.y
) +
289 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy2
) +
290 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy2
+ dy3
))))
294 T1hvcurveto
= _T1hvcurveto()
297 class _T1rlineto(T1cmd
):
300 T1cmd
.__init
__(self
, 5)
305 def updatepath(self
, path
, trafo
, context
):
306 dx
= context
.t1stack
.pop(0)
307 dy
= context
.t1stack
.pop(0)
308 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
+ dy
)))
312 T1rlineto
= _T1rlineto()
315 class _T1rmoveto(T1cmd
):
318 T1cmd
.__init
__(self
, 21)
323 def updatepath(self
, path
, trafo
, context
):
324 dx
= context
.t1stack
.pop(0)
325 dy
= context
.t1stack
.pop(0)
326 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
+ dy
)))
330 T1rmoveto
= _T1rmoveto()
333 class _T1rrcurveto(T1cmd
):
336 T1cmd
.__init
__(self
, 8)
341 def updatepath(self
, path
, trafo
, context
):
342 dx1
= context
.t1stack
.pop(0)
343 dy1
= context
.t1stack
.pop(0)
344 dx2
= context
.t1stack
.pop(0)
345 dy2
= context
.t1stack
.pop(0)
346 dx3
= context
.t1stack
.pop(0)
347 dy3
= context
.t1stack
.pop(0)
348 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
+ dx1
, context
.y
+ dy1
) +
349 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy1
+ dy2
) +
350 trafo
.apply_pt(context
.x
+ dx1
+ dx2
+ dx3
, context
.y
+ dy1
+ dy2
+ dy3
))))
351 context
.x
+= dx1
+dx2
+dx3
352 context
.y
+= dy1
+dy2
+dy3
354 T1rrcurveto
= _T1rrcurveto()
357 class _T1vlineto(T1cmd
):
360 T1cmd
.__init
__(self
, 7)
365 def updatepath(self
, path
, trafo
, context
):
366 dy
= context
.t1stack
.pop(0)
367 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
, context
.y
+ dy
)))
370 T1vlineto
= _T1vlineto()
373 class _T1vmoveto(T1cmd
):
376 T1cmd
.__init
__(self
, 4)
381 def updatepath(self
, path
, trafo
, context
):
382 dy
= context
.t1stack
.pop(0)
383 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
, context
.y
+ dy
)))
386 T1vmoveto
= _T1vmoveto()
389 class _T1vhcurveto(T1cmd
):
392 T1cmd
.__init
__(self
, 30)
397 def updatepath(self
, path
, trafo
, context
):
398 dy1
= context
.t1stack
.pop(0)
399 dx2
= context
.t1stack
.pop(0)
400 dy2
= context
.t1stack
.pop(0)
401 dx3
= context
.t1stack
.pop(0)
402 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
, context
.y
+ dy1
) +
403 trafo
.apply_pt(context
.x
+ dx2
, context
.y
+ dy1
+ dy2
) +
404 trafo
.apply_pt(context
.x
+ dx2
+ dx3
, context
.y
+ dy1
+ dy2
))))
408 T1vhcurveto
= _T1vhcurveto()
413 class _T1dotsection(T1cmd
):
416 T1cmd
.__init
__(self
, 0, subcmd
=1)
421 def updatepath(self
, path
, trafo
, context
):
424 T1dotsection
= _T1dotsection()
427 class _T1hstem(T1cmd
):
430 T1cmd
.__init
__(self
, 1)
435 def updatepath(self
, path
, trafo
, context
):
436 y
= context
.t1stack
.pop(0)
437 dy
= context
.t1stack
.pop(0)
442 class _T1hstem3(T1cmd
):
445 T1cmd
.__init
__(self
, 2, subcmd
=1)
450 def updatepath(self
, path
, trafo
, context
):
451 y0
= context
.t1stack
.pop(0)
452 dy0
= context
.t1stack
.pop(0)
453 y1
= context
.t1stack
.pop(0)
454 dy1
= context
.t1stack
.pop(0)
455 y2
= context
.t1stack
.pop(0)
456 dy2
= context
.t1stack
.pop(0)
458 T1hstem3
= _T1hstem3()
461 class _T1vstem(T1cmd
):
464 T1cmd
.__init
__(self
, 3)
469 def updatepath(self
, path
, trafo
, context
):
470 x
= context
.t1stack
.pop(0)
471 dx
= context
.t1stack
.pop(0)
476 class _T1vstem3(T1cmd
):
479 T1cmd
.__init
__(self
, 1, subcmd
=1)
484 def updatepath(self
, path
, trafo
, context
):
485 self
.x0
= context
.t1stack
.pop(0)
486 self
.dx0
= context
.t1stack
.pop(0)
487 self
.x1
= context
.t1stack
.pop(0)
488 self
.dx1
= context
.t1stack
.pop(0)
489 self
.x2
= context
.t1stack
.pop(0)
490 self
.dx2
= context
.t1stack
.pop(0)
492 T1vstem3
= _T1vstem3()
500 T1cmd
.__init
__(self
, 12, subcmd
=1)
505 def updatepath(self
, path
, trafo
, context
):
506 num2
= context
.t1stack
.pop()
507 num1
= context
.t1stack
.pop()
508 context
.t1stack
.append(divmod(num1
, num2
)[0])
510 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
511 num2
= context
.t1stack
.pop()
512 num1
= context
.t1stack
.pop()
513 context
.t1stack
.append(divmod(num1
, num2
)[0])
518 # subroutine commands
520 class _T1callothersubr(T1cmd
):
523 T1cmd
.__init
__(self
, 16, subcmd
=1)
526 return "callothersubr"
528 def updatepath(self
, path
, trafo
, context
):
529 othersubrnumber
= context
.t1stack
.pop()
530 n
= context
.t1stack
.pop()
532 context
.psstack
.append(context
.t1stack
.pop())
534 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
535 othersubrnumber
= context
.t1stack
.pop()
536 othersubrs
.add(othersubrnumber
)
537 n
= context
.t1stack
.pop()
539 context
.psstack
.append(context
.t1stack
.pop())
541 T1callothersubr
= _T1callothersubr()
544 class _T1callsubr(T1cmd
):
547 T1cmd
.__init
__(self
, 10)
552 def updatepath(self
, path
, trafo
, context
):
553 subr
= context
.t1stack
.pop()
554 context
.t1font
.updatesubrpath(subr
, path
, trafo
, context
)
556 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
557 subr
= context
.t1stack
.pop()
559 context
.t1font
.gathersubrcalls(subr
, seacglyphs
, subrs
, othersubrs
, context
)
561 T1callsubr
= _T1callsubr()
567 T1cmd
.__init
__(self
, 17, subcmd
=1)
572 def updatepath(self
, path
, trafo
, context
):
573 context
.t1stack
.append(context
.psstack
.pop())
575 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
576 context
.t1stack
.append(context
.psstack
.pop())
581 class _T1return(T1cmd
):
584 T1cmd
.__init
__(self
, 11)
589 def updatepath(self
, path
, trafo
, context
):
592 T1return
= _T1return()
595 class _T1setcurrentpoint(T1cmd
):
598 T1cmd
.__init
__(self
, 33, subcmd
=1)
601 return "setcurrentpoint"
603 def updatepath(self
, path
, trafo
, context
):
604 x
= context
.t1stack
.pop(0)
605 y
= context
.t1stack
.pop(0)
606 path
.append(moveto_pt(*trafo
.apply_pt(x
, y
)))
610 T1setcurrentpoint
= _T1setcurrentpoint()
613 ######################################################################
620 fontnamepattern
= re
.compile("/FontName\s+/(.*?)\s+def\s+")
621 fontmatrixpattern
= re
.compile("/FontMatrix\s*\[\s*(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)\s*\]\s*(readonly\s+)?def")
623 def __init__(self
, data1
, data2eexec
, data3
):
624 """initializes a t1font instance
626 data1 and data3 are the two clear text data parts and data2 is
627 the binary data part"""
629 self
._data
2eexec
= data2eexec
632 # marker and value for decoded data
634 # note that data2eexec is set to none by setsubrcmds and setglyphcmds
635 # this *also* denotes, that data2 is out-of-date; hence they are both
636 # marked by an _ and getdata2 and getdata2eexec will properly resolve
637 # the current state of decoding ...
639 # marker and value for standard encoding check
642 self
.name
, = self
.fontnamepattern
.search(self
.data1
).groups()
643 m11
, m12
, m21
, m22
, v1
, v2
= map(float, self
.fontmatrixpattern
.search(self
.data1
).groups()[:6])
644 self
.fontmatrix
= trafo
.trafo_pt(matrix
=((m11
, m12
), (m21
, m22
)), vector
=(v1
, v2
))
646 def _eexecdecode(self
, code
):
647 """eexec decoding of code"""
648 return decoder(code
, self
.eexecr
, 4)
650 def _charstringdecode(self
, code
):
651 """charstring decoding of code"""
652 return decoder(code
, self
.charstringr
, self
.lenIV
)
654 def _eexecencode(self
, data
):
655 """eexec encoding of data"""
656 return encoder(data
, self
.eexecr
, "PyX!")
658 def _charstringencode(self
, data
):
659 """eexec encoding of data"""
660 return encoder(data
, self
.charstringr
, "PyX!"[:self
.lenIV
])
663 """helper method to lookup the encoding in the font"""
664 c
= reader
.PStokenizer(self
.data1
, "/Encoding")
665 token1
= c
.gettoken()
666 token2
= c
.gettoken()
667 if token1
== "StandardEncoding" and token2
== "def":
668 self
.encoding
= adobestandardencoding
670 self
.encoding
= [None]*256
672 self
.encodingstart
= c
.pos
673 if c
.gettoken() == "dup":
679 self
.encoding
[i
] = glyph
[1:]
680 token
= c
.gettoken(); assert token
== "put"
681 self
.encodingend
= c
.pos
683 if token
== "readonly" or token
== "def":
685 assert token
== "dup"
687 lenIVpattern
= re
.compile("/lenIV\s+(\d+)\s+def\s+")
688 flexhintsubrs
= [[3, 0, T1callothersubr
, T1pop
, T1pop
, T1setcurrentpoint
, T1return
],
689 [0, 1, T1callothersubr
, T1return
],
690 [0, 2, T1callothersubr
, T1return
],
693 def _data2decode(self
):
694 """decodes data2eexec to the data2 string and the subr and glyphs dictionary
696 It doesn't make sense to call this method twice -- check the content of
697 data2 before calling. The method also keeps the subrs and charstrings
698 start and end positions for later use."""
699 self
._data
2 = self
._eexecdecode
(self
._data
2eexec
)
701 m
= self
.lenIVpattern
.search(self
._data
2)
703 self
.lenIV
= int(m
.group(1))
706 self
.emptysubr
= self
._charstringencode
(chr(11))
709 c
= reader
.PStokenizer(self
._data
2, "/Subrs")
710 self
.subrsstart
= c
.pos
711 arraycount
= c
.getint()
712 token
= c
.gettoken(); assert token
== "array"
714 for i
in range(arraycount
):
715 token
= c
.gettoken(); assert token
== "dup"
716 token
= c
.getint(); assert token
== i
719 self
.subrrdtoken
= c
.gettoken()
721 token
= c
.gettoken(); assert token
== self
.subrrdtoken
722 self
.subrs
.append(c
.getbytes(size
))
724 if token
== "noaccess":
725 token
= "%s %s" % (token
, c
.gettoken())
727 self
.subrnptoken
= token
729 assert token
== self
.subrnptoken
730 self
.subrsend
= c
.pos
732 # hasflexhintsubrs is a boolean indicating that the font uses flex or
733 # hint replacement subrs as specified by Adobe (tm). When it does, the
734 # first 4 subrs should all be copied except when none of them are used
735 # in the stripped version of the font since we then get a font not
736 # using flex or hint replacement subrs at all.
737 self
.hasflexhintsubrs
= (arraycount
>= len(self
.flexhintsubrs
) and
739 for i
in range(len(self
.flexhintsubrs
))] == self
.flexhintsubrs
)
743 self
.glyphlist
= [] # we want to keep the order of the glyph names
744 c
= reader
.PStokenizer(self
._data
2, "/CharStrings")
745 self
.charstringsstart
= c
.pos
747 token
= c
.gettoken(); assert token
== "dict"
748 token
= c
.gettoken(); assert token
== "dup"
749 token
= c
.gettoken(); assert token
== "begin"
752 chartoken
= c
.gettoken()
753 if chartoken
== "end":
755 assert chartoken
[0] == "/"
758 self
.glyphrdtoken
= c
.gettoken()
760 token
= c
.gettoken(); assert token
== self
.glyphrdtoken
761 self
.glyphlist
.append(chartoken
[1:])
762 self
.glyphs
[chartoken
[1:]] = c
.getbytes(size
)
764 self
.glyphndtoken
= c
.gettoken()
766 token
= c
.gettoken(); assert token
== self
.glyphndtoken
768 self
.charstringsend
= c
.pos
769 assert not self
.subrs
or self
.subrrdtoken
== self
.glyphrdtoken
771 def _cmds(self
, code
):
772 """return a list of T1cmd's for encoded charstring data in code"""
773 code
= array
.array("B", self
._charstringdecode
(code
))
777 if x
== 12: # this starts an escaped cmd
778 cmds
.append(T1subcmds
[code
.pop(0)])
779 elif 0 <= x
< 32: # those are cmd's
780 cmds
.append(T1cmds
[x
])
781 elif 32 <= x
<= 246: # short ints
783 elif 247 <= x
<= 250: # mid size ints
784 cmds
.append(((x
- 247)*256) + code
.pop(0) + 108)
785 elif 251 <= x
<= 254: # mid size ints
786 cmds
.append(-((x
- 251)*256) - code
.pop(0) - 108)
787 else: # x = 255, i.e. full size ints
788 y
= ((code
.pop(0)*256l+code
.pop(0))*256+code
.pop(0))*256+code
.pop(0)
790 cmds
.append(y
- (1l << 32))
795 def _code(self
, cmds
):
796 """return an encoded charstring data for list of T1cmd's in cmds"""
797 code
= array
.array("B")
802 code
.append(cmd
.code
)
803 except AttributeError:
804 if -107 <= cmd
<= 107:
806 elif 108 <= cmd
<= 1131:
807 a
, b
= divmod(cmd
-108, 256)
810 elif -1131 <= cmd
<= -108:
811 a
, b
= divmod(-cmd
-108, 256)
817 cmd
, x4
= divmod(cmd
, 256)
818 cmd
, x3
= divmod(cmd
, 256)
819 x1
, x2
= divmod(cmd
, 256)
825 return self
._charstringencode
(code
.tostring())
827 def getsubrcmds(self
, subr
):
828 """return a list of T1cmd's for subr subr"""
831 return self
._cmds
(self
.subrs
[subr
])
833 def getglyphcmds(self
, glyph
):
834 """return a list of T1cmd's for glyph glyph"""
837 return self
._cmds
(self
.glyphs
[glyph
])
839 def setsubrcmds(self
, subr
, cmds
):
840 """replaces the T1cmd's by the list cmds for subr subr"""
843 self
._data
2eexec
= None
844 self
.subrs
[subr
] = self
._code
(cmds
)
846 def setglyphcmds(self
, glyph
, cmds
):
847 """replaces the T1cmd's by the list cmds for glyph glyph"""
850 self
._data
2eexec
= None
851 self
.glyphs
[glyph
] = self
._code
(cmds
)
853 def updatepath(self
, cmds
, path
, trafo
, context
):
855 if isinstance(cmd
, T1cmd
):
856 cmd
.updatepath(path
, trafo
, context
)
858 context
.t1stack
.append(cmd
)
860 def updatesubrpath(self
, subr
, path
, trafo
, context
):
861 self
.updatepath(self
.getsubrcmds(subr
), path
, trafo
, context
)
863 def updateglyphpath(self
, glyph
, path
, trafo
, context
):
864 self
.updatepath(self
.getglyphcmds(glyph
), path
, trafo
, context
)
866 def gathercalls(self
, cmds
, seacglyphs
, subrs
, othersubrs
, context
):
868 if isinstance(cmd
, T1cmd
):
869 cmd
.gathercalls(seacglyphs
, subrs
, othersubrs
, context
)
871 context
.t1stack
.append(cmd
)
873 def gathersubrcalls(self
, subr
, seacglyphs
, subrs
, othersubrs
, context
):
874 self
.gathercalls(self
.getsubrcmds(subr
), seacglyphs
, subrs
, othersubrs
, context
)
876 def gatherglyphcalls(self
, glyph
, seacglyphs
, subrs
, othersubrs
, context
):
877 self
.gathercalls(self
.getglyphcmds(glyph
), seacglyphs
, subrs
, othersubrs
, context
)
879 def getglyphpathwxwy_pt(self
, glyph
, size
, convertcharcode
=False):
881 if not self
.encoding
:
883 glyph
= self
.encoding
[glyph
]
884 t
= self
.fontmatrix
.scaled(size
)
885 context
= T1context(self
)
887 self
.updateglyphpath(glyph
, p
, t
, context
)
888 wx
, wy
= t
.apply_pt(context
.wx
, context
.wy
)
891 def getglyphpath(self
, glyph
, size
, convertcharcode
=False):
892 """return a PyX path for glyph named glyph"""
893 return self
.getglyphpathwxwy_pt(glyph
, size
)[0]
895 def getglyphwxwy_pt(self
, glyph
, size
, convertcharcode
=False):
896 return self
.getglyphpathwxwy_pt(glyph
, size
)[1:]
898 def getdata2(self
, subrs
=None, glyphs
=None):
899 """makes a data2 string
901 subrs is a dict containing those subrs numbers as keys,
902 which are to be contained in the subrsstring to be created.
903 If subrs is None, all subrs in self.subrs will be used.
904 The subrs dict might be modified *in place*.
906 glyphs is a dict containing those glyph names as keys,
907 which are to be contained in the charstringsstring to be created.
908 If glyphs is None, all glyphs in self.glyphs will be used."""
909 def addsubrs(subrs
, result
):
910 if subrs
is not None:
911 # some adjustments to the subrs dict
913 subrsmin
= min(subrs
)
914 subrsmax
= max(subrs
)
915 if self
.hasflexhintsubrs
and subrsmin
< len(self
.flexhintsubrs
):
916 # According to the spec we need to keep all the flex and hint subrs
917 # as long as any of it is used.
918 for subr
in range(len(self
.flexhintsubrs
)):
923 # build a new subrs dict containing all subrs
924 subrs
= dict([(subr
, 1) for subr
in range(len(self
.subrs
))])
925 subrsmax
= len(self
.subrs
) - 1
927 # build the string from all selected subrs
928 result
.append("%d array\n" % (subrsmax
+ 1))
929 for subr
in range(subrsmax
+1):
931 code
= self
.subrs
[subr
]
933 code
= self
.emptysubr
934 result
.append("dup %d %d %s %s %s\n" % (subr
, len(code
), self
.subrrdtoken
, code
, self
.subrnptoken
))
936 def addcharstrings(glyphs
, result
):
937 result
.append("%d dict dup begin\n" % (glyphs
is None and len(self
.glyphlist
) or len(glyphs
)))
938 for glyph
in self
.glyphlist
:
939 if glyphs
is None or glyph
in glyphs
:
940 result
.append("/%s %d %s %s %s\n" % (glyph
, len(self
.glyphs
[glyph
]), self
.glyphrdtoken
, self
.glyphs
[glyph
], self
.glyphndtoken
))
941 result
.append("end\n")
943 if self
.subrsstart
< self
.charstringsstart
:
944 result
= [self
._data
2[:self
.subrsstart
]]
945 addsubrs(subrs
, result
)
946 result
.append(self
._data
2[self
.subrsend
:self
.charstringsstart
])
947 addcharstrings(glyphs
, result
)
948 result
.append(self
._data
2[self
.charstringsend
:])
950 result
= [self
._data
2[:self
.charstringsstart
]]
951 addcharstrings(glyphs
, result
)
952 result
.append(self
._data
2[self
.charstringsend
:self
.subrsstart
])
953 addsubrs(subrs
, result
)
954 result
.append(self
._data
2[self
.subrsend
:])
955 return "".join(result
)
957 def getdata2eexec(self
):
959 return self
._data
2eexec
960 # note that self._data2 is out-of-date here too, hence we need to call getdata2
961 return self
._eexecencode
(self
.getdata2())
963 newlinepattern
= re
.compile("\s*[\r\n]\s*")
964 uniqueidpattern
= re
.compile("%?/UniqueID\s+\d+\s+def\s+")
965 # when UniqueID is commented out (as in modern latin), prepare to remove the comment character as well
967 def getstrippedfont(self
, glyphs
, charcodes
):
968 """create a T1file instance containing only certain glyphs
970 glyphs is a set of the glyph names. It might be modified *in place*!
972 # TODO: we could also strip othersubrs to those actually used
973 if not self
.encoding
:
975 for charcode
in charcodes
:
976 glyphs
.add(self
.encoding
[charcode
])
978 # collect information about used glyphs and subrs
979 seacglyphs
= pycompat
.set()
980 subrs
= pycompat
.set()
981 othersubrs
= pycompat
.set()
983 self
.gatherglyphcalls(glyph
, seacglyphs
, subrs
, othersubrs
, T1context(self
))
984 # while we have gathered all subrs for the seacglyphs alreadys, we
985 # might have missed the glyphs themself (when they are not used stand-alone)
986 glyphs
.update(seacglyphs
)
987 glyphs
.add(".notdef")
990 if self
.encoding
is adobestandardencoding
:
994 for char
, glyph
in enumerate(self
.encoding
):
996 encodingstrings
.append("dup %i /%s put\n" % (char
, glyph
))
997 data1
= self
.data1
[:self
.encodingstart
] + "".join(encodingstrings
) + self
.data1
[self
.encodingend
:]
998 data1
= self
.newlinepattern
.subn("\n", data1
)[0]
999 data1
= self
.uniqueidpattern
.subn("", data1
)[0]
1002 data2
= self
.uniqueidpattern
.subn("", self
.getdata2(subrs
, glyphs
))[0]
1005 data3
= self
.newlinepattern
.subn("\n", self
.data3
)[0]
1007 # create and return the new font instance
1008 return T1file(data1
.rstrip() + "\n", self
._eexecencode
(data2
), data3
.rstrip() + "\n")
1010 # The following two methods, writePDFfontinfo and getglyphinfo,
1011 # extract informtion which should better be taken from the afm file.
1012 def writePDFfontinfo(self
, file):
1014 glyphinfo_y
= self
.getglyphinfo("y")
1015 glyphinfo_W
= self
.getglyphinfo("W")
1016 glyphinfo_H
= self
.getglyphinfo("H")
1017 glyphinfo_h
= self
.getglyphinfo("h")
1018 glyphinfo_period
= self
.getglyphinfo("period")
1019 glyphinfo_colon
= self
.getglyphinfo("colon")
1021 warnings
.warn("Auto-guessing of font information for font '%s' failed. We're writing stub data instead." % self
.name
)
1022 file.write("/Flags 4\n")
1023 file.write("/FontBBox [0 -100 1000 1000]\n")
1024 file.write("/ItalicAngle 0\n")
1025 file.write("/Ascent 1000\n")
1026 file.write("/Descent -100\n")
1027 file.write("/CapHeight 700\n")
1028 file.write("/StemV 100\n")
1030 if not self
.encoding
:
1032 # As a simple heuristics we assume non-symbolic fonts if and only
1033 # if the Adobe standard encoding is used. All other font flags are
1034 # not specified here.
1035 if self
.encoding
is adobestandardencoding
:
1036 file.write("/Flags 32\n")
1038 file.write("/Flags 4\n")
1039 file.write("/FontBBox [0 %f %f %f]\n" % (glyphinfo_y
[3], glyphinfo_W
[0], glyphinfo_H
[5]))
1040 file.write("/ItalicAngle %f\n" % math
.degrees(math
.atan2(glyphinfo_period
[4]-glyphinfo_colon
[4], glyphinfo_colon
[5]-glyphinfo_period
[5])))
1041 file.write("/Ascent %f\n" % glyphinfo_H
[5])
1042 file.write("/Descent %f\n" % glyphinfo_y
[3])
1043 file.write("/CapHeight %f\n" % glyphinfo_h
[5])
1044 file.write("/StemV %f\n" % (glyphinfo_period
[4]-glyphinfo_period
[2]))
1046 def getglyphinfo(self
, glyph
):
1047 warnings
.warn("We are about to extract font information for the Type 1 font '%s' from its pfb file. This is bad practice (and it's slow). You should use an afm file instead." % self
.name
)
1048 context
= T1context(self
)
1050 self
.updateglyphpath(glyph
, p
, trafo
.trafo(), context
)
1052 return context
.wx
, context
.wy
, bbox
.llx_pt
, bbox
.lly_pt
, bbox
.urx_pt
, bbox
.ury_pt
1054 def outputPFA(self
, file):
1055 """output the T1file in PFA format"""
1056 file.write(self
.data1
)
1057 data2eexechex
= binascii
.b2a_hex(self
.getdata2eexec())
1059 for i
in range((len(data2eexechex
)-1)/linelength
+ 1):
1060 file.write(data2eexechex
[i
*linelength
: i
*linelength
+linelength
])
1062 file.write(self
.data3
)
1064 def outputPFB(self
, file):
1065 """output the T1file in PFB format"""
1066 data2eexec
= self
.getdata2eexec()
1067 def pfblength(data
):
1069 l
, x1
= divmod(l
, 256)
1070 l
, x2
= divmod(l
, 256)
1071 x4
, x3
= divmod(l
, 256)
1072 return chr(x1
) + chr(x2
) + chr(x3
) + chr(x4
)
1073 file.write("\200\1")
1074 file.write(pfblength(self
.data1
))
1075 file.write(self
.data1
)
1076 file.write("\200\2")
1077 file.write(pfblength(data2eexec
))
1078 file.write(data2eexec
)
1079 file.write("\200\1")
1080 file.write(pfblength(self
.data3
))
1081 file.write(self
.data3
)
1082 file.write("\200\3")
1084 def outputPS(self
, file, writer
):
1085 """output the PostScript code for the T1file to the file file"""
1086 self
.outputPFA(file)
1088 def outputPDF(self
, file, writer
):
1089 data2eexec
= self
.getdata2eexec()
1091 # we might be allowed to skip the third part ...
1092 if (data3
.replace("\n", "")
1095 .replace(" ", "")) == "0"*512 + "cleartomark":
1098 data
= self
.data1
+ data2eexec
+ data3
1099 if writer
.compress
and haszlib
:
1100 data
= zlib
.compress(data
)
1106 "/Length3 %d\n" % (len(data
), len(self
.data1
), len(data2eexec
), len(data3
)))
1107 if writer
.compress
and haszlib
:
1108 file.write("/Filter /FlateDecode\n")
1117 class FontFormatError(Exception):
1120 def from_PFA_bytes(bytes
):
1121 """create a T1file instance from a string of bytes corresponding to a PFA file"""
1123 m1
= bytes
.index("eexec") + 6
1124 m2
= bytes
.index("0"*40)
1126 raise FontFormatError
1129 data2
= binascii
.a2b_hex(bytes
[m1
: m2
].replace(" ", "").replace("\r", "").replace("\n", ""))
1131 return T1file(data1
, data2
, data3
)
1133 def from_PFA_filename(filename
):
1134 """create a T1file instance from PFA font file of given name"""
1135 file = open(filename
, "rb")
1136 t1file
= from_PFA_bytes(file.read())
1140 def from_PFB_bytes(bytes
):
1141 """create a T1file instance from a string of bytes corresponding to a PFB file"""
1145 raise ValueError("invalid string length")
1149 ord(s
[3])*256*256*256)
1151 def __init__(self
, bytes
):
1154 def __call__(self
, n
):
1155 result
= self
.bytes
[self
.pos
:self
.pos
+n
]
1159 consume
= consumer(bytes
)
1161 if mark
!= "\200\1":
1162 raise FontFormatError
1163 data1
= consume(pfblength(consume(4)))
1165 if mark
!= "\200\2":
1166 raise FontFormatError
1168 while mark
== "\200\2":
1169 data2
= data2
+ consume(pfblength(consume(4)))
1171 if mark
!= "\200\1":
1172 raise FontFormatError
1173 data3
= consume(pfblength(consume(4)))
1175 if mark
!= "\200\3":
1176 raise FontFormatError
1178 raise FontFormatError
1180 return T1file(data1
, data2
, data3
)
1182 def from_PFB_filename(filename
):
1183 """create a T1file instance from PFB font file of given name"""
1184 file = open(filename
, "rb")
1185 t1file
= from_PFB_bytes(file.read())
1189 def from_PF_bytes(bytes
):
1191 return from_PFB_bytes(bytes
)
1192 except FontFormatError
:
1193 return from_PFA_bytes(bytes
)
1195 def from_PF_filename(filename
):
1196 """create a T1file instance from PFA or PFB font file of given name"""
1197 file = open(filename
, "rb")
1198 t1file
= from_PF_bytes(file.read())