Its right now.
[python.git] / Lib / lib-old / fmt.py
blob997d37a07dbc3a8add2e1fa3038c358dae25d234
1 # Text formatting abstractions
2 # Note -- this module is obsolete, it's too slow anyway
5 import string
6 import Para
9 # A formatter back-end object has one method that is called by the formatter:
10 # addpara(p), where p is a paragraph object. For example:
13 # Formatter back-end to do nothing at all with the paragraphs
14 class NullBackEnd:
16 def __init__(self):
17 pass
19 def addpara(self, p):
20 pass
22 def bgn_anchor(self, id):
23 pass
25 def end_anchor(self, id):
26 pass
29 # Formatter back-end to collect the paragraphs in a list
30 class SavingBackEnd(NullBackEnd):
32 def __init__(self):
33 self.paralist = []
35 def addpara(self, p):
36 self.paralist.append(p)
38 def hitcheck(self, h, v):
39 hits = []
40 for p in self.paralist:
41 if p.top <= v <= p.bottom:
42 for id in p.hitcheck(h, v):
43 if id not in hits:
44 hits.append(id)
45 return hits
47 def extract(self):
48 text = ''
49 for p in self.paralist:
50 text = text + (p.extract())
51 return text
53 def extractpart(self, long1, long2):
54 if long1 > long2: long1, long2 = long2, long1
55 para1, pos1 = long1
56 para2, pos2 = long2
57 text = ''
58 while para1 < para2:
59 ptext = self.paralist[para1].extract()
60 text = text + ptext[pos1:]
61 pos1 = 0
62 para1 = para1 + 1
63 ptext = self.paralist[para2].extract()
64 return text + ptext[pos1:pos2]
66 def whereis(self, d, h, v):
67 total = 0
68 for i in range(len(self.paralist)):
69 p = self.paralist[i]
70 result = p.whereis(d, h, v)
71 if result is not None:
72 return i, result
73 return None
75 def roundtowords(self, long1, long2):
76 i, offset = long1
77 text = self.paralist[i].extract()
78 while offset > 0 and text[offset-1] != ' ': offset = offset-1
79 long1 = i, offset
81 i, offset = long2
82 text = self.paralist[i].extract()
83 n = len(text)
84 while offset < n-1 and text[offset] != ' ': offset = offset+1
85 long2 = i, offset
87 return long1, long2
89 def roundtoparagraphs(self, long1, long2):
90 long1 = long1[0], 0
91 long2 = long2[0], len(self.paralist[long2[0]].extract())
92 return long1, long2
95 # Formatter back-end to send the text directly to the drawing object
96 class WritingBackEnd(NullBackEnd):
98 def __init__(self, d, width):
99 self.d = d
100 self.width = width
101 self.lineno = 0
103 def addpara(self, p):
104 self.lineno = p.render(self.d, 0, self.lineno, self.width)
107 # A formatter receives a stream of formatting instructions and assembles
108 # these into a stream of paragraphs on to a back-end. The assembly is
109 # parametrized by a text measurement object, which must match the output
110 # operations of the back-end. The back-end is responsible for splitting
111 # paragraphs up in lines of a given maximum width. (This is done because
112 # in a windowing environment, when the window size changes, there is no
113 # need to redo the assembly into paragraphs, but the splitting into lines
114 # must be done taking the new window size into account.)
117 # Formatter base class. Initialize it with a text measurement object,
118 # which is used for text measurements, and a back-end object,
119 # which receives the completed paragraphs. The formatting methods are:
120 # setfont(font)
121 # setleftindent(nspaces)
122 # setjust(type) where type is 'l', 'c', 'r', or 'lr'
123 # flush()
124 # vspace(nlines)
125 # needvspace(nlines)
126 # addword(word, nspaces)
127 class BaseFormatter:
129 def __init__(self, d, b):
130 # Drawing object used for text measurements
131 self.d = d
133 # BackEnd object receiving completed paragraphs
134 self.b = b
136 # Parameters of the formatting model
137 self.leftindent = 0
138 self.just = 'l'
139 self.font = None
140 self.blanklines = 0
142 # Parameters derived from the current font
143 self.space = d.textwidth(' ')
144 self.line = d.lineheight()
145 self.ascent = d.baseline()
146 self.descent = self.line - self.ascent
148 # Parameter derived from the default font
149 self.n_space = self.space
151 # Current paragraph being built
152 self.para = None
153 self.nospace = 1
155 # Font to set on the next word
156 self.nextfont = None
158 def newpara(self):
159 return Para.Para()
161 def setfont(self, font):
162 if font is None: return
163 self.font = self.nextfont = font
164 d = self.d
165 d.setfont(font)
166 self.space = d.textwidth(' ')
167 self.line = d.lineheight()
168 self.ascent = d.baseline()
169 self.descent = self.line - self.ascent
171 def setleftindent(self, nspaces):
172 self.leftindent = int(self.n_space * nspaces)
173 if self.para:
174 hang = self.leftindent - self.para.indent_left
175 if hang > 0 and self.para.getlength() <= hang:
176 self.para.makehangingtag(hang)
177 self.nospace = 1
178 else:
179 self.flush()
181 def setrightindent(self, nspaces):
182 self.rightindent = int(self.n_space * nspaces)
183 if self.para:
184 self.para.indent_right = self.rightindent
185 self.flush()
187 def setjust(self, just):
188 self.just = just
189 if self.para:
190 self.para.just = self.just
192 def flush(self):
193 if self.para:
194 self.b.addpara(self.para)
195 self.para = None
196 if self.font is not None:
197 self.d.setfont(self.font)
198 self.nospace = 1
200 def vspace(self, nlines):
201 self.flush()
202 if nlines > 0:
203 self.para = self.newpara()
204 tuple = None, '', 0, 0, 0, int(nlines*self.line), 0
205 self.para.words.append(tuple)
206 self.flush()
207 self.blanklines = self.blanklines + nlines
209 def needvspace(self, nlines):
210 self.flush() # Just to be sure
211 if nlines > self.blanklines:
212 self.vspace(nlines - self.blanklines)
214 def addword(self, text, space):
215 if self.nospace and not text:
216 return
217 self.nospace = 0
218 self.blanklines = 0
219 if not self.para:
220 self.para = self.newpara()
221 self.para.indent_left = self.leftindent
222 self.para.just = self.just
223 self.nextfont = self.font
224 space = int(space * self.space)
225 self.para.words.append((self.nextfont, text,
226 self.d.textwidth(text), space, space,
227 self.ascent, self.descent))
228 self.nextfont = None
230 def bgn_anchor(self, id):
231 if not self.para:
232 self.nospace = 0
233 self.addword('', 0)
234 self.para.bgn_anchor(id)
236 def end_anchor(self, id):
237 if not self.para:
238 self.nospace = 0
239 self.addword('', 0)
240 self.para.end_anchor(id)
243 # Measuring object for measuring text as viewed on a tty
244 class NullMeasurer:
246 def __init__(self):
247 pass
249 def setfont(self, font):
250 pass
252 def textwidth(self, text):
253 return len(text)
255 def lineheight(self):
256 return 1
258 def baseline(self):
259 return 0
262 # Drawing object for writing plain ASCII text to a file
263 class FileWriter:
265 def __init__(self, fp):
266 self.fp = fp
267 self.lineno, self.colno = 0, 0
269 def setfont(self, font):
270 pass
272 def text(self, (h, v), str):
273 if not str: return
274 if '\n' in str:
275 raise ValueError, 'can\'t write \\n'
276 while self.lineno < v:
277 self.fp.write('\n')
278 self.colno, self.lineno = 0, self.lineno + 1
279 while self.lineno > v:
280 # XXX This should never happen...
281 self.fp.write('\033[A') # ANSI up arrow
282 self.lineno = self.lineno - 1
283 if self.colno < h:
284 self.fp.write(' ' * (h - self.colno))
285 elif self.colno > h:
286 self.fp.write('\b' * (self.colno - h))
287 self.colno = h
288 self.fp.write(str)
289 self.colno = h + len(str)
292 # Formatting class to do nothing at all with the data
293 class NullFormatter(BaseFormatter):
295 def __init__(self):
296 d = NullMeasurer()
297 b = NullBackEnd()
298 BaseFormatter.__init__(self, d, b)
301 # Formatting class to write directly to a file
302 class WritingFormatter(BaseFormatter):
304 def __init__(self, fp, width):
305 dm = NullMeasurer()
306 dw = FileWriter(fp)
307 b = WritingBackEnd(dw, width)
308 BaseFormatter.__init__(self, dm, b)
309 self.blanklines = 1
311 # Suppress multiple blank lines
312 def needvspace(self, nlines):
313 BaseFormatter.needvspace(self, min(1, nlines))
316 # A "FunnyFormatter" writes ASCII text with a twist: *bold words*,
317 # _italic text_ and _underlined words_, and `quoted text'.
318 # It assumes that the fonts are 'r', 'i', 'b', 'u', 'q': (roman,
319 # italic, bold, underline, quote).
320 # Moreover, if the font is in upper case, the text is converted to
321 # UPPER CASE.
322 class FunnyFormatter(WritingFormatter):
324 def flush(self):
325 if self.para: finalize(self.para)
326 WritingFormatter.flush(self)
329 # Surrounds *bold words* and _italic text_ in a paragraph with
330 # appropriate markers, fixing the size (assuming these characters'
331 # width is 1).
332 openchar = \
333 {'b':'*', 'i':'_', 'u':'_', 'q':'`', 'B':'*', 'I':'_', 'U':'_', 'Q':'`'}
334 closechar = \
335 {'b':'*', 'i':'_', 'u':'_', 'q':'\'', 'B':'*', 'I':'_', 'U':'_', 'Q':'\''}
336 def finalize(para):
337 oldfont = curfont = 'r'
338 para.words.append(('r', '', 0, 0, 0, 0)) # temporary, deleted at end
339 for i in range(len(para.words)):
340 fo, te, wi = para.words[i][:3]
341 if fo is not None: curfont = fo
342 if curfont != oldfont:
343 if closechar.has_key(oldfont):
344 c = closechar[oldfont]
345 j = i-1
346 while j > 0 and para.words[j][1] == '': j = j-1
347 fo1, te1, wi1 = para.words[j][:3]
348 te1 = te1 + c
349 wi1 = wi1 + len(c)
350 para.words[j] = (fo1, te1, wi1) + \
351 para.words[j][3:]
352 if openchar.has_key(curfont) and te:
353 c = openchar[curfont]
354 te = c + te
355 wi = len(c) + wi
356 para.words[i] = (fo, te, wi) + \
357 para.words[i][3:]
358 if te: oldfont = curfont
359 else: oldfont = 'r'
360 if curfont in string.uppercase:
361 te = string.upper(te)
362 para.words[i] = (fo, te, wi) + para.words[i][3:]
363 del para.words[-1]
366 # Formatter back-end to draw the text in a window.
367 # This has an option to draw while the paragraphs are being added,
368 # to minimize the delay before the user sees anything.
369 # This manages the entire "document" of the window.
370 class StdwinBackEnd(SavingBackEnd):
372 def __init__(self, window, drawnow):
373 self.window = window
374 self.drawnow = drawnow
375 self.width = window.getwinsize()[0]
376 self.selection = None
377 self.height = 0
378 window.setorigin(0, 0)
379 window.setdocsize(0, 0)
380 self.d = window.begindrawing()
381 SavingBackEnd.__init__(self)
383 def finish(self):
384 self.d.close()
385 self.d = None
386 self.window.setdocsize(0, self.height)
388 def addpara(self, p):
389 self.paralist.append(p)
390 if self.drawnow:
391 self.height = \
392 p.render(self.d, 0, self.height, self.width)
393 else:
394 p.layout(self.width)
395 p.left = 0
396 p.top = self.height
397 p.right = self.width
398 p.bottom = self.height + p.height
399 self.height = p.bottom
401 def resize(self):
402 self.window.change((0, 0), (self.width, self.height))
403 self.width = self.window.getwinsize()[0]
404 self.height = 0
405 for p in self.paralist:
406 p.layout(self.width)
407 p.left = 0
408 p.top = self.height
409 p.right = self.width
410 p.bottom = self.height + p.height
411 self.height = p.bottom
412 self.window.change((0, 0), (self.width, self.height))
413 self.window.setdocsize(0, self.height)
415 def redraw(self, area):
416 d = self.window.begindrawing()
417 (left, top), (right, bottom) = area
418 d.erase(area)
419 d.cliprect(area)
420 for p in self.paralist:
421 if top < p.bottom and p.top < bottom:
422 v = p.render(d, p.left, p.top, p.right)
423 if self.selection:
424 self.invert(d, self.selection)
425 d.close()
427 def setselection(self, new):
428 if new:
429 long1, long2 = new
430 pos1 = long1[:3]
431 pos2 = long2[:3]
432 new = pos1, pos2
433 if new != self.selection:
434 d = self.window.begindrawing()
435 if self.selection:
436 self.invert(d, self.selection)
437 if new:
438 self.invert(d, new)
439 d.close()
440 self.selection = new
442 def getselection(self):
443 return self.selection
445 def extractselection(self):
446 if self.selection:
447 a, b = self.selection
448 return self.extractpart(a, b)
449 else:
450 return None
452 def invert(self, d, region):
453 long1, long2 = region
454 if long1 > long2: long1, long2 = long2, long1
455 para1, pos1 = long1
456 para2, pos2 = long2
457 while para1 < para2:
458 self.paralist[para1].invert(d, pos1, None)
459 pos1 = None
460 para1 = para1 + 1
461 self.paralist[para2].invert(d, pos1, pos2)
463 def search(self, prog):
464 import re, string
465 if type(prog) is type(''):
466 prog = re.compile(string.lower(prog))
467 if self.selection:
468 iold = self.selection[0][0]
469 else:
470 iold = -1
471 hit = None
472 for i in range(len(self.paralist)):
473 if i == iold or i < iold and hit:
474 continue
475 p = self.paralist[i]
476 text = string.lower(p.extract())
477 match = prog.search(text)
478 if match:
479 a, b = match.group(0)
480 long1 = i, a
481 long2 = i, b
482 hit = long1, long2
483 if i > iold:
484 break
485 if hit:
486 self.setselection(hit)
487 i = hit[0][0]
488 p = self.paralist[i]
489 self.window.show((p.left, p.top), (p.right, p.bottom))
490 return 1
491 else:
492 return 0
494 def showanchor(self, id):
495 for i in range(len(self.paralist)):
496 p = self.paralist[i]
497 if p.hasanchor(id):
498 long1 = i, 0
499 long2 = i, len(p.extract())
500 hit = long1, long2
501 self.setselection(hit)
502 self.window.show(
503 (p.left, p.top), (p.right, p.bottom))
504 break
507 # GL extensions
509 class GLFontCache:
511 def __init__(self):
512 self.reset()
513 self.setfont('')
515 def reset(self):
516 self.fontkey = None
517 self.fonthandle = None
518 self.fontinfo = None
519 self.fontcache = {}
521 def close(self):
522 self.reset()
524 def setfont(self, fontkey):
525 if fontkey == '':
526 fontkey = 'Times-Roman 12'
527 elif ' ' not in fontkey:
528 fontkey = fontkey + ' 12'
529 if fontkey == self.fontkey:
530 return
531 if self.fontcache.has_key(fontkey):
532 handle = self.fontcache[fontkey]
533 else:
534 import string
535 i = string.index(fontkey, ' ')
536 name, sizestr = fontkey[:i], fontkey[i:]
537 size = eval(sizestr)
538 key1 = name + ' 1'
539 key = name + ' ' + `size`
540 # NB key may differ from fontkey!
541 if self.fontcache.has_key(key):
542 handle = self.fontcache[key]
543 else:
544 if self.fontcache.has_key(key1):
545 handle = self.fontcache[key1]
546 else:
547 import fm
548 handle = fm.findfont(name)
549 self.fontcache[key1] = handle
550 handle = handle.scalefont(size)
551 self.fontcache[fontkey] = \
552 self.fontcache[key] = handle
553 self.fontkey = fontkey
554 if self.fonthandle != handle:
555 self.fonthandle = handle
556 self.fontinfo = handle.getfontinfo()
557 handle.setfont()
560 class GLMeasurer(GLFontCache):
562 def textwidth(self, text):
563 return self.fonthandle.getstrwidth(text)
565 def baseline(self):
566 return self.fontinfo[6] - self.fontinfo[3]
568 def lineheight(self):
569 return self.fontinfo[6]
572 class GLWriter(GLFontCache):
574 # NOTES:
575 # (1) Use gl.ortho2 to use X pixel coordinates!
577 def text(self, (h, v), text):
578 import gl, fm
579 gl.cmov2i(h, v + self.fontinfo[6] - self.fontinfo[3])
580 fm.prstr(text)
582 def setfont(self, fontkey):
583 oldhandle = self.fonthandle
584 GLFontCache.setfont(fontkey)
585 if self.fonthandle != oldhandle:
586 handle.setfont()
589 class GLMeasurerWriter(GLMeasurer, GLWriter):
590 pass
593 class GLBackEnd(SavingBackEnd):
595 def __init__(self, wid):
596 import gl
597 gl.winset(wid)
598 self.wid = wid
599 self.width = gl.getsize()[1]
600 self.height = 0
601 self.d = GLMeasurerWriter()
602 SavingBackEnd.__init__(self)
604 def finish(self):
605 pass
607 def addpara(self, p):
608 self.paralist.append(p)
609 self.height = p.render(self.d, 0, self.height, self.width)
611 def redraw(self):
612 import gl
613 gl.winset(self.wid)
614 width = gl.getsize()[1]
615 if width != self.width:
616 setdocsize = 1
617 self.width = width
618 for p in self.paralist:
619 p.top = p.bottom = None
620 d = self.d
621 v = 0
622 for p in self.paralist:
623 v = p.render(d, 0, v, width)