2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2005 André Wobst <wobsta@users.sourceforge.net>
6 # Copyright (C) 2006 Jörg Lehmann <joergl@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 from __future__
import nested_scopes
26 import array
, binascii
, re
34 from pyx
.path
import path
, moveto_pt
, lineto_pt
, curveto_pt
, closepath
45 def __init__(self
, t1font
):
46 """context for T1cmd evaluation"""
58 ######################################################################
60 # Note, that the T1 commands are variable-free except for plain number,
61 # which are stored as integers. All other T1 commands exist as a single
69 def __init__(self
, code
, subcmd
=False):
73 T1subcmds
[code
] = self
78 """returns a string representation of the T1 command"""
79 raise NotImplementedError
81 def updatepath(self
, path
, trafo
, context
):
82 """update path instance applying trafo to the points"""
83 raise NotImplementedError
85 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
86 """gather dependancy information
88 subrs is the "called-subrs" dictionary. gathercalls will insert the
89 subr number as key having the value 1, i.e. subrs.keys() will become the
90 numbers of used subrs. Similar seacglyphs will contain all glyphs in
91 composite characters (subrs and othersubrs for those glyphs will also
92 already be included) and othersubrs the othersubrs called.
94 This method might will not properly update all information in the
95 context (especially consuming values from the stack) and will also skip
96 various tests for performance reasons. For most T1 commands it just
97 doesn't need to do anything.
102 # commands for starting and finishing
104 class _T1endchar(T1cmd
):
107 T1cmd
.__init
__(self
, 14)
112 def updatepath(self
, path
, trafo
, context
):
115 T1endchar
= _T1endchar()
118 class _T1hsbw(T1cmd
):
121 T1cmd
.__init
__(self
, 13)
126 def updatepath(self
, path
, trafo
, context
):
127 sbx
= context
.t1stack
.pop(0)
128 wx
= context
.t1stack
.pop(0)
129 path
.append(moveto_pt(*trafo
.apply_pt(sbx
, 0)))
138 class _T1seac(T1cmd
):
141 T1cmd
.__init
__(self
, 6, subcmd
=True)
146 def updatepath(self
, path
, atrafo
, context
):
147 sab
= context
.t1stack
.pop(0)
148 adx
= context
.t1stack
.pop(0)
149 ady
= context
.t1stack
.pop(0)
150 bchar
= context
.t1stack
.pop(0)
151 achar
= context
.t1stack
.pop(0)
152 context
.t1font
.updateglyphpath(bglyph
, path
, atrafo
, context
)
153 atrafo
= atrafo
* trafo
.translate_pt(adx
-sab
, ady
)
154 context
.t1font
.updateglyphpath(aglyph
, path
, atrafo
, context
)
156 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
157 bchar
= context
.t1stack
.pop()
158 achar
= context
.t1stack
.pop()
159 aglyph
= encoding
.adobestandardencoding
.decode(achar
)
160 bglyph
= encoding
.adobestandardencoding
.decode(bchar
)
161 seacglyphs
[aglyph
] = 1
162 seacglyphs
[bglyph
] = 1
163 context
.t1font
.gatherglyphcalls(bglyph
, seacglyphs
, subrs
, othersubrs
, context
)
164 context
.t1font
.gatherglyphcalls(aglyph
, seacglyphs
, subrs
, othersubrs
, context
)
172 T1cmd
.__init
__(self
, 7, subcmd
=True)
177 def updatepath(self
, path
, trafo
, context
):
178 sbx
= context
.t1stack
.pop(0)
179 sby
= context
.t1stack
.pop(0)
180 wx
= context
.t1stack
.pop(0)
181 wy
= context
.t1stack
.pop(0)
182 path
.append(moveto_pt(*trafo
.apply_pt(sbx
, sby
)))
191 # path construction commands
193 class _T1closepath(T1cmd
):
196 T1cmd
.__init
__(self
, 9)
201 def updatepath(self
, path
, trafo
, context
):
202 path
.append(closepath())
203 # The closepath in T1 is different from PostScripts in that it does
204 # *not* modify the current position; hence we need to add an additional
206 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
, context
.y
)))
208 T1closepath
= _T1closepath()
211 class _T1hlineto(T1cmd
):
214 T1cmd
.__init
__(self
, 6)
219 def updatepath(self
, path
, trafo
, context
):
220 dx
= context
.t1stack
.pop(0)
221 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
)))
224 T1hlineto
= _T1hlineto()
227 class _T1hmoveto(T1cmd
):
230 T1cmd
.__init
__(self
, 22)
235 def updatepath(self
, path
, trafo
, context
):
236 dx
= context
.t1stack
.pop(0)
237 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
)))
240 T1hmoveto
= _T1hmoveto()
243 class _T1hvcurveto(T1cmd
):
246 T1cmd
.__init
__(self
, 31)
251 def updatepath(self
, path
, trafo
, context
):
252 dx1
= context
.t1stack
.pop(0)
253 dx2
= context
.t1stack
.pop(0)
254 dy2
= context
.t1stack
.pop(0)
255 dy3
= context
.t1stack
.pop(0)
256 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
+ dx1
, context
.y
) +
257 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy2
) +
258 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy2
+ dy3
))))
262 T1hvcurveto
= _T1hvcurveto()
265 class _T1rlineto(T1cmd
):
268 T1cmd
.__init
__(self
, 5)
273 def updatepath(self
, path
, trafo
, context
):
274 dx
= context
.t1stack
.pop(0)
275 dy
= context
.t1stack
.pop(0)
276 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
+ dy
)))
280 T1rlineto
= _T1rlineto()
283 class _T1rmoveto(T1cmd
):
286 T1cmd
.__init
__(self
, 21)
291 def updatepath(self
, path
, trafo
, context
):
292 dx
= context
.t1stack
.pop(0)
293 dy
= context
.t1stack
.pop(0)
294 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
+ dy
)))
298 T1rmoveto
= _T1rmoveto()
301 class _T1rrcurveto(T1cmd
):
304 T1cmd
.__init
__(self
, 8)
309 def updatepath(self
, path
, trafo
, context
):
310 dx1
= context
.t1stack
.pop(0)
311 dy1
= context
.t1stack
.pop(0)
312 dx2
= context
.t1stack
.pop(0)
313 dy2
= context
.t1stack
.pop(0)
314 dx3
= context
.t1stack
.pop(0)
315 dy3
= context
.t1stack
.pop(0)
316 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
+ dx1
, context
.y
+ dy1
) +
317 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy1
+ dy2
) +
318 trafo
.apply_pt(context
.x
+ dx1
+ dx2
+ dx3
, context
.y
+ dy1
+ dy2
+ dy3
))))
319 context
.x
+= dx1
+dx2
+dx3
320 context
.y
+= dy1
+dy2
+dy3
322 T1rrcurveto
= _T1rrcurveto()
325 class _T1vlineto(T1cmd
):
328 T1cmd
.__init
__(self
, 7)
333 def updatepath(self
, path
, trafo
, context
):
334 dy
= context
.t1stack
.pop(0)
335 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
, context
.y
+ dy
)))
338 T1vlineto
= _T1vlineto()
341 class _T1vmoveto(T1cmd
):
344 T1cmd
.__init
__(self
, 4)
349 def updatepath(self
, path
, trafo
, context
):
350 dy
= context
.t1stack
.pop(0)
351 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
, context
.y
+ dy
)))
354 T1vmoveto
= _T1vmoveto()
357 class _T1vhcurveto(T1cmd
):
360 T1cmd
.__init
__(self
, 30)
365 def updatepath(self
, path
, trafo
, context
):
366 dy1
= context
.t1stack
.pop(0)
367 dx2
= context
.t1stack
.pop(0)
368 dy2
= context
.t1stack
.pop(0)
369 dx3
= context
.t1stack
.pop(0)
370 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
, context
.y
+ dy1
) +
371 trafo
.apply_pt(context
.x
+ dx2
, context
.y
+ dy1
+ dy2
) +
372 trafo
.apply_pt(context
.x
+ dx2
+ dx3
, context
.y
+ dy1
+ dy2
))))
376 T1vhcurveto
= _T1vhcurveto()
381 class _T1dotsection(T1cmd
):
384 T1cmd
.__init
__(self
, 0, subcmd
=True)
389 def updatepath(self
, path
, trafo
, context
):
392 T1dotsection
= _T1dotsection()
395 class _T1hstem(T1cmd
):
398 T1cmd
.__init
__(self
, 1)
403 def updatepath(self
, path
, trafo
, context
):
404 y
= context
.t1stack
.pop(0)
405 dy
= context
.t1stack
.pop(0)
410 class _T1hstem3(T1cmd
):
413 T1cmd
.__init
__(self
, 2, subcmd
=True)
418 def updatepath(self
, path
, trafo
, context
):
419 y0
= context
.t1stack
.pop(0)
420 dy0
= context
.t1stack
.pop(0)
421 y1
= context
.t1stack
.pop(0)
422 dy1
= context
.t1stack
.pop(0)
423 y2
= context
.t1stack
.pop(0)
424 dy2
= context
.t1stack
.pop(0)
426 T1hstem3
= _T1hstem3()
429 class _T1vstem(T1cmd
):
432 T1cmd
.__init
__(self
, 3)
437 def updatepath(self
, path
, trafo
, context
):
438 x
= context
.t1stack
.pop(0)
439 dx
= context
.t1stack
.pop(0)
444 class _T1vstem3(T1cmd
):
447 T1cmd
.__init
__(self
, 1, subcmd
=True)
452 def updatepath(self
, path
, trafo
, context
):
453 self
.x0
= context
.t1stack
.pop(0)
454 self
.dx0
= context
.t1stack
.pop(0)
455 self
.x1
= context
.t1stack
.pop(0)
456 self
.dx1
= context
.t1stack
.pop(0)
457 self
.x2
= context
.t1stack
.pop(0)
458 self
.dx2
= context
.t1stack
.pop(0)
460 T1vstem3
= _T1vstem3()
468 T1cmd
.__init
__(self
, 12, subcmd
=True)
473 def updatepath(self
, path
, trafo
, context
):
474 num2
= context
.t1stack
.pop()
475 num1
= context
.t1stack
.pop()
476 context
.t1stack
.append(divmod(num1
, num2
)[0])
478 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
479 num2
= context
.t1stack
.pop()
480 num1
= context
.t1stack
.pop()
481 context
.t1stack
.append(divmod(num1
, num2
)[0])
486 # subroutine commands
488 class _T1callothersubr(T1cmd
):
491 T1cmd
.__init
__(self
, 16, subcmd
=True)
494 return "callothersubr"
496 def updatepath(self
, path
, trafo
, context
):
497 othersubrnumber
= context
.t1stack
.pop()
498 n
= context
.t1stack
.pop()
500 context
.psstack
.append(context
.t1stack
.pop())
502 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
503 othersubrnumber
= context
.t1stack
.pop()
504 othersubrs
[othersubrnumber
] = 1
505 n
= context
.t1stack
.pop()
507 context
.psstack
.append(context
.t1stack
.pop())
509 T1callothersubr
= _T1callothersubr()
512 class _T1callsubr(T1cmd
):
515 T1cmd
.__init
__(self
, 10)
520 def updatepath(self
, path
, trafo
, context
):
521 subr
= context
.t1stack
.pop()
522 context
.t1font
.updatesubrpath(subr
, path
, trafo
, context
)
524 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
525 subr
= context
.t1stack
.pop()
527 context
.t1font
.gathersubrcalls(subr
, seacglyphs
, subrs
, othersubrs
, context
)
529 T1callsubr
= _T1callsubr()
535 T1cmd
.__init
__(self
, 17, subcmd
=True)
540 def updatepath(self
, path
, trafo
, context
):
541 context
.t1stack
.append(context
.psstack
.pop())
543 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
544 context
.t1stack
.append(context
.psstack
.pop())
549 class _T1return(T1cmd
):
552 T1cmd
.__init
__(self
, 11)
557 def updatepath(self
, path
, trafo
, context
):
560 T1return
= _T1return()
563 class _T1setcurrentpoint(T1cmd
):
566 T1cmd
.__init
__(self
, 33, subcmd
=True)
569 return "setcurrentpoint" % self
.x
, self
.y
571 def updatepath(self
, path
, trafo
, context
):
572 x
= context
.t1stack
.pop(0)
573 y
= context
.t1stack
.pop(0)
574 path
.append(moveto_pt(*trafo
.apply_pt(x
, y
)))
578 T1setcurrentpoint
= _T1setcurrentpoint()
581 ######################################################################
584 """cursor to read a string token by token"""
586 def __init__(self
, data
, startstring
, eattokensep
=1, tokenseps
=" \t\r\n", tokenstarts
="()<>[]{}/%"):
587 """creates a cursor for the string data
589 startstring is a string at which the cursor should start at. The first
590 ocurance of startstring is used. When startstring is not in data, an
591 exception is raised, otherwise the cursor is set to the position right
592 after the startstring. When eattokenseps is set, startstring must be
593 followed by a tokensep and this first tokensep is also consumed.
594 tokenseps is a string containing characters to be used as token
595 separators. tokenstarts is a string containing characters which
596 directly (even without intermediate token separator) start a new token.
599 self
.pos
= self
.data
.index(startstring
) + len(startstring
)
600 self
.tokenseps
= tokenseps
601 self
.tokenstarts
= tokenstarts
603 if self
.data
[self
.pos
] not in self
.tokenstarts
:
604 if self
.data
[self
.pos
] not in self
.tokenseps
:
605 raise ValueError("cursor initialization string is not followed by a token separator")
609 """get the next token
611 Leading token separators and comments are silently consumed. The first token
612 separator after the token is also silently consumed."""
613 while self
.data
[self
.pos
] in self
.tokenseps
:
615 # ignore comments including subsequent whitespace characters
616 while self
.data
[self
.pos
] == "%":
617 while self
.data
[self
.pos
] not in "\r\n":
619 while self
.data
[self
.pos
] in self
.tokenseps
:
622 while self
.data
[self
.pos
] not in self
.tokenseps
:
623 # any character in self.tokenstarts ends the token
624 if self
.pos
>startpos
and self
.data
[self
.pos
] in self
.tokenstarts
:
627 result
= self
.data
[startpos
:self
.pos
]
628 if self
.data
[self
.pos
] in self
.tokenseps
:
629 self
.pos
+= 1 # consume a single tokensep
633 """get the next token as an integer"""
634 return int(self
.gettoken())
636 def getbytes(self
, count
):
637 """get the next count bytes"""
640 return self
.data
[startpos
: self
.pos
]
648 def __init__(self
, data1
, data2eexec
, data3
):
649 """initializes a t1font instance
651 data1 and data3 are the two clear text data parts and data2 is
652 the binary data part"""
654 self
._data
2eexec
= data2eexec
657 # marker and value for decoded data
659 # note that data2eexec is set to none by setsubrcmds and setglyphcmds
660 # this *also* denotes, that data2 is out-of-date; hence they are both
661 # marked by an _ and getdata2 and getdata2eexec will properly resolve
662 # the current state of decoding ...
664 # marker and value for standard encoding check
667 def _eexecdecode(self
, code
):
668 """eexec decoding of code"""
669 return decoder(code
, self
.eexecr
, 4)
671 def _charstringdecode(self
, code
):
672 """charstring decoding of code"""
673 return decoder(code
, self
.charstringr
, self
.lenIV
)
675 def _eexecencode(self
, data
):
676 """eexec encoding of data"""
677 return encoder(data
, self
.eexecr
, "PyX!")
679 def _charstringencode(self
, data
):
680 """eexec encoding of data"""
681 return encoder(data
, self
.charstringr
, "PyX!"[:self
.lenIV
])
683 lenIVpattern
= re
.compile("/lenIV\s+(\d+)\s+def\s+")
684 flexhintsubrs
= [[3, 0, T1callothersubr
, T1pop
, T1pop
, T1setcurrentpoint
, T1return
],
685 [0, 1, T1callothersubr
, T1return
],
686 [0, 2, T1callothersubr
, T1return
],
690 """helper method to lookup the encoding in the font"""
691 c
= cursor(self
.data1
, "/Encoding")
692 token1
= c
.gettoken()
693 token2
= c
.gettoken()
694 if token1
== "StandardEncoding" and token2
== "def":
695 self
.encoding
= encoding
.adobestandardencoding
697 encvector
= [None]*256
699 self
.encodingstart
= c
.pos
700 if c
.gettoken() == "dup":
706 encvector
[i
] = glyph
[1:]
707 token
= c
.gettoken(); assert token
== "put"
708 self
.encodingend
= c
.pos
710 if token
== "readonly" or token
== "def":
712 assert token
== "dup"
713 self
.encoding
= encoding
.encoding(encvector
)
715 def _data2decode(self
):
716 """decodes data2eexec to the data2 string and the subr and glyphs dictionary
718 It doesn't make sense to call this method twice -- check the content of
719 data2 before calling. The method also keeps the subrs and charstrings
720 start and end positions for later use."""
721 self
._data
2 = self
._eexecdecode
(self
._data
2eexec
)
723 m
= self
.lenIVpattern
.search(self
._data
2)
725 self
.lenIV
= int(m
.group(1))
728 self
.emptysubr
= self
._charstringencode
(chr(11))
731 c
= cursor(self
._data
2, "/Subrs")
732 self
.subrsstart
= c
.pos
733 arraycount
= c
.getint()
734 token
= c
.gettoken(); assert token
== "array"
736 for i
in range(arraycount
):
737 token
= c
.gettoken(); assert token
== "dup"
738 token
= c
.getint(); assert token
== i
741 self
.subrrdtoken
= c
.gettoken()
743 token
= c
.gettoken(); assert token
== self
.subrrdtoken
744 self
.subrs
.append(c
.getbytes(size
))
746 if token
== "noaccess":
747 token
= "%s %s" % (token
, c
.gettoken())
749 self
.subrnptoken
= token
751 assert token
== self
.subrnptoken
752 self
.subrsend
= c
.pos
754 # hasflexhintsubrs is a boolean indicating that the font uses flex or
755 # hint replacement subrs as specified by Adobe (tm). When it does, the
756 # first 4 subrs should all be copied except when none of them are used
757 # in the stripped version of the font since we than get a font not
758 # using flex or hint replacement subrs at all.
759 self
.hasflexhintsubrs
= (arraycount
>= len(self
.flexhintsubrs
) and
761 for i
in range(len(self
.flexhintsubrs
))] == self
.flexhintsubrs
)
765 self
.glyphlist
= [] # we want to keep the order of the glyph names
766 c
= cursor(self
._data
2, "/CharStrings")
767 self
.charstringsstart
= c
.pos
769 token
= c
.gettoken(); assert token
== "dict"
770 token
= c
.gettoken(); assert token
== "dup"
771 token
= c
.gettoken(); assert token
== "begin"
774 chartoken
= c
.gettoken()
775 if chartoken
== "end":
777 assert chartoken
[0] == "/"
780 self
.glyphrdtoken
= c
.gettoken()
782 token
= c
.gettoken(); assert token
== self
.glyphrdtoken
783 self
.glyphlist
.append(chartoken
[1:])
784 self
.glyphs
[chartoken
[1:]] = c
.getbytes(size
)
786 self
.glyphndtoken
= c
.gettoken()
788 token
= c
.gettoken(); assert token
== self
.glyphndtoken
790 self
.charstringsend
= c
.pos
791 assert not self
.subrs
or self
.subrrdtoken
== self
.glyphrdtoken
793 def _cmds(self
, code
):
794 """return a list of T1cmd's for encoded charstring data in code"""
795 code
= array
.array("B", self
._charstringdecode
(code
))
799 if x
== 12: # this starts an escaped cmd
800 cmds
.append(T1subcmds
[code
.pop(0)])
801 elif 0 <= x
< 32: # those are cmd's
802 cmds
.append(T1cmds
[x
])
803 elif 32 <= x
<= 246: # short ints
805 elif 247 <= x
<= 250: # mid size ints
806 cmds
.append(((x
- 247)*256) + code
.pop(0) + 108)
807 elif 251 <= x
<= 254: # mid size ints
808 cmds
.append(-((x
- 251)*256) - code
.pop(0) - 108)
809 else: # x = 255, i.e. full size ints
810 y
= ((code
.pop(0)*256+code
.pop(0))*256+code
.pop(0))*256+code
.pop(0)
812 cmds
.append(y
- (1l << 32))
817 def _code(self
, cmds
):
818 """return an encoded charstring data for list of T1cmd's in cmds"""
819 code
= array
.array("B")
824 code
.append(cmd
.code
)
825 except AttributeError:
826 if -107 <= cmd
<= 107:
828 elif 108 <= cmd
<= 1131:
829 a
, b
= divmod(cmd
-108, 256)
832 elif -1131 <= cmd
<= -108:
833 a
, b
= divmod(-cmd
-108, 256)
839 cmd
, x4
= divmod(cmd
, 256)
840 cmd
, x3
= divmod(cmd
, 256)
841 x1
, x2
= divmod(cmd
, 256)
847 return self
._charstringencode
(code
.tostring())
849 def getsubrcmds(self
, subr
):
850 """return a list of T1cmd's for subr subr"""
853 return self
._cmds
(self
.subrs
[subr
])
855 def getglyphcmds(self
, glyph
):
856 """return a list of T1cmd's for glyph glyph"""
859 return self
._cmds
(self
.glyphs
[glyph
])
861 def setsubrcmds(self
, subr
, cmds
):
862 """replaces the T1cmd's by the list cmds for subr subr"""
865 self
._data
2eexec
= None
866 self
.subrs
[subr
] = self
._code
(cmds
)
868 def setglyphcmds(self
, glyph
, cmds
):
869 """replaces the T1cmd's by the list cmds for glyph glyph"""
872 self
._data
2eexec
= None
873 self
.glyphs
[glyph
] = self
._code
(cmds
)
875 def updatepath(self
, cmds
, path
, trafo
, context
):
877 if isinstance(cmd
, T1cmd
):
878 cmd
.updatepath(path
, trafo
, context
)
880 context
.t1stack
.append(cmd
)
882 def updatesubrpath(self
, subr
, path
, trafo
, context
):
883 self
.updatepath(self
.getsubrcmds(subr
), path
, trafo
, context
)
885 def updateglyphpath(self
, glyph
, path
, trafo
, context
):
886 self
.updatepath(self
.getglyphcmds(glyph
), path
, trafo
, context
)
888 def gathercalls(self
, cmds
, seacglyphs
, subrs
, othersubrs
, context
):
890 if isinstance(cmd
, T1cmd
):
891 cmd
.gathercalls(seacglyphs
, subrs
, othersubrs
, context
)
893 context
.t1stack
.append(cmd
)
895 def gathersubrcalls(self
, subr
, seacglyphs
, subrs
, othersubrs
, context
):
896 self
.gathercalls(self
.getsubrcmds(subr
), seacglyphs
, subrs
, othersubrs
, context
)
898 def gatherglyphcalls(self
, glyph
, seacglyphs
, subrs
, othersubrs
, context
):
899 self
.gathercalls(self
.getglyphcmds(glyph
), seacglyphs
, subrs
, othersubrs
, context
)
901 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")
903 def getglyphpathwxwy_pt(self
, glyph
, size
):
904 m
= self
.fontmatrixpattern
.search(self
.data1
)
905 m11
, m12
, m21
, m22
, v1
, v2
= map(float, m
.groups()[:6])
906 t
= trafo
.trafo_pt(matrix
=((m11
, m12
), (m21
, m22
)), vector
=(v1
, v2
)).scaled(size
)
907 context
= T1context(self
)
909 self
.updateglyphpath(glyph
, p
, t
, context
)
910 wx
, wy
= t
.apply_pt(context
.wx
, context
.wy
)
913 def getglyphpath(self
, glyph
, size
):
914 """return a PyX path for glyph named glyph"""
915 return self
.getglyphpathwxwy_pt(glyph
, size
)[0]
917 def getglyphwxwy_pt(self
, glyph
, size
):
918 return self
.getglyphpathwxwy_pt(glyph
, size
)[1:]
920 def getdata2(self
, subrs
=None, glyphs
=None):
921 """makes a data2 string
923 subrs is a dict containing those subrs numbers as keys,
924 which are to be contained in the subrsstring to be created.
925 If subrs is None, all subrs in self.subrs will be used.
926 The subrs dict might be modified *in place*.
928 glyphs is a dict containing those glyph names as keys,
929 which are to be contained in the charstringsstring to be created.
930 If glyphs is None, all glyphs in self.glyphs will be used."""
931 def addsubrs(subrs
, result
):
932 if subrs
is not None:
933 # some adjustments to the subrs dict
935 subrsindices
= subrs
.keys()
936 subrsmin
= min(subrsindices
)
937 subrsmax
= max(subrsindices
)
938 if self
.hasflexhintsubrs
and subrsmin
< len(self
.flexhintsubrs
):
939 # According to the spec we need to keep all the flex and hint subrs
940 # as long as any of it is used.
941 for subr
in len(self
.flexhintsubrs
):
946 # build a new subrs dict containing all subrs
947 subrs
= dict([(subr
, 1) for subr
in range(len(self
.subrs
))])
948 subrsmax
= len(self
.subrs
) - 1
950 # build the string from all selected subrs
951 result
.append("%d array\n" % (subrsmax
+ 1))
952 for subr
in range(subrsmax
+1):
953 if subrs
.has_key(subr
):
954 code
= self
.subrs
[subr
]
956 code
= self
.emptysubr
957 result
.append("dup %d %d %s %s %s\n" % (subr
, len(code
), self
.subrrdtoken
, code
, self
.subrnptoken
))
959 def addcharstrings(glyphs
, result
):
960 result
.append("%d dict dup begin\n" % (glyphs
is None and len(self
.glyphlist
) or len(glyphs
)))
961 for glyph
in self
.glyphlist
:
962 if glyphs
is None or glyphs
.has_key(glyph
):
963 result
.append("/%s %d %s %s %s\n" % (glyph
, len(self
.glyphs
[glyph
]), self
.glyphrdtoken
, self
.glyphs
[glyph
], self
.glyphndtoken
))
964 result
.append("end\n")
966 if self
.subrsstart
< self
.charstringsstart
:
967 result
= [self
._data
2[:self
.subrsstart
]]
968 addsubrs(subrs
, result
)
969 result
.append(self
._data
2[self
.subrsend
:self
.charstringsstart
])
970 addcharstrings(glyphs
, result
)
971 result
.append(self
._data
2[self
.charstringsend
:])
973 result
= [self
._data
2[:self
.charstringsstart
]]
974 addcharstrings(glyphs
, result
)
975 result
.append(self
._data
2[self
.charstringsend
:self
.subrsstart
])
976 addsubrs(subrs
, result
)
977 result
.append(self
._data
2[self
.subrsend
:])
978 return "".join(result
)
980 def getdata2eexec(self
):
982 return self
._data
2eexec
983 # note that self._data2 is out-of-date here too, hence we need to call getdata2
984 return self
._eexecencode
(self
.getdata2())
986 newlinepattern
= re
.compile("\s*[\r\n]\s*")
987 uniqueidpattern
= re
.compile("/UniqueID\s+\d+\s+def\s+")
989 def getstrippedfont(self
, glyphs
):
990 """create a T1font instance containing only certain glyphs
992 glyphs is a dict having the glyph names to be contained as keys.
993 The glyphs dict might be modified *in place*.
995 # TODO: we could also strip othersubrs to those actually used
997 # collect information about used glyphs and subrs
1001 for glyph
in glyphs
.keys():
1002 self
.gatherglyphcalls(glyph
, seacglyphs
, subrs
, othersubrs
, T1context(self
))
1003 # while we have gathered all subrs for the seacglyphs alreadys, we
1004 # might have missed the glyphs themself (when they are not used stand-alone)
1005 glyphs
.update(seacglyphs
)
1006 glyphs
[".notdef"] = 1
1009 if not self
.encoding
:
1011 if self
.encoding
is encoding
.adobestandardencoding
:
1014 encodingstrings
= []
1015 for char
, glyph
in enumerate(self
.encoding
.encvector
):
1016 if glyph
in glyphs
.keys():
1017 encodingstrings
.append("dup %i /%s put\n" % (char
, glyph
))
1018 data1
= self
.data1
[:self
.encodingstart
] + "".join(encodingstrings
) + self
.data1
[self
.encodingend
:]
1019 data1
= self
.newlinepattern
.subn("\n", data1
)[0]
1020 data1
= self
.uniqueidpattern
.subn("", data1
)[0]
1023 data2
= self
.uniqueidpattern
.subn("", self
.getdata2(subrs
, glyphs
))[0]
1026 data3
= self
.newlinepattern
.subn("\n", self
.data3
)[0]
1028 # create and return the new font instance
1029 return T1font(data1
.rstrip() + "\n", self
._eexecencode
(data2
), data3
.rstrip() + "\n")
1032 # As a simple heuristics we assume non-symbolic fonts if and only
1033 # if the Adobe standard encoding is used. All other font flags are
1034 # not specified here.
1035 if not self
.encoding
:
1037 if self
.encoding
is encoding
.adobestandardencoding
:
1041 def outputPFA(self
, file):
1042 """output the T1font in PFA format"""
1043 file.write(self
.data1
)
1044 data2eexechex
= binascii
.b2a_hex(self
.getdata2eexec())
1046 for i
in range((len(data2eexechex
)-1)/linelength
+ 1):
1047 file.write(data2eexechex
[i
*linelength
: i
*linelength
+linelength
])
1049 file.write(self
.data3
)
1051 def outputPFB(self
, file):
1052 """output the T1font in PFB format"""
1053 data2eexec
= self
.getdata2eexec()
1054 def pfblength(data
):
1056 l
, x1
= divmod(l
, 256)
1057 l
, x2
= divmod(l
, 256)
1058 x4
, x3
= divmod(l
, 256)
1059 return chr(x1
) + chr(x2
) + chr(x3
) + chr(x4
)
1060 file.write("\200\1")
1061 file.write(pfblength(self
.data1
))
1062 file.write(self
.data1
)
1063 file.write("\200\2")
1064 file.write(pfblength(data2eexec
))
1065 file.write(data2eexec
)
1066 file.write("\200\1")
1067 file.write(pfblength(self
.data3
))
1068 file.write(self
.data3
)
1069 file.write("\200\3")
1071 def outputPS(self
, file, writer
):
1072 """output the PostScript code for the T1font to the file file"""
1073 self
.outputPFA(file)
1075 def outputPDF(self
, file, writer
):
1076 data2eexec
= self
.getdata2eexec()
1078 # we might be allowed to skip the third part ...
1079 if (data3
.replace("\n", "")
1082 .replace(" ", "")) == "0"*512 + "cleartomark":
1085 data
= self
.data1
+ data2eexec
+ data3
1086 if writer
.compress
and haszlib
:
1087 data
= zlib
.compress(data
)
1093 "/Length3 %d\n" % (len(data
), len(self
.data1
), len(data2eexec
), len(data3
)))
1094 if writer
.compress
and haszlib
:
1095 file.write("/Filter /FlateDecode\n")
1103 class T1pfafont(T1font
):
1105 """create a T1font instance from a pfa font file"""
1107 def __init__(self
, filename
):
1108 d
= open(filename
, "rb").read()
1109 # hey, that's quick'n'dirty
1110 m1
= d
.index("eexec") + 6
1111 m2
= d
.index("0"*40)
1113 data2
= binascii
.a2b_hex(d
[m1
: m2
].replace(" ", "").replace("\r", "").replace("\n", ""))
1115 T1font
.__init
__(self
, data1
, data2
, data3
)
1118 class T1pfbfont(T1font
):
1120 """create a T1font instance from a pfb font file"""
1122 def __init__(self
, filename
):
1125 raise ValueError("invalid string length")
1129 ord(s
[3])*256*256*256)
1130 f
= open(filename
, "rb")
1131 mark
= f
.read(2); assert mark
== "\200\1"
1132 data1
= f
.read(pfblength(f
.read(4)))
1133 mark
= f
.read(2); assert mark
== "\200\2"
1135 while mark
== "\200\2":
1136 data2
= data2
+ f
.read(pfblength(f
.read(4)))
1138 assert mark
== "\200\1"
1139 data3
= f
.read(pfblength(f
.read(4)))
1140 mark
= f
.read(2); assert mark
== "\200\3"
1141 assert not f
.read(1)
1142 T1font
.__init
__(self
, data1
, data2
, data3
)