update upload data
[PyX.git] / pyx / font / t1file.py
blob7353a5382e5f34446062e2ca3836771bd66d7457
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 getglyphpathwxwy_pt(self, glyph, size, convertcharcode=False):
880 if convertcharcode:
881 if not self.encoding:
882 self._encoding()
883 glyph = self.encoding[glyph]
884 t = self.fontmatrix.scaled(size)
885 context = T1context(self)
886 p = path()
887 self.updateglyphpath(glyph, p, t, context)
888 wx, wy = t.apply_pt(context.wx, context.wy)
889 return p, wx, wy
891 def getglyphpath(self, glyph, size, convertcharcode=False):
892 """return a PyX path for glyph named glyph"""
893 return self.getglyphpathwxwy_pt(glyph, size)[0]
895 def getglyphwxwy_pt(self, glyph, size, convertcharcode=False):
896 return self.getglyphpathwxwy_pt(glyph, size)[1:]
898 def getdata2(self, subrs=None, glyphs=None):
899 """makes a data2 string
901 subrs is a dict containing those subrs numbers as keys,
902 which are to be contained in the subrsstring to be created.
903 If subrs is None, all subrs in self.subrs will be used.
904 The subrs dict might be modified *in place*.
906 glyphs is a dict containing those glyph names as keys,
907 which are to be contained in the charstringsstring to be created.
908 If glyphs is None, all glyphs in self.glyphs will be used."""
909 def addsubrs(subrs, result):
910 if subrs is not None:
911 # some adjustments to the subrs dict
912 if subrs:
913 subrsmin = min(subrs)
914 subrsmax = max(subrs)
915 if self.hasflexhintsubrs and subrsmin < len(self.flexhintsubrs):
916 # According to the spec we need to keep all the flex and hint subrs
917 # as long as any of it is used.
918 for subr in range(len(self.flexhintsubrs)):
919 subrs.add(subr)
920 else:
921 subrsmax = -1
922 else:
923 # build a new subrs dict containing all subrs
924 subrs = dict([(subr, 1) for subr in range(len(self.subrs))])
925 subrsmax = len(self.subrs) - 1
927 # build the string from all selected subrs
928 result.append("%d array\n" % (subrsmax + 1))
929 for subr in range(subrsmax+1):
930 if subr in subrs:
931 code = self.subrs[subr]
932 else:
933 code = self.emptysubr
934 result.append("dup %d %d %s %s %s\n" % (subr, len(code), self.subrrdtoken, code, self.subrnptoken))
936 def addcharstrings(glyphs, result):
937 result.append("%d dict dup begin\n" % (glyphs is None and len(self.glyphlist) or len(glyphs)))
938 for glyph in self.glyphlist:
939 if glyphs is None or glyph in glyphs:
940 result.append("/%s %d %s %s %s\n" % (glyph, len(self.glyphs[glyph]), self.glyphrdtoken, self.glyphs[glyph], self.glyphndtoken))
941 result.append("end\n")
943 if self.subrsstart < self.charstringsstart:
944 result = [self._data2[:self.subrsstart]]
945 addsubrs(subrs, result)
946 result.append(self._data2[self.subrsend:self.charstringsstart])
947 addcharstrings(glyphs, result)
948 result.append(self._data2[self.charstringsend:])
949 else:
950 result = [self._data2[:self.charstringsstart]]
951 addcharstrings(glyphs, result)
952 result.append(self._data2[self.charstringsend:self.subrsstart])
953 addsubrs(subrs, result)
954 result.append(self._data2[self.subrsend:])
955 return "".join(result)
957 def getdata2eexec(self):
958 if self._data2eexec:
959 return self._data2eexec
960 # note that self._data2 is out-of-date here too, hence we need to call getdata2
961 return self._eexecencode(self.getdata2())
963 newlinepattern = re.compile("\s*[\r\n]\s*")
964 uniqueidpattern = re.compile("%?/UniqueID\s+\d+\s+def\s+")
965 # when UniqueID is commented out (as in modern latin), prepare to remove the comment character as well
967 def getstrippedfont(self, glyphs, charcodes):
968 """create a T1file instance containing only certain glyphs
970 glyphs is a set of the glyph names. It might be modified *in place*!
972 # TODO: we could also strip othersubrs to those actually used
973 if not self.encoding:
974 self._encoding()
975 for charcode in charcodes:
976 glyphs.add(self.encoding[charcode])
978 # collect information about used glyphs and subrs
979 seacglyphs = pycompat.set()
980 subrs = pycompat.set()
981 othersubrs = pycompat.set()
982 for glyph in glyphs:
983 self.gatherglyphcalls(glyph, seacglyphs, subrs, othersubrs, T1context(self))
984 # while we have gathered all subrs for the seacglyphs alreadys, we
985 # might have missed the glyphs themself (when they are not used stand-alone)
986 glyphs.update(seacglyphs)
987 glyphs.add(".notdef")
989 # strip data1
990 if self.encoding is adobestandardencoding:
991 data1 = self.data1
992 else:
993 encodingstrings = []
994 for char, glyph in enumerate(self.encoding):
995 if glyph in glyphs:
996 encodingstrings.append("dup %i /%s put\n" % (char, glyph))
997 data1 = self.data1[:self.encodingstart] + "".join(encodingstrings) + self.data1[self.encodingend:]
998 data1 = self.newlinepattern.subn("\n", data1)[0]
999 data1 = self.uniqueidpattern.subn("", data1)[0]
1001 # strip data2
1002 data2 = self.uniqueidpattern.subn("", self.getdata2(subrs, glyphs))[0]
1004 # strip data3
1005 data3 = self.newlinepattern.subn("\n", self.data3)[0]
1007 # create and return the new font instance
1008 return T1file(data1.rstrip() + "\n", self._eexecencode(data2), data3.rstrip() + "\n")
1010 # The following two methods, writePDFfontinfo and getglyphinfo,
1011 # extract informtion which should better be taken from the afm file.
1012 def writePDFfontinfo(self, file):
1013 try:
1014 glyphinfo_y = self.getglyphinfo("y")
1015 glyphinfo_W = self.getglyphinfo("W")
1016 glyphinfo_H = self.getglyphinfo("H")
1017 glyphinfo_h = self.getglyphinfo("h")
1018 glyphinfo_period = self.getglyphinfo("period")
1019 glyphinfo_colon = self.getglyphinfo("colon")
1020 except:
1021 warnings.warn("Auto-guessing of font information for font '%s' failed. We're writing stub data instead." % self.name)
1022 file.write("/Flags 4\n")
1023 file.write("/FontBBox [0 -100 1000 1000]\n")
1024 file.write("/ItalicAngle 0\n")
1025 file.write("/Ascent 1000\n")
1026 file.write("/Descent -100\n")
1027 file.write("/CapHeight 700\n")
1028 file.write("/StemV 100\n")
1029 else:
1030 if not self.encoding:
1031 self._encoding()
1032 # As a simple heuristics we assume non-symbolic fonts if and only
1033 # if the Adobe standard encoding is used. All other font flags are
1034 # not specified here.
1035 if self.encoding is adobestandardencoding:
1036 file.write("/Flags 32\n")
1037 else:
1038 file.write("/Flags 4\n")
1039 file.write("/FontBBox [0 %f %f %f]\n" % (glyphinfo_y[3], glyphinfo_W[0], glyphinfo_H[5]))
1040 file.write("/ItalicAngle %f\n" % math.degrees(math.atan2(glyphinfo_period[4]-glyphinfo_colon[4], glyphinfo_colon[5]-glyphinfo_period[5])))
1041 file.write("/Ascent %f\n" % glyphinfo_H[5])
1042 file.write("/Descent %f\n" % glyphinfo_y[3])
1043 file.write("/CapHeight %f\n" % glyphinfo_h[5])
1044 file.write("/StemV %f\n" % (glyphinfo_period[4]-glyphinfo_period[2]))
1046 def getglyphinfo(self, glyph):
1047 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)
1048 context = T1context(self)
1049 p = path()
1050 self.updateglyphpath(glyph, p, trafo.trafo(), context)
1051 bbox = p.bbox()
1052 return context.wx, context.wy, bbox.llx_pt, bbox.lly_pt, bbox.urx_pt, bbox.ury_pt
1054 def outputPFA(self, file):
1055 """output the T1file in PFA format"""
1056 file.write(self.data1)
1057 data2eexechex = binascii.b2a_hex(self.getdata2eexec())
1058 linelength = 64
1059 for i in range((len(data2eexechex)-1)/linelength + 1):
1060 file.write(data2eexechex[i*linelength: i*linelength+linelength])
1061 file.write("\n")
1062 file.write(self.data3)
1064 def outputPFB(self, file):
1065 """output the T1file in PFB format"""
1066 data2eexec = self.getdata2eexec()
1067 def pfblength(data):
1068 l = len(data)
1069 l, x1 = divmod(l, 256)
1070 l, x2 = divmod(l, 256)
1071 x4, x3 = divmod(l, 256)
1072 return chr(x1) + chr(x2) + chr(x3) + chr(x4)
1073 file.write("\200\1")
1074 file.write(pfblength(self.data1))
1075 file.write(self.data1)
1076 file.write("\200\2")
1077 file.write(pfblength(data2eexec))
1078 file.write(data2eexec)
1079 file.write("\200\1")
1080 file.write(pfblength(self.data3))
1081 file.write(self.data3)
1082 file.write("\200\3")
1084 def outputPS(self, file, writer):
1085 """output the PostScript code for the T1file to the file file"""
1086 self.outputPFA(file)
1088 def outputPDF(self, file, writer):
1089 data2eexec = self.getdata2eexec()
1090 data3 = self.data3
1091 # we might be allowed to skip the third part ...
1092 if (data3.replace("\n", "")
1093 .replace("\r", "")
1094 .replace("\t", "")
1095 .replace(" ", "")) == "0"*512 + "cleartomark":
1096 data3 = ""
1098 data = self.data1 + data2eexec + data3
1099 if writer.compress and haszlib:
1100 data = zlib.compress(data)
1102 file.write("<<\n"
1103 "/Length %d\n"
1104 "/Length1 %d\n"
1105 "/Length2 %d\n"
1106 "/Length3 %d\n" % (len(data), len(self.data1), len(data2eexec), len(data3)))
1107 if writer.compress and haszlib:
1108 file.write("/Filter /FlateDecode\n")
1109 file.write(">>\n"
1110 "stream\n")
1111 file.write(data)
1112 file.write("\n"
1113 "endstream\n")
1115 # factory functions
1117 class FontFormatError(Exception):
1118 pass
1120 def from_PFA_bytes(bytes):
1121 """create a T1file instance from a string of bytes corresponding to a PFA file"""
1122 try:
1123 m1 = bytes.index("eexec") + 6
1124 m2 = bytes.index("0"*40)
1125 except ValueError:
1126 raise FontFormatError
1128 data1 = bytes[:m1]
1129 data2 = binascii.a2b_hex(bytes[m1: m2].replace(" ", "").replace("\r", "").replace("\n", ""))
1130 data3 = bytes[m2:]
1131 return T1file(data1, data2, data3)
1133 def from_PFA_filename(filename):
1134 """create a T1file instance from PFA font file of given name"""
1135 file = open(filename, "rb")
1136 t1file = from_PFA_bytes(file.read())
1137 file.close()
1138 return t1file
1140 def from_PFB_bytes(bytes):
1141 """create a T1file instance from a string of bytes corresponding to a PFB file"""
1143 def pfblength(s):
1144 if len(s) != 4:
1145 raise ValueError("invalid string length")
1146 return (ord(s[0]) +
1147 ord(s[1])*256 +
1148 ord(s[2])*256*256 +
1149 ord(s[3])*256*256*256)
1150 class consumer:
1151 def __init__(self, bytes):
1152 self.bytes = bytes
1153 self.pos = 0
1154 def __call__(self, n):
1155 result = self.bytes[self.pos:self.pos+n]
1156 self.pos += n
1157 return result
1159 consume = consumer(bytes)
1160 mark = consume(2)
1161 if mark != "\200\1":
1162 raise FontFormatError
1163 data1= consume(pfblength(consume(4)))
1164 mark = consume(2)
1165 if mark != "\200\2":
1166 raise FontFormatError
1167 data2 = ""
1168 while mark == "\200\2":
1169 data2 = data2 + consume(pfblength(consume(4)))
1170 mark = consume(2)
1171 if mark != "\200\1":
1172 raise FontFormatError
1173 data3 = consume(pfblength(consume(4)))
1174 mark = consume(2)
1175 if mark != "\200\3":
1176 raise FontFormatError
1177 if consume(1):
1178 raise FontFormatError
1180 return T1file(data1, data2, data3)
1182 def from_PFB_filename(filename):
1183 """create a T1file instance from PFB font file of given name"""
1184 file = open(filename, "rb")
1185 t1file = from_PFB_bytes(file.read())
1186 file.close()
1187 return t1file
1189 def from_PF_bytes(bytes):
1190 try:
1191 return from_PFB_bytes(bytes)
1192 except FontFormatError:
1193 return from_PFA_bytes(bytes)
1195 def from_PF_filename(filename):
1196 """create a T1file instance from PFA or PFB font file of given name"""
1197 file = open(filename, "rb")
1198 t1file = from_PF_bytes(file.read())
1199 file.close()
1200 return t1file