- move PS font resources to new font.font module
[PyX/mjg.git] / pyx / font / t1file.py
blob72455431bab0e92a2297aa3ee626a08843d1f688
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 set()
34 except NameError:
35 # Python 2.3
36 from sets import Set as set
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 fontnamepattern = re.compile("/FontName\s+/(.*?)\s+def\s+")
657 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")
659 def __init__(self, data1, data2eexec, data3):
660 """initializes a t1font instance
662 data1 and data3 are the two clear text data parts and data2 is
663 the binary data part"""
664 self.data1 = data1
665 self._data2eexec = data2eexec
666 self.data3 = data3
668 # marker and value for decoded data
669 self._data2 = None
670 # note that data2eexec is set to none by setsubrcmds and setglyphcmds
671 # this *also* denotes, that data2 is out-of-date; hence they are both
672 # marked by an _ and getdata2 and getdata2eexec will properly resolve
673 # the current state of decoding ...
675 # marker and value for standard encoding check
676 self.encoding = None
678 self.name, = self.fontnamepattern.search(self.data1).groups()
679 m11, m12, m21, m22, v1, v2 = map(float, self.fontmatrixpattern.search(self.data1).groups()[:6])
680 self.fontmatrix = trafo.trafo_pt(matrix=((m11, m12), (m21, m22)), vector=(v1, v2))
682 def _eexecdecode(self, code):
683 """eexec decoding of code"""
684 return decoder(code, self.eexecr, 4)
686 def _charstringdecode(self, code):
687 """charstring decoding of code"""
688 return decoder(code, self.charstringr, self.lenIV)
690 def _eexecencode(self, data):
691 """eexec encoding of data"""
692 return encoder(data, self.eexecr, "PyX!")
694 def _charstringencode(self, data):
695 """eexec encoding of data"""
696 return encoder(data, self.charstringr, "PyX!"[:self.lenIV])
698 lenIVpattern = re.compile("/lenIV\s+(\d+)\s+def\s+")
699 flexhintsubrs = [[3, 0, T1callothersubr, T1pop, T1pop, T1setcurrentpoint, T1return],
700 [0, 1, T1callothersubr, T1return],
701 [0, 2, T1callothersubr, T1return],
702 [T1return]]
704 def _encoding(self):
705 """helper method to lookup the encoding in the font"""
706 c = cursor(self.data1, "/Encoding")
707 token1 = c.gettoken()
708 token2 = c.gettoken()
709 if token1 == "StandardEncoding" and token2 == "def":
710 self.encoding = encoding.adobestandardencoding
711 else:
712 encvector = [None]*256
713 while 1:
714 self.encodingstart = c.pos
715 if c.gettoken() == "dup":
716 break
717 while 1:
718 i = c.getint()
719 glyph = c.gettoken()
720 if 0 <= i < 256:
721 encvector[i] = glyph[1:]
722 token = c.gettoken(); assert token == "put"
723 self.encodingend = c.pos
724 token = c.gettoken()
725 if token == "readonly" or token == "def":
726 break
727 assert token == "dup"
728 self.encoding = encoding.encoding(encvector)
730 def _data2decode(self):
731 """decodes data2eexec to the data2 string and the subr and glyphs dictionary
733 It doesn't make sense to call this method twice -- check the content of
734 data2 before calling. The method also keeps the subrs and charstrings
735 start and end positions for later use."""
736 self._data2 = self._eexecdecode(self._data2eexec)
738 m = self.lenIVpattern.search(self._data2)
739 if m:
740 self.lenIV = int(m.group(1))
741 else:
742 self.lenIV = 4
743 self.emptysubr = self._charstringencode(chr(11))
745 # extract Subrs
746 c = cursor(self._data2, "/Subrs")
747 self.subrsstart = c.pos
748 arraycount = c.getint()
749 token = c.gettoken(); assert token == "array"
750 self.subrs = []
751 for i in range(arraycount):
752 token = c.gettoken(); assert token == "dup"
753 token = c.getint(); assert token == i
754 size = c.getint()
755 if not i:
756 self.subrrdtoken = c.gettoken()
757 else:
758 token = c.gettoken(); assert token == self.subrrdtoken
759 self.subrs.append(c.getbytes(size))
760 token = c.gettoken()
761 if token == "noaccess":
762 token = "%s %s" % (token, c.gettoken())
763 if not i:
764 self.subrnptoken = token
765 else:
766 assert token == self.subrnptoken
767 self.subrsend = c.pos
769 # hasflexhintsubrs is a boolean indicating that the font uses flex or
770 # hint replacement subrs as specified by Adobe (tm). When it does, the
771 # first 4 subrs should all be copied except when none of them are used
772 # in the stripped version of the font since we than get a font not
773 # using flex or hint replacement subrs at all.
774 self.hasflexhintsubrs = (arraycount >= len(self.flexhintsubrs) and
775 [self.getsubrcmds(i)
776 for i in range(len(self.flexhintsubrs))] == self.flexhintsubrs)
778 # extract glyphs
779 self.glyphs = {}
780 self.glyphlist = [] # we want to keep the order of the glyph names
781 c = cursor(self._data2, "/CharStrings")
782 self.charstringsstart = c.pos
783 c.getint()
784 token = c.gettoken(); assert token == "dict"
785 token = c.gettoken(); assert token == "dup"
786 token = c.gettoken(); assert token == "begin"
787 first = 1
788 while 1:
789 chartoken = c.gettoken()
790 if chartoken == "end":
791 break
792 assert chartoken[0] == "/"
793 size = c.getint()
794 if first:
795 self.glyphrdtoken = c.gettoken()
796 else:
797 token = c.gettoken(); assert token == self.glyphrdtoken
798 self.glyphlist.append(chartoken[1:])
799 self.glyphs[chartoken[1:]] = c.getbytes(size)
800 if first:
801 self.glyphndtoken = c.gettoken()
802 else:
803 token = c.gettoken(); assert token == self.glyphndtoken
804 first = 0
805 self.charstringsend = c.pos
806 assert not self.subrs or self.subrrdtoken == self.glyphrdtoken
808 def _cmds(self, code):
809 """return a list of T1cmd's for encoded charstring data in code"""
810 code = array.array("B", self._charstringdecode(code))
811 cmds = []
812 while code:
813 x = code.pop(0)
814 if x == 12: # this starts an escaped cmd
815 cmds.append(T1subcmds[code.pop(0)])
816 elif 0 <= x < 32: # those are cmd's
817 cmds.append(T1cmds[x])
818 elif 32 <= x <= 246: # short ints
819 cmds.append(x-139)
820 elif 247 <= x <= 250: # mid size ints
821 cmds.append(((x - 247)*256) + code.pop(0) + 108)
822 elif 251 <= x <= 254: # mid size ints
823 cmds.append(-((x - 251)*256) - code.pop(0) - 108)
824 else: # x = 255, i.e. full size ints
825 y = ((code.pop(0)*256l+code.pop(0))*256+code.pop(0))*256+code.pop(0)
826 if y > (1l << 31):
827 cmds.append(y - (1l << 32))
828 else:
829 cmds.append(y)
830 return cmds
832 def _code(self, cmds):
833 """return an encoded charstring data for list of T1cmd's in cmds"""
834 code = array.array("B")
835 for cmd in cmds:
836 try:
837 if cmd.subcmd:
838 code.append(12)
839 code.append(cmd.code)
840 except AttributeError:
841 if -107 <= cmd <= 107:
842 code.append(cmd+139)
843 elif 108 <= cmd <= 1131:
844 a, b = divmod(cmd-108, 256)
845 code.append(a+247)
846 code.append(b)
847 elif -1131 <= cmd <= -108:
848 a, b = divmod(-cmd-108, 256)
849 code.append(a+251)
850 code.append(b)
851 else:
852 if cmd < 0:
853 cmd += 1l << 32
854 cmd, x4 = divmod(cmd, 256)
855 cmd, x3 = divmod(cmd, 256)
856 x1, x2 = divmod(cmd, 256)
857 code.append(255)
858 code.append(x1)
859 code.append(x2)
860 code.append(x3)
861 code.append(x4)
862 return self._charstringencode(code.tostring())
864 def getsubrcmds(self, subr):
865 """return a list of T1cmd's for subr subr"""
866 if not self._data2:
867 self._data2decode()
868 return self._cmds(self.subrs[subr])
870 def getglyphcmds(self, glyph):
871 """return a list of T1cmd's for glyph glyph"""
872 if not self._data2:
873 self._data2decode()
874 return self._cmds(self.glyphs[glyph])
876 def setsubrcmds(self, subr, cmds):
877 """replaces the T1cmd's by the list cmds for subr subr"""
878 if not self._data2:
879 self._data2decode()
880 self._data2eexec = None
881 self.subrs[subr] = self._code(cmds)
883 def setglyphcmds(self, glyph, cmds):
884 """replaces the T1cmd's by the list cmds for glyph glyph"""
885 if not self._data2:
886 self._data2decode()
887 self._data2eexec = None
888 self.glyphs[glyph] = self._code(cmds)
890 def updatepath(self, cmds, path, trafo, context):
891 for cmd in cmds:
892 if isinstance(cmd, T1cmd):
893 cmd.updatepath(path, trafo, context)
894 else:
895 context.t1stack.append(cmd)
897 def updatesubrpath(self, subr, path, trafo, context):
898 self.updatepath(self.getsubrcmds(subr), path, trafo, context)
900 def updateglyphpath(self, glyph, path, trafo, context):
901 self.updatepath(self.getglyphcmds(glyph), path, trafo, context)
903 def gathercalls(self, cmds, seacglyphs, subrs, othersubrs, context):
904 for cmd in cmds:
905 if isinstance(cmd, T1cmd):
906 cmd.gathercalls(seacglyphs, subrs, othersubrs, context)
907 else:
908 context.t1stack.append(cmd)
910 def gathersubrcalls(self, subr, seacglyphs, subrs, othersubrs, context):
911 self.gathercalls(self.getsubrcmds(subr), seacglyphs, subrs, othersubrs, context)
913 def gatherglyphcalls(self, glyph, seacglyphs, subrs, othersubrs, context):
914 self.gathercalls(self.getglyphcmds(glyph), seacglyphs, subrs, othersubrs, context)
916 def getglyphpathwxwy_pt(self, glyph, size):
917 t = self.fontmatrix.scaled(size)
918 context = T1context(self)
919 p = path()
920 self.updateglyphpath(glyph, p, t, context)
921 wx, wy = t.apply_pt(context.wx, context.wy)
922 return p, wx, wy
924 def getglyphpath(self, glyph, size):
925 """return a PyX path for glyph named glyph"""
926 return self.getglyphpathwxwy_pt(glyph, size)[0]
928 def getglyphwxwy_pt(self, glyph, size):
929 return self.getglyphpathwxwy_pt(glyph, size)[1:]
931 def getdata2(self, subrs=None, glyphs=None):
932 """makes a data2 string
934 subrs is a dict containing those subrs numbers as keys,
935 which are to be contained in the subrsstring to be created.
936 If subrs is None, all subrs in self.subrs will be used.
937 The subrs dict might be modified *in place*.
939 glyphs is a dict containing those glyph names as keys,
940 which are to be contained in the charstringsstring to be created.
941 If glyphs is None, all glyphs in self.glyphs will be used."""
942 def addsubrs(subrs, result):
943 if subrs is not None:
944 # some adjustments to the subrs dict
945 if subrs:
946 subrsindices = subrs.keys()
947 subrsmin = min(subrsindices)
948 subrsmax = max(subrsindices)
949 if self.hasflexhintsubrs and subrsmin < len(self.flexhintsubrs):
950 # According to the spec we need to keep all the flex and hint subrs
951 # as long as any of it is used.
952 for subr in range(len(self.flexhintsubrs)):
953 subrs[subr] = 1
954 else:
955 subrsmax = -1
956 else:
957 # build a new subrs dict containing all subrs
958 subrs = dict([(subr, 1) for subr in range(len(self.subrs))])
959 subrsmax = len(self.subrs) - 1
961 # build the string from all selected subrs
962 result.append("%d array\n" % (subrsmax + 1))
963 for subr in range(subrsmax+1):
964 if subrs.has_key(subr):
965 code = self.subrs[subr]
966 else:
967 code = self.emptysubr
968 result.append("dup %d %d %s %s %s\n" % (subr, len(code), self.subrrdtoken, code, self.subrnptoken))
970 def addcharstrings(glyphs, result):
971 result.append("%d dict dup begin\n" % (glyphs is None and len(self.glyphlist) or len(glyphs)))
972 for glyph in self.glyphlist:
973 if glyphs is None or glyph in glyphs:
974 result.append("/%s %d %s %s %s\n" % (glyph, len(self.glyphs[glyph]), self.glyphrdtoken, self.glyphs[glyph], self.glyphndtoken))
975 result.append("end\n")
977 if self.subrsstart < self.charstringsstart:
978 result = [self._data2[:self.subrsstart]]
979 addsubrs(subrs, result)
980 result.append(self._data2[self.subrsend:self.charstringsstart])
981 addcharstrings(glyphs, result)
982 result.append(self._data2[self.charstringsend:])
983 else:
984 result = [self._data2[:self.charstringsstart]]
985 addcharstrings(glyphs, result)
986 result.append(self._data2[self.charstringsend:self.subrsstart])
987 addsubrs(subrs, result)
988 result.append(self._data2[self.subrsend:])
989 return "".join(result)
991 def getdata2eexec(self):
992 if self._data2eexec:
993 return self._data2eexec
994 # note that self._data2 is out-of-date here too, hence we need to call getdata2
995 return self._eexecencode(self.getdata2())
997 newlinepattern = re.compile("\s*[\r\n]\s*")
998 uniqueidpattern = re.compile("/UniqueID\s+\d+\s+def\s+")
1000 def getstrippedfont(self, glyphs):
1001 """create a T1file instance containing only certain glyphs
1003 glyphs is a set of the glyph names. It might be modified *in place*!
1005 # TODO: we could also strip othersubrs to those actually used
1007 # collect information about used glyphs and subrs
1008 seacglyphs = set()
1009 subrs = set()
1010 othersubrs = set()
1011 for glyph in glyphs:
1012 self.gatherglyphcalls(glyph, seacglyphs, subrs, othersubrs, T1context(self))
1013 # while we have gathered all subrs for the seacglyphs alreadys, we
1014 # might have missed the glyphs themself (when they are not used stand-alone)
1015 glyphs.update(seacglyphs)
1016 glyphs.add(".notdef")
1018 # strip data1
1019 if not self.encoding:
1020 self._encoding()
1021 if self.encoding is encoding.adobestandardencoding:
1022 data1 = self.data1
1023 else:
1024 encodingstrings = []
1025 for char, glyph in enumerate(self.encoding.encvector):
1026 if glyph in glyphs:
1027 encodingstrings.append("dup %i /%s put\n" % (char, glyph))
1028 data1 = self.data1[:self.encodingstart] + "".join(encodingstrings) + self.data1[self.encodingend:]
1029 data1 = self.newlinepattern.subn("\n", data1)[0]
1030 data1 = self.uniqueidpattern.subn("", data1)[0]
1032 # strip data2
1033 data2 = self.uniqueidpattern.subn("", self.getdata2(subrs, glyphs))[0]
1035 # strip data3
1036 data3 = self.newlinepattern.subn("\n", self.data3)[0]
1038 # create and return the new font instance
1039 return T1file(data1.rstrip() + "\n", self._eexecencode(data2), data3.rstrip() + "\n")
1041 def getflags(self):
1042 # As a simple heuristics we assume non-symbolic fonts if and only
1043 # if the Adobe standard encoding is used. All other font flags are
1044 # not specified here.
1045 if not self.encoding:
1046 self._encoding()
1047 if self.encoding is encoding.adobestandardencoding:
1048 return 32
1049 return 4
1051 def outputPFA(self, file):
1052 """output the T1file in PFA format"""
1053 file.write(self.data1)
1054 data2eexechex = binascii.b2a_hex(self.getdata2eexec())
1055 linelength = 64
1056 for i in range((len(data2eexechex)-1)/linelength + 1):
1057 file.write(data2eexechex[i*linelength: i*linelength+linelength])
1058 file.write("\n")
1059 file.write(self.data3)
1061 def outputPFB(self, file):
1062 """output the T1file in PFB format"""
1063 data2eexec = self.getdata2eexec()
1064 def pfblength(data):
1065 l = len(data)
1066 l, x1 = divmod(l, 256)
1067 l, x2 = divmod(l, 256)
1068 x4, x3 = divmod(l, 256)
1069 return chr(x1) + chr(x2) + chr(x3) + chr(x4)
1070 file.write("\200\1")
1071 file.write(pfblength(self.data1))
1072 file.write(self.data1)
1073 file.write("\200\2")
1074 file.write(pfblength(data2eexec))
1075 file.write(data2eexec)
1076 file.write("\200\1")
1077 file.write(pfblength(self.data3))
1078 file.write(self.data3)
1079 file.write("\200\3")
1081 def outputPS(self, file, writer):
1082 """output the PostScript code for the T1file to the file file"""
1083 self.outputPFA(file)
1085 def outputPDF(self, file, writer):
1086 data2eexec = self.getdata2eexec()
1087 data3 = self.data3
1088 # we might be allowed to skip the third part ...
1089 if (data3.replace("\n", "")
1090 .replace("\r", "")
1091 .replace("\t", "")
1092 .replace(" ", "")) == "0"*512 + "cleartomark":
1093 data3 = ""
1095 data = self.data1 + data2eexec + data3
1096 if writer.compress and haszlib:
1097 data = zlib.compress(data)
1099 file.write("<<\n"
1100 "/Length %d\n"
1101 "/Length1 %d\n"
1102 "/Length2 %d\n"
1103 "/Length3 %d\n" % (len(data), len(self.data1), len(data2eexec), len(data3)))
1104 if writer.compress and haszlib:
1105 file.write("/Filter /FlateDecode\n")
1106 file.write(">>\n"
1107 "stream\n")
1108 file.write(data)
1109 file.write("\n"
1110 "endstream\n")
1113 class PFAfile(T1file):
1115 """create a T1file instance from a pfa font file"""
1117 def __init__(self, filename):
1118 d = open(filename, "rb").read()
1119 # hey, that's quick'n'dirty
1120 m1 = d.index("eexec") + 6
1121 m2 = d.index("0"*40)
1122 data1 = d[:m1]
1123 data2 = binascii.a2b_hex(d[m1: m2].replace(" ", "").replace("\r", "").replace("\n", ""))
1124 data3 = d[m2:]
1125 T1file.__init__(self, data1, data2, data3)
1128 class PFBfile(T1file):
1130 """create a T1file instance from a pfb font file"""
1132 def __init__(self, filename):
1133 def pfblength(s):
1134 if len(s) != 4:
1135 raise ValueError("invalid string length")
1136 return (ord(s[0]) +
1137 ord(s[1])*256 +
1138 ord(s[2])*256*256 +
1139 ord(s[3])*256*256*256)
1140 f = open(filename, "rb")
1141 mark = f.read(2); assert mark == "\200\1"
1142 data1 = f.read(pfblength(f.read(4)))
1143 mark = f.read(2); assert mark == "\200\2"
1144 data2 = ""
1145 while mark == "\200\2":
1146 data2 = data2 + f.read(pfblength(f.read(4)))
1147 mark = f.read(2)
1148 assert mark == "\200\1"
1149 data3 = f.read(pfblength(f.read(4)))
1150 mark = f.read(2); assert mark == "\200\3"
1151 assert not f.read(1)
1152 T1file.__init__(self, data1, data2, data3)