1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
|
# Text formatting abstractions
# Note -- this module is obsolete, it's too slow anyway
# Oft-used type object
Int = type(0)
# Represent a paragraph. This is a list of words with associated
# font and size information, plus indents and justification for the
# entire paragraph.
# Once the words have been added to a paragraph, it can be laid out
# for different line widths. Once laid out, it can be rendered at
# different screen locations. Once rendered, it can be queried
# for mouse hits, and parts of the text can be highlighted
class Para:
#
def __init__(self):
self.words = [] # The words
self.just = 'l' # Justification: 'l', 'r', 'lr' or 'c'
self.indent_left = self.indent_right = self.indent_hang = 0
# Final lay-out parameters, may change
self.left = self.top = self.right = self.bottom = \
self.width = self.height = self.lines = None
#
# Add a word, computing size information for it.
# Words may also be added manually by appending to self.words
# Each word should be a 7-tuple:
# (font, text, width, space, stretch, ascent, descent)
def addword(self, d, font, text, space, stretch):
if font <> None:
d.setfont(font)
width = d.textwidth(text)
ascent = d.baseline()
descent = d.lineheight() - ascent
spw = d.textwidth(' ')
space = space * spw
stretch = stretch * spw
tuple = (font, text, width, space, stretch, ascent, descent)
self.words.append(tuple)
#
# Hooks to begin and end anchors -- insert numbers in the word list!
def bgn_anchor(self, id):
self.words.append(id)
#
def end_anchor(self, id):
self.words.append(0)
#
# Return the total length (width) of the text added so far, in pixels
def getlength(self):
total = 0
for word in self.words:
if type(word) <> Int:
total = total + word[2] + word[3]
return total
#
# Tab to a given position (relative to the current left indent):
# remove all stretch, add fixed space up to the new indent.
# If the current position is already beying the tab stop,
# don't add any new space (but still remove the stretch)
def tabto(self, tab):
total = 0
as, de = 1, 0
for i in range(len(self.words)):
word = self.words[i]
if type(word) == Int: continue
(fo, te, wi, sp, st, as, de) = word
self.words[i] = (fo, te, wi, sp, 0, as, de)
total = total + wi + sp
if total < tab:
self.words.append((None, '', 0, tab-total, 0, as, de))
#
# Make a hanging tag: tab to hang, increment indent_left by hang,
# and reset indent_hang to -hang
def makehangingtag(self, hang):
self.tabto(hang)
self.indent_left = self.indent_left + hang
self.indent_hang = -hang
#
# Decide where the line breaks will be given some screen width
def layout(self, linewidth):
self.width = linewidth
height = 0
self.lines = lines = []
avail1 = self.width - self.indent_left - self.indent_right
avail = avail1 - self.indent_hang
words = self.words
i = 0
n = len(words)
lastfont = None
while i < n:
firstfont = lastfont
charcount = 0
width = 0
stretch = 0
ascent = 0
descent = 0
lsp = 0
j = i
while i < n:
word = words[i]
if type(word) == Int:
if word > 0 and width >= avail:
break
i = i+1
continue
fo, te, wi, sp, st, as, de = word
if width + wi > avail and width > 0 and wi > 0:
break
if fo <> None:
lastfont = fo
if width == 0:
firstfont = fo
charcount = charcount + len(te) + (sp > 0)
width = width + wi + sp
lsp = sp
stretch = stretch + st
lst = st
ascent = max(ascent, as)
descent = max(descent, de)
i = i+1
while i > j and type(words[i-1]) == Int and \
words[i-1] > 0: i = i-1
width = width - lsp
if i < n:
stretch = stretch - lst
else:
stretch = 0
tuple = i-j, firstfont, charcount, width, stretch, \
ascent, descent
lines.append(tuple)
height = height + ascent + descent
avail = avail1
self.height = height
#
# Call a function for all words in a line
def visit(self, wordfunc, anchorfunc):
avail1 = self.width - self.indent_left - self.indent_right
avail = avail1 - self.indent_hang
v = self.top
i = 0
for tuple in self.lines:
wordcount, firstfont, charcount, width, stretch, \
ascent, descent = tuple
h = self.left + self.indent_left
if i == 0: h = h + self.indent_hang
extra = 0
if self.just == 'r': h = h + avail - width
elif self.just == 'c': h = h + (avail - width) / 2
elif self.just == 'lr' and stretch > 0:
extra = avail - width
v2 = v + ascent + descent
for j in range(i, i+wordcount):
word = self.words[j]
if type(word) == Int:
ok = anchorfunc(self, tuple, word, \
h, v)
if ok <> None: return ok
continue
fo, te, wi, sp, st, as, de = word
if extra > 0 and stretch > 0:
ex = extra * st / stretch
extra = extra - ex
stretch = stretch - st
else:
ex = 0
h2 = h + wi + sp + ex
ok = wordfunc(self, tuple, word, h, v, \
h2, v2, (j==i), (j==i+wordcount-1))
if ok <> None: return ok
h = h2
v = v2
i = i + wordcount
avail = avail1
#
# Render a paragraph in "drawing object" d, using the rectangle
# given by (left, top, right) with an unspecified bottom.
# Return the computed bottom of the text.
def render(self, d, left, top, right):
if self.width <> right-left:
self.layout(right-left)
self.left = left
self.top = top
self.right = right
self.bottom = self.top + self.height
self.anchorid = 0
try:
self.d = d
self.visit(self.__class__._renderword, \
self.__class__._renderanchor)
finally:
self.d = None
return self.bottom
#
def _renderword(self, tuple, word, h, v, h2, v2, isfirst, islast):
if word[0] <> None: self.d.setfont(word[0])
baseline = v + tuple[5]
self.d.text((h, baseline - word[5]), word[1])
if self.anchorid > 0:
self.d.line((h, baseline+2), (h2, baseline+2))
#
def _renderanchor(self, tuple, word, h, v):
self.anchorid = word
#
# Return which anchor(s) was hit by the mouse
def hitcheck(self, mouseh, mousev):
self.mouseh = mouseh
self.mousev = mousev
self.anchorid = 0
self.hits = []
self.visit(self.__class__._hitcheckword, \
self.__class__._hitcheckanchor)
return self.hits
#
def _hitcheckword(self, tuple, word, h, v, h2, v2, isfirst, islast):
if self.anchorid > 0 and h <= self.mouseh <= h2 and \
v <= self.mousev <= v2:
self.hits.append(self.anchorid)
#
def _hitcheckanchor(self, tuple, word, h, v):
self.anchorid = word
#
# Return whether the given anchor id is present
def hasanchor(self, id):
return id in self.words or -id in self.words
#
# Extract the raw text from the word list, substituting one space
# for non-empty inter-word space, and terminating with '\n'
def extract(self):
text = ''
for w in self.words:
if type(w) <> Int:
word = w[1]
if w[3]: word = word + ' '
text = text + word
return text + '\n'
#
# Return which character position was hit by the mouse, as
# an offset in the entire text as returned by extract().
# Return None if the mouse was not in this paragraph
def whereis(self, d, mouseh, mousev):
if mousev < self.top or mousev > self.bottom:
return None
self.mouseh = mouseh
self.mousev = mousev
self.lastfont = None
self.charcount = 0
try:
self.d = d
return self.visit(self.__class__._whereisword, \
self.__class__._whereisanchor)
finally:
self.d = None
#
def _whereisword(self, tuple, word, h1, v1, h2, v2, isfirst, islast):
fo, te, wi, sp, st, as, de = word
if fo <> None: self.lastfont = fo
h = h1
if isfirst: h1 = 0
if islast: h2 = 999999
if not (v1 <= self.mousev <= v2 and h1 <= self.mouseh <= h2):
self.charcount = self.charcount + len(te) + (sp > 0)
return
if self.lastfont <> None:
self.d.setfont(self.lastfont)
cc = 0
for c in te:
cw = self.d.textwidth(c)
if self.mouseh <= h + cw/2:
return self.charcount + cc
cc = cc+1
h = h+cw
self.charcount = self.charcount + cc
if self.mouseh <= (h+h2) / 2:
return self.charcount
else:
return self.charcount + 1
#
def _whereisanchor(self, tuple, word, h, v):
pass
#
# Return screen position corresponding to position in paragraph.
# Return tuple (h, vtop, vbaseline, vbottom).
# This is more or less the inverse of whereis()
def screenpos(self, d, pos):
if pos < 0:
ascent, descent = self.lines[0][5:7]
return self.left, self.top, self.top + ascent, \
self.top + ascent + descent
self.pos = pos
self.lastfont = None
try:
self.d = d
ok = self.visit(self.__class__._screenposword, \
self.__class__._screenposanchor)
finally:
self.d = None
if ok == None:
ascent, descent = self.lines[-1][5:7]
ok = self.right, self.bottom - ascent - descent, \
self.bottom - descent, self.bottom
return ok
#
def _screenposword(self, tuple, word, h1, v1, h2, v2, isfirst, islast):
fo, te, wi, sp, st, as, de = word
if fo <> None: self.lastfont = fo
cc = len(te) + (sp > 0)
if self.pos > cc:
self.pos = self.pos - cc
return
if self.pos < cc:
self.d.setfont(self.lastfont)
h = h1 + self.d.textwidth(te[:self.pos])
else:
h = h2
ascent, descent = tuple[5:7]
return h, v1, v1+ascent, v2
#
def _screenposanchor(self, tuple, word, h, v):
pass
#
# Invert the stretch of text between pos1 and pos2.
# If pos1 is None, the beginning is implied;
# if pos2 is None, the end is implied.
# Undoes its own effect when called again with the same arguments
def invert(self, d, pos1, pos2):
if pos1 == None:
pos1 = self.left, self.top, self.top, self.top
else:
pos1 = self.screenpos(d, pos1)
if pos2 == None:
pos2 = self.right, self.bottom,self.bottom,self.bottom
else:
pos2 = self.screenpos(d, pos2)
h1, top1, baseline1, bottom1 = pos1
h2, top2, baseline2, bottom2 = pos2
if bottom1 <= top2:
d.invert((h1, top1), (self.right, bottom1))
h1 = self.left
if bottom1 < top2:
d.invert((h1, bottom1), (self.right, top2))
top1, bottom1 = top2, bottom2
d.invert((h1, top1), (h2, bottom2))
# Test class Para
# XXX This was last used on the Mac, hence the weird fonts...
def test():
import stdwin
from stdwinevents import *
words = 'The', 'quick', 'brown', 'fox', 'jumps', 'over', \
'the', 'lazy', 'dog.'
paralist = []
for just in 'l', 'r', 'lr', 'c':
p = Para()
p.just = just
p.addword(stdwin, ('New York', 'p', 12), words[0], 1, 1)
for word in words[1:-1]:
p.addword(stdwin, None, word, 1, 1)
p.addword(stdwin, None, words[-1], 2, 4)
p.addword(stdwin, ('New York', 'b', 18), 'Bye!', 0, 0)
p.addword(stdwin, ('New York', 'p', 10), 'Bye!', 0, 0)
paralist.append(p)
window = stdwin.open('Para.test()')
start = stop = selpara = None
while 1:
etype, win, detail = stdwin.getevent()
if etype == WE_CLOSE:
break
if etype == WE_SIZE:
window.change((0, 0), (1000, 1000))
if etype == WE_DRAW:
width, height = window.getwinsize()
d = None
try:
d = window.begindrawing()
d.cliprect(detail)
d.erase(detail)
v = 0
for p in paralist:
v = p.render(d, 0, v, width)
if p == selpara and \
start <> None and stop <> None:
p.invert(d, start, stop)
finally:
if d: d.close()
if etype == WE_MOUSE_DOWN:
if selpara and start <> None and stop <> None:
d = window.begindrawing()
selpara.invert(d, start, stop)
d.close()
start = stop = selpara = None
mouseh, mousev = detail[0]
for p in paralist:
start = p.whereis(stdwin, mouseh, mousev)
if start <> None:
selpara = p
break
if etype == WE_MOUSE_UP and start <> None and selpara:
mouseh, mousev = detail[0]
stop = selpara.whereis(stdwin, mouseh, mousev)
if stop == None: start = selpara = None
else:
if start > stop:
start, stop = stop, start
d = window.begindrawing()
selpara.invert(d, start, stop)
d.close()
window.close()
|