remove shebang -- see comment 3 on https://bugzilla.redhat.com/bugzilla/show_bug...
[PyX/mjg.git] / pyx / font / t1font.py
blobcefd3aadf38e29a20a8102f75bd7825fa3589364
1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2005 André Wobst <wobsta@users.sourceforge.net>
5 # Copyright (C) 2006 Jörg Lehmann <joergl@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 from __future__ import nested_scopes
25 import array, binascii, re
26 try:
27 import zlib
28 haszlib = 1
29 except ImportError:
30 haszlib = 0
32 try:
33 enumerate([])
34 except NameError:
35 # fallback implementation for Python 2.2 and below
36 def enumerate(list):
37 return zip(xrange(len(list)), list)
39 from pyx import trafo
40 from pyx.path import path, moveto_pt, lineto_pt, curveto_pt, closepath
41 import encoding
43 try:
44 from _t1code import *
45 except:
46 from t1code import *
49 class T1context:
51 def __init__(self, t1font):
52 """context for T1cmd evaluation"""
53 self.t1font = t1font
55 # state description
56 self.x = None
57 self.y = None
58 self.wx = None
59 self.wy = None
60 self.t1stack = []
61 self.psstack = []
64 ######################################################################
65 # T1 commands
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
68 # instance only
70 T1cmds = {}
71 T1subcmds = {}
73 class T1cmd:
75 def __init__(self, code, subcmd=0):
76 self.code = code
77 self.subcmd = subcmd
78 if subcmd:
79 T1subcmds[code] = self
80 else:
81 T1cmds[code] = self
83 def __str__(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.
105 pass
108 # commands for starting and finishing
110 class _T1endchar(T1cmd):
112 def __init__(self):
113 T1cmd.__init__(self, 14)
115 def __str__(self):
116 return "endchar"
118 def updatepath(self, path, trafo, context):
119 pass
121 T1endchar = _T1endchar()
124 class _T1hsbw(T1cmd):
126 def __init__(self):
127 T1cmd.__init__(self, 13)
129 def __str__(self):
130 return "hsbw"
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)))
136 context.x = sbx
137 context.y = 0
138 context.wx = wx
139 context.wy = 0
141 T1hsbw = _T1hsbw()
144 class _T1seac(T1cmd):
146 def __init__(self):
147 T1cmd.__init__(self, 6, subcmd=1)
149 def __str__(self):
150 return "seac"
152 def updatepath(self, path, atrafo, context):
153 sab = context.t1stack.pop(0)
154 adx = context.t1stack.pop(0)
155 ady = context.t1stack.pop(0)
156 bchar = context.t1stack.pop(0)
157 achar = context.t1stack.pop(0)
158 context.t1font.updateglyphpath(bglyph, path, atrafo, context)
159 atrafo = atrafo * trafo.translate_pt(adx-sab, ady)
160 context.t1font.updateglyphpath(aglyph, path, atrafo, context)
162 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
163 bchar = context.t1stack.pop()
164 achar = context.t1stack.pop()
165 aglyph = encoding.adobestandardencoding.decode(achar)
166 bglyph = encoding.adobestandardencoding.decode(bchar)
167 seacglyphs[aglyph] = 1
168 seacglyphs[bglyph] = 1
169 context.t1font.gatherglyphcalls(bglyph, seacglyphs, subrs, othersubrs, context)
170 context.t1font.gatherglyphcalls(aglyph, seacglyphs, subrs, othersubrs, context)
172 T1seac = _T1seac()
175 class _T1sbw(T1cmd):
177 def __init__(self):
178 T1cmd.__init__(self, 7, subcmd=1)
180 def __str__(self):
181 return "sbw"
183 def updatepath(self, path, trafo, context):
184 sbx = context.t1stack.pop(0)
185 sby = context.t1stack.pop(0)
186 wx = context.t1stack.pop(0)
187 wy = context.t1stack.pop(0)
188 path.append(moveto_pt(*trafo.apply_pt(sbx, sby)))
189 context.x = sbx
190 context.y = sby
191 context.wx = wx
192 context.wy = wy
194 T1sbw = _T1sbw()
197 # path construction commands
199 class _T1closepath(T1cmd):
201 def __init__(self):
202 T1cmd.__init__(self, 9)
204 def __str__(self):
205 return "closepath"
207 def updatepath(self, path, trafo, context):
208 path.append(closepath())
209 # The closepath in T1 is different from PostScripts in that it does
210 # *not* modify the current position; hence we need to add an additional
211 # moveto here ...
212 path.append(moveto_pt(*trafo.apply_pt(context.x, context.y)))
214 T1closepath = _T1closepath()
217 class _T1hlineto(T1cmd):
219 def __init__(self):
220 T1cmd.__init__(self, 6)
222 def __str__(self):
223 return "hlineto"
225 def updatepath(self, path, trafo, context):
226 dx = context.t1stack.pop(0)
227 path.append(lineto_pt(*trafo.apply_pt(context.x + dx, context.y)))
228 context.x += dx
230 T1hlineto = _T1hlineto()
233 class _T1hmoveto(T1cmd):
235 def __init__(self):
236 T1cmd.__init__(self, 22)
238 def __str__(self):
239 return "hmoveto"
241 def updatepath(self, path, trafo, context):
242 dx = context.t1stack.pop(0)
243 path.append(moveto_pt(*trafo.apply_pt(context.x + dx, context.y)))
244 context.x += dx
246 T1hmoveto = _T1hmoveto()
249 class _T1hvcurveto(T1cmd):
251 def __init__(self):
252 T1cmd.__init__(self, 31)
254 def __str__(self):
255 return "hvcurveto"
257 def updatepath(self, path, trafo, context):
258 dx1 = context.t1stack.pop(0)
259 dx2 = context.t1stack.pop(0)
260 dy2 = context.t1stack.pop(0)
261 dy3 = context.t1stack.pop(0)
262 path.append(curveto_pt(*(trafo.apply_pt(context.x + dx1, context.y) +
263 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy2) +
264 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy2 + dy3))))
265 context.x += dx1+dx2
266 context.y += dy2+dy3
268 T1hvcurveto = _T1hvcurveto()
271 class _T1rlineto(T1cmd):
273 def __init__(self):
274 T1cmd.__init__(self, 5)
276 def __str__(self):
277 return "rlineto"
279 def updatepath(self, path, trafo, context):
280 dx = context.t1stack.pop(0)
281 dy = context.t1stack.pop(0)
282 path.append(lineto_pt(*trafo.apply_pt(context.x + dx, context.y + dy)))
283 context.x += dx
284 context.y += dy
286 T1rlineto = _T1rlineto()
289 class _T1rmoveto(T1cmd):
291 def __init__(self):
292 T1cmd.__init__(self, 21)
294 def __str__(self):
295 return "rmoveto"
297 def updatepath(self, path, trafo, context):
298 dx = context.t1stack.pop(0)
299 dy = context.t1stack.pop(0)
300 path.append(moveto_pt(*trafo.apply_pt(context.x + dx, context.y + dy)))
301 context.x += dx
302 context.y += dy
304 T1rmoveto = _T1rmoveto()
307 class _T1rrcurveto(T1cmd):
309 def __init__(self):
310 T1cmd.__init__(self, 8)
312 def __str__(self):
313 return "rrcurveto"
315 def updatepath(self, path, trafo, context):
316 dx1 = context.t1stack.pop(0)
317 dy1 = context.t1stack.pop(0)
318 dx2 = context.t1stack.pop(0)
319 dy2 = context.t1stack.pop(0)
320 dx3 = context.t1stack.pop(0)
321 dy3 = context.t1stack.pop(0)
322 path.append(curveto_pt(*(trafo.apply_pt(context.x + dx1, context.y + dy1) +
323 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy1 + dy2) +
324 trafo.apply_pt(context.x + dx1 + dx2 + dx3, context.y + dy1 + dy2 + dy3))))
325 context.x += dx1+dx2+dx3
326 context.y += dy1+dy2+dy3
328 T1rrcurveto = _T1rrcurveto()
331 class _T1vlineto(T1cmd):
333 def __init__(self):
334 T1cmd.__init__(self, 7)
336 def __str__(self):
337 return "vlineto"
339 def updatepath(self, path, trafo, context):
340 dy = context.t1stack.pop(0)
341 path.append(lineto_pt(*trafo.apply_pt(context.x, context.y + dy)))
342 context.y += dy
344 T1vlineto = _T1vlineto()
347 class _T1vmoveto(T1cmd):
349 def __init__(self):
350 T1cmd.__init__(self, 4)
352 def __str__(self):
353 return "vmoveto"
355 def updatepath(self, path, trafo, context):
356 dy = context.t1stack.pop(0)
357 path.append(moveto_pt(*trafo.apply_pt(context.x, context.y + dy)))
358 context.y += dy
360 T1vmoveto = _T1vmoveto()
363 class _T1vhcurveto(T1cmd):
365 def __init__(self):
366 T1cmd.__init__(self, 30)
368 def __str__(self):
369 return "vhcurveto"
371 def updatepath(self, path, trafo, context):
372 dy1 = context.t1stack.pop(0)
373 dx2 = context.t1stack.pop(0)
374 dy2 = context.t1stack.pop(0)
375 dx3 = context.t1stack.pop(0)
376 path.append(curveto_pt(*(trafo.apply_pt(context.x, context.y + dy1) +
377 trafo.apply_pt(context.x + dx2, context.y + dy1 + dy2) +
378 trafo.apply_pt(context.x + dx2 + dx3, context.y + dy1 + dy2))))
379 context.x += dx2+dx3
380 context.y += dy1+dy2
382 T1vhcurveto = _T1vhcurveto()
385 # hint commands
387 class _T1dotsection(T1cmd):
389 def __init__(self):
390 T1cmd.__init__(self, 0, subcmd=1)
392 def __str__(self):
393 return "dotsection"
395 def updatepath(self, path, trafo, context):
396 pass
398 T1dotsection = _T1dotsection()
401 class _T1hstem(T1cmd):
403 def __init__(self):
404 T1cmd.__init__(self, 1)
406 def __str__(self):
407 return "hstem"
409 def updatepath(self, path, trafo, context):
410 y = context.t1stack.pop(0)
411 dy = context.t1stack.pop(0)
413 T1hstem = _T1hstem()
416 class _T1hstem3(T1cmd):
418 def __init__(self):
419 T1cmd.__init__(self, 2, subcmd=1)
421 def __str__(self):
422 return "hstem3"
424 def updatepath(self, path, trafo, context):
425 y0 = context.t1stack.pop(0)
426 dy0 = context.t1stack.pop(0)
427 y1 = context.t1stack.pop(0)
428 dy1 = context.t1stack.pop(0)
429 y2 = context.t1stack.pop(0)
430 dy2 = context.t1stack.pop(0)
432 T1hstem3 = _T1hstem3()
435 class _T1vstem(T1cmd):
437 def __init__(self):
438 T1cmd.__init__(self, 3)
440 def __str__(self):
441 return "vstem"
443 def updatepath(self, path, trafo, context):
444 x = context.t1stack.pop(0)
445 dx = context.t1stack.pop(0)
447 T1vstem = _T1vstem()
450 class _T1vstem3(T1cmd):
452 def __init__(self):
453 T1cmd.__init__(self, 1, subcmd=1)
455 def __str__(self):
456 return "vstem3"
458 def updatepath(self, path, trafo, context):
459 self.x0 = context.t1stack.pop(0)
460 self.dx0 = context.t1stack.pop(0)
461 self.x1 = context.t1stack.pop(0)
462 self.dx1 = context.t1stack.pop(0)
463 self.x2 = context.t1stack.pop(0)
464 self.dx2 = context.t1stack.pop(0)
466 T1vstem3 = _T1vstem3()
469 # arithmetic command
471 class _T1div(T1cmd):
473 def __init__(self):
474 T1cmd.__init__(self, 12, subcmd=1)
476 def __str__(self):
477 return "div"
479 def updatepath(self, path, trafo, context):
480 num2 = context.t1stack.pop()
481 num1 = context.t1stack.pop()
482 context.t1stack.append(divmod(num1, num2)[0])
484 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
485 num2 = context.t1stack.pop()
486 num1 = context.t1stack.pop()
487 context.t1stack.append(divmod(num1, num2)[0])
489 T1div = _T1div()
492 # subroutine commands
494 class _T1callothersubr(T1cmd):
496 def __init__(self):
497 T1cmd.__init__(self, 16, subcmd=1)
499 def __str__(self):
500 return "callothersubr"
502 def updatepath(self, path, trafo, context):
503 othersubrnumber = context.t1stack.pop()
504 n = context.t1stack.pop()
505 for i in range(n):
506 context.psstack.append(context.t1stack.pop())
508 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
509 othersubrnumber = context.t1stack.pop()
510 othersubrs[othersubrnumber] = 1
511 n = context.t1stack.pop()
512 for i in range(n):
513 context.psstack.append(context.t1stack.pop())
515 T1callothersubr = _T1callothersubr()
518 class _T1callsubr(T1cmd):
520 def __init__(self):
521 T1cmd.__init__(self, 10)
523 def __str__(self):
524 return "callsubr"
526 def updatepath(self, path, trafo, context):
527 subr = context.t1stack.pop()
528 context.t1font.updatesubrpath(subr, path, trafo, context)
530 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
531 subr = context.t1stack.pop()
532 subrs[subr] = 1
533 context.t1font.gathersubrcalls(subr, seacglyphs, subrs, othersubrs, context)
535 T1callsubr = _T1callsubr()
538 class _T1pop(T1cmd):
540 def __init__(self):
541 T1cmd.__init__(self, 17, subcmd=1)
543 def __str__(self):
544 return "pop"
546 def updatepath(self, path, trafo, context):
547 context.t1stack.append(context.psstack.pop())
549 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
550 context.t1stack.append(context.psstack.pop())
552 T1pop = _T1pop()
555 class _T1return(T1cmd):
557 def __init__(self):
558 T1cmd.__init__(self, 11)
560 def __str__(self):
561 return "return"
563 def updatepath(self, path, trafo, context):
564 pass
566 T1return = _T1return()
569 class _T1setcurrentpoint(T1cmd):
571 def __init__(self):
572 T1cmd.__init__(self, 33, subcmd=1)
574 def __str__(self):
575 return "setcurrentpoint" % self.x, self.y
577 def updatepath(self, path, trafo, context):
578 x = context.t1stack.pop(0)
579 y = context.t1stack.pop(0)
580 path.append(moveto_pt(*trafo.apply_pt(x, y)))
581 context.x = x
582 context.y = y
584 T1setcurrentpoint = _T1setcurrentpoint()
587 ######################################################################
589 class cursor:
590 """cursor to read a string token by token"""
592 def __init__(self, data, startstring, eattokensep=1, tokenseps=" \t\r\n", tokenstarts="()<>[]{}/%"):
593 """creates a cursor for the string data
595 startstring is a string at which the cursor should start at. The first
596 ocurance of startstring is used. When startstring is not in data, an
597 exception is raised, otherwise the cursor is set to the position right
598 after the startstring. When eattokenseps is set, startstring must be
599 followed by a tokensep and this first tokensep is also consumed.
600 tokenseps is a string containing characters to be used as token
601 separators. tokenstarts is a string containing characters which
602 directly (even without intermediate token separator) start a new token.
604 self.data = data
605 self.pos = self.data.index(startstring) + len(startstring)
606 self.tokenseps = tokenseps
607 self.tokenstarts = tokenstarts
608 if eattokensep:
609 if self.data[self.pos] not in self.tokenstarts:
610 if self.data[self.pos] not in self.tokenseps:
611 raise ValueError("cursor initialization string is not followed by a token separator")
612 self.pos += 1
614 def gettoken(self):
615 """get the next token
617 Leading token separators and comments are silently consumed. The first token
618 separator after the token is also silently consumed."""
619 while self.data[self.pos] in self.tokenseps:
620 self.pos += 1
621 # ignore comments including subsequent whitespace characters
622 while self.data[self.pos] == "%":
623 while self.data[self.pos] not in "\r\n":
624 self.pos += 1
625 while self.data[self.pos] in self.tokenseps:
626 self.pos += 1
627 startpos = self.pos
628 while self.data[self.pos] not in self.tokenseps:
629 # any character in self.tokenstarts ends the token
630 if self.pos>startpos and self.data[self.pos] in self.tokenstarts:
631 break
632 self.pos += 1
633 result = self.data[startpos:self.pos]
634 if self.data[self.pos] in self.tokenseps:
635 self.pos += 1 # consume a single tokensep
636 return result
638 def getint(self):
639 """get the next token as an integer"""
640 return int(self.gettoken())
642 def getbytes(self, count):
643 """get the next count bytes"""
644 startpos = self.pos
645 self.pos += count
646 return self.data[startpos: self.pos]
649 class T1font:
651 eexecr = 55665
652 charstringr = 4330
654 def __init__(self, data1, data2eexec, data3):
655 """initializes a t1font instance
657 data1 and data3 are the two clear text data parts and data2 is
658 the binary data part"""
659 self.data1 = data1
660 self._data2eexec = data2eexec
661 self.data3 = data3
663 # marker and value for decoded data
664 self._data2 = None
665 # note that data2eexec is set to none by setsubrcmds and setglyphcmds
666 # this *also* denotes, that data2 is out-of-date; hence they are both
667 # marked by an _ and getdata2 and getdata2eexec will properly resolve
668 # the current state of decoding ...
670 # marker and value for standard encoding check
671 self.encoding = None
673 def _eexecdecode(self, code):
674 """eexec decoding of code"""
675 return decoder(code, self.eexecr, 4)
677 def _charstringdecode(self, code):
678 """charstring decoding of code"""
679 return decoder(code, self.charstringr, self.lenIV)
681 def _eexecencode(self, data):
682 """eexec encoding of data"""
683 return encoder(data, self.eexecr, "PyX!")
685 def _charstringencode(self, data):
686 """eexec encoding of data"""
687 return encoder(data, self.charstringr, "PyX!"[:self.lenIV])
689 lenIVpattern = re.compile("/lenIV\s+(\d+)\s+def\s+")
690 flexhintsubrs = [[3, 0, T1callothersubr, T1pop, T1pop, T1setcurrentpoint, T1return],
691 [0, 1, T1callothersubr, T1return],
692 [0, 2, T1callothersubr, T1return],
693 [T1return]]
695 def _encoding(self):
696 """helper method to lookup the encoding in the font"""
697 c = cursor(self.data1, "/Encoding")
698 token1 = c.gettoken()
699 token2 = c.gettoken()
700 if token1 == "StandardEncoding" and token2 == "def":
701 self.encoding = encoding.adobestandardencoding
702 else:
703 encvector = [None]*256
704 while 1:
705 self.encodingstart = c.pos
706 if c.gettoken() == "dup":
707 break
708 while 1:
709 i = c.getint()
710 glyph = c.gettoken()
711 if 0 <= i < 256:
712 encvector[i] = glyph[1:]
713 token = c.gettoken(); assert token == "put"
714 self.encodingend = c.pos
715 token = c.gettoken()
716 if token == "readonly" or token == "def":
717 break
718 assert token == "dup"
719 self.encoding = encoding.encoding(encvector)
721 def _data2decode(self):
722 """decodes data2eexec to the data2 string and the subr and glyphs dictionary
724 It doesn't make sense to call this method twice -- check the content of
725 data2 before calling. The method also keeps the subrs and charstrings
726 start and end positions for later use."""
727 self._data2 = self._eexecdecode(self._data2eexec)
729 m = self.lenIVpattern.search(self._data2)
730 if m:
731 self.lenIV = int(m.group(1))
732 else:
733 self.lenIV = 4
734 self.emptysubr = self._charstringencode(chr(11))
736 # extract Subrs
737 c = cursor(self._data2, "/Subrs")
738 self.subrsstart = c.pos
739 arraycount = c.getint()
740 token = c.gettoken(); assert token == "array"
741 self.subrs = []
742 for i in range(arraycount):
743 token = c.gettoken(); assert token == "dup"
744 token = c.getint(); assert token == i
745 size = c.getint()
746 if not i:
747 self.subrrdtoken = c.gettoken()
748 else:
749 token = c.gettoken(); assert token == self.subrrdtoken
750 self.subrs.append(c.getbytes(size))
751 token = c.gettoken()
752 if token == "noaccess":
753 token = "%s %s" % (token, c.gettoken())
754 if not i:
755 self.subrnptoken = token
756 else:
757 assert token == self.subrnptoken
758 self.subrsend = c.pos
760 # hasflexhintsubrs is a boolean indicating that the font uses flex or
761 # hint replacement subrs as specified by Adobe (tm). When it does, the
762 # first 4 subrs should all be copied except when none of them are used
763 # in the stripped version of the font since we than get a font not
764 # using flex or hint replacement subrs at all.
765 self.hasflexhintsubrs = (arraycount >= len(self.flexhintsubrs) and
766 [self.getsubrcmds(i)
767 for i in range(len(self.flexhintsubrs))] == self.flexhintsubrs)
769 # extract glyphs
770 self.glyphs = {}
771 self.glyphlist = [] # we want to keep the order of the glyph names
772 c = cursor(self._data2, "/CharStrings")
773 self.charstringsstart = c.pos
774 c.getint()
775 token = c.gettoken(); assert token == "dict"
776 token = c.gettoken(); assert token == "dup"
777 token = c.gettoken(); assert token == "begin"
778 first = 1
779 while 1:
780 chartoken = c.gettoken()
781 if chartoken == "end":
782 break
783 assert chartoken[0] == "/"
784 size = c.getint()
785 if first:
786 self.glyphrdtoken = c.gettoken()
787 else:
788 token = c.gettoken(); assert token == self.glyphrdtoken
789 self.glyphlist.append(chartoken[1:])
790 self.glyphs[chartoken[1:]] = c.getbytes(size)
791 if first:
792 self.glyphndtoken = c.gettoken()
793 else:
794 token = c.gettoken(); assert token == self.glyphndtoken
795 first = 0
796 self.charstringsend = c.pos
797 assert not self.subrs or self.subrrdtoken == self.glyphrdtoken
799 def _cmds(self, code):
800 """return a list of T1cmd's for encoded charstring data in code"""
801 code = array.array("B", self._charstringdecode(code))
802 cmds = []
803 while code:
804 x = code.pop(0)
805 if x == 12: # this starts an escaped cmd
806 cmds.append(T1subcmds[code.pop(0)])
807 elif 0 <= x < 32: # those are cmd's
808 cmds.append(T1cmds[x])
809 elif 32 <= x <= 246: # short ints
810 cmds.append(x-139)
811 elif 247 <= x <= 250: # mid size ints
812 cmds.append(((x - 247)*256) + code.pop(0) + 108)
813 elif 251 <= x <= 254: # mid size ints
814 cmds.append(-((x - 251)*256) - code.pop(0) - 108)
815 else: # x = 255, i.e. full size ints
816 y = ((code.pop(0)*256l+code.pop(0))*256+code.pop(0))*256+code.pop(0)
817 if y > (1l << 31):
818 cmds.append(y - (1l << 32))
819 else:
820 cmds.append(y)
821 return cmds
823 def _code(self, cmds):
824 """return an encoded charstring data for list of T1cmd's in cmds"""
825 code = array.array("B")
826 for cmd in cmds:
827 try:
828 if cmd.subcmd:
829 code.append(12)
830 code.append(cmd.code)
831 except AttributeError:
832 if -107 <= cmd <= 107:
833 code.append(cmd+139)
834 elif 108 <= cmd <= 1131:
835 a, b = divmod(cmd-108, 256)
836 code.append(a+247)
837 code.append(b)
838 elif -1131 <= cmd <= -108:
839 a, b = divmod(-cmd-108, 256)
840 code.append(a+251)
841 code.append(b)
842 else:
843 if cmd < 0:
844 cmd += 1l << 32
845 cmd, x4 = divmod(cmd, 256)
846 cmd, x3 = divmod(cmd, 256)
847 x1, x2 = divmod(cmd, 256)
848 code.append(255)
849 code.append(x1)
850 code.append(x2)
851 code.append(x3)
852 code.append(x4)
853 return self._charstringencode(code.tostring())
855 def getsubrcmds(self, subr):
856 """return a list of T1cmd's for subr subr"""
857 if not self._data2:
858 self._data2decode()
859 return self._cmds(self.subrs[subr])
861 def getglyphcmds(self, glyph):
862 """return a list of T1cmd's for glyph glyph"""
863 if not self._data2:
864 self._data2decode()
865 return self._cmds(self.glyphs[glyph])
867 def setsubrcmds(self, subr, cmds):
868 """replaces the T1cmd's by the list cmds for subr subr"""
869 if not self._data2:
870 self._data2decode()
871 self._data2eexec = None
872 self.subrs[subr] = self._code(cmds)
874 def setglyphcmds(self, glyph, cmds):
875 """replaces the T1cmd's by the list cmds for glyph glyph"""
876 if not self._data2:
877 self._data2decode()
878 self._data2eexec = None
879 self.glyphs[glyph] = self._code(cmds)
881 def updatepath(self, cmds, path, trafo, context):
882 for cmd in cmds:
883 if isinstance(cmd, T1cmd):
884 cmd.updatepath(path, trafo, context)
885 else:
886 context.t1stack.append(cmd)
888 def updatesubrpath(self, subr, path, trafo, context):
889 self.updatepath(self.getsubrcmds(subr), path, trafo, context)
891 def updateglyphpath(self, glyph, path, trafo, context):
892 self.updatepath(self.getglyphcmds(glyph), path, trafo, context)
894 def gathercalls(self, cmds, seacglyphs, subrs, othersubrs, context):
895 for cmd in cmds:
896 if isinstance(cmd, T1cmd):
897 cmd.gathercalls(seacglyphs, subrs, othersubrs, context)
898 else:
899 context.t1stack.append(cmd)
901 def gathersubrcalls(self, subr, seacglyphs, subrs, othersubrs, context):
902 self.gathercalls(self.getsubrcmds(subr), seacglyphs, subrs, othersubrs, context)
904 def gatherglyphcalls(self, glyph, seacglyphs, subrs, othersubrs, context):
905 self.gathercalls(self.getglyphcmds(glyph), seacglyphs, subrs, othersubrs, context)
907 fontmatrixpattern = re.compile("/FontMatrix\s*\[\s*(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)\s*\]\s*(readonly\s+)?def")
909 def getglyphpathwxwy_pt(self, glyph, size):
910 m = self.fontmatrixpattern.search(self.data1)
911 m11, m12, m21, m22, v1, v2 = map(float, m.groups()[:6])
912 t = trafo.trafo_pt(matrix=((m11, m12), (m21, m22)), vector=(v1, v2)).scaled(size)
913 context = T1context(self)
914 p = path()
915 self.updateglyphpath(glyph, p, t, context)
916 wx, wy = t.apply_pt(context.wx, context.wy)
917 return p, wx, wy
919 def getglyphpath(self, glyph, size):
920 """return a PyX path for glyph named glyph"""
921 return self.getglyphpathwxwy_pt(glyph, size)[0]
923 def getglyphwxwy_pt(self, glyph, size):
924 return self.getglyphpathwxwy_pt(glyph, size)[1:]
926 def getdata2(self, subrs=None, glyphs=None):
927 """makes a data2 string
929 subrs is a dict containing those subrs numbers as keys,
930 which are to be contained in the subrsstring to be created.
931 If subrs is None, all subrs in self.subrs will be used.
932 The subrs dict might be modified *in place*.
934 glyphs is a dict containing those glyph names as keys,
935 which are to be contained in the charstringsstring to be created.
936 If glyphs is None, all glyphs in self.glyphs will be used."""
937 def addsubrs(subrs, result):
938 if subrs is not None:
939 # some adjustments to the subrs dict
940 if subrs:
941 subrsindices = subrs.keys()
942 subrsmin = min(subrsindices)
943 subrsmax = max(subrsindices)
944 if self.hasflexhintsubrs and subrsmin < len(self.flexhintsubrs):
945 # According to the spec we need to keep all the flex and hint subrs
946 # as long as any of it is used.
947 for subr in range(len(self.flexhintsubrs)):
948 subrs[subr] = 1
949 else:
950 subrsmax = -1
951 else:
952 # build a new subrs dict containing all subrs
953 subrs = dict([(subr, 1) for subr in range(len(self.subrs))])
954 subrsmax = len(self.subrs) - 1
956 # build the string from all selected subrs
957 result.append("%d array\n" % (subrsmax + 1))
958 for subr in range(subrsmax+1):
959 if subrs.has_key(subr):
960 code = self.subrs[subr]
961 else:
962 code = self.emptysubr
963 result.append("dup %d %d %s %s %s\n" % (subr, len(code), self.subrrdtoken, code, self.subrnptoken))
965 def addcharstrings(glyphs, result):
966 result.append("%d dict dup begin\n" % (glyphs is None and len(self.glyphlist) or len(glyphs)))
967 for glyph in self.glyphlist:
968 if glyphs is None or glyphs.has_key(glyph):
969 result.append("/%s %d %s %s %s\n" % (glyph, len(self.glyphs[glyph]), self.glyphrdtoken, self.glyphs[glyph], self.glyphndtoken))
970 result.append("end\n")
972 if self.subrsstart < self.charstringsstart:
973 result = [self._data2[:self.subrsstart]]
974 addsubrs(subrs, result)
975 result.append(self._data2[self.subrsend:self.charstringsstart])
976 addcharstrings(glyphs, result)
977 result.append(self._data2[self.charstringsend:])
978 else:
979 result = [self._data2[:self.charstringsstart]]
980 addcharstrings(glyphs, result)
981 result.append(self._data2[self.charstringsend:self.subrsstart])
982 addsubrs(subrs, result)
983 result.append(self._data2[self.subrsend:])
984 return "".join(result)
986 def getdata2eexec(self):
987 if self._data2eexec:
988 return self._data2eexec
989 # note that self._data2 is out-of-date here too, hence we need to call getdata2
990 return self._eexecencode(self.getdata2())
992 newlinepattern = re.compile("\s*[\r\n]\s*")
993 uniqueidpattern = re.compile("/UniqueID\s+\d+\s+def\s+")
995 def getstrippedfont(self, glyphs):
996 """create a T1font instance containing only certain glyphs
998 glyphs is a dict having the glyph names to be contained as keys.
999 The glyphs dict might be modified *in place*.
1001 # TODO: we could also strip othersubrs to those actually used
1003 # collect information about used glyphs and subrs
1004 seacglyphs = {}
1005 subrs = {}
1006 othersubrs = {}
1007 for glyph in glyphs.keys():
1008 self.gatherglyphcalls(glyph, seacglyphs, subrs, othersubrs, T1context(self))
1009 # while we have gathered all subrs for the seacglyphs alreadys, we
1010 # might have missed the glyphs themself (when they are not used stand-alone)
1011 glyphs.update(seacglyphs)
1012 glyphs[".notdef"] = 1
1014 # strip data1
1015 if not self.encoding:
1016 self._encoding()
1017 if self.encoding is encoding.adobestandardencoding:
1018 data1 = self.data1
1019 else:
1020 encodingstrings = []
1021 for char, glyph in enumerate(self.encoding.encvector):
1022 if glyph in glyphs.keys():
1023 encodingstrings.append("dup %i /%s put\n" % (char, glyph))
1024 data1 = self.data1[:self.encodingstart] + "".join(encodingstrings) + self.data1[self.encodingend:]
1025 data1 = self.newlinepattern.subn("\n", data1)[0]
1026 data1 = self.uniqueidpattern.subn("", data1)[0]
1028 # strip data2
1029 data2 = self.uniqueidpattern.subn("", self.getdata2(subrs, glyphs))[0]
1031 # strip data3
1032 data3 = self.newlinepattern.subn("\n", self.data3)[0]
1034 # create and return the new font instance
1035 return T1font(data1.rstrip() + "\n", self._eexecencode(data2), data3.rstrip() + "\n")
1037 def getflags(self):
1038 # As a simple heuristics we assume non-symbolic fonts if and only
1039 # if the Adobe standard encoding is used. All other font flags are
1040 # not specified here.
1041 if not self.encoding:
1042 self._encoding()
1043 if self.encoding is encoding.adobestandardencoding:
1044 return 32
1045 return 4
1047 def outputPFA(self, file):
1048 """output the T1font in PFA format"""
1049 file.write(self.data1)
1050 data2eexechex = binascii.b2a_hex(self.getdata2eexec())
1051 linelength = 64
1052 for i in range((len(data2eexechex)-1)/linelength + 1):
1053 file.write(data2eexechex[i*linelength: i*linelength+linelength])
1054 file.write("\n")
1055 file.write(self.data3)
1057 def outputPFB(self, file):
1058 """output the T1font in PFB format"""
1059 data2eexec = self.getdata2eexec()
1060 def pfblength(data):
1061 l = len(data)
1062 l, x1 = divmod(l, 256)
1063 l, x2 = divmod(l, 256)
1064 x4, x3 = divmod(l, 256)
1065 return chr(x1) + chr(x2) + chr(x3) + chr(x4)
1066 file.write("\200\1")
1067 file.write(pfblength(self.data1))
1068 file.write(self.data1)
1069 file.write("\200\2")
1070 file.write(pfblength(data2eexec))
1071 file.write(data2eexec)
1072 file.write("\200\1")
1073 file.write(pfblength(self.data3))
1074 file.write(self.data3)
1075 file.write("\200\3")
1077 def outputPS(self, file, writer):
1078 """output the PostScript code for the T1font to the file file"""
1079 self.outputPFA(file)
1081 def outputPDF(self, file, writer):
1082 data2eexec = self.getdata2eexec()
1083 data3 = self.data3
1084 # we might be allowed to skip the third part ...
1085 if (data3.replace("\n", "")
1086 .replace("\r", "")
1087 .replace("\t", "")
1088 .replace(" ", "")) == "0"*512 + "cleartomark":
1089 data3 = ""
1091 data = self.data1 + data2eexec + data3
1092 if writer.compress and haszlib:
1093 data = zlib.compress(data)
1095 file.write("<<\n"
1096 "/Length %d\n"
1097 "/Length1 %d\n"
1098 "/Length2 %d\n"
1099 "/Length3 %d\n" % (len(data), len(self.data1), len(data2eexec), len(data3)))
1100 if writer.compress and haszlib:
1101 file.write("/Filter /FlateDecode\n")
1102 file.write(">>\n"
1103 "stream\n")
1104 file.write(data)
1105 file.write("\n"
1106 "endstream\n")
1109 class T1pfafont(T1font):
1111 """create a T1font instance from a pfa font file"""
1113 def __init__(self, filename):
1114 d = open(filename, "rb").read()
1115 # hey, that's quick'n'dirty
1116 m1 = d.index("eexec") + 6
1117 m2 = d.index("0"*40)
1118 data1 = d[:m1]
1119 data2 = binascii.a2b_hex(d[m1: m2].replace(" ", "").replace("\r", "").replace("\n", ""))
1120 data3 = d[m2:]
1121 T1font.__init__(self, data1, data2, data3)
1124 class T1pfbfont(T1font):
1126 """create a T1font instance from a pfb font file"""
1128 def __init__(self, filename):
1129 def pfblength(s):
1130 if len(s) != 4:
1131 raise ValueError("invalid string length")
1132 return (ord(s[0]) +
1133 ord(s[1])*256 +
1134 ord(s[2])*256*256 +
1135 ord(s[3])*256*256*256)
1136 f = open(filename, "rb")
1137 mark = f.read(2); assert mark == "\200\1"
1138 data1 = f.read(pfblength(f.read(4)))
1139 mark = f.read(2); assert mark == "\200\2"
1140 data2 = ""
1141 while mark == "\200\2":
1142 data2 = data2 + f.read(pfblength(f.read(4)))
1143 mark = f.read(2)
1144 assert mark == "\200\1"
1145 data3 = f.read(pfblength(f.read(4)))
1146 mark = f.read(2); assert mark == "\200\3"
1147 assert not f.read(1)
1148 T1font.__init__(self, data1, data2, data3)