1 # Text formatting abstractions
2 # Note -- this module is obsolete, it's too slow anyway
9 # Represent a paragraph. This is a list of words with associated
10 # font and size information, plus indents and justification for the
12 # Once the words have been added to a paragraph, it can be laid out
13 # for different line widths. Once laid out, it can be rendered at
14 # different screen locations. Once rendered, it can be queried
15 # for mouse hits, and parts of the text can be highlighted
19 self
.words
= [] # The words
20 self
.just
= 'l' # Justification: 'l', 'r', 'lr' or 'c'
21 self
.indent_left
= self
.indent_right
= self
.indent_hang
= 0
22 # Final lay-out parameters, may change
23 self
.left
= self
.top
= self
.right
= self
.bottom
= \
24 self
.width
= self
.height
= self
.lines
= None
26 # Add a word, computing size information for it.
27 # Words may also be added manually by appending to self.words
28 # Each word should be a 7-tuple:
29 # (font, text, width, space, stretch, ascent, descent)
30 def addword(self
, d
, font
, text
, space
, stretch
):
33 width
= d
.textwidth(text
)
35 descent
= d
.lineheight() - ascent
36 spw
= d
.textwidth(' ')
38 stretch
= stretch
* spw
39 tuple = (font
, text
, width
, space
, stretch
, ascent
, descent
)
40 self
.words
.append(tuple)
42 # Hooks to begin and end anchors -- insert numbers in the word list!
43 def bgn_anchor(self
, id):
46 def end_anchor(self
, id):
49 # Return the total length (width) of the text added so far, in pixels
52 for word
in self
.words
:
53 if type(word
) is not Int
:
54 total
= total
+ word
[2] + word
[3]
57 # Tab to a given position (relative to the current left indent):
58 # remove all stretch, add fixed space up to the new indent.
59 # If the current position is already at the tab stop,
60 # don't add any new space (but still remove the stretch)
64 for i
in range(len(self
.words
)):
66 if type(word
) is Int
: continue
67 (fo
, te
, wi
, sp
, st
, as, de
) = word
68 self
.words
[i
] = (fo
, te
, wi
, sp
, 0, as, de
)
69 total
= total
+ wi
+ sp
71 self
.words
.append((None, '', 0, tab
-total
, 0, as, de
))
73 # Make a hanging tag: tab to hang, increment indent_left by hang,
74 # and reset indent_hang to -hang
75 def makehangingtag(self
, hang
):
77 self
.indent_left
= self
.indent_left
+ hang
78 self
.indent_hang
= -hang
80 # Decide where the line breaks will be given some screen width
81 def layout(self
, linewidth
):
82 self
.width
= linewidth
84 self
.lines
= lines
= []
85 avail1
= self
.width
- self
.indent_left
- self
.indent_right
86 avail
= avail1
- self
.indent_hang
102 if type(word
) is Int
:
103 if word
> 0 and width
>= avail
:
107 fo
, te
, wi
, sp
, st
, as, de
= word
108 if width
+ wi
> avail
and width
> 0 and wi
> 0:
114 charcount
= charcount
+ len(te
) + (sp
> 0)
115 width
= width
+ wi
+ sp
117 stretch
= stretch
+ st
119 ascent
= max(ascent
, as)
120 descent
= max(descent
, de
)
122 while i
> j
and type(words
[i
-1]) is Int
and \
123 words
[i
-1] > 0: i
= i
-1
126 stretch
= stretch
- lst
129 tuple = i
-j
, firstfont
, charcount
, width
, stretch
, \
132 height
= height
+ ascent
+ descent
136 # Call a function for all words in a line
137 def visit(self
, wordfunc
, anchorfunc
):
138 avail1
= self
.width
- self
.indent_left
- self
.indent_right
139 avail
= avail1
- self
.indent_hang
142 for tuple in self
.lines
:
143 wordcount
, firstfont
, charcount
, width
, stretch
, \
144 ascent
, descent
= tuple
145 h
= self
.left
+ self
.indent_left
146 if i
== 0: h
= h
+ self
.indent_hang
148 if self
.just
== 'r': h
= h
+ avail
- width
149 elif self
.just
== 'c': h
= h
+ (avail
- width
) / 2
150 elif self
.just
== 'lr' and stretch
> 0:
151 extra
= avail
- width
152 v2
= v
+ ascent
+ descent
153 for j
in range(i
, i
+wordcount
):
155 if type(word
) is Int
:
156 ok
= anchorfunc(self
, tuple, word
, \
158 if ok
is not None: return ok
160 fo
, te
, wi
, sp
, st
, as, de
= word
161 if extra
> 0 and stretch
> 0:
162 ex
= extra
* st
/ stretch
164 stretch
= stretch
- st
167 h2
= h
+ wi
+ sp
+ ex
168 ok
= wordfunc(self
, tuple, word
, h
, v
, \
169 h2
, v2
, (j
==i
), (j
==i
+wordcount
-1))
170 if ok
is not None: return ok
176 # Render a paragraph in "drawing object" d, using the rectangle
177 # given by (left, top, right) with an unspecified bottom.
178 # Return the computed bottom of the text.
179 def render(self
, d
, left
, top
, right
):
180 if self
.width
!= right
-left
:
181 self
.layout(right
-left
)
185 self
.bottom
= self
.top
+ self
.height
189 self
.visit(self
.__class
__._renderword
, \
190 self
.__class
__._renderanchor
)
195 def _renderword(self
, tuple, word
, h
, v
, h2
, v2
, isfirst
, islast
):
196 if word
[0] is not None: self
.d
.setfont(word
[0])
197 baseline
= v
+ tuple[5]
198 self
.d
.text((h
, baseline
- word
[5]), word
[1])
199 if self
.anchorid
> 0:
200 self
.d
.line((h
, baseline
+2), (h2
, baseline
+2))
202 def _renderanchor(self
, tuple, word
, h
, v
):
205 # Return which anchor(s) was hit by the mouse
206 def hitcheck(self
, mouseh
, mousev
):
211 self
.visit(self
.__class
__._hitcheckword
, \
212 self
.__class
__._hitcheckanchor
)
215 def _hitcheckword(self
, tuple, word
, h
, v
, h2
, v2
, isfirst
, islast
):
216 if self
.anchorid
> 0 and h
<= self
.mouseh
<= h2
and \
217 v
<= self
.mousev
<= v2
:
218 self
.hits
.append(self
.anchorid
)
220 def _hitcheckanchor(self
, tuple, word
, h
, v
):
223 # Return whether the given anchor id is present
224 def hasanchor(self
, id):
225 return id in self
.words
or -id in self
.words
227 # Extract the raw text from the word list, substituting one space
228 # for non-empty inter-word space, and terminating with '\n'
232 if type(w
) is not Int
:
234 if w
[3]: word
= word
+ ' '
238 # Return which character position was hit by the mouse, as
239 # an offset in the entire text as returned by extract().
240 # Return None if the mouse was not in this paragraph
241 def whereis(self
, d
, mouseh
, mousev
):
242 if mousev
< self
.top
or mousev
> self
.bottom
:
250 return self
.visit(self
.__class
__._whereisword
, \
251 self
.__class
__._whereisanchor
)
255 def _whereisword(self
, tuple, word
, h1
, v1
, h2
, v2
, isfirst
, islast
):
256 fo
, te
, wi
, sp
, st
, as, de
= word
257 if fo
is not None: self
.lastfont
= fo
260 if islast
: h2
= 999999
261 if not (v1
<= self
.mousev
<= v2
and h1
<= self
.mouseh
<= h2
):
262 self
.charcount
= self
.charcount
+ len(te
) + (sp
> 0)
264 if self
.lastfont
is not None:
265 self
.d
.setfont(self
.lastfont
)
268 cw
= self
.d
.textwidth(c
)
269 if self
.mouseh
<= h
+ cw
/2:
270 return self
.charcount
+ cc
273 self
.charcount
= self
.charcount
+ cc
274 if self
.mouseh
<= (h
+h2
) / 2:
275 return self
.charcount
277 return self
.charcount
+ 1
279 def _whereisanchor(self
, tuple, word
, h
, v
):
282 # Return screen position corresponding to position in paragraph.
283 # Return tuple (h, vtop, vbaseline, vbottom).
284 # This is more or less the inverse of whereis()
285 def screenpos(self
, d
, pos
):
287 ascent
, descent
= self
.lines
[0][5:7]
288 return self
.left
, self
.top
, self
.top
+ ascent
, \
289 self
.top
+ ascent
+ descent
294 ok
= self
.visit(self
.__class
__._screenposword
, \
295 self
.__class
__._screenposanchor
)
299 ascent
, descent
= self
.lines
[-1][5:7]
300 ok
= self
.right
, self
.bottom
- ascent
- descent
, \
301 self
.bottom
- descent
, self
.bottom
304 def _screenposword(self
, tuple, word
, h1
, v1
, h2
, v2
, isfirst
, islast
):
305 fo
, te
, wi
, sp
, st
, as, de
= word
306 if fo
is not None: self
.lastfont
= fo
307 cc
= len(te
) + (sp
> 0)
309 self
.pos
= self
.pos
- cc
312 self
.d
.setfont(self
.lastfont
)
313 h
= h1
+ self
.d
.textwidth(te
[:self
.pos
])
316 ascent
, descent
= tuple[5:7]
317 return h
, v1
, v1
+ascent
, v2
319 def _screenposanchor(self
, tuple, word
, h
, v
):
322 # Invert the stretch of text between pos1 and pos2.
323 # If pos1 is None, the beginning is implied;
324 # if pos2 is None, the end is implied.
325 # Undoes its own effect when called again with the same arguments
326 def invert(self
, d
, pos1
, pos2
):
328 pos1
= self
.left
, self
.top
, self
.top
, self
.top
330 pos1
= self
.screenpos(d
, pos1
)
332 pos2
= self
.right
, self
.bottom
,self
.bottom
,self
.bottom
334 pos2
= self
.screenpos(d
, pos2
)
335 h1
, top1
, baseline1
, bottom1
= pos1
336 h2
, top2
, baseline2
, bottom2
= pos2
338 d
.invert((h1
, top1
), (self
.right
, bottom1
))
341 d
.invert((h1
, bottom1
), (self
.right
, top2
))
342 top1
, bottom1
= top2
, bottom2
343 d
.invert((h1
, top1
), (h2
, bottom2
))