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
, math
, re
, warnings
31 from pyx
import trafo
, reader
, writer
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
, flex
=True):
76 """context for T1cmd evaluation"""
89 ######################################################################
91 # Note, that the T1 commands are variable-free except for plain number,
92 # which are stored as integers. All other T1 commands exist as a single
100 def __init__(self
, code
, subcmd
=0):
104 T1subcmds
[code
] = self
109 """returns a string representation of the T1 command"""
110 raise NotImplementedError
112 def updatepath(self
, path
, trafo
, context
):
113 """update path instance applying trafo to the points"""
114 raise NotImplementedError
116 def gathercalls(self
, seacglyphs
, subrs
, context
):
117 """gather dependancy information
119 subrs is the "called-subrs" dictionary. gathercalls will insert the
120 subr number as key having the value 1, i.e. subrs will become the
121 numbers of used subrs. Similar seacglyphs will contain all glyphs in
122 composite characters (subrs for those glyphs will also
123 already be included).
125 This method might will not properly update all information in the
126 context (especially consuming values from the stack) and will also skip
127 various tests for performance reasons. For most T1 commands it just
128 doesn't need to do anything.
133 # commands for starting and finishing
135 class _T1endchar(T1cmd
):
138 T1cmd
.__init
__(self
, 14)
143 def updatepath(self
, path
, trafo
, context
):
146 T1endchar
= _T1endchar()
149 class _T1hsbw(T1cmd
):
152 T1cmd
.__init
__(self
, 13)
157 def updatepath(self
, path
, trafo
, context
):
158 sbx
= context
.t1stack
.pop(0)
159 wx
= context
.t1stack
.pop(0)
160 path
.append(moveto_pt(*trafo
.apply_pt(sbx
, 0)))
169 class _T1seac(T1cmd
):
172 T1cmd
.__init
__(self
, 6, subcmd
=1)
177 def updatepath(self
, path
, atrafo
, context
):
178 sab
= context
.t1stack
.pop(0)
179 adx
= context
.t1stack
.pop(0)
180 ady
= context
.t1stack
.pop(0)
181 bchar
= context
.t1stack
.pop(0)
182 achar
= context
.t1stack
.pop(0)
183 aglyph
= adobestandardencoding
[achar
]
184 bglyph
= adobestandardencoding
[bchar
]
185 context
.t1font
.updateglyphpath(bglyph
, path
, atrafo
, context
)
186 atrafo
= atrafo
* trafo
.translate_pt(adx
-sab
, ady
)
187 context
.t1font
.updateglyphpath(aglyph
, path
, atrafo
, context
)
189 def gathercalls(self
, seacglyphs
, subrs
, context
):
190 achar
= context
.t1stack
.pop()
191 bchar
= context
.t1stack
.pop()
192 aglyph
= adobestandardencoding
[achar
]
193 bglyph
= adobestandardencoding
[bchar
]
194 seacglyphs
.add(aglyph
)
195 seacglyphs
.add(bglyph
)
196 context
.t1font
.gatherglyphcalls(bglyph
, seacglyphs
, subrs
, context
)
197 context
.t1font
.gatherglyphcalls(aglyph
, seacglyphs
, subrs
, context
)
205 T1cmd
.__init
__(self
, 7, subcmd
=1)
210 def updatepath(self
, path
, trafo
, context
):
211 sbx
= context
.t1stack
.pop(0)
212 sby
= context
.t1stack
.pop(0)
213 wx
= context
.t1stack
.pop(0)
214 wy
= context
.t1stack
.pop(0)
215 path
.append(moveto_pt(*trafo
.apply_pt(sbx
, sby
)))
224 # path construction commands
226 class _T1closepath(T1cmd
):
229 T1cmd
.__init
__(self
, 9)
234 def updatepath(self
, path
, trafo
, context
):
235 path
.append(closepath())
236 # The closepath in T1 is different from PostScripts in that it does
237 # *not* modify the current position; hence we need to add an additional
239 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
, context
.y
)))
241 T1closepath
= _T1closepath()
244 class _T1hlineto(T1cmd
):
247 T1cmd
.__init
__(self
, 6)
252 def updatepath(self
, path
, trafo
, context
):
253 dx
= context
.t1stack
.pop(0)
254 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
)))
257 T1hlineto
= _T1hlineto()
260 class _T1hmoveto(T1cmd
):
263 T1cmd
.__init
__(self
, 22)
268 def updatepath(self
, path
, trafo
, context
):
269 dx
= context
.t1stack
.pop(0)
270 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
)))
273 T1hmoveto
= _T1hmoveto()
276 class _T1hvcurveto(T1cmd
):
279 T1cmd
.__init
__(self
, 31)
284 def updatepath(self
, path
, trafo
, context
):
285 dx1
= context
.t1stack
.pop(0)
286 dx2
= context
.t1stack
.pop(0)
287 dy2
= context
.t1stack
.pop(0)
288 dy3
= context
.t1stack
.pop(0)
289 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
+ dx1
, context
.y
) +
290 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy2
) +
291 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy2
+ dy3
))))
295 T1hvcurveto
= _T1hvcurveto()
298 class _T1rlineto(T1cmd
):
301 T1cmd
.__init
__(self
, 5)
306 def updatepath(self
, path
, trafo
, context
):
307 dx
= context
.t1stack
.pop(0)
308 dy
= context
.t1stack
.pop(0)
309 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
+ dy
)))
313 T1rlineto
= _T1rlineto()
316 class _T1rmoveto(T1cmd
):
319 T1cmd
.__init
__(self
, 21)
324 def updatepath(self
, path
, trafo
, context
):
325 dx
= context
.t1stack
.pop(0)
326 dy
= context
.t1stack
.pop(0)
327 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
+ dy
)))
331 T1rmoveto
= _T1rmoveto()
334 class _T1rrcurveto(T1cmd
):
337 T1cmd
.__init
__(self
, 8)
342 def updatepath(self
, path
, trafo
, context
):
343 dx1
= context
.t1stack
.pop(0)
344 dy1
= context
.t1stack
.pop(0)
345 dx2
= context
.t1stack
.pop(0)
346 dy2
= context
.t1stack
.pop(0)
347 dx3
= context
.t1stack
.pop(0)
348 dy3
= context
.t1stack
.pop(0)
349 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
+ dx1
, context
.y
+ dy1
) +
350 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy1
+ dy2
) +
351 trafo
.apply_pt(context
.x
+ dx1
+ dx2
+ dx3
, context
.y
+ dy1
+ dy2
+ dy3
))))
352 context
.x
+= dx1
+dx2
+dx3
353 context
.y
+= dy1
+dy2
+dy3
355 T1rrcurveto
= _T1rrcurveto()
358 class _T1vlineto(T1cmd
):
361 T1cmd
.__init
__(self
, 7)
366 def updatepath(self
, path
, trafo
, context
):
367 dy
= context
.t1stack
.pop(0)
368 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
, context
.y
+ dy
)))
371 T1vlineto
= _T1vlineto()
374 class _T1vmoveto(T1cmd
):
377 T1cmd
.__init
__(self
, 4)
382 def updatepath(self
, path
, trafo
, context
):
383 dy
= context
.t1stack
.pop(0)
384 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
, context
.y
+ dy
)))
387 T1vmoveto
= _T1vmoveto()
390 class _T1vhcurveto(T1cmd
):
393 T1cmd
.__init
__(self
, 30)
398 def updatepath(self
, path
, trafo
, context
):
399 dy1
= context
.t1stack
.pop(0)
400 dx2
= context
.t1stack
.pop(0)
401 dy2
= context
.t1stack
.pop(0)
402 dx3
= context
.t1stack
.pop(0)
403 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
, context
.y
+ dy1
) +
404 trafo
.apply_pt(context
.x
+ dx2
, context
.y
+ dy1
+ dy2
) +
405 trafo
.apply_pt(context
.x
+ dx2
+ dx3
, context
.y
+ dy1
+ dy2
))))
409 T1vhcurveto
= _T1vhcurveto()
414 class _T1dotsection(T1cmd
):
417 T1cmd
.__init
__(self
, 0, subcmd
=1)
422 def updatepath(self
, path
, trafo
, context
):
425 T1dotsection
= _T1dotsection()
428 class _T1hstem(T1cmd
):
431 T1cmd
.__init
__(self
, 1)
436 def updatepath(self
, path
, trafo
, context
):
437 y
= context
.t1stack
.pop(0)
438 dy
= context
.t1stack
.pop(0)
443 class _T1hstem3(T1cmd
):
446 T1cmd
.__init
__(self
, 2, subcmd
=1)
451 def updatepath(self
, path
, trafo
, context
):
452 y0
= context
.t1stack
.pop(0)
453 dy0
= context
.t1stack
.pop(0)
454 y1
= context
.t1stack
.pop(0)
455 dy1
= context
.t1stack
.pop(0)
456 y2
= context
.t1stack
.pop(0)
457 dy2
= context
.t1stack
.pop(0)
459 T1hstem3
= _T1hstem3()
462 class _T1vstem(T1cmd
):
465 T1cmd
.__init
__(self
, 3)
470 def updatepath(self
, path
, trafo
, context
):
471 x
= context
.t1stack
.pop(0)
472 dx
= context
.t1stack
.pop(0)
477 class _T1vstem3(T1cmd
):
480 T1cmd
.__init
__(self
, 1, subcmd
=1)
485 def updatepath(self
, path
, trafo
, context
):
486 self
.x0
= context
.t1stack
.pop(0)
487 self
.dx0
= context
.t1stack
.pop(0)
488 self
.x1
= context
.t1stack
.pop(0)
489 self
.dx1
= context
.t1stack
.pop(0)
490 self
.x2
= context
.t1stack
.pop(0)
491 self
.dx2
= context
.t1stack
.pop(0)
493 T1vstem3
= _T1vstem3()
501 T1cmd
.__init
__(self
, 12, subcmd
=1)
506 def updatepath(self
, path
, trafo
, context
):
507 num2
= context
.t1stack
.pop()
508 num1
= context
.t1stack
.pop()
509 context
.t1stack
.append(divmod(num1
, num2
)[0])
511 def gathercalls(self
, seacglyphs
, subrs
, context
):
512 num2
= context
.t1stack
.pop()
513 num1
= context
.t1stack
.pop()
514 context
.t1stack
.append(divmod(num1
, num2
)[0])
519 # subroutine commands
521 class _T1callothersubr(T1cmd
):
524 T1cmd
.__init
__(self
, 16, subcmd
=1)
527 return "callothersubr"
529 def updatepath(self
, path
, trafo
, context
):
530 othersubrnumber
= context
.t1stack
.pop()
531 n
= context
.t1stack
.pop()
533 context
.psstack
.append(context
.t1stack
.pop(0))
534 if othersubrnumber
== 0:
535 flex_size
, x
, y
= context
.psstack
[-3:]
537 x1
, y1
, x2
, y2
, x3
, y3
= context
.psstack
[2:8]
538 x1
, y1
= trafo
.apply_pt(x1
, y1
)
539 x2
, y2
= trafo
.apply_pt(x2
, y2
)
540 x3
, y3
= trafo
.apply_pt(x3
, y3
)
541 path
.append(curveto_pt(x1
, y1
, x2
, y2
, x3
, y3
))
542 x1
, y1
, x2
, y2
, x3
, y3
= context
.psstack
[8:14]
543 x1
, y1
= trafo
.apply_pt(x1
, y1
)
544 x2
, y2
= trafo
.apply_pt(x2
, y2
)
545 x3
, y3
= trafo
.apply_pt(x3
, y3
)
546 path
.append(curveto_pt(x1
, y1
, x2
, y2
, x3
, y3
))
548 path
.append(lineto_pt(*trafo
.apply_pt(x
, y
)))
549 context
.psstack
= [y
, x
]
550 elif othersubrnumber
== 1:
552 elif othersubrnumber
== 2:
554 context
.psstack
.append(context
.x
)
555 context
.psstack
.append(context
.y
)
557 def gathercalls(self
, seacglyphs
, subrs
, context
):
558 othersubrnumber
= context
.t1stack
.pop()
559 n
= context
.t1stack
.pop()
560 context
.psstack
.extend([context
.t1stack
.pop() for i
in range(n
)][::-1])
562 T1callothersubr
= _T1callothersubr()
565 class _T1callsubr(T1cmd
):
568 T1cmd
.__init
__(self
, 10)
573 def updatepath(self
, path
, trafo
, context
):
574 subr
= context
.t1stack
.pop()
575 context
.t1font
.updatesubrpath(subr
, path
, trafo
, context
)
577 def gathercalls(self
, seacglyphs
, subrs
, context
):
578 subr
= context
.t1stack
.pop()
580 context
.t1font
.gathersubrcalls(subr
, seacglyphs
, subrs
, context
)
582 T1callsubr
= _T1callsubr()
588 T1cmd
.__init
__(self
, 17, subcmd
=1)
593 def updatepath(self
, path
, trafo
, context
):
594 context
.t1stack
.append(context
.psstack
.pop())
596 def gathercalls(self
, seacglyphs
, subrs
, context
):
597 context
.t1stack
.append(context
.psstack
.pop())
602 class _T1return(T1cmd
):
605 T1cmd
.__init
__(self
, 11)
610 def updatepath(self
, path
, trafo
, context
):
613 T1return
= _T1return()
616 class _T1setcurrentpoint(T1cmd
):
619 T1cmd
.__init
__(self
, 33, subcmd
=1)
622 return "setcurrentpoint"
624 def updatepath(self
, path
, trafo
, context
):
625 context
.x
= context
.t1stack
.pop(0)
626 context
.y
= context
.t1stack
.pop(0)
628 T1setcurrentpoint
= _T1setcurrentpoint()
631 ######################################################################
638 fontnamepattern
= re
.compile("/FontName\s+/(.*?)\s+def\s+")
639 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")
641 def __init__(self
, data1
, data2eexec
, data3
):
642 """initializes a t1font instance
644 data1 and data3 are the two clear text data parts and data2 is
645 the binary data part"""
647 self
._data
2eexec
= data2eexec
650 # marker and value for decoded data
652 # note that data2eexec is set to none by setsubrcmds and setglyphcmds
653 # this *also* denotes, that data2 is out-of-date; hence they are both
654 # marked by an _ and getdata2 and getdata2eexec will properly resolve
655 # the current state of decoding ...
657 # marker and value for standard encoding check
660 self
.name
, = self
.fontnamepattern
.search(self
.data1
).groups()
661 m11
, m12
, m21
, m22
, v1
, v2
= list(map(float, self
.fontmatrixpattern
.search(self
.data1
).groups()[:6]))
662 self
.fontmatrix
= trafo
.trafo_pt(matrix
=((m11
, m12
), (m21
, m22
)), vector
=(v1
, v2
))
664 def _eexecdecode(self
, code
):
665 """eexec decoding of code"""
666 return decoder(code
, self
.eexecr
, 4)
668 def _charstringdecode(self
, code
):
669 """charstring decoding of code"""
670 return decoder(code
, self
.charstringr
, self
.lenIV
)
672 def _eexecencode(self
, data
):
673 """eexec encoding of data"""
674 return encoder(data
, self
.eexecr
, b
"PyX!")
676 def _charstringencode(self
, data
):
677 """eexec encoding of data"""
678 return encoder(data
, self
.charstringr
, b
"PyX!"[:self
.lenIV
])
681 """helper method to lookup the encoding in the font"""
682 c
= reader
.PStokenizer(self
.data1
, "/Encoding")
683 token1
= c
.gettoken()
684 token2
= c
.gettoken()
685 if token1
== "StandardEncoding" and token2
== "def":
686 self
.encoding
= adobestandardencoding
688 self
.encoding
= [None]*256
690 self
.encodingstart
= c
.pos
691 if c
.gettoken() == "dup":
697 self
.encoding
[i
] = glyph
[1:]
698 token
= c
.gettoken(); assert token
== "put"
699 self
.encodingend
= c
.pos
701 if token
== "readonly" or token
== "def":
703 assert token
== "dup"
705 lenIVpattern
= re
.compile(b
"/lenIV\s+(\d+)\s+def\s+")
706 flexhintsubrs
= [[3, 0, T1callothersubr
, T1pop
, T1pop
, T1setcurrentpoint
, T1return
],
707 [0, 1, T1callothersubr
, T1return
],
708 [0, 2, T1callothersubr
, T1return
],
711 def _data2decode(self
):
712 """decodes data2eexec to the data2 string and the subr and glyphs dictionary
714 It doesn't make sense to call this method twice -- check the content of
715 data2 before calling. The method also keeps the subrs and charstrings
716 start and end positions for later use."""
717 self
._data
2 = self
._eexecdecode
(self
._data
2eexec
)
719 m
= self
.lenIVpattern
.search(self
._data
2)
721 self
.lenIV
= int(m
.group(1))
725 self
.emptysubr
= self
._charstringencode
(b
"\x0b") # 11, i.e. return
728 c
= reader
.PSbytes_tokenizer(self
._data
2, b
"/Subrs")
729 self
.subrsstart
= c
.pos
730 arraycount
= c
.getint()
731 token
= c
.gettoken(); assert token
== b
"array"
733 for i
in range(arraycount
):
734 token
= c
.gettoken(); assert token
== b
"dup"
735 token
= c
.getint(); assert token
== i
738 self
.subrrdtoken
= c
.gettoken()
740 token
= c
.gettoken(); assert token
== self
.subrrdtoken
741 self
.subrs
.append(c
.getbytes(size
))
743 if token
== b
"noaccess":
744 token
= token
+ b
" " + c
.gettoken()
746 self
.subrnptoken
= token
748 assert token
== self
.subrnptoken
749 self
.subrsend
= c
.pos
751 # hasflexhintsubrs is a boolean indicating that the font uses flex or
752 # hint replacement subrs as specified by Adobe (tm). When it does, the
753 # first 4 subrs should all be copied except when none of them are used
754 # in the stripped version of the font since we then get a font not
755 # using flex or hint replacement subrs at all.
756 self
.hasflexhintsubrs
= (arraycount
>= len(self
.flexhintsubrs
) and
758 for i
in range(len(self
.flexhintsubrs
))] == self
.flexhintsubrs
)
762 self
.glyphlist
= [] # we want to keep the order of the glyph names
763 c
= reader
.PSbytes_tokenizer(self
._data
2, b
"/CharStrings")
764 self
.charstringsstart
= c
.pos
766 token
= c
.gettoken(); assert token
== b
"dict"
767 token
= c
.gettoken(); assert token
== b
"dup"
768 token
= c
.gettoken(); assert token
== b
"begin"
771 chartoken
= c
.gettoken().decode("ascii")
772 if chartoken
== "end":
774 assert chartoken
[0] == "/"
777 self
.glyphrdtoken
= c
.gettoken()
779 token
= c
.gettoken(); assert token
== self
.glyphrdtoken
780 self
.glyphlist
.append(chartoken
[1:])
781 self
.glyphs
[chartoken
[1:]] = c
.getbytes(size
)
783 self
.glyphndtoken
= c
.gettoken()
785 token
= c
.gettoken(); assert token
== self
.glyphndtoken
787 self
.charstringsend
= c
.pos
788 assert not self
.subrs
or self
.subrrdtoken
== self
.glyphrdtoken
790 def _cmds(self
, code
):
791 """return a list of T1cmd's for encoded charstring data in code"""
792 code
= array
.array("B", self
._charstringdecode
(code
))
796 if x
== 12: # this starts an escaped cmd
797 cmds
.append(T1subcmds
[code
.pop(0)])
798 elif 0 <= x
< 32: # those are cmd's
799 cmds
.append(T1cmds
[x
])
800 elif 32 <= x
<= 246: # short ints
802 elif 247 <= x
<= 250: # mid size ints
803 cmds
.append(((x
- 247)*256) + code
.pop(0) + 108)
804 elif 251 <= x
<= 254: # mid size ints
805 cmds
.append(-((x
- 251)*256) - code
.pop(0) - 108)
806 else: # x = 255, i.e. full size ints
807 y
= ((code
.pop(0)*256+code
.pop(0))*256+code
.pop(0))*256+code
.pop(0)
809 cmds
.append(y
- (1 << 32))
814 def _code(self
, cmds
):
815 """return an encoded charstring data for list of T1cmd's in cmds"""
816 code
= array
.array("B")
821 code
.append(cmd
.code
)
822 except AttributeError:
823 if -107 <= cmd
<= 107:
825 elif 108 <= cmd
<= 1131:
826 a
, b
= divmod(cmd
-108, 256)
829 elif -1131 <= cmd
<= -108:
830 a
, b
= divmod(-cmd
-108, 256)
836 cmd
, x4
= divmod(cmd
, 256)
837 cmd
, x3
= divmod(cmd
, 256)
838 x1
, x2
= divmod(cmd
, 256)
844 return self
._charstringencode
(code
.tostring())
846 def getsubrcmds(self
, subr
):
847 """return a list of T1cmd's for subr subr"""
850 return self
._cmds
(self
.subrs
[subr
])
852 def getglyphcmds(self
, glyph
):
853 """return a list of T1cmd's for glyph glyph"""
856 return self
._cmds
(self
.glyphs
[glyph
])
858 def setsubrcmds(self
, subr
, cmds
):
859 """replaces the T1cmd's by the list cmds for subr subr"""
862 self
._data
2eexec
= None
863 self
.subrs
[subr
] = self
._code
(cmds
)
865 def setglyphcmds(self
, glyph
, cmds
):
866 """replaces the T1cmd's by the list cmds for glyph glyph"""
869 self
._data
2eexec
= None
870 self
.glyphs
[glyph
] = self
._code
(cmds
)
872 def updatepath(self
, cmds
, path
, trafo
, context
):
874 if isinstance(cmd
, T1cmd
):
875 cmd
.updatepath(path
, trafo
, context
)
877 context
.t1stack
.append(cmd
)
879 def updatesubrpath(self
, subr
, path
, trafo
, context
):
880 self
.updatepath(self
.getsubrcmds(subr
), path
, trafo
, context
)
882 def updateglyphpath(self
, glyph
, path
, trafo
, context
):
883 self
.updatepath(self
.getglyphcmds(glyph
), path
, trafo
, context
)
885 def gathercalls(self
, cmds
, seacglyphs
, subrs
, context
):
887 if isinstance(cmd
, T1cmd
):
888 cmd
.gathercalls(seacglyphs
, subrs
, context
)
890 context
.t1stack
.append(cmd
)
892 def gathersubrcalls(self
, subr
, seacglyphs
, subrs
, context
):
893 self
.gathercalls(self
.getsubrcmds(subr
), seacglyphs
, subrs
, context
)
895 def gatherglyphcalls(self
, glyph
, seacglyphs
, subrs
, context
):
896 self
.gathercalls(self
.getglyphcmds(glyph
), seacglyphs
, subrs
, context
)
898 def getglyphpath_pt(self
, x_pt
, y_pt
, glyph
, size_pt
, convertcharcode
=False, flex
=True):
899 """return an object containing the PyX path, wx_pt and wy_pt for glyph named glyph"""
901 if not self
.encoding
:
903 glyph
= self
.encoding
[glyph
]
904 t
= self
.fontmatrix
.scaled(size_pt
)
905 tpath
= t
.translated_pt(x_pt
, y_pt
)
906 context
= T1context(self
, flex
=flex
)
908 self
.updateglyphpath(glyph
, p
, tpath
, context
)
910 def __init__(self
, p
, wx_pt
, wy_pt
):
914 return glyphpath(p
, *t
.apply_pt(context
.wx
, context
.wy
))
916 def getdata2(self
, subrs
=None, glyphs
=None):
917 """makes a data2 string
919 subrs is a dict containing those subrs numbers as keys,
920 which are to be contained in the subrsstring to be created.
921 If subrs is None, all subrs in self.subrs will be used.
922 The subrs dict might be modified *in place*.
924 glyphs is a dict containing those glyph names as keys,
925 which are to be contained in the charstringsstring to be created.
926 If glyphs is None, all glyphs in self.glyphs will be used."""
927 w
= writer
.writer(io
.BytesIO())
930 if subrs
is not None:
931 # some adjustments to the subrs dict
933 subrsmin
= min(subrs
)
934 subrsmax
= max(subrs
)
935 if self
.hasflexhintsubrs
and subrsmin
< len(self
.flexhintsubrs
):
936 # According to the spec we need to keep all the flex and hint subrs
937 # as long as any of it is used.
938 for subr
in range(len(self
.flexhintsubrs
)):
943 # build a new subrs dict containing all subrs
944 subrs
= dict([(subr
, 1) for subr
in range(len(self
.subrs
))])
945 subrsmax
= len(self
.subrs
) - 1
947 # build the string from all selected subrs
948 w
.write("%d array\n" % (subrsmax
+ 1))
949 for subr
in range(subrsmax
+1):
951 code
= self
.subrs
[subr
]
953 code
= self
.emptysubr
954 w
.write("dup %d %d " % (subr
, len(code
)))
955 w
.write_bytes(self
.subrrdtoken
)
959 w
.write_bytes(self
.subrnptoken
)
962 def addcharstrings(glyphs
):
963 w
.write("%d dict dup begin\n" % (glyphs
is None and len(self
.glyphlist
) or len(glyphs
)))
964 for glyph
in self
.glyphlist
:
965 if glyphs
is None or glyph
in glyphs
:
966 w
.write("/%s %d " % (glyph
, len(self
.glyphs
[glyph
])))
967 w
.write_bytes(self
.glyphrdtoken
)
969 w
.write_bytes(self
.glyphs
[glyph
])
971 w
.write_bytes(self
.glyphndtoken
)
975 if self
.subrsstart
< self
.charstringsstart
:
976 w
.write_bytes(self
._data
2[:self
.subrsstart
])
978 w
.write_bytes(self
._data
2[self
.subrsend
:self
.charstringsstart
])
979 addcharstrings(glyphs
)
980 w
.write_bytes(self
._data
2[self
.charstringsend
:])
982 w
.write_bytes(self
._data
2[:self
.charstringsstart
])
983 addcharstrings(glyphs
)
984 w
.write_bytes(self
._data
2[self
.charstringsend
:self
.subrsstart
])
986 w
.write_bytes(self
._data
2[self
.subrsend
:])
987 return w
.file.getvalue()
989 def getdata2eexec(self
):
991 return self
._data
2eexec
992 # note that self._data2 is out-of-date here too, hence we need to call getdata2
993 return self
._eexecencode
(self
.getdata2())
995 newlinepattern
= re
.compile("\s*[\r\n]\s*")
996 uniqueidstrpattern
= re
.compile("%?/UniqueID\s+\d+\s+def\s+")
997 uniqueidbytespattern
= re
.compile(b
"%?/UniqueID\s+\d+\s+def\s+")
998 # when UniqueID is commented out (as in modern latin), prepare to remove the comment character as well
1000 def getstrippedfont(self
, glyphs
, charcodes
):
1001 """create a T1file instance containing only certain glyphs
1003 glyphs is a set of the glyph names. It might be modified *in place*!
1005 if not self
.encoding
:
1007 for charcode
in charcodes
:
1008 glyphs
.add(self
.encoding
[charcode
])
1010 # collect information about used glyphs and subrs
1013 for glyph
in glyphs
:
1014 self
.gatherglyphcalls(glyph
, seacglyphs
, subrs
, T1context(self
))
1015 # while we have gathered all subrs for the seacglyphs alreadys, we
1016 # might have missed the glyphs themself (when they are not used stand-alone)
1017 glyphs
.update(seacglyphs
)
1018 glyphs
.add(".notdef")
1021 if self
.encoding
is adobestandardencoding
:
1024 encodingstrings
= []
1025 for char
, glyph
in enumerate(self
.encoding
):
1027 encodingstrings
.append("dup %i /%s put\n" % (char
, glyph
))
1028 data1
= self
.data1
[:self
.encodingstart
] + "\n" + "".join(encodingstrings
) + self
.data1
[self
.encodingend
:]
1029 data1
= self
.newlinepattern
.subn("\n", data1
)[0]
1030 data1
= self
.uniqueidstrpattern
.subn("", data1
)[0]
1033 data2
= self
.uniqueidbytespattern
.subn(b
"", self
.getdata2(subrs
, glyphs
))[0]
1036 data3
= self
.newlinepattern
.subn("\n", self
.data3
)[0]
1038 # create and return the new font instance
1039 return T1file(data1
.rstrip() + "\n", self
._eexecencode
(data2
), data3
.rstrip() + "\n")
1041 # The following two methods, writePDFfontinfo and getglyphinfo,
1042 # extract informtion which should better be taken from the afm file.
1043 def writePDFfontinfo(self
, file):
1045 glyphinfo_y
= self
.getglyphinfo("y")
1046 glyphinfo_W
= self
.getglyphinfo("W")
1047 glyphinfo_H
= self
.getglyphinfo("H")
1048 glyphinfo_h
= self
.getglyphinfo("h")
1049 glyphinfo_period
= self
.getglyphinfo("period")
1050 glyphinfo_colon
= self
.getglyphinfo("colon")
1052 warnings
.warn("Auto-guessing of font information for font '%s' failed. We're writing stub data instead." % self
.name
)
1053 file.write("/Flags 4\n")
1054 file.write("/FontBBox [0 -100 1000 1000]\n")
1055 file.write("/ItalicAngle 0\n")
1056 file.write("/Ascent 1000\n")
1057 file.write("/Descent -100\n")
1058 file.write("/CapHeight 700\n")
1059 file.write("/StemV 100\n")
1061 if not self
.encoding
:
1063 # As a simple heuristics we assume non-symbolic fonts if and only
1064 # if the Adobe standard encoding is used. All other font flags are
1065 # not specified here.
1066 if self
.encoding
is adobestandardencoding
:
1067 file.write("/Flags 32\n")
1069 file.write("/Flags 4\n")
1070 file.write("/FontBBox [0 %f %f %f]\n" % (glyphinfo_y
[3], glyphinfo_W
[0], glyphinfo_H
[5]))
1071 file.write("/ItalicAngle %f\n" % math
.degrees(math
.atan2(glyphinfo_period
[4]-glyphinfo_colon
[4], glyphinfo_colon
[5]-glyphinfo_period
[5])))
1072 file.write("/Ascent %f\n" % glyphinfo_H
[5])
1073 file.write("/Descent %f\n" % glyphinfo_y
[3])
1074 file.write("/CapHeight %f\n" % glyphinfo_h
[5])
1075 file.write("/StemV %f\n" % (glyphinfo_period
[4]-glyphinfo_period
[2]))
1077 def getglyphinfo(self
, glyph
, flex
=True):
1078 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
)
1079 context
= T1context(self
, flex
=flex
)
1081 self
.updateglyphpath(glyph
, p
, trafo
.trafo(), context
)
1083 return context
.wx
, context
.wy
, bbox
.llx_pt
, bbox
.lly_pt
, bbox
.urx_pt
, bbox
.ury_pt
1085 def outputPFA(self
, file, remove_UniqueID_lookup
=False):
1086 """output the T1file in PFA format"""
1089 if remove_UniqueID_lookup
:
1090 m1
= re
.search("""FontDirectory\s*/%(name)s\s+known{/%(name)s\s+findfont\s+dup\s*/UniqueID\s+known\s*{\s*dup\s*
1091 /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*
1092 {save\s+true\s*}\s*{\s*false\s*}\s*ifelse\s*}\s*{\s*false\s*}\s*ifelse""" % {"name": self
.name
},
1094 m3
= re
.search("\s*{restore}\s*if", data3
)
1096 data1
= data1
[:m1
.start()] + data1
[m1
.end():]
1097 data3
= data3
[:m3
.start()] + data3
[m3
.end():]
1099 data2eexechex
= binascii
.b2a_hex(self
.getdata2eexec())
1101 for i
in range((len(data2eexechex
)-1)//linelength
+ 1):
1102 file.write_bytes(data2eexechex
[i
*linelength
: i
*linelength
+linelength
])
1106 def outputPFB(self
, file):
1107 """output the T1file in PFB format"""
1108 data2eexec
= self
.getdata2eexec()
1109 def pfblength(data
):
1111 l
, x1
= divmod(l
, 256)
1112 l
, x2
= divmod(l
, 256)
1113 x4
, x3
= divmod(l
, 256)
1114 return chr(x1
) + chr(x2
) + chr(x3
) + chr(x4
)
1115 file.write("\200\1")
1116 file.write(pfblength(self
.data1
))
1117 file.write(self
.data1
)
1118 file.write("\200\2")
1119 file.write(pfblength(data2eexec
))
1120 file.write(data2eexec
)
1121 file.write("\200\1")
1122 file.write(pfblength(self
.data3
))
1123 file.write(self
.data3
)
1124 file.write("\200\3")
1126 def outputPS(self
, file, writer
):
1127 """output the PostScript code for the T1file to the file file"""
1128 self
.outputPFA(file, remove_UniqueID_lookup
=True)
1130 def outputPDF(self
, file, writer
):
1131 data2eexec
= self
.getdata2eexec()
1133 # we might be allowed to skip the third part ...
1134 if (data3
.replace("\n", "")
1137 .replace(" ", "")) == "0"*512 + "cleartomark":
1140 data
= self
.data1
.encode("ascii", errors
="surrogateescape") + data2eexec
+ data3
.encode("ascii", errors
="surrogateescape")
1141 if writer
.compress
and haszlib
:
1142 data
= zlib
.compress(data
)
1148 "/Length3 %d\n" % (len(data
), len(self
.data1
), len(data2eexec
), len(data3
)))
1149 if writer
.compress
and haszlib
:
1150 file.write("/Filter /FlateDecode\n")
1153 file.write_bytes(data
)
1159 class FontFormatError(Exception):
1162 def from_PFA_bytes(bytes
):
1163 """create a T1file instance from a string of bytes corresponding to a PFA file"""
1165 m1
= bytes
.index("eexec") + 6
1166 m2
= bytes
.index("0"*40)
1168 raise FontFormatError
1170 data1
= bytes
[:m1
].decode("ascii", errors
="surrogateescape")
1171 data2eexec
= binascii
.a2b_hex(bytes
[m1
: m2
].replace(" ", "").replace("\r", "").replace("\n", ""))
1172 data3
= bytes
[m2
:].decode("ascii", errors
="surrogateescape")
1173 return T1file(data1
, data2eexec
, data3
)
1175 def from_PFA_filename(filename
):
1176 """create a T1file instance from PFA font file of given name"""
1177 file = open(filename
, "rb")
1178 t1file
= from_PFA_bytes(file.read())
1182 def from_PFB_bytes(bytes
):
1183 """create a T1file instance from a string of bytes corresponding to a PFB file"""
1187 raise ValueError("invalid string length")
1193 def __init__(self
, bytes
):
1196 def __call__(self
, n
):
1197 result
= self
.bytes
[self
.pos
:self
.pos
+n
]
1201 consume
= consumer(bytes
)
1203 if mark
!= b
"\200\1":
1204 raise FontFormatError
1205 data1
= consume(pfblength(consume(4))).decode("ascii", errors
="surrogateescape")
1207 if mark
!= b
"\200\2":
1208 raise FontFormatError
1210 while mark
== b
"\200\2":
1211 data2eexec
= data2eexec
+ consume(pfblength(consume(4)))
1213 if mark
!= b
"\200\1":
1214 raise FontFormatError
1215 data3
= consume(pfblength(consume(4))).decode("ascii", errors
="surrogateescape")
1217 if mark
!= b
"\200\3":
1218 raise FontFormatError
1220 raise FontFormatError
1222 return T1file(data1
, data2eexec
, data3
)
1224 def from_PFB_filename(filename
):
1225 """create a T1file instance from PFB font file of given name"""
1226 file = open(filename
, "rb")
1227 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 file = open(filename
, "rb")
1240 t1file
= from_PF_bytes(file.read())