2fd8dc6499496243866372efeb44532a8b5ccb56
# Text formatting abstractions
# Note -- this module is obsolete, it's too slow anyway
# Represent a paragraph. This is a list of words with associated
# font and size information, plus indents and justification for the
# 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
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
):
width
= d
.textwidth(text
)
descent
= d
.lineheight() - ascent
tuple = (font
, text
, width
, space
, stretch
, ascent
, descent
)
# Hooks to begin and end anchors -- insert numbers in the word list!
def bgn_anchor(self
, id):
def end_anchor(self
, id):
# Return the total length (width) of the text added so far, in pixels
if type(word
) is not Int
:
total
= total
+ word
[2] + word
[3]
# 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 at the tab stop,
# don't add any new space (but still remove the stretch)
for i
in range(len(self
.words
)):
if type(word
) is Int
: continue
(fo
, te
, wi
, sp
, st
, as, de
) = word
self
.words
[i
] = (fo
, te
, wi
, sp
, 0, as, de
)
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
.indent_left
= self
.indent_left
+ hang
# Decide where the line breaks will be given some screen width
def layout(self
, linewidth
):
avail1
= self
.width
- self
.indent_left
- self
.indent_right
avail
= avail1
- self
.indent_hang
if word
> 0 and width
>= avail
:
fo
, te
, wi
, sp
, st
, as, de
= word
if width
+ wi
> avail
and width
> 0 and wi
> 0:
charcount
= charcount
+ len(te
) + (sp
> 0)
descent
= max(descent
, de
)
while i
> j
and type(words
[i
-1]) is Int
and \
tuple = i
-j
, firstfont
, charcount
, width
, stretch
, \
height
= height
+ ascent
+ descent
# 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
wordcount
, firstfont
, charcount
, width
, stretch
, \
h
= self
.left
+ self
.indent_left
if i
== 0: h
= h
+ self
.indent_hang
if self
.just
== 'r': h
= h
+ avail
- width
elif self
.just
== 'c': h
= h
+ (avail
- width
) / 2
elif self
.just
== 'lr' and stretch
> 0:
v2
= v
+ ascent
+ descent
for j
in range(i
, i
+wordcount
):
ok
= anchorfunc(self
, tuple, word
, \
if ok
is not None: return ok
fo
, te
, wi
, sp
, st
, as, de
= word
if extra
> 0 and stretch
> 0:
ex
= extra
* st
/ stretch
ok
= wordfunc(self
, tuple, word
, h
, v
, \
h2
, v2
, (j
==i
), (j
==i
+wordcount
-1))
if ok
is not None: return ok
# 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
.bottom
= self
.top
+ self
.height
self
.visit(self
.__class
__._renderword
, \
self
.__class
__._renderanchor
)
def _renderword(self
, tuple, word
, h
, v
, h2
, v2
, isfirst
, islast
):
if word
[0] is not None: self
.d
.setfont(word
[0])
self
.d
.text((h
, baseline
- word
[5]), word
[1])
self
.d
.line((h
, baseline
+2), (h2
, baseline
+2))
def _renderanchor(self
, tuple, word
, h
, v
):
# Return which anchor(s) was hit by the mouse
def hitcheck(self
, mouseh
, mousev
):
self
.visit(self
.__class
__._hitcheckword
, \
self
.__class
__._hitcheckanchor
)
def _hitcheckword(self
, tuple, word
, h
, v
, h2
, v2
, isfirst
, islast
):
if self
.anchorid
> 0 and h
<= self
.mouseh
<= h2
and \
self
.hits
.append(self
.anchorid
)
def _hitcheckanchor(self
, tuple, word
, h
, v
):
# Return whether the given anchor id is present
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'
if w
[3]: word
= word
+ ' '
# 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 self
.visit(self
.__class
__._whereisword
, \
self
.__class
__._whereisanchor
)
def _whereisword(self
, tuple, word
, h1
, v1
, h2
, v2
, isfirst
, islast
):
fo
, te
, wi
, sp
, st
, as, de
= word
if fo
is not None: self
.lastfont
= fo
if not (v1
<= self
.mousev
<= v2
and h1
<= self
.mouseh
<= h2
):
self
.charcount
= self
.charcount
+ len(te
) + (sp
> 0)
if self
.lastfont
is not None:
self
.d
.setfont(self
.lastfont
)
if self
.mouseh
<= h
+ cw
/2:
return self
.charcount
+ cc
self
.charcount
= self
.charcount
+ cc
if self
.mouseh
<= (h
+h2
) / 2:
return self
.charcount
+ 1
def _whereisanchor(self
, tuple, word
, h
, v
):
# 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
):
ascent
, descent
= self
.lines
[0][5:7]
return self
.left
, self
.top
, self
.top
+ ascent
, \
self
.top
+ ascent
+ descent
ok
= self
.visit(self
.__class
__._screenposword
, \
self
.__class
__._screenposanchor
)
ascent
, descent
= self
.lines
[-1][5:7]
ok
= self
.right
, self
.bottom
- ascent
- descent
, \
self
.bottom
- descent
, self
.bottom
def _screenposword(self
, tuple, word
, h1
, v1
, h2
, v2
, isfirst
, islast
):
fo
, te
, wi
, sp
, st
, as, de
= word
if fo
is not None: self
.lastfont
= fo
self
.d
.setfont(self
.lastfont
)
h
= h1
+ self
.d
.textwidth(te
[:self
.pos
])
ascent
, descent
= tuple[5:7]
return h
, v1
, v1
+ascent
, v2
def _screenposanchor(self
, tuple, word
, h
, v
):
# 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
):
pos1
= self
.left
, self
.top
, self
.top
, self
.top
pos1
= self
.screenpos(d
, pos1
)
pos2
= self
.right
, self
.bottom
,self
.bottom
,self
.bottom
pos2
= self
.screenpos(d
, pos2
)
h1
, top1
, baseline1
, bottom1
= pos1
h2
, top2
, baseline2
, bottom2
= pos2
d
.invert((h1
, top1
), (self
.right
, bottom1
))
d
.invert((h1
, bottom1
), (self
.right
, top2
))
top1
, bottom1
= top2
, bottom2
d
.invert((h1
, top1
), (h2
, bottom2
))