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
25 import array
, binascii
, re
33 from pyx
.path
import path
, moveto_pt
, lineto_pt
, curveto_pt
, closepath
44 def __init__(self
, t1font
):
45 """context for T1cmd evaluation"""
57 ######################################################################
59 # Note, that all commands except for the T1value are variable-free and
60 # are thus implemented as instances.
65 """returns a string representation of the T1 command"""
66 raise NotImplementedError
68 def updatepath(self
, path
, trafo
, context
):
69 """update path instance applying trafo to the points"""
70 raise NotImplementedError
72 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
73 """gather dependancy information
75 subrs is the "called-subrs" dictionary. gathercalls will insert the
76 subrnumber as key having the value 1, i.e. subrs.keys() will become the
77 numbers of used subrs. Similar seacglyphs will contain all glyphs in
78 composite characters (subrs and othersubrs for those glyphs will also
79 already be included) and othersubrs the othersubrs called.
81 This method might will not properly update all information in the
82 context (especially consuming values from the stack) and will also skip
83 various tests for performance reasons. For most T1 commands it just
84 doesn't need to do anything.
89 class T1value(_T1cmd
):
91 def __init__(self
, value
):
95 return str(self
.value
)
97 def updatepath(self
, path
, trafo
, context
):
98 context
.t1stack
.append(self
.value
)
100 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
101 context
.t1stack
.append(self
.value
)
103 def __eq__(self
, other
):
104 # while we can compare the other commands, since they are instances,
105 # for T1value we need to compare its values
106 if isinstance(other
, T1value
):
107 return self
.value
== other
.value
112 # commands for starting and finishing
114 class _T1endchar(_T1cmd
):
119 def updatepath(self
, path
, trafo
, context
):
122 T1endchar
= _T1endchar()
125 class _T1hsbw(_T1cmd
):
130 def updatepath(self
, path
, trafo
, context
):
131 sbx
= context
.t1stack
.pop(0)
132 wx
= context
.t1stack
.pop(0)
133 path
.append(moveto_pt(*trafo
.apply_pt(sbx
, 0)))
142 class _T1seac(_T1cmd
):
147 def updatepath(self
, path
, atrafo
, context
):
148 sab
= context
.t1stack
.pop(0)
149 adx
= context
.t1stack
.pop(0)
150 ady
= context
.t1stack
.pop(0)
151 bchar
= context
.t1stack
.pop(0)
152 achar
= context
.t1stack
.pop(0)
153 for cmd
in context
.t1font
.getglyphcmds(encoding
.adobestandardencoding
.decode(bchar
)):
154 cmd
.updatepath(path
, atrafo
, context
)
155 atrafo
= atrafo
* trafo
.translate_pt(adx
-sab
, ady
)
156 for cmd
in context
.t1font
.getglyphcmds(encoding
.adobestandardencoding
.decode(achar
)):
157 cmd
.updatepath(path
, atrafo
, context
)
159 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
160 bchar
= context
.t1stack
.pop()
161 achar
= context
.t1stack
.pop()
162 aglyph
= encoding
.adobestandardencoding
.decode(achar
)
163 bglyph
= encoding
.adobestandardencoding
.decode(bchar
)
164 seacglyphs
[aglyph
] = 1
165 seacglyphs
[bglyph
] = 1
166 for cmd
in context
.t1font
.getglyphcmds(bglyph
):
167 cmd
.gathercalls(seacglyphs
, subrs
, othersubrs
, context
)
168 for cmd
in context
.t1font
.getglyphcmds(aglyph
):
169 cmd
.gathercalls(seacglyphs
, subrs
, othersubrs
, context
)
174 class _T1sbw(_T1cmd
):
179 def updatepath(self
, path
, trafo
, context
):
180 sbx
= context
.t1stack
.pop(0)
181 sby
= context
.t1stack
.pop(0)
182 wx
= context
.t1stack
.pop(0)
183 wy
= context
.t1stack
.pop(0)
184 path
.append(moveto_pt(*trafo
.apply_pt(sbx
, sby
)))
193 # path construction commands
195 class _T1closepath(_T1cmd
):
200 def updatepath(self
, path
, trafo
, context
):
201 path
.append(closepath())
202 # The closepath in T1 is different from PostScripts in that it does
203 # *not* modify the current position; hence we need to add an additional
205 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
, context
.y
)))
207 T1closepath
= _T1closepath()
210 class _T1hlineto(_T1cmd
):
215 def updatepath(self
, path
, trafo
, context
):
216 dx
= context
.t1stack
.pop(0)
217 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
)))
220 T1hlineto
= _T1hlineto()
223 class _T1hmoveto(_T1cmd
):
228 def updatepath(self
, path
, trafo
, context
):
229 dx
= context
.t1stack
.pop(0)
230 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
)))
233 T1hmoveto
= _T1hmoveto()
236 class _T1hvcurveto(_T1cmd
):
241 def updatepath(self
, path
, trafo
, context
):
242 dx1
= context
.t1stack
.pop(0)
243 dx2
= context
.t1stack
.pop(0)
244 dy2
= context
.t1stack
.pop(0)
245 dy3
= context
.t1stack
.pop(0)
246 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
+ dx1
, context
.y
) +
247 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy2
) +
248 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy2
+ dy3
))))
252 T1hvcurveto
= _T1hvcurveto()
255 class _T1rlineto(_T1cmd
):
260 def updatepath(self
, path
, trafo
, context
):
261 dx
= context
.t1stack
.pop(0)
262 dy
= context
.t1stack
.pop(0)
263 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
+ dy
)))
267 T1rlineto
= _T1rlineto()
270 class _T1rmoveto(_T1cmd
):
275 def updatepath(self
, path
, trafo
, context
):
276 dx
= context
.t1stack
.pop(0)
277 dy
= context
.t1stack
.pop(0)
278 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
+ dx
, context
.y
+ dy
)))
282 T1rmoveto
= _T1rmoveto()
285 class _T1rrcurveto(_T1cmd
):
290 def updatepath(self
, path
, trafo
, context
):
291 dx1
= context
.t1stack
.pop(0)
292 dy1
= context
.t1stack
.pop(0)
293 dx2
= context
.t1stack
.pop(0)
294 dy2
= context
.t1stack
.pop(0)
295 dx3
= context
.t1stack
.pop(0)
296 dy3
= context
.t1stack
.pop(0)
297 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
+ dx1
, context
.y
+ dy1
) +
298 trafo
.apply_pt(context
.x
+ dx1
+ dx2
, context
.y
+ dy1
+ dy2
) +
299 trafo
.apply_pt(context
.x
+ dx1
+ dx2
+ dx3
, context
.y
+ dy1
+ dy2
+ dy3
))))
300 context
.x
+= dx1
+dx2
+dx3
301 context
.y
+= dy1
+dy2
+dy3
303 T1rrcurveto
= _T1rrcurveto()
306 class _T1vlineto(_T1cmd
):
311 def updatepath(self
, path
, trafo
, context
):
312 dy
= context
.t1stack
.pop(0)
313 path
.append(lineto_pt(*trafo
.apply_pt(context
.x
, context
.y
+ dy
)))
316 T1vlineto
= _T1vlineto()
319 class _T1vmoveto(_T1cmd
):
324 def updatepath(self
, path
, trafo
, context
):
325 dy
= context
.t1stack
.pop(0)
326 path
.append(moveto_pt(*trafo
.apply_pt(context
.x
, context
.y
+ dy
)))
329 T1vmoveto
= _T1vmoveto()
332 class _T1vhcurveto(_T1cmd
):
337 def updatepath(self
, path
, trafo
, context
):
338 dy1
= context
.t1stack
.pop(0)
339 dx2
= context
.t1stack
.pop(0)
340 dy2
= context
.t1stack
.pop(0)
341 dx3
= context
.t1stack
.pop(0)
342 path
.append(curveto_pt(*(trafo
.apply_pt(context
.x
, context
.y
+ dy1
) +
343 trafo
.apply_pt(context
.x
+ dx2
, context
.y
+ dy1
+ dy2
) +
344 trafo
.apply_pt(context
.x
+ dx2
+ dx3
, context
.y
+ dy1
+ dy2
))))
348 T1vhcurveto
= _T1vhcurveto()
353 class _T1dotsection(_T1cmd
):
358 def updatepath(self
, path
, trafo
, context
):
361 T1dotsection
= _T1dotsection()
364 class _T1hstem(_T1cmd
):
369 def updatepath(self
, path
, trafo
, context
):
370 y
= context
.t1stack
.pop(0)
371 dy
= context
.t1stack
.pop(0)
376 class _T1hstem3(_T1cmd
):
381 def updatepath(self
, path
, trafo
, context
):
382 y0
= context
.t1stack
.pop(0)
383 dy0
= context
.t1stack
.pop(0)
384 y1
= context
.t1stack
.pop(0)
385 dy1
= context
.t1stack
.pop(0)
386 y2
= context
.t1stack
.pop(0)
387 dy2
= context
.t1stack
.pop(0)
389 T1hstem3
= _T1hstem3()
392 class _T1vstem(_T1cmd
):
397 def updatepath(self
, path
, trafo
, context
):
398 x
= context
.t1stack
.pop(0)
399 dx
= context
.t1stack
.pop(0)
404 class _T1vstem3(_T1cmd
):
409 def updatepath(self
, path
, trafo
, context
):
410 self
.x0
= context
.t1stack
.pop(0)
411 self
.dx0
= context
.t1stack
.pop(0)
412 self
.x1
= context
.t1stack
.pop(0)
413 self
.dx1
= context
.t1stack
.pop(0)
414 self
.x2
= context
.t1stack
.pop(0)
415 self
.dx2
= context
.t1stack
.pop(0)
417 T1vstem3
= _T1vstem3()
422 class _T1div(_T1cmd
):
427 def updatepath(self
, path
, trafo
, context
):
428 num2
= context
.t1stack
.pop()
429 num1
= context
.t1stack
.pop()
430 context
.t1stack
.append(divmod(num1
, num2
)[0])
432 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
433 num2
= context
.t1stack
.pop()
434 num1
= context
.t1stack
.pop()
435 context
.t1stack
.append(divmod(num1
, num2
)[0])
440 # subroutine commands
442 class _T1callothersubr(_T1cmd
):
445 return "callothersubr"
447 def updatepath(self
, path
, trafo
, context
):
448 othersubrnumber
= context
.t1stack
.pop()
449 n
= context
.t1stack
.pop()
451 context
.psstack
.append(context
.t1stack
.pop())
453 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
454 othersubrnumber
= context
.t1stack
.pop()
455 othersubrs
[othersubrnumber
] = 1
456 n
= context
.t1stack
.pop()
458 context
.psstack
.append(context
.t1stack
.pop())
460 T1callothersubr
= _T1callothersubr()
463 class _T1callsubr(_T1cmd
):
468 def updatepath(self
, path
, trafo
, context
):
469 subrnumber
= context
.t1stack
.pop()
470 for cmd
in context
.t1font
.getsubrcmds(subrnumber
):
471 cmd
.updatepath(path
, trafo
, context
)
473 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
474 subrnumber
= context
.t1stack
.pop()
475 subrs
[subrnumber
] = 1
476 for cmd
in context
.t1font
.getsubrcmds(subrnumber
):
477 cmd
.gathercalls(seacglyphs
, subrs
, othersubrs
, context
)
479 T1callsubr
= _T1callsubr()
482 class _T1pop(_T1cmd
):
487 def updatepath(self
, path
, trafo
, context
):
488 context
.t1stack
.append(context
.psstack
.pop())
490 def gathercalls(self
, seacglyphs
, subrs
, othersubrs
, context
):
491 context
.t1stack
.append(context
.psstack
.pop())
496 class _T1return(_T1cmd
):
501 def updatepath(self
, path
, trafo
, context
):
504 T1return
= _T1return()
507 class _T1setcurrentpoint(_T1cmd
):
510 return "setcurrentpoint" % self
.x
, self
.y
512 def updatepath(self
, path
, trafo
, context
):
513 x
= context
.t1stack
.pop(0)
514 y
= context
.t1stack
.pop(0)
515 path
.append(moveto_pt(*trafo
.apply_pt(x
, y
)))
519 T1setcurrentpoint
= _T1setcurrentpoint()
522 ######################################################################
525 """cursor to read a string token by token"""
527 def __init__(self
, data
, startstring
, eattokensep
=1, tokenseps
=" \t\r\n", tokenstarts
="()<>[]{}/%"):
528 """creates a cursor for the string data
530 startstring is a string at which the cursor should start at. The first
531 ocurance of startstring is used. When startstring is not in data, an
532 exception is raised, otherwise the cursor is set to the position right
533 after the startstring. When eattokenseps is set, startstring must be
534 followed by a tokensep and this first tokensep is also consumed.
535 tokenseps is a string containing characters to be used as token
536 separators. tokenstarts is a string containing characters which
537 directly (even without intermediate token separator) start a new token.
540 self
.pos
= self
.data
.index(startstring
) + len(startstring
)
541 self
.tokenseps
= tokenseps
542 self
.tokenstarts
= tokenstarts
544 if self
.data
[self
.pos
] not in self
.tokenstarts
:
545 if self
.data
[self
.pos
] not in self
.tokenseps
:
546 raise ValueError("cursor initialization string is not followed by a token separator")
550 """get the next token
552 Leading token separators and comments are silently consumed. The first token
553 separator after the token is also silently consumed."""
554 while self
.data
[self
.pos
] in self
.tokenseps
:
556 # ignore comments including subsequent whitespace characters
557 while self
.data
[self
.pos
] == "%":
558 while self
.data
[self
.pos
] not in "\r\n":
560 while self
.data
[self
.pos
] in self
.tokenseps
:
563 while self
.data
[self
.pos
] not in self
.tokenseps
:
564 # any character in self.tokenstarts ends the token
565 if self
.pos
>startpos
and self
.data
[self
.pos
] in self
.tokenstarts
:
568 result
= self
.data
[startpos
:self
.pos
]
569 if self
.data
[self
.pos
] in self
.tokenseps
:
570 self
.pos
+= 1 # consume a single tokensep
574 """get the next token as an integer"""
575 return int(self
.gettoken())
577 def getbytes(self
, count
):
578 """get the next count bytes"""
581 return self
.data
[startpos
: self
.pos
]
589 def __init__(self
, data1
, data2eexec
, data3
):
590 """initializes a t1font instance
592 data1 and data3 are the two clear text data parts and data2 is
593 the binary data part"""
595 self
.data2eexec
= data2eexec
598 # marker and value for decoded data
601 # marker and value for standard encoding check
604 def _eexecdecode(self
, code
):
605 """eexec decoding of code"""
606 return decoder(code
, self
.eexecr
, 4)
608 def _charstringdecode(self
, code
):
609 """charstring decoding of code"""
610 return decoder(code
, self
.charstringr
, self
.lenIV
)
612 def _eexecencode(self
, data
):
613 """eexec encoding of data"""
614 return encoder(data
, self
.eexecr
, "PyX!")
616 def _charstringencode(self
, data
):
617 """eexec encoding of data"""
618 return encoder(data
, self
.charstringr
, "PyX!"[:self
.lenIV
])
620 lenIVpattern
= re
.compile("/lenIV\s+(\d+)\s+def\s+")
621 flexhintsubrs
= [[T1value(3), T1value(0), T1callothersubr
, T1pop
, T1pop
, T1setcurrentpoint
, T1return
],
622 [T1value(0), T1value(1), T1callothersubr
, T1return
],
623 [T1value(0), T1value(2), T1callothersubr
, T1return
],
627 c
= cursor(self
.data1
, "/Encoding")
628 token1
= c
.gettoken()
629 token2
= c
.gettoken()
630 if token1
== "StandardEncoding" and token2
== "def":
631 self
.encoding
= encoding
.adobestandardencoding
633 encvector
= [None]*256
635 self
.encodingstart
= c
.pos
636 if c
.gettoken() == "dup":
642 encvector
[i
] = glyph
[1:]
643 token
= c
.gettoken(); assert token
== "put"
644 self
.encodingend
= c
.pos
646 if token
== "readonly" or token
== "def":
648 assert token
== "dup"
649 self
.encoding
= encoding
.encoding(encvector
)
651 def _data2decode(self
):
652 """decodes data2eexec to the data2 string and the subr and glyphs dictionary
654 It doesn't make sense to call this method twice -- check the content of
655 data2 before calling. The method also keeps the subrs and charstrings
656 start and end positions for later replacement by stripped data.
659 self
.data2
= self
._eexecdecode
(self
.data2eexec
)
661 m
= self
.lenIVpattern
.search(self
.data2
)
663 self
.lenIV
= int(m
.group(1))
666 self
.emptysubr
= self
._charstringencode
(chr(11))
669 c
= cursor(self
.data2
, "/Subrs")
670 self
.subrsstart
= c
.pos
671 arraycount
= c
.getint()
672 token
= c
.gettoken(); assert token
== "array"
674 for i
in range(arraycount
):
675 token
= c
.gettoken(); assert token
== "dup"
676 token
= c
.getint(); assert token
== i
679 self
.subrrdtoken
= c
.gettoken()
681 token
= c
.gettoken(); assert token
== self
.subrrdtoken
682 self
.subrs
.append(c
.getbytes(size
))
684 if token
== "noaccess":
685 token
= "%s %s" % (token
, c
.gettoken())
687 self
.subrnptoken
= token
689 assert token
== self
.subrnptoken
690 self
.subrsend
= c
.pos
692 # hasflexhintsubrs is a boolean indicating that the font uses flex or
693 # hint replacement subrs as specified by Adobe (tm). When it does, the
694 # first 4 subrs should all be copied except when none of them are used
695 # in the stripped version of the font since we than get a font not
696 # using flex or hint replacement subrs at all.
697 self
.hasflexhintsubrs
= (arraycount
>= len(self
.flexhintsubrs
) and
699 for i
in range(len(self
.flexhintsubrs
))] == self
.flexhintsubrs
)
703 self
.glyphlist
= [] # we want to keep the order of the glyph names
704 c
= cursor(self
.data2
, "/CharStrings")
705 self
.charstingsstart
= c
.pos
707 token
= c
.gettoken(); assert token
== "dict"
708 token
= c
.gettoken(); assert token
== "dup"
709 token
= c
.gettoken(); assert token
== "begin"
712 chartoken
= c
.gettoken()
713 if chartoken
== "end":
715 assert chartoken
[0] == "/"
718 self
.glyphrdtoken
= c
.gettoken()
720 token
= c
.gettoken(); assert token
== self
.glyphrdtoken
721 self
.glyphlist
.append(chartoken
[1:])
722 self
.glyphs
[chartoken
[1:]] = c
.getbytes(size
)
724 self
.glyphndtoken
= c
.gettoken()
726 token
= c
.gettoken(); assert token
== self
.glyphndtoken
728 self
.charstingsend
= c
.pos
729 assert not self
.subrs
or self
.subrrdtoken
== self
.glyphrdtoken
731 def _cmds(self
, code
):
732 """return a list of T1cmd's for encoded charstring data in code"""
733 code
= array
.array("B", self
._charstringdecode
(code
))
737 if 0 <= x
< 32: # those are cmd's
739 cmds
.append({1: T1hstem
,
756 if x
== 12: # this starts an escaped cmd
759 cmds
.append({0: T1dotsection
,
767 33: T1setcurrentpoint
}[x
])
769 raise ValueError("invalid escaped command %d" % x
)
771 raise ValueError("invalid command %d" % x
)
772 elif 32 <= x
<= 246: # short ints
773 cmds
.append(T1value(x
-139))
774 elif 247 <= x
<= 250: # mid size ints
775 cmds
.append(T1value(((x
- 247)*256) + code
.pop(0) + 108))
776 elif 251 <= x
<= 254: # mid size ints
777 cmds
.append(T1value(-((x
- 251)*256) - code
.pop(0) - 108))
778 else: # x = 255, i.e. full size ints
779 y
= ((code
.pop(0)*256+code
.pop(0))*256+code
.pop(0))*256+code
.pop(0)
781 cmds
.append(T1value(y
- (1l << 32)))
783 cmds
.append(T1value(y
))
786 def getsubrcmds(self
, n
):
787 """return a list of T1cmd's for subr n"""
790 return self
._cmds
(self
.subrs
[n
])
792 def getglyphcmds(self
, glyph
):
793 """return a list of T1cmd's for glyph glyph"""
796 return self
._cmds
(self
.glyphs
[glyph
])
798 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")
800 def getglyphpath(self
, glyph
, size
):
801 """return a PyX path for glyph named glyph"""
802 m
= self
.fontmatrixpattern
.search(self
.data1
)
803 m11
, m12
, m21
, m22
, v1
, v2
= map(float, m
.groups()[:6])
804 t
= trafo
.trafo_pt(matrix
=((m11
, m12
), (m21
, m22
)), vector
=(v1
, v2
)).scaled(size
)
805 context
= T1context(self
)
807 for cmd
in self
.getglyphcmds(glyph
):
808 cmd
.updatepath(p
, t
, context
)
809 p
.wx_pt
, p
.wy_pt
= t
.apply_pt(context
.wx
, context
.wy
)
812 newlinepattern
= re
.compile("\s*[\r\n]\s*")
813 uniqueidpattern
= re
.compile("/UniqueID\s+\d+\s+def\s+")
815 def getstrippedfont(self
, glyphs
):
816 """create a T1font instance containing only certain glyphs
818 glyphs is a list of glyph names to be contained.
821 # collect information about used glyphs and subrs
826 context
= T1context(self
)
827 for cmd
in self
.getglyphcmds(glyph
):
828 cmd
.gathercalls(seacglyphs
, subrs
, othersubrs
, context
)
829 for glyph
in seacglyphs
.keys():
830 if glyph
not in glyphs
:
832 if ".notdef" not in glyphs
:
833 glyphs
.append(".notdef")
835 # strip subrs to those actually used
839 if self
.hasflexhintsubrs
and subrs
[0] < len(self
.flexhintsubrs
):
840 # According to the spec we need to keep all the flex and hint subrs
841 # as long as any of it is used.
842 while subrs
and subrs
[0] < len(self
.flexhintsubrs
):
844 subrs
= list(range(len(self
.flexhintsubrs
))) + subrs
848 strippedsubrs
= ["%d array\n" % count
]
849 for subr
in range(count
):
851 code
= self
.subrs
[subr
]
853 code
= self
.emptysubr
854 strippedsubrs
.append("dup %d %d %s %s %s\n" % (subr
, len(code
), self
.subrrdtoken
, code
, self
.subrnptoken
))
855 strippedsubrs
= "".join(strippedsubrs
)
857 # strip charstrings (i.e. glyphs) to those actually used
858 strippedcharstrings
= ["%d dict dup begin\n" % len(glyphs
)]
859 for glyph
in self
.glyphlist
:
861 strippedcharstrings
.append("/%s %d %s %s %s\n" % (glyph
, len(self
.glyphs
[glyph
]), self
.glyphrdtoken
, self
.glyphs
[glyph
], self
.glyphndtoken
))
862 strippedcharstrings
.append("end\n")
863 strippedcharstrings
= "".join(strippedcharstrings
)
865 # TODO: we could also strip othersubrs to those actually used
868 if not self
.encoding
:
870 if self
.encoding
is encoding
.adobestandardencoding
:
874 for char
, glyph
in enumerate(self
.encoding
.encvector
):
876 encodingstrings
.append("dup %i /%s put\n" % (char
, glyph
))
877 data1
= self
.data1
[:self
.encodingstart
] + "".join(encodingstrings
) + self
.data1
[self
.encodingend
:]
878 data1
= self
.newlinepattern
.subn("\n", data1
)[0]
879 data1
= self
.uniqueidpattern
.subn("", data1
)[0]
882 # TODO: in the future, for full control, we might want to write data2 as well as data1 and data3 from scratch
883 if self
.subrsstart
< self
.charstingsstart
:
884 data2
= self
.data2
[:self
.charstingsstart
] + strippedcharstrings
+ self
.data2
[self
.charstingsend
:]
885 data2
= data2
[:self
.subrsstart
] + strippedsubrs
+ data2
[self
.subrsend
:]
887 data2
= self
.data2
[:self
.subrsstart
] + strippedsubrs
+ self
.data2
[self
.subrsend
:]
888 data2
= data2
[:self
.charstingsstart
] + strippedcharstrings
+ data2
[self
.charstingsend
:]
889 data2
= self
.uniqueidpattern
.subn("", data2
)[0]
892 data3
= self
.newlinepattern
.subn("\n", self
.data3
)[0]
894 # create and return the new font instance
895 return T1font(data1
, self
._eexecencode
(data2
), data3
.rstrip())
897 def outputPS(self
, file, writer
):
898 """output the PostScript code for the T1font to the file file"""
899 file.write(self
.data1
)
900 data2eexechex
= binascii
.b2a_hex(self
.data2eexec
)
902 for i
in range((len(data2eexechex
)-1)/linelength
+ 1):
903 file.write(data2eexechex
[i
*linelength
: i
*linelength
+linelength
])
905 file.write(self
.data3
)
908 # As a simple heuristics we assume non-symbolic fonts if and only
909 # if the Adobe standard encoding is used. All other font flags are
910 # not specified here.
911 if not self
.encoding
:
913 if self
.encoding
is encoding
.adobestandardencoding
:
917 def outputPDF(self
, file, writer
):
919 # we might be allowed to skip the third part ...
920 if (data3
.replace("\n", "")
923 .replace(" ", "")) == "0"*512 + "cleartomark":
926 data
= self
.data1
+ self
.data2eexec
+ data3
927 if writer
.compress
and haszlib
:
928 data
= zlib
.compress(data
)
934 "/Length3 %d\n" % (len(data
), len(self
.data1
), len(self
.data2eexec
), len(data3
)))
935 if writer
.compress
and haszlib
:
936 file.write("/Filter /FlateDecode\n")
944 class T1pfafont(T1font
):
946 """create a T1font instance from a pfa font file"""
948 def __init__(self
, filename
):
949 d
= open(filename
, "rb").read()
950 # hey, that's quick'n'dirty
951 m1
= d
.index("eexec") + 6
954 data2
= binascii
.a2b_hex(d
[m1
: m2
].replace(" ", "").replace("\r", "").replace("\n", ""))
956 T1font
.__init
__(self
, data1
, data2
, data3
)
959 class T1pfbfont(T1font
):
961 """create a T1font instance from a pfb font file"""
963 def __init__(self
, filename
):
966 raise ValueError("invalid string length")
970 ord(s
[3])*256*256*256)
971 f
= open(filename
, "rb")
972 mark
= f
.read(2); assert mark
== "\200\1"
973 data1
= f
.read(pfblength(f
.read(4)))
974 mark
= f
.read(2); assert mark
== "\200\2"
976 while mark
== "\200\2":
977 data2
= data2
+ f
.read(pfblength(f
.read(4)))
979 assert mark
== "\200\1"
980 data3
= f
.read(pfblength(f
.read(4)))
981 mark
= f
.read(2); assert mark
== "\200\3"
983 T1font
.__init
__(self
, data1
, data2
, data3
)