1 # Text formatting abstractions
8 # A formatter back-end object has one method that is called by the formatter:
9 # addpara(p), where p is a paragraph object. For example:
12 # Formatter back-end to do nothing at all with the paragraphs
21 def bgn_anchor(self
, id):
24 def end_anchor(self
, id):
28 # Formatter back-end to collect the paragraphs in a list
29 class SavingBackEnd(NullBackEnd
):
35 self
.paralist
.append(p
)
37 def hitcheck(self
, h
, v
):
39 for p
in self
.paralist
:
40 if p
.top
<= v
<= p
.bottom
:
41 for id in p
.hitcheck(h
, v
):
48 for p
in self
.paralist
:
49 text
= text
+ (p
.extract())
52 def extractpart(self
, long1
, long2
):
53 if long1
> long2
: long1
, long2
= long2
, long1
58 ptext
= self
.paralist
[para1
].extract()
59 text
= text
+ ptext
[pos1
:]
62 ptext
= self
.paralist
[para2
].extract()
63 return text
+ ptext
[pos1
:pos2
]
65 def whereis(self
, d
, h
, v
):
67 for i
in range(len(self
.paralist
)):
69 result
= p
.whereis(d
, h
, v
)
74 def roundtowords(self
, long1
, long2
):
76 text
= self
.paralist
[i
].extract()
77 while offset
> 0 and text
[offset
-1] <> ' ': offset
= offset
-1
81 text
= self
.paralist
[i
].extract()
83 while offset
< n
-1 and text
[offset
] <> ' ': offset
= offset
+1
88 def roundtoparagraphs(self
, long1
, long2
):
90 long2
= long2
[0], len(self
.paralist
[long2
[0]].extract())
94 # Formatter back-end to send the text directly to the drawing object
95 class WritingBackEnd(NullBackEnd
):
97 def __init__(self
, d
, width
):
102 def addpara(self
, p
):
103 self
.lineno
= p
.render(self
.d
, 0, self
.lineno
, self
.width
)
106 # A formatter receives a stream of formatting instructions and assembles
107 # these into a stream of paragraphs on to a back-end. The assembly is
108 # parametrized by a text measurement object, which must match the output
109 # operations of the back-end. The back-end is responsible for splitting
110 # paragraphs up in lines of a given maximum width. (This is done because
111 # in a windowing environment, when the window size changes, there is no
112 # need to redo the assembly into paragraphs, but the splitting into lines
113 # must be done taking the new window size into account.)
116 # Formatter base class. Initialize it with a text measurement object,
117 # which is used for text measurements, and a back-end object,
118 # which receives the completed paragraphs. The formatting methods are:
120 # setleftindent(nspaces)
121 # setjust(type) where type is 'l', 'c', 'r', or 'lr'
125 # addword(word, nspaces)
128 def __init__(self
, d
, b
):
129 # Drawing object used for text measurements
132 # BackEnd object receiving completed paragraphs
135 # Parameters of the formatting model
141 # Parameters derived from the current font
142 self
.space
= d
.textwidth(' ')
143 self
.line
= d
.lineheight()
144 self
.ascent
= d
.baseline()
145 self
.descent
= self
.line
- self
.ascent
147 # Parameter derived from the default font
148 self
.n_space
= self
.space
150 # Current paragraph being built
154 # Font to set on the next word
160 def setfont(self
, font
):
161 if font
== None: return
162 self
.font
= self
.nextfont
= font
165 self
.space
= d
.textwidth(' ')
166 self
.line
= d
.lineheight()
167 self
.ascent
= d
.baseline()
168 self
.descent
= self
.line
- self
.ascent
170 def setleftindent(self
, nspaces
):
171 self
.leftindent
= int(self
.n_space
* nspaces
)
173 hang
= self
.leftindent
- self
.para
.indent_left
174 if hang
> 0 and self
.para
.getlength() <= hang
:
175 self
.para
.makehangingtag(hang
)
180 def setrightindent(self
, nspaces
):
181 self
.rightindent
= int(self
.n_space
* nspaces
)
183 self
.para
.indent_right
= self
.rightindent
186 def setjust(self
, just
):
189 self
.para
.just
= self
.just
193 self
.b
.addpara(self
.para
)
195 if self
.font
<> None:
196 self
.d
.setfont(self
.font
)
199 def vspace(self
, nlines
):
202 self
.para
= self
.newpara()
203 tuple = None, '', 0, 0, 0, int(nlines
*self
.line
), 0
204 self
.para
.words
.append(tuple)
206 self
.blanklines
= self
.blanklines
+ nlines
208 def needvspace(self
, nlines
):
209 self
.flush() # Just to be sure
210 if nlines
> self
.blanklines
:
211 self
.vspace(nlines
- self
.blanklines
)
213 def addword(self
, text
, space
):
214 if self
.nospace
and not text
:
219 self
.para
= self
.newpara()
220 self
.para
.indent_left
= self
.leftindent
221 self
.para
.just
= self
.just
222 self
.nextfont
= self
.font
223 space
= int(space
* self
.space
)
224 self
.para
.words
.append(self
.nextfont
, text
, \
225 self
.d
.textwidth(text
), space
, space
, \
226 self
.ascent
, self
.descent
)
229 def bgn_anchor(self
, id):
233 self
.para
.bgn_anchor(id)
235 def end_anchor(self
, id):
239 self
.para
.end_anchor(id)
242 # Typically need to override this for bit-mapped displays
244 self
.addword('-'*60, 0)
248 # Measuring object for measuring text as viewed on a tty
254 def setfont(self
, font
):
257 def textwidth(self
, text
):
260 def lineheight(self
):
267 # Drawing object for writing plain ASCII text to a file
270 def __init__(self
, fp
):
272 self
.lineno
, self
.colno
= 0, 0
274 def setfont(self
, font
):
277 def text(self
, (h
, v
), str):
280 raise ValueError, 'can\'t write \\n'
281 while self
.lineno
< v
:
283 self
.colno
, self
.lineno
= 0, self
.lineno
+ 1
284 while self
.lineno
> v
:
285 # XXX This should never happen...
286 self
.fp
.write('\033[A') # ANSI up arrow
287 self
.lineno
= self
.lineno
- 1
289 self
.fp
.write(' ' * (h
- self
.colno
))
291 self
.fp
.write('\b' * (self
.colno
- h
))
294 self
.colno
= h
+ len(str)
297 # Formatting class to do nothing at all with the data
298 class NullFormatter(BaseFormatter
):
303 BaseFormatter
.__init
__(self
, d
, b
)
306 # Formatting class to write directly to a file
307 class WritingFormatter(BaseFormatter
):
309 def __init__(self
, fp
, width
):
312 b
= WritingBackEnd(dw
, width
)
313 BaseFormatter
.__init
__(self
, dm
, b
)
316 # Suppress multiple blank lines
317 def needvspace(self
, nlines
):
318 BaseFormatter
.needvspace(self
, min(1, nlines
))
321 # A "FunnyFormatter" writes ASCII text with a twist: *bold words*,
322 # _italic text_ and _underlined words_, and `quoted text'.
323 # It assumes that the fonts are 'r', 'i', 'b', 'u', 'q': (roman,
324 # italic, bold, underline, quote).
325 # Moreover, if the font is in upper case, the text is converted to
327 class FunnyFormatter(WritingFormatter
):
330 if self
.para
: finalize(self
.para
)
331 WritingFormatter
.flush(self
)
334 # Surrounds *bold words* and _italic text_ in a paragraph with
335 # appropriate markers, fixing the size (assuming these characters'
338 {'b':'*', 'i':'_', 'u':'_', 'q':'`', 'B':'*', 'I':'_', 'U':'_', 'Q':'`'}
340 {'b':'*', 'i':'_', 'u':'_', 'q':'\'', 'B':'*', 'I':'_', 'U':'_', 'Q':'\''}
342 oldfont
= curfont
= 'r'
343 para
.words
.append('r', '', 0, 0, 0, 0) # temporary, deleted at end
344 for i
in range(len(para
.words
)):
345 fo
, te
, wi
= para
.words
[i
][:3]
346 if fo
<> None: curfont
= fo
347 if curfont
<> oldfont
:
348 if closechar
.has_key(oldfont
):
349 c
= closechar
[oldfont
]
351 while j
> 0 and para
.words
[j
][1] == '': j
= j
-1
352 fo1
, te1
, wi1
= para
.words
[j
][:3]
355 para
.words
[j
] = (fo1
, te1
, wi1
) + \
357 if openchar
.has_key(curfont
) and te
:
358 c
= openchar
[curfont
]
361 para
.words
[i
] = (fo
, te
, wi
) + \
363 if te
: oldfont
= curfont
365 if curfont
in string
.uppercase
:
366 te
= string
.upper(te
)
367 para
.words
[i
] = (fo
, te
, wi
) + para
.words
[i
][3:]
371 # Formatter back-end to draw the text in a window.
372 # This has an option to draw while the paragraphs are being added,
373 # to minimize the delay before the user sees anything.
374 # This manages the entire "document" of the window.
375 class StdwinBackEnd(SavingBackEnd
):
377 def __init__(self
, window
, drawnow
):
379 self
.drawnow
= drawnow
380 self
.width
= window
.getwinsize()[0]
381 self
.selection
= None
383 window
.setorigin(0, 0)
384 window
.setdocsize(0, 0)
385 self
.d
= window
.begindrawing()
386 SavingBackEnd
.__init
__(self
)
391 self
.window
.setdocsize(0, self
.height
)
393 def addpara(self
, p
):
394 self
.paralist
.append(p
)
397 p
.render(self
.d
, 0, self
.height
, self
.width
)
403 p
.bottom
= self
.height
+ p
.height
404 self
.height
= p
.bottom
407 self
.window
.change((0, 0), (self
.width
, self
.height
))
408 self
.width
= self
.window
.getwinsize()[0]
410 for p
in self
.paralist
:
415 p
.bottom
= self
.height
+ p
.height
416 self
.height
= p
.bottom
417 self
.window
.change((0, 0), (self
.width
, self
.height
))
418 self
.window
.setdocsize(0, self
.height
)
420 def redraw(self
, area
):
421 d
= self
.window
.begindrawing()
422 (left
, top
), (right
, bottom
) = area
425 for p
in self
.paralist
:
426 if top
< p
.bottom
and p
.top
< bottom
:
427 v
= p
.render(d
, p
.left
, p
.top
, p
.right
)
429 self
.invert(d
, self
.selection
)
432 def setselection(self
, new
):
438 if new
<> self
.selection
:
439 d
= self
.window
.begindrawing()
441 self
.invert(d
, self
.selection
)
447 def getselection(self
):
448 return self
.selection
450 def extractselection(self
):
452 a
, b
= self
.selection
453 return self
.extractpart(a
, b
)
457 def invert(self
, d
, region
):
458 long1
, long2
= region
459 if long1
> long2
: long1
, long2
= long2
, long1
463 self
.paralist
[para1
].invert(d
, pos1
, None)
466 self
.paralist
[para2
].invert(d
, pos1
, pos2
)
468 def search(self
, prog
):
470 if type(prog
) == type(''):
471 prog
= regex
.compile(string
.lower(prog
))
473 iold
= self
.selection
[0][0]
477 for i
in range(len(self
.paralist
)):
478 if i
== iold
or i
< iold
and hit
:
481 text
= string
.lower(p
.extract())
482 if prog
.search(text
) >= 0:
490 self
.setselection(hit
)
493 self
.window
.show((p
.left
, p
.top
), (p
.right
, p
.bottom
))
498 def showanchor(self
, id):
499 for i
in range(len(self
.paralist
)):
503 long2
= i
, len(p
.extract())
505 self
.setselection(hit
)
507 (p
.left
, p
.top
), (p
.right
, p
.bottom
))
521 self
.fonthandle
= None
528 def setfont(self
, fontkey
):
530 fontkey
= 'Times-Roman 12'
531 elif ' ' not in fontkey
:
532 fontkey
= fontkey
+ ' 12'
533 if fontkey
== self
.fontkey
:
535 if self
.fontcache
.has_key(fontkey
):
536 handle
= self
.fontcache
[fontkey
]
539 i
= string
.index(fontkey
, ' ')
540 name
, sizestr
= fontkey
[:i
], fontkey
[i
:]
543 key
= name
+ ' ' + `size`
544 # NB key may differ from fontkey!
545 if self
.fontcache
.has_key(key
):
546 handle
= self
.fontcache
[key
]
548 if self
.fontcache
.has_key(key1
):
549 handle
= self
.fontcache
[key1
]
552 handle
= fm
.findfont(name
)
553 self
.fontcache
[key1
] = handle
554 handle
= handle
.scalefont(size
)
555 self
.fontcache
[fontkey
] = \
556 self
.fontcache
[key
] = handle
557 self
.fontkey
= fontkey
558 if self
.fonthandle
<> handle
:
559 self
.fonthandle
= handle
560 self
.fontinfo
= handle
.getfontinfo()
564 class GLMeasurer(GLFontCache
):
566 def textwidth(self
, text
):
567 return self
.fonthandle
.getstrwidth(text
)
570 return self
.fontinfo
[6] - self
.fontinfo
[3]
572 def lineheight(self
):
573 return self
.fontinfo
[6]
576 class GLWriter(GLFontCache
):
579 # (1) Use gl.ortho2 to use X pixel coordinates!
581 def text(self
, (h
, v
), text
):
583 gl
.cmov2i(h
, v
+ self
.fontinfo
[6] - self
.fontinfo
[3])
586 def setfont(self
, fontkey
):
587 oldhandle
= self
.fonthandle
588 GLFontCache
.setfont(fontkey
)
589 if self
.fonthandle
<> oldhandle
:
593 class GLMeasurerWriter(GLMeasurer
, GLWriter
):
597 class GLBackEnd(SavingBackEnd
):
599 def __init__(self
, wid
):
603 self
.width
= gl
.getsize()[1]
605 self
.d
= GLMeasurerWriter()
606 SavingBackEnd
.__init
__(self
)
611 def addpara(self
, p
):
612 self
.paralist
.append(p
)
613 self
.height
= p
.render(self
.d
, 0, self
.height
, self
.width
)
618 width
= gl
.getsize()[1]
619 if width
<> self
.width
:
622 for p
in self
.paralist
:
623 p
.top
= p
.bottom
= None
626 for p
in self
.paralist
:
627 v
= p
.render(d
, 0, v
, width
)