# Text formatting abstractions# Note -- this module is obsolete, it's too slow anyway# Oft-used type objectInt=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 highlightedclassPara:#def__init__(self):self.words=[]# The wordsself.just='l'# Justification: 'l', 'r', 'lr' or 'c'self.indent_left=self.indent_right=self.indent_hang=0# Final lay-out parameters, may changeself.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)defaddword(self,d,font,text,space,stretch):iffont<>None:d.setfont(font)width=d.textwidth(text)ascent=d.baseline()descent=d.lineheight()-ascentspw=d.textwidth(' ')space=space*spwstretch=stretch*spwtuple=(font,text,width,space,stretch,ascent,descent)self.words.append(tuple)## Hooks to begin and end anchors -- insert numbers in the word list!defbgn_anchor(self,id):self.words.append(id)#defend_anchor(self,id):self.words.append(0)## Return the total length (width) of the text added so far, in pixelsdefgetlength(self):total=0forwordinself.words:iftype(word)<>Int:total=total+word[2]+word[3]returntotal## 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)deftabto(self,tab):total=0as,de=1,0foriinrange(len(self.words)):word=self.words[i]iftype(word)==Int:continuefo,te,wi,sp,st,as,de=wordself.words[i]=fo,te,wi,sp,0,as,detotal=total+wi+spiftotal<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 -hangdefmakehangingtag(self,hang):self.tabto(hang)self.indent_left=self.indent_left+hangself.indent_hang=-hang## Decide where the line breaks will be given some screen widthdeflayout(self,linewidth):self.width=linewidthheight=0self.lines=lines=[]avail1=self.width-self.indent_left-self.indent_rightavail=avail1-self.indent_hangwords=self.wordsi=0n=len(words)lastfont=Nonewhilei<n:firstfont=lastfontcharcount=0width=0stretch=0ascent=0descent=0lsp=0j=iwhilei<n:word=words[i]iftype(word)==Int:ifword>0andwidth>=avail:breaki=i+1continuefo,te,wi,sp,st,as,de=wordifwidth+wi>availandwidth>0andwi>0:breakiffo<>None:lastfont=foifwidth==0:firstfont=focharcount=charcount+len(te)+(sp>0)width=width+wi+splsp=spstretch=stretch+stlst=stascent=max(ascent,as)descent=max(descent,de)i=i+1whilei>jandtype(words[i-1])==Intand \
words[i-1]>0:i=i-1width=width-lspifi<n:stretch=stretch-lstelse:stretch=0tuple=i-j,firstfont,charcount,width,stretch, \
ascent,descentlines.append(tuple)height=height+ascent+descentavail=avail1self.height=height## Call a function for all words in a linedefvisit(self,wordfunc,anchorfunc):avail1=self.width-self.indent_left-self.indent_rightavail=avail1-self.indent_hangv=self.topi=0fortupleinself.lines:wordcount,firstfont,charcount,width,stretch, \
ascent,descent=tupleh=self.left+self.indent_leftifi==0:h=h+self.indent_hangextra=0ifself.just=='r':h=h+avail-widthelifself.just=='c':h=h+(avail-width)/2elifself.just=='lr'andstretch>0:extra=avail-widthv2=v+ascent+descentforjinrange(i,i+wordcount):word=self.words[j]iftype(word)==Int:ok=anchorfunc(self,tuple,word, \
h,v)ifok<>None:returnokcontinuefo,te,wi,sp,st,as,de=wordifextra>0andstretch>0:ex=extra*st/stretchextra=extra-exstretch=stretch-stelse:ex=0h2=h+wi+sp+exok=wordfunc(self,tuple,word,h,v, \
h2,v2,(j==i),(j==i+wordcount-1))ifok<>None:returnokh=h2v=v2i=i+wordcountavail=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.defrender(self,d,left,top,right):ifself.width<>right-left:self.layout(right-left)self.left=leftself.top=topself.right=rightself.bottom=self.top+self.heightself.anchorid=0try:self.d=dself.visit(self.__class__._renderword, \
self.__class__._renderanchor)finally:self.d=Nonereturnself.bottom#def_renderword(self,tuple,word,h,v,h2,v2,isfirst,islast):ifword[0]<>None:self.d.setfont(word[0])baseline=v+tuple[5]self.d.text((h,baseline-word[5]),word[1])ifself.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 mousedefhitcheck(self,mouseh,mousev):self.mouseh=mousehself.mousev=mousevself.anchorid=0self.hits=[]self.visit(self.__class__._hitcheckword, \
self.__class__._hitcheckanchor)returnself.hits#def_hitcheckword(self,tuple,word,h,v,h2,v2,isfirst,islast):ifself.anchorid>0andh<=self.mouseh<=h2and \
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 presentdefhasanchor(self,id):returnidinself.wordsor-idinself.words## Extract the raw text from the word list, substituting one space# for non-empty inter-word space, and terminating with '\n'defextract(self):text=''forwinself.words:iftype(w)<>Int:word=w[1]ifw[3]:word=word+' 'text=text+wordreturntext+'\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 paragraphdefwhereis(self,d,mouseh,mousev):ifmousev<self.topormousev>self.bottom:returnNoneself.mouseh=mousehself.mousev=mousevself.lastfont=Noneself.charcount=0try:self.d=dreturnself.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=wordiffo<>None:self.lastfont=foh=h1ifisfirst:h1=0ifislast:h2=999999ifnot(v1<=self.mousev<=v2andh1<=self.mouseh<=h2):self.charcount=self.charcount+len(te)+(sp>0)returnifself.lastfont<>None:self.d.setfont(self.lastfont)cc=0forcinte:cw=self.d.textwidth(c)ifself.mouseh<=h+cw/2:returnself.charcount+cccc=cc+1h=h+cwself.charcount=self.charcount+ccifself.mouseh<=(h+h2)/2:returnself.charcountelse:returnself.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()defscreenpos(self,d,pos):ifpos<0:ascent,descent=self.lines[0][5:7]returnself.left,self.top,self.top+ascent, \
self.top+ascent+descentself.pos=posself.lastfont=Nonetry:self.d=dok=self.visit(self.__class__._screenposword, \
self.__class__._screenposanchor)finally:self.d=Noneifok==None:ascent,descent=self.lines[-1][5:7]ok=self.right,self.bottom-ascent-descent, \
self.bottom-descent,self.bottomreturnok#def_screenposword(self,tuple,word,h1,v1,h2,v2,isfirst,islast):fo,te,wi,sp,st,as,de=wordiffo<>None:self.lastfont=focc=len(te)+(sp>0)ifself.pos>cc:self.pos=self.pos-ccreturnifself.pos<cc:self.d.setfont(self.lastfont)h=h1+self.d.textwidth(te[:self.pos])else:h=h2ascent,descent=tuple[5:7]returnh,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 argumentsdefinvert(self,d,pos1,pos2):ifpos1==None:pos1=self.left,self.top,self.top,self.topelse:pos1=self.screenpos(d,pos1)ifpos2==None:pos2=self.right,self.bottom,self.bottom,self.bottomelse:pos2=self.screenpos(d,pos2)h1,top1,baseline1,bottom1=pos1h2,top2,baseline2,bottom2=pos2ifbottom1<=top2:d.invert((h1,top1),(self.right,bottom1))h1=self.leftifbottom1<top2:d.invert((h1,bottom1),(self.right,top2))top1,bottom1=top2,bottom2d.invert((h1,top1),(h2,bottom2))# Test class Para# XXX This was last used on the Mac, hence the weird fonts...deftest():importstdwinfromstdwineventsimport*words='The','quick','brown','fox','jumps','over', \
'the','lazy','dog.'paralist=[]forjustin'l','r','lr','c':p=Para()p.just=justp.addword(stdwin,('New York','p',12),words[0],1,1)forwordinwords[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=Nonewhile1:etype,win,detail=stdwin.getevent()ifetype==WE_CLOSE:breakifetype==WE_SIZE:window.change((0,0),(1000,1000))ifetype==WE_DRAW:width,height=window.getwinsize()d=Nonetry:d=window.begindrawing()d.cliprect(detail)d.erase(detail)v=0forpinparalist:v=p.render(d,0,v,width)ifp==selparaand \
start<>Noneandstop<>None:p.invert(d,start,stop)finally:ifd:d.close()ifetype==WE_MOUSE_DOWN:ifselparaandstart<>Noneandstop<>None:d=window.begindrawing()selpara.invert(d,start,stop)d.close()start=stop=selpara=Nonemouseh,mousev=detail[0]forpinparalist:start=p.whereis(stdwin,mouseh,mousev)ifstart<>None:selpara=pbreakifetype==WE_MOUSE_UPandstart<>Noneandselpara:mouseh,mousev=detail[0]stop=selpara.whereis(stdwin,mouseh,mousev)ifstop==None:start=selpara=Noneelse:ifstart>stop:start,stop=stop,startd=window.begindrawing()selpara.invert(d,start,stop)d.close()window.close()