another font -> file renaming
[PyX/mjg.git] / pyx / font / t1file.py
bloba60a469b38383ac807192c25614529139ff64337
1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2005-2006 André Wobst <wobsta@users.sourceforge.net>
5 # Copyright (C) 2006 Jörg Lehmann <joergl@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 from __future__ import nested_scopes
25 import array, binascii, re
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 aglyph = encoding.adobestandardencoding.decode(achar)
159 bglyph = encoding.adobestandardencoding.decode(bchar)
160 context.t1font.updateglyphpath(bglyph, path, atrafo, context)
161 atrafo = atrafo * trafo.translate_pt(adx-sab, ady)
162 context.t1font.updateglyphpath(aglyph, path, atrafo, context)
164 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
165 bchar = context.t1stack.pop()
166 achar = context.t1stack.pop()
167 aglyph = encoding.adobestandardencoding.decode(achar)
168 bglyph = encoding.adobestandardencoding.decode(bchar)
169 seacglyphs[aglyph] = 1
170 seacglyphs[bglyph] = 1
171 context.t1font.gatherglyphcalls(bglyph, seacglyphs, subrs, othersubrs, context)
172 context.t1font.gatherglyphcalls(aglyph, seacglyphs, subrs, othersubrs, context)
174 T1seac = _T1seac()
177 class _T1sbw(T1cmd):
179 def __init__(self):
180 T1cmd.__init__(self, 7, subcmd=1)
182 def __str__(self):
183 return "sbw"
185 def updatepath(self, path, trafo, context):
186 sbx = context.t1stack.pop(0)
187 sby = context.t1stack.pop(0)
188 wx = context.t1stack.pop(0)
189 wy = context.t1stack.pop(0)
190 path.append(moveto_pt(*trafo.apply_pt(sbx, sby)))
191 context.x = sbx
192 context.y = sby
193 context.wx = wx
194 context.wy = wy
196 T1sbw = _T1sbw()
199 # path construction commands
201 class _T1closepath(T1cmd):
203 def __init__(self):
204 T1cmd.__init__(self, 9)
206 def __str__(self):
207 return "closepath"
209 def updatepath(self, path, trafo, context):
210 path.append(closepath())
211 # The closepath in T1 is different from PostScripts in that it does
212 # *not* modify the current position; hence we need to add an additional
213 # moveto here ...
214 path.append(moveto_pt(*trafo.apply_pt(context.x, context.y)))
216 T1closepath = _T1closepath()
219 class _T1hlineto(T1cmd):
221 def __init__(self):
222 T1cmd.__init__(self, 6)
224 def __str__(self):
225 return "hlineto"
227 def updatepath(self, path, trafo, context):
228 dx = context.t1stack.pop(0)
229 path.append(lineto_pt(*trafo.apply_pt(context.x + dx, context.y)))
230 context.x += dx
232 T1hlineto = _T1hlineto()
235 class _T1hmoveto(T1cmd):
237 def __init__(self):
238 T1cmd.__init__(self, 22)
240 def __str__(self):
241 return "hmoveto"
243 def updatepath(self, path, trafo, context):
244 dx = context.t1stack.pop(0)
245 path.append(moveto_pt(*trafo.apply_pt(context.x + dx, context.y)))
246 context.x += dx
248 T1hmoveto = _T1hmoveto()
251 class _T1hvcurveto(T1cmd):
253 def __init__(self):
254 T1cmd.__init__(self, 31)
256 def __str__(self):
257 return "hvcurveto"
259 def updatepath(self, path, trafo, context):
260 dx1 = context.t1stack.pop(0)
261 dx2 = context.t1stack.pop(0)
262 dy2 = context.t1stack.pop(0)
263 dy3 = context.t1stack.pop(0)
264 path.append(curveto_pt(*(trafo.apply_pt(context.x + dx1, context.y) +
265 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy2) +
266 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy2 + dy3))))
267 context.x += dx1+dx2
268 context.y += dy2+dy3
270 T1hvcurveto = _T1hvcurveto()
273 class _T1rlineto(T1cmd):
275 def __init__(self):
276 T1cmd.__init__(self, 5)
278 def __str__(self):
279 return "rlineto"
281 def updatepath(self, path, trafo, context):
282 dx = context.t1stack.pop(0)
283 dy = context.t1stack.pop(0)
284 path.append(lineto_pt(*trafo.apply_pt(context.x + dx, context.y + dy)))
285 context.x += dx
286 context.y += dy
288 T1rlineto = _T1rlineto()
291 class _T1rmoveto(T1cmd):
293 def __init__(self):
294 T1cmd.__init__(self, 21)
296 def __str__(self):
297 return "rmoveto"
299 def updatepath(self, path, trafo, context):
300 dx = context.t1stack.pop(0)
301 dy = context.t1stack.pop(0)
302 path.append(moveto_pt(*trafo.apply_pt(context.x + dx, context.y + dy)))
303 context.x += dx
304 context.y += dy
306 T1rmoveto = _T1rmoveto()
309 class _T1rrcurveto(T1cmd):
311 def __init__(self):
312 T1cmd.__init__(self, 8)
314 def __str__(self):
315 return "rrcurveto"
317 def updatepath(self, path, trafo, context):
318 dx1 = context.t1stack.pop(0)
319 dy1 = context.t1stack.pop(0)
320 dx2 = context.t1stack.pop(0)
321 dy2 = context.t1stack.pop(0)
322 dx3 = context.t1stack.pop(0)
323 dy3 = context.t1stack.pop(0)
324 path.append(curveto_pt(*(trafo.apply_pt(context.x + dx1, context.y + dy1) +
325 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy1 + dy2) +
326 trafo.apply_pt(context.x + dx1 + dx2 + dx3, context.y + dy1 + dy2 + dy3))))
327 context.x += dx1+dx2+dx3
328 context.y += dy1+dy2+dy3
330 T1rrcurveto = _T1rrcurveto()
333 class _T1vlineto(T1cmd):
335 def __init__(self):
336 T1cmd.__init__(self, 7)
338 def __str__(self):
339 return "vlineto"
341 def updatepath(self, path, trafo, context):
342 dy = context.t1stack.pop(0)
343 path.append(lineto_pt(*trafo.apply_pt(context.x, context.y + dy)))
344 context.y += dy
346 T1vlineto = _T1vlineto()
349 class _T1vmoveto(T1cmd):
351 def __init__(self):
352 T1cmd.__init__(self, 4)
354 def __str__(self):
355 return "vmoveto"
357 def updatepath(self, path, trafo, context):
358 dy = context.t1stack.pop(0)
359 path.append(moveto_pt(*trafo.apply_pt(context.x, context.y + dy)))
360 context.y += dy
362 T1vmoveto = _T1vmoveto()
365 class _T1vhcurveto(T1cmd):
367 def __init__(self):
368 T1cmd.__init__(self, 30)
370 def __str__(self):
371 return "vhcurveto"
373 def updatepath(self, path, trafo, context):
374 dy1 = context.t1stack.pop(0)
375 dx2 = context.t1stack.pop(0)
376 dy2 = context.t1stack.pop(0)
377 dx3 = context.t1stack.pop(0)
378 path.append(curveto_pt(*(trafo.apply_pt(context.x, context.y + dy1) +
379 trafo.apply_pt(context.x + dx2, context.y + dy1 + dy2) +
380 trafo.apply_pt(context.x + dx2 + dx3, context.y + dy1 + dy2))))
381 context.x += dx2+dx3
382 context.y += dy1+dy2
384 T1vhcurveto = _T1vhcurveto()
387 # hint commands
389 class _T1dotsection(T1cmd):
391 def __init__(self):
392 T1cmd.__init__(self, 0, subcmd=1)
394 def __str__(self):
395 return "dotsection"
397 def updatepath(self, path, trafo, context):
398 pass
400 T1dotsection = _T1dotsection()
403 class _T1hstem(T1cmd):
405 def __init__(self):
406 T1cmd.__init__(self, 1)
408 def __str__(self):
409 return "hstem"
411 def updatepath(self, path, trafo, context):
412 y = context.t1stack.pop(0)
413 dy = context.t1stack.pop(0)
415 T1hstem = _T1hstem()
418 class _T1hstem3(T1cmd):
420 def __init__(self):
421 T1cmd.__init__(self, 2, subcmd=1)
423 def __str__(self):
424 return "hstem3"
426 def updatepath(self, path, trafo, context):
427 y0 = context.t1stack.pop(0)
428 dy0 = context.t1stack.pop(0)
429 y1 = context.t1stack.pop(0)
430 dy1 = context.t1stack.pop(0)
431 y2 = context.t1stack.pop(0)
432 dy2 = context.t1stack.pop(0)
434 T1hstem3 = _T1hstem3()
437 class _T1vstem(T1cmd):
439 def __init__(self):
440 T1cmd.__init__(self, 3)
442 def __str__(self):
443 return "vstem"
445 def updatepath(self, path, trafo, context):
446 x = context.t1stack.pop(0)
447 dx = context.t1stack.pop(0)
449 T1vstem = _T1vstem()
452 class _T1vstem3(T1cmd):
454 def __init__(self):
455 T1cmd.__init__(self, 1, subcmd=1)
457 def __str__(self):
458 return "vstem3"
460 def updatepath(self, path, trafo, context):
461 self.x0 = context.t1stack.pop(0)
462 self.dx0 = context.t1stack.pop(0)
463 self.x1 = context.t1stack.pop(0)
464 self.dx1 = context.t1stack.pop(0)
465 self.x2 = context.t1stack.pop(0)
466 self.dx2 = context.t1stack.pop(0)
468 T1vstem3 = _T1vstem3()
471 # arithmetic command
473 class _T1div(T1cmd):
475 def __init__(self):
476 T1cmd.__init__(self, 12, subcmd=1)
478 def __str__(self):
479 return "div"
481 def updatepath(self, path, trafo, context):
482 num2 = context.t1stack.pop()
483 num1 = context.t1stack.pop()
484 context.t1stack.append(divmod(num1, num2)[0])
486 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
487 num2 = context.t1stack.pop()
488 num1 = context.t1stack.pop()
489 context.t1stack.append(divmod(num1, num2)[0])
491 T1div = _T1div()
494 # subroutine commands
496 class _T1callothersubr(T1cmd):
498 def __init__(self):
499 T1cmd.__init__(self, 16, subcmd=1)
501 def __str__(self):
502 return "callothersubr"
504 def updatepath(self, path, trafo, context):
505 othersubrnumber = context.t1stack.pop()
506 n = context.t1stack.pop()
507 for i in range(n):
508 context.psstack.append(context.t1stack.pop())
510 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
511 othersubrnumber = context.t1stack.pop()
512 othersubrs[othersubrnumber] = 1
513 n = context.t1stack.pop()
514 for i in range(n):
515 context.psstack.append(context.t1stack.pop())
517 T1callothersubr = _T1callothersubr()
520 class _T1callsubr(T1cmd):
522 def __init__(self):
523 T1cmd.__init__(self, 10)
525 def __str__(self):
526 return "callsubr"
528 def updatepath(self, path, trafo, context):
529 subr = context.t1stack.pop()
530 context.t1font.updatesubrpath(subr, path, trafo, context)
532 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
533 subr = context.t1stack.pop()
534 subrs[subr] = 1
535 context.t1font.gathersubrcalls(subr, seacglyphs, subrs, othersubrs, context)
537 T1callsubr = _T1callsubr()
540 class _T1pop(T1cmd):
542 def __init__(self):
543 T1cmd.__init__(self, 17, subcmd=1)
545 def __str__(self):
546 return "pop"
548 def updatepath(self, path, trafo, context):
549 context.t1stack.append(context.psstack.pop())
551 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
552 context.t1stack.append(context.psstack.pop())
554 T1pop = _T1pop()
557 class _T1return(T1cmd):
559 def __init__(self):
560 T1cmd.__init__(self, 11)
562 def __str__(self):
563 return "return"
565 def updatepath(self, path, trafo, context):
566 pass
568 T1return = _T1return()
571 class _T1setcurrentpoint(T1cmd):
573 def __init__(self):
574 T1cmd.__init__(self, 33, subcmd=1)
576 def __str__(self):
577 return "setcurrentpoint" % self.x, self.y
579 def updatepath(self, path, trafo, context):
580 x = context.t1stack.pop(0)
581 y = context.t1stack.pop(0)
582 path.append(moveto_pt(*trafo.apply_pt(x, y)))
583 context.x = x
584 context.y = y
586 T1setcurrentpoint = _T1setcurrentpoint()
589 ######################################################################
591 class cursor:
592 """cursor to read a string token by token"""
594 def __init__(self, data, startstring, eattokensep=1, tokenseps=" \t\r\n", tokenstarts="()<>[]{}/%"):
595 """creates a cursor for the string data
597 startstring is a string at which the cursor should start at. The first
598 ocurance of startstring is used. When startstring is not in data, an
599 exception is raised, otherwise the cursor is set to the position right
600 after the startstring. When eattokenseps is set, startstring must be
601 followed by a tokensep and this first tokensep is also consumed.
602 tokenseps is a string containing characters to be used as token
603 separators. tokenstarts is a string containing characters which
604 directly (even without intermediate token separator) start a new token.
606 self.data = data
607 self.pos = self.data.index(startstring) + len(startstring)
608 self.tokenseps = tokenseps
609 self.tokenstarts = tokenstarts
610 if eattokensep:
611 if self.data[self.pos] not in self.tokenstarts:
612 if self.data[self.pos] not in self.tokenseps:
613 raise ValueError("cursor initialization string is not followed by a token separator")
614 self.pos += 1
616 def gettoken(self):
617 """get the next token
619 Leading token separators and comments are silently consumed. The first token
620 separator after the token is also silently consumed."""
621 while self.data[self.pos] in self.tokenseps:
622 self.pos += 1
623 # ignore comments including subsequent whitespace characters
624 while self.data[self.pos] == "%":
625 while self.data[self.pos] not in "\r\n":
626 self.pos += 1
627 while self.data[self.pos] in self.tokenseps:
628 self.pos += 1
629 startpos = self.pos
630 while self.data[self.pos] not in self.tokenseps:
631 # any character in self.tokenstarts ends the token
632 if self.pos>startpos and self.data[self.pos] in self.tokenstarts:
633 break
634 self.pos += 1
635 result = self.data[startpos:self.pos]
636 if self.data[self.pos] in self.tokenseps:
637 self.pos += 1 # consume a single tokensep
638 return result
640 def getint(self):
641 """get the next token as an integer"""
642 return int(self.gettoken())
644 def getbytes(self, count):
645 """get the next count bytes"""
646 startpos = self.pos
647 self.pos += count
648 return self.data[startpos: self.pos]
651 class T1file:
653 eexecr = 55665
654 charstringr = 4330
656 def __init__(self, data1, data2eexec, data3):
657 """initializes a t1font instance
659 data1 and data3 are the two clear text data parts and data2 is
660 the binary data part"""
661 self.data1 = data1
662 self._data2eexec = data2eexec
663 self.data3 = data3
665 # marker and value for decoded data
666 self._data2 = None
667 # note that data2eexec is set to none by setsubrcmds and setglyphcmds
668 # this *also* denotes, that data2 is out-of-date; hence they are both
669 # marked by an _ and getdata2 and getdata2eexec will properly resolve
670 # the current state of decoding ...
672 # marker and value for standard encoding check
673 self.encoding = None
675 def _eexecdecode(self, code):
676 """eexec decoding of code"""
677 return decoder(code, self.eexecr, 4)
679 def _charstringdecode(self, code):
680 """charstring decoding of code"""
681 return decoder(code, self.charstringr, self.lenIV)
683 def _eexecencode(self, data):
684 """eexec encoding of data"""
685 return encoder(data, self.eexecr, "PyX!")
687 def _charstringencode(self, data):
688 """eexec encoding of data"""
689 return encoder(data, self.charstringr, "PyX!"[:self.lenIV])
691 lenIVpattern = re.compile("/lenIV\s+(\d+)\s+def\s+")
692 flexhintsubrs = [[3, 0, T1callothersubr, T1pop, T1pop, T1setcurrentpoint, T1return],
693 [0, 1, T1callothersubr, T1return],
694 [0, 2, T1callothersubr, T1return],
695 [T1return]]
697 def _encoding(self):
698 """helper method to lookup the encoding in the font"""
699 c = cursor(self.data1, "/Encoding")
700 token1 = c.gettoken()
701 token2 = c.gettoken()
702 if token1 == "StandardEncoding" and token2 == "def":
703 self.encoding = encoding.adobestandardencoding
704 else:
705 encvector = [None]*256
706 while 1:
707 self.encodingstart = c.pos
708 if c.gettoken() == "dup":
709 break
710 while 1:
711 i = c.getint()
712 glyph = c.gettoken()
713 if 0 <= i < 256:
714 encvector[i] = glyph[1:]
715 token = c.gettoken(); assert token == "put"
716 self.encodingend = c.pos
717 token = c.gettoken()
718 if token == "readonly" or token == "def":
719 break
720 assert token == "dup"
721 self.encoding = encoding.encoding(encvector)
723 def _data2decode(self):
724 """decodes data2eexec to the data2 string and the subr and glyphs dictionary
726 It doesn't make sense to call this method twice -- check the content of
727 data2 before calling. The method also keeps the subrs and charstrings
728 start and end positions for later use."""
729 self._data2 = self._eexecdecode(self._data2eexec)
731 m = self.lenIVpattern.search(self._data2)
732 if m:
733 self.lenIV = int(m.group(1))
734 else:
735 self.lenIV = 4
736 self.emptysubr = self._charstringencode(chr(11))
738 # extract Subrs
739 c = cursor(self._data2, "/Subrs")
740 self.subrsstart = c.pos
741 arraycount = c.getint()
742 token = c.gettoken(); assert token == "array"
743 self.subrs = []
744 for i in range(arraycount):
745 token = c.gettoken(); assert token == "dup"
746 token = c.getint(); assert token == i
747 size = c.getint()
748 if not i:
749 self.subrrdtoken = c.gettoken()
750 else:
751 token = c.gettoken(); assert token == self.subrrdtoken
752 self.subrs.append(c.getbytes(size))
753 token = c.gettoken()
754 if token == "noaccess":
755 token = "%s %s" % (token, c.gettoken())
756 if not i:
757 self.subrnptoken = token
758 else:
759 assert token == self.subrnptoken
760 self.subrsend = c.pos
762 # hasflexhintsubrs is a boolean indicating that the font uses flex or
763 # hint replacement subrs as specified by Adobe (tm). When it does, the
764 # first 4 subrs should all be copied except when none of them are used
765 # in the stripped version of the font since we than get a font not
766 # using flex or hint replacement subrs at all.
767 self.hasflexhintsubrs = (arraycount >= len(self.flexhintsubrs) and
768 [self.getsubrcmds(i)
769 for i in range(len(self.flexhintsubrs))] == self.flexhintsubrs)
771 # extract glyphs
772 self.glyphs = {}
773 self.glyphlist = [] # we want to keep the order of the glyph names
774 c = cursor(self._data2, "/CharStrings")
775 self.charstringsstart = c.pos
776 c.getint()
777 token = c.gettoken(); assert token == "dict"
778 token = c.gettoken(); assert token == "dup"
779 token = c.gettoken(); assert token == "begin"
780 first = 1
781 while 1:
782 chartoken = c.gettoken()
783 if chartoken == "end":
784 break
785 assert chartoken[0] == "/"
786 size = c.getint()
787 if first:
788 self.glyphrdtoken = c.gettoken()
789 else:
790 token = c.gettoken(); assert token == self.glyphrdtoken
791 self.glyphlist.append(chartoken[1:])
792 self.glyphs[chartoken[1:]] = c.getbytes(size)
793 if first:
794 self.glyphndtoken = c.gettoken()
795 else:
796 token = c.gettoken(); assert token == self.glyphndtoken
797 first = 0
798 self.charstringsend = c.pos
799 assert not self.subrs or self.subrrdtoken == self.glyphrdtoken
801 def _cmds(self, code):
802 """return a list of T1cmd's for encoded charstring data in code"""
803 code = array.array("B", self._charstringdecode(code))
804 cmds = []
805 while code:
806 x = code.pop(0)
807 if x == 12: # this starts an escaped cmd
808 cmds.append(T1subcmds[code.pop(0)])
809 elif 0 <= x < 32: # those are cmd's
810 cmds.append(T1cmds[x])
811 elif 32 <= x <= 246: # short ints
812 cmds.append(x-139)
813 elif 247 <= x <= 250: # mid size ints
814 cmds.append(((x - 247)*256) + code.pop(0) + 108)
815 elif 251 <= x <= 254: # mid size ints
816 cmds.append(-((x - 251)*256) - code.pop(0) - 108)
817 else: # x = 255, i.e. full size ints
818 y = ((code.pop(0)*256l+code.pop(0))*256+code.pop(0))*256+code.pop(0)
819 if y > (1l << 31):
820 cmds.append(y - (1l << 32))
821 else:
822 cmds.append(y)
823 return cmds
825 def _code(self, cmds):
826 """return an encoded charstring data for list of T1cmd's in cmds"""
827 code = array.array("B")
828 for cmd in cmds:
829 try:
830 if cmd.subcmd:
831 code.append(12)
832 code.append(cmd.code)
833 except AttributeError:
834 if -107 <= cmd <= 107:
835 code.append(cmd+139)
836 elif 108 <= cmd <= 1131:
837 a, b = divmod(cmd-108, 256)
838 code.append(a+247)
839 code.append(b)
840 elif -1131 <= cmd <= -108:
841 a, b = divmod(-cmd-108, 256)
842 code.append(a+251)
843 code.append(b)
844 else:
845 if cmd < 0:
846 cmd += 1l << 32
847 cmd, x4 = divmod(cmd, 256)
848 cmd, x3 = divmod(cmd, 256)
849 x1, x2 = divmod(cmd, 256)
850 code.append(255)
851 code.append(x1)
852 code.append(x2)
853 code.append(x3)
854 code.append(x4)
855 return self._charstringencode(code.tostring())
857 def getsubrcmds(self, subr):
858 """return a list of T1cmd's for subr subr"""
859 if not self._data2:
860 self._data2decode()
861 return self._cmds(self.subrs[subr])
863 def getglyphcmds(self, glyph):
864 """return a list of T1cmd's for glyph glyph"""
865 if not self._data2:
866 self._data2decode()
867 return self._cmds(self.glyphs[glyph])
869 def setsubrcmds(self, subr, cmds):
870 """replaces the T1cmd's by the list cmds for subr subr"""
871 if not self._data2:
872 self._data2decode()
873 self._data2eexec = None
874 self.subrs[subr] = self._code(cmds)
876 def setglyphcmds(self, glyph, cmds):
877 """replaces the T1cmd's by the list cmds for glyph glyph"""
878 if not self._data2:
879 self._data2decode()
880 self._data2eexec = None
881 self.glyphs[glyph] = self._code(cmds)
883 def updatepath(self, cmds, path, trafo, context):
884 for cmd in cmds:
885 if isinstance(cmd, T1cmd):
886 cmd.updatepath(path, trafo, context)
887 else:
888 context.t1stack.append(cmd)
890 def updatesubrpath(self, subr, path, trafo, context):
891 self.updatepath(self.getsubrcmds(subr), path, trafo, context)
893 def updateglyphpath(self, glyph, path, trafo, context):
894 self.updatepath(self.getglyphcmds(glyph), path, trafo, context)
896 def gathercalls(self, cmds, seacglyphs, subrs, othersubrs, context):
897 for cmd in cmds:
898 if isinstance(cmd, T1cmd):
899 cmd.gathercalls(seacglyphs, subrs, othersubrs, context)
900 else:
901 context.t1stack.append(cmd)
903 def gathersubrcalls(self, subr, seacglyphs, subrs, othersubrs, context):
904 self.gathercalls(self.getsubrcmds(subr), seacglyphs, subrs, othersubrs, context)
906 def gatherglyphcalls(self, glyph, seacglyphs, subrs, othersubrs, context):
907 self.gathercalls(self.getglyphcmds(glyph), seacglyphs, subrs, othersubrs, context)
909 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")
911 def getglyphpathwxwy_pt(self, glyph, size):
912 m = self.fontmatrixpattern.search(self.data1)
913 m11, m12, m21, m22, v1, v2 = map(float, m.groups()[:6])
914 t = trafo.trafo_pt(matrix=((m11, m12), (m21, m22)), vector=(v1, v2)).scaled(size)
915 context = T1context(self)
916 p = path()
917 self.updateglyphpath(glyph, p, t, context)
918 wx, wy = t.apply_pt(context.wx, context.wy)
919 return p, wx, wy
921 def getglyphpath(self, glyph, size):
922 """return a PyX path for glyph named glyph"""
923 return self.getglyphpathwxwy_pt(glyph, size)[0]
925 def getglyphwxwy_pt(self, glyph, size):
926 return self.getglyphpathwxwy_pt(glyph, size)[1:]
928 def getdata2(self, subrs=None, glyphs=None):
929 """makes a data2 string
931 subrs is a dict containing those subrs numbers as keys,
932 which are to be contained in the subrsstring to be created.
933 If subrs is None, all subrs in self.subrs will be used.
934 The subrs dict might be modified *in place*.
936 glyphs is a dict containing those glyph names as keys,
937 which are to be contained in the charstringsstring to be created.
938 If glyphs is None, all glyphs in self.glyphs will be used."""
939 def addsubrs(subrs, result):
940 if subrs is not None:
941 # some adjustments to the subrs dict
942 if subrs:
943 subrsindices = subrs.keys()
944 subrsmin = min(subrsindices)
945 subrsmax = max(subrsindices)
946 if self.hasflexhintsubrs and subrsmin < len(self.flexhintsubrs):
947 # According to the spec we need to keep all the flex and hint subrs
948 # as long as any of it is used.
949 for subr in range(len(self.flexhintsubrs)):
950 subrs[subr] = 1
951 else:
952 subrsmax = -1
953 else:
954 # build a new subrs dict containing all subrs
955 subrs = dict([(subr, 1) for subr in range(len(self.subrs))])
956 subrsmax = len(self.subrs) - 1
958 # build the string from all selected subrs
959 result.append("%d array\n" % (subrsmax + 1))
960 for subr in range(subrsmax+1):
961 if subrs.has_key(subr):
962 code = self.subrs[subr]
963 else:
964 code = self.emptysubr
965 result.append("dup %d %d %s %s %s\n" % (subr, len(code), self.subrrdtoken, code, self.subrnptoken))
967 def addcharstrings(glyphs, result):
968 result.append("%d dict dup begin\n" % (glyphs is None and len(self.glyphlist) or len(glyphs)))
969 for glyph in self.glyphlist:
970 if glyphs is None or glyphs.has_key(glyph):
971 result.append("/%s %d %s %s %s\n" % (glyph, len(self.glyphs[glyph]), self.glyphrdtoken, self.glyphs[glyph], self.glyphndtoken))
972 result.append("end\n")
974 if self.subrsstart < self.charstringsstart:
975 result = [self._data2[:self.subrsstart]]
976 addsubrs(subrs, result)
977 result.append(self._data2[self.subrsend:self.charstringsstart])
978 addcharstrings(glyphs, result)
979 result.append(self._data2[self.charstringsend:])
980 else:
981 result = [self._data2[:self.charstringsstart]]
982 addcharstrings(glyphs, result)
983 result.append(self._data2[self.charstringsend:self.subrsstart])
984 addsubrs(subrs, result)
985 result.append(self._data2[self.subrsend:])
986 return "".join(result)
988 def getdata2eexec(self):
989 if self._data2eexec:
990 return self._data2eexec
991 # note that self._data2 is out-of-date here too, hence we need to call getdata2
992 return self._eexecencode(self.getdata2())
994 newlinepattern = re.compile("\s*[\r\n]\s*")
995 uniqueidpattern = re.compile("/UniqueID\s+\d+\s+def\s+")
997 def getstrippedfont(self, glyphs):
998 """create a T1file instance containing only certain glyphs
1000 glyphs is a dict having the glyph names to be contained as keys.
1001 The glyphs dict might be modified *in place*.
1003 # TODO: we could also strip othersubrs to those actually used
1005 # collect information about used glyphs and subrs
1006 seacglyphs = {}
1007 subrs = {}
1008 othersubrs = {}
1009 for glyph in glyphs.keys():
1010 self.gatherglyphcalls(glyph, seacglyphs, subrs, othersubrs, T1context(self))
1011 # while we have gathered all subrs for the seacglyphs alreadys, we
1012 # might have missed the glyphs themself (when they are not used stand-alone)
1013 glyphs.update(seacglyphs)
1014 glyphs[".notdef"] = 1
1016 # strip data1
1017 if not self.encoding:
1018 self._encoding()
1019 if self.encoding is encoding.adobestandardencoding:
1020 data1 = self.data1
1021 else:
1022 encodingstrings = []
1023 for char, glyph in enumerate(self.encoding.encvector):
1024 if glyph in glyphs.keys():
1025 encodingstrings.append("dup %i /%s put\n" % (char, glyph))
1026 data1 = self.data1[:self.encodingstart] + "".join(encodingstrings) + self.data1[self.encodingend:]
1027 data1 = self.newlinepattern.subn("\n", data1)[0]
1028 data1 = self.uniqueidpattern.subn("", data1)[0]
1030 # strip data2
1031 data2 = self.uniqueidpattern.subn("", self.getdata2(subrs, glyphs))[0]
1033 # strip data3
1034 data3 = self.newlinepattern.subn("\n", self.data3)[0]
1036 # create and return the new font instance
1037 return T1file(data1.rstrip() + "\n", self._eexecencode(data2), data3.rstrip() + "\n")
1039 def getflags(self):
1040 # As a simple heuristics we assume non-symbolic fonts if and only
1041 # if the Adobe standard encoding is used. All other font flags are
1042 # not specified here.
1043 if not self.encoding:
1044 self._encoding()
1045 if self.encoding is encoding.adobestandardencoding:
1046 return 32
1047 return 4
1049 def outputPFA(self, file):
1050 """output the T1file in PFA format"""
1051 file.write(self.data1)
1052 data2eexechex = binascii.b2a_hex(self.getdata2eexec())
1053 linelength = 64
1054 for i in range((len(data2eexechex)-1)/linelength + 1):
1055 file.write(data2eexechex[i*linelength: i*linelength+linelength])
1056 file.write("\n")
1057 file.write(self.data3)
1059 def outputPFB(self, file):
1060 """output the T1file in PFB format"""
1061 data2eexec = self.getdata2eexec()
1062 def pfblength(data):
1063 l = len(data)
1064 l, x1 = divmod(l, 256)
1065 l, x2 = divmod(l, 256)
1066 x4, x3 = divmod(l, 256)
1067 return chr(x1) + chr(x2) + chr(x3) + chr(x4)
1068 file.write("\200\1")
1069 file.write(pfblength(self.data1))
1070 file.write(self.data1)
1071 file.write("\200\2")
1072 file.write(pfblength(data2eexec))
1073 file.write(data2eexec)
1074 file.write("\200\1")
1075 file.write(pfblength(self.data3))
1076 file.write(self.data3)
1077 file.write("\200\3")
1079 def outputPS(self, file, writer):
1080 """output the PostScript code for the T1file to the file file"""
1081 self.outputPFA(file)
1083 def outputPDF(self, file, writer):
1084 data2eexec = self.getdata2eexec()
1085 data3 = self.data3
1086 # we might be allowed to skip the third part ...
1087 if (data3.replace("\n", "")
1088 .replace("\r", "")
1089 .replace("\t", "")
1090 .replace(" ", "")) == "0"*512 + "cleartomark":
1091 data3 = ""
1093 data = self.data1 + data2eexec + data3
1094 if writer.compress and haszlib:
1095 data = zlib.compress(data)
1097 file.write("<<\n"
1098 "/Length %d\n"
1099 "/Length1 %d\n"
1100 "/Length2 %d\n"
1101 "/Length3 %d\n" % (len(data), len(self.data1), len(data2eexec), len(data3)))
1102 if writer.compress and haszlib:
1103 file.write("/Filter /FlateDecode\n")
1104 file.write(">>\n"
1105 "stream\n")
1106 file.write(data)
1107 file.write("\n"
1108 "endstream\n")
1111 class PFAfile(T1file):
1113 """create a T1file instance from a pfa font file"""
1115 def __init__(self, filename):
1116 d = open(filename, "rb").read()
1117 # hey, that's quick'n'dirty
1118 m1 = d.index("eexec") + 6
1119 m2 = d.index("0"*40)
1120 data1 = d[:m1]
1121 data2 = binascii.a2b_hex(d[m1: m2].replace(" ", "").replace("\r", "").replace("\n", ""))
1122 data3 = d[m2:]
1123 T1file.__init__(self, data1, data2, data3)
1126 class PFBfile(T1file):
1128 """create a T1file instance from a pfb font file"""
1130 def __init__(self, filename):
1131 def pfblength(s):
1132 if len(s) != 4:
1133 raise ValueError("invalid string length")
1134 return (ord(s[0]) +
1135 ord(s[1])*256 +
1136 ord(s[2])*256*256 +
1137 ord(s[3])*256*256*256)
1138 f = open(filename, "rb")
1139 mark = f.read(2); assert mark == "\200\1"
1140 data1 = f.read(pfblength(f.read(4)))
1141 mark = f.read(2); assert mark == "\200\2"
1142 data2 = ""
1143 while mark == "\200\2":
1144 data2 = data2 + f.read(pfblength(f.read(4)))
1145 mark = f.read(2)
1146 assert mark == "\200\1"
1147 data3 = f.read(pfblength(f.read(4)))
1148 mark = f.read(2); assert mark == "\200\3"
1149 assert not f.read(1)
1150 T1file.__init__(self, data1, data2, data3)