axis drawing after data drawing (better solution needed)
[PyX/mjg.git] / pyx / text.py
blob8d68a9cdbebfe209a9c6fa1f127e3ade10f87fbe
1 #!/usr/bin/env python
3 import os, struct
5 # this code will be part of PyX 0.2 ... ;-)
7 _DVI_CHARMIN = 0 # typeset a character and move right (range min)
8 _DVI_CHARMAX = 127 # typeset a character and move right (range max)
9 _DVI_SET1234 = 128 # typeset a character and move right
10 _DVI_SETRULE = 132 # typeset a rule and move right
11 _DVI_PUT1234 = 133 # typeset a character
12 _DVI_PUTRULE = 137 # typeset a rule
13 _DVI_NOP = 138 # no operation
14 _DVI_BOP = 139 # beginning of page
15 _DVI_EOP = 140 # ending of page
16 _DVI_PUSH = 141 # save the current positions (h, v, w, x, y, z)
17 _DVI_POP = 142 # restore positions (h, v, w, x, y, z)
18 _DVI_RIGHT1234 = 143 # move right
19 _DVI_W0 = 147 # move right by w
20 _DVI_W1234 = 148 # move right and set w
21 _DVI_X0 = 152 # move right by x
22 _DVI_X1234 = 153 # move right and set x
23 _DVI_DOWN1234 = 157 # move down
24 _DVI_Y0 = 161 # move down by y
25 _DVI_Y1234 = 162 # move down and set y
26 _DVI_Z0 = 166 # move down by z
27 _DVI_Z1234 = 167 # move down and set z
28 _DVI_FNTNUMMIN = 171 # set current font (range min)
29 _DVI_FNTNUMMAX = 234 # set current font (range max)
30 _DVI_FNT1234 = 235 # set current font
31 _DVI_SPECIAL1234 = 239 # special (dvi extention)
32 _DVI_FNTDEF1234 = 243 # define the meaning of a font number
33 _DVI_PRE = 247 # preamble
34 _DVI_POST = 248 # postamble beginning
35 _DVI_POSTPOST = 249 # postamble ending
37 _DVI_VERSION = 2 # dvi version
39 # position variable indices
40 _POS_H = 0
41 _POS_V = 1
42 _POS_W = 2
43 _POS_X = 3
44 _POS_Y = 4
45 _POS_Z = 5
47 # reader states
48 _READ_PRE = 1
49 _READ_NOPAGE = 2
50 _READ_PAGE = 3
51 _READ_POST = 4
52 _READ_POSTPOST = 5
53 _READ_DONE = 6
55 class fix_word:
56 def __init__(self, word):
57 if word>=0:
58 self.sign = 1
59 else:
60 self.sign = -1
62 self.precomma = abs(word) >> 20
63 self.postcomma = abs(word) & 0xFFFFF
65 def __float__(self):
66 return self.sign * (self.precomma + 1.0*self.postcomma/0xFFFFF)
68 def __mul__(self, other):
69 # hey, it's Q&D
70 result = fix_word(0)
72 result.sign = self.sign*other.sign
73 c = self.postcomma*other.precomma + self.precomma*other.postcomma
74 result.precomma = self.precomma*other.precomma + (c >> 20)
75 result.postcomma = c & 0xFFFFF + ((self.postcomma*other.postcomma) >> 40)
76 return result
79 class char_info_word:
80 def __init__(self, word):
81 self.width_index = (word & 0xFF000000) >> 24
82 self.height_index = (word & 0x00F00000) >> 20
83 self.depth_index = (word & 0x000F0000) >> 16
84 self.italic_index = (word & 0x0000FC00) >> 10
85 self.tag = (word & 0x00000300) >> 8
86 self.remainder = (word & 0x000000FF)
88 if self.width_index==0:
89 raise TFMError, "width_index should not be zero"
91 class binfile(file):
93 def readint(self, bytes = 4, signed = 0):
94 first = 1
95 result = 0
96 while bytes:
97 value = ord(self.read(1))
98 if first and signed and value > 127:
99 value -= 256
100 first = 0
101 result = 256 * result + value
102 bytes -= 1
103 return result
105 def readint32(self):
106 return struct.unpack(">l", self.read(4))[0]
108 def readuint32(self):
109 return struct.unpack(">L", self.read(4))[0]
111 def readint16(self):
112 return struct.unpack(">h", self.read(2))[0]
114 def readuint16(self):
115 return struct.unpack(">H", self.read(2))[0]
117 def readchar(self):
118 return struct.unpack("b", self.read(1))[0]
120 def readuchar(self):
121 return struct.unpack("B", self.read(1))[0]
123 def readstring(self, bytes):
124 l = self.readuchar()
125 assert l<bytes-1, "inconsistency in file: string too long"
126 return self.read(bytes-1)[:l]
128 class DVIError(Exception): pass
130 class TFMError(Exception): pass
132 class TFMFile:
133 def __init__(self, name):
134 self.file = binfile(name, "rb")
137 # read pre header
140 self.lf = self.file.readint16()
141 self.lh = self.file.readint16()
142 self.bc = self.file.readint16()
143 self.ec = self.file.readint16()
144 self.nw = self.file.readint16()
145 self.nh = self.file.readint16()
146 self.nd = self.file.readint16()
147 self.ni = self.file.readint16()
148 self.nl = self.file.readint16()
149 self.nk = self.file.readint16()
150 self.ne = self.file.readint16()
151 self.np = self.file.readint16()
153 if not (self.bc-1<=self.ec<=255 and
154 self.ne<=256 and
155 self.lf==6+self.lh+(self.ec-self.bc+1)+self.nw+self.nh+self.nd
156 +self.ni+self.nl+self.nk+self.ne+self.np):
157 raise TFMError, "error in TFM pre-header"
160 # read header
163 self.checksum = self.file.readuint32()
164 self.designsize = fix_word(self.file.readint32())
165 if self.lh>2:
166 self.charcoding = self.file.readstring(40)
167 else:
168 self.charcoding = None
170 if self.lh>12:
171 self.fontfamily = self.file.readstring(20)
172 else:
173 self.fontfamily = None
175 if self.lh>17:
176 self.sevenbitsave = self.file.readuchar()
177 # ignore the following two bytes
178 self.file.readint16()
179 facechar = self.file.readuchar()
180 # decode ugly face specification into the Knuth suggested string
181 if facechar<18:
182 if facechar>=12:
183 self.face = "E"
184 facechar -= 12
185 elif facechar>=6:
186 self.face = "C"
187 facechar -= 6
188 else:
189 self.face = "R"
191 if facechar>=4:
192 self.face = "L" + self.face
193 facechar -= 4
194 elif facechar>=2:
195 self.face = "B" + self.face
196 facechar -= 2
197 else:
198 self.face = "M" + self.face
200 if facechar==1:
201 self.face = self.face[0] + "I" + self.face[1]
202 else:
203 self.face = self.face[0] + "R" + self.face[1]
205 else:
206 self.face = None
207 else:
208 self.sevenbitsave = self.face = None
210 if self.lh>18:
211 # just ignore the rest
212 self.file.read((self.lh-18)*4)
215 # read char_info
218 self.char_info = [None for charcode in range(self.ec+1)]
220 for charcode in range(self.bc, self.ec+1):
221 self.char_info[charcode] = char_info_word(self.file.readint32())
224 # read widths
227 self.width = [None for width_index in range(self.nw)]
228 for width_index in range(self.nw):
229 self.width[width_index] = fix_word(self.file.readint32())
232 # read heights
235 self.height = [None for height_index in range(self.nh)]
236 for height_index in range(self.nh):
237 self.height[height_index] = fix_word(self.file.readint32())
240 # read depths
243 self.depth = [None for depth_index in range(self.nd)]
244 for depth_index in range(self.nd):
245 self.depth[depth_index] = fix_word(self.file.readint32())
248 # read italic
251 self.italic = [None for italic_index in range(self.ni)]
252 for italic_index in range(self.ni):
253 self.italic[italic_index] = fix_word(self.file.readint32())
256 # read lig_kern
259 # XXX decode to lig_kern_command
261 self.lig_kern = [None for lig_kern_index in range(self.nl)]
262 for lig_kern_index in range(self.nl):
263 self.lig_kern[lig_kern_index] = self.file.readint32()
266 # read kern
269 self.kern = [None for kern_index in range(self.nk)]
270 for kern_index in range(self.nk):
271 self.kern[kern_index] = fix_word(self.file.readint32())
274 # read exten
277 # XXX decode to extensible_recipe
279 self.exten = [None for exten_index in range(self.ne)]
280 for exten_index in range(self.ne):
281 self.exten[exten_index] = self.file.readint32()
284 # read param
287 # XXX decode
289 self.param = [None for param_index in range(self.np)]
290 for param_index in range(self.np):
291 self.param[param_index] = self.file.readint32()
294 class Font:
295 def __init__(self, name):
296 path = os.popen("kpsewhich %s.tfm" % name, "r").readline()[:-1]
297 self.tfmfile = TFMFile(path)
299 def __getattr__(self, attr):
300 return self.tfmfile.__dict__[attr]
302 def getwidth(self, charcode):
303 return self.tfmfile.width[self.char_info[charcode].width_index] * self.designsize
305 def getheight(self, charcode):
306 return self.tfmfile.height[self.char_info[charcode].height_index] * self.designsize
308 def getdepth(self, charcode):
309 return self.tfmfile.depth[self.char_info[charcode].depth_index] * self.designsize
311 def getitalic(self, charcode):
312 return self.tfmfile.italic[self.char_info[charcode].italic_index] * self.designsize
315 class DVIFile:
317 def char(self, char, inch=1):
318 x = self.pos[_POS_H] * self.scale * 1e-5
319 y = self.pos[_POS_V] * self.scale * 1e-5
320 ascii = (char > 32 and char < 128) and "(%s)" % chr(char) or "???"
321 print "type 0x%08x %s at (%.3f cm, %.3f cm)" % (char, ascii, x, y)
322 if inch:
323 # XXX what about scaling
324 self.pos[_POS_H] += float(self.fonts[self.activefont].getwidth(char))
326 def rule(self, height, width, inch=1):
327 if height > 0 and width > 0:
328 x1 = self.pos[_POS_H] * self.scale * 1e-5
329 y1 = self.pos[_POS_V] * self.scale * 1e-5
330 x2 = (self.pos[_POS_H] + width) * self.scale * 1e-5
331 y2 = (self.pos[_POS_V] + height) * self.scale * 1e-5
332 print "rule ((%.3f..%.3f cm), (%.3f..%.3f cm))" % (x1, x2, y1, y2)
333 if inch:
334 pass # TODO: increment h
336 def __init__(self, name):
338 file = binfile(name, "rb")
339 state = _READ_PRE
340 stack = []
342 # XXX max number of fonts
343 self.fonts = [None for i in range(64)]
344 self.activefont = None
346 while state != _READ_DONE:
347 cmd = file.readuchar()
348 if cmd == _DVI_NOP: pass
350 elif state == _READ_PRE:
351 if cmd == _DVI_PRE:
352 if file.readuchar() != _DVI_VERSION: raise DVIError
353 num = file.readuint32()
354 den = file.readuint32()
355 mag = file.readuint32()
356 self.scale = num*mag/1000.0/den
357 comment = file.read(file.readuchar())
358 state = _READ_NOPAGE
359 else: raise DVIError
361 elif state == _READ_NOPAGE:
362 if cmd == _DVI_BOP:
363 print "page",
364 for i in range(10): print file.readuint32(),
365 print
366 file.readuint32()
367 self.pos = [0, 0, 0, 0, 0, 0]
368 state = _READ_PAGE
369 elif cmd == _DVI_POST:
370 state = _READ_DONE # we skip the rest
371 else: raise DVIError
373 elif state == _READ_PAGE:
374 if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
375 self.char(cmd)
376 elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
377 self.char(file.readint(cmd - _DVI_SET1234 + 1))
378 elif cmd == _DVI_SETRULE:
379 self.rule(file.readint32(), file.readint32())
380 elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
381 self.char(file.readint(cmd - _DVI_PUT1234 + 1))
382 elif cmd == _DVI_PUTRULE:
383 self.rule(file.readint32(), file.readint32(), 0)
384 elif cmd == _DVI_EOP:
385 state = _READ_NOPAGE
386 elif cmd == _DVI_PUSH:
387 stack.append(tuple(self.pos))
388 elif cmd == _DVI_POP:
389 self.pos = list(stack[-1])
390 del stack[-1]
391 elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
392 self.pos[_POS_H] += file.readint(cmd - _DVI_RIGHT1234 + 1, 1)
393 elif cmd == _DVI_W0:
394 self.pos[_POS_H] += self.pos[_POS_W]
395 elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
396 self.pos[_POS_W] = file.readint(cmd - _DVI_W1234 + 1, 1)
397 self.pos[_POS_H] += self.pos[_POS_W]
398 elif cmd == _DVI_X0:
399 self.pos[_POS_H] += self.pos[_POS_X]
400 elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
401 self.pos[_POS_X] = file.readint(cmd - _DVI_X1234 + 1, 1)
402 self.pos[_POS_H] += self.pos[_POS_X]
403 elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
404 self.pos[_POS_V] += file.readint(cmd - _DVI_DOWN1234 + 1, 1)
405 elif cmd == _DVI_Y0:
406 self.pos[_POS_V] += self.pos[_POS_Y]
407 elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
408 self.pos[_POS_Y] = file.readint(cmd - _DVI_Y1234 + 1, 1)
409 self.pos[_POS_V] += self.pos[_POS_Y]
410 elif cmd == _DVI_Z0:
411 self.pos[_POS_V] += self.pos[_POS_Z]
412 elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
413 self.pos[_POS_Z] = file.readint(cmd - _DVI_Z1234 + 1, 1)
414 self.pos[_POS_V] += self.pos[_POS_Z]
415 elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
416 self.activefont = cmd - _DVI_FNTNUMMIN
417 print "use font %i" % self.activefont
418 elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
419 self.activefont = file.readint(cmd - _DVI_FNT1234 + 1, 1)
420 print "use font %i" % self.activefont
421 elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
422 print "special %s" % file.read(file.readint(cmd - _DVI_SPECIAL1234 + 1))
423 elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
424 num = file.readint(cmd - _DVI_FNTDEF1234 + 1)
425 file.readuint32()
426 file.readuint32()
427 file.readuint32()
428 fontname = file.read(file.readuchar()+file.readuchar())
429 self.fonts[num] = Font(fontname)
430 print "font %i is %s" % (num, fontname)
432 else: raise DVIError
434 else: raise DVIError # unexpected reader state
436 if __name__=="__main__":
437 cmr10 = Font("cmr10")
438 print cmr10.charcoding
439 print cmr10.fontfamily
440 print cmr10.face
441 for charcode in range(cmr10.bc, cmr10.ec+1):
442 print "%d\th=%f\tw=%f\td=%f\ti=%f" % (
443 charcode,
444 cmr10.getwidth(charcode),
445 cmr10.getheight(charcode),
446 cmr10.getdepth(charcode),
447 cmr10.getitalic(charcode))
449 DVIFile("test.dvi")