fixes to properly support Python versions 2.1 to 2.4
[PyX/mjg.git] / pyx / font / t1font.py
blob1e062915594799adee07e84dee91101e762d4344
1 #!/usr/bin/env python
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
27 try:
28 import zlib
29 haszlib = 1
30 except ImportError:
31 haszlib = 0
33 try:
34 enumerate([])
35 except NameError:
36 # fallback implementation for Python 2.2 and below
37 def enumerate(list):
38 return zip(xrange(len(list)), list)
40 from pyx import trafo
41 from pyx.path import path, moveto_pt, lineto_pt, curveto_pt, closepath
42 import encoding
44 try:
45 from _t1code import *
46 except:
47 from t1code import *
50 class T1context:
52 def __init__(self, t1font):
53 """context for T1cmd evaluation"""
54 self.t1font = t1font
56 # state description
57 self.x = None
58 self.y = None
59 self.wx = None
60 self.wy = None
61 self.t1stack = []
62 self.psstack = []
65 ######################################################################
66 # T1 commands
67 # Note, that the T1 commands are variable-free except for plain number,
68 # which are stored as integers. All other T1 commands exist as a single
69 # instance only
71 T1cmds = {}
72 T1subcmds = {}
74 class T1cmd:
76 def __init__(self, code, subcmd=0):
77 self.code = code
78 self.subcmd = subcmd
79 if subcmd:
80 T1subcmds[code] = self
81 else:
82 T1cmds[code] = self
84 def __str__(self):
85 """returns a string representation of the T1 command"""
86 raise NotImplementedError
88 def updatepath(self, path, trafo, context):
89 """update path instance applying trafo to the points"""
90 raise NotImplementedError
92 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
93 """gather dependancy information
95 subrs is the "called-subrs" dictionary. gathercalls will insert the
96 subr number as key having the value 1, i.e. subrs.keys() will become the
97 numbers of used subrs. Similar seacglyphs will contain all glyphs in
98 composite characters (subrs and othersubrs for those glyphs will also
99 already be included) and othersubrs the othersubrs called.
101 This method might will not properly update all information in the
102 context (especially consuming values from the stack) and will also skip
103 various tests for performance reasons. For most T1 commands it just
104 doesn't need to do anything.
106 pass
109 # commands for starting and finishing
111 class _T1endchar(T1cmd):
113 def __init__(self):
114 T1cmd.__init__(self, 14)
116 def __str__(self):
117 return "endchar"
119 def updatepath(self, path, trafo, context):
120 pass
122 T1endchar = _T1endchar()
125 class _T1hsbw(T1cmd):
127 def __init__(self):
128 T1cmd.__init__(self, 13)
130 def __str__(self):
131 return "hsbw"
133 def updatepath(self, path, trafo, context):
134 sbx = context.t1stack.pop(0)
135 wx = context.t1stack.pop(0)
136 path.append(moveto_pt(*trafo.apply_pt(sbx, 0)))
137 context.x = sbx
138 context.y = 0
139 context.wx = wx
140 context.wy = 0
142 T1hsbw = _T1hsbw()
145 class _T1seac(T1cmd):
147 def __init__(self):
148 T1cmd.__init__(self, 6, subcmd=1)
150 def __str__(self):
151 return "seac"
153 def updatepath(self, path, atrafo, context):
154 sab = context.t1stack.pop(0)
155 adx = context.t1stack.pop(0)
156 ady = context.t1stack.pop(0)
157 bchar = context.t1stack.pop(0)
158 achar = context.t1stack.pop(0)
159 context.t1font.updateglyphpath(bglyph, path, atrafo, context)
160 atrafo = atrafo * trafo.translate_pt(adx-sab, ady)
161 context.t1font.updateglyphpath(aglyph, path, atrafo, context)
163 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
164 bchar = context.t1stack.pop()
165 achar = context.t1stack.pop()
166 aglyph = encoding.adobestandardencoding.decode(achar)
167 bglyph = encoding.adobestandardencoding.decode(bchar)
168 seacglyphs[aglyph] = 1
169 seacglyphs[bglyph] = 1
170 context.t1font.gatherglyphcalls(bglyph, seacglyphs, subrs, othersubrs, context)
171 context.t1font.gatherglyphcalls(aglyph, seacglyphs, subrs, othersubrs, context)
173 T1seac = _T1seac()
176 class _T1sbw(T1cmd):
178 def __init__(self):
179 T1cmd.__init__(self, 7, subcmd=1)
181 def __str__(self):
182 return "sbw"
184 def updatepath(self, path, trafo, context):
185 sbx = context.t1stack.pop(0)
186 sby = context.t1stack.pop(0)
187 wx = context.t1stack.pop(0)
188 wy = context.t1stack.pop(0)
189 path.append(moveto_pt(*trafo.apply_pt(sbx, sby)))
190 context.x = sbx
191 context.y = sby
192 context.wx = wx
193 context.wy = wy
195 T1sbw = _T1sbw()
198 # path construction commands
200 class _T1closepath(T1cmd):
202 def __init__(self):
203 T1cmd.__init__(self, 9)
205 def __str__(self):
206 return "closepath"
208 def updatepath(self, path, trafo, context):
209 path.append(closepath())
210 # The closepath in T1 is different from PostScripts in that it does
211 # *not* modify the current position; hence we need to add an additional
212 # moveto here ...
213 path.append(moveto_pt(*trafo.apply_pt(context.x, context.y)))
215 T1closepath = _T1closepath()
218 class _T1hlineto(T1cmd):
220 def __init__(self):
221 T1cmd.__init__(self, 6)
223 def __str__(self):
224 return "hlineto"
226 def updatepath(self, path, trafo, context):
227 dx = context.t1stack.pop(0)
228 path.append(lineto_pt(*trafo.apply_pt(context.x + dx, context.y)))
229 context.x += dx
231 T1hlineto = _T1hlineto()
234 class _T1hmoveto(T1cmd):
236 def __init__(self):
237 T1cmd.__init__(self, 22)
239 def __str__(self):
240 return "hmoveto"
242 def updatepath(self, path, trafo, context):
243 dx = context.t1stack.pop(0)
244 path.append(moveto_pt(*trafo.apply_pt(context.x + dx, context.y)))
245 context.x += dx
247 T1hmoveto = _T1hmoveto()
250 class _T1hvcurveto(T1cmd):
252 def __init__(self):
253 T1cmd.__init__(self, 31)
255 def __str__(self):
256 return "hvcurveto"
258 def updatepath(self, path, trafo, context):
259 dx1 = context.t1stack.pop(0)
260 dx2 = context.t1stack.pop(0)
261 dy2 = context.t1stack.pop(0)
262 dy3 = context.t1stack.pop(0)
263 path.append(curveto_pt(*(trafo.apply_pt(context.x + dx1, context.y) +
264 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy2) +
265 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy2 + dy3))))
266 context.x += dx1+dx2
267 context.y += dy2+dy3
269 T1hvcurveto = _T1hvcurveto()
272 class _T1rlineto(T1cmd):
274 def __init__(self):
275 T1cmd.__init__(self, 5)
277 def __str__(self):
278 return "rlineto"
280 def updatepath(self, path, trafo, context):
281 dx = context.t1stack.pop(0)
282 dy = context.t1stack.pop(0)
283 path.append(lineto_pt(*trafo.apply_pt(context.x + dx, context.y + dy)))
284 context.x += dx
285 context.y += dy
287 T1rlineto = _T1rlineto()
290 class _T1rmoveto(T1cmd):
292 def __init__(self):
293 T1cmd.__init__(self, 21)
295 def __str__(self):
296 return "rmoveto"
298 def updatepath(self, path, trafo, context):
299 dx = context.t1stack.pop(0)
300 dy = context.t1stack.pop(0)
301 path.append(moveto_pt(*trafo.apply_pt(context.x + dx, context.y + dy)))
302 context.x += dx
303 context.y += dy
305 T1rmoveto = _T1rmoveto()
308 class _T1rrcurveto(T1cmd):
310 def __init__(self):
311 T1cmd.__init__(self, 8)
313 def __str__(self):
314 return "rrcurveto"
316 def updatepath(self, path, trafo, context):
317 dx1 = context.t1stack.pop(0)
318 dy1 = context.t1stack.pop(0)
319 dx2 = context.t1stack.pop(0)
320 dy2 = context.t1stack.pop(0)
321 dx3 = context.t1stack.pop(0)
322 dy3 = context.t1stack.pop(0)
323 path.append(curveto_pt(*(trafo.apply_pt(context.x + dx1, context.y + dy1) +
324 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy1 + dy2) +
325 trafo.apply_pt(context.x + dx1 + dx2 + dx3, context.y + dy1 + dy2 + dy3))))
326 context.x += dx1+dx2+dx3
327 context.y += dy1+dy2+dy3
329 T1rrcurveto = _T1rrcurveto()
332 class _T1vlineto(T1cmd):
334 def __init__(self):
335 T1cmd.__init__(self, 7)
337 def __str__(self):
338 return "vlineto"
340 def updatepath(self, path, trafo, context):
341 dy = context.t1stack.pop(0)
342 path.append(lineto_pt(*trafo.apply_pt(context.x, context.y + dy)))
343 context.y += dy
345 T1vlineto = _T1vlineto()
348 class _T1vmoveto(T1cmd):
350 def __init__(self):
351 T1cmd.__init__(self, 4)
353 def __str__(self):
354 return "vmoveto"
356 def updatepath(self, path, trafo, context):
357 dy = context.t1stack.pop(0)
358 path.append(moveto_pt(*trafo.apply_pt(context.x, context.y + dy)))
359 context.y += dy
361 T1vmoveto = _T1vmoveto()
364 class _T1vhcurveto(T1cmd):
366 def __init__(self):
367 T1cmd.__init__(self, 30)
369 def __str__(self):
370 return "vhcurveto"
372 def updatepath(self, path, trafo, context):
373 dy1 = context.t1stack.pop(0)
374 dx2 = context.t1stack.pop(0)
375 dy2 = context.t1stack.pop(0)
376 dx3 = context.t1stack.pop(0)
377 path.append(curveto_pt(*(trafo.apply_pt(context.x, context.y + dy1) +
378 trafo.apply_pt(context.x + dx2, context.y + dy1 + dy2) +
379 trafo.apply_pt(context.x + dx2 + dx3, context.y + dy1 + dy2))))
380 context.x += dx2+dx3
381 context.y += dy1+dy2
383 T1vhcurveto = _T1vhcurveto()
386 # hint commands
388 class _T1dotsection(T1cmd):
390 def __init__(self):
391 T1cmd.__init__(self, 0, subcmd=1)
393 def __str__(self):
394 return "dotsection"
396 def updatepath(self, path, trafo, context):
397 pass
399 T1dotsection = _T1dotsection()
402 class _T1hstem(T1cmd):
404 def __init__(self):
405 T1cmd.__init__(self, 1)
407 def __str__(self):
408 return "hstem"
410 def updatepath(self, path, trafo, context):
411 y = context.t1stack.pop(0)
412 dy = context.t1stack.pop(0)
414 T1hstem = _T1hstem()
417 class _T1hstem3(T1cmd):
419 def __init__(self):
420 T1cmd.__init__(self, 2, subcmd=1)
422 def __str__(self):
423 return "hstem3"
425 def updatepath(self, path, trafo, context):
426 y0 = context.t1stack.pop(0)
427 dy0 = context.t1stack.pop(0)
428 y1 = context.t1stack.pop(0)
429 dy1 = context.t1stack.pop(0)
430 y2 = context.t1stack.pop(0)
431 dy2 = context.t1stack.pop(0)
433 T1hstem3 = _T1hstem3()
436 class _T1vstem(T1cmd):
438 def __init__(self):
439 T1cmd.__init__(self, 3)
441 def __str__(self):
442 return "vstem"
444 def updatepath(self, path, trafo, context):
445 x = context.t1stack.pop(0)
446 dx = context.t1stack.pop(0)
448 T1vstem = _T1vstem()
451 class _T1vstem3(T1cmd):
453 def __init__(self):
454 T1cmd.__init__(self, 1, subcmd=1)
456 def __str__(self):
457 return "vstem3"
459 def updatepath(self, path, trafo, context):
460 self.x0 = context.t1stack.pop(0)
461 self.dx0 = context.t1stack.pop(0)
462 self.x1 = context.t1stack.pop(0)
463 self.dx1 = context.t1stack.pop(0)
464 self.x2 = context.t1stack.pop(0)
465 self.dx2 = context.t1stack.pop(0)
467 T1vstem3 = _T1vstem3()
470 # arithmetic command
472 class _T1div(T1cmd):
474 def __init__(self):
475 T1cmd.__init__(self, 12, subcmd=1)
477 def __str__(self):
478 return "div"
480 def updatepath(self, path, trafo, context):
481 num2 = context.t1stack.pop()
482 num1 = context.t1stack.pop()
483 context.t1stack.append(divmod(num1, num2)[0])
485 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
486 num2 = context.t1stack.pop()
487 num1 = context.t1stack.pop()
488 context.t1stack.append(divmod(num1, num2)[0])
490 T1div = _T1div()
493 # subroutine commands
495 class _T1callothersubr(T1cmd):
497 def __init__(self):
498 T1cmd.__init__(self, 16, subcmd=1)
500 def __str__(self):
501 return "callothersubr"
503 def updatepath(self, path, trafo, context):
504 othersubrnumber = context.t1stack.pop()
505 n = context.t1stack.pop()
506 for i in range(n):
507 context.psstack.append(context.t1stack.pop())
509 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
510 othersubrnumber = context.t1stack.pop()
511 othersubrs[othersubrnumber] = 1
512 n = context.t1stack.pop()
513 for i in range(n):
514 context.psstack.append(context.t1stack.pop())
516 T1callothersubr = _T1callothersubr()
519 class _T1callsubr(T1cmd):
521 def __init__(self):
522 T1cmd.__init__(self, 10)
524 def __str__(self):
525 return "callsubr"
527 def updatepath(self, path, trafo, context):
528 subr = context.t1stack.pop()
529 context.t1font.updatesubrpath(subr, path, trafo, context)
531 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
532 subr = context.t1stack.pop()
533 subrs[subr] = 1
534 context.t1font.gathersubrcalls(subr, seacglyphs, subrs, othersubrs, context)
536 T1callsubr = _T1callsubr()
539 class _T1pop(T1cmd):
541 def __init__(self):
542 T1cmd.__init__(self, 17, subcmd=1)
544 def __str__(self):
545 return "pop"
547 def updatepath(self, path, trafo, context):
548 context.t1stack.append(context.psstack.pop())
550 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
551 context.t1stack.append(context.psstack.pop())
553 T1pop = _T1pop()
556 class _T1return(T1cmd):
558 def __init__(self):
559 T1cmd.__init__(self, 11)
561 def __str__(self):
562 return "return"
564 def updatepath(self, path, trafo, context):
565 pass
567 T1return = _T1return()
570 class _T1setcurrentpoint(T1cmd):
572 def __init__(self):
573 T1cmd.__init__(self, 33, subcmd=1)
575 def __str__(self):
576 return "setcurrentpoint" % self.x, self.y
578 def updatepath(self, path, trafo, context):
579 x = context.t1stack.pop(0)
580 y = context.t1stack.pop(0)
581 path.append(moveto_pt(*trafo.apply_pt(x, y)))
582 context.x = x
583 context.y = y
585 T1setcurrentpoint = _T1setcurrentpoint()
588 ######################################################################
590 class cursor:
591 """cursor to read a string token by token"""
593 def __init__(self, data, startstring, eattokensep=1, tokenseps=" \t\r\n", tokenstarts="()<>[]{}/%"):
594 """creates a cursor for the string data
596 startstring is a string at which the cursor should start at. The first
597 ocurance of startstring is used. When startstring is not in data, an
598 exception is raised, otherwise the cursor is set to the position right
599 after the startstring. When eattokenseps is set, startstring must be
600 followed by a tokensep and this first tokensep is also consumed.
601 tokenseps is a string containing characters to be used as token
602 separators. tokenstarts is a string containing characters which
603 directly (even without intermediate token separator) start a new token.
605 self.data = data
606 self.pos = self.data.index(startstring) + len(startstring)
607 self.tokenseps = tokenseps
608 self.tokenstarts = tokenstarts
609 if eattokensep:
610 if self.data[self.pos] not in self.tokenstarts:
611 if self.data[self.pos] not in self.tokenseps:
612 raise ValueError("cursor initialization string is not followed by a token separator")
613 self.pos += 1
615 def gettoken(self):
616 """get the next token
618 Leading token separators and comments are silently consumed. The first token
619 separator after the token is also silently consumed."""
620 while self.data[self.pos] in self.tokenseps:
621 self.pos += 1
622 # ignore comments including subsequent whitespace characters
623 while self.data[self.pos] == "%":
624 while self.data[self.pos] not in "\r\n":
625 self.pos += 1
626 while self.data[self.pos] in self.tokenseps:
627 self.pos += 1
628 startpos = self.pos
629 while self.data[self.pos] not in self.tokenseps:
630 # any character in self.tokenstarts ends the token
631 if self.pos>startpos and self.data[self.pos] in self.tokenstarts:
632 break
633 self.pos += 1
634 result = self.data[startpos:self.pos]
635 if self.data[self.pos] in self.tokenseps:
636 self.pos += 1 # consume a single tokensep
637 return result
639 def getint(self):
640 """get the next token as an integer"""
641 return int(self.gettoken())
643 def getbytes(self, count):
644 """get the next count bytes"""
645 startpos = self.pos
646 self.pos += count
647 return self.data[startpos: self.pos]
650 class T1font:
652 eexecr = 55665
653 charstringr = 4330
655 def __init__(self, data1, data2eexec, data3):
656 """initializes a t1font instance
658 data1 and data3 are the two clear text data parts and data2 is
659 the binary data part"""
660 self.data1 = data1
661 self._data2eexec = data2eexec
662 self.data3 = data3
664 # marker and value for decoded data
665 self._data2 = None
666 # note that data2eexec is set to none by setsubrcmds and setglyphcmds
667 # this *also* denotes, that data2 is out-of-date; hence they are both
668 # marked by an _ and getdata2 and getdata2eexec will properly resolve
669 # the current state of decoding ...
671 # marker and value for standard encoding check
672 self.encoding = None
674 def _eexecdecode(self, code):
675 """eexec decoding of code"""
676 return decoder(code, self.eexecr, 4)
678 def _charstringdecode(self, code):
679 """charstring decoding of code"""
680 return decoder(code, self.charstringr, self.lenIV)
682 def _eexecencode(self, data):
683 """eexec encoding of data"""
684 return encoder(data, self.eexecr, "PyX!")
686 def _charstringencode(self, data):
687 """eexec encoding of data"""
688 return encoder(data, self.charstringr, "PyX!"[:self.lenIV])
690 lenIVpattern = re.compile("/lenIV\s+(\d+)\s+def\s+")
691 flexhintsubrs = [[3, 0, T1callothersubr, T1pop, T1pop, T1setcurrentpoint, T1return],
692 [0, 1, T1callothersubr, T1return],
693 [0, 2, T1callothersubr, T1return],
694 [T1return]]
696 def _encoding(self):
697 """helper method to lookup the encoding in the font"""
698 c = cursor(self.data1, "/Encoding")
699 token1 = c.gettoken()
700 token2 = c.gettoken()
701 if token1 == "StandardEncoding" and token2 == "def":
702 self.encoding = encoding.adobestandardencoding
703 else:
704 encvector = [None]*256
705 while 1:
706 self.encodingstart = c.pos
707 if c.gettoken() == "dup":
708 break
709 while 1:
710 i = c.getint()
711 glyph = c.gettoken()
712 if 0 <= i < 256:
713 encvector[i] = glyph[1:]
714 token = c.gettoken(); assert token == "put"
715 self.encodingend = c.pos
716 token = c.gettoken()
717 if token == "readonly" or token == "def":
718 break
719 assert token == "dup"
720 self.encoding = encoding.encoding(encvector)
722 def _data2decode(self):
723 """decodes data2eexec to the data2 string and the subr and glyphs dictionary
725 It doesn't make sense to call this method twice -- check the content of
726 data2 before calling. The method also keeps the subrs and charstrings
727 start and end positions for later use."""
728 self._data2 = self._eexecdecode(self._data2eexec)
730 m = self.lenIVpattern.search(self._data2)
731 if m:
732 self.lenIV = int(m.group(1))
733 else:
734 self.lenIV = 4
735 self.emptysubr = self._charstringencode(chr(11))
737 # extract Subrs
738 c = cursor(self._data2, "/Subrs")
739 self.subrsstart = c.pos
740 arraycount = c.getint()
741 token = c.gettoken(); assert token == "array"
742 self.subrs = []
743 for i in range(arraycount):
744 token = c.gettoken(); assert token == "dup"
745 token = c.getint(); assert token == i
746 size = c.getint()
747 if not i:
748 self.subrrdtoken = c.gettoken()
749 else:
750 token = c.gettoken(); assert token == self.subrrdtoken
751 self.subrs.append(c.getbytes(size))
752 token = c.gettoken()
753 if token == "noaccess":
754 token = "%s %s" % (token, c.gettoken())
755 if not i:
756 self.subrnptoken = token
757 else:
758 assert token == self.subrnptoken
759 self.subrsend = c.pos
761 # hasflexhintsubrs is a boolean indicating that the font uses flex or
762 # hint replacement subrs as specified by Adobe (tm). When it does, the
763 # first 4 subrs should all be copied except when none of them are used
764 # in the stripped version of the font since we than get a font not
765 # using flex or hint replacement subrs at all.
766 self.hasflexhintsubrs = (arraycount >= len(self.flexhintsubrs) and
767 [self.getsubrcmds(i)
768 for i in range(len(self.flexhintsubrs))] == self.flexhintsubrs)
770 # extract glyphs
771 self.glyphs = {}
772 self.glyphlist = [] # we want to keep the order of the glyph names
773 c = cursor(self._data2, "/CharStrings")
774 self.charstringsstart = c.pos
775 c.getint()
776 token = c.gettoken(); assert token == "dict"
777 token = c.gettoken(); assert token == "dup"
778 token = c.gettoken(); assert token == "begin"
779 first = 1
780 while 1:
781 chartoken = c.gettoken()
782 if chartoken == "end":
783 break
784 assert chartoken[0] == "/"
785 size = c.getint()
786 if first:
787 self.glyphrdtoken = c.gettoken()
788 else:
789 token = c.gettoken(); assert token == self.glyphrdtoken
790 self.glyphlist.append(chartoken[1:])
791 self.glyphs[chartoken[1:]] = c.getbytes(size)
792 if first:
793 self.glyphndtoken = c.gettoken()
794 else:
795 token = c.gettoken(); assert token == self.glyphndtoken
796 first = 0
797 self.charstringsend = c.pos
798 assert not self.subrs or self.subrrdtoken == self.glyphrdtoken
800 def _cmds(self, code):
801 """return a list of T1cmd's for encoded charstring data in code"""
802 code = array.array("B", self._charstringdecode(code))
803 cmds = []
804 while code:
805 x = code.pop(0)
806 if x == 12: # this starts an escaped cmd
807 cmds.append(T1subcmds[code.pop(0)])
808 elif 0 <= x < 32: # those are cmd's
809 cmds.append(T1cmds[x])
810 elif 32 <= x <= 246: # short ints
811 cmds.append(x-139)
812 elif 247 <= x <= 250: # mid size ints
813 cmds.append(((x - 247)*256) + code.pop(0) + 108)
814 elif 251 <= x <= 254: # mid size ints
815 cmds.append(-((x - 251)*256) - code.pop(0) - 108)
816 else: # x = 255, i.e. full size ints
817 y = ((code.pop(0)*256l+code.pop(0))*256+code.pop(0))*256+code.pop(0)
818 if y > (1l << 31):
819 cmds.append(y - (1l << 32))
820 else:
821 cmds.append(y)
822 return cmds
824 def _code(self, cmds):
825 """return an encoded charstring data for list of T1cmd's in cmds"""
826 code = array.array("B")
827 for cmd in cmds:
828 try:
829 if cmd.subcmd:
830 code.append(12)
831 code.append(cmd.code)
832 except AttributeError:
833 if -107 <= cmd <= 107:
834 code.append(cmd+139)
835 elif 108 <= cmd <= 1131:
836 a, b = divmod(cmd-108, 256)
837 code.append(a+247)
838 code.append(b)
839 elif -1131 <= cmd <= -108:
840 a, b = divmod(-cmd-108, 256)
841 code.append(a+251)
842 code.append(b)
843 else:
844 if cmd < 0:
845 cmd += 1l << 32
846 cmd, x4 = divmod(cmd, 256)
847 cmd, x3 = divmod(cmd, 256)
848 x1, x2 = divmod(cmd, 256)
849 code.append(255)
850 code.append(x1)
851 code.append(x2)
852 code.append(x3)
853 code.append(x4)
854 return self._charstringencode(code.tostring())
856 def getsubrcmds(self, subr):
857 """return a list of T1cmd's for subr subr"""
858 if not self._data2:
859 self._data2decode()
860 return self._cmds(self.subrs[subr])
862 def getglyphcmds(self, glyph):
863 """return a list of T1cmd's for glyph glyph"""
864 if not self._data2:
865 self._data2decode()
866 return self._cmds(self.glyphs[glyph])
868 def setsubrcmds(self, subr, cmds):
869 """replaces the T1cmd's by the list cmds for subr subr"""
870 if not self._data2:
871 self._data2decode()
872 self._data2eexec = None
873 self.subrs[subr] = self._code(cmds)
875 def setglyphcmds(self, glyph, cmds):
876 """replaces the T1cmd's by the list cmds for glyph glyph"""
877 if not self._data2:
878 self._data2decode()
879 self._data2eexec = None
880 self.glyphs[glyph] = self._code(cmds)
882 def updatepath(self, cmds, path, trafo, context):
883 for cmd in cmds:
884 if isinstance(cmd, T1cmd):
885 cmd.updatepath(path, trafo, context)
886 else:
887 context.t1stack.append(cmd)
889 def updatesubrpath(self, subr, path, trafo, context):
890 self.updatepath(self.getsubrcmds(subr), path, trafo, context)
892 def updateglyphpath(self, glyph, path, trafo, context):
893 self.updatepath(self.getglyphcmds(glyph), path, trafo, context)
895 def gathercalls(self, cmds, seacglyphs, subrs, othersubrs, context):
896 for cmd in cmds:
897 if isinstance(cmd, T1cmd):
898 cmd.gathercalls(seacglyphs, subrs, othersubrs, context)
899 else:
900 context.t1stack.append(cmd)
902 def gathersubrcalls(self, subr, seacglyphs, subrs, othersubrs, context):
903 self.gathercalls(self.getsubrcmds(subr), seacglyphs, subrs, othersubrs, context)
905 def gatherglyphcalls(self, glyph, seacglyphs, subrs, othersubrs, context):
906 self.gathercalls(self.getglyphcmds(glyph), seacglyphs, subrs, othersubrs, context)
908 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")
910 def getglyphpathwxwy_pt(self, glyph, size):
911 m = self.fontmatrixpattern.search(self.data1)
912 m11, m12, m21, m22, v1, v2 = map(float, m.groups()[:6])
913 t = trafo.trafo_pt(matrix=((m11, m12), (m21, m22)), vector=(v1, v2)).scaled(size)
914 context = T1context(self)
915 p = path()
916 self.updateglyphpath(glyph, p, t, context)
917 wx, wy = t.apply_pt(context.wx, context.wy)
918 return p, wx, wy
920 def getglyphpath(self, glyph, size):
921 """return a PyX path for glyph named glyph"""
922 return self.getglyphpathwxwy_pt(glyph, size)[0]
924 def getglyphwxwy_pt(self, glyph, size):
925 return self.getglyphpathwxwy_pt(glyph, size)[1:]
927 def getdata2(self, subrs=None, glyphs=None):
928 """makes a data2 string
930 subrs is a dict containing those subrs numbers as keys,
931 which are to be contained in the subrsstring to be created.
932 If subrs is None, all subrs in self.subrs will be used.
933 The subrs dict might be modified *in place*.
935 glyphs is a dict containing those glyph names as keys,
936 which are to be contained in the charstringsstring to be created.
937 If glyphs is None, all glyphs in self.glyphs will be used."""
938 def addsubrs(subrs, result):
939 if subrs is not None:
940 # some adjustments to the subrs dict
941 if subrs:
942 subrsindices = subrs.keys()
943 subrsmin = min(subrsindices)
944 subrsmax = max(subrsindices)
945 if self.hasflexhintsubrs and subrsmin < len(self.flexhintsubrs):
946 # According to the spec we need to keep all the flex and hint subrs
947 # as long as any of it is used.
948 for subr in range(len(self.flexhintsubrs)):
949 subrs[subr] = 1
950 else:
951 subrsmax = -1
952 else:
953 # build a new subrs dict containing all subrs
954 subrs = dict([(subr, 1) for subr in range(len(self.subrs))])
955 subrsmax = len(self.subrs) - 1
957 # build the string from all selected subrs
958 result.append("%d array\n" % (subrsmax + 1))
959 for subr in range(subrsmax+1):
960 if subrs.has_key(subr):
961 code = self.subrs[subr]
962 else:
963 code = self.emptysubr
964 result.append("dup %d %d %s %s %s\n" % (subr, len(code), self.subrrdtoken, code, self.subrnptoken))
966 def addcharstrings(glyphs, result):
967 result.append("%d dict dup begin\n" % (glyphs is None and len(self.glyphlist) or len(glyphs)))
968 for glyph in self.glyphlist:
969 if glyphs is None or glyphs.has_key(glyph):
970 result.append("/%s %d %s %s %s\n" % (glyph, len(self.glyphs[glyph]), self.glyphrdtoken, self.glyphs[glyph], self.glyphndtoken))
971 result.append("end\n")
973 if self.subrsstart < self.charstringsstart:
974 result = [self._data2[:self.subrsstart]]
975 addsubrs(subrs, result)
976 result.append(self._data2[self.subrsend:self.charstringsstart])
977 addcharstrings(glyphs, result)
978 result.append(self._data2[self.charstringsend:])
979 else:
980 result = [self._data2[:self.charstringsstart]]
981 addcharstrings(glyphs, result)
982 result.append(self._data2[self.charstringsend:self.subrsstart])
983 addsubrs(subrs, result)
984 result.append(self._data2[self.subrsend:])
985 return "".join(result)
987 def getdata2eexec(self):
988 if self._data2eexec:
989 return self._data2eexec
990 # note that self._data2 is out-of-date here too, hence we need to call getdata2
991 return self._eexecencode(self.getdata2())
993 newlinepattern = re.compile("\s*[\r\n]\s*")
994 uniqueidpattern = re.compile("/UniqueID\s+\d+\s+def\s+")
996 def getstrippedfont(self, glyphs):
997 """create a T1font instance containing only certain glyphs
999 glyphs is a dict having the glyph names to be contained as keys.
1000 The glyphs dict might be modified *in place*.
1002 # TODO: we could also strip othersubrs to those actually used
1004 # collect information about used glyphs and subrs
1005 seacglyphs = {}
1006 subrs = {}
1007 othersubrs = {}
1008 for glyph in glyphs.keys():
1009 self.gatherglyphcalls(glyph, seacglyphs, subrs, othersubrs, T1context(self))
1010 # while we have gathered all subrs for the seacglyphs alreadys, we
1011 # might have missed the glyphs themself (when they are not used stand-alone)
1012 glyphs.update(seacglyphs)
1013 glyphs[".notdef"] = 1
1015 # strip data1
1016 if not self.encoding:
1017 self._encoding()
1018 if self.encoding is encoding.adobestandardencoding:
1019 data1 = self.data1
1020 else:
1021 encodingstrings = []
1022 for char, glyph in enumerate(self.encoding.encvector):
1023 if glyph in glyphs.keys():
1024 encodingstrings.append("dup %i /%s put\n" % (char, glyph))
1025 data1 = self.data1[:self.encodingstart] + "".join(encodingstrings) + self.data1[self.encodingend:]
1026 data1 = self.newlinepattern.subn("\n", data1)[0]
1027 data1 = self.uniqueidpattern.subn("", data1)[0]
1029 # strip data2
1030 data2 = self.uniqueidpattern.subn("", self.getdata2(subrs, glyphs))[0]
1032 # strip data3
1033 data3 = self.newlinepattern.subn("\n", self.data3)[0]
1035 # create and return the new font instance
1036 return T1font(data1.rstrip() + "\n", self._eexecencode(data2), data3.rstrip() + "\n")
1038 def getflags(self):
1039 # As a simple heuristics we assume non-symbolic fonts if and only
1040 # if the Adobe standard encoding is used. All other font flags are
1041 # not specified here.
1042 if not self.encoding:
1043 self._encoding()
1044 if self.encoding is encoding.adobestandardencoding:
1045 return 32
1046 return 4
1048 def outputPFA(self, file):
1049 """output the T1font in PFA format"""
1050 file.write(self.data1)
1051 data2eexechex = binascii.b2a_hex(self.getdata2eexec())
1052 linelength = 64
1053 for i in range((len(data2eexechex)-1)/linelength + 1):
1054 file.write(data2eexechex[i*linelength: i*linelength+linelength])
1055 file.write("\n")
1056 file.write(self.data3)
1058 def outputPFB(self, file):
1059 """output the T1font in PFB format"""
1060 data2eexec = self.getdata2eexec()
1061 def pfblength(data):
1062 l = len(data)
1063 l, x1 = divmod(l, 256)
1064 l, x2 = divmod(l, 256)
1065 x4, x3 = divmod(l, 256)
1066 return chr(x1) + chr(x2) + chr(x3) + chr(x4)
1067 file.write("\200\1")
1068 file.write(pfblength(self.data1))
1069 file.write(self.data1)
1070 file.write("\200\2")
1071 file.write(pfblength(data2eexec))
1072 file.write(data2eexec)
1073 file.write("\200\1")
1074 file.write(pfblength(self.data3))
1075 file.write(self.data3)
1076 file.write("\200\3")
1078 def outputPS(self, file, writer):
1079 """output the PostScript code for the T1font to the file file"""
1080 self.outputPFA(file)
1082 def outputPDF(self, file, writer):
1083 data2eexec = self.getdata2eexec()
1084 data3 = self.data3
1085 # we might be allowed to skip the third part ...
1086 if (data3.replace("\n", "")
1087 .replace("\r", "")
1088 .replace("\t", "")
1089 .replace(" ", "")) == "0"*512 + "cleartomark":
1090 data3 = ""
1092 data = self.data1 + data2eexec + data3
1093 if writer.compress and haszlib:
1094 data = zlib.compress(data)
1096 file.write("<<\n"
1097 "/Length %d\n"
1098 "/Length1 %d\n"
1099 "/Length2 %d\n"
1100 "/Length3 %d\n" % (len(data), len(self.data1), len(data2eexec), len(data3)))
1101 if writer.compress and haszlib:
1102 file.write("/Filter /FlateDecode\n")
1103 file.write(">>\n"
1104 "stream\n")
1105 file.write(data)
1106 file.write("\n"
1107 "endstream\n")
1110 class T1pfafont(T1font):
1112 """create a T1font instance from a pfa font file"""
1114 def __init__(self, filename):
1115 d = open(filename, "rb").read()
1116 # hey, that's quick'n'dirty
1117 m1 = d.index("eexec") + 6
1118 m2 = d.index("0"*40)
1119 data1 = d[:m1]
1120 data2 = binascii.a2b_hex(d[m1: m2].replace(" ", "").replace("\r", "").replace("\n", ""))
1121 data3 = d[m2:]
1122 T1font.__init__(self, data1, data2, data3)
1125 class T1pfbfont(T1font):
1127 """create a T1font instance from a pfb font file"""
1129 def __init__(self, filename):
1130 def pfblength(s):
1131 if len(s) != 4:
1132 raise ValueError("invalid string length")
1133 return (ord(s[0]) +
1134 ord(s[1])*256 +
1135 ord(s[2])*256*256 +
1136 ord(s[3])*256*256*256)
1137 f = open(filename, "rb")
1138 mark = f.read(2); assert mark == "\200\1"
1139 data1 = f.read(pfblength(f.read(4)))
1140 mark = f.read(2); assert mark == "\200\2"
1141 data2 = ""
1142 while mark == "\200\2":
1143 data2 = data2 + f.read(pfblength(f.read(4)))
1144 mark = f.read(2)
1145 assert mark == "\200\1"
1146 data3 = f.read(pfblength(f.read(4)))
1147 mark = f.read(2); assert mark == "\200\3"
1148 assert not f.read(1)
1149 T1font.__init__(self, data1, data2, data3)