1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2005-2006 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
36 from sets
import Set
as set
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 aglyph
= encoding
.adobestandardencoding
.decode(achar
)
159 bglyph
= encoding
.adobestandardencoding
.decode(bchar
)
160 context
.t1font
.updateglyphpath(bglyph
, path
, atrafo
, context
)
161 atrafo
= atrafo
* trafo
.translate_pt(adx
-sab
, ady
)
162 context
.t1font
.updateglyphpath(aglyph
, path
, atrafo
, context
)
164 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
165 bchar
= context
.t1stack
.pop()
166 achar
= context
.t1stack
.pop()
167 aglyph
= encoding
.adobestandardencoding
.decode(achar
)
168 bglyph
= encoding
.adobestandardencoding
.decode(bchar
)
169 seacglyphs
[aglyph
] = 1
170 seacglyphs
[bglyph
] = 1
171 context
.t1font
.gatherglyphcalls(bglyph
, seacglyphs
, subrs
, othersubrs
, context
)
172 context
.t1font
.gatherglyphcalls(aglyph
, seacglyphs
, subrs
, othersubrs
, context
)
180 T1cmd
.__init
__(self
, 7, subcmd
=1)
185 def updatepath(self
, path
, trafo
, context
):
186 sbx
= context
.t1stack
.pop(0)
187 sby
= context
.t1stack
.pop(0)
188 wx
= context
.t1stack
.pop(0)
189 wy
= context
.t1stack
.pop(0)
190 path
.append(moveto_pt(*trafo
.apply_pt(sbx
, sby
)))
199 # path construction commands
201 class _T1closepath(T1cmd
):
204 T1cmd
.__init
__(self
, 9)
209 def updatepath(self
, path
, trafo
, context
):
210 path
.append(closepath())
211 # The closepath in T1 is different from PostScripts in that it does
212 # *not* modify the current position; hence we need to add an additional
214 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
, context
.y
)))
216 T1closepath
= _T1closepath()
219 class _T1hlineto(T1cmd
):
222 T1cmd
.__init
__(self
, 6)
227 def updatepath(self
, path
, trafo
, context
):
228 dx
= context
.t1stack
.pop(0)
229 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
)))
232 T1hlineto
= _T1hlineto()
235 class _T1hmoveto(T1cmd
):
238 T1cmd
.__init
__(self
, 22)
243 def updatepath(self
, path
, trafo
, context
):
244 dx
= context
.t1stack
.pop(0)
245 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
)))
248 T1hmoveto
= _T1hmoveto()
251 class _T1hvcurveto(T1cmd
):
254 T1cmd
.__init
__(self
, 31)
259 def updatepath(self
, path
, trafo
, context
):
260 dx1
= context
.t1stack
.pop(0)
261 dx2
= context
.t1stack
.pop(0)
262 dy2
= context
.t1stack
.pop(0)
263 dy3
= context
.t1stack
.pop(0)
264 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
+ dx1
, context
.y
) +
265 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy2
) +
266 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy2
+ dy3
))))
270 T1hvcurveto
= _T1hvcurveto()
273 class _T1rlineto(T1cmd
):
276 T1cmd
.__init
__(self
, 5)
281 def updatepath(self
, path
, trafo
, context
):
282 dx
= context
.t1stack
.pop(0)
283 dy
= context
.t1stack
.pop(0)
284 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
+ dy
)))
288 T1rlineto
= _T1rlineto()
291 class _T1rmoveto(T1cmd
):
294 T1cmd
.__init
__(self
, 21)
299 def updatepath(self
, path
, trafo
, context
):
300 dx
= context
.t1stack
.pop(0)
301 dy
= context
.t1stack
.pop(0)
302 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
+ dy
)))
306 T1rmoveto
= _T1rmoveto()
309 class _T1rrcurveto(T1cmd
):
312 T1cmd
.__init
__(self
, 8)
317 def updatepath(self
, path
, trafo
, context
):
318 dx1
= context
.t1stack
.pop(0)
319 dy1
= context
.t1stack
.pop(0)
320 dx2
= context
.t1stack
.pop(0)
321 dy2
= context
.t1stack
.pop(0)
322 dx3
= context
.t1stack
.pop(0)
323 dy3
= context
.t1stack
.pop(0)
324 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
+ dx1
, context
.y
+ dy1
) +
325 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy1
+ dy2
) +
326 trafo
.apply_pt(context
.x
+ dx1
+ dx2
+ dx3
, context
.y
+ dy1
+ dy2
+ dy3
))))
327 context
.x
+= dx1
+dx2
+dx3
328 context
.y
+= dy1
+dy2
+dy3
330 T1rrcurveto
= _T1rrcurveto()
333 class _T1vlineto(T1cmd
):
336 T1cmd
.__init
__(self
, 7)
341 def updatepath(self
, path
, trafo
, context
):
342 dy
= context
.t1stack
.pop(0)
343 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
, context
.y
+ dy
)))
346 T1vlineto
= _T1vlineto()
349 class _T1vmoveto(T1cmd
):
352 T1cmd
.__init
__(self
, 4)
357 def updatepath(self
, path
, trafo
, context
):
358 dy
= context
.t1stack
.pop(0)
359 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
, context
.y
+ dy
)))
362 T1vmoveto
= _T1vmoveto()
365 class _T1vhcurveto(T1cmd
):
368 T1cmd
.__init
__(self
, 30)
373 def updatepath(self
, path
, trafo
, context
):
374 dy1
= context
.t1stack
.pop(0)
375 dx2
= context
.t1stack
.pop(0)
376 dy2
= context
.t1stack
.pop(0)
377 dx3
= context
.t1stack
.pop(0)
378 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
, context
.y
+ dy1
) +
379 trafo
.apply_pt(context
.x
+ dx2
, context
.y
+ dy1
+ dy2
) +
380 trafo
.apply_pt(context
.x
+ dx2
+ dx3
, context
.y
+ dy1
+ dy2
))))
384 T1vhcurveto
= _T1vhcurveto()
389 class _T1dotsection(T1cmd
):
392 T1cmd
.__init
__(self
, 0, subcmd
=1)
397 def updatepath(self
, path
, trafo
, context
):
400 T1dotsection
= _T1dotsection()
403 class _T1hstem(T1cmd
):
406 T1cmd
.__init
__(self
, 1)
411 def updatepath(self
, path
, trafo
, context
):
412 y
= context
.t1stack
.pop(0)
413 dy
= context
.t1stack
.pop(0)
418 class _T1hstem3(T1cmd
):
421 T1cmd
.__init
__(self
, 2, subcmd
=1)
426 def updatepath(self
, path
, trafo
, context
):
427 y0
= context
.t1stack
.pop(0)
428 dy0
= context
.t1stack
.pop(0)
429 y1
= context
.t1stack
.pop(0)
430 dy1
= context
.t1stack
.pop(0)
431 y2
= context
.t1stack
.pop(0)
432 dy2
= context
.t1stack
.pop(0)
434 T1hstem3
= _T1hstem3()
437 class _T1vstem(T1cmd
):
440 T1cmd
.__init
__(self
, 3)
445 def updatepath(self
, path
, trafo
, context
):
446 x
= context
.t1stack
.pop(0)
447 dx
= context
.t1stack
.pop(0)
452 class _T1vstem3(T1cmd
):
455 T1cmd
.__init
__(self
, 1, subcmd
=1)
460 def updatepath(self
, path
, trafo
, context
):
461 self
.x0
= context
.t1stack
.pop(0)
462 self
.dx0
= context
.t1stack
.pop(0)
463 self
.x1
= context
.t1stack
.pop(0)
464 self
.dx1
= context
.t1stack
.pop(0)
465 self
.x2
= context
.t1stack
.pop(0)
466 self
.dx2
= context
.t1stack
.pop(0)
468 T1vstem3
= _T1vstem3()
476 T1cmd
.__init
__(self
, 12, subcmd
=1)
481 def updatepath(self
, path
, trafo
, context
):
482 num2
= context
.t1stack
.pop()
483 num1
= context
.t1stack
.pop()
484 context
.t1stack
.append(divmod(num1
, num2
)[0])
486 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
487 num2
= context
.t1stack
.pop()
488 num1
= context
.t1stack
.pop()
489 context
.t1stack
.append(divmod(num1
, num2
)[0])
494 # subroutine commands
496 class _T1callothersubr(T1cmd
):
499 T1cmd
.__init
__(self
, 16, subcmd
=1)
502 return "callothersubr"
504 def updatepath(self
, path
, trafo
, context
):
505 othersubrnumber
= context
.t1stack
.pop()
506 n
= context
.t1stack
.pop()
508 context
.psstack
.append(context
.t1stack
.pop())
510 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
511 othersubrnumber
= context
.t1stack
.pop()
512 othersubrs
[othersubrnumber
] = 1
513 n
= context
.t1stack
.pop()
515 context
.psstack
.append(context
.t1stack
.pop())
517 T1callothersubr
= _T1callothersubr()
520 class _T1callsubr(T1cmd
):
523 T1cmd
.__init
__(self
, 10)
528 def updatepath(self
, path
, trafo
, context
):
529 subr
= context
.t1stack
.pop()
530 context
.t1font
.updatesubrpath(subr
, path
, trafo
, context
)
532 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
533 subr
= context
.t1stack
.pop()
535 context
.t1font
.gathersubrcalls(subr
, seacglyphs
, subrs
, othersubrs
, context
)
537 T1callsubr
= _T1callsubr()
543 T1cmd
.__init
__(self
, 17, subcmd
=1)
548 def updatepath(self
, path
, trafo
, context
):
549 context
.t1stack
.append(context
.psstack
.pop())
551 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
552 context
.t1stack
.append(context
.psstack
.pop())
557 class _T1return(T1cmd
):
560 T1cmd
.__init
__(self
, 11)
565 def updatepath(self
, path
, trafo
, context
):
568 T1return
= _T1return()
571 class _T1setcurrentpoint(T1cmd
):
574 T1cmd
.__init
__(self
, 33, subcmd
=1)
577 return "setcurrentpoint" % self
.x
, self
.y
579 def updatepath(self
, path
, trafo
, context
):
580 x
= context
.t1stack
.pop(0)
581 y
= context
.t1stack
.pop(0)
582 path
.append(moveto_pt(*trafo
.apply_pt(x
, y
)))
586 T1setcurrentpoint
= _T1setcurrentpoint()
589 ######################################################################
592 """cursor to read a string token by token"""
594 def __init__(self
, data
, startstring
, eattokensep
=1, tokenseps
=" \t\r\n", tokenstarts
="()<>[]{}/%"):
595 """creates a cursor for the string data
597 startstring is a string at which the cursor should start at. The first
598 ocurance of startstring is used. When startstring is not in data, an
599 exception is raised, otherwise the cursor is set to the position right
600 after the startstring. When eattokenseps is set, startstring must be
601 followed by a tokensep and this first tokensep is also consumed.
602 tokenseps is a string containing characters to be used as token
603 separators. tokenstarts is a string containing characters which
604 directly (even without intermediate token separator) start a new token.
607 self
.pos
= self
.data
.index(startstring
) + len(startstring
)
608 self
.tokenseps
= tokenseps
609 self
.tokenstarts
= tokenstarts
611 if self
.data
[self
.pos
] not in self
.tokenstarts
:
612 if self
.data
[self
.pos
] not in self
.tokenseps
:
613 raise ValueError("cursor initialization string is not followed by a token separator")
617 """get the next token
619 Leading token separators and comments are silently consumed. The first token
620 separator after the token is also silently consumed."""
621 while self
.data
[self
.pos
] in self
.tokenseps
:
623 # ignore comments including subsequent whitespace characters
624 while self
.data
[self
.pos
] == "%":
625 while self
.data
[self
.pos
] not in "\r\n":
627 while self
.data
[self
.pos
] in self
.tokenseps
:
630 while self
.data
[self
.pos
] not in self
.tokenseps
:
631 # any character in self.tokenstarts ends the token
632 if self
.pos
>startpos
and self
.data
[self
.pos
] in self
.tokenstarts
:
635 result
= self
.data
[startpos
:self
.pos
]
636 if self
.data
[self
.pos
] in self
.tokenseps
:
637 self
.pos
+= 1 # consume a single tokensep
641 """get the next token as an integer"""
642 return int(self
.gettoken())
644 def getbytes(self
, count
):
645 """get the next count bytes"""
648 return self
.data
[startpos
: self
.pos
]
656 fontnamepattern
= re
.compile("/FontName\s+/(.*?)\s+def\s+")
657 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")
659 def __init__(self
, data1
, data2eexec
, data3
):
660 """initializes a t1font instance
662 data1 and data3 are the two clear text data parts and data2 is
663 the binary data part"""
665 self
._data
2eexec
= data2eexec
668 # marker and value for decoded data
670 # note that data2eexec is set to none by setsubrcmds and setglyphcmds
671 # this *also* denotes, that data2 is out-of-date; hence they are both
672 # marked by an _ and getdata2 and getdata2eexec will properly resolve
673 # the current state of decoding ...
675 # marker and value for standard encoding check
678 self
.name
, = self
.fontnamepattern
.search(self
.data1
).groups()
679 m11
, m12
, m21
, m22
, v1
, v2
= map(float, self
.fontmatrixpattern
.search(self
.data1
).groups()[:6])
680 self
.fontmatrix
= trafo
.trafo_pt(matrix
=((m11
, m12
), (m21
, m22
)), vector
=(v1
, v2
))
682 def _eexecdecode(self
, code
):
683 """eexec decoding of code"""
684 return decoder(code
, self
.eexecr
, 4)
686 def _charstringdecode(self
, code
):
687 """charstring decoding of code"""
688 return decoder(code
, self
.charstringr
, self
.lenIV
)
690 def _eexecencode(self
, data
):
691 """eexec encoding of data"""
692 return encoder(data
, self
.eexecr
, "PyX!")
694 def _charstringencode(self
, data
):
695 """eexec encoding of data"""
696 return encoder(data
, self
.charstringr
, "PyX!"[:self
.lenIV
])
698 lenIVpattern
= re
.compile("/lenIV\s+(\d+)\s+def\s+")
699 flexhintsubrs
= [[3, 0, T1callothersubr
, T1pop
, T1pop
, T1setcurrentpoint
, T1return
],
700 [0, 1, T1callothersubr
, T1return
],
701 [0, 2, T1callothersubr
, T1return
],
705 """helper method to lookup the encoding in the font"""
706 c
= cursor(self
.data1
, "/Encoding")
707 token1
= c
.gettoken()
708 token2
= c
.gettoken()
709 if token1
== "StandardEncoding" and token2
== "def":
710 self
.encoding
= encoding
.adobestandardencoding
712 encvector
= [None]*256
714 self
.encodingstart
= c
.pos
715 if c
.gettoken() == "dup":
721 encvector
[i
] = glyph
[1:]
722 token
= c
.gettoken(); assert token
== "put"
723 self
.encodingend
= c
.pos
725 if token
== "readonly" or token
== "def":
727 assert token
== "dup"
728 self
.encoding
= encoding
.encoding(encvector
)
730 def _data2decode(self
):
731 """decodes data2eexec to the data2 string and the subr and glyphs dictionary
733 It doesn't make sense to call this method twice -- check the content of
734 data2 before calling. The method also keeps the subrs and charstrings
735 start and end positions for later use."""
736 self
._data
2 = self
._eexecdecode
(self
._data
2eexec
)
738 m
= self
.lenIVpattern
.search(self
._data
2)
740 self
.lenIV
= int(m
.group(1))
743 self
.emptysubr
= self
._charstringencode
(chr(11))
746 c
= cursor(self
._data
2, "/Subrs")
747 self
.subrsstart
= c
.pos
748 arraycount
= c
.getint()
749 token
= c
.gettoken(); assert token
== "array"
751 for i
in range(arraycount
):
752 token
= c
.gettoken(); assert token
== "dup"
753 token
= c
.getint(); assert token
== i
756 self
.subrrdtoken
= c
.gettoken()
758 token
= c
.gettoken(); assert token
== self
.subrrdtoken
759 self
.subrs
.append(c
.getbytes(size
))
761 if token
== "noaccess":
762 token
= "%s %s" % (token
, c
.gettoken())
764 self
.subrnptoken
= token
766 assert token
== self
.subrnptoken
767 self
.subrsend
= c
.pos
769 # hasflexhintsubrs is a boolean indicating that the font uses flex or
770 # hint replacement subrs as specified by Adobe (tm). When it does, the
771 # first 4 subrs should all be copied except when none of them are used
772 # in the stripped version of the font since we than get a font not
773 # using flex or hint replacement subrs at all.
774 self
.hasflexhintsubrs
= (arraycount
>= len(self
.flexhintsubrs
) and
776 for i
in range(len(self
.flexhintsubrs
))] == self
.flexhintsubrs
)
780 self
.glyphlist
= [] # we want to keep the order of the glyph names
781 c
= cursor(self
._data
2, "/CharStrings")
782 self
.charstringsstart
= c
.pos
784 token
= c
.gettoken(); assert token
== "dict"
785 token
= c
.gettoken(); assert token
== "dup"
786 token
= c
.gettoken(); assert token
== "begin"
789 chartoken
= c
.gettoken()
790 if chartoken
== "end":
792 assert chartoken
[0] == "/"
795 self
.glyphrdtoken
= c
.gettoken()
797 token
= c
.gettoken(); assert token
== self
.glyphrdtoken
798 self
.glyphlist
.append(chartoken
[1:])
799 self
.glyphs
[chartoken
[1:]] = c
.getbytes(size
)
801 self
.glyphndtoken
= c
.gettoken()
803 token
= c
.gettoken(); assert token
== self
.glyphndtoken
805 self
.charstringsend
= c
.pos
806 assert not self
.subrs
or self
.subrrdtoken
== self
.glyphrdtoken
808 def _cmds(self
, code
):
809 """return a list of T1cmd's for encoded charstring data in code"""
810 code
= array
.array("B", self
._charstringdecode
(code
))
814 if x
== 12: # this starts an escaped cmd
815 cmds
.append(T1subcmds
[code
.pop(0)])
816 elif 0 <= x
< 32: # those are cmd's
817 cmds
.append(T1cmds
[x
])
818 elif 32 <= x
<= 246: # short ints
820 elif 247 <= x
<= 250: # mid size ints
821 cmds
.append(((x
- 247)*256) + code
.pop(0) + 108)
822 elif 251 <= x
<= 254: # mid size ints
823 cmds
.append(-((x
- 251)*256) - code
.pop(0) - 108)
824 else: # x = 255, i.e. full size ints
825 y
= ((code
.pop(0)*256l+code
.pop(0))*256+code
.pop(0))*256+code
.pop(0)
827 cmds
.append(y
- (1l << 32))
832 def _code(self
, cmds
):
833 """return an encoded charstring data for list of T1cmd's in cmds"""
834 code
= array
.array("B")
839 code
.append(cmd
.code
)
840 except AttributeError:
841 if -107 <= cmd
<= 107:
843 elif 108 <= cmd
<= 1131:
844 a
, b
= divmod(cmd
-108, 256)
847 elif -1131 <= cmd
<= -108:
848 a
, b
= divmod(-cmd
-108, 256)
854 cmd
, x4
= divmod(cmd
, 256)
855 cmd
, x3
= divmod(cmd
, 256)
856 x1
, x2
= divmod(cmd
, 256)
862 return self
._charstringencode
(code
.tostring())
864 def getsubrcmds(self
, subr
):
865 """return a list of T1cmd's for subr subr"""
868 return self
._cmds
(self
.subrs
[subr
])
870 def getglyphcmds(self
, glyph
):
871 """return a list of T1cmd's for glyph glyph"""
874 return self
._cmds
(self
.glyphs
[glyph
])
876 def setsubrcmds(self
, subr
, cmds
):
877 """replaces the T1cmd's by the list cmds for subr subr"""
880 self
._data
2eexec
= None
881 self
.subrs
[subr
] = self
._code
(cmds
)
883 def setglyphcmds(self
, glyph
, cmds
):
884 """replaces the T1cmd's by the list cmds for glyph glyph"""
887 self
._data
2eexec
= None
888 self
.glyphs
[glyph
] = self
._code
(cmds
)
890 def updatepath(self
, cmds
, path
, trafo
, context
):
892 if isinstance(cmd
, T1cmd
):
893 cmd
.updatepath(path
, trafo
, context
)
895 context
.t1stack
.append(cmd
)
897 def updatesubrpath(self
, subr
, path
, trafo
, context
):
898 self
.updatepath(self
.getsubrcmds(subr
), path
, trafo
, context
)
900 def updateglyphpath(self
, glyph
, path
, trafo
, context
):
901 self
.updatepath(self
.getglyphcmds(glyph
), path
, trafo
, context
)
903 def gathercalls(self
, cmds
, seacglyphs
, subrs
, othersubrs
, context
):
905 if isinstance(cmd
, T1cmd
):
906 cmd
.gathercalls(seacglyphs
, subrs
, othersubrs
, context
)
908 context
.t1stack
.append(cmd
)
910 def gathersubrcalls(self
, subr
, seacglyphs
, subrs
, othersubrs
, context
):
911 self
.gathercalls(self
.getsubrcmds(subr
), seacglyphs
, subrs
, othersubrs
, context
)
913 def gatherglyphcalls(self
, glyph
, seacglyphs
, subrs
, othersubrs
, context
):
914 self
.gathercalls(self
.getglyphcmds(glyph
), seacglyphs
, subrs
, othersubrs
, context
)
916 def getglyphpathwxwy_pt(self
, glyph
, size
):
917 t
= self
.fontmatrix
.scaled(size
)
918 context
= T1context(self
)
920 self
.updateglyphpath(glyph
, p
, t
, context
)
921 wx
, wy
= t
.apply_pt(context
.wx
, context
.wy
)
924 def getglyphpath(self
, glyph
, size
):
925 """return a PyX path for glyph named glyph"""
926 return self
.getglyphpathwxwy_pt(glyph
, size
)[0]
928 def getglyphwxwy_pt(self
, glyph
, size
):
929 return self
.getglyphpathwxwy_pt(glyph
, size
)[1:]
931 def getdata2(self
, subrs
=None, glyphs
=None):
932 """makes a data2 string
934 subrs is a dict containing those subrs numbers as keys,
935 which are to be contained in the subrsstring to be created.
936 If subrs is None, all subrs in self.subrs will be used.
937 The subrs dict might be modified *in place*.
939 glyphs is a dict containing those glyph names as keys,
940 which are to be contained in the charstringsstring to be created.
941 If glyphs is None, all glyphs in self.glyphs will be used."""
942 def addsubrs(subrs
, result
):
943 if subrs
is not None:
944 # some adjustments to the subrs dict
946 subrsindices
= subrs
.keys()
947 subrsmin
= min(subrsindices
)
948 subrsmax
= max(subrsindices
)
949 if self
.hasflexhintsubrs
and subrsmin
< len(self
.flexhintsubrs
):
950 # According to the spec we need to keep all the flex and hint subrs
951 # as long as any of it is used.
952 for subr
in range(len(self
.flexhintsubrs
)):
957 # build a new subrs dict containing all subrs
958 subrs
= dict([(subr
, 1) for subr
in range(len(self
.subrs
))])
959 subrsmax
= len(self
.subrs
) - 1
961 # build the string from all selected subrs
962 result
.append("%d array\n" % (subrsmax
+ 1))
963 for subr
in range(subrsmax
+1):
964 if subrs
.has_key(subr
):
965 code
= self
.subrs
[subr
]
967 code
= self
.emptysubr
968 result
.append("dup %d %d %s %s %s\n" % (subr
, len(code
), self
.subrrdtoken
, code
, self
.subrnptoken
))
970 def addcharstrings(glyphs
, result
):
971 result
.append("%d dict dup begin\n" % (glyphs
is None and len(self
.glyphlist
) or len(glyphs
)))
972 for glyph
in self
.glyphlist
:
973 if glyphs
is None or glyph
in glyphs
:
974 result
.append("/%s %d %s %s %s\n" % (glyph
, len(self
.glyphs
[glyph
]), self
.glyphrdtoken
, self
.glyphs
[glyph
], self
.glyphndtoken
))
975 result
.append("end\n")
977 if self
.subrsstart
< self
.charstringsstart
:
978 result
= [self
._data
2[:self
.subrsstart
]]
979 addsubrs(subrs
, result
)
980 result
.append(self
._data
2[self
.subrsend
:self
.charstringsstart
])
981 addcharstrings(glyphs
, result
)
982 result
.append(self
._data
2[self
.charstringsend
:])
984 result
= [self
._data
2[:self
.charstringsstart
]]
985 addcharstrings(glyphs
, result
)
986 result
.append(self
._data
2[self
.charstringsend
:self
.subrsstart
])
987 addsubrs(subrs
, result
)
988 result
.append(self
._data
2[self
.subrsend
:])
989 return "".join(result
)
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 uniqueidpattern
= re
.compile("/UniqueID\s+\d+\s+def\s+")
1000 def getstrippedfont(self
, glyphs
):
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 # TODO: we could also strip othersubrs to those actually used
1007 # collect information about used glyphs and subrs
1011 for glyph
in glyphs
:
1012 self
.gatherglyphcalls(glyph
, seacglyphs
, subrs
, othersubrs
, T1context(self
))
1013 # while we have gathered all subrs for the seacglyphs alreadys, we
1014 # might have missed the glyphs themself (when they are not used stand-alone)
1015 glyphs
.update(seacglyphs
)
1016 glyphs
.add(".notdef")
1019 if not self
.encoding
:
1021 if self
.encoding
is encoding
.adobestandardencoding
:
1024 encodingstrings
= []
1025 for char
, glyph
in enumerate(self
.encoding
.encvector
):
1027 encodingstrings
.append("dup %i /%s put\n" % (char
, glyph
))
1028 data1
= self
.data1
[:self
.encodingstart
] + "".join(encodingstrings
) + self
.data1
[self
.encodingend
:]
1029 data1
= self
.newlinepattern
.subn("\n", data1
)[0]
1030 data1
= self
.uniqueidpattern
.subn("", data1
)[0]
1033 data2
= self
.uniqueidpattern
.subn("", 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")
1042 # As a simple heuristics we assume non-symbolic fonts if and only
1043 # if the Adobe standard encoding is used. All other font flags are
1044 # not specified here.
1045 if not self
.encoding
:
1047 if self
.encoding
is encoding
.adobestandardencoding
:
1051 def outputPFA(self
, file):
1052 """output the T1file in PFA format"""
1053 file.write(self
.data1
)
1054 data2eexechex
= binascii
.b2a_hex(self
.getdata2eexec())
1056 for i
in range((len(data2eexechex
)-1)/linelength
+ 1):
1057 file.write(data2eexechex
[i
*linelength
: i
*linelength
+linelength
])
1059 file.write(self
.data3
)
1061 def outputPFB(self
, file):
1062 """output the T1file in PFB format"""
1063 data2eexec
= self
.getdata2eexec()
1064 def pfblength(data
):
1066 l
, x1
= divmod(l
, 256)
1067 l
, x2
= divmod(l
, 256)
1068 x4
, x3
= divmod(l
, 256)
1069 return chr(x1
) + chr(x2
) + chr(x3
) + chr(x4
)
1070 file.write("\200\1")
1071 file.write(pfblength(self
.data1
))
1072 file.write(self
.data1
)
1073 file.write("\200\2")
1074 file.write(pfblength(data2eexec
))
1075 file.write(data2eexec
)
1076 file.write("\200\1")
1077 file.write(pfblength(self
.data3
))
1078 file.write(self
.data3
)
1079 file.write("\200\3")
1081 def outputPS(self
, file, writer
):
1082 """output the PostScript code for the T1file to the file file"""
1083 self
.outputPFA(file)
1085 def outputPDF(self
, file, writer
):
1086 data2eexec
= self
.getdata2eexec()
1088 # we might be allowed to skip the third part ...
1089 if (data3
.replace("\n", "")
1092 .replace(" ", "")) == "0"*512 + "cleartomark":
1095 data
= self
.data1
+ data2eexec
+ data3
1096 if writer
.compress
and haszlib
:
1097 data
= zlib
.compress(data
)
1103 "/Length3 %d\n" % (len(data
), len(self
.data1
), len(data2eexec
), len(data3
)))
1104 if writer
.compress
and haszlib
:
1105 file.write("/Filter /FlateDecode\n")
1113 class PFAfile(T1file
):
1115 """create a T1file instance from a pfa font file"""
1117 def __init__(self
, filename
):
1118 d
= open(filename
, "rb").read()
1119 # hey, that's quick'n'dirty
1120 m1
= d
.index("eexec") + 6
1121 m2
= d
.index("0"*40)
1123 data2
= binascii
.a2b_hex(d
[m1
: m2
].replace(" ", "").replace("\r", "").replace("\n", ""))
1125 T1file
.__init
__(self
, data1
, data2
, data3
)
1128 class PFBfile(T1file
):
1130 """create a T1file instance from a pfb font file"""
1132 def __init__(self
, filename
):
1135 raise ValueError("invalid string length")
1139 ord(s
[3])*256*256*256)
1140 f
= open(filename
, "rb")
1141 mark
= f
.read(2); assert mark
== "\200\1"
1142 data1
= f
.read(pfblength(f
.read(4)))
1143 mark
= f
.read(2); assert mark
== "\200\2"
1145 while mark
== "\200\2":
1146 data2
= data2
+ f
.read(pfblength(f
.read(4)))
1148 assert mark
== "\200\1"
1149 data3
= f
.read(pfblength(f
.read(4)))
1150 mark
= f
.read(2); assert mark
== "\200\3"
1151 assert not f
.read(1)
1152 T1file
.__init
__(self
, data1
, data2
, data3
)