various fixes after cleanup
[PyX/mjg.git] / pyx / font / t1font.py
bloba8ce76d9f96b60927756ffb9590f05bbb60bca90
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
25 import array, binascii, re
26 try:
27 import zlib
28 haszlib = 1
29 except ImportError:
30 haszlib = 0
32 from pyx import trafo
33 from pyx.path import path, moveto_pt, lineto_pt, curveto_pt, closepath
34 import encoding
36 try:
37 from _t1code import *
38 except:
39 from t1code import *
42 class T1context:
44 def __init__(self, t1font):
45 """context for T1cmd evaluation"""
46 self.t1font = t1font
48 # state description
49 self.x = None
50 self.y = None
51 self.wx = None
52 self.wy = None
53 self.t1stack = []
54 self.psstack = []
57 ######################################################################
58 # T1 commands
59 # Note, that all commands except for the T1value are variable-free and
60 # are thus implemented as instances.
62 class _T1cmd:
64 def __str__(self):
65 """returns a string representation of the T1 command"""
66 raise NotImplementedError
68 def updatepath(self, path, trafo, context):
69 """update path instance applying trafo to the points"""
70 raise NotImplementedError
72 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
73 """gather dependancy information
75 subrs is the "called-subrs" dictionary. gathercalls will insert the
76 subrnumber as key having the value 1, i.e. subrs.keys() will become the
77 numbers of used subrs. Similar seacglyphs will contain all glyphs in
78 composite characters (subrs and othersubrs for those glyphs will also
79 already be included) and othersubrs the othersubrs called.
81 This method might will not properly update all information in the
82 context (especially consuming values from the stack) and will also skip
83 various tests for performance reasons. For most T1 commands it just
84 doesn't need to do anything.
85 """
86 pass
89 class T1value(_T1cmd):
91 def __init__(self, value):
92 self.value = value
94 def __str__(self):
95 return str(self.value)
97 def updatepath(self, path, trafo, context):
98 context.t1stack.append(self.value)
100 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
101 context.t1stack.append(self.value)
103 def __eq__(self, other):
104 # while we can compare the other commands, since they are instances,
105 # for T1value we need to compare its values
106 if isinstance(other, T1value):
107 return self.value == other.value
108 else:
109 return 0
112 # commands for starting and finishing
114 class _T1endchar(_T1cmd):
116 def __str__(self):
117 return "endchar"
119 def updatepath(self, path, trafo, context):
120 pass
122 T1endchar = _T1endchar()
125 class _T1hsbw(_T1cmd):
127 def __str__(self):
128 return "hsbw"
130 def updatepath(self, path, trafo, context):
131 sbx = context.t1stack.pop(0)
132 wx = context.t1stack.pop(0)
133 path.append(moveto_pt(*trafo.apply_pt(sbx, 0)))
134 context.x = sbx
135 context.y = 0
136 context.wx = wx
137 context.wy = 0
139 T1hsbw = _T1hsbw()
142 class _T1seac(_T1cmd):
144 def __str__(self):
145 return "seac"
147 def updatepath(self, path, atrafo, context):
148 sab = context.t1stack.pop(0)
149 adx = context.t1stack.pop(0)
150 ady = context.t1stack.pop(0)
151 bchar = context.t1stack.pop(0)
152 achar = context.t1stack.pop(0)
153 for cmd in context.t1font.getglyphcmds(encoding.adobestandardencoding.decode(bchar)):
154 cmd.updatepath(path, atrafo, context)
155 atrafo = atrafo * trafo.translate_pt(adx-sab, ady)
156 for cmd in context.t1font.getglyphcmds(encoding.adobestandardencoding.decode(achar)):
157 cmd.updatepath(path, atrafo, context)
159 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
160 bchar = context.t1stack.pop()
161 achar = context.t1stack.pop()
162 aglyph = encoding.adobestandardencoding.decode(achar)
163 bglyph = encoding.adobestandardencoding.decode(bchar)
164 seacglyphs[aglyph] = 1
165 seacglyphs[bglyph] = 1
166 for cmd in context.t1font.getglyphcmds(bglyph):
167 cmd.gathercalls(seacglyphs, subrs, othersubrs, context)
168 for cmd in context.t1font.getglyphcmds(aglyph):
169 cmd.gathercalls(seacglyphs, subrs, othersubrs, context)
171 T1seac = _T1seac()
174 class _T1sbw(_T1cmd):
176 def __str__(self):
177 return "sbw"
179 def updatepath(self, path, trafo, context):
180 sbx = context.t1stack.pop(0)
181 sby = context.t1stack.pop(0)
182 wx = context.t1stack.pop(0)
183 wy = context.t1stack.pop(0)
184 path.append(moveto_pt(*trafo.apply_pt(sbx, sby)))
185 context.x = sbx
186 context.y = sby
187 context.wx = wx
188 context.wy = wy
190 T1sbw = _T1sbw()
193 # path construction commands
195 class _T1closepath(_T1cmd):
197 def __str__(self):
198 return "closepath"
200 def updatepath(self, path, trafo, context):
201 path.append(closepath())
202 # The closepath in T1 is different from PostScripts in that it does
203 # *not* modify the current position; hence we need to add an additional
204 # moveto here ...
205 path.append(moveto_pt(*trafo.apply_pt(context.x, context.y)))
207 T1closepath = _T1closepath()
210 class _T1hlineto(_T1cmd):
212 def __str__(self):
213 return "hlineto"
215 def updatepath(self, path, trafo, context):
216 dx = context.t1stack.pop(0)
217 path.append(lineto_pt(*trafo.apply_pt(context.x + dx, context.y)))
218 context.x += dx
220 T1hlineto = _T1hlineto()
223 class _T1hmoveto(_T1cmd):
225 def __str__(self):
226 return "hmoveto"
228 def updatepath(self, path, trafo, context):
229 dx = context.t1stack.pop(0)
230 path.append(moveto_pt(*trafo.apply_pt(context.x + dx, context.y)))
231 context.x += dx
233 T1hmoveto = _T1hmoveto()
236 class _T1hvcurveto(_T1cmd):
238 def __str__(self):
239 return "hvcurveto"
241 def updatepath(self, path, trafo, context):
242 dx1 = context.t1stack.pop(0)
243 dx2 = context.t1stack.pop(0)
244 dy2 = context.t1stack.pop(0)
245 dy3 = context.t1stack.pop(0)
246 path.append(curveto_pt(*(trafo.apply_pt(context.x + dx1, context.y) +
247 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy2) +
248 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy2 + dy3))))
249 context.x += dx1+dx2
250 context.y += dy2+dy3
252 T1hvcurveto = _T1hvcurveto()
255 class _T1rlineto(_T1cmd):
257 def __str__(self):
258 return "rlineto"
260 def updatepath(self, path, trafo, context):
261 dx = context.t1stack.pop(0)
262 dy = context.t1stack.pop(0)
263 path.append(lineto_pt(*trafo.apply_pt(context.x + dx, context.y + dy)))
264 context.x += dx
265 context.y += dy
267 T1rlineto = _T1rlineto()
270 class _T1rmoveto(_T1cmd):
272 def __str__(self):
273 return "rmoveto"
275 def updatepath(self, path, trafo, context):
276 dx = context.t1stack.pop(0)
277 dy = context.t1stack.pop(0)
278 path.append(moveto_pt(*trafo.apply_pt(context.x + dx, context.y + dy)))
279 context.x += dx
280 context.y += dy
282 T1rmoveto = _T1rmoveto()
285 class _T1rrcurveto(_T1cmd):
287 def __str__(self):
288 return "rrcurveto"
290 def updatepath(self, path, trafo, context):
291 dx1 = context.t1stack.pop(0)
292 dy1 = context.t1stack.pop(0)
293 dx2 = context.t1stack.pop(0)
294 dy2 = context.t1stack.pop(0)
295 dx3 = context.t1stack.pop(0)
296 dy3 = context.t1stack.pop(0)
297 path.append(curveto_pt(*(trafo.apply_pt(context.x + dx1, context.y + dy1) +
298 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy1 + dy2) +
299 trafo.apply_pt(context.x + dx1 + dx2 + dx3, context.y + dy1 + dy2 + dy3))))
300 context.x += dx1+dx2+dx3
301 context.y += dy1+dy2+dy3
303 T1rrcurveto = _T1rrcurveto()
306 class _T1vlineto(_T1cmd):
308 def __str__(self):
309 return "vlineto"
311 def updatepath(self, path, trafo, context):
312 dy = context.t1stack.pop(0)
313 path.append(lineto_pt(*trafo.apply_pt(context.x, context.y + dy)))
314 context.y += dy
316 T1vlineto = _T1vlineto()
319 class _T1vmoveto(_T1cmd):
321 def __str__(self):
322 return "vmoveto"
324 def updatepath(self, path, trafo, context):
325 dy = context.t1stack.pop(0)
326 path.append(moveto_pt(*trafo.apply_pt(context.x, context.y + dy)))
327 context.y += dy
329 T1vmoveto = _T1vmoveto()
332 class _T1vhcurveto(_T1cmd):
334 def __str__(self):
335 return "vhcurveto"
337 def updatepath(self, path, trafo, context):
338 dy1 = context.t1stack.pop(0)
339 dx2 = context.t1stack.pop(0)
340 dy2 = context.t1stack.pop(0)
341 dx3 = context.t1stack.pop(0)
342 path.append(curveto_pt(*(trafo.apply_pt(context.x, context.y + dy1) +
343 trafo.apply_pt(context.x + dx2, context.y + dy1 + dy2) +
344 trafo.apply_pt(context.x + dx2 + dx3, context.y + dy1 + dy2))))
345 context.x += dx2+dx3
346 context.y += dy1+dy2
348 T1vhcurveto = _T1vhcurveto()
351 # hint commands
353 class _T1dotsection(_T1cmd):
355 def __str__(self):
356 return "dotsection"
358 def updatepath(self, path, trafo, context):
359 pass
361 T1dotsection = _T1dotsection()
364 class _T1hstem(_T1cmd):
366 def __str__(self):
367 return "hstem"
369 def updatepath(self, path, trafo, context):
370 y = context.t1stack.pop(0)
371 dy = context.t1stack.pop(0)
373 T1hstem = _T1hstem()
376 class _T1hstem3(_T1cmd):
378 def __str__(self):
379 return "hstem3"
381 def updatepath(self, path, trafo, context):
382 y0 = context.t1stack.pop(0)
383 dy0 = context.t1stack.pop(0)
384 y1 = context.t1stack.pop(0)
385 dy1 = context.t1stack.pop(0)
386 y2 = context.t1stack.pop(0)
387 dy2 = context.t1stack.pop(0)
389 T1hstem3 = _T1hstem3()
392 class _T1vstem(_T1cmd):
394 def __str__(self):
395 return "hstem"
397 def updatepath(self, path, trafo, context):
398 x = context.t1stack.pop(0)
399 dx = context.t1stack.pop(0)
401 T1vstem = _T1vstem()
404 class _T1vstem3(_T1cmd):
406 def __str__(self):
407 return "hstem3"
409 def updatepath(self, path, trafo, context):
410 self.x0 = context.t1stack.pop(0)
411 self.dx0 = context.t1stack.pop(0)
412 self.x1 = context.t1stack.pop(0)
413 self.dx1 = context.t1stack.pop(0)
414 self.x2 = context.t1stack.pop(0)
415 self.dx2 = context.t1stack.pop(0)
417 T1vstem3 = _T1vstem3()
420 # arithmetic command
422 class _T1div(_T1cmd):
424 def __str__(self):
425 return "div"
427 def updatepath(self, path, trafo, context):
428 num2 = context.t1stack.pop()
429 num1 = context.t1stack.pop()
430 context.t1stack.append(divmod(num1, num2)[0])
432 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
433 num2 = context.t1stack.pop()
434 num1 = context.t1stack.pop()
435 context.t1stack.append(divmod(num1, num2)[0])
437 T1div = _T1div()
440 # subroutine commands
442 class _T1callothersubr(_T1cmd):
444 def __str__(self):
445 return "callothersubr"
447 def updatepath(self, path, trafo, context):
448 othersubrnumber = context.t1stack.pop()
449 n = context.t1stack.pop()
450 for i in range(n):
451 context.psstack.append(context.t1stack.pop())
453 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
454 othersubrnumber = context.t1stack.pop()
455 othersubrs[othersubrnumber] = 1
456 n = context.t1stack.pop()
457 for i in range(n):
458 context.psstack.append(context.t1stack.pop())
460 T1callothersubr = _T1callothersubr()
463 class _T1callsubr(_T1cmd):
465 def __str__(self):
466 return "callsubr"
468 def updatepath(self, path, trafo, context):
469 subrnumber = context.t1stack.pop()
470 for cmd in context.t1font.getsubrcmds(subrnumber):
471 cmd.updatepath(path, trafo, context)
473 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
474 subrnumber = context.t1stack.pop()
475 subrs[subrnumber] = 1
476 for cmd in context.t1font.getsubrcmds(subrnumber):
477 cmd.gathercalls(seacglyphs, subrs, othersubrs, context)
479 T1callsubr = _T1callsubr()
482 class _T1pop(_T1cmd):
484 def __str__(self):
485 return "pop"
487 def updatepath(self, path, trafo, context):
488 context.t1stack.append(context.psstack.pop())
490 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
491 context.t1stack.append(context.psstack.pop())
493 T1pop = _T1pop()
496 class _T1return(_T1cmd):
498 def __str__(self):
499 return "return"
501 def updatepath(self, path, trafo, context):
502 pass
504 T1return = _T1return()
507 class _T1setcurrentpoint(_T1cmd):
509 def __str__(self):
510 return "setcurrentpoint" % self.x, self.y
512 def updatepath(self, path, trafo, context):
513 x = context.t1stack.pop(0)
514 y = context.t1stack.pop(0)
515 path.append(moveto_pt(*trafo.apply_pt(x, y)))
516 context.x = x
517 context.y = y
519 T1setcurrentpoint = _T1setcurrentpoint()
522 ######################################################################
524 class cursor:
525 """cursor to read a string token by token"""
527 def __init__(self, data, startstring, eattokensep=1, tokenseps=" \t\r\n", tokenstarts="()<>[]{}/%"):
528 """creates a cursor for the string data
530 startstring is a string at which the cursor should start at. The first
531 ocurance of startstring is used. When startstring is not in data, an
532 exception is raised, otherwise the cursor is set to the position right
533 after the startstring. When eattokenseps is set, startstring must be
534 followed by a tokensep and this first tokensep is also consumed.
535 tokenseps is a string containing characters to be used as token
536 separators. tokenstarts is a string containing characters which
537 directly (even without intermediate token separator) start a new token.
539 self.data = data
540 self.pos = self.data.index(startstring) + len(startstring)
541 self.tokenseps = tokenseps
542 self.tokenstarts = tokenstarts
543 if eattokensep:
544 if self.data[self.pos] not in self.tokenstarts:
545 if self.data[self.pos] not in self.tokenseps:
546 raise ValueError("cursor initialization string is not followed by a token separator")
547 self.pos += 1
549 def gettoken(self):
550 """get the next token
552 Leading token separators and comments are silently consumed. The first token
553 separator after the token is also silently consumed."""
554 while self.data[self.pos] in self.tokenseps:
555 self.pos += 1
556 # ignore comments including subsequent whitespace characters
557 while self.data[self.pos] == "%":
558 while self.data[self.pos] not in "\r\n":
559 self.pos += 1
560 while self.data[self.pos] in self.tokenseps:
561 self.pos += 1
562 startpos = self.pos
563 while self.data[self.pos] not in self.tokenseps:
564 # any character in self.tokenstarts ends the token
565 if self.pos>startpos and self.data[self.pos] in self.tokenstarts:
566 break
567 self.pos += 1
568 result = self.data[startpos:self.pos]
569 if self.data[self.pos] in self.tokenseps:
570 self.pos += 1 # consume a single tokensep
571 return result
573 def getint(self):
574 """get the next token as an integer"""
575 return int(self.gettoken())
577 def getbytes(self, count):
578 """get the next count bytes"""
579 startpos = self.pos
580 self.pos += count
581 return self.data[startpos: self.pos]
584 class T1font:
586 eexecr = 55665
587 charstringr = 4330
589 def __init__(self, data1, data2eexec, data3):
590 """initializes a t1font instance
592 data1 and data3 are the two clear text data parts and data2 is
593 the binary data part"""
594 self.data1 = data1
595 self.data2eexec = data2eexec
596 self.data3 = data3
598 # marker and value for decoded data
599 self.data2 = None
601 # marker and value for standard encoding check
602 self.encoding = None
604 def _eexecdecode(self, code):
605 """eexec decoding of code"""
606 return decoder(code, self.eexecr, 4)
608 def _charstringdecode(self, code):
609 """charstring decoding of code"""
610 return decoder(code, self.charstringr, self.lenIV)
612 def _eexecencode(self, data):
613 """eexec encoding of data"""
614 return encoder(data, self.eexecr, "PyX!")
616 def _charstringencode(self, data):
617 """eexec encoding of data"""
618 return encoder(data, self.charstringr, "PyX!"[:self.lenIV])
620 lenIVpattern = re.compile("/lenIV\s+(\d+)\s+def\s+")
621 flexhintsubrs = [[T1value(3), T1value(0), T1callothersubr, T1pop, T1pop, T1setcurrentpoint, T1return],
622 [T1value(0), T1value(1), T1callothersubr, T1return],
623 [T1value(0), T1value(2), T1callothersubr, T1return],
624 [T1return]]
626 def _encoding(self):
627 c = cursor(self.data1, "/Encoding")
628 token1 = c.gettoken()
629 token2 = c.gettoken()
630 if token1 == "StandardEncoding" and token2 == "def":
631 self.encoding = encoding.adobestandardencoding
632 else:
633 encvector = [None]*256
634 while 1:
635 self.encodingstart = c.pos
636 if c.gettoken() == "dup":
637 break
638 while 1:
639 i = c.getint()
640 glyph = c.gettoken()
641 if 0 <= i < 256:
642 encvector[i] = glyph[1:]
643 token = c.gettoken(); assert token == "put"
644 self.encodingend = c.pos
645 token = c.gettoken()
646 if token == "readonly" or token == "def":
647 break
648 assert token == "dup"
649 self.encoding = encoding.encoding(encvector)
651 def _data2decode(self):
652 """decodes data2eexec to the data2 string and the subr and glyphs dictionary
654 It doesn't make sense to call this method twice -- check the content of
655 data2 before calling. The method also keeps the subrs and charstrings
656 start and end positions for later replacement by stripped data.
659 self.data2 = self._eexecdecode(self.data2eexec)
661 m = self.lenIVpattern.search(self.data2)
662 if m:
663 self.lenIV = int(m.group(1))
664 else:
665 self.lenIV = 4
666 self.emptysubr = self._charstringencode(chr(11))
668 # extract Subrs
669 c = cursor(self.data2, "/Subrs")
670 self.subrsstart = c.pos
671 arraycount = c.getint()
672 token = c.gettoken(); assert token == "array"
673 self.subrs = []
674 for i in range(arraycount):
675 token = c.gettoken(); assert token == "dup"
676 token = c.getint(); assert token == i
677 size = c.getint()
678 if not i:
679 self.subrrdtoken = c.gettoken()
680 else:
681 token = c.gettoken(); assert token == self.subrrdtoken
682 self.subrs.append(c.getbytes(size))
683 token = c.gettoken()
684 if token == "noaccess":
685 token = "%s %s" % (token, c.gettoken())
686 if not i:
687 self.subrnptoken = token
688 else:
689 assert token == self.subrnptoken
690 self.subrsend = c.pos
692 # hasflexhintsubrs is a boolean indicating that the font uses flex or
693 # hint replacement subrs as specified by Adobe (tm). When it does, the
694 # first 4 subrs should all be copied except when none of them are used
695 # in the stripped version of the font since we than get a font not
696 # using flex or hint replacement subrs at all.
697 self.hasflexhintsubrs = (arraycount >= len(self.flexhintsubrs) and
698 [self.getsubrcmds(i)
699 for i in range(len(self.flexhintsubrs))] == self.flexhintsubrs)
701 # extract glyphs
702 self.glyphs = {}
703 self.glyphlist = [] # we want to keep the order of the glyph names
704 c = cursor(self.data2, "/CharStrings")
705 self.charstingsstart = c.pos
706 c.getint()
707 token = c.gettoken(); assert token == "dict"
708 token = c.gettoken(); assert token == "dup"
709 token = c.gettoken(); assert token == "begin"
710 first = 1
711 while 1:
712 chartoken = c.gettoken()
713 if chartoken == "end":
714 break
715 assert chartoken[0] == "/"
716 size = c.getint()
717 if first:
718 self.glyphrdtoken = c.gettoken()
719 else:
720 token = c.gettoken(); assert token == self.glyphrdtoken
721 self.glyphlist.append(chartoken[1:])
722 self.glyphs[chartoken[1:]] = c.getbytes(size)
723 if first:
724 self.glyphndtoken = c.gettoken()
725 else:
726 token = c.gettoken(); assert token == self.glyphndtoken
727 first = 0
728 self.charstingsend = c.pos
729 assert not self.subrs or self.subrrdtoken == self.glyphrdtoken
731 def _cmds(self, code):
732 """return a list of T1cmd's for encoded charstring data in code"""
733 code = array.array("B", self._charstringdecode(code))
734 cmds = []
735 while code:
736 x = code.pop(0)
737 if 0 <= x < 32: # those are cmd's
738 try:
739 cmds.append({1: T1hstem,
740 3: T1vstem,
741 4: T1vmoveto,
742 5: T1rlineto,
743 6: T1hlineto,
744 7: T1vlineto,
745 8: T1rrcurveto,
746 9: T1closepath,
747 10: T1callsubr,
748 11: T1return,
749 13: T1hsbw,
750 14: T1endchar,
751 21: T1rmoveto,
752 22: T1hmoveto,
753 30: T1vhcurveto,
754 31: T1hvcurveto}[x])
755 except KeyError:
756 if x == 12: # this starts an escaped cmd
757 x = code.pop(0)
758 try:
759 cmds.append({0: T1dotsection,
760 1: T1vstem3,
761 2: T1hstem3,
762 6: T1seac,
763 7: T1sbw,
764 12: T1div,
765 16: T1callothersubr,
766 17: T1pop,
767 33: T1setcurrentpoint}[x])
768 except KeyError:
769 raise ValueError("invalid escaped command %d" % x)
770 else:
771 raise ValueError("invalid command %d" % x)
772 elif 32 <= x <= 246: # short ints
773 cmds.append(T1value(x-139))
774 elif 247 <= x <= 250: # mid size ints
775 cmds.append(T1value(((x - 247)*256) + code.pop(0) + 108))
776 elif 251 <= x <= 254: # mid size ints
777 cmds.append(T1value(-((x - 251)*256) - code.pop(0) - 108))
778 else: # x = 255, i.e. full size ints
779 y = ((code.pop(0)*256+code.pop(0))*256+code.pop(0))*256+code.pop(0)
780 if y > (1l << 31):
781 cmds.append(T1value(y - (1l << 32)))
782 else:
783 cmds.append(T1value(y))
784 return cmds
786 def getsubrcmds(self, n):
787 """return a list of T1cmd's for subr n"""
788 if not self.data2:
789 self._data2decode()
790 return self._cmds(self.subrs[n])
792 def getglyphcmds(self, glyph):
793 """return a list of T1cmd's for glyph glyph"""
794 if not self.data2:
795 self._data2decode()
796 return self._cmds(self.glyphs[glyph])
798 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")
800 def getglyphpath(self, glyph, size):
801 """return a PyX path for glyph named glyph"""
802 m = self.fontmatrixpattern.search(self.data1)
803 m11, m12, m21, m22, v1, v2 = map(float, m.groups()[:6])
804 t = trafo.trafo_pt(matrix=((m11, m12), (m21, m22)), vector=(v1, v2)).scaled(size)
805 context = T1context(self)
806 p = path()
807 for cmd in self.getglyphcmds(glyph):
808 cmd.updatepath(p, t, context)
809 p.wx_pt, p.wy_pt = t.apply_pt(context.wx, context.wy)
810 return p
812 newlinepattern = re.compile("\s*[\r\n]\s*")
813 uniqueidpattern = re.compile("/UniqueID\s+\d+\s+def\s+")
815 def getstrippedfont(self, glyphs):
816 """create a T1font instance containing only certain glyphs
818 glyphs is a list of glyph names to be contained.
821 # collect information about used glyphs and subrs
822 seacglyphs = {}
823 subrs = {}
824 othersubrs = {}
825 for glyph in glyphs:
826 context = T1context(self)
827 for cmd in self.getglyphcmds(glyph):
828 cmd.gathercalls(seacglyphs, subrs, othersubrs, context)
829 for glyph in seacglyphs.keys():
830 if glyph not in glyphs:
831 glyphs.append(glyph)
832 if ".notdef" not in glyphs:
833 glyphs.append(".notdef")
835 # strip subrs to those actually used
836 subrs = subrs.keys()
837 subrs.sort()
838 if subrs:
839 if self.hasflexhintsubrs and subrs[0] < len(self.flexhintsubrs):
840 # According to the spec we need to keep all the flex and hint subrs
841 # as long as any of it is used.
842 while subrs and subrs[0] < len(self.flexhintsubrs):
843 del subrs[0]
844 subrs = list(range(len(self.flexhintsubrs))) + subrs
845 count = subrs[-1]+1
846 else:
847 count = 0
848 strippedsubrs = ["%d array\n" % count]
849 for subr in range(count):
850 if subr in subrs:
851 code = self.subrs[subr]
852 else:
853 code = self.emptysubr
854 strippedsubrs.append("dup %d %d %s %s %s\n" % (subr, len(code), self.subrrdtoken, code, self.subrnptoken))
855 strippedsubrs = "".join(strippedsubrs)
857 # strip charstrings (i.e. glyphs) to those actually used
858 strippedcharstrings = ["%d dict dup begin\n" % len(glyphs)]
859 for glyph in self.glyphlist:
860 if glyph in glyphs:
861 strippedcharstrings.append("/%s %d %s %s %s\n" % (glyph, len(self.glyphs[glyph]), self.glyphrdtoken, self.glyphs[glyph], self.glyphndtoken))
862 strippedcharstrings.append("end\n")
863 strippedcharstrings = "".join(strippedcharstrings)
865 # TODO: we could also strip othersubrs to those actually used
867 # strip data1
868 if not self.encoding:
869 self._encoding()
870 if self.encoding is encoding.adobestandardencoding:
871 data1 = self.data1
872 else:
873 encodingstrings = []
874 for char, glyph in enumerate(self.encoding.encvector):
875 if glyph in glyphs:
876 encodingstrings.append("dup %i /%s put\n" % (char, glyph))
877 data1 = self.data1[:self.encodingstart] + "".join(encodingstrings) + self.data1[self.encodingend:]
878 data1 = self.newlinepattern.subn("\n", data1)[0]
879 data1 = self.uniqueidpattern.subn("", data1)[0]
881 # strip data2
882 # TODO: in the future, for full control, we might want to write data2 as well as data1 and data3 from scratch
883 if self.subrsstart < self.charstingsstart:
884 data2 = self.data2[:self.charstingsstart] + strippedcharstrings + self.data2[self.charstingsend:]
885 data2 = data2[:self.subrsstart] + strippedsubrs + data2[self.subrsend:]
886 else:
887 data2 = self.data2[:self.subrsstart] + strippedsubrs + self.data2[self.subrsend:]
888 data2 = data2[:self.charstingsstart] + strippedcharstrings + data2[self.charstingsend:]
889 data2 = self.uniqueidpattern.subn("", data2)[0]
891 # strip data3
892 data3 = self.newlinepattern.subn("\n", self.data3)[0]
894 # create and return the new font instance
895 return T1font(data1, self._eexecencode(data2), data3.rstrip())
897 def outputPS(self, file, writer):
898 """output the PostScript code for the T1font to the file file"""
899 file.write(self.data1)
900 data2eexechex = binascii.b2a_hex(self.data2eexec)
901 linelength = 64
902 for i in range((len(data2eexechex)-1)/linelength + 1):
903 file.write(data2eexechex[i*linelength: i*linelength+linelength])
904 file.write("\n")
905 file.write(self.data3)
907 def getflags(self):
908 # As a simple heuristics we assume non-symbolic fonts if and only
909 # if the Adobe standard encoding is used. All other font flags are
910 # not specified here.
911 if not self.encoding:
912 self._encoding()
913 if self.encoding is encoding.adobestandardencoding:
914 return 32
915 return 4
917 def outputPDF(self, file, writer):
918 data3 = self.data3
919 # we might be allowed to skip the third part ...
920 if (data3.replace("\n", "")
921 .replace("\r", "")
922 .replace("\t", "")
923 .replace(" ", "")) == "0"*512 + "cleartomark":
924 data3 = ""
926 data = self.data1 + self.data2eexec + data3
927 if writer.compress and haszlib:
928 data = zlib.compress(data)
930 file.write("<<\n"
931 "/Length %d\n"
932 "/Length1 %d\n"
933 "/Length2 %d\n"
934 "/Length3 %d\n" % (len(data), len(self.data1), len(self.data2eexec), len(data3)))
935 if writer.compress and haszlib:
936 file.write("/Filter /FlateDecode\n")
937 file.write(">>\n"
938 "stream\n")
939 file.write(data)
940 file.write("\n"
941 "endstream\n")
944 class T1pfafont(T1font):
946 """create a T1font instance from a pfa font file"""
948 def __init__(self, filename):
949 d = open(filename, "rb").read()
950 # hey, that's quick'n'dirty
951 m1 = d.index("eexec") + 6
952 m2 = d.index("0"*40)
953 data1 = d[:m1]
954 data2 = binascii.a2b_hex(d[m1: m2].replace(" ", "").replace("\r", "").replace("\n", ""))
955 data3 = d[m2:]
956 T1font.__init__(self, data1, data2, data3)
959 class T1pfbfont(T1font):
961 """create a T1font instance from a pfb font file"""
963 def __init__(self, filename):
964 def pfblength(s):
965 if len(s) != 4:
966 raise ValueError("invalid string length")
967 return (ord(s[0]) +
968 ord(s[1])*256 +
969 ord(s[2])*256*256 +
970 ord(s[3])*256*256*256)
971 f = open(filename, "rb")
972 mark = f.read(2); assert mark == "\200\1"
973 data1 = f.read(pfblength(f.read(4)))
974 mark = f.read(2); assert mark == "\200\2"
975 data2 = ""
976 while mark == "\200\2":
977 data2 = data2 + f.read(pfblength(f.read(4)))
978 mark = f.read(2)
979 assert mark == "\200\1"
980 data3 = f.read(pfblength(f.read(4)))
981 mark = f.read(2); assert mark == "\200\3"
982 assert not f.read(1)
983 T1font.__init__(self, data1, data2, data3)