1 # Text formatting abstractions
2 # Note -- this module is obsolete, it's too slow anyway
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
22 def bgn_anchor(self
, id):
25 def end_anchor(self
, id):
29 # Formatter back-end to collect the paragraphs in a list
30 class SavingBackEnd(NullBackEnd
):
36 self
.paralist
.append(p
)
38 def hitcheck(self
, h
, v
):
40 for p
in self
.paralist
:
41 if p
.top
<= v
<= p
.bottom
:
42 for id in p
.hitcheck(h
, v
):
49 for p
in self
.paralist
:
50 text
= text
+ (p
.extract())
53 def extractpart(self
, long1
, long2
):
54 if long1
> long2
: long1
, long2
= long2
, long1
59 ptext
= self
.paralist
[para1
].extract()
60 text
= text
+ ptext
[pos1
:]
63 ptext
= self
.paralist
[para2
].extract()
64 return text
+ ptext
[pos1
:pos2
]
66 def whereis(self
, d
, h
, v
):
68 for i
in range(len(self
.paralist
)):
70 result
= p
.whereis(d
, h
, v
)
71 if result
is not None:
75 def roundtowords(self
, long1
, long2
):
77 text
= self
.paralist
[i
].extract()
78 while offset
> 0 and text
[offset
-1] != ' ': offset
= offset
-1
82 text
= self
.paralist
[i
].extract()
84 while offset
< n
-1 and text
[offset
] != ' ': offset
= offset
+1
89 def roundtoparagraphs(self
, long1
, long2
):
91 long2
= long2
[0], len(self
.paralist
[long2
[0]].extract())
95 # Formatter back-end to send the text directly to the drawing object
96 class WritingBackEnd(NullBackEnd
):
98 def __init__(self
, d
, width
):
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:
121 # setleftindent(nspaces)
122 # setjust(type) where type is 'l', 'c', 'r', or 'lr'
126 # addword(word, nspaces)
129 def __init__(self
, d
, b
):
130 # Drawing object used for text measurements
133 # BackEnd object receiving completed paragraphs
136 # Parameters of the formatting model
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
155 # Font to set on the next word
161 def setfont(self
, font
):
162 if font
is None: return
163 self
.font
= self
.nextfont
= 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
)
174 hang
= self
.leftindent
- self
.para
.indent_left
175 if hang
> 0 and self
.para
.getlength() <= hang
:
176 self
.para
.makehangingtag(hang
)
181 def setrightindent(self
, nspaces
):
182 self
.rightindent
= int(self
.n_space
* nspaces
)
184 self
.para
.indent_right
= self
.rightindent
187 def setjust(self
, just
):
190 self
.para
.just
= self
.just
194 self
.b
.addpara(self
.para
)
196 if self
.font
is not None:
197 self
.d
.setfont(self
.font
)
200 def vspace(self
, nlines
):
203 self
.para
= self
.newpara()
204 tuple = None, '', 0, 0, 0, int(nlines
*self
.line
), 0
205 self
.para
.words
.append(tuple)
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
:
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
))
230 def bgn_anchor(self
, id):
234 self
.para
.bgn_anchor(id)
236 def end_anchor(self
, id):
240 self
.para
.end_anchor(id)
243 # Measuring object for measuring text as viewed on a tty
249 def setfont(self
, font
):
252 def textwidth(self
, text
):
255 def lineheight(self
):
262 # Drawing object for writing plain ASCII text to a file
265 def __init__(self
, fp
):
267 self
.lineno
, self
.colno
= 0, 0
269 def setfont(self
, font
):
272 def text(self
, (h
, v
), str):
275 raise ValueError, 'can\'t write \\n'
276 while self
.lineno
< v
:
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
284 self
.fp
.write(' ' * (h
- self
.colno
))
286 self
.fp
.write('\b' * (self
.colno
- h
))
289 self
.colno
= h
+ len(str)
292 # Formatting class to do nothing at all with the data
293 class NullFormatter(BaseFormatter
):
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
):
307 b
= WritingBackEnd(dw
, width
)
308 BaseFormatter
.__init
__(self
, dm
, b
)
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
322 class FunnyFormatter(WritingFormatter
):
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'
333 {'b':'*', 'i':'_', 'u':'_', 'q':'`', 'B':'*', 'I':'_', 'U':'_', 'Q':'`'}
335 {'b':'*', 'i':'_', 'u':'_', 'q':'\'', 'B':'*', 'I':'_', 'U':'_', 'Q':'\''}
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
]
346 while j
> 0 and para
.words
[j
][1] == '': j
= j
-1
347 fo1
, te1
, wi1
= para
.words
[j
][:3]
350 para
.words
[j
] = (fo1
, te1
, wi1
) + \
352 if openchar
.has_key(curfont
) and te
:
353 c
= openchar
[curfont
]
356 para
.words
[i
] = (fo
, te
, wi
) + \
358 if te
: oldfont
= curfont
360 if curfont
in string
.uppercase
:
361 te
= string
.upper(te
)
362 para
.words
[i
] = (fo
, te
, wi
) + para
.words
[i
][3:]
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
):
374 self
.drawnow
= drawnow
375 self
.width
= window
.getwinsize()[0]
376 self
.selection
= None
378 window
.setorigin(0, 0)
379 window
.setdocsize(0, 0)
380 self
.d
= window
.begindrawing()
381 SavingBackEnd
.__init
__(self
)
386 self
.window
.setdocsize(0, self
.height
)
388 def addpara(self
, p
):
389 self
.paralist
.append(p
)
392 p
.render(self
.d
, 0, self
.height
, self
.width
)
398 p
.bottom
= self
.height
+ p
.height
399 self
.height
= p
.bottom
402 self
.window
.change((0, 0), (self
.width
, self
.height
))
403 self
.width
= self
.window
.getwinsize()[0]
405 for p
in self
.paralist
:
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
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
)
424 self
.invert(d
, self
.selection
)
427 def setselection(self
, new
):
433 if new
!= self
.selection
:
434 d
= self
.window
.begindrawing()
436 self
.invert(d
, self
.selection
)
442 def getselection(self
):
443 return self
.selection
445 def extractselection(self
):
447 a
, b
= self
.selection
448 return self
.extractpart(a
, b
)
452 def invert(self
, d
, region
):
453 long1
, long2
= region
454 if long1
> long2
: long1
, long2
= long2
, long1
458 self
.paralist
[para1
].invert(d
, pos1
, None)
461 self
.paralist
[para2
].invert(d
, pos1
, pos2
)
463 def search(self
, prog
):
465 if type(prog
) is type(''):
466 prog
= re
.compile(string
.lower(prog
))
468 iold
= self
.selection
[0][0]
472 for i
in range(len(self
.paralist
)):
473 if i
== iold
or i
< iold
and hit
:
476 text
= string
.lower(p
.extract())
477 match
= prog
.search(text
)
479 a
, b
= match
.group(0)
486 self
.setselection(hit
)
489 self
.window
.show((p
.left
, p
.top
), (p
.right
, p
.bottom
))
494 def showanchor(self
, id):
495 for i
in range(len(self
.paralist
)):
499 long2
= i
, len(p
.extract())
501 self
.setselection(hit
)
503 (p
.left
, p
.top
), (p
.right
, p
.bottom
))
517 self
.fonthandle
= None
524 def setfont(self
, fontkey
):
526 fontkey
= 'Times-Roman 12'
527 elif ' ' not in fontkey
:
528 fontkey
= fontkey
+ ' 12'
529 if fontkey
== self
.fontkey
:
531 if self
.fontcache
.has_key(fontkey
):
532 handle
= self
.fontcache
[fontkey
]
535 i
= string
.index(fontkey
, ' ')
536 name
, sizestr
= fontkey
[:i
], fontkey
[i
:]
539 key
= name
+ ' ' + `size`
540 # NB key may differ from fontkey!
541 if self
.fontcache
.has_key(key
):
542 handle
= self
.fontcache
[key
]
544 if self
.fontcache
.has_key(key1
):
545 handle
= self
.fontcache
[key1
]
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()
560 class GLMeasurer(GLFontCache
):
562 def textwidth(self
, text
):
563 return self
.fonthandle
.getstrwidth(text
)
566 return self
.fontinfo
[6] - self
.fontinfo
[3]
568 def lineheight(self
):
569 return self
.fontinfo
[6]
572 class GLWriter(GLFontCache
):
575 # (1) Use gl.ortho2 to use X pixel coordinates!
577 def text(self
, (h
, v
), text
):
579 gl
.cmov2i(h
, v
+ self
.fontinfo
[6] - self
.fontinfo
[3])
582 def setfont(self
, fontkey
):
583 oldhandle
= self
.fonthandle
584 GLFontCache
.setfont(fontkey
)
585 if self
.fonthandle
!= oldhandle
:
589 class GLMeasurerWriter(GLMeasurer
, GLWriter
):
593 class GLBackEnd(SavingBackEnd
):
595 def __init__(self
, wid
):
599 self
.width
= gl
.getsize()[1]
601 self
.d
= GLMeasurerWriter()
602 SavingBackEnd
.__init
__(self
)
607 def addpara(self
, p
):
608 self
.paralist
.append(p
)
609 self
.height
= p
.render(self
.d
, 0, self
.height
, self
.width
)
614 width
= gl
.getsize()[1]
615 if width
!= self
.width
:
618 for p
in self
.paralist
:
619 p
.top
= p
.bottom
= None
622 for p
in self
.paralist
:
623 v
= p
.render(d
, 0, v
, width
)