~ubuntu-branches/ubuntu/trusty/pyx/trusty

« back to all changes in this revision

Viewing changes to pyx/tex.py

  • Committer: Bazaar Package Importer
  • Author(s): Graham Wilson
  • Date: 2004-12-25 06:42:57 UTC
  • Revision ID: james.westby@ubuntu.com-20041225064257-31469ij5uysqq302
Tags: upstream-0.7.1
Import upstream version 0.7.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- coding: ISO-8859-1 -*-
 
3
#
 
4
#
 
5
# Copyright (C) 2002-2004 J�rg Lehmann <joergl@users.sourceforge.net>
 
6
# Copyright (C) 2002-2004 Andr� Wobst <wobsta@users.sourceforge.net>
 
7
#
 
8
# This file is part of PyX (http://pyx.sourceforge.net/).
 
9
#
 
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.
 
14
#
 
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.
 
19
#
 
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
23
 
 
24
 
 
25
"""
 
26
(La)TeX interface of PyX
 
27
 
 
28
This module provides the classes tex and latex, which can be inserted into a
 
29
PyX canvas. The method (la)tex.text prints text, while (la)tex.textwd,
 
30
(la)tex.textht, and (la)tex.textdp appraise the width, height, and depth of a
 
31
text, respectively. The method (la)tex.define can be used to define macros in
 
32
(La)TeX.
 
33
"""
 
34
 
 
35
import os, string, tempfile, sys, md5, traceback, time, StringIO, re, atexit
 
36
import base, helper, unit, epsfile, color
 
37
 
 
38
sys.stderr.write("*** PyX Warning: the tex module is obsolete, consider the text module instead\n")
 
39
 
 
40
# Code snippets from former attrlist module (which has been removed from the
 
41
# CVS tree). We keep them here, until they are finally removed together with
 
42
# the tex module
 
43
 
 
44
class AttrlistError(base.PyXExcept):
 
45
    pass
 
46
 
 
47
 
 
48
class attrlist:
 
49
    def attrcheck(self, attrs, allowonce=(), allowmulti=()):
 
50
        hadonce = []
 
51
        for attr in attrs:
 
52
            for once in allowonce:
 
53
                if isinstance(attr, once):
 
54
                    if once in hadonce:
 
55
                        raise AttrlistError
 
56
                    else:
 
57
                        hadonce += [once]
 
58
                        break
 
59
            else:
 
60
                for multi in allowmulti:
 
61
                    if isinstance(attr, multi):
 
62
                        break
 
63
                else:
 
64
                    raise AttrlistError
 
65
 
 
66
    def attrgetall(self, attrs, get, default=helper.nodefault):
 
67
        first = 1
 
68
        for attr in attrs:
 
69
            if isinstance(attr, get):
 
70
                if first:
 
71
                    result = [attr]
 
72
                    first = 0
 
73
                else:
 
74
                    result.append(attr)
 
75
        if first:
 
76
            if default is helper.nodefault:
 
77
                raise AttrlistError
 
78
            else:
 
79
                return default
 
80
        return result
 
81
 
 
82
    def attrcount(self, attrs, check):
 
83
        return len(self.attrgetall(attrs, check, ()))
 
84
 
 
85
    def attrget(self, attrs, get, default=helper.nodefault):
 
86
        try:
 
87
            result = self.attrgetall(attrs, get)
 
88
        except AttrlistError:
 
89
            if default is helper.nodefault:
 
90
                raise AttrlistError
 
91
            else:
 
92
                return default
 
93
        if len(result) > 1:
 
94
            raise AttrlistError
 
95
        return result[0]
 
96
 
 
97
    def attrgetfirst(self, attrs, get, default=helper.nodefault):
 
98
        try:
 
99
            result = self.attrgetall(attrs, get)
 
100
        except AttrlistError:
 
101
            if default is helper.nodefault:
 
102
                raise AttrlistError
 
103
            else:
 
104
                return default
 
105
        return result[0]
 
106
 
 
107
    def attrgetlast(self, attrs, get, default=helper.nodefault):
 
108
        try:
 
109
            result = self.attrgetall(attrs, get)
 
110
        except AttrlistError:
 
111
            if default is helper.nodefault:
 
112
                raise AttrlistError
 
113
            else:
 
114
                return default
 
115
        return result[-1]
 
116
 
 
117
    def attrdel(self, attrs, remove):
 
118
        result = []
 
119
        for attr in attrs:
 
120
            if not isinstance(attr, remove):
 
121
                result.append(attr)
 
122
        return result
 
123
 
 
124
 
 
125
################################################################################
 
126
# TeX attributes
 
127
################################################################################
 
128
 
 
129
class _texattr:
 
130
 
 
131
    """base class for all TeX attributes"""
 
132
 
 
133
    pass
 
134
 
 
135
 
 
136
class fontsize(_texattr):
 
137
    
 
138
    """fontsize TeX attribute"""
 
139
    
 
140
    def __init__(self, value):
 
141
        self.value = value
 
142
 
 
143
    def __str__(self):
 
144
        return self.value
 
145
 
 
146
 
 
147
fontsize.tiny = fontsize("tiny")
 
148
fontsize.scriptsize = fontsize("scriptsize")
 
149
fontsize.footnotesize = fontsize("footnotesize")
 
150
fontsize.small = fontsize("small")
 
151
fontsize.normalsize = fontsize("normalsize")
 
152
fontsize.large = fontsize("large")
 
153
fontsize.Large = fontsize("Large")
 
154
fontsize.LARGE = fontsize("LARGE")
 
155
fontsize.huge = fontsize("huge")
 
156
fontsize.Huge = fontsize("Huge")
 
157
 
 
158
 
 
159
class halign(_texattr):
 
160
    
 
161
    """tex horizontal align attribute"""
 
162
 
 
163
    def __init__(self, value):
 
164
        self.value = value
 
165
 
 
166
    def __cmp__(self, other):
 
167
        if other is None: return 1
 
168
        return cmp(self.value, other.value)
 
169
 
 
170
    __rcmp__ = __cmp__
 
171
 
 
172
 
 
173
halign.left   = halign("left")
 
174
halign.center = halign("center")
 
175
halign.right  = halign("right")
 
176
 
 
177
   
 
178
class valign(_texattr):
 
179
 
 
180
    """abstract tex vertical align attribute"""
 
181
 
 
182
    def __init__(self, hsize):
 
183
        self.hsize = hsize
 
184
 
 
185
 
 
186
class _valignvtop(valign):
 
187
 
 
188
    """tex top vertical align attribute"""
 
189
 
 
190
    pass
 
191
 
 
192
 
 
193
valign.top = _valignvtop
 
194
 
 
195
 
 
196
class _valignvbox(valign):
 
197
 
 
198
    """tex bottom vertical align attribute"""
 
199
 
 
200
    pass
 
201
 
 
202
 
 
203
valign.bottom = _valignvbox
 
204
 
 
205
 
 
206
class direction(_texattr):
 
207
 
 
208
    """tex output direction attribute"""
 
209
 
 
210
    def __init__(self, value):
 
211
        self.value = value
 
212
 
 
213
    def __str__(self):
 
214
        return "%.5f" % self.value
 
215
 
 
216
 
 
217
 
 
218
direction.horizontal = direction(0)
 
219
direction.vertical   = direction(90)
 
220
direction.upsidedown = direction(180)
 
221
direction.rvertical  = direction(270)
 
222
 
 
223
 
 
224
class style(_texattr):
 
225
 
 
226
    """tex style modification attribute"""
 
227
 
 
228
    def __init__(self, praefix, suffix):
 
229
        self.praefix = praefix
 
230
        self.suffix = suffix
 
231
 
 
232
    def ModifyCmd(self, cmd):
 
233
        return self.praefix + cmd + self.suffix
 
234
 
 
235
 
 
236
style.text = style("", "")
 
237
style.math = style("$\displaystyle{}", "$")
 
238
 
 
239
 
 
240
################################################################################
 
241
# TeX message handlers
 
242
################################################################################
 
243
 
 
244
class msghandler(_texattr):
 
245
 
 
246
    """abstract base class for tex message handlers
 
247
    
 
248
    A message handler has to provide a parsemsg method. It gets a string and
 
249
    returns a string. Within the parsemsg method the handler may remove any
 
250
    part of the message it is familiar with."""
 
251
 
 
252
    def removeemptylines(self, msg):
 
253
        """any message parser may use this method to remove empty lines"""
 
254
 
 
255
        msg = re.sub("^(\n)*", "", msg)
 
256
        msg = re.sub("(\n){3,}", "\n\n", msg)
 
257
        msg = re.sub("(\n)+$", "\n", msg)
 
258
        return msg
 
259
 
 
260
 
 
261
class _msghandlershowall(msghandler):
 
262
 
 
263
    """a message handler, which shows all messages"""
 
264
 
 
265
    def parsemsg(self, msg):
 
266
        return msg
 
267
 
 
268
 
 
269
msghandler.showall = _msghandlershowall()
 
270
 
 
271
class _msghandlerhideload(msghandler):
 
272
 
 
273
    """a message handler, which hides all messages inside proper '(filename' and ')'
 
274
    the string filename has to be a readable file"""
 
275
 
 
276
    def parsemsg(self, msg):
 
277
        depth = 0
 
278
        newstr = ""
 
279
        newlevel = 0
 
280
        for c in msg:
 
281
            if newlevel and (c in (list(string.whitespace) + ["(", ")"])):
 
282
                if filestr not in ("c", "C"):
 
283
                    if not len(filestr):
 
284
                        break
 
285
                    if not os.access(filestr,os.R_OK):
 
286
                        break
 
287
                newlevel = 0
 
288
            if c == "(":
 
289
                depth += 1
 
290
                filestr = ""
 
291
                newlevel = 1
 
292
            elif c == ")":
 
293
                depth -= 1
 
294
                if depth < 0:
 
295
                    break
 
296
            elif depth == 0:
 
297
                newstr += c
 
298
            else:
 
299
                filestr += c
 
300
        else:
 
301
            # replace msg only if loop was completed and no ")" is missing
 
302
            if depth == 0:
 
303
                msg = self.removeemptylines(newstr)
 
304
        return msg
 
305
 
 
306
 
 
307
msghandler.hideload = _msghandlerhideload()
 
308
 
 
309
 
 
310
class _msghandlerhidegraphicsload(msghandler):
 
311
 
 
312
    """a message handler, which hides all messages like '<filename>'
 
313
    the string filename has to be a readable file"""
 
314
 
 
315
    def parsemsg(self, msg):
 
316
        depth = 0
 
317
        newstr = ""
 
318
        for c in msg:
 
319
            if c == "<":
 
320
                depth += 1
 
321
                if depth > 1:
 
322
                    break
 
323
                filestr = ""
 
324
            elif c == ">":
 
325
                depth -= 1
 
326
                if depth < 0:
 
327
                    break
 
328
                if not os.access(filestr,os.R_OK):
 
329
                    newstr += "<" + filestr + ">"
 
330
            elif depth == 0:
 
331
                newstr += c
 
332
            else:
 
333
                filestr += c
 
334
        else:
 
335
            # replace msg only if loop was completed and no ">" missing
 
336
            if depth == 0:
 
337
                msg = self.removeemptylines(newstr)
 
338
        return msg
 
339
 
 
340
 
 
341
msghandler.hidegraphicsload = _msghandlerhidegraphicsload()
 
342
 
 
343
 
 
344
class _msghandlerhidefontwarning(msghandler):
 
345
 
 
346
    """a message handler, which hides LaTeX font warnings, e.g.
 
347
    Messages starting with 'LaTeX Font Warning: ' which might be
 
348
    continued on following lines by '(Font)              '"""
 
349
 
 
350
    def parsemsg(self, msg):
 
351
        msglines = string.split(msg, "\n")
 
352
        newmsglines = []
 
353
        fontwarning = 0
 
354
        for line in msglines:
 
355
            if fontwarning and line[:20] != "(Font)              ":
 
356
                fontwarning = 0
 
357
            if not fontwarning and line[:20] == "LaTeX Font Warning: ":
 
358
                fontwarning = 1
 
359
            if not fontwarning:
 
360
                newmsglines.append(line)
 
361
        newmsg = reduce(lambda x, y: x + y + "\n", newmsglines, "")
 
362
        return self.removeemptylines(newmsg)
 
363
 
 
364
 
 
365
msghandler.hidefontwarning = _msghandlerhidefontwarning()
 
366
 
 
367
 
 
368
class _msghandlerhidebuterror(msghandler):
 
369
 
 
370
    """a message handler, hides all messages whenever they do
 
371
    not contain a line starting with '! '"""
 
372
 
 
373
    def parsemsg(self, msg):
 
374
        # the "\n" + msg instead of msg itself is needed, if the message starts with "! "
 
375
        if string.find("\n" + msg, "\n! ") != -1:
 
376
            return msg
 
377
        else:
 
378
            return ""
 
379
 
 
380
 
 
381
msghandler.hidebuterror = _msghandlerhidebuterror()
 
382
 
 
383
 
 
384
class _msghandlerhideall(msghandler):
 
385
 
 
386
    """a message handler, which hides all messages"""
 
387
 
 
388
    def parsemsg(self, msg):
 
389
        return ""
 
390
 
 
391
 
 
392
msghandler.hideall = _msghandlerhideall()
 
393
 
 
394
 
 
395
################################################################################
 
396
# extent handlers
 
397
################################################################################
 
398
 
 
399
class missextents(_texattr):
 
400
 
 
401
    """abstract base class for handling missing extents
 
402
 
 
403
    A miss extent class has to provide a misshandler method."""
 
404
 
 
405
 
 
406
_missextentsreturnzero_report = 0
 
407
def _missextentsreturnzero_printreport():
 
408
    sys.stderr.write("""
 
409
pyx.tex: Some requested extents were missing and have been replaced by zero.
 
410
         Please run the file again to get correct extents.\n""")
 
411
 
 
412
class _missextentsreturnzero(missextents):
 
413
 
 
414
    def misshandler(self, texinstance):
 
415
        global _missextentsreturnzero_report
 
416
        if not _missextentsreturnzero_report:
 
417
            atexit.register(_missextentsreturnzero_printreport)
 
418
        _missextentsreturnzero_report = 1
 
419
        return map(lambda x: 0 * unit.t_pt, texinstance.BoxCmds[0].CmdExtents)
 
420
 
 
421
 
 
422
missextents.returnzero = _missextentsreturnzero()
 
423
 
 
424
 
 
425
class _missextentsreturnzeroquiet(missextents):
 
426
 
 
427
    def misshandler(self, texinstance):
 
428
        return map(lambda x: 0 * unit.t_pt, texinstance.BoxCmds[0].CmdExtents)
 
429
 
 
430
 
 
431
missextents.returnzeroquiet = _missextentsreturnzeroquiet()
 
432
 
 
433
 
 
434
class _missextentsraiseerror(missextents):
 
435
 
 
436
    def misshandler(self, texinstance):
 
437
        raise TexMissExtentError
 
438
 
 
439
 
 
440
missextents.raiseerror = _missextentsraiseerror()
 
441
 
 
442
 
 
443
class _missextentscreateextents(missextents):
 
444
 
 
445
    def misshandler(self, texinstance):
 
446
        if isinstance(texinstance, latex):
 
447
            storeauxfilename = texinstance.auxfilename
 
448
            texinstance.auxfilename = None
 
449
        texinstance.DoneRunTex = 0
 
450
        texinstance._run()
 
451
        texinstance.DoneRunTex = 0
 
452
        if isinstance(texinstance, latex):
 
453
            texinstance.auxfilename = storeauxfilename
 
454
        return texinstance.BoxCmds[0].Extents(texinstance.BoxCmds[0].CmdExtents,
 
455
                                              missextents.returnzero, texinstance)
 
456
 
 
457
 
 
458
missextents.createextents = _missextentscreateextents()
 
459
 
 
460
 
 
461
class _missextentscreateallextents(missextents):
 
462
 
 
463
    def misshandler(self, texinstance):
 
464
        if isinstance(texinstance, latex):
 
465
            storeauxfilename = texinstance.auxfilename
 
466
            texinstance.auxfilename = None
 
467
        texinstance.DoneRunTex = 0
 
468
        storeextents = texinstance.BoxCmds[0].CmdExtents[0]
 
469
        texinstance.BoxCmds[0].CmdExtents = [_extent.wd, _extent.ht, _extent.dp]
 
470
        texinstance._run()
 
471
        texinstance.BoxCmds[0].CmdExtents[0] = storeextents
 
472
        texinstance.DoneRunTex = 0
 
473
        if isinstance(texinstance, latex):
 
474
            texinstance.auxfilename = storeauxfilename
 
475
        return texinstance.BoxCmds[0].Extents(texinstance.BoxCmds[0].CmdExtents,
 
476
                                              missextents.returnzero, texinstance)
 
477
 
 
478
 
 
479
missextents.createallextents = _missextentscreateallextents()
 
480
 
 
481
 
 
482
################################################################################
 
483
# TeX exceptions
 
484
################################################################################
 
485
 
 
486
class TexExcept(base.PyXExcept):
 
487
 
 
488
    pass
 
489
 
 
490
 
 
491
class TexLeftParenthesisError(TexExcept):
 
492
 
 
493
    def __str__(self):
 
494
        return "no matching parenthesis for '{' found"
 
495
 
 
496
 
 
497
class TexRightParenthesisError(TexExcept):
 
498
 
 
499
    def __str__(self):
 
500
        return "no matching parenthesis for '}' found"
 
501
 
 
502
 
 
503
class TexHalignError(TexExcept):
 
504
 
 
505
    def __str__(self):
 
506
        return "unkown halign"
 
507
 
 
508
 
 
509
class TexValignError(TexExcept):
 
510
 
 
511
    def __str__(self):
 
512
        return "unkown valign"
 
513
 
 
514
 
 
515
class TexDefAfterBoxError(TexExcept):
 
516
 
 
517
    def __str__(self):
 
518
        return "definition commands not allowed after output commands"
 
519
 
 
520
 
 
521
class TexMissExtentError(TexExcept):
 
522
 
 
523
    def __str__(self):
 
524
        return "requested tex extent not available"
 
525
 
 
526
 
 
527
################################################################################
 
528
# modules internal stuff
 
529
################################################################################
 
530
 
 
531
class _extent:
 
532
 
 
533
    def __init__(self, value):
 
534
        self.value = value
 
535
 
 
536
    def __str__(self):
 
537
        return self.value
 
538
 
 
539
 
 
540
_extent.wd = _extent("wd")
 
541
_extent.ht = _extent("ht")
 
542
_extent.dp = _extent("dp")
 
543
 
 
544
 
 
545
class _TexCmd:
 
546
 
 
547
    """class for all user supplied commands"""
 
548
 
 
549
    PyxMarker = "PyxMarker"
 
550
    BeginPyxMarker = "Begin" + PyxMarker
 
551
    EndPyxMarker = "End" + PyxMarker
 
552
 
 
553
    def __init__(self, Marker, Stack, msghandlers):
 
554
        self.Marker = Marker
 
555
        self.Stack = Stack
 
556
        self.msghandlers = msghandlers
 
557
 
 
558
    def TexParenthesisCheck(self, Cmd):
 
559
        """check for proper usage of "{" and "}" in Cmd"""
 
560
 
 
561
        depth = 0
 
562
        esc = 0
 
563
        for c in Cmd:
 
564
            if c == "{" and not esc:
 
565
                depth = depth + 1
 
566
            if c == "}" and not esc:
 
567
                depth = depth - 1
 
568
                if depth < 0:
 
569
                    raise TexRightParenthesisError
 
570
            if c == "\\":
 
571
                esc = (esc + 1) % 2
 
572
            else:
 
573
                esc = 0
 
574
        if depth > 0:
 
575
            raise TexLeftParenthesisError
 
576
 
 
577
    def BeginMarkerStr(self):
 
578
        return "%s[%s]" % (self.BeginPyxMarker, self.Marker, )
 
579
    
 
580
    def WriteBeginMarker(self, file):
 
581
        file.write("\\immediate\\write16{%s}%%\n" % self.BeginMarkerStr())
 
582
 
 
583
    def EndMarkerStr(self):
 
584
        return "%s[%s]" % (self.EndPyxMarker, self.Marker, )
 
585
 
 
586
    def WriteEndMarker(self, file):
 
587
        file.write("\\immediate\\write16{%s}%%\n" % self.EndMarkerStr())
 
588
 
 
589
    def WriteError(self, msg):
 
590
        sys.stderr.write("Traceback (innermost last):\n")
 
591
        traceback.print_list(self.Stack)
 
592
        sys.stderr.write("(La)TeX Message:\n" + msg + "\n")
 
593
 
 
594
    def CheckMarkerError(self, file):
 
595
        """read markers and identify the message"""
 
596
 
 
597
        line = file.readline()
 
598
        while (line != "") and (line[:-1] != self.BeginMarkerStr()):
 
599
            line = file.readline()
 
600
        msg = ""
 
601
        line = file.readline()
 
602
        while (line != "") and (line[:-1] != self.EndMarkerStr()):
 
603
            msg = msg + line
 
604
            line = file.readline()
 
605
        if line == "":
 
606
            self.WriteError(msg)
 
607
            raise IOError
 
608
        else:
 
609
            # check if message can be ignored
 
610
            doprint = 0
 
611
            parsedmsg = msg
 
612
            for msghandler in self.msghandlers:
 
613
                parsedmsg = msghandler.parsemsg(parsedmsg)
 
614
            for c in parsedmsg:
 
615
                if c not in string.whitespace:
 
616
                    self.WriteError(parsedmsg)
 
617
                    break
 
618
 
 
619
 
 
620
class _DefCmd(_TexCmd):
 
621
 
 
622
    """definition commands"""
 
623
 
 
624
    def __init__(self, DefCmd, Marker, Stack, msghandlers):
 
625
        _TexCmd.__init__(self, Marker, Stack, msghandlers)
 
626
        self.TexParenthesisCheck(DefCmd)
 
627
        self.DefCmd = "%s%%\n" % DefCmd
 
628
 
 
629
    def write(self, file):
 
630
        self.WriteBeginMarker(file)
 
631
        file.write(self.DefCmd)
 
632
        self.WriteEndMarker(file)
 
633
 
 
634
 
 
635
class _CmdPut:
 
636
 
 
637
    """print parameters for a BoxCmd (data structure)"""
 
638
 
 
639
    def __init__(self, x, y, halign, direction, color):
 
640
        self.x = x
 
641
        self.y = y
 
642
        self.halign = halign
 
643
        self.direction = direction
 
644
        self.color = color
 
645
 
 
646
 
 
647
class _BoxCmd(_TexCmd):
 
648
 
 
649
    """BoxCmd (for printing text and getting extents)"""
 
650
 
 
651
    def __init__(self, DefCmdsStr, BoxCmd, style, fontsize, valign, Marker, Stack, msghandlers):
 
652
        _TexCmd.__init__(self, Marker, Stack, msghandlers)
 
653
        self.TexParenthesisCheck(BoxCmd)
 
654
        self.DefCmdsStr = DefCmdsStr
 
655
        self.BoxCmd = "{%s}%%\n" % BoxCmd # add another "{" to ensure, that everything goes into the Box
 
656
        self.CmdPuts = [] # list, where to put the command
 
657
        self.CmdExtents = [] # list, which extents are requested
 
658
 
 
659
        self.BoxCmd = style.ModifyCmd(self.BoxCmd)
 
660
        if valign is not None:
 
661
            if isinstance(valign, _valignvtop):
 
662
                self.BoxCmd = "\\linewidth%.5ftruept\\vtop{\\hsize\\linewidth{%s}}" % \
 
663
                               (unit.topt(valign.hsize) * 72.27/72, self.BoxCmd, )
 
664
            elif isinstance(valign, _valignvbox):
 
665
                self.BoxCmd = "\\linewidth%.5ftruept\\vbox{\\hsize\\linewidth{%s}}" % \
 
666
                               (unit.topt(valign.hsize) * 72.27/72, self.BoxCmd, )
 
667
            else:
 
668
                raise TexValignError
 
669
        self.BoxCmd = "\\setbox\\localbox=\\hbox{\\%s%s}%%\n" % (fontsize, self.BoxCmd, )
 
670
 
 
671
    def __cmp__(self, other):
 
672
        if other is None: return 1
 
673
        return cmp(self.BoxCmd, other.BoxCmd)
 
674
 
 
675
    __rcmp__ = __cmp__
 
676
 
 
677
    def write(self, file):
 
678
        self.WriteBeginMarker(file)
 
679
        file.write(self.BoxCmd)
 
680
        self.WriteEndMarker(file)
 
681
        for CmdExtent in self.CmdExtents:
 
682
            file.write("\\immediate\\write\\sizefile{%s:%s:%s:\\the\\%s\\localbox}%%\n" %
 
683
                       (self.MD5(), CmdExtent, time.time(), CmdExtent, ))
 
684
        for CmdPut in self.CmdPuts:
 
685
 
 
686
            file.write("{\\vbox to0pt{\\kern%.5ftruept\\hbox{\\kern%.5ftruept\\ht\\localbox0pt" %
 
687
                        (-CmdPut.y, CmdPut.x))
 
688
 
 
689
            if CmdPut.direction != direction.horizontal:
 
690
                file.write("\\special{ps: gsave currentpoint currentpoint translate " +
 
691
                           str(CmdPut.direction) + " neg rotate neg exch neg exch translate }")
 
692
            if CmdPut.color != color.gray.black:
 
693
                file.write("\\special{ps: ")
 
694
                CmdPut.color.outputPS(file)
 
695
                file.write(" }")
 
696
            if CmdPut.halign == halign.left:
 
697
                pass
 
698
            elif CmdPut.halign == halign.center:
 
699
                file.write("\kern-.5\wd\localbox")
 
700
            elif CmdPut.halign == halign.right:
 
701
                file.write("\kern-\wd\localbox")
 
702
            else:
 
703
                raise TexHalignError
 
704
            file.write("\\copy\\localbox")
 
705
 
 
706
            if CmdPut.color != color.gray.black:
 
707
                file.write("\\special{ps: ")
 
708
                color.gray.black.outputPS(file)
 
709
                file.write(" }")
 
710
            if CmdPut.direction != direction.horizontal:
 
711
                file.write("\\special{ps: currentpoint grestore moveto }")
 
712
            file.write("}\\vss}\\nointerlineskip}%\n")
 
713
 
 
714
    def MD5(self):
 
715
        """creates an MD5 hex string for texinit + Cmd"""
 
716
 
 
717
        h = string.hexdigits
 
718
        r = ''
 
719
        s = md5.md5(self.DefCmdsStr + self.BoxCmd).digest()
 
720
        for c in s:
 
721
            i = ord(c)
 
722
            r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
 
723
        return r
 
724
 
 
725
    def Put(self, x, y, halign, direction, color):
 
726
        self.CmdPuts.append(_CmdPut(x, y, halign, direction, color))
 
727
 
 
728
    def Extents(self, extents, missextents, texinstance):
 
729
        """get sizes from previous LaTeX run"""
 
730
 
 
731
        for extent in extents:
 
732
            if extent not in self.CmdExtents:
 
733
                self.CmdExtents.append(extent)
 
734
 
 
735
        result = []
 
736
        for extent in extents:
 
737
            s = self.MD5() + ":" + str(extent)
 
738
            for size in texinstance.Sizes:
 
739
                if size[:len(s)] == s:
 
740
                    texpt = float(string.rstrip(size.split(":")[3][:-3]))
 
741
                    result.append(unit.t_pt * texpt * 72.0 / 72.27)
 
742
                    break
 
743
            else:
 
744
                break
 
745
        else:
 
746
            return result
 
747
 
 
748
        # extent was not found --- temporarily remove all other commands in
 
749
        # order to allow the misshandler to access everything it ever wants
 
750
        storeboxcmds = texinstance.BoxCmds
 
751
        storecmdputs = self.CmdPuts
 
752
        storecmdextents = self.CmdExtents
 
753
        texinstance.BoxCmds = [self, ]
 
754
        self.CmdPuts = []
 
755
        self.CmdExtents = extents
 
756
        try:
 
757
            result = missextents.misshandler(texinstance)
 
758
        finally:
 
759
            texinstance.BoxCmds = storeboxcmds
 
760
            self.CmdPuts = storecmdputs
 
761
            self.CmdExtents = storecmdextents
 
762
        return result
 
763
 
 
764
 
 
765
################################################################################
 
766
# tex, latex class
 
767
################################################################################
 
768
 
 
769
class _tex(base.canvasitem, attrlist):
 
770
 
 
771
    """major parts are of tex and latex class are shared and implemented here"""
 
772
 
 
773
    def __init__(self, defaultmsghandlers=msghandler.hideload,
 
774
                       defaultmissextents=missextents.returnzero,
 
775
                       texfilename=None):
 
776
        if isinstance(defaultmsghandlers, msghandler):
 
777
            self.defaultmsghandlers = (defaultmsghandlers,)
 
778
        else:
 
779
            self.defaultmsghandlers = defaultmsghandlers
 
780
        self.defaultmissextents = defaultmissextents
 
781
        self.texfilename = texfilename
 
782
        self.DefCmds = []
 
783
        self.DefCmdsStr = None
 
784
        self.BoxCmds = []
 
785
        self.DoneRunTex = 0
 
786
 
 
787
        if len(os.path.basename(sys.argv[0])):
 
788
            basename = os.path.basename(sys.argv[0])
 
789
            if basename[-3:] == ".py":
 
790
                basename = basename[:-3]
 
791
            self.SizeFileName = os.path.join(os.getcwd(), basename + ".size")
 
792
        else:
 
793
            self.SizeFileName = os.path.join(os.getcwd(), "pyxput.size")
 
794
        try:
 
795
            file = open(self.SizeFileName, "r")
 
796
            self.Sizes = file.readlines()
 
797
            file.close()
 
798
        except IOError:
 
799
            self.Sizes = [ ]
 
800
 
 
801
    def _execute(self, command):
 
802
        if os.system(command):
 
803
            sys.stderr.write("The exit code of the following command was non-zero:\n" + command +
 
804
"""\nUsually, additional information causing this trouble appears closeby.
 
805
However, you may check the origin by keeping all temporary files.
 
806
In order to achieve this, you have to specify a texfilename in the
 
807
constructor of the class pyx.(la)tex. You can then try to run the
 
808
command by yourself.\n""")
 
809
 
 
810
    def _createaddfiles(self, tempname):
 
811
        pass
 
812
 
 
813
    def _removeaddfiles(self, tempname):
 
814
        pass
 
815
 
 
816
    def _executetex(self, tempname):
 
817
        pass
 
818
 
 
819
    def _executedvips(self, tempname):
 
820
        self._execute("dvips -O0in,11in -E -o %(t)s.eps %(t)s.dvi > %(t)s.dvipsout 2> %(t)s.dvipserr" % {"t": tempname})
 
821
 
 
822
    def _run(self):
 
823
        """run (La)TeX & dvips, report errors, fill self.abbox & self.epsdata"""
 
824
 
 
825
        if self.DoneRunTex:
 
826
            return
 
827
 
 
828
        if self.texfilename:
 
829
            mktemp = str(self.texfilename)
 
830
        else:
 
831
            storetempdir = tempfile.tempdir
 
832
            tempfile.tempdir = os.curdir
 
833
            mktemp = tempfile.mktemp()
 
834
            tempfile.tempdir = storetempdir
 
835
        tempname = os.path.basename(mktemp)
 
836
 
 
837
        self._createaddfiles(tempname)
 
838
 
 
839
        texfile = open(tempname + ".tex", "w")
 
840
 
 
841
        texfile.write("\\nonstopmode%\n")
 
842
        texfile.write("\\def\PyX{P\\kern-.3em\\lower.5ex\\hbox{Y}\\kern-.18em X}%\n")
 
843
        texfile.write("\\newwrite\\sizefile%\n\\newbox\\localbox%\n\\newbox\\pagebox%\n")
 
844
        texfile.write("{\\catcode`\\~=12\\immediate\\openout\\sizefile=%s.size\\relax}%%\n" % tempname)
 
845
 
 
846
        for Cmd in self.DefCmds:
 
847
            Cmd.write(texfile)
 
848
 
 
849
        texfile.write("\\setbox\\pagebox=\\vbox{%\n")
 
850
 
 
851
        for Cmd in self.BoxCmds:
 
852
            Cmd.write(texfile)
 
853
 
 
854
        texfile.write("}\n\\immediate\\closeout\\sizefile\n\\shipout\\copy\\pagebox\n")
 
855
        texfile.write(self._endcmd())
 
856
        texfile.close()
 
857
 
 
858
        self._executetex(tempname)
 
859
 
 
860
        try:
 
861
            outfile = open(tempname + ".texout", "r")
 
862
            for Cmd in self.DefCmds + self.BoxCmds:
 
863
                Cmd.CheckMarkerError(outfile)
 
864
            outfile.close()
 
865
        except IOError:
 
866
            sys.stderr.write("""An unexpected error occured while reading the (La)TeX output.
 
867
May be, you just have no disk space available. Or something badly
 
868
in your commands caused (La)TeX to give up completely. Or your
 
869
(La)TeX installation might be broken at all.
 
870
You may try to check the origin by keeping all temporary files.
 
871
In order to achieve this, you have to specify a texfilename in the
 
872
constructor of the class pyx.tex. You can then try to run (La)TeX
 
873
by yourself.\n""")
 
874
 
 
875
        if not os.access(tempname + ".dvi", 0):
 
876
            sys.stderr.write("""Can't find the dvi file which should be produced by (La)TeX.
 
877
May be, you just have no disk space available. Or something badly
 
878
in your commands caused (La)TeX to give up completely. Or your
 
879
(La)TeX installation might be broken at all.
 
880
You may try to check the origin by keeping all temporary files.
 
881
In order to achieve this, you have to specify a texfilename in the
 
882
constructor of the class pyx.tex. You can then try to run (La)TeX
 
883
by yourself.\n""")
 
884
 
 
885
        else:
 
886
            self._executedvips(tempname)
 
887
            if not os.access(tempname + ".eps", 0):
 
888
                sys.stderr.write("""Error reading the eps file which should be produced by dvips.
 
889
May be, you just have no disk space available. Or something badly
 
890
in your commands caused dvips to give up completely. Or your
 
891
(La)TeX installation might be broken at all.
 
892
You may try to check the origin by keeping all temporary files.
 
893
In order to achieve this, you have to specify a texfilename in the
 
894
constructor of the class pyx.tex. You can then try to run dvips
 
895
by yourself.\n""")
 
896
            else:
 
897
                aepsfile = epsfile.epsfile(0, 0, tempname + ".eps", translatebbox=0, clip=0)
 
898
                self.abbox = aepsfile.bbox()
 
899
                self.aprolog = aepsfile.prolog()
 
900
                epsdatafile = StringIO.StringIO()
 
901
                aepsfile.outputPS(epsdatafile)
 
902
                self.epsdata = epsdatafile.getvalue()
 
903
 
 
904
        # merge new sizes
 
905
 
 
906
        OldSizes = self.Sizes
 
907
 
 
908
        try:
 
909
            NewSizeFile = open(tempname + ".size", "r")
 
910
            NewSizes = NewSizeFile.readlines()
 
911
            NewSizeFile.close()
 
912
        except IOError:
 
913
            NewSizes = []
 
914
 
 
915
        if (len(NewSizes) != 0) or (len(OldSizes) != 0):
 
916
            SizeFile = open(self.SizeFileName, "w")
 
917
            SizeFile.writelines(NewSizes)
 
918
            self.Sizes = NewSizes
 
919
            for OldSize in OldSizes:
 
920
                OldSizeSplit = OldSize.split(":")
 
921
                for NewSize in NewSizes:
 
922
                    if NewSize.split(":")[0:2] == OldSizeSplit[0:2]:
 
923
                        break
 
924
                else:
 
925
                    if time.time() < float(OldSizeSplit[2]) + 60*60*24:   # we keep size results for one day
 
926
                        SizeFile.write(OldSize)
 
927
                        self.Sizes.append(OldSize)
 
928
 
 
929
        if not self.texfilename:
 
930
            for suffix in ("tex", "log", "size", "dvi", "eps", "texout", "texerr", "dvipsout", "dvipserr", ):
 
931
                try:
 
932
                    os.unlink(tempname + "." + suffix)
 
933
                except:
 
934
                    pass
 
935
 
 
936
        self._removeaddfiles(tempname)
 
937
        self.DoneRunTex = 1
 
938
 
 
939
    def prolog(self):
 
940
        self._run()
 
941
        return self.aprolog
 
942
 
 
943
    def bbox(self):
 
944
        self._run()
 
945
        return self.abbox
 
946
 
 
947
    def outputPS(self, file):
 
948
        self._run()
 
949
        file.writelines(self.epsdata)
 
950
 
 
951
    def define(self, Cmd, *attrs):
 
952
        if len(self.BoxCmds):
 
953
            raise TexDefAfterBoxError
 
954
        self.DoneRunTex = 0
 
955
        self.attrcheck(attrs, (), (msghandler,))
 
956
        self.DefCmds.append(_DefCmd(Cmd,
 
957
                                    len(self.DefCmds)+ len(self.BoxCmds),
 
958
                                    traceback.extract_stack(),
 
959
                                    self.attrgetall(attrs, msghandler, self.defaultmsghandlers)))
 
960
 
 
961
    def _insertcmd(self, Cmd, *attrs):
 
962
        if not len(self.BoxCmds):
 
963
            self._beginboxcmds()
 
964
            self.DefCmdsStr = reduce(lambda x,y: x + y.DefCmd, self.DefCmds, "")
 
965
        mystyle = self.attrget(attrs, style, style.text)
 
966
        myfontsize = self.attrget(attrs, fontsize, fontsize.normalsize)
 
967
        myvalign = self.attrget(attrs, valign, None)
 
968
        mymsghandlers = self.attrgetall(attrs, msghandler, self.defaultmsghandlers)
 
969
        MyCmd = _BoxCmd(self.DefCmdsStr, Cmd, mystyle, myfontsize, myvalign,
 
970
                        len(self.DefCmds) + len(self.BoxCmds), traceback.extract_stack(), mymsghandlers)
 
971
        if MyCmd not in self.BoxCmds:
 
972
            self.BoxCmds.append(MyCmd)
 
973
        for Cmd in self.BoxCmds:
 
974
            if Cmd == MyCmd:
 
975
                UseCmd = Cmd    # we could use MyCmd directly if we have just inserted it before
 
976
                                # (that's due to the side effect, that append doesn't make a copy of the element,
 
977
                                # but we ignore this here -- we don't want to depend on this side effect)
 
978
        return UseCmd
 
979
 
 
980
    def _text(self, x, y, Cmd, *attrs):
 
981
        """print Cmd at (x, y) --- position parameters in postscipt points"""
 
982
 
 
983
        self.DoneRunTex = 0
 
984
        self.attrcheck(attrs, (style, fontsize, halign, valign, direction, color.color), (msghandler,))
 
985
        myhalign = self.attrget(attrs, halign, halign.left)
 
986
        mydirection = self.attrget(attrs, direction, direction.horizontal)
 
987
        mycolor = self.attrget(attrs, color.color, color.gray.black)
 
988
        self._insertcmd(Cmd, *attrs).Put(x * 72.27 / 72.0, y * 72.27 / 72.0, myhalign, mydirection, mycolor)
 
989
 
 
990
    def text(self, x, y, Cmd, *attrs):
 
991
        """print Cmd at (x, y)"""
 
992
 
 
993
        self._text(unit.topt(x), unit.topt(y), Cmd, *attrs)
 
994
 
 
995
    def textwd(self, Cmd, *attrs):
 
996
        """get width of Cmd"""
 
997
 
 
998
        self.DoneRunTex = 0
 
999
        self.attrcheck(attrs, (style, fontsize, missextents), (msghandler,))
 
1000
        mymissextents = self.attrget(attrs, missextents, self.defaultmissextents)
 
1001
        return self._insertcmd(Cmd, *attrs).Extents((_extent.wd, ), mymissextents, self)[0]
 
1002
 
 
1003
    def textht(self, Cmd, *attrs):
 
1004
        """get height of Cmd"""
 
1005
 
 
1006
        self.DoneRunTex = 0
 
1007
        self.attrcheck(attrs, (style, fontsize, valign, missextents), (msghandler,))
 
1008
        mymissextents = self.attrget(attrs, missextents, self.defaultmissextents)
 
1009
        return self._insertcmd(Cmd, *attrs).Extents((_extent.ht, ), mymissextents, self)[0]
 
1010
 
 
1011
 
 
1012
    def textdp(self, Cmd, *attrs):
 
1013
        """get depth of Cmd"""
 
1014
 
 
1015
        self.DoneRunTex = 0
 
1016
        self.attrcheck(attrs, (style, fontsize, valign, missextents), (msghandler,))
 
1017
        mymissextents = self.attrget(attrs, missextents, self.defaultmissextents)
 
1018
        return self._insertcmd(Cmd, *attrs).Extents((_extent.dp, ), mymissextents, self)[0]
 
1019
 
 
1020
 
 
1021
class tex(_tex):
 
1022
 
 
1023
    """tex class adds the specializations to _tex needed for tex"""
 
1024
 
 
1025
    def __init__(self, lfs="10pt", **addargs):
 
1026
        _tex.__init__(self, **addargs)
 
1027
        # XXX other ways for creating font sizes?
 
1028
        try:
 
1029
            LocalLfsName = str(lfs) + ".lfs"
 
1030
            lfsdef = open(LocalLfsName, "r").read()
 
1031
        except IOError:
 
1032
            try:
 
1033
                try:
 
1034
                    SysLfsName = os.path.join(sys.prefix, "share", "pyx", str(lfs) + ".lfs")
 
1035
                    lfsdef = open(SysLfsName, "r").read()
 
1036
                except IOError:
 
1037
                    SysLfsName = os.path.join(os.path.dirname(__file__), "lfs", str(lfs) + ".lfs")
 
1038
                    lfsdef = open(SysLfsName, "r").read()
 
1039
            except IOError:
 
1040
                files = map(lambda x: x[:-4],
 
1041
                            filter(lambda x: x[-4:] == ".lfs",
 
1042
                                   os.listdir(".") +
 
1043
                                   os.listdir(os.path.join(sys.prefix, "share", "pyx")),
 
1044
                                   os.listdir(os.path.join(os.path.dirname(__file__), "lfs"))))
 
1045
                raise IOError("file '%s.lfs' not found. Available latex font sizes:\n%s" % (lfs, files))
 
1046
        self.define(lfsdef)
 
1047
        self.define("\\newdimen\\linewidth%\n\\hsize0truein%\n\\vsize0truein%\n\\hoffset-1truein%\n\\voffset-1truein")
 
1048
 
 
1049
    def _beginboxcmds(self):
 
1050
        pass
 
1051
 
 
1052
    def _endcmd(self):
 
1053
        return "\\end\n"
 
1054
 
 
1055
    def _executetex(self, tempname):
 
1056
        self._execute("tex %(t)s.tex > %(t)s.texout 2> %(t)s.texerr" % {"t": tempname})
 
1057
 
 
1058
 
 
1059
class latex(_tex):
 
1060
 
 
1061
    """latex class adds the specializations to _tex needed for latex"""
 
1062
 
 
1063
    def __init__(self, docclass="article", docopt=None, auxfilename=None, **addargs):
 
1064
        _tex.__init__(self, **addargs)
 
1065
        self.auxfilename = auxfilename
 
1066
        if docopt:
 
1067
            self.define("\\documentclass[" + str(docopt) + "]{" + str(docclass) + "}")
 
1068
        else:
 
1069
            self.define("\\documentclass{" + str(docclass) + "}")
 
1070
        self.define("\\hsize0truein%\n\\vsize0truein%\n\\hoffset-1truein%\n\\voffset-1truein")
 
1071
 
 
1072
    def _beginboxcmds(self):
 
1073
        self.define("\\begin{document}")
 
1074
 
 
1075
    def _endcmd(self):
 
1076
        return "\\end{document}\n"
 
1077
 
 
1078
    def _createaddfiles(self, tempname):
 
1079
        if self.auxfilename is not None:
 
1080
            writenew = 0
 
1081
            try:
 
1082
                os.rename(self.auxfilename + ".aux", tempname + ".aux")
 
1083
            except OSError:
 
1084
                writenew = 1
 
1085
        else:
 
1086
            writenew = 1
 
1087
        if writenew:
 
1088
            auxfile = open(tempname + ".aux", "w")
 
1089
            auxfile.write("\\relax\n")
 
1090
            auxfile.close()
 
1091
 
 
1092
    def _executetex(self, tempname):
 
1093
        self._execute("latex %(t)s.tex > %(t)s.texout 2> %(t)s.texerr" % {"t": tempname})
 
1094
 
 
1095
    def _removeaddfiles(self, tempname):
 
1096
        if self.auxfilename is not None:
 
1097
            os.rename(tempname + ".aux", self.auxfilename + ".aux")
 
1098
        else:
 
1099
            os.unlink(tempname + ".aux")
 
1100