some code reorganization and cleanup
[PyX/mjg.git] / pyx / font / t1font.py
blobfb6f20e3691b182da77070e7fcd71645b002abf6
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 from pyx import trafo
34 from pyx.path import path, moveto_pt, lineto_pt, curveto_pt, closepath
35 import encoding
37 try:
38 from _t1code import *
39 except:
40 from t1code import *
43 class T1context:
45 def __init__(self, t1font):
46 """context for T1cmd evaluation"""
47 self.t1font = t1font
49 # state description
50 self.x = None
51 self.y = None
52 self.wx = None
53 self.wy = None
54 self.t1stack = []
55 self.psstack = []
58 ######################################################################
59 # T1 commands
60 # Note, that the T1 commands are variable-free except for plain number,
61 # which are stored as integers. All other T1 commands exist as a single
62 # instance only
64 T1cmds = {}
65 T1subcmds = {}
67 class T1cmd:
69 def __init__(self, code, subcmd=False):
70 self.code = code
71 self.subcmd = subcmd
72 if subcmd:
73 T1subcmds[code] = self
74 else:
75 T1cmds[code] = self
77 def __str__(self):
78 """returns a string representation of the T1 command"""
79 raise NotImplementedError
81 def updatepath(self, path, trafo, context):
82 """update path instance applying trafo to the points"""
83 raise NotImplementedError
85 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
86 """gather dependancy information
88 subrs is the "called-subrs" dictionary. gathercalls will insert the
89 subr number as key having the value 1, i.e. subrs.keys() will become the
90 numbers of used subrs. Similar seacglyphs will contain all glyphs in
91 composite characters (subrs and othersubrs for those glyphs will also
92 already be included) and othersubrs the othersubrs called.
94 This method might will not properly update all information in the
95 context (especially consuming values from the stack) and will also skip
96 various tests for performance reasons. For most T1 commands it just
97 doesn't need to do anything.
98 """
99 pass
102 # commands for starting and finishing
104 class _T1endchar(T1cmd):
106 def __init__(self):
107 T1cmd.__init__(self, 14)
109 def __str__(self):
110 return "endchar"
112 def updatepath(self, path, trafo, context):
113 pass
115 T1endchar = _T1endchar()
118 class _T1hsbw(T1cmd):
120 def __init__(self):
121 T1cmd.__init__(self, 13)
123 def __str__(self):
124 return "hsbw"
126 def updatepath(self, path, trafo, context):
127 sbx = context.t1stack.pop(0)
128 wx = context.t1stack.pop(0)
129 path.append(moveto_pt(*trafo.apply_pt(sbx, 0)))
130 context.x = sbx
131 context.y = 0
132 context.wx = wx
133 context.wy = 0
135 T1hsbw = _T1hsbw()
138 class _T1seac(T1cmd):
140 def __init__(self):
141 T1cmd.__init__(self, 6, subcmd=True)
143 def __str__(self):
144 return "seac"
146 def updatepath(self, path, atrafo, context):
147 sab = context.t1stack.pop(0)
148 adx = context.t1stack.pop(0)
149 ady = context.t1stack.pop(0)
150 bchar = context.t1stack.pop(0)
151 achar = context.t1stack.pop(0)
152 context.t1font.updateglyphpath(bglyph, path, atrafo, context)
153 atrafo = atrafo * trafo.translate_pt(adx-sab, ady)
154 context.t1font.updateglyphpath(aglyph, path, atrafo, context)
156 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
157 bchar = context.t1stack.pop()
158 achar = context.t1stack.pop()
159 aglyph = encoding.adobestandardencoding.decode(achar)
160 bglyph = encoding.adobestandardencoding.decode(bchar)
161 seacglyphs[aglyph] = 1
162 seacglyphs[bglyph] = 1
163 context.t1font.gatherglyphcalls(bglyph, seacglyphs, subrs, othersubrs, context)
164 context.t1font.gatherglyphcalls(aglyph, seacglyphs, subrs, othersubrs, context)
166 T1seac = _T1seac()
169 class _T1sbw(T1cmd):
171 def __init__(self):
172 T1cmd.__init__(self, 7, subcmd=True)
174 def __str__(self):
175 return "sbw"
177 def updatepath(self, path, trafo, context):
178 sbx = context.t1stack.pop(0)
179 sby = context.t1stack.pop(0)
180 wx = context.t1stack.pop(0)
181 wy = context.t1stack.pop(0)
182 path.append(moveto_pt(*trafo.apply_pt(sbx, sby)))
183 context.x = sbx
184 context.y = sby
185 context.wx = wx
186 context.wy = wy
188 T1sbw = _T1sbw()
191 # path construction commands
193 class _T1closepath(T1cmd):
195 def __init__(self):
196 T1cmd.__init__(self, 9)
198 def __str__(self):
199 return "closepath"
201 def updatepath(self, path, trafo, context):
202 path.append(closepath())
203 # The closepath in T1 is different from PostScripts in that it does
204 # *not* modify the current position; hence we need to add an additional
205 # moveto here ...
206 path.append(moveto_pt(*trafo.apply_pt(context.x, context.y)))
208 T1closepath = _T1closepath()
211 class _T1hlineto(T1cmd):
213 def __init__(self):
214 T1cmd.__init__(self, 6)
216 def __str__(self):
217 return "hlineto"
219 def updatepath(self, path, trafo, context):
220 dx = context.t1stack.pop(0)
221 path.append(lineto_pt(*trafo.apply_pt(context.x + dx, context.y)))
222 context.x += dx
224 T1hlineto = _T1hlineto()
227 class _T1hmoveto(T1cmd):
229 def __init__(self):
230 T1cmd.__init__(self, 22)
232 def __str__(self):
233 return "hmoveto"
235 def updatepath(self, path, trafo, context):
236 dx = context.t1stack.pop(0)
237 path.append(moveto_pt(*trafo.apply_pt(context.x + dx, context.y)))
238 context.x += dx
240 T1hmoveto = _T1hmoveto()
243 class _T1hvcurveto(T1cmd):
245 def __init__(self):
246 T1cmd.__init__(self, 31)
248 def __str__(self):
249 return "hvcurveto"
251 def updatepath(self, path, trafo, context):
252 dx1 = context.t1stack.pop(0)
253 dx2 = context.t1stack.pop(0)
254 dy2 = context.t1stack.pop(0)
255 dy3 = context.t1stack.pop(0)
256 path.append(curveto_pt(*(trafo.apply_pt(context.x + dx1, context.y) +
257 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy2) +
258 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy2 + dy3))))
259 context.x += dx1+dx2
260 context.y += dy2+dy3
262 T1hvcurveto = _T1hvcurveto()
265 class _T1rlineto(T1cmd):
267 def __init__(self):
268 T1cmd.__init__(self, 5)
270 def __str__(self):
271 return "rlineto"
273 def updatepath(self, path, trafo, context):
274 dx = context.t1stack.pop(0)
275 dy = context.t1stack.pop(0)
276 path.append(lineto_pt(*trafo.apply_pt(context.x + dx, context.y + dy)))
277 context.x += dx
278 context.y += dy
280 T1rlineto = _T1rlineto()
283 class _T1rmoveto(T1cmd):
285 def __init__(self):
286 T1cmd.__init__(self, 21)
288 def __str__(self):
289 return "rmoveto"
291 def updatepath(self, path, trafo, context):
292 dx = context.t1stack.pop(0)
293 dy = context.t1stack.pop(0)
294 path.append(moveto_pt(*trafo.apply_pt(context.x + dx, context.y + dy)))
295 context.x += dx
296 context.y += dy
298 T1rmoveto = _T1rmoveto()
301 class _T1rrcurveto(T1cmd):
303 def __init__(self):
304 T1cmd.__init__(self, 8)
306 def __str__(self):
307 return "rrcurveto"
309 def updatepath(self, path, trafo, context):
310 dx1 = context.t1stack.pop(0)
311 dy1 = context.t1stack.pop(0)
312 dx2 = context.t1stack.pop(0)
313 dy2 = context.t1stack.pop(0)
314 dx3 = context.t1stack.pop(0)
315 dy3 = context.t1stack.pop(0)
316 path.append(curveto_pt(*(trafo.apply_pt(context.x + dx1, context.y + dy1) +
317 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy1 + dy2) +
318 trafo.apply_pt(context.x + dx1 + dx2 + dx3, context.y + dy1 + dy2 + dy3))))
319 context.x += dx1+dx2+dx3
320 context.y += dy1+dy2+dy3
322 T1rrcurveto = _T1rrcurveto()
325 class _T1vlineto(T1cmd):
327 def __init__(self):
328 T1cmd.__init__(self, 7)
330 def __str__(self):
331 return "vlineto"
333 def updatepath(self, path, trafo, context):
334 dy = context.t1stack.pop(0)
335 path.append(lineto_pt(*trafo.apply_pt(context.x, context.y + dy)))
336 context.y += dy
338 T1vlineto = _T1vlineto()
341 class _T1vmoveto(T1cmd):
343 def __init__(self):
344 T1cmd.__init__(self, 4)
346 def __str__(self):
347 return "vmoveto"
349 def updatepath(self, path, trafo, context):
350 dy = context.t1stack.pop(0)
351 path.append(moveto_pt(*trafo.apply_pt(context.x, context.y + dy)))
352 context.y += dy
354 T1vmoveto = _T1vmoveto()
357 class _T1vhcurveto(T1cmd):
359 def __init__(self):
360 T1cmd.__init__(self, 30)
362 def __str__(self):
363 return "vhcurveto"
365 def updatepath(self, path, trafo, context):
366 dy1 = context.t1stack.pop(0)
367 dx2 = context.t1stack.pop(0)
368 dy2 = context.t1stack.pop(0)
369 dx3 = context.t1stack.pop(0)
370 path.append(curveto_pt(*(trafo.apply_pt(context.x, context.y + dy1) +
371 trafo.apply_pt(context.x + dx2, context.y + dy1 + dy2) +
372 trafo.apply_pt(context.x + dx2 + dx3, context.y + dy1 + dy2))))
373 context.x += dx2+dx3
374 context.y += dy1+dy2
376 T1vhcurveto = _T1vhcurveto()
379 # hint commands
381 class _T1dotsection(T1cmd):
383 def __init__(self):
384 T1cmd.__init__(self, 0, subcmd=True)
386 def __str__(self):
387 return "dotsection"
389 def updatepath(self, path, trafo, context):
390 pass
392 T1dotsection = _T1dotsection()
395 class _T1hstem(T1cmd):
397 def __init__(self):
398 T1cmd.__init__(self, 1)
400 def __str__(self):
401 return "hstem"
403 def updatepath(self, path, trafo, context):
404 y = context.t1stack.pop(0)
405 dy = context.t1stack.pop(0)
407 T1hstem = _T1hstem()
410 class _T1hstem3(T1cmd):
412 def __init__(self):
413 T1cmd.__init__(self, 2, subcmd=True)
415 def __str__(self):
416 return "hstem3"
418 def updatepath(self, path, trafo, context):
419 y0 = context.t1stack.pop(0)
420 dy0 = context.t1stack.pop(0)
421 y1 = context.t1stack.pop(0)
422 dy1 = context.t1stack.pop(0)
423 y2 = context.t1stack.pop(0)
424 dy2 = context.t1stack.pop(0)
426 T1hstem3 = _T1hstem3()
429 class _T1vstem(T1cmd):
431 def __init__(self):
432 T1cmd.__init__(self, 3)
434 def __str__(self):
435 return "vstem"
437 def updatepath(self, path, trafo, context):
438 x = context.t1stack.pop(0)
439 dx = context.t1stack.pop(0)
441 T1vstem = _T1vstem()
444 class _T1vstem3(T1cmd):
446 def __init__(self):
447 T1cmd.__init__(self, 1, subcmd=True)
449 def __str__(self):
450 return "vstem3"
452 def updatepath(self, path, trafo, context):
453 self.x0 = context.t1stack.pop(0)
454 self.dx0 = context.t1stack.pop(0)
455 self.x1 = context.t1stack.pop(0)
456 self.dx1 = context.t1stack.pop(0)
457 self.x2 = context.t1stack.pop(0)
458 self.dx2 = context.t1stack.pop(0)
460 T1vstem3 = _T1vstem3()
463 # arithmetic command
465 class _T1div(T1cmd):
467 def __init__(self):
468 T1cmd.__init__(self, 12, subcmd=True)
470 def __str__(self):
471 return "div"
473 def updatepath(self, path, trafo, context):
474 num2 = context.t1stack.pop()
475 num1 = context.t1stack.pop()
476 context.t1stack.append(divmod(num1, num2)[0])
478 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
479 num2 = context.t1stack.pop()
480 num1 = context.t1stack.pop()
481 context.t1stack.append(divmod(num1, num2)[0])
483 T1div = _T1div()
486 # subroutine commands
488 class _T1callothersubr(T1cmd):
490 def __init__(self):
491 T1cmd.__init__(self, 16, subcmd=True)
493 def __str__(self):
494 return "callothersubr"
496 def updatepath(self, path, trafo, context):
497 othersubrnumber = context.t1stack.pop()
498 n = context.t1stack.pop()
499 for i in range(n):
500 context.psstack.append(context.t1stack.pop())
502 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
503 othersubrnumber = context.t1stack.pop()
504 othersubrs[othersubrnumber] = 1
505 n = context.t1stack.pop()
506 for i in range(n):
507 context.psstack.append(context.t1stack.pop())
509 T1callothersubr = _T1callothersubr()
512 class _T1callsubr(T1cmd):
514 def __init__(self):
515 T1cmd.__init__(self, 10)
517 def __str__(self):
518 return "callsubr"
520 def updatepath(self, path, trafo, context):
521 subr = context.t1stack.pop()
522 context.t1font.updatesubrpath(subr, path, trafo, context)
524 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
525 subr = context.t1stack.pop()
526 subrs[subr] = 1
527 context.t1font.gathersubrcalls(subr, seacglyphs, subrs, othersubrs, context)
529 T1callsubr = _T1callsubr()
532 class _T1pop(T1cmd):
534 def __init__(self):
535 T1cmd.__init__(self, 17, subcmd=True)
537 def __str__(self):
538 return "pop"
540 def updatepath(self, path, trafo, context):
541 context.t1stack.append(context.psstack.pop())
543 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
544 context.t1stack.append(context.psstack.pop())
546 T1pop = _T1pop()
549 class _T1return(T1cmd):
551 def __init__(self):
552 T1cmd.__init__(self, 11)
554 def __str__(self):
555 return "return"
557 def updatepath(self, path, trafo, context):
558 pass
560 T1return = _T1return()
563 class _T1setcurrentpoint(T1cmd):
565 def __init__(self):
566 T1cmd.__init__(self, 33, subcmd=True)
568 def __str__(self):
569 return "setcurrentpoint" % self.x, self.y
571 def updatepath(self, path, trafo, context):
572 x = context.t1stack.pop(0)
573 y = context.t1stack.pop(0)
574 path.append(moveto_pt(*trafo.apply_pt(x, y)))
575 context.x = x
576 context.y = y
578 T1setcurrentpoint = _T1setcurrentpoint()
581 ######################################################################
583 class cursor:
584 """cursor to read a string token by token"""
586 def __init__(self, data, startstring, eattokensep=1, tokenseps=" \t\r\n", tokenstarts="()<>[]{}/%"):
587 """creates a cursor for the string data
589 startstring is a string at which the cursor should start at. The first
590 ocurance of startstring is used. When startstring is not in data, an
591 exception is raised, otherwise the cursor is set to the position right
592 after the startstring. When eattokenseps is set, startstring must be
593 followed by a tokensep and this first tokensep is also consumed.
594 tokenseps is a string containing characters to be used as token
595 separators. tokenstarts is a string containing characters which
596 directly (even without intermediate token separator) start a new token.
598 self.data = data
599 self.pos = self.data.index(startstring) + len(startstring)
600 self.tokenseps = tokenseps
601 self.tokenstarts = tokenstarts
602 if eattokensep:
603 if self.data[self.pos] not in self.tokenstarts:
604 if self.data[self.pos] not in self.tokenseps:
605 raise ValueError("cursor initialization string is not followed by a token separator")
606 self.pos += 1
608 def gettoken(self):
609 """get the next token
611 Leading token separators and comments are silently consumed. The first token
612 separator after the token is also silently consumed."""
613 while self.data[self.pos] in self.tokenseps:
614 self.pos += 1
615 # ignore comments including subsequent whitespace characters
616 while self.data[self.pos] == "%":
617 while self.data[self.pos] not in "\r\n":
618 self.pos += 1
619 while self.data[self.pos] in self.tokenseps:
620 self.pos += 1
621 startpos = self.pos
622 while self.data[self.pos] not in self.tokenseps:
623 # any character in self.tokenstarts ends the token
624 if self.pos>startpos and self.data[self.pos] in self.tokenstarts:
625 break
626 self.pos += 1
627 result = self.data[startpos:self.pos]
628 if self.data[self.pos] in self.tokenseps:
629 self.pos += 1 # consume a single tokensep
630 return result
632 def getint(self):
633 """get the next token as an integer"""
634 return int(self.gettoken())
636 def getbytes(self, count):
637 """get the next count bytes"""
638 startpos = self.pos
639 self.pos += count
640 return self.data[startpos: self.pos]
643 class T1font:
645 eexecr = 55665
646 charstringr = 4330
648 def __init__(self, data1, data2eexec, data3):
649 """initializes a t1font instance
651 data1 and data3 are the two clear text data parts and data2 is
652 the binary data part"""
653 self.data1 = data1
654 self._data2eexec = data2eexec
655 self.data3 = data3
657 # marker and value for decoded data
658 self._data2 = None
659 # note that data2eexec is set to none by setsubrcmds and setglyphcmds
660 # this *also* denotes, that data2 is out-of-date; hence they are both
661 # marked by an _ and getdata2 and getdata2eexec will properly resolve
662 # the current state of decoding ...
664 # marker and value for standard encoding check
665 self.encoding = None
667 def _eexecdecode(self, code):
668 """eexec decoding of code"""
669 return decoder(code, self.eexecr, 4)
671 def _charstringdecode(self, code):
672 """charstring decoding of code"""
673 return decoder(code, self.charstringr, self.lenIV)
675 def _eexecencode(self, data):
676 """eexec encoding of data"""
677 return encoder(data, self.eexecr, "PyX!")
679 def _charstringencode(self, data):
680 """eexec encoding of data"""
681 return encoder(data, self.charstringr, "PyX!"[:self.lenIV])
683 lenIVpattern = re.compile("/lenIV\s+(\d+)\s+def\s+")
684 flexhintsubrs = [[3, 0, T1callothersubr, T1pop, T1pop, T1setcurrentpoint, T1return],
685 [0, 1, T1callothersubr, T1return],
686 [0, 2, T1callothersubr, T1return],
687 [T1return]]
689 def _encoding(self):
690 """helper method to lookup the encoding in the font"""
691 c = cursor(self.data1, "/Encoding")
692 token1 = c.gettoken()
693 token2 = c.gettoken()
694 if token1 == "StandardEncoding" and token2 == "def":
695 self.encoding = encoding.adobestandardencoding
696 else:
697 encvector = [None]*256
698 while 1:
699 self.encodingstart = c.pos
700 if c.gettoken() == "dup":
701 break
702 while 1:
703 i = c.getint()
704 glyph = c.gettoken()
705 if 0 <= i < 256:
706 encvector[i] = glyph[1:]
707 token = c.gettoken(); assert token == "put"
708 self.encodingend = c.pos
709 token = c.gettoken()
710 if token == "readonly" or token == "def":
711 break
712 assert token == "dup"
713 self.encoding = encoding.encoding(encvector)
715 def _data2decode(self):
716 """decodes data2eexec to the data2 string and the subr and glyphs dictionary
718 It doesn't make sense to call this method twice -- check the content of
719 data2 before calling. The method also keeps the subrs and charstrings
720 start and end positions for later use."""
721 self._data2 = self._eexecdecode(self._data2eexec)
723 m = self.lenIVpattern.search(self._data2)
724 if m:
725 self.lenIV = int(m.group(1))
726 else:
727 self.lenIV = 4
728 self.emptysubr = self._charstringencode(chr(11))
730 # extract Subrs
731 c = cursor(self._data2, "/Subrs")
732 self.subrsstart = c.pos
733 arraycount = c.getint()
734 token = c.gettoken(); assert token == "array"
735 self.subrs = []
736 for i in range(arraycount):
737 token = c.gettoken(); assert token == "dup"
738 token = c.getint(); assert token == i
739 size = c.getint()
740 if not i:
741 self.subrrdtoken = c.gettoken()
742 else:
743 token = c.gettoken(); assert token == self.subrrdtoken
744 self.subrs.append(c.getbytes(size))
745 token = c.gettoken()
746 if token == "noaccess":
747 token = "%s %s" % (token, c.gettoken())
748 if not i:
749 self.subrnptoken = token
750 else:
751 assert token == self.subrnptoken
752 self.subrsend = c.pos
754 # hasflexhintsubrs is a boolean indicating that the font uses flex or
755 # hint replacement subrs as specified by Adobe (tm). When it does, the
756 # first 4 subrs should all be copied except when none of them are used
757 # in the stripped version of the font since we than get a font not
758 # using flex or hint replacement subrs at all.
759 self.hasflexhintsubrs = (arraycount >= len(self.flexhintsubrs) and
760 [self.getsubrcmds(i)
761 for i in range(len(self.flexhintsubrs))] == self.flexhintsubrs)
763 # extract glyphs
764 self.glyphs = {}
765 self.glyphlist = [] # we want to keep the order of the glyph names
766 c = cursor(self._data2, "/CharStrings")
767 self.charstringsstart = c.pos
768 c.getint()
769 token = c.gettoken(); assert token == "dict"
770 token = c.gettoken(); assert token == "dup"
771 token = c.gettoken(); assert token == "begin"
772 first = 1
773 while 1:
774 chartoken = c.gettoken()
775 if chartoken == "end":
776 break
777 assert chartoken[0] == "/"
778 size = c.getint()
779 if first:
780 self.glyphrdtoken = c.gettoken()
781 else:
782 token = c.gettoken(); assert token == self.glyphrdtoken
783 self.glyphlist.append(chartoken[1:])
784 self.glyphs[chartoken[1:]] = c.getbytes(size)
785 if first:
786 self.glyphndtoken = c.gettoken()
787 else:
788 token = c.gettoken(); assert token == self.glyphndtoken
789 first = 0
790 self.charstringsend = c.pos
791 assert not self.subrs or self.subrrdtoken == self.glyphrdtoken
793 def _cmds(self, code):
794 """return a list of T1cmd's for encoded charstring data in code"""
795 code = array.array("B", self._charstringdecode(code))
796 cmds = []
797 while code:
798 x = code.pop(0)
799 if x == 12: # this starts an escaped cmd
800 cmds.append(T1subcmds[code.pop(0)])
801 elif 0 <= x < 32: # those are cmd's
802 cmds.append(T1cmds[x])
803 elif 32 <= x <= 246: # short ints
804 cmds.append(x-139)
805 elif 247 <= x <= 250: # mid size ints
806 cmds.append(((x - 247)*256) + code.pop(0) + 108)
807 elif 251 <= x <= 254: # mid size ints
808 cmds.append(-((x - 251)*256) - code.pop(0) - 108)
809 else: # x = 255, i.e. full size ints
810 y = ((code.pop(0)*256+code.pop(0))*256+code.pop(0))*256+code.pop(0)
811 if y > (1l << 31):
812 cmds.append(y - (1l << 32))
813 else:
814 cmds.append(y)
815 return cmds
817 def _code(self, cmds):
818 """return an encoded charstring data for list of T1cmd's in cmds"""
819 code = array.array("B")
820 for cmd in cmds:
821 try:
822 if cmd.subcmd:
823 code.append(12)
824 code.append(cmd.code)
825 except AttributeError:
826 if -107 <= cmd <= 107:
827 code.append(cmd+139)
828 elif 108 <= cmd <= 1131:
829 a, b = divmod(cmd-108, 256)
830 code.append(a+247)
831 code.append(b)
832 elif -1131 <= cmd <= -108:
833 a, b = divmod(-cmd-108, 256)
834 code.append(a+251)
835 code.append(b)
836 else:
837 if cmd < 0:
838 cmd += 1l << 32
839 cmd, x4 = divmod(cmd, 256)
840 cmd, x3 = divmod(cmd, 256)
841 x1, x2 = divmod(cmd, 256)
842 code.append(255)
843 code.append(x1)
844 code.append(x2)
845 code.append(x3)
846 code.append(x4)
847 return self._charstringencode(code.tostring())
849 def getsubrcmds(self, subr):
850 """return a list of T1cmd's for subr subr"""
851 if not self._data2:
852 self._data2decode()
853 return self._cmds(self.subrs[subr])
855 def getglyphcmds(self, glyph):
856 """return a list of T1cmd's for glyph glyph"""
857 if not self._data2:
858 self._data2decode()
859 return self._cmds(self.glyphs[glyph])
861 def setsubrcmds(self, subr, cmds):
862 """replaces the T1cmd's by the list cmds for subr subr"""
863 if not self._data2:
864 self._data2decode()
865 self._data2eexec = None
866 self.subrs[subr] = self._code(cmds)
868 def setglyphcmds(self, glyph, cmds):
869 """replaces the T1cmd's by the list cmds for glyph glyph"""
870 if not self._data2:
871 self._data2decode()
872 self._data2eexec = None
873 self.glyphs[glyph] = self._code(cmds)
875 def updatepath(self, cmds, path, trafo, context):
876 for cmd in cmds:
877 if isinstance(cmd, T1cmd):
878 cmd.updatepath(path, trafo, context)
879 else:
880 context.t1stack.append(cmd)
882 def updatesubrpath(self, subr, path, trafo, context):
883 self.updatepath(self.getsubrcmds(subr), path, trafo, context)
885 def updateglyphpath(self, glyph, path, trafo, context):
886 self.updatepath(self.getglyphcmds(glyph), path, trafo, context)
888 def gathercalls(self, cmds, seacglyphs, subrs, othersubrs, context):
889 for cmd in cmds:
890 if isinstance(cmd, T1cmd):
891 cmd.gathercalls(seacglyphs, subrs, othersubrs, context)
892 else:
893 context.t1stack.append(cmd)
895 def gathersubrcalls(self, subr, seacglyphs, subrs, othersubrs, context):
896 self.gathercalls(self.getsubrcmds(subr), seacglyphs, subrs, othersubrs, context)
898 def gatherglyphcalls(self, glyph, seacglyphs, subrs, othersubrs, context):
899 self.gathercalls(self.getglyphcmds(glyph), seacglyphs, subrs, othersubrs, context)
901 fontmatrixpattern = re.compile("/FontMatrix\s*\[\s*(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)\s*\]\s*(readonly\s+)?def")
903 def getglyphpathwxwy_pt(self, glyph, size):
904 m = self.fontmatrixpattern.search(self.data1)
905 m11, m12, m21, m22, v1, v2 = map(float, m.groups()[:6])
906 t = trafo.trafo_pt(matrix=((m11, m12), (m21, m22)), vector=(v1, v2)).scaled(size)
907 context = T1context(self)
908 p = path()
909 self.updateglyphpath(glyph, p, t, context)
910 wx, wy = t.apply_pt(context.wx, context.wy)
911 return p, wx, wy
913 def getglyphpath(self, glyph, size):
914 """return a PyX path for glyph named glyph"""
915 return self.getglyphpathwxwy_pt(glyph, size)[0]
917 def getglyphwxwy_pt(self, glyph, size):
918 return self.getglyphpathwxwy_pt(glyph, size)[1:]
920 def getdata2(self, subrs=None, glyphs=None):
921 """makes a data2 string
923 subrs is a dict containing those subrs numbers as keys,
924 which are to be contained in the subrsstring to be created.
925 If subrs is None, all subrs in self.subrs will be used.
926 The subrs dict might be modified *in place*.
928 glyphs is a dict containing those glyph names as keys,
929 which are to be contained in the charstringsstring to be created.
930 If glyphs is None, all glyphs in self.glyphs will be used."""
931 def addsubrs(subrs, result):
932 if subrs is not None:
933 # some adjustments to the subrs dict
934 if subrs:
935 subrsindices = subrs.keys()
936 subrsmin = min(subrsindices)
937 subrsmax = max(subrsindices)
938 if self.hasflexhintsubrs and subrsmin < len(self.flexhintsubrs):
939 # According to the spec we need to keep all the flex and hint subrs
940 # as long as any of it is used.
941 for subr in len(self.flexhintsubrs):
942 subrs[subr] = 1
943 else:
944 subrsmax = -1
945 else:
946 # build a new subrs dict containing all subrs
947 subrs = dict([(subr, 1) for subr in range(len(self.subrs))])
948 subrsmax = len(self.subrs) - 1
950 # build the string from all selected subrs
951 result.append("%d array\n" % (subrsmax + 1))
952 for subr in range(subrsmax+1):
953 if subrs.has_key(subr):
954 code = self.subrs[subr]
955 else:
956 code = self.emptysubr
957 result.append("dup %d %d %s %s %s\n" % (subr, len(code), self.subrrdtoken, code, self.subrnptoken))
959 def addcharstrings(glyphs, result):
960 result.append("%d dict dup begin\n" % (glyphs is None and len(self.glyphlist) or len(glyphs)))
961 for glyph in self.glyphlist:
962 if glyphs is None or glyphs.has_key(glyph):
963 result.append("/%s %d %s %s %s\n" % (glyph, len(self.glyphs[glyph]), self.glyphrdtoken, self.glyphs[glyph], self.glyphndtoken))
964 result.append("end\n")
966 if self.subrsstart < self.charstringsstart:
967 result = [self._data2[:self.subrsstart]]
968 addsubrs(subrs, result)
969 result.append(self._data2[self.subrsend:self.charstringsstart])
970 addcharstrings(glyphs, result)
971 result.append(self._data2[self.charstringsend:])
972 else:
973 result = [self._data2[:self.charstringsstart]]
974 addcharstrings(glyphs, result)
975 result.append(self._data2[self.charstringsend:self.subrsstart])
976 addsubrs(subrs, result)
977 result.append(self._data2[self.subrsend:])
978 return "".join(result)
980 def getdata2eexec(self):
981 if self._data2eexec:
982 return self._data2eexec
983 # note that self._data2 is out-of-date here too, hence we need to call getdata2
984 return self._eexecencode(self.getdata2())
986 newlinepattern = re.compile("\s*[\r\n]\s*")
987 uniqueidpattern = re.compile("/UniqueID\s+\d+\s+def\s+")
989 def getstrippedfont(self, glyphs):
990 """create a T1font instance containing only certain glyphs
992 glyphs is a dict having the glyph names to be contained as keys.
993 The glyphs dict might be modified *in place*.
995 # TODO: we could also strip othersubrs to those actually used
997 # collect information about used glyphs and subrs
998 seacglyphs = {}
999 subrs = {}
1000 othersubrs = {}
1001 for glyph in glyphs.keys():
1002 self.gatherglyphcalls(glyph, seacglyphs, subrs, othersubrs, T1context(self))
1003 # while we have gathered all subrs for the seacglyphs alreadys, we
1004 # might have missed the glyphs themself (when they are not used stand-alone)
1005 glyphs.update(seacglyphs)
1006 glyphs[".notdef"] = 1
1008 # strip data1
1009 if not self.encoding:
1010 self._encoding()
1011 if self.encoding is encoding.adobestandardencoding:
1012 data1 = self.data1
1013 else:
1014 encodingstrings = []
1015 for char, glyph in enumerate(self.encoding.encvector):
1016 if glyph in glyphs.keys():
1017 encodingstrings.append("dup %i /%s put\n" % (char, glyph))
1018 data1 = self.data1[:self.encodingstart] + "".join(encodingstrings) + self.data1[self.encodingend:]
1019 data1 = self.newlinepattern.subn("\n", data1)[0]
1020 data1 = self.uniqueidpattern.subn("", data1)[0]
1022 # strip data2
1023 data2 = self.uniqueidpattern.subn("", self.getdata2(subrs, glyphs))[0]
1025 # strip data3
1026 data3 = self.newlinepattern.subn("\n", self.data3)[0]
1028 # create and return the new font instance
1029 return T1font(data1.rstrip() + "\n", self._eexecencode(data2), data3.rstrip() + "\n")
1031 def getflags(self):
1032 # As a simple heuristics we assume non-symbolic fonts if and only
1033 # if the Adobe standard encoding is used. All other font flags are
1034 # not specified here.
1035 if not self.encoding:
1036 self._encoding()
1037 if self.encoding is encoding.adobestandardencoding:
1038 return 32
1039 return 4
1041 def outputPFA(self, file):
1042 """output the T1font in PFA format"""
1043 file.write(self.data1)
1044 data2eexechex = binascii.b2a_hex(self.getdata2eexec())
1045 linelength = 64
1046 for i in range((len(data2eexechex)-1)/linelength + 1):
1047 file.write(data2eexechex[i*linelength: i*linelength+linelength])
1048 file.write("\n")
1049 file.write(self.data3)
1051 def outputPFB(self, file):
1052 """output the T1font in PFB format"""
1053 data2eexec = self.getdata2eexec()
1054 def pfblength(data):
1055 l = len(data)
1056 l, x1 = divmod(l, 256)
1057 l, x2 = divmod(l, 256)
1058 x4, x3 = divmod(l, 256)
1059 return chr(x1) + chr(x2) + chr(x3) + chr(x4)
1060 file.write("\200\1")
1061 file.write(pfblength(self.data1))
1062 file.write(self.data1)
1063 file.write("\200\2")
1064 file.write(pfblength(data2eexec))
1065 file.write(data2eexec)
1066 file.write("\200\1")
1067 file.write(pfblength(self.data3))
1068 file.write(self.data3)
1069 file.write("\200\3")
1071 def outputPS(self, file, writer):
1072 """output the PostScript code for the T1font to the file file"""
1073 self.outputPFA(file)
1075 def outputPDF(self, file, writer):
1076 data2eexec = self.getdata2eexec()
1077 data3 = self.data3
1078 # we might be allowed to skip the third part ...
1079 if (data3.replace("\n", "")
1080 .replace("\r", "")
1081 .replace("\t", "")
1082 .replace(" ", "")) == "0"*512 + "cleartomark":
1083 data3 = ""
1085 data = self.data1 + data2eexec + data3
1086 if writer.compress and haszlib:
1087 data = zlib.compress(data)
1089 file.write("<<\n"
1090 "/Length %d\n"
1091 "/Length1 %d\n"
1092 "/Length2 %d\n"
1093 "/Length3 %d\n" % (len(data), len(self.data1), len(data2eexec), len(data3)))
1094 if writer.compress and haszlib:
1095 file.write("/Filter /FlateDecode\n")
1096 file.write(">>\n"
1097 "stream\n")
1098 file.write(data)
1099 file.write("\n"
1100 "endstream\n")
1103 class T1pfafont(T1font):
1105 """create a T1font instance from a pfa font file"""
1107 def __init__(self, filename):
1108 d = open(filename, "rb").read()
1109 # hey, that's quick'n'dirty
1110 m1 = d.index("eexec") + 6
1111 m2 = d.index("0"*40)
1112 data1 = d[:m1]
1113 data2 = binascii.a2b_hex(d[m1: m2].replace(" ", "").replace("\r", "").replace("\n", ""))
1114 data3 = d[m2:]
1115 T1font.__init__(self, data1, data2, data3)
1118 class T1pfbfont(T1font):
1120 """create a T1font instance from a pfb font file"""
1122 def __init__(self, filename):
1123 def pfblength(s):
1124 if len(s) != 4:
1125 raise ValueError("invalid string length")
1126 return (ord(s[0]) +
1127 ord(s[1])*256 +
1128 ord(s[2])*256*256 +
1129 ord(s[3])*256*256*256)
1130 f = open(filename, "rb")
1131 mark = f.read(2); assert mark == "\200\1"
1132 data1 = f.read(pfblength(f.read(4)))
1133 mark = f.read(2); assert mark == "\200\2"
1134 data2 = ""
1135 while mark == "\200\2":
1136 data2 = data2 + f.read(pfblength(f.read(4)))
1137 mark = f.read(2)
1138 assert mark == "\200\1"
1139 data3 = f.read(pfblength(f.read(4)))
1140 mark = f.read(2); assert mark == "\200\3"
1141 assert not f.read(1)
1142 T1font.__init__(self, data1, data2, data3)