1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2005 André Wobst <wobsta@users.sourceforge.net>
5 # Copyright (C) 2006 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 from __future__
import nested_scopes
25 import array
, binascii
, re
35 # fallback implementation for Python 2.2 and below
37 return zip(xrange(len(list)), list)
40 from pyx
.path
import path
, moveto_pt
, lineto_pt
, curveto_pt
, closepath
51 def __init__(self
, t1font
):
52 """context for T1cmd evaluation"""
64 ######################################################################
66 # Note, that the T1 commands are variable-free except for plain number,
67 # which are stored as integers. All other T1 commands exist as a single
75 def __init__(self
, code
, subcmd
=0):
79 T1subcmds
[code
] = self
84 """returns a string representation of the T1 command"""
85 raise NotImplementedError
87 def updatepath(self
, path
, trafo
, context
):
88 """update path instance applying trafo to the points"""
89 raise NotImplementedError
91 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
92 """gather dependancy information
94 subrs is the "called-subrs" dictionary. gathercalls will insert the
95 subr number as key having the value 1, i.e. subrs.keys() will become the
96 numbers of used subrs. Similar seacglyphs will contain all glyphs in
97 composite characters (subrs and othersubrs for those glyphs will also
98 already be included) and othersubrs the othersubrs called.
100 This method might will not properly update all information in the
101 context (especially consuming values from the stack) and will also skip
102 various tests for performance reasons. For most T1 commands it just
103 doesn't need to do anything.
108 # commands for starting and finishing
110 class _T1endchar(T1cmd
):
113 T1cmd
.__init
__(self
, 14)
118 def updatepath(self
, path
, trafo
, context
):
121 T1endchar
= _T1endchar()
124 class _T1hsbw(T1cmd
):
127 T1cmd
.__init
__(self
, 13)
132 def updatepath(self
, path
, trafo
, context
):
133 sbx
= context
.t1stack
.pop(0)
134 wx
= context
.t1stack
.pop(0)
135 path
.append(moveto_pt(*trafo
.apply_pt(sbx
, 0)))
144 class _T1seac(T1cmd
):
147 T1cmd
.__init
__(self
, 6, subcmd
=1)
152 def updatepath(self
, path
, atrafo
, context
):
153 sab
= context
.t1stack
.pop(0)
154 adx
= context
.t1stack
.pop(0)
155 ady
= context
.t1stack
.pop(0)
156 bchar
= context
.t1stack
.pop(0)
157 achar
= context
.t1stack
.pop(0)
158 context
.t1font
.updateglyphpath(bglyph
, path
, atrafo
, context
)
159 atrafo
= atrafo
* trafo
.translate_pt(adx
-sab
, ady
)
160 context
.t1font
.updateglyphpath(aglyph
, path
, atrafo
, context
)
162 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
163 bchar
= context
.t1stack
.pop()
164 achar
= context
.t1stack
.pop()
165 aglyph
= encoding
.adobestandardencoding
.decode(achar
)
166 bglyph
= encoding
.adobestandardencoding
.decode(bchar
)
167 seacglyphs
[aglyph
] = 1
168 seacglyphs
[bglyph
] = 1
169 context
.t1font
.gatherglyphcalls(bglyph
, seacglyphs
, subrs
, othersubrs
, context
)
170 context
.t1font
.gatherglyphcalls(aglyph
, seacglyphs
, subrs
, othersubrs
, context
)
178 T1cmd
.__init
__(self
, 7, subcmd
=1)
183 def updatepath(self
, path
, trafo
, context
):
184 sbx
= context
.t1stack
.pop(0)
185 sby
= context
.t1stack
.pop(0)
186 wx
= context
.t1stack
.pop(0)
187 wy
= context
.t1stack
.pop(0)
188 path
.append(moveto_pt(*trafo
.apply_pt(sbx
, sby
)))
197 # path construction commands
199 class _T1closepath(T1cmd
):
202 T1cmd
.__init
__(self
, 9)
207 def updatepath(self
, path
, trafo
, context
):
208 path
.append(closepath())
209 # The closepath in T1 is different from PostScripts in that it does
210 # *not* modify the current position; hence we need to add an additional
212 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
, context
.y
)))
214 T1closepath
= _T1closepath()
217 class _T1hlineto(T1cmd
):
220 T1cmd
.__init
__(self
, 6)
225 def updatepath(self
, path
, trafo
, context
):
226 dx
= context
.t1stack
.pop(0)
227 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
)))
230 T1hlineto
= _T1hlineto()
233 class _T1hmoveto(T1cmd
):
236 T1cmd
.__init
__(self
, 22)
241 def updatepath(self
, path
, trafo
, context
):
242 dx
= context
.t1stack
.pop(0)
243 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
)))
246 T1hmoveto
= _T1hmoveto()
249 class _T1hvcurveto(T1cmd
):
252 T1cmd
.__init
__(self
, 31)
257 def updatepath(self
, path
, trafo
, context
):
258 dx1
= context
.t1stack
.pop(0)
259 dx2
= context
.t1stack
.pop(0)
260 dy2
= context
.t1stack
.pop(0)
261 dy3
= context
.t1stack
.pop(0)
262 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
+ dx1
, context
.y
) +
263 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy2
) +
264 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy2
+ dy3
))))
268 T1hvcurveto
= _T1hvcurveto()
271 class _T1rlineto(T1cmd
):
274 T1cmd
.__init
__(self
, 5)
279 def updatepath(self
, path
, trafo
, context
):
280 dx
= context
.t1stack
.pop(0)
281 dy
= context
.t1stack
.pop(0)
282 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
+ dy
)))
286 T1rlineto
= _T1rlineto()
289 class _T1rmoveto(T1cmd
):
292 T1cmd
.__init
__(self
, 21)
297 def updatepath(self
, path
, trafo
, context
):
298 dx
= context
.t1stack
.pop(0)
299 dy
= context
.t1stack
.pop(0)
300 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
+ dy
)))
304 T1rmoveto
= _T1rmoveto()
307 class _T1rrcurveto(T1cmd
):
310 T1cmd
.__init
__(self
, 8)
315 def updatepath(self
, path
, trafo
, context
):
316 dx1
= context
.t1stack
.pop(0)
317 dy1
= context
.t1stack
.pop(0)
318 dx2
= context
.t1stack
.pop(0)
319 dy2
= context
.t1stack
.pop(0)
320 dx3
= context
.t1stack
.pop(0)
321 dy3
= context
.t1stack
.pop(0)
322 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
+ dx1
, context
.y
+ dy1
) +
323 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy1
+ dy2
) +
324 trafo
.apply_pt(context
.x
+ dx1
+ dx2
+ dx3
, context
.y
+ dy1
+ dy2
+ dy3
))))
325 context
.x
+= dx1
+dx2
+dx3
326 context
.y
+= dy1
+dy2
+dy3
328 T1rrcurveto
= _T1rrcurveto()
331 class _T1vlineto(T1cmd
):
334 T1cmd
.__init
__(self
, 7)
339 def updatepath(self
, path
, trafo
, context
):
340 dy
= context
.t1stack
.pop(0)
341 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
, context
.y
+ dy
)))
344 T1vlineto
= _T1vlineto()
347 class _T1vmoveto(T1cmd
):
350 T1cmd
.__init
__(self
, 4)
355 def updatepath(self
, path
, trafo
, context
):
356 dy
= context
.t1stack
.pop(0)
357 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
, context
.y
+ dy
)))
360 T1vmoveto
= _T1vmoveto()
363 class _T1vhcurveto(T1cmd
):
366 T1cmd
.__init
__(self
, 30)
371 def updatepath(self
, path
, trafo
, context
):
372 dy1
= context
.t1stack
.pop(0)
373 dx2
= context
.t1stack
.pop(0)
374 dy2
= context
.t1stack
.pop(0)
375 dx3
= context
.t1stack
.pop(0)
376 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
, context
.y
+ dy1
) +
377 trafo
.apply_pt(context
.x
+ dx2
, context
.y
+ dy1
+ dy2
) +
378 trafo
.apply_pt(context
.x
+ dx2
+ dx3
, context
.y
+ dy1
+ dy2
))))
382 T1vhcurveto
= _T1vhcurveto()
387 class _T1dotsection(T1cmd
):
390 T1cmd
.__init
__(self
, 0, subcmd
=1)
395 def updatepath(self
, path
, trafo
, context
):
398 T1dotsection
= _T1dotsection()
401 class _T1hstem(T1cmd
):
404 T1cmd
.__init
__(self
, 1)
409 def updatepath(self
, path
, trafo
, context
):
410 y
= context
.t1stack
.pop(0)
411 dy
= context
.t1stack
.pop(0)
416 class _T1hstem3(T1cmd
):
419 T1cmd
.__init
__(self
, 2, subcmd
=1)
424 def updatepath(self
, path
, trafo
, context
):
425 y0
= context
.t1stack
.pop(0)
426 dy0
= context
.t1stack
.pop(0)
427 y1
= context
.t1stack
.pop(0)
428 dy1
= context
.t1stack
.pop(0)
429 y2
= context
.t1stack
.pop(0)
430 dy2
= context
.t1stack
.pop(0)
432 T1hstem3
= _T1hstem3()
435 class _T1vstem(T1cmd
):
438 T1cmd
.__init
__(self
, 3)
443 def updatepath(self
, path
, trafo
, context
):
444 x
= context
.t1stack
.pop(0)
445 dx
= context
.t1stack
.pop(0)
450 class _T1vstem3(T1cmd
):
453 T1cmd
.__init
__(self
, 1, subcmd
=1)
458 def updatepath(self
, path
, trafo
, context
):
459 self
.x0
= context
.t1stack
.pop(0)
460 self
.dx0
= context
.t1stack
.pop(0)
461 self
.x1
= context
.t1stack
.pop(0)
462 self
.dx1
= context
.t1stack
.pop(0)
463 self
.x2
= context
.t1stack
.pop(0)
464 self
.dx2
= context
.t1stack
.pop(0)
466 T1vstem3
= _T1vstem3()
474 T1cmd
.__init
__(self
, 12, subcmd
=1)
479 def updatepath(self
, path
, trafo
, context
):
480 num2
= context
.t1stack
.pop()
481 num1
= context
.t1stack
.pop()
482 context
.t1stack
.append(divmod(num1
, num2
)[0])
484 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
485 num2
= context
.t1stack
.pop()
486 num1
= context
.t1stack
.pop()
487 context
.t1stack
.append(divmod(num1
, num2
)[0])
492 # subroutine commands
494 class _T1callothersubr(T1cmd
):
497 T1cmd
.__init
__(self
, 16, subcmd
=1)
500 return "callothersubr"
502 def updatepath(self
, path
, trafo
, context
):
503 othersubrnumber
= context
.t1stack
.pop()
504 n
= context
.t1stack
.pop()
506 context
.psstack
.append(context
.t1stack
.pop())
508 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
509 othersubrnumber
= context
.t1stack
.pop()
510 othersubrs
[othersubrnumber
] = 1
511 n
= context
.t1stack
.pop()
513 context
.psstack
.append(context
.t1stack
.pop())
515 T1callothersubr
= _T1callothersubr()
518 class _T1callsubr(T1cmd
):
521 T1cmd
.__init
__(self
, 10)
526 def updatepath(self
, path
, trafo
, context
):
527 subr
= context
.t1stack
.pop()
528 context
.t1font
.updatesubrpath(subr
, path
, trafo
, context
)
530 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
531 subr
= context
.t1stack
.pop()
533 context
.t1font
.gathersubrcalls(subr
, seacglyphs
, subrs
, othersubrs
, context
)
535 T1callsubr
= _T1callsubr()
541 T1cmd
.__init
__(self
, 17, subcmd
=1)
546 def updatepath(self
, path
, trafo
, context
):
547 context
.t1stack
.append(context
.psstack
.pop())
549 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
550 context
.t1stack
.append(context
.psstack
.pop())
555 class _T1return(T1cmd
):
558 T1cmd
.__init
__(self
, 11)
563 def updatepath(self
, path
, trafo
, context
):
566 T1return
= _T1return()
569 class _T1setcurrentpoint(T1cmd
):
572 T1cmd
.__init
__(self
, 33, subcmd
=1)
575 return "setcurrentpoint" % self
.x
, self
.y
577 def updatepath(self
, path
, trafo
, context
):
578 x
= context
.t1stack
.pop(0)
579 y
= context
.t1stack
.pop(0)
580 path
.append(moveto_pt(*trafo
.apply_pt(x
, y
)))
584 T1setcurrentpoint
= _T1setcurrentpoint()
587 ######################################################################
590 """cursor to read a string token by token"""
592 def __init__(self
, data
, startstring
, eattokensep
=1, tokenseps
=" \t\r\n", tokenstarts
="()<>[]{}/%"):
593 """creates a cursor for the string data
595 startstring is a string at which the cursor should start at. The first
596 ocurance of startstring is used. When startstring is not in data, an
597 exception is raised, otherwise the cursor is set to the position right
598 after the startstring. When eattokenseps is set, startstring must be
599 followed by a tokensep and this first tokensep is also consumed.
600 tokenseps is a string containing characters to be used as token
601 separators. tokenstarts is a string containing characters which
602 directly (even without intermediate token separator) start a new token.
605 self
.pos
= self
.data
.index(startstring
) + len(startstring
)
606 self
.tokenseps
= tokenseps
607 self
.tokenstarts
= tokenstarts
609 if self
.data
[self
.pos
] not in self
.tokenstarts
:
610 if self
.data
[self
.pos
] not in self
.tokenseps
:
611 raise ValueError("cursor initialization string is not followed by a token separator")
615 """get the next token
617 Leading token separators and comments are silently consumed. The first token
618 separator after the token is also silently consumed."""
619 while self
.data
[self
.pos
] in self
.tokenseps
:
621 # ignore comments including subsequent whitespace characters
622 while self
.data
[self
.pos
] == "%":
623 while self
.data
[self
.pos
] not in "\r\n":
625 while self
.data
[self
.pos
] in self
.tokenseps
:
628 while self
.data
[self
.pos
] not in self
.tokenseps
:
629 # any character in self.tokenstarts ends the token
630 if self
.pos
>startpos
and self
.data
[self
.pos
] in self
.tokenstarts
:
633 result
= self
.data
[startpos
:self
.pos
]
634 if self
.data
[self
.pos
] in self
.tokenseps
:
635 self
.pos
+= 1 # consume a single tokensep
639 """get the next token as an integer"""
640 return int(self
.gettoken())
642 def getbytes(self
, count
):
643 """get the next count bytes"""
646 return self
.data
[startpos
: self
.pos
]
654 def __init__(self
, data1
, data2eexec
, data3
):
655 """initializes a t1font instance
657 data1 and data3 are the two clear text data parts and data2 is
658 the binary data part"""
660 self
._data
2eexec
= data2eexec
663 # marker and value for decoded data
665 # note that data2eexec is set to none by setsubrcmds and setglyphcmds
666 # this *also* denotes, that data2 is out-of-date; hence they are both
667 # marked by an _ and getdata2 and getdata2eexec will properly resolve
668 # the current state of decoding ...
670 # marker and value for standard encoding check
673 def _eexecdecode(self
, code
):
674 """eexec decoding of code"""
675 return decoder(code
, self
.eexecr
, 4)
677 def _charstringdecode(self
, code
):
678 """charstring decoding of code"""
679 return decoder(code
, self
.charstringr
, self
.lenIV
)
681 def _eexecencode(self
, data
):
682 """eexec encoding of data"""
683 return encoder(data
, self
.eexecr
, "PyX!")
685 def _charstringencode(self
, data
):
686 """eexec encoding of data"""
687 return encoder(data
, self
.charstringr
, "PyX!"[:self
.lenIV
])
689 lenIVpattern
= re
.compile("/lenIV\s+(\d+)\s+def\s+")
690 flexhintsubrs
= [[3, 0, T1callothersubr
, T1pop
, T1pop
, T1setcurrentpoint
, T1return
],
691 [0, 1, T1callothersubr
, T1return
],
692 [0, 2, T1callothersubr
, T1return
],
696 """helper method to lookup the encoding in the font"""
697 c
= cursor(self
.data1
, "/Encoding")
698 token1
= c
.gettoken()
699 token2
= c
.gettoken()
700 if token1
== "StandardEncoding" and token2
== "def":
701 self
.encoding
= encoding
.adobestandardencoding
703 encvector
= [None]*256
705 self
.encodingstart
= c
.pos
706 if c
.gettoken() == "dup":
712 encvector
[i
] = glyph
[1:]
713 token
= c
.gettoken(); assert token
== "put"
714 self
.encodingend
= c
.pos
716 if token
== "readonly" or token
== "def":
718 assert token
== "dup"
719 self
.encoding
= encoding
.encoding(encvector
)
721 def _data2decode(self
):
722 """decodes data2eexec to the data2 string and the subr and glyphs dictionary
724 It doesn't make sense to call this method twice -- check the content of
725 data2 before calling. The method also keeps the subrs and charstrings
726 start and end positions for later use."""
727 self
._data
2 = self
._eexecdecode
(self
._data
2eexec
)
729 m
= self
.lenIVpattern
.search(self
._data
2)
731 self
.lenIV
= int(m
.group(1))
734 self
.emptysubr
= self
._charstringencode
(chr(11))
737 c
= cursor(self
._data
2, "/Subrs")
738 self
.subrsstart
= c
.pos
739 arraycount
= c
.getint()
740 token
= c
.gettoken(); assert token
== "array"
742 for i
in range(arraycount
):
743 token
= c
.gettoken(); assert token
== "dup"
744 token
= c
.getint(); assert token
== i
747 self
.subrrdtoken
= c
.gettoken()
749 token
= c
.gettoken(); assert token
== self
.subrrdtoken
750 self
.subrs
.append(c
.getbytes(size
))
752 if token
== "noaccess":
753 token
= "%s %s" % (token
, c
.gettoken())
755 self
.subrnptoken
= token
757 assert token
== self
.subrnptoken
758 self
.subrsend
= c
.pos
760 # hasflexhintsubrs is a boolean indicating that the font uses flex or
761 # hint replacement subrs as specified by Adobe (tm). When it does, the
762 # first 4 subrs should all be copied except when none of them are used
763 # in the stripped version of the font since we than get a font not
764 # using flex or hint replacement subrs at all.
765 self
.hasflexhintsubrs
= (arraycount
>= len(self
.flexhintsubrs
) and
767 for i
in range(len(self
.flexhintsubrs
))] == self
.flexhintsubrs
)
771 self
.glyphlist
= [] # we want to keep the order of the glyph names
772 c
= cursor(self
._data
2, "/CharStrings")
773 self
.charstringsstart
= c
.pos
775 token
= c
.gettoken(); assert token
== "dict"
776 token
= c
.gettoken(); assert token
== "dup"
777 token
= c
.gettoken(); assert token
== "begin"
780 chartoken
= c
.gettoken()
781 if chartoken
== "end":
783 assert chartoken
[0] == "/"
786 self
.glyphrdtoken
= c
.gettoken()
788 token
= c
.gettoken(); assert token
== self
.glyphrdtoken
789 self
.glyphlist
.append(chartoken
[1:])
790 self
.glyphs
[chartoken
[1:]] = c
.getbytes(size
)
792 self
.glyphndtoken
= c
.gettoken()
794 token
= c
.gettoken(); assert token
== self
.glyphndtoken
796 self
.charstringsend
= c
.pos
797 assert not self
.subrs
or self
.subrrdtoken
== self
.glyphrdtoken
799 def _cmds(self
, code
):
800 """return a list of T1cmd's for encoded charstring data in code"""
801 code
= array
.array("B", self
._charstringdecode
(code
))
805 if x
== 12: # this starts an escaped cmd
806 cmds
.append(T1subcmds
[code
.pop(0)])
807 elif 0 <= x
< 32: # those are cmd's
808 cmds
.append(T1cmds
[x
])
809 elif 32 <= x
<= 246: # short ints
811 elif 247 <= x
<= 250: # mid size ints
812 cmds
.append(((x
- 247)*256) + code
.pop(0) + 108)
813 elif 251 <= x
<= 254: # mid size ints
814 cmds
.append(-((x
- 251)*256) - code
.pop(0) - 108)
815 else: # x = 255, i.e. full size ints
816 y
= ((code
.pop(0)*256l+code
.pop(0))*256+code
.pop(0))*256+code
.pop(0)
818 cmds
.append(y
- (1l << 32))
823 def _code(self
, cmds
):
824 """return an encoded charstring data for list of T1cmd's in cmds"""
825 code
= array
.array("B")
830 code
.append(cmd
.code
)
831 except AttributeError:
832 if -107 <= cmd
<= 107:
834 elif 108 <= cmd
<= 1131:
835 a
, b
= divmod(cmd
-108, 256)
838 elif -1131 <= cmd
<= -108:
839 a
, b
= divmod(-cmd
-108, 256)
845 cmd
, x4
= divmod(cmd
, 256)
846 cmd
, x3
= divmod(cmd
, 256)
847 x1
, x2
= divmod(cmd
, 256)
853 return self
._charstringencode
(code
.tostring())
855 def getsubrcmds(self
, subr
):
856 """return a list of T1cmd's for subr subr"""
859 return self
._cmds
(self
.subrs
[subr
])
861 def getglyphcmds(self
, glyph
):
862 """return a list of T1cmd's for glyph glyph"""
865 return self
._cmds
(self
.glyphs
[glyph
])
867 def setsubrcmds(self
, subr
, cmds
):
868 """replaces the T1cmd's by the list cmds for subr subr"""
871 self
._data
2eexec
= None
872 self
.subrs
[subr
] = self
._code
(cmds
)
874 def setglyphcmds(self
, glyph
, cmds
):
875 """replaces the T1cmd's by the list cmds for glyph glyph"""
878 self
._data
2eexec
= None
879 self
.glyphs
[glyph
] = self
._code
(cmds
)
881 def updatepath(self
, cmds
, path
, trafo
, context
):
883 if isinstance(cmd
, T1cmd
):
884 cmd
.updatepath(path
, trafo
, context
)
886 context
.t1stack
.append(cmd
)
888 def updatesubrpath(self
, subr
, path
, trafo
, context
):
889 self
.updatepath(self
.getsubrcmds(subr
), path
, trafo
, context
)
891 def updateglyphpath(self
, glyph
, path
, trafo
, context
):
892 self
.updatepath(self
.getglyphcmds(glyph
), path
, trafo
, context
)
894 def gathercalls(self
, cmds
, seacglyphs
, subrs
, othersubrs
, context
):
896 if isinstance(cmd
, T1cmd
):
897 cmd
.gathercalls(seacglyphs
, subrs
, othersubrs
, context
)
899 context
.t1stack
.append(cmd
)
901 def gathersubrcalls(self
, subr
, seacglyphs
, subrs
, othersubrs
, context
):
902 self
.gathercalls(self
.getsubrcmds(subr
), seacglyphs
, subrs
, othersubrs
, context
)
904 def gatherglyphcalls(self
, glyph
, seacglyphs
, subrs
, othersubrs
, context
):
905 self
.gathercalls(self
.getglyphcmds(glyph
), seacglyphs
, subrs
, othersubrs
, context
)
907 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")
909 def getglyphpathwxwy_pt(self
, glyph
, size
):
910 m
= self
.fontmatrixpattern
.search(self
.data1
)
911 m11
, m12
, m21
, m22
, v1
, v2
= map(float, m
.groups()[:6])
912 t
= trafo
.trafo_pt(matrix
=((m11
, m12
), (m21
, m22
)), vector
=(v1
, v2
)).scaled(size
)
913 context
= T1context(self
)
915 self
.updateglyphpath(glyph
, p
, t
, context
)
916 wx
, wy
= t
.apply_pt(context
.wx
, context
.wy
)
919 def getglyphpath(self
, glyph
, size
):
920 """return a PyX path for glyph named glyph"""
921 return self
.getglyphpathwxwy_pt(glyph
, size
)[0]
923 def getglyphwxwy_pt(self
, glyph
, size
):
924 return self
.getglyphpathwxwy_pt(glyph
, size
)[1:]
926 def getdata2(self
, subrs
=None, glyphs
=None):
927 """makes a data2 string
929 subrs is a dict containing those subrs numbers as keys,
930 which are to be contained in the subrsstring to be created.
931 If subrs is None, all subrs in self.subrs will be used.
932 The subrs dict might be modified *in place*.
934 glyphs is a dict containing those glyph names as keys,
935 which are to be contained in the charstringsstring to be created.
936 If glyphs is None, all glyphs in self.glyphs will be used."""
937 def addsubrs(subrs
, result
):
938 if subrs
is not None:
939 # some adjustments to the subrs dict
941 subrsindices
= subrs
.keys()
942 subrsmin
= min(subrsindices
)
943 subrsmax
= max(subrsindices
)
944 if self
.hasflexhintsubrs
and subrsmin
< len(self
.flexhintsubrs
):
945 # According to the spec we need to keep all the flex and hint subrs
946 # as long as any of it is used.
947 for subr
in range(len(self
.flexhintsubrs
)):
952 # build a new subrs dict containing all subrs
953 subrs
= dict([(subr
, 1) for subr
in range(len(self
.subrs
))])
954 subrsmax
= len(self
.subrs
) - 1
956 # build the string from all selected subrs
957 result
.append("%d array\n" % (subrsmax
+ 1))
958 for subr
in range(subrsmax
+1):
959 if subrs
.has_key(subr
):
960 code
= self
.subrs
[subr
]
962 code
= self
.emptysubr
963 result
.append("dup %d %d %s %s %s\n" % (subr
, len(code
), self
.subrrdtoken
, code
, self
.subrnptoken
))
965 def addcharstrings(glyphs
, result
):
966 result
.append("%d dict dup begin\n" % (glyphs
is None and len(self
.glyphlist
) or len(glyphs
)))
967 for glyph
in self
.glyphlist
:
968 if glyphs
is None or glyphs
.has_key(glyph
):
969 result
.append("/%s %d %s %s %s\n" % (glyph
, len(self
.glyphs
[glyph
]), self
.glyphrdtoken
, self
.glyphs
[glyph
], self
.glyphndtoken
))
970 result
.append("end\n")
972 if self
.subrsstart
< self
.charstringsstart
:
973 result
= [self
._data
2[:self
.subrsstart
]]
974 addsubrs(subrs
, result
)
975 result
.append(self
._data
2[self
.subrsend
:self
.charstringsstart
])
976 addcharstrings(glyphs
, result
)
977 result
.append(self
._data
2[self
.charstringsend
:])
979 result
= [self
._data
2[:self
.charstringsstart
]]
980 addcharstrings(glyphs
, result
)
981 result
.append(self
._data
2[self
.charstringsend
:self
.subrsstart
])
982 addsubrs(subrs
, result
)
983 result
.append(self
._data
2[self
.subrsend
:])
984 return "".join(result
)
986 def getdata2eexec(self
):
988 return self
._data
2eexec
989 # note that self._data2 is out-of-date here too, hence we need to call getdata2
990 return self
._eexecencode
(self
.getdata2())
992 newlinepattern
= re
.compile("\s*[\r\n]\s*")
993 uniqueidpattern
= re
.compile("/UniqueID\s+\d+\s+def\s+")
995 def getstrippedfont(self
, glyphs
):
996 """create a T1font instance containing only certain glyphs
998 glyphs is a dict having the glyph names to be contained as keys.
999 The glyphs dict might be modified *in place*.
1001 # TODO: we could also strip othersubrs to those actually used
1003 # collect information about used glyphs and subrs
1007 for glyph
in glyphs
.keys():
1008 self
.gatherglyphcalls(glyph
, seacglyphs
, subrs
, othersubrs
, T1context(self
))
1009 # while we have gathered all subrs for the seacglyphs alreadys, we
1010 # might have missed the glyphs themself (when they are not used stand-alone)
1011 glyphs
.update(seacglyphs
)
1012 glyphs
[".notdef"] = 1
1015 if not self
.encoding
:
1017 if self
.encoding
is encoding
.adobestandardencoding
:
1020 encodingstrings
= []
1021 for char
, glyph
in enumerate(self
.encoding
.encvector
):
1022 if glyph
in glyphs
.keys():
1023 encodingstrings
.append("dup %i /%s put\n" % (char
, glyph
))
1024 data1
= self
.data1
[:self
.encodingstart
] + "".join(encodingstrings
) + self
.data1
[self
.encodingend
:]
1025 data1
= self
.newlinepattern
.subn("\n", data1
)[0]
1026 data1
= self
.uniqueidpattern
.subn("", data1
)[0]
1029 data2
= self
.uniqueidpattern
.subn("", self
.getdata2(subrs
, glyphs
))[0]
1032 data3
= self
.newlinepattern
.subn("\n", self
.data3
)[0]
1034 # create and return the new font instance
1035 return T1font(data1
.rstrip() + "\n", self
._eexecencode
(data2
), data3
.rstrip() + "\n")
1038 # As a simple heuristics we assume non-symbolic fonts if and only
1039 # if the Adobe standard encoding is used. All other font flags are
1040 # not specified here.
1041 if not self
.encoding
:
1043 if self
.encoding
is encoding
.adobestandardencoding
:
1047 def outputPFA(self
, file):
1048 """output the T1font in PFA format"""
1049 file.write(self
.data1
)
1050 data2eexechex
= binascii
.b2a_hex(self
.getdata2eexec())
1052 for i
in range((len(data2eexechex
)-1)/linelength
+ 1):
1053 file.write(data2eexechex
[i
*linelength
: i
*linelength
+linelength
])
1055 file.write(self
.data3
)
1057 def outputPFB(self
, file):
1058 """output the T1font in PFB format"""
1059 data2eexec
= self
.getdata2eexec()
1060 def pfblength(data
):
1062 l
, x1
= divmod(l
, 256)
1063 l
, x2
= divmod(l
, 256)
1064 x4
, x3
= divmod(l
, 256)
1065 return chr(x1
) + chr(x2
) + chr(x3
) + chr(x4
)
1066 file.write("\200\1")
1067 file.write(pfblength(self
.data1
))
1068 file.write(self
.data1
)
1069 file.write("\200\2")
1070 file.write(pfblength(data2eexec
))
1071 file.write(data2eexec
)
1072 file.write("\200\1")
1073 file.write(pfblength(self
.data3
))
1074 file.write(self
.data3
)
1075 file.write("\200\3")
1077 def outputPS(self
, file, writer
):
1078 """output the PostScript code for the T1font to the file file"""
1079 self
.outputPFA(file)
1081 def outputPDF(self
, file, writer
):
1082 data2eexec
= self
.getdata2eexec()
1084 # we might be allowed to skip the third part ...
1085 if (data3
.replace("\n", "")
1088 .replace(" ", "")) == "0"*512 + "cleartomark":
1091 data
= self
.data1
+ data2eexec
+ data3
1092 if writer
.compress
and haszlib
:
1093 data
= zlib
.compress(data
)
1099 "/Length3 %d\n" % (len(data
), len(self
.data1
), len(data2eexec
), len(data3
)))
1100 if writer
.compress
and haszlib
:
1101 file.write("/Filter /FlateDecode\n")
1109 class T1pfafont(T1font
):
1111 """create a T1font instance from a pfa font file"""
1113 def __init__(self
, filename
):
1114 d
= open(filename
, "rb").read()
1115 # hey, that's quick'n'dirty
1116 m1
= d
.index("eexec") + 6
1117 m2
= d
.index("0"*40)
1119 data2
= binascii
.a2b_hex(d
[m1
: m2
].replace(" ", "").replace("\r", "").replace("\n", ""))
1121 T1font
.__init
__(self
, data1
, data2
, data3
)
1124 class T1pfbfont(T1font
):
1126 """create a T1font instance from a pfb font file"""
1128 def __init__(self
, filename
):
1131 raise ValueError("invalid string length")
1135 ord(s
[3])*256*256*256)
1136 f
= open(filename
, "rb")
1137 mark
= f
.read(2); assert mark
== "\200\1"
1138 data1
= f
.read(pfblength(f
.read(4)))
1139 mark
= f
.read(2); assert mark
== "\200\2"
1141 while mark
== "\200\2":
1142 data2
= data2
+ f
.read(pfblength(f
.read(4)))
1144 assert mark
== "\200\1"
1145 data3
= f
.read(pfblength(f
.read(4)))
1146 mark
= f
.read(2); assert mark
== "\200\3"
1147 assert not f
.read(1)
1148 T1font
.__init
__(self
, data1
, data2
, data3
)