cache textpath in T1text
[PyX/mjg.git] / pyx / font / t1file.py
blobc1664762f884380a3addc7be4afc8826b536b24b
1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2005-2011 André Wobst <wobsta@users.sourceforge.net>
5 # Copyright (C) 2006-2011 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 import array, binascii, math, re, warnings
24 try:
25 import zlib
26 haszlib = 1
27 except ImportError:
28 haszlib = 0
31 from pyx import trafo, reader, pycompat
32 from pyx.path import path, moveto_pt, lineto_pt, curveto_pt, closepath
34 try:
35 from _t1code import *
36 except:
37 from t1code import *
40 adobestandardencoding = [None, None, None, None, None, None, None, None,
41 None, None, None, None, None, None, None, None,
42 None, None, None, None, None, None, None, None,
43 None, None, None, None, None, None, None, None,
44 "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quoteright",
45 "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash",
46 "zero", "one", "two", "three", "four", "five", "six", "seven",
47 "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question",
48 "at", "A", "B", "C", "D", "E", "F", "G",
49 "H", "I", "J", "K", "L", "M", "N", "O",
50 "P", "Q", "R", "S", "T", "U", "V", "W",
51 "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore",
52 "quoteleft", "a", "b", "c", "d", "e", "f", "g",
53 "h", "i", "j", "k", "l", "m", "n", "o",
54 "p", "q", "r", "s", "t", "u", "v", "w",
55 "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", None,
56 None, None, None, None, None, None, None, None,
57 None, None, None, None, None, None, None, None,
58 None, None, None, None, None, None, None, None,
59 None, None, None, None, None, None, None, None,
60 None, "exclamdown", "cent", "sterling", "fraction", "yen", "florin", "section",
61 "currency", "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl",
62 None, "endash", "dagger", "daggerdbl", "periodcentered", None, "paragraph", "bullet",
63 "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand", None, "questiondown",
64 None, "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent",
65 "dieresis", None, "ring", "cedilla", None, "hungarumlaut", "ogonek", "caron",
66 "emdash", None, None, None, None, None, None, None,
67 None, None, None, None, None, None, None, None,
68 None, "AE", None, "ordfeminine", None, None, None, None,
69 "Lslash", "Oslash", "OE", "ordmasculine", None, None, None, None,
70 None, "ae", None, None, None, "dotlessi", None, None,
71 "lslash", "oslash", "oe", "germandbls", None, None, None, None]
73 class T1context:
75 def __init__(self, t1font):
76 """context for T1cmd evaluation"""
77 self.t1font = t1font
79 # state description
80 self.x = None
81 self.y = None
82 self.wx = None
83 self.wy = None
84 self.t1stack = []
85 self.psstack = []
88 ######################################################################
89 # T1 commands
90 # Note, that the T1 commands are variable-free except for plain number,
91 # which are stored as integers. All other T1 commands exist as a single
92 # instance only
94 T1cmds = {}
95 T1subcmds = {}
97 class T1cmd:
99 def __init__(self, code, subcmd=0):
100 self.code = code
101 self.subcmd = subcmd
102 if subcmd:
103 T1subcmds[code] = self
104 else:
105 T1cmds[code] = self
107 def __str__(self):
108 """returns a string representation of the T1 command"""
109 raise NotImplementedError
111 def updatepath(self, path, trafo, context):
112 """update path instance applying trafo to the points"""
113 raise NotImplementedError
115 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
116 """gather dependancy information
118 subrs is the "called-subrs" dictionary. gathercalls will insert the
119 subr number as key having the value 1, i.e. subrs will become the
120 numbers of used subrs. Similar seacglyphs will contain all glyphs in
121 composite characters (subrs and othersubrs for those glyphs will also
122 already be included) and othersubrs the othersubrs called.
124 This method might will not properly update all information in the
125 context (especially consuming values from the stack) and will also skip
126 various tests for performance reasons. For most T1 commands it just
127 doesn't need to do anything.
129 pass
132 # commands for starting and finishing
134 class _T1endchar(T1cmd):
136 def __init__(self):
137 T1cmd.__init__(self, 14)
139 def __str__(self):
140 return "endchar"
142 def updatepath(self, path, trafo, context):
143 pass
145 T1endchar = _T1endchar()
148 class _T1hsbw(T1cmd):
150 def __init__(self):
151 T1cmd.__init__(self, 13)
153 def __str__(self):
154 return "hsbw"
156 def updatepath(self, path, trafo, context):
157 sbx = context.t1stack.pop(0)
158 wx = context.t1stack.pop(0)
159 path.append(moveto_pt(*trafo.apply_pt(sbx, 0)))
160 context.x = sbx
161 context.y = 0
162 context.wx = wx
163 context.wy = 0
165 T1hsbw = _T1hsbw()
168 class _T1seac(T1cmd):
170 def __init__(self):
171 T1cmd.__init__(self, 6, subcmd=1)
173 def __str__(self):
174 return "seac"
176 def updatepath(self, path, atrafo, context):
177 sab = context.t1stack.pop(0)
178 adx = context.t1stack.pop(0)
179 ady = context.t1stack.pop(0)
180 bchar = context.t1stack.pop(0)
181 achar = context.t1stack.pop(0)
182 aglyph = adobestandardencoding[achar]
183 bglyph = adobestandardencoding[bchar]
184 context.t1font.updateglyphpath(bglyph, path, atrafo, context)
185 atrafo = atrafo * trafo.translate_pt(adx-sab, ady)
186 context.t1font.updateglyphpath(aglyph, path, atrafo, context)
188 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
189 bchar = context.t1stack.pop()
190 achar = context.t1stack.pop()
191 aglyph = adobestandardencoding[achar]
192 bglyph = adobestandardencoding[bchar]
193 seacglyphs.add(aglyph)
194 seacglyphs.add(bglyph)
195 context.t1font.gatherglyphcalls(bglyph, seacglyphs, subrs, othersubrs, context)
196 context.t1font.gatherglyphcalls(aglyph, seacglyphs, subrs, othersubrs, context)
198 T1seac = _T1seac()
201 class _T1sbw(T1cmd):
203 def __init__(self):
204 T1cmd.__init__(self, 7, subcmd=1)
206 def __str__(self):
207 return "sbw"
209 def updatepath(self, path, trafo, context):
210 sbx = context.t1stack.pop(0)
211 sby = context.t1stack.pop(0)
212 wx = context.t1stack.pop(0)
213 wy = context.t1stack.pop(0)
214 path.append(moveto_pt(*trafo.apply_pt(sbx, sby)))
215 context.x = sbx
216 context.y = sby
217 context.wx = wx
218 context.wy = wy
220 T1sbw = _T1sbw()
223 # path construction commands
225 class _T1closepath(T1cmd):
227 def __init__(self):
228 T1cmd.__init__(self, 9)
230 def __str__(self):
231 return "closepath"
233 def updatepath(self, path, trafo, context):
234 path.append(closepath())
235 # The closepath in T1 is different from PostScripts in that it does
236 # *not* modify the current position; hence we need to add an additional
237 # moveto here ...
238 path.append(moveto_pt(*trafo.apply_pt(context.x, context.y)))
240 T1closepath = _T1closepath()
243 class _T1hlineto(T1cmd):
245 def __init__(self):
246 T1cmd.__init__(self, 6)
248 def __str__(self):
249 return "hlineto"
251 def updatepath(self, path, trafo, context):
252 dx = context.t1stack.pop(0)
253 path.append(lineto_pt(*trafo.apply_pt(context.x + dx, context.y)))
254 context.x += dx
256 T1hlineto = _T1hlineto()
259 class _T1hmoveto(T1cmd):
261 def __init__(self):
262 T1cmd.__init__(self, 22)
264 def __str__(self):
265 return "hmoveto"
267 def updatepath(self, path, trafo, context):
268 dx = context.t1stack.pop(0)
269 path.append(moveto_pt(*trafo.apply_pt(context.x + dx, context.y)))
270 context.x += dx
272 T1hmoveto = _T1hmoveto()
275 class _T1hvcurveto(T1cmd):
277 def __init__(self):
278 T1cmd.__init__(self, 31)
280 def __str__(self):
281 return "hvcurveto"
283 def updatepath(self, path, trafo, context):
284 dx1 = context.t1stack.pop(0)
285 dx2 = context.t1stack.pop(0)
286 dy2 = context.t1stack.pop(0)
287 dy3 = context.t1stack.pop(0)
288 path.append(curveto_pt(*(trafo.apply_pt(context.x + dx1, context.y) +
289 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy2) +
290 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy2 + dy3))))
291 context.x += dx1+dx2
292 context.y += dy2+dy3
294 T1hvcurveto = _T1hvcurveto()
297 class _T1rlineto(T1cmd):
299 def __init__(self):
300 T1cmd.__init__(self, 5)
302 def __str__(self):
303 return "rlineto"
305 def updatepath(self, path, trafo, context):
306 dx = context.t1stack.pop(0)
307 dy = context.t1stack.pop(0)
308 path.append(lineto_pt(*trafo.apply_pt(context.x + dx, context.y + dy)))
309 context.x += dx
310 context.y += dy
312 T1rlineto = _T1rlineto()
315 class _T1rmoveto(T1cmd):
317 def __init__(self):
318 T1cmd.__init__(self, 21)
320 def __str__(self):
321 return "rmoveto"
323 def updatepath(self, path, trafo, context):
324 dx = context.t1stack.pop(0)
325 dy = context.t1stack.pop(0)
326 path.append(moveto_pt(*trafo.apply_pt(context.x + dx, context.y + dy)))
327 context.x += dx
328 context.y += dy
330 T1rmoveto = _T1rmoveto()
333 class _T1rrcurveto(T1cmd):
335 def __init__(self):
336 T1cmd.__init__(self, 8)
338 def __str__(self):
339 return "rrcurveto"
341 def updatepath(self, path, trafo, context):
342 dx1 = context.t1stack.pop(0)
343 dy1 = context.t1stack.pop(0)
344 dx2 = context.t1stack.pop(0)
345 dy2 = context.t1stack.pop(0)
346 dx3 = context.t1stack.pop(0)
347 dy3 = context.t1stack.pop(0)
348 path.append(curveto_pt(*(trafo.apply_pt(context.x + dx1, context.y + dy1) +
349 trafo.apply_pt(context.x + dx1 + dx2, context.y + dy1 + dy2) +
350 trafo.apply_pt(context.x + dx1 + dx2 + dx3, context.y + dy1 + dy2 + dy3))))
351 context.x += dx1+dx2+dx3
352 context.y += dy1+dy2+dy3
354 T1rrcurveto = _T1rrcurveto()
357 class _T1vlineto(T1cmd):
359 def __init__(self):
360 T1cmd.__init__(self, 7)
362 def __str__(self):
363 return "vlineto"
365 def updatepath(self, path, trafo, context):
366 dy = context.t1stack.pop(0)
367 path.append(lineto_pt(*trafo.apply_pt(context.x, context.y + dy)))
368 context.y += dy
370 T1vlineto = _T1vlineto()
373 class _T1vmoveto(T1cmd):
375 def __init__(self):
376 T1cmd.__init__(self, 4)
378 def __str__(self):
379 return "vmoveto"
381 def updatepath(self, path, trafo, context):
382 dy = context.t1stack.pop(0)
383 path.append(moveto_pt(*trafo.apply_pt(context.x, context.y + dy)))
384 context.y += dy
386 T1vmoveto = _T1vmoveto()
389 class _T1vhcurveto(T1cmd):
391 def __init__(self):
392 T1cmd.__init__(self, 30)
394 def __str__(self):
395 return "vhcurveto"
397 def updatepath(self, path, trafo, context):
398 dy1 = context.t1stack.pop(0)
399 dx2 = context.t1stack.pop(0)
400 dy2 = context.t1stack.pop(0)
401 dx3 = context.t1stack.pop(0)
402 path.append(curveto_pt(*(trafo.apply_pt(context.x, context.y + dy1) +
403 trafo.apply_pt(context.x + dx2, context.y + dy1 + dy2) +
404 trafo.apply_pt(context.x + dx2 + dx3, context.y + dy1 + dy2))))
405 context.x += dx2+dx3
406 context.y += dy1+dy2
408 T1vhcurveto = _T1vhcurveto()
411 # hint commands
413 class _T1dotsection(T1cmd):
415 def __init__(self):
416 T1cmd.__init__(self, 0, subcmd=1)
418 def __str__(self):
419 return "dotsection"
421 def updatepath(self, path, trafo, context):
422 pass
424 T1dotsection = _T1dotsection()
427 class _T1hstem(T1cmd):
429 def __init__(self):
430 T1cmd.__init__(self, 1)
432 def __str__(self):
433 return "hstem"
435 def updatepath(self, path, trafo, context):
436 y = context.t1stack.pop(0)
437 dy = context.t1stack.pop(0)
439 T1hstem = _T1hstem()
442 class _T1hstem3(T1cmd):
444 def __init__(self):
445 T1cmd.__init__(self, 2, subcmd=1)
447 def __str__(self):
448 return "hstem3"
450 def updatepath(self, path, trafo, context):
451 y0 = context.t1stack.pop(0)
452 dy0 = context.t1stack.pop(0)
453 y1 = context.t1stack.pop(0)
454 dy1 = context.t1stack.pop(0)
455 y2 = context.t1stack.pop(0)
456 dy2 = context.t1stack.pop(0)
458 T1hstem3 = _T1hstem3()
461 class _T1vstem(T1cmd):
463 def __init__(self):
464 T1cmd.__init__(self, 3)
466 def __str__(self):
467 return "vstem"
469 def updatepath(self, path, trafo, context):
470 x = context.t1stack.pop(0)
471 dx = context.t1stack.pop(0)
473 T1vstem = _T1vstem()
476 class _T1vstem3(T1cmd):
478 def __init__(self):
479 T1cmd.__init__(self, 1, subcmd=1)
481 def __str__(self):
482 return "vstem3"
484 def updatepath(self, path, trafo, context):
485 self.x0 = context.t1stack.pop(0)
486 self.dx0 = context.t1stack.pop(0)
487 self.x1 = context.t1stack.pop(0)
488 self.dx1 = context.t1stack.pop(0)
489 self.x2 = context.t1stack.pop(0)
490 self.dx2 = context.t1stack.pop(0)
492 T1vstem3 = _T1vstem3()
495 # arithmetic command
497 class _T1div(T1cmd):
499 def __init__(self):
500 T1cmd.__init__(self, 12, subcmd=1)
502 def __str__(self):
503 return "div"
505 def updatepath(self, path, trafo, context):
506 num2 = context.t1stack.pop()
507 num1 = context.t1stack.pop()
508 context.t1stack.append(divmod(num1, num2)[0])
510 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
511 num2 = context.t1stack.pop()
512 num1 = context.t1stack.pop()
513 context.t1stack.append(divmod(num1, num2)[0])
515 T1div = _T1div()
518 # subroutine commands
520 class _T1callothersubr(T1cmd):
522 def __init__(self):
523 T1cmd.__init__(self, 16, subcmd=1)
525 def __str__(self):
526 return "callothersubr"
528 def updatepath(self, path, trafo, context):
529 othersubrnumber = context.t1stack.pop()
530 n = context.t1stack.pop()
531 for i in range(n):
532 context.psstack.append(context.t1stack.pop())
534 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
535 othersubrnumber = context.t1stack.pop()
536 othersubrs.add(othersubrnumber)
537 n = context.t1stack.pop()
538 for i in range(n):
539 context.psstack.append(context.t1stack.pop())
541 T1callothersubr = _T1callothersubr()
544 class _T1callsubr(T1cmd):
546 def __init__(self):
547 T1cmd.__init__(self, 10)
549 def __str__(self):
550 return "callsubr"
552 def updatepath(self, path, trafo, context):
553 subr = context.t1stack.pop()
554 context.t1font.updatesubrpath(subr, path, trafo, context)
556 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
557 subr = context.t1stack.pop()
558 subrs.add(subr)
559 context.t1font.gathersubrcalls(subr, seacglyphs, subrs, othersubrs, context)
561 T1callsubr = _T1callsubr()
564 class _T1pop(T1cmd):
566 def __init__(self):
567 T1cmd.__init__(self, 17, subcmd=1)
569 def __str__(self):
570 return "pop"
572 def updatepath(self, path, trafo, context):
573 context.t1stack.append(context.psstack.pop())
575 def gathercalls(self, seacglyphs, subrs, othersubrs, context):
576 context.t1stack.append(context.psstack.pop())
578 T1pop = _T1pop()
581 class _T1return(T1cmd):
583 def __init__(self):
584 T1cmd.__init__(self, 11)
586 def __str__(self):
587 return "return"
589 def updatepath(self, path, trafo, context):
590 pass
592 T1return = _T1return()
595 class _T1setcurrentpoint(T1cmd):
597 def __init__(self):
598 T1cmd.__init__(self, 33, subcmd=1)
600 def __str__(self):
601 return "setcurrentpoint"
603 def updatepath(self, path, trafo, context):
604 x = context.t1stack.pop(0)
605 y = context.t1stack.pop(0)
606 path.append(moveto_pt(*trafo.apply_pt(x, y)))
607 context.x = x
608 context.y = y
610 T1setcurrentpoint = _T1setcurrentpoint()
613 ######################################################################
615 class T1file:
617 eexecr = 55665
618 charstringr = 4330
620 fontnamepattern = re.compile("/FontName\s+/(.*?)\s+def\s+")
621 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")
623 def __init__(self, data1, data2eexec, data3):
624 """initializes a t1font instance
626 data1 and data3 are the two clear text data parts and data2 is
627 the binary data part"""
628 self.data1 = data1
629 self._data2eexec = data2eexec
630 self.data3 = data3
632 # marker and value for decoded data
633 self._data2 = None
634 # note that data2eexec is set to none by setsubrcmds and setglyphcmds
635 # this *also* denotes, that data2 is out-of-date; hence they are both
636 # marked by an _ and getdata2 and getdata2eexec will properly resolve
637 # the current state of decoding ...
639 # marker and value for standard encoding check
640 self.encoding = None
642 self.name, = self.fontnamepattern.search(self.data1).groups()
643 m11, m12, m21, m22, v1, v2 = map(float, self.fontmatrixpattern.search(self.data1).groups()[:6])
644 self.fontmatrix = trafo.trafo_pt(matrix=((m11, m12), (m21, m22)), vector=(v1, v2))
646 def _eexecdecode(self, code):
647 """eexec decoding of code"""
648 return decoder(code, self.eexecr, 4)
650 def _charstringdecode(self, code):
651 """charstring decoding of code"""
652 return decoder(code, self.charstringr, self.lenIV)
654 def _eexecencode(self, data):
655 """eexec encoding of data"""
656 return encoder(data, self.eexecr, "PyX!")
658 def _charstringencode(self, data):
659 """eexec encoding of data"""
660 return encoder(data, self.charstringr, "PyX!"[:self.lenIV])
662 def _encoding(self):
663 """helper method to lookup the encoding in the font"""
664 c = reader.PStokenizer(self.data1, "/Encoding")
665 token1 = c.gettoken()
666 token2 = c.gettoken()
667 if token1 == "StandardEncoding" and token2 == "def":
668 self.encoding = adobestandardencoding
669 else:
670 self.encoding = [None]*256
671 while 1:
672 self.encodingstart = c.pos
673 if c.gettoken() == "dup":
674 break
675 while 1:
676 i = c.getint()
677 glyph = c.gettoken()
678 if 0 <= i < 256:
679 self.encoding[i] = glyph[1:]
680 token = c.gettoken(); assert token == "put"
681 self.encodingend = c.pos
682 token = c.gettoken()
683 if token == "readonly" or token == "def":
684 break
685 assert token == "dup"
687 lenIVpattern = re.compile("/lenIV\s+(\d+)\s+def\s+")
688 flexhintsubrs = [[3, 0, T1callothersubr, T1pop, T1pop, T1setcurrentpoint, T1return],
689 [0, 1, T1callothersubr, T1return],
690 [0, 2, T1callothersubr, T1return],
691 [T1return]]
693 def _data2decode(self):
694 """decodes data2eexec to the data2 string and the subr and glyphs dictionary
696 It doesn't make sense to call this method twice -- check the content of
697 data2 before calling. The method also keeps the subrs and charstrings
698 start and end positions for later use."""
699 self._data2 = self._eexecdecode(self._data2eexec)
701 m = self.lenIVpattern.search(self._data2)
702 if m:
703 self.lenIV = int(m.group(1))
704 else:
705 self.lenIV = 4
706 self.emptysubr = self._charstringencode(chr(11))
708 # extract Subrs
709 c = reader.PStokenizer(self._data2, "/Subrs")
710 self.subrsstart = c.pos
711 arraycount = c.getint()
712 token = c.gettoken(); assert token == "array"
713 self.subrs = []
714 for i in range(arraycount):
715 token = c.gettoken(); assert token == "dup"
716 token = c.getint(); assert token == i
717 size = c.getint()
718 if not i:
719 self.subrrdtoken = c.gettoken()
720 else:
721 token = c.gettoken(); assert token == self.subrrdtoken
722 self.subrs.append(c.getbytes(size))
723 token = c.gettoken()
724 if token == "noaccess":
725 token = "%s %s" % (token, c.gettoken())
726 if not i:
727 self.subrnptoken = token
728 else:
729 assert token == self.subrnptoken
730 self.subrsend = c.pos
732 # hasflexhintsubrs is a boolean indicating that the font uses flex or
733 # hint replacement subrs as specified by Adobe (tm). When it does, the
734 # first 4 subrs should all be copied except when none of them are used
735 # in the stripped version of the font since we then get a font not
736 # using flex or hint replacement subrs at all.
737 self.hasflexhintsubrs = (arraycount >= len(self.flexhintsubrs) and
738 [self.getsubrcmds(i)
739 for i in range(len(self.flexhintsubrs))] == self.flexhintsubrs)
741 # extract glyphs
742 self.glyphs = {}
743 self.glyphlist = [] # we want to keep the order of the glyph names
744 c = reader.PStokenizer(self._data2, "/CharStrings")
745 self.charstringsstart = c.pos
746 c.getint()
747 token = c.gettoken(); assert token == "dict"
748 token = c.gettoken(); assert token == "dup"
749 token = c.gettoken(); assert token == "begin"
750 first = 1
751 while 1:
752 chartoken = c.gettoken()
753 if chartoken == "end":
754 break
755 assert chartoken[0] == "/"
756 size = c.getint()
757 if first:
758 self.glyphrdtoken = c.gettoken()
759 else:
760 token = c.gettoken(); assert token == self.glyphrdtoken
761 self.glyphlist.append(chartoken[1:])
762 self.glyphs[chartoken[1:]] = c.getbytes(size)
763 if first:
764 self.glyphndtoken = c.gettoken()
765 else:
766 token = c.gettoken(); assert token == self.glyphndtoken
767 first = 0
768 self.charstringsend = c.pos
769 assert not self.subrs or self.subrrdtoken == self.glyphrdtoken
771 def _cmds(self, code):
772 """return a list of T1cmd's for encoded charstring data in code"""
773 code = array.array("B", self._charstringdecode(code))
774 cmds = []
775 while code:
776 x = code.pop(0)
777 if x == 12: # this starts an escaped cmd
778 cmds.append(T1subcmds[code.pop(0)])
779 elif 0 <= x < 32: # those are cmd's
780 cmds.append(T1cmds[x])
781 elif 32 <= x <= 246: # short ints
782 cmds.append(x-139)
783 elif 247 <= x <= 250: # mid size ints
784 cmds.append(((x - 247)*256) + code.pop(0) + 108)
785 elif 251 <= x <= 254: # mid size ints
786 cmds.append(-((x - 251)*256) - code.pop(0) - 108)
787 else: # x = 255, i.e. full size ints
788 y = ((code.pop(0)*256l+code.pop(0))*256+code.pop(0))*256+code.pop(0)
789 if y > (1l << 31):
790 cmds.append(y - (1l << 32))
791 else:
792 cmds.append(y)
793 return cmds
795 def _code(self, cmds):
796 """return an encoded charstring data for list of T1cmd's in cmds"""
797 code = array.array("B")
798 for cmd in cmds:
799 try:
800 if cmd.subcmd:
801 code.append(12)
802 code.append(cmd.code)
803 except AttributeError:
804 if -107 <= cmd <= 107:
805 code.append(cmd+139)
806 elif 108 <= cmd <= 1131:
807 a, b = divmod(cmd-108, 256)
808 code.append(a+247)
809 code.append(b)
810 elif -1131 <= cmd <= -108:
811 a, b = divmod(-cmd-108, 256)
812 code.append(a+251)
813 code.append(b)
814 else:
815 if cmd < 0:
816 cmd += 1l << 32
817 cmd, x4 = divmod(cmd, 256)
818 cmd, x3 = divmod(cmd, 256)
819 x1, x2 = divmod(cmd, 256)
820 code.append(255)
821 code.append(x1)
822 code.append(x2)
823 code.append(x3)
824 code.append(x4)
825 return self._charstringencode(code.tostring())
827 def getsubrcmds(self, subr):
828 """return a list of T1cmd's for subr subr"""
829 if not self._data2:
830 self._data2decode()
831 return self._cmds(self.subrs[subr])
833 def getglyphcmds(self, glyph):
834 """return a list of T1cmd's for glyph glyph"""
835 if not self._data2:
836 self._data2decode()
837 return self._cmds(self.glyphs[glyph])
839 def setsubrcmds(self, subr, cmds):
840 """replaces the T1cmd's by the list cmds for subr subr"""
841 if not self._data2:
842 self._data2decode()
843 self._data2eexec = None
844 self.subrs[subr] = self._code(cmds)
846 def setglyphcmds(self, glyph, cmds):
847 """replaces the T1cmd's by the list cmds for glyph glyph"""
848 if not self._data2:
849 self._data2decode()
850 self._data2eexec = None
851 self.glyphs[glyph] = self._code(cmds)
853 def updatepath(self, cmds, path, trafo, context):
854 for cmd in cmds:
855 if isinstance(cmd, T1cmd):
856 cmd.updatepath(path, trafo, context)
857 else:
858 context.t1stack.append(cmd)
860 def updatesubrpath(self, subr, path, trafo, context):
861 self.updatepath(self.getsubrcmds(subr), path, trafo, context)
863 def updateglyphpath(self, glyph, path, trafo, context):
864 self.updatepath(self.getglyphcmds(glyph), path, trafo, context)
866 def gathercalls(self, cmds, seacglyphs, subrs, othersubrs, context):
867 for cmd in cmds:
868 if isinstance(cmd, T1cmd):
869 cmd.gathercalls(seacglyphs, subrs, othersubrs, context)
870 else:
871 context.t1stack.append(cmd)
873 def gathersubrcalls(self, subr, seacglyphs, subrs, othersubrs, context):
874 self.gathercalls(self.getsubrcmds(subr), seacglyphs, subrs, othersubrs, context)
876 def gatherglyphcalls(self, glyph, seacglyphs, subrs, othersubrs, context):
877 self.gathercalls(self.getglyphcmds(glyph), seacglyphs, subrs, othersubrs, context)
879 def getglyphpath_pt(self, x_pt, y_pt, glyph, size_pt, convertcharcode=False):
880 """return an object containing the PyX path, wx_pt and wy_pt for glyph named glyph"""
881 if convertcharcode:
882 if not self.encoding:
883 self._encoding()
884 glyph = self.encoding[glyph]
885 t = self.fontmatrix.scaled(size_pt)
886 tpath = t.translated_pt(x_pt, y_pt)
887 context = T1context(self)
888 p = path()
889 self.updateglyphpath(glyph, p, tpath, context)
890 class glyphpath:
891 def __init__(self, p, wx_pt, wy_pt):
892 self.path = p
893 self.wx_pt = wx_pt
894 self.wy_pt = wy_pt
895 return glyphpath(p, *t.apply_pt(context.wx, context.wy))
897 def getdata2(self, subrs=None, glyphs=None):
898 """makes a data2 string
900 subrs is a dict containing those subrs numbers as keys,
901 which are to be contained in the subrsstring to be created.
902 If subrs is None, all subrs in self.subrs will be used.
903 The subrs dict might be modified *in place*.
905 glyphs is a dict containing those glyph names as keys,
906 which are to be contained in the charstringsstring to be created.
907 If glyphs is None, all glyphs in self.glyphs will be used."""
908 def addsubrs(subrs, result):
909 if subrs is not None:
910 # some adjustments to the subrs dict
911 if subrs:
912 subrsmin = min(subrs)
913 subrsmax = max(subrs)
914 if self.hasflexhintsubrs and subrsmin < len(self.flexhintsubrs):
915 # According to the spec we need to keep all the flex and hint subrs
916 # as long as any of it is used.
917 for subr in range(len(self.flexhintsubrs)):
918 subrs.add(subr)
919 else:
920 subrsmax = -1
921 else:
922 # build a new subrs dict containing all subrs
923 subrs = dict([(subr, 1) for subr in range(len(self.subrs))])
924 subrsmax = len(self.subrs) - 1
926 # build the string from all selected subrs
927 result.append("%d array\n" % (subrsmax + 1))
928 for subr in range(subrsmax+1):
929 if subr in subrs:
930 code = self.subrs[subr]
931 else:
932 code = self.emptysubr
933 result.append("dup %d %d %s %s %s\n" % (subr, len(code), self.subrrdtoken, code, self.subrnptoken))
935 def addcharstrings(glyphs, result):
936 result.append("%d dict dup begin\n" % (glyphs is None and len(self.glyphlist) or len(glyphs)))
937 for glyph in self.glyphlist:
938 if glyphs is None or glyph in glyphs:
939 result.append("/%s %d %s %s %s\n" % (glyph, len(self.glyphs[glyph]), self.glyphrdtoken, self.glyphs[glyph], self.glyphndtoken))
940 result.append("end\n")
942 if self.subrsstart < self.charstringsstart:
943 result = [self._data2[:self.subrsstart]]
944 addsubrs(subrs, result)
945 result.append(self._data2[self.subrsend:self.charstringsstart])
946 addcharstrings(glyphs, result)
947 result.append(self._data2[self.charstringsend:])
948 else:
949 result = [self._data2[:self.charstringsstart]]
950 addcharstrings(glyphs, result)
951 result.append(self._data2[self.charstringsend:self.subrsstart])
952 addsubrs(subrs, result)
953 result.append(self._data2[self.subrsend:])
954 return "".join(result)
956 def getdata2eexec(self):
957 if self._data2eexec:
958 return self._data2eexec
959 # note that self._data2 is out-of-date here too, hence we need to call getdata2
960 return self._eexecencode(self.getdata2())
962 newlinepattern = re.compile("\s*[\r\n]\s*")
963 uniqueidpattern = re.compile("%?/UniqueID\s+\d+\s+def\s+")
964 # when UniqueID is commented out (as in modern latin), prepare to remove the comment character as well
966 def getstrippedfont(self, glyphs, charcodes):
967 """create a T1file instance containing only certain glyphs
969 glyphs is a set of the glyph names. It might be modified *in place*!
971 # TODO: we could also strip othersubrs to those actually used
972 if not self.encoding:
973 self._encoding()
974 for charcode in charcodes:
975 glyphs.add(self.encoding[charcode])
977 # collect information about used glyphs and subrs
978 seacglyphs = pycompat.set()
979 subrs = pycompat.set()
980 othersubrs = pycompat.set()
981 for glyph in glyphs:
982 self.gatherglyphcalls(glyph, seacglyphs, subrs, othersubrs, T1context(self))
983 # while we have gathered all subrs for the seacglyphs alreadys, we
984 # might have missed the glyphs themself (when they are not used stand-alone)
985 glyphs.update(seacglyphs)
986 glyphs.add(".notdef")
988 # strip data1
989 if self.encoding is adobestandardencoding:
990 data1 = self.data1
991 else:
992 encodingstrings = []
993 for char, glyph in enumerate(self.encoding):
994 if glyph in glyphs:
995 encodingstrings.append("dup %i /%s put\n" % (char, glyph))
996 data1 = self.data1[:self.encodingstart] + "\n" + "".join(encodingstrings) + self.data1[self.encodingend:]
997 data1 = self.newlinepattern.subn("\n", data1)[0]
998 data1 = self.uniqueidpattern.subn("", data1)[0]
1000 # strip data2
1001 data2 = self.uniqueidpattern.subn("", self.getdata2(subrs, glyphs))[0]
1003 # strip data3
1004 data3 = self.newlinepattern.subn("\n", self.data3)[0]
1006 # create and return the new font instance
1007 return T1file(data1.rstrip() + "\n", self._eexecencode(data2), data3.rstrip() + "\n")
1009 # The following two methods, writePDFfontinfo and getglyphinfo,
1010 # extract informtion which should better be taken from the afm file.
1011 def writePDFfontinfo(self, file):
1012 try:
1013 glyphinfo_y = self.getglyphinfo("y")
1014 glyphinfo_W = self.getglyphinfo("W")
1015 glyphinfo_H = self.getglyphinfo("H")
1016 glyphinfo_h = self.getglyphinfo("h")
1017 glyphinfo_period = self.getglyphinfo("period")
1018 glyphinfo_colon = self.getglyphinfo("colon")
1019 except:
1020 warnings.warn("Auto-guessing of font information for font '%s' failed. We're writing stub data instead." % self.name)
1021 file.write("/Flags 4\n")
1022 file.write("/FontBBox [0 -100 1000 1000]\n")
1023 file.write("/ItalicAngle 0\n")
1024 file.write("/Ascent 1000\n")
1025 file.write("/Descent -100\n")
1026 file.write("/CapHeight 700\n")
1027 file.write("/StemV 100\n")
1028 else:
1029 if not self.encoding:
1030 self._encoding()
1031 # As a simple heuristics we assume non-symbolic fonts if and only
1032 # if the Adobe standard encoding is used. All other font flags are
1033 # not specified here.
1034 if self.encoding is adobestandardencoding:
1035 file.write("/Flags 32\n")
1036 else:
1037 file.write("/Flags 4\n")
1038 file.write("/FontBBox [0 %f %f %f]\n" % (glyphinfo_y[3], glyphinfo_W[0], glyphinfo_H[5]))
1039 file.write("/ItalicAngle %f\n" % math.degrees(math.atan2(glyphinfo_period[4]-glyphinfo_colon[4], glyphinfo_colon[5]-glyphinfo_period[5])))
1040 file.write("/Ascent %f\n" % glyphinfo_H[5])
1041 file.write("/Descent %f\n" % glyphinfo_y[3])
1042 file.write("/CapHeight %f\n" % glyphinfo_h[5])
1043 file.write("/StemV %f\n" % (glyphinfo_period[4]-glyphinfo_period[2]))
1045 def getglyphinfo(self, glyph):
1046 warnings.warn("We are about to extract font information for the Type 1 font '%s' from its pfb file. This is bad practice (and it's slow). You should use an afm file instead." % self.name)
1047 context = T1context(self)
1048 p = path()
1049 self.updateglyphpath(glyph, p, trafo.trafo(), context)
1050 bbox = p.bbox()
1051 return context.wx, context.wy, bbox.llx_pt, bbox.lly_pt, bbox.urx_pt, bbox.ury_pt
1053 def outputPFA(self, file):
1054 """output the T1file in PFA format"""
1055 file.write(self.data1)
1056 data2eexechex = binascii.b2a_hex(self.getdata2eexec())
1057 linelength = 64
1058 for i in range((len(data2eexechex)-1)/linelength + 1):
1059 file.write(data2eexechex[i*linelength: i*linelength+linelength])
1060 file.write("\n")
1061 file.write(self.data3)
1063 def outputPFB(self, file):
1064 """output the T1file in PFB format"""
1065 data2eexec = self.getdata2eexec()
1066 def pfblength(data):
1067 l = len(data)
1068 l, x1 = divmod(l, 256)
1069 l, x2 = divmod(l, 256)
1070 x4, x3 = divmod(l, 256)
1071 return chr(x1) + chr(x2) + chr(x3) + chr(x4)
1072 file.write("\200\1")
1073 file.write(pfblength(self.data1))
1074 file.write(self.data1)
1075 file.write("\200\2")
1076 file.write(pfblength(data2eexec))
1077 file.write(data2eexec)
1078 file.write("\200\1")
1079 file.write(pfblength(self.data3))
1080 file.write(self.data3)
1081 file.write("\200\3")
1083 def outputPS(self, file, writer):
1084 """output the PostScript code for the T1file to the file file"""
1085 self.outputPFA(file)
1087 def outputPDF(self, file, writer):
1088 data2eexec = self.getdata2eexec()
1089 data3 = self.data3
1090 # we might be allowed to skip the third part ...
1091 if (data3.replace("\n", "")
1092 .replace("\r", "")
1093 .replace("\t", "")
1094 .replace(" ", "")) == "0"*512 + "cleartomark":
1095 data3 = ""
1097 data = self.data1 + data2eexec + data3
1098 if writer.compress and haszlib:
1099 data = zlib.compress(data)
1101 file.write("<<\n"
1102 "/Length %d\n"
1103 "/Length1 %d\n"
1104 "/Length2 %d\n"
1105 "/Length3 %d\n" % (len(data), len(self.data1), len(data2eexec), len(data3)))
1106 if writer.compress and haszlib:
1107 file.write("/Filter /FlateDecode\n")
1108 file.write(">>\n"
1109 "stream\n")
1110 file.write(data)
1111 file.write("\n"
1112 "endstream\n")
1114 # factory functions
1116 class FontFormatError(Exception):
1117 pass
1119 def from_PFA_bytes(bytes):
1120 """create a T1file instance from a string of bytes corresponding to a PFA file"""
1121 try:
1122 m1 = bytes.index("eexec") + 6
1123 m2 = bytes.index("0"*40)
1124 except ValueError:
1125 raise FontFormatError
1127 data1 = bytes[:m1]
1128 data2 = binascii.a2b_hex(bytes[m1: m2].replace(" ", "").replace("\r", "").replace("\n", ""))
1129 data3 = bytes[m2:]
1130 return T1file(data1, data2, data3)
1132 def from_PFA_filename(filename):
1133 """create a T1file instance from PFA font file of given name"""
1134 file = open(filename, "rb")
1135 t1file = from_PFA_bytes(file.read())
1136 file.close()
1137 return t1file
1139 def from_PFB_bytes(bytes):
1140 """create a T1file instance from a string of bytes corresponding to a PFB file"""
1142 def pfblength(s):
1143 if len(s) != 4:
1144 raise ValueError("invalid string length")
1145 return (ord(s[0]) +
1146 ord(s[1])*256 +
1147 ord(s[2])*256*256 +
1148 ord(s[3])*256*256*256)
1149 class consumer:
1150 def __init__(self, bytes):
1151 self.bytes = bytes
1152 self.pos = 0
1153 def __call__(self, n):
1154 result = self.bytes[self.pos:self.pos+n]
1155 self.pos += n
1156 return result
1158 consume = consumer(bytes)
1159 mark = consume(2)
1160 if mark != "\200\1":
1161 raise FontFormatError
1162 data1= consume(pfblength(consume(4)))
1163 mark = consume(2)
1164 if mark != "\200\2":
1165 raise FontFormatError
1166 data2 = ""
1167 while mark == "\200\2":
1168 data2 = data2 + consume(pfblength(consume(4)))
1169 mark = consume(2)
1170 if mark != "\200\1":
1171 raise FontFormatError
1172 data3 = consume(pfblength(consume(4)))
1173 mark = consume(2)
1174 if mark != "\200\3":
1175 raise FontFormatError
1176 if consume(1):
1177 raise FontFormatError
1179 return T1file(data1, data2, data3)
1181 def from_PFB_filename(filename):
1182 """create a T1file instance from PFB font file of given name"""
1183 file = open(filename, "rb")
1184 t1file = from_PFB_bytes(file.read())
1185 file.close()
1186 return t1file
1188 def from_PF_bytes(bytes):
1189 try:
1190 return from_PFB_bytes(bytes)
1191 except FontFormatError:
1192 return from_PFA_bytes(bytes)
1194 def from_PF_filename(filename):
1195 """create a T1file instance from PFA or PFB font file of given name"""
1196 file = open(filename, "rb")
1197 t1file = from_PF_bytes(file.read())
1198 file.close()
1199 return t1file