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 getglyphpath_pt(self
, x_pt
, y_pt
, glyph
, size_pt
, convertcharcode
=False):
880 """return an object containing the PyX path, wx_pt and wy_pt for glyph named glyph"""
882 if not self
.encoding
:
884 glyph
= self
.encoding
[glyph
]
885 t
= self
.fontmatrix
.scaled(size_pt
)
886 tpath
= t
.translated_pt(x_pt
, y_pt
)
887 context
= T1context(self
)
889 self
.updateglyphpath(glyph
, p
, tpath
, context
)
891 def __init__(self
, p
, wx_pt
, wy_pt
):
895 return glyphpath(p
, *t
.apply_pt(context
.wx
, context
.wy
))
897 def getdata2(self
, subrs
=None, glyphs
=None):
898 """makes a data2 string
900 subrs is a dict containing those subrs numbers as keys,
901 which are to be contained in the subrsstring to be created.
902 If subrs is None, all subrs in self.subrs will be used.
903 The subrs dict might be modified *in place*.
905 glyphs is a dict containing those glyph names as keys,
906 which are to be contained in the charstringsstring to be created.
907 If glyphs is None, all glyphs in self.glyphs will be used."""
908 def addsubrs(subrs
, result
):
909 if subrs
is not None:
910 # some adjustments to the subrs dict
912 subrsmin
= min(subrs
)
913 subrsmax
= max(subrs
)
914 if self
.hasflexhintsubrs
and subrsmin
< len(self
.flexhintsubrs
):
915 # According to the spec we need to keep all the flex and hint subrs
916 # as long as any of it is used.
917 for subr
in range(len(self
.flexhintsubrs
)):
922 # build a new subrs dict containing all subrs
923 subrs
= dict([(subr
, 1) for subr
in range(len(self
.subrs
))])
924 subrsmax
= len(self
.subrs
) - 1
926 # build the string from all selected subrs
927 result
.append("%d array\n" % (subrsmax
+ 1))
928 for subr
in range(subrsmax
+1):
930 code
= self
.subrs
[subr
]
932 code
= self
.emptysubr
933 result
.append("dup %d %d %s %s %s\n" % (subr
, len(code
), self
.subrrdtoken
, code
, self
.subrnptoken
))
935 def addcharstrings(glyphs
, result
):
936 result
.append("%d dict dup begin\n" % (glyphs
is None and len(self
.glyphlist
) or len(glyphs
)))
937 for glyph
in self
.glyphlist
:
938 if glyphs
is None or glyph
in glyphs
:
939 result
.append("/%s %d %s %s %s\n" % (glyph
, len(self
.glyphs
[glyph
]), self
.glyphrdtoken
, self
.glyphs
[glyph
], self
.glyphndtoken
))
940 result
.append("end\n")
942 if self
.subrsstart
< self
.charstringsstart
:
943 result
= [self
._data
2[:self
.subrsstart
]]
944 addsubrs(subrs
, result
)
945 result
.append(self
._data
2[self
.subrsend
:self
.charstringsstart
])
946 addcharstrings(glyphs
, result
)
947 result
.append(self
._data
2[self
.charstringsend
:])
949 result
= [self
._data
2[:self
.charstringsstart
]]
950 addcharstrings(glyphs
, result
)
951 result
.append(self
._data
2[self
.charstringsend
:self
.subrsstart
])
952 addsubrs(subrs
, result
)
953 result
.append(self
._data
2[self
.subrsend
:])
954 return "".join(result
)
956 def getdata2eexec(self
):
958 return self
._data
2eexec
959 # note that self._data2 is out-of-date here too, hence we need to call getdata2
960 return self
._eexecencode
(self
.getdata2())
962 newlinepattern
= re
.compile("\s*[\r\n]\s*")
963 uniqueidpattern
= re
.compile("%?/UniqueID\s+\d+\s+def\s+")
964 # when UniqueID is commented out (as in modern latin), prepare to remove the comment character as well
966 def getstrippedfont(self
, glyphs
, charcodes
):
967 """create a T1file instance containing only certain glyphs
969 glyphs is a set of the glyph names. It might be modified *in place*!
971 # TODO: we could also strip othersubrs to those actually used
972 if not self
.encoding
:
974 for charcode
in charcodes
:
975 glyphs
.add(self
.encoding
[charcode
])
977 # collect information about used glyphs and subrs
978 seacglyphs
= pycompat
.set()
979 subrs
= pycompat
.set()
980 othersubrs
= pycompat
.set()
982 self
.gatherglyphcalls(glyph
, seacglyphs
, subrs
, othersubrs
, T1context(self
))
983 # while we have gathered all subrs for the seacglyphs alreadys, we
984 # might have missed the glyphs themself (when they are not used stand-alone)
985 glyphs
.update(seacglyphs
)
986 glyphs
.add(".notdef")
989 if self
.encoding
is adobestandardencoding
:
993 for char
, glyph
in enumerate(self
.encoding
):
995 encodingstrings
.append("dup %i /%s put\n" % (char
, glyph
))
996 data1
= self
.data1
[:self
.encodingstart
] + "\n" + "".join(encodingstrings
) + self
.data1
[self
.encodingend
:]
997 data1
= self
.newlinepattern
.subn("\n", data1
)[0]
998 data1
= self
.uniqueidpattern
.subn("", data1
)[0]
1001 data2
= self
.uniqueidpattern
.subn("", self
.getdata2(subrs
, glyphs
))[0]
1004 data3
= self
.newlinepattern
.subn("\n", self
.data3
)[0]
1006 # create and return the new font instance
1007 return T1file(data1
.rstrip() + "\n", self
._eexecencode
(data2
), data3
.rstrip() + "\n")
1009 # The following two methods, writePDFfontinfo and getglyphinfo,
1010 # extract informtion which should better be taken from the afm file.
1011 def writePDFfontinfo(self
, file):
1013 glyphinfo_y
= self
.getglyphinfo("y")
1014 glyphinfo_W
= self
.getglyphinfo("W")
1015 glyphinfo_H
= self
.getglyphinfo("H")
1016 glyphinfo_h
= self
.getglyphinfo("h")
1017 glyphinfo_period
= self
.getglyphinfo("period")
1018 glyphinfo_colon
= self
.getglyphinfo("colon")
1020 warnings
.warn("Auto-guessing of font information for font '%s' failed. We're writing stub data instead." % self
.name
)
1021 file.write("/Flags 4\n")
1022 file.write("/FontBBox [0 -100 1000 1000]\n")
1023 file.write("/ItalicAngle 0\n")
1024 file.write("/Ascent 1000\n")
1025 file.write("/Descent -100\n")
1026 file.write("/CapHeight 700\n")
1027 file.write("/StemV 100\n")
1029 if not self
.encoding
:
1031 # As a simple heuristics we assume non-symbolic fonts if and only
1032 # if the Adobe standard encoding is used. All other font flags are
1033 # not specified here.
1034 if self
.encoding
is adobestandardencoding
:
1035 file.write("/Flags 32\n")
1037 file.write("/Flags 4\n")
1038 file.write("/FontBBox [0 %f %f %f]\n" % (glyphinfo_y
[3], glyphinfo_W
[0], glyphinfo_H
[5]))
1039 file.write("/ItalicAngle %f\n" % math
.degrees(math
.atan2(glyphinfo_period
[4]-glyphinfo_colon
[4], glyphinfo_colon
[5]-glyphinfo_period
[5])))
1040 file.write("/Ascent %f\n" % glyphinfo_H
[5])
1041 file.write("/Descent %f\n" % glyphinfo_y
[3])
1042 file.write("/CapHeight %f\n" % glyphinfo_h
[5])
1043 file.write("/StemV %f\n" % (glyphinfo_period
[4]-glyphinfo_period
[2]))
1045 def getglyphinfo(self
, glyph
):
1046 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
)
1047 context
= T1context(self
)
1049 self
.updateglyphpath(glyph
, p
, trafo
.trafo(), context
)
1051 return context
.wx
, context
.wy
, bbox
.llx_pt
, bbox
.lly_pt
, bbox
.urx_pt
, bbox
.ury_pt
1053 def outputPFA(self
, file):
1054 """output the T1file in PFA format"""
1055 file.write(self
.data1
)
1056 data2eexechex
= binascii
.b2a_hex(self
.getdata2eexec())
1058 for i
in range((len(data2eexechex
)-1)/linelength
+ 1):
1059 file.write(data2eexechex
[i
*linelength
: i
*linelength
+linelength
])
1061 file.write(self
.data3
)
1063 def outputPFB(self
, file):
1064 """output the T1file in PFB format"""
1065 data2eexec
= self
.getdata2eexec()
1066 def pfblength(data
):
1068 l
, x1
= divmod(l
, 256)
1069 l
, x2
= divmod(l
, 256)
1070 x4
, x3
= divmod(l
, 256)
1071 return chr(x1
) + chr(x2
) + chr(x3
) + chr(x4
)
1072 file.write("\200\1")
1073 file.write(pfblength(self
.data1
))
1074 file.write(self
.data1
)
1075 file.write("\200\2")
1076 file.write(pfblength(data2eexec
))
1077 file.write(data2eexec
)
1078 file.write("\200\1")
1079 file.write(pfblength(self
.data3
))
1080 file.write(self
.data3
)
1081 file.write("\200\3")
1083 def outputPS(self
, file, writer
):
1084 """output the PostScript code for the T1file to the file file"""
1085 self
.outputPFA(file)
1087 def outputPDF(self
, file, writer
):
1088 data2eexec
= self
.getdata2eexec()
1090 # we might be allowed to skip the third part ...
1091 if (data3
.replace("\n", "")
1094 .replace(" ", "")) == "0"*512 + "cleartomark":
1097 data
= self
.data1
+ data2eexec
+ data3
1098 if writer
.compress
and haszlib
:
1099 data
= zlib
.compress(data
)
1105 "/Length3 %d\n" % (len(data
), len(self
.data1
), len(data2eexec
), len(data3
)))
1106 if writer
.compress
and haszlib
:
1107 file.write("/Filter /FlateDecode\n")
1116 class FontFormatError(Exception):
1119 def from_PFA_bytes(bytes
):
1120 """create a T1file instance from a string of bytes corresponding to a PFA file"""
1122 m1
= bytes
.index("eexec") + 6
1123 m2
= bytes
.index("0"*40)
1125 raise FontFormatError
1128 data2
= binascii
.a2b_hex(bytes
[m1
: m2
].replace(" ", "").replace("\r", "").replace("\n", ""))
1130 return T1file(data1
, data2
, data3
)
1132 def from_PFA_filename(filename
):
1133 """create a T1file instance from PFA font file of given name"""
1134 file = open(filename
, "rb")
1135 t1file
= from_PFA_bytes(file.read())
1139 def from_PFB_bytes(bytes
):
1140 """create a T1file instance from a string of bytes corresponding to a PFB file"""
1144 raise ValueError("invalid string length")
1148 ord(s
[3])*256*256*256)
1150 def __init__(self
, bytes
):
1153 def __call__(self
, n
):
1154 result
= self
.bytes
[self
.pos
:self
.pos
+n
]
1158 consume
= consumer(bytes
)
1160 if mark
!= "\200\1":
1161 raise FontFormatError
1162 data1
= consume(pfblength(consume(4)))
1164 if mark
!= "\200\2":
1165 raise FontFormatError
1167 while mark
== "\200\2":
1168 data2
= data2
+ consume(pfblength(consume(4)))
1170 if mark
!= "\200\1":
1171 raise FontFormatError
1172 data3
= consume(pfblength(consume(4)))
1174 if mark
!= "\200\3":
1175 raise FontFormatError
1177 raise FontFormatError
1179 return T1file(data1
, data2
, data3
)
1181 def from_PFB_filename(filename
):
1182 """create a T1file instance from PFB font file of given name"""
1183 file = open(filename
, "rb")
1184 t1file
= from_PFB_bytes(file.read())
1188 def from_PF_bytes(bytes
):
1190 return from_PFB_bytes(bytes
)
1191 except FontFormatError
:
1192 return from_PFA_bytes(bytes
)
1194 def from_PF_filename(filename
):
1195 """create a T1file instance from PFA or PFB font file of given name"""
1196 file = open(filename
, "rb")
1197 t1file
= from_PF_bytes(file.read())