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 # Measuring object for measuring text as viewed on a tty
248 def setfont(self
, font
):
251 def textwidth(self
, text
):
254 def lineheight(self
):
261 # Drawing object for writing plain ASCII text to a file
264 def __init__(self
, fp
):
266 self
.lineno
, self
.colno
= 0, 0
268 def setfont(self
, font
):
271 def text(self
, (h
, v
), str):
274 raise ValueError, 'can\'t write \\n'
275 while self
.lineno
< v
:
277 self
.colno
, self
.lineno
= 0, self
.lineno
+ 1
278 while self
.lineno
> v
:
279 # XXX This should never happen...
280 self
.fp
.write('\033[A') # ANSI up arrow
281 self
.lineno
= self
.lineno
- 1
283 self
.fp
.write(' ' * (h
- self
.colno
))
285 self
.fp
.write('\b' * (self
.colno
- h
))
288 self
.colno
= h
+ len(str)
291 # Formatting class to do nothing at all with the data
292 class NullFormatter(BaseFormatter
):
297 BaseFormatter
.__init
__(self
, d
, b
)
300 # Formatting class to write directly to a file
301 class WritingFormatter(BaseFormatter
):
303 def __init__(self
, fp
, width
):
306 b
= WritingBackEnd(dw
, width
)
307 BaseFormatter
.__init
__(self
, dm
, b
)
310 # Suppress multiple blank lines
311 def needvspace(self
, nlines
):
312 BaseFormatter
.needvspace(self
, min(1, nlines
))
315 # A "FunnyFormatter" writes ASCII text with a twist: *bold words*,
316 # _italic text_ and _underlined words_, and `quoted text'.
317 # It assumes that the fonts are 'r', 'i', 'b', 'u', 'q': (roman,
318 # italic, bold, underline, quote).
319 # Moreover, if the font is in upper case, the text is converted to
321 class FunnyFormatter(WritingFormatter
):
324 if self
.para
: finalize(self
.para
)
325 WritingFormatter
.flush(self
)
328 # Surrounds *bold words* and _italic text_ in a paragraph with
329 # appropriate markers, fixing the size (assuming these characters'
332 {'b':'*', 'i':'_', 'u':'_', 'q':'`', 'B':'*', 'I':'_', 'U':'_', 'Q':'`'}
334 {'b':'*', 'i':'_', 'u':'_', 'q':'\'', 'B':'*', 'I':'_', 'U':'_', 'Q':'\''}
336 oldfont
= curfont
= 'r'
337 para
.words
.append('r', '', 0, 0, 0, 0) # temporary, deleted at end
338 for i
in range(len(para
.words
)):
339 fo
, te
, wi
= para
.words
[i
][:3]
340 if fo
<> None: curfont
= fo
341 if curfont
<> oldfont
:
342 if closechar
.has_key(oldfont
):
343 c
= closechar
[oldfont
]
345 while j
> 0 and para
.words
[j
][1] == '': j
= j
-1
346 fo1
, te1
, wi1
= para
.words
[j
][:3]
349 para
.words
[j
] = (fo1
, te1
, wi1
) + \
351 if openchar
.has_key(curfont
) and te
:
352 c
= openchar
[curfont
]
355 para
.words
[i
] = (fo
, te
, wi
) + \
357 if te
: oldfont
= curfont
359 if curfont
in string
.uppercase
:
360 te
= string
.upper(te
)
361 para
.words
[i
] = (fo
, te
, wi
) + para
.words
[i
][3:]
365 # Formatter back-end to draw the text in a window.
366 # This has an option to draw while the paragraphs are being added,
367 # to minimize the delay before the user sees anything.
368 # This manages the entire "document" of the window.
369 class StdwinBackEnd(SavingBackEnd
):
371 def __init__(self
, window
, drawnow
):
373 self
.drawnow
= drawnow
374 self
.width
= window
.getwinsize()[0]
375 self
.selection
= None
377 window
.setorigin(0, 0)
378 window
.setdocsize(0, 0)
379 self
.d
= window
.begindrawing()
380 SavingBackEnd
.__init
__(self
)
385 self
.window
.setdocsize(0, self
.height
)
387 def addpara(self
, p
):
388 self
.paralist
.append(p
)
391 p
.render(self
.d
, 0, self
.height
, self
.width
)
397 p
.bottom
= self
.height
+ p
.height
398 self
.height
= p
.bottom
401 self
.window
.change((0, 0), (self
.width
, self
.height
))
402 self
.width
= self
.window
.getwinsize()[0]
404 for p
in self
.paralist
:
409 p
.bottom
= self
.height
+ p
.height
410 self
.height
= p
.bottom
411 self
.window
.change((0, 0), (self
.width
, self
.height
))
412 self
.window
.setdocsize(0, self
.height
)
414 def redraw(self
, area
):
415 d
= self
.window
.begindrawing()
416 (left
, top
), (right
, bottom
) = area
419 for p
in self
.paralist
:
420 if top
< p
.bottom
and p
.top
< bottom
:
421 v
= p
.render(d
, p
.left
, p
.top
, p
.right
)
423 self
.invert(d
, self
.selection
)
426 def setselection(self
, new
):
432 if new
<> self
.selection
:
433 d
= self
.window
.begindrawing()
435 self
.invert(d
, self
.selection
)
441 def getselection(self
):
442 return self
.selection
444 def extractselection(self
):
446 a
, b
= self
.selection
447 return self
.extractpart(a
, b
)
451 def invert(self
, d
, region
):
452 long1
, long2
= region
453 if long1
> long2
: long1
, long2
= long2
, long1
457 self
.paralist
[para1
].invert(d
, pos1
, None)
460 self
.paralist
[para2
].invert(d
, pos1
, pos2
)
462 def search(self
, prog
):
464 if type(prog
) == type(''):
465 prog
= regex
.compile(string
.lower(prog
))
467 iold
= self
.selection
[0][0]
471 for i
in range(len(self
.paralist
)):
472 if i
== iold
or i
< iold
and hit
:
475 text
= string
.lower(p
.extract())
476 if prog
.search(text
) >= 0:
484 self
.setselection(hit
)
487 self
.window
.show((p
.left
, p
.top
), (p
.right
, p
.bottom
))
492 def showanchor(self
, id):
493 for i
in range(len(self
.paralist
)):
497 long2
= i
, len(p
.extract())
499 self
.setselection(hit
)
501 (p
.left
, p
.top
), (p
.right
, p
.bottom
))
515 self
.fonthandle
= None
522 def setfont(self
, fontkey
):
524 fontkey
= 'Times-Roman 12'
525 elif ' ' not in fontkey
:
526 fontkey
= fontkey
+ ' 12'
527 if fontkey
== self
.fontkey
:
529 if self
.fontcache
.has_key(fontkey
):
530 handle
= self
.fontcache
[fontkey
]
533 i
= string
.index(fontkey
, ' ')
534 name
, sizestr
= fontkey
[:i
], fontkey
[i
:]
537 key
= name
+ ' ' + `size`
538 # NB key may differ from fontkey!
539 if self
.fontcache
.has_key(key
):
540 handle
= self
.fontcache
[key
]
542 if self
.fontcache
.has_key(key1
):
543 handle
= self
.fontcache
[key1
]
546 handle
= fm
.findfont(name
)
547 self
.fontcache
[key1
] = handle
548 handle
= handle
.scalefont(size
)
549 self
.fontcache
[fontkey
] = \
550 self
.fontcache
[key
] = handle
551 self
.fontkey
= fontkey
552 if self
.fonthandle
<> handle
:
553 self
.fonthandle
= handle
554 self
.fontinfo
= handle
.getfontinfo()
558 class GLMeasurer(GLFontCache
):
560 def textwidth(self
, text
):
561 return self
.fonthandle
.getstrwidth(text
)
564 return self
.fontinfo
[6] - self
.fontinfo
[3]
566 def lineheight(self
):
567 return self
.fontinfo
[6]
570 class GLWriter(GLFontCache
):
573 # (1) Use gl.ortho2 to use X pixel coordinates!
575 def text(self
, (h
, v
), text
):
577 gl
.cmov2i(h
, v
+ self
.fontinfo
[6] - self
.fontinfo
[3])
580 def setfont(self
, fontkey
):
581 oldhandle
= self
.fonthandle
582 GLFontCache
.setfont(fontkey
)
583 if self
.fonthandle
<> oldhandle
:
587 class GLMeasurerWriter(GLMeasurer
, GLWriter
):
591 class GLBackEnd(SavingBackEnd
):
593 def __init__(self
, wid
):
597 self
.width
= gl
.getsize()[1]
599 self
.d
= GLMeasurerWriter()
600 SavingBackEnd
.__init
__(self
)
605 def addpara(self
, p
):
606 self
.paralist
.append(p
)
607 self
.height
= p
.render(self
.d
, 0, self
.height
, self
.width
)
612 width
= gl
.getsize()[1]
613 if width
<> self
.width
:
616 for p
in self
.paralist
:
617 p
.top
= p
.bottom
= None
620 for p
in self
.paralist
:
621 v
= p
.render(d
, 0, v
, width
)