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
, io
, logging
, math
, re
30 logger
= logging
.getLogger("pyx")
32 from pyx
import trafo
, reader
, writer
33 from pyx
.path
import path
, moveto_pt
, lineto_pt
, curveto_pt
, closepath
41 adobestandardencoding
= [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 None, None, None, None, None, None, None, None,
45 "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quoteright",
46 "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash",
47 "zero", "one", "two", "three", "four", "five", "six", "seven",
48 "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question",
49 "at", "A", "B", "C", "D", "E", "F", "G",
50 "H", "I", "J", "K", "L", "M", "N", "O",
51 "P", "Q", "R", "S", "T", "U", "V", "W",
52 "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore",
53 "quoteleft", "a", "b", "c", "d", "e", "f", "g",
54 "h", "i", "j", "k", "l", "m", "n", "o",
55 "p", "q", "r", "s", "t", "u", "v", "w",
56 "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", 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, None, None, None, None, None, None, None,
61 None, "exclamdown", "cent", "sterling", "fraction", "yen", "florin", "section",
62 "currency", "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl",
63 None, "endash", "dagger", "daggerdbl", "periodcentered", None, "paragraph", "bullet",
64 "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand", None, "questiondown",
65 None, "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent",
66 "dieresis", None, "ring", "cedilla", None, "hungarumlaut", "ogonek", "caron",
67 "emdash", None, None, None, None, None, None, None,
68 None, None, None, None, None, None, None, None,
69 None, "AE", None, "ordfeminine", None, None, None, None,
70 "Lslash", "Oslash", "OE", "ordmasculine", None, None, None, None,
71 None, "ae", None, None, None, "dotlessi", None, None,
72 "lslash", "oslash", "oe", "germandbls", None, None, None, None]
76 def __init__(self
, t1font
, flex
=True):
77 """context for T1cmd evaluation"""
90 ######################################################################
92 # Note, that the T1 commands are variable-free except for plain number,
93 # which are stored as integers. All other T1 commands exist as a single
101 def __init__(self
, code
, subcmd
=0):
105 T1subcmds
[code
] = self
110 """returns a string representation of the T1 command"""
111 raise NotImplementedError
113 def updatepath(self
, path
, trafo
, context
):
114 """update path instance applying trafo to the points"""
115 raise NotImplementedError
117 def gathercalls(self
, seacglyphs
, subrs
, context
):
118 """gather dependancy information
120 subrs is the "called-subrs" dictionary. gathercalls will insert the
121 subr number as key having the value 1, i.e. subrs will become the
122 numbers of used subrs. Similar seacglyphs will contain all glyphs in
123 composite characters (subrs for those glyphs will also
124 already be included).
126 This method might will not properly update all information in the
127 context (especially consuming values from the stack) and will also skip
128 various tests for performance reasons. For most T1 commands it just
129 doesn't need to do anything.
134 # commands for starting and finishing
136 class _T1endchar(T1cmd
):
139 T1cmd
.__init
__(self
, 14)
144 def updatepath(self
, path
, trafo
, context
):
147 T1endchar
= _T1endchar()
150 class _T1hsbw(T1cmd
):
153 T1cmd
.__init
__(self
, 13)
158 def updatepath(self
, path
, trafo
, context
):
159 sbx
= context
.t1stack
.pop(0)
160 wx
= context
.t1stack
.pop(0)
161 path
.append(moveto_pt(*trafo
.apply_pt(sbx
, 0)))
170 class _T1seac(T1cmd
):
173 T1cmd
.__init
__(self
, 6, subcmd
=1)
178 def updatepath(self
, path
, atrafo
, context
):
179 sab
= context
.t1stack
.pop(0)
180 adx
= context
.t1stack
.pop(0)
181 ady
= context
.t1stack
.pop(0)
182 bchar
= context
.t1stack
.pop(0)
183 achar
= context
.t1stack
.pop(0)
184 aglyph
= adobestandardencoding
[achar
]
185 bglyph
= adobestandardencoding
[bchar
]
186 context
.t1font
.updateglyphpath(bglyph
, path
, atrafo
, context
)
187 atrafo
= atrafo
* trafo
.translate_pt(adx
-sab
, ady
)
188 context
.t1font
.updateglyphpath(aglyph
, path
, atrafo
, context
)
190 def gathercalls(self
, seacglyphs
, subrs
, context
):
191 achar
= context
.t1stack
.pop()
192 bchar
= context
.t1stack
.pop()
193 aglyph
= adobestandardencoding
[achar
]
194 bglyph
= adobestandardencoding
[bchar
]
195 seacglyphs
.add(aglyph
)
196 seacglyphs
.add(bglyph
)
197 context
.t1font
.gatherglyphcalls(bglyph
, seacglyphs
, subrs
, context
)
198 context
.t1font
.gatherglyphcalls(aglyph
, seacglyphs
, subrs
, context
)
206 T1cmd
.__init
__(self
, 7, subcmd
=1)
211 def updatepath(self
, path
, trafo
, context
):
212 sbx
= context
.t1stack
.pop(0)
213 sby
= context
.t1stack
.pop(0)
214 wx
= context
.t1stack
.pop(0)
215 wy
= context
.t1stack
.pop(0)
216 path
.append(moveto_pt(*trafo
.apply_pt(sbx
, sby
)))
225 # path construction commands
227 class _T1closepath(T1cmd
):
230 T1cmd
.__init
__(self
, 9)
235 def updatepath(self
, path
, trafo
, context
):
236 path
.append(closepath())
237 # The closepath in T1 is different from PostScripts in that it does
238 # *not* modify the current position; hence we need to add an additional
240 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
, context
.y
)))
242 T1closepath
= _T1closepath()
245 class _T1hlineto(T1cmd
):
248 T1cmd
.__init
__(self
, 6)
253 def updatepath(self
, path
, trafo
, context
):
254 dx
= context
.t1stack
.pop(0)
255 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
)))
258 T1hlineto
= _T1hlineto()
261 class _T1hmoveto(T1cmd
):
264 T1cmd
.__init
__(self
, 22)
269 def updatepath(self
, path
, trafo
, context
):
270 dx
= context
.t1stack
.pop(0)
271 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
)))
274 T1hmoveto
= _T1hmoveto()
277 class _T1hvcurveto(T1cmd
):
280 T1cmd
.__init
__(self
, 31)
285 def updatepath(self
, path
, trafo
, context
):
286 dx1
= context
.t1stack
.pop(0)
287 dx2
= context
.t1stack
.pop(0)
288 dy2
= context
.t1stack
.pop(0)
289 dy3
= context
.t1stack
.pop(0)
290 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
+ dx1
, context
.y
) +
291 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy2
) +
292 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy2
+ dy3
))))
296 T1hvcurveto
= _T1hvcurveto()
299 class _T1rlineto(T1cmd
):
302 T1cmd
.__init
__(self
, 5)
307 def updatepath(self
, path
, trafo
, context
):
308 dx
= context
.t1stack
.pop(0)
309 dy
= context
.t1stack
.pop(0)
310 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
+ dy
)))
314 T1rlineto
= _T1rlineto()
317 class _T1rmoveto(T1cmd
):
320 T1cmd
.__init
__(self
, 21)
325 def updatepath(self
, path
, trafo
, context
):
326 dx
= context
.t1stack
.pop(0)
327 dy
= context
.t1stack
.pop(0)
328 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
+ dy
)))
332 T1rmoveto
= _T1rmoveto()
335 class _T1rrcurveto(T1cmd
):
338 T1cmd
.__init
__(self
, 8)
343 def updatepath(self
, path
, trafo
, context
):
344 dx1
= context
.t1stack
.pop(0)
345 dy1
= context
.t1stack
.pop(0)
346 dx2
= context
.t1stack
.pop(0)
347 dy2
= context
.t1stack
.pop(0)
348 dx3
= context
.t1stack
.pop(0)
349 dy3
= context
.t1stack
.pop(0)
350 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
+ dx1
, context
.y
+ dy1
) +
351 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy1
+ dy2
) +
352 trafo
.apply_pt(context
.x
+ dx1
+ dx2
+ dx3
, context
.y
+ dy1
+ dy2
+ dy3
))))
353 context
.x
+= dx1
+dx2
+dx3
354 context
.y
+= dy1
+dy2
+dy3
356 T1rrcurveto
= _T1rrcurveto()
359 class _T1vlineto(T1cmd
):
362 T1cmd
.__init
__(self
, 7)
367 def updatepath(self
, path
, trafo
, context
):
368 dy
= context
.t1stack
.pop(0)
369 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
, context
.y
+ dy
)))
372 T1vlineto
= _T1vlineto()
375 class _T1vmoveto(T1cmd
):
378 T1cmd
.__init
__(self
, 4)
383 def updatepath(self
, path
, trafo
, context
):
384 dy
= context
.t1stack
.pop(0)
385 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
, context
.y
+ dy
)))
388 T1vmoveto
= _T1vmoveto()
391 class _T1vhcurveto(T1cmd
):
394 T1cmd
.__init
__(self
, 30)
399 def updatepath(self
, path
, trafo
, context
):
400 dy1
= context
.t1stack
.pop(0)
401 dx2
= context
.t1stack
.pop(0)
402 dy2
= context
.t1stack
.pop(0)
403 dx3
= context
.t1stack
.pop(0)
404 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
, context
.y
+ dy1
) +
405 trafo
.apply_pt(context
.x
+ dx2
, context
.y
+ dy1
+ dy2
) +
406 trafo
.apply_pt(context
.x
+ dx2
+ dx3
, context
.y
+ dy1
+ dy2
))))
410 T1vhcurveto
= _T1vhcurveto()
415 class _T1dotsection(T1cmd
):
418 T1cmd
.__init
__(self
, 0, subcmd
=1)
423 def updatepath(self
, path
, trafo
, context
):
426 T1dotsection
= _T1dotsection()
429 class _T1hstem(T1cmd
):
432 T1cmd
.__init
__(self
, 1)
437 def updatepath(self
, path
, trafo
, context
):
438 y
= context
.t1stack
.pop(0)
439 dy
= context
.t1stack
.pop(0)
444 class _T1hstem3(T1cmd
):
447 T1cmd
.__init
__(self
, 2, subcmd
=1)
452 def updatepath(self
, path
, trafo
, context
):
453 y0
= context
.t1stack
.pop(0)
454 dy0
= context
.t1stack
.pop(0)
455 y1
= context
.t1stack
.pop(0)
456 dy1
= context
.t1stack
.pop(0)
457 y2
= context
.t1stack
.pop(0)
458 dy2
= context
.t1stack
.pop(0)
460 T1hstem3
= _T1hstem3()
463 class _T1vstem(T1cmd
):
466 T1cmd
.__init
__(self
, 3)
471 def updatepath(self
, path
, trafo
, context
):
472 x
= context
.t1stack
.pop(0)
473 dx
= context
.t1stack
.pop(0)
478 class _T1vstem3(T1cmd
):
481 T1cmd
.__init
__(self
, 1, subcmd
=1)
486 def updatepath(self
, path
, trafo
, context
):
487 self
.x0
= context
.t1stack
.pop(0)
488 self
.dx0
= context
.t1stack
.pop(0)
489 self
.x1
= context
.t1stack
.pop(0)
490 self
.dx1
= context
.t1stack
.pop(0)
491 self
.x2
= context
.t1stack
.pop(0)
492 self
.dx2
= context
.t1stack
.pop(0)
494 T1vstem3
= _T1vstem3()
502 T1cmd
.__init
__(self
, 12, subcmd
=1)
507 def updatepath(self
, path
, trafo
, context
):
508 num2
= context
.t1stack
.pop()
509 num1
= context
.t1stack
.pop()
510 context
.t1stack
.append(divmod(num1
, num2
)[0])
512 def gathercalls(self
, seacglyphs
, subrs
, context
):
513 num2
= context
.t1stack
.pop()
514 num1
= context
.t1stack
.pop()
515 context
.t1stack
.append(divmod(num1
, num2
)[0])
520 # subroutine commands
522 class _T1callothersubr(T1cmd
):
525 T1cmd
.__init
__(self
, 16, subcmd
=1)
528 return "callothersubr"
530 def updatepath(self
, path
, trafo
, context
):
531 othersubrnumber
= context
.t1stack
.pop()
532 n
= context
.t1stack
.pop()
534 context
.psstack
.append(context
.t1stack
.pop(0))
535 if othersubrnumber
== 0:
536 flex_size
, x
, y
= context
.psstack
[-3:]
538 x1
, y1
, x2
, y2
, x3
, y3
= context
.psstack
[2:8]
539 x1
, y1
= trafo
.apply_pt(x1
, y1
)
540 x2
, y2
= trafo
.apply_pt(x2
, y2
)
541 x3
, y3
= trafo
.apply_pt(x3
, y3
)
542 path
.append(curveto_pt(x1
, y1
, x2
, y2
, x3
, y3
))
543 x1
, y1
, x2
, y2
, x3
, y3
= context
.psstack
[8:14]
544 x1
, y1
= trafo
.apply_pt(x1
, y1
)
545 x2
, y2
= trafo
.apply_pt(x2
, y2
)
546 x3
, y3
= trafo
.apply_pt(x3
, y3
)
547 path
.append(curveto_pt(x1
, y1
, x2
, y2
, x3
, y3
))
549 path
.append(lineto_pt(*trafo
.apply_pt(x
, y
)))
550 context
.psstack
= [y
, x
]
551 elif othersubrnumber
== 1:
553 elif othersubrnumber
== 2:
555 context
.psstack
.append(context
.x
)
556 context
.psstack
.append(context
.y
)
558 def gathercalls(self
, seacglyphs
, subrs
, context
):
559 othersubrnumber
= context
.t1stack
.pop()
560 n
= context
.t1stack
.pop()
561 context
.psstack
.extend([context
.t1stack
.pop() for i
in range(n
)][::-1])
563 T1callothersubr
= _T1callothersubr()
566 class _T1callsubr(T1cmd
):
569 T1cmd
.__init
__(self
, 10)
574 def updatepath(self
, path
, trafo
, context
):
575 subr
= context
.t1stack
.pop()
576 context
.t1font
.updatesubrpath(subr
, path
, trafo
, context
)
578 def gathercalls(self
, seacglyphs
, subrs
, context
):
579 subr
= context
.t1stack
.pop()
581 context
.t1font
.gathersubrcalls(subr
, seacglyphs
, subrs
, context
)
583 T1callsubr
= _T1callsubr()
589 T1cmd
.__init
__(self
, 17, subcmd
=1)
594 def updatepath(self
, path
, trafo
, context
):
595 context
.t1stack
.append(context
.psstack
.pop())
597 def gathercalls(self
, seacglyphs
, subrs
, context
):
598 context
.t1stack
.append(context
.psstack
.pop())
603 class _T1return(T1cmd
):
606 T1cmd
.__init
__(self
, 11)
611 def updatepath(self
, path
, trafo
, context
):
614 T1return
= _T1return()
617 class _T1setcurrentpoint(T1cmd
):
620 T1cmd
.__init
__(self
, 33, subcmd
=1)
623 return "setcurrentpoint"
625 def updatepath(self
, path
, trafo
, context
):
626 context
.x
= context
.t1stack
.pop(0)
627 context
.y
= context
.t1stack
.pop(0)
629 T1setcurrentpoint
= _T1setcurrentpoint()
632 ######################################################################
639 fontnamepattern
= re
.compile("/FontName\s+/(.*?)\s+def\s+")
640 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")
642 def __init__(self
, data1
, data2eexec
, data3
):
643 """initializes a t1font instance
645 data1 and data3 are the two clear text data parts and data2 is
646 the binary data part"""
648 self
._data
2eexec
= data2eexec
651 # marker and value for decoded data
653 # note that data2eexec is set to none by setsubrcmds and setglyphcmds
654 # this *also* denotes, that data2 is out-of-date; hence they are both
655 # marked by an _ and getdata2 and getdata2eexec will properly resolve
656 # the current state of decoding ...
658 # marker and value for standard encoding check
661 self
.name
, = self
.fontnamepattern
.search(self
.data1
).groups()
662 m11
, m12
, m21
, m22
, v1
, v2
= list(map(float, self
.fontmatrixpattern
.search(self
.data1
).groups()[:6]))
663 self
.fontmatrix
= trafo
.trafo_pt(matrix
=((m11
, m12
), (m21
, m22
)), vector
=(v1
, v2
))
665 def _eexecdecode(self
, code
):
666 """eexec decoding of code"""
667 return decoder(code
, self
.eexecr
, 4)
669 def _charstringdecode(self
, code
):
670 """charstring decoding of code"""
671 return decoder(code
, self
.charstringr
, self
.lenIV
)
673 def _eexecencode(self
, data
):
674 """eexec encoding of data"""
675 return encoder(data
, self
.eexecr
, b
"PyX!")
677 def _charstringencode(self
, data
):
678 """eexec encoding of data"""
679 return encoder(data
, self
.charstringr
, b
"PyX!"[:self
.lenIV
])
682 """helper method to lookup the encoding in the font"""
683 c
= reader
.PStokenizer(self
.data1
, "/Encoding")
684 token1
= c
.gettoken()
685 token2
= c
.gettoken()
686 if token1
== "StandardEncoding" and token2
== "def":
687 self
.encoding
= adobestandardencoding
689 self
.encoding
= [None]*256
691 self
.encodingstart
= c
.pos
692 if c
.gettoken() == "dup":
698 self
.encoding
[i
] = glyph
[1:]
699 token
= c
.gettoken(); assert token
== "put"
700 self
.encodingend
= c
.pos
702 if token
== "readonly" or token
== "def":
704 assert token
== "dup"
706 lenIVpattern
= re
.compile(b
"/lenIV\s+(\d+)\s+def\s+")
707 flexhintsubrs
= [[3, 0, T1callothersubr
, T1pop
, T1pop
, T1setcurrentpoint
, T1return
],
708 [0, 1, T1callothersubr
, T1return
],
709 [0, 2, T1callothersubr
, T1return
],
712 def _data2decode(self
):
713 """decodes data2eexec to the data2 string and the subr and glyphs dictionary
715 It doesn't make sense to call this method twice -- check the content of
716 data2 before calling. The method also keeps the subrs and charstrings
717 start and end positions for later use."""
718 self
._data
2 = self
._eexecdecode
(self
._data
2eexec
)
720 m
= self
.lenIVpattern
.search(self
._data
2)
722 self
.lenIV
= int(m
.group(1))
726 self
.emptysubr
= self
._charstringencode
(b
"\x0b") # 11, i.e. return
729 c
= reader
.PSbytes_tokenizer(self
._data
2, b
"/Subrs")
730 self
.subrsstart
= c
.pos
731 arraycount
= c
.getint()
732 token
= c
.gettoken(); assert token
== b
"array"
734 for i
in range(arraycount
):
735 token
= c
.gettoken(); assert token
== b
"dup"
736 token
= c
.getint(); assert token
== i
739 self
.subrrdtoken
= c
.gettoken()
741 token
= c
.gettoken(); assert token
== self
.subrrdtoken
742 self
.subrs
.append(c
.getbytes(size
))
744 if token
== b
"noaccess":
745 token
= token
+ b
" " + c
.gettoken()
747 self
.subrnptoken
= token
749 assert token
== self
.subrnptoken
750 self
.subrsend
= c
.pos
752 # hasflexhintsubrs is a boolean indicating that the font uses flex or
753 # hint replacement subrs as specified by Adobe (tm). When it does, the
754 # first 4 subrs should all be copied except when none of them are used
755 # in the stripped version of the font since we then get a font not
756 # using flex or hint replacement subrs at all.
757 self
.hasflexhintsubrs
= (arraycount
>= len(self
.flexhintsubrs
) and
759 for i
in range(len(self
.flexhintsubrs
))] == self
.flexhintsubrs
)
763 self
.glyphlist
= [] # we want to keep the order of the glyph names
764 c
= reader
.PSbytes_tokenizer(self
._data
2, b
"/CharStrings")
765 self
.charstringsstart
= c
.pos
767 token
= c
.gettoken(); assert token
== b
"dict"
768 token
= c
.gettoken(); assert token
== b
"dup"
769 token
= c
.gettoken(); assert token
== b
"begin"
772 chartoken
= c
.gettoken().decode("ascii")
773 if chartoken
== "end":
775 assert chartoken
[0] == "/"
778 self
.glyphrdtoken
= c
.gettoken()
780 token
= c
.gettoken(); assert token
== self
.glyphrdtoken
781 self
.glyphlist
.append(chartoken
[1:])
782 self
.glyphs
[chartoken
[1:]] = c
.getbytes(size
)
784 self
.glyphndtoken
= c
.gettoken()
786 token
= c
.gettoken(); assert token
== self
.glyphndtoken
788 self
.charstringsend
= c
.pos
789 assert not self
.subrs
or self
.subrrdtoken
== self
.glyphrdtoken
791 def _cmds(self
, code
):
792 """return a list of T1cmd's for encoded charstring data in code"""
793 code
= array
.array("B", self
._charstringdecode
(code
))
797 if x
== 12: # this starts an escaped cmd
798 cmds
.append(T1subcmds
[code
.pop(0)])
799 elif 0 <= x
< 32: # those are cmd's
800 cmds
.append(T1cmds
[x
])
801 elif 32 <= x
<= 246: # short ints
803 elif 247 <= x
<= 250: # mid size ints
804 cmds
.append(((x
- 247)*256) + code
.pop(0) + 108)
805 elif 251 <= x
<= 254: # mid size ints
806 cmds
.append(-((x
- 251)*256) - code
.pop(0) - 108)
807 else: # x = 255, i.e. full size ints
808 y
= ((code
.pop(0)*256+code
.pop(0))*256+code
.pop(0))*256+code
.pop(0)
810 cmds
.append(y
- (1 << 32))
815 def _code(self
, cmds
):
816 """return an encoded charstring data for list of T1cmd's in cmds"""
817 code
= array
.array("B")
822 code
.append(cmd
.code
)
823 except AttributeError:
824 if -107 <= cmd
<= 107:
826 elif 108 <= cmd
<= 1131:
827 a
, b
= divmod(cmd
-108, 256)
830 elif -1131 <= cmd
<= -108:
831 a
, b
= divmod(-cmd
-108, 256)
837 cmd
, x4
= divmod(cmd
, 256)
838 cmd
, x3
= divmod(cmd
, 256)
839 x1
, x2
= divmod(cmd
, 256)
845 return self
._charstringencode
(code
.tobytes())
847 def getsubrcmds(self
, subr
):
848 """return a list of T1cmd's for subr subr"""
851 return self
._cmds
(self
.subrs
[subr
])
853 def getglyphcmds(self
, glyph
):
854 """return a list of T1cmd's for glyph glyph"""
857 return self
._cmds
(self
.glyphs
[glyph
])
859 def setsubrcmds(self
, subr
, cmds
):
860 """replaces the T1cmd's by the list cmds for subr subr"""
863 self
._data
2eexec
= None
864 self
.subrs
[subr
] = self
._code
(cmds
)
866 def setglyphcmds(self
, glyph
, cmds
):
867 """replaces the T1cmd's by the list cmds for glyph glyph"""
870 self
._data
2eexec
= None
871 self
.glyphs
[glyph
] = self
._code
(cmds
)
873 def updatepath(self
, cmds
, path
, trafo
, context
):
875 if isinstance(cmd
, T1cmd
):
876 cmd
.updatepath(path
, trafo
, context
)
878 context
.t1stack
.append(cmd
)
880 def updatesubrpath(self
, subr
, path
, trafo
, context
):
881 self
.updatepath(self
.getsubrcmds(subr
), path
, trafo
, context
)
883 def updateglyphpath(self
, glyph
, path
, trafo
, context
):
884 self
.updatepath(self
.getglyphcmds(glyph
), path
, trafo
, context
)
886 def gathercalls(self
, cmds
, seacglyphs
, subrs
, context
):
888 if isinstance(cmd
, T1cmd
):
889 cmd
.gathercalls(seacglyphs
, subrs
, context
)
891 context
.t1stack
.append(cmd
)
893 def gathersubrcalls(self
, subr
, seacglyphs
, subrs
, context
):
894 self
.gathercalls(self
.getsubrcmds(subr
), seacglyphs
, subrs
, context
)
896 def gatherglyphcalls(self
, glyph
, seacglyphs
, subrs
, context
):
897 self
.gathercalls(self
.getglyphcmds(glyph
), seacglyphs
, subrs
, context
)
899 def getglyphpath_pt(self
, x_pt
, y_pt
, glyph
, size_pt
, convertcharcode
=False, flex
=True):
900 """return an object containing the PyX path, wx_pt and wy_pt for glyph named glyph"""
902 if not self
.encoding
:
904 glyph
= self
.encoding
[glyph
]
905 t
= self
.fontmatrix
.scaled(size_pt
)
906 tpath
= t
.translated_pt(x_pt
, y_pt
)
907 context
= T1context(self
, flex
=flex
)
909 self
.updateglyphpath(glyph
, p
, tpath
, context
)
911 def __init__(self
, p
, wx_pt
, wy_pt
):
915 return glyphpath(p
, *t
.apply_pt(context
.wx
, context
.wy
))
917 def getdata2(self
, subrs
=None, glyphs
=None):
918 """makes a data2 string
920 subrs is a dict containing those subrs numbers as keys,
921 which are to be contained in the subrsstring to be created.
922 If subrs is None, all subrs in self.subrs will be used.
923 The subrs dict might be modified *in place*.
925 glyphs is a dict containing those glyph names as keys,
926 which are to be contained in the charstringsstring to be created.
927 If glyphs is None, all glyphs in self.glyphs will be used."""
928 w
= writer
.writer(io
.BytesIO())
931 if subrs
is not None:
932 # some adjustments to the subrs dict
934 subrsmin
= min(subrs
)
935 subrsmax
= max(subrs
)
936 if self
.hasflexhintsubrs
and subrsmin
< len(self
.flexhintsubrs
):
937 # According to the spec we need to keep all the flex and hint subrs
938 # as long as any of it is used.
939 for subr
in range(len(self
.flexhintsubrs
)):
941 subrsmax
= max(subrs
)
945 # build a new subrs dict containing all subrs
946 subrs
= dict([(subr
, 1) for subr
in range(len(self
.subrs
))])
947 subrsmax
= len(self
.subrs
) - 1
949 # build the string from all selected subrs
950 w
.write("%d array\n" % (subrsmax
+ 1))
951 for subr
in range(subrsmax
+1):
953 code
= self
.subrs
[subr
]
955 code
= self
.emptysubr
956 w
.write("dup %d %d " % (subr
, len(code
)))
957 w
.write_bytes(self
.subrrdtoken
)
961 w
.write_bytes(self
.subrnptoken
)
964 def addcharstrings(glyphs
):
965 w
.write("%d dict dup begin\n" % (glyphs
is None and len(self
.glyphlist
) or len(glyphs
)))
966 for glyph
in self
.glyphlist
:
967 if glyphs
is None or glyph
in glyphs
:
968 w
.write("/%s %d " % (glyph
, len(self
.glyphs
[glyph
])))
969 w
.write_bytes(self
.glyphrdtoken
)
971 w
.write_bytes(self
.glyphs
[glyph
])
973 w
.write_bytes(self
.glyphndtoken
)
977 if self
.subrsstart
< self
.charstringsstart
:
978 w
.write_bytes(self
._data
2[:self
.subrsstart
])
980 w
.write_bytes(self
._data
2[self
.subrsend
:self
.charstringsstart
])
981 addcharstrings(glyphs
)
982 w
.write_bytes(self
._data
2[self
.charstringsend
:])
984 w
.write_bytes(self
._data
2[:self
.charstringsstart
])
985 addcharstrings(glyphs
)
986 w
.write_bytes(self
._data
2[self
.charstringsend
:self
.subrsstart
])
988 w
.write_bytes(self
._data
2[self
.subrsend
:])
989 return w
.file.getvalue()
991 def getdata2eexec(self
):
993 return self
._data
2eexec
994 # note that self._data2 is out-of-date here too, hence we need to call getdata2
995 return self
._eexecencode
(self
.getdata2())
997 newlinepattern
= re
.compile("\s*[\r\n]\s*")
998 uniqueidstrpattern
= re
.compile("%?/UniqueID\s+\d+\s+def\s+")
999 uniqueidbytespattern
= re
.compile(b
"%?/UniqueID\s+\d+\s+def\s+")
1000 # when UniqueID is commented out (as in modern latin), prepare to remove the comment character as well
1002 def getstrippedfont(self
, glyphs
, charcodes
):
1003 """create a T1file instance containing only certain glyphs
1005 glyphs is a set of the glyph names. It might be modified *in place*!
1007 if not self
.encoding
:
1009 for charcode
in charcodes
:
1010 glyphs
.add(self
.encoding
[charcode
])
1012 # collect information about used glyphs and subrs
1015 for glyph
in glyphs
:
1016 self
.gatherglyphcalls(glyph
, seacglyphs
, subrs
, T1context(self
))
1017 # while we have gathered all subrs for the seacglyphs alreadys, we
1018 # might have missed the glyphs themself (when they are not used stand-alone)
1019 glyphs
.update(seacglyphs
)
1020 glyphs
.add(".notdef")
1023 if self
.encoding
is adobestandardencoding
:
1026 encodingstrings
= []
1027 for char
, glyph
in enumerate(self
.encoding
):
1029 encodingstrings
.append("dup %i /%s put\n" % (char
, glyph
))
1030 data1
= self
.data1
[:self
.encodingstart
] + "\n" + "".join(encodingstrings
) + self
.data1
[self
.encodingend
:]
1031 data1
= self
.newlinepattern
.subn("\n", data1
)[0]
1032 data1
= self
.uniqueidstrpattern
.subn("", data1
)[0]
1035 data2
= self
.uniqueidbytespattern
.subn(b
"", self
.getdata2(subrs
, glyphs
))[0]
1038 data3
= self
.newlinepattern
.subn("\n", self
.data3
)[0]
1040 # create and return the new font instance
1041 return T1file(data1
.rstrip() + "\n", self
._eexecencode
(data2
), data3
.rstrip() + "\n")
1043 # The following two methods, writePDFfontinfo and getglyphinfo,
1044 # extract informtion which should better be taken from the afm file.
1045 def writePDFfontinfo(self
, file):
1047 glyphinfo_y
= self
.getglyphinfo("y")
1048 glyphinfo_W
= self
.getglyphinfo("W")
1049 glyphinfo_H
= self
.getglyphinfo("H")
1050 glyphinfo_h
= self
.getglyphinfo("h")
1051 glyphinfo_period
= self
.getglyphinfo("period")
1052 glyphinfo_colon
= self
.getglyphinfo("colon")
1054 logger
.warning("Auto-guessing of font information for font '%s' failed. We're writing stub data instead." % self
.name
)
1055 file.write("/Flags 4\n")
1056 file.write("/FontBBox [0 -100 1000 1000]\n")
1057 file.write("/ItalicAngle 0\n")
1058 file.write("/Ascent 1000\n")
1059 file.write("/Descent -100\n")
1060 file.write("/CapHeight 700\n")
1061 file.write("/StemV 100\n")
1063 if not self
.encoding
:
1065 # As a simple heuristics we assume non-symbolic fonts if and only
1066 # if the Adobe standard encoding is used. All other font flags are
1067 # not specified here.
1068 if self
.encoding
is adobestandardencoding
:
1069 file.write("/Flags 32\n")
1071 file.write("/Flags 4\n")
1072 file.write("/FontBBox [0 %f %f %f]\n" % (glyphinfo_y
[3], glyphinfo_W
[0], glyphinfo_H
[5]))
1073 file.write("/ItalicAngle %f\n" % math
.degrees(math
.atan2(glyphinfo_period
[4]-glyphinfo_colon
[4], glyphinfo_colon
[5]-glyphinfo_period
[5])))
1074 file.write("/Ascent %f\n" % glyphinfo_H
[5])
1075 file.write("/Descent %f\n" % glyphinfo_y
[3])
1076 file.write("/CapHeight %f\n" % glyphinfo_h
[5])
1077 file.write("/StemV %f\n" % (glyphinfo_period
[4]-glyphinfo_period
[2]))
1079 def getglyphinfo(self
, glyph
, flex
=True):
1080 logger
.warning("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
)
1081 context
= T1context(self
, flex
=flex
)
1083 self
.updateglyphpath(glyph
, p
, trafo
.trafo(), context
)
1085 return context
.wx
, context
.wy
, bbox
.llx_pt
, bbox
.lly_pt
, bbox
.urx_pt
, bbox
.ury_pt
1087 def outputPFA(self
, file, remove_UniqueID_lookup
=False):
1088 """output the T1file in PFA format"""
1091 if remove_UniqueID_lookup
:
1092 m1
= re
.search("""FontDirectory\s*/%(name)s\s+known{/%(name)s\s+findfont\s+dup\s*/UniqueID\s+known\s*{\s*dup\s*
1093 /UniqueID\s+get\s+\d+\s+eq\s+exch\s*/FontType\s+get\s+1\s+eq\s+and\s*}\s*{\s*pop\s+false\s*}\s*ifelse\s*
1094 {save\s+true\s*}\s*{\s*false\s*}\s*ifelse\s*}\s*{\s*false\s*}\s*ifelse""" % {"name": self
.name
},
1096 m3
= re
.search("\s*{restore}\s*if", data3
)
1098 data1
= data1
[:m1
.start()] + data1
[m1
.end():]
1099 data3
= data3
[:m3
.start()] + data3
[m3
.end():]
1101 data2eexechex
= binascii
.b2a_hex(self
.getdata2eexec())
1103 for i
in range((len(data2eexechex
)-1)//linelength
+ 1):
1104 file.write_bytes(data2eexechex
[i
*linelength
: i
*linelength
+linelength
])
1108 def outputPFB(self
, file):
1109 """output the T1file in PFB format"""
1110 data2eexec
= self
.getdata2eexec()
1111 def pfblength(data
):
1113 l
, x1
= divmod(l
, 256)
1114 l
, x2
= divmod(l
, 256)
1115 x4
, x3
= divmod(l
, 256)
1116 return chr(x1
) + chr(x2
) + chr(x3
) + chr(x4
)
1117 file.write("\200\1")
1118 file.write(pfblength(self
.data1
))
1119 file.write(self
.data1
)
1120 file.write("\200\2")
1121 file.write(pfblength(data2eexec
))
1122 file.write(data2eexec
)
1123 file.write("\200\1")
1124 file.write(pfblength(self
.data3
))
1125 file.write(self
.data3
)
1126 file.write("\200\3")
1128 def outputPS(self
, file, writer
):
1129 """output the PostScript code for the T1file to the file file"""
1130 self
.outputPFA(file, remove_UniqueID_lookup
=True)
1132 def outputPDF(self
, file, writer
):
1133 data2eexec
= self
.getdata2eexec()
1135 # we might be allowed to skip the third part ...
1136 if (data3
.replace("\n", "")
1139 .replace(" ", "")) == "0"*512 + "cleartomark":
1142 data
= self
.data1
.encode("ascii", errors
="surrogateescape") + data2eexec
+ data3
.encode("ascii", errors
="surrogateescape")
1143 if writer
.compress
and haszlib
:
1144 data
= zlib
.compress(data
)
1150 "/Length3 %d\n" % (len(data
), len(self
.data1
), len(data2eexec
), len(data3
)))
1151 if writer
.compress
and haszlib
:
1152 file.write("/Filter /FlateDecode\n")
1155 file.write_bytes(data
)
1161 class FontFormatError(Exception):
1164 def from_PFA_bytes(bytes
):
1165 """create a T1file instance from a string of bytes corresponding to a PFA file"""
1167 m1
= bytes
.index("eexec") + 6
1168 m2
= bytes
.index("0"*40)
1170 raise FontFormatError
1172 data1
= bytes
[:m1
].decode("ascii", errors
="surrogateescape")
1173 data2eexec
= binascii
.a2b_hex(bytes
[m1
: m2
].replace(" ", "").replace("\r", "").replace("\n", ""))
1174 data3
= bytes
[m2
:].decode("ascii", errors
="surrogateescape")
1175 return T1file(data1
, data2eexec
, data3
)
1177 def from_PFA_filename(filename
):
1178 """create a T1file instance from PFA font file of given name"""
1179 with
open(filename
, "rb") as file:
1180 t1file
= from_PFA_bytes(file.read())
1183 def from_PFB_bytes(bytes
):
1184 """create a T1file instance from a string of bytes corresponding to a PFB file"""
1188 raise ValueError("invalid string length")
1194 def __init__(self
, bytes
):
1197 def __call__(self
, n
):
1198 result
= self
.bytes
[self
.pos
:self
.pos
+n
]
1202 consume
= consumer(bytes
)
1204 if mark
!= b
"\200\1":
1205 raise FontFormatError
1206 data1
= consume(pfblength(consume(4))).decode("ascii", errors
="surrogateescape")
1208 if mark
!= b
"\200\2":
1209 raise FontFormatError
1211 while mark
== b
"\200\2":
1212 data2eexec
= data2eexec
+ consume(pfblength(consume(4)))
1214 if mark
!= b
"\200\1":
1215 raise FontFormatError
1216 data3
= consume(pfblength(consume(4))).decode("ascii", errors
="surrogateescape")
1218 if mark
!= b
"\200\3":
1219 raise FontFormatError
1221 raise FontFormatError
1223 return T1file(data1
, data2eexec
, data3
)
1225 def from_PFB_filename(filename
):
1226 """create a T1file instance from PFB font file of given name"""
1227 with
open(filename
, "rb") as file:
1228 t1file
= from_PFB_bytes(file.read())
1231 def from_PF_bytes(bytes
):
1233 return from_PFB_bytes(bytes
)
1234 #except FontFormatError:
1235 # return from_PFA_bytes(bytes)
1237 def from_PF_filename(filename
):
1238 """create a T1file instance from PFA or PFB font file of given name"""
1239 with
open(filename
, "rb") as file:
1240 t1file
= from_PF_bytes(file.read())