# Text formatting abstractions # Note -- this module is obsolete, it's too slow anyway import string import Para # A formatter back-end object has one method that is called by the formatter: # addpara(p), where p is a paragraph object. For example: # Formatter back-end to do nothing at all with the paragraphs class NullBackEnd: # def __init__(self): pass # def addpara(self, p): pass # def bgn_anchor(self, id): pass # def end_anchor(self, id): pass # Formatter back-end to collect the paragraphs in a list class SavingBackEnd(NullBackEnd): # def __init__(self): self.paralist = [] # def addpara(self, p): self.paralist.append(p) # def hitcheck(self, h, v): hits = [] for p in self.paralist: if p.top <= v <= p.bottom: for id in p.hitcheck(h, v): if id not in hits: hits.append(id) return hits # def extract(self): text = '' for p in self.paralist: text = text + (p.extract()) return text # def extractpart(self, long1, long2): if long1 > long2: long1, long2 = long2, long1 para1, pos1 = long1 para2, pos2 = long2 text = '' while para1 < para2: ptext = self.paralist[para1].extract() text = text + ptext[pos1:] pos1 = 0 para1 = para1 + 1 ptext = self.paralist[para2].extract() return text + ptext[pos1:pos2] # def whereis(self, d, h, v): total = 0 for i in range(len(self.paralist)): p = self.paralist[i] result = p.whereis(d, h, v) if result <> None: return i, result return None # def roundtowords(self, long1, long2): i, offset = long1 text = self.paralist[i].extract() while offset > 0 and text[offset-1] <> ' ': offset = offset-1 long1 = i, offset # i, offset = long2 text = self.paralist[i].extract() n = len(text) while offset < n-1 and text[offset] <> ' ': offset = offset+1 long2 = i, offset # return long1, long2 # def roundtoparagraphs(self, long1, long2): long1 = long1[0], 0 long2 = long2[0], len(self.paralist[long2[0]].extract()) return long1, long2 # Formatter back-end to send the text directly to the drawing object class WritingBackEnd(NullBackEnd): # def __init__(self, d, width): self.d = d self.width = width self.lineno = 0 # def addpara(self, p): self.lineno = p.render(self.d, 0, self.lineno, self.width) # A formatter receives a stream of formatting instructions and assembles # these into a stream of paragraphs on to a back-end. The assembly is # parametrized by a text measurement object, which must match the output # operations of the back-end. The back-end is responsible for splitting # paragraphs up in lines of a given maximum width. (This is done because # in a windowing environment, when the window size changes, there is no # need to redo the assembly into paragraphs, but the splitting into lines # must be done taking the new window size into account.) # Formatter base class. Initialize it with a text measurement object, # which is used for text measurements, and a back-end object, # which receives the completed paragraphs. The formatting methods are: # setfont(font) # setleftindent(nspaces) # setjust(type) where type is 'l', 'c', 'r', or 'lr' # flush() # vspace(nlines) # needvspace(nlines) # addword(word, nspaces) class BaseFormatter: # def __init__(self, d, b): # Drawing object used for text measurements self.d = d # # BackEnd object receiving completed paragraphs self.b = b # # Parameters of the formatting model self.leftindent = 0 self.just = 'l' self.font = None self.blanklines = 0 # # Parameters derived from the current font self.space = d.textwidth(' ') self.line = d.lineheight() self.ascent = d.baseline() self.descent = self.line - self.ascent # # Parameter derived from the default font self.n_space = self.space # # Current paragraph being built self.para = None self.nospace = 1 # # Font to set on the next word self.nextfont = None # def newpara(self): return Para.Para() # def setfont(self, font): if font == None: return self.font = self.nextfont = font d = self.d d.setfont(font) self.space = d.textwidth(' ') self.line = d.lineheight() self.ascent = d.baseline() self.descent = self.line - self.ascent # def setleftindent(self, nspaces): self.leftindent = int(self.n_space * nspaces) if self.para: hang = self.leftindent - self.para.indent_left if hang > 0 and self.para.getlength() <= hang: self.para.makehangingtag(hang) self.nospace = 1 else: self.flush() # def setrightindent(self, nspaces): self.rightindent = int(self.n_space * nspaces) if self.para: self.para.indent_right = self.rightindent self.flush() # def setjust(self, just): self.just = just if self.para: self.para.just = self.just # def flush(self): if self.para: self.b.addpara(self.para) self.para = None if self.font <> None: self.d.setfont(self.font) self.nospace = 1 # def vspace(self, nlines): self.flush() if nlines > 0: self.para = self.newpara() tuple = None, '', 0, 0, 0, int(nlines*self.line), 0 self.para.words.append(tuple) self.flush() self.blanklines = self.blanklines + nlines # def needvspace(self, nlines): self.flush() # Just to be sure if nlines > self.blanklines: self.vspace(nlines - self.blanklines) # def addword(self, text, space): if self.nospace and not text: return self.nospace = 0 self.blanklines = 0 if not self.para: self.para = self.newpara() self.para.indent_left = self.leftindent self.para.just = self.just self.nextfont = self.font space = int(space * self.space) self.para.words.append(self.nextfont, text, \ self.d.textwidth(text), space, space, \ self.ascent, self.descent) self.nextfont = None # def bgn_anchor(self, id): if not self.para: self.nospace = 0 self.addword('', 0) self.para.bgn_anchor(id) # def end_anchor(self, id): if not self.para: self.nospace = 0 self.addword('', 0) self.para.end_anchor(id) # Measuring object for measuring text as viewed on a tty class NullMeasurer: # def __init__(self): pass # def setfont(self, font): pass # def textwidth(self, text): return len(text) # def lineheight(self): return 1 # def baseline(self): return 0 # Drawing object for writing plain ASCII text to a file class FileWriter: # def __init__(self, fp): self.fp = fp self.lineno, self.colno = 0, 0 # def setfont(self, font): pass # def text(self, (h, v), str): if not str: return if '\n' in str: raise ValueError, 'can\'t write \\n' while self.lineno < v: self.fp.write('\n') self.colno, self.lineno = 0, self.lineno + 1 while self.lineno > v: # XXX This should never happen... self.fp.write('\033[A') # ANSI up arrow self.lineno = self.lineno - 1 if self.colno < h: self.fp.write(' ' * (h - self.colno)) elif self.colno > h: self.fp.write('\b' * (self.colno - h)) self.colno = h self.fp.write(str) self.colno = h + len(str) # Formatting class to do nothing at all with the data class NullFormatter(BaseFormatter): # def __init__(self): d = NullMeasurer() b = NullBackEnd() BaseFormatter.__init__(self, d, b) # Formatting class to write directly to a file class WritingFormatter(BaseFormatter): # def __init__(self, fp, width): dm = NullMeasurer() dw = FileWriter(fp) b = WritingBackEnd(dw, width) BaseFormatter.__init__(self, dm, b) self.blanklines = 1 # # Suppress multiple blank lines def needvspace(self, nlines): BaseFormatter.needvspace(self, min(1, nlines)) # A "FunnyFormatter" writes ASCII text with a twist: *bold words*, # _italic text_ and _underlined words_, and `quoted text'. # It assumes that the fonts are 'r', 'i', 'b', 'u', 'q': (roman, # italic, bold, underline, quote). # Moreover, if the font is in upper case, the text is converted to # UPPER CASE. class FunnyFormatter(WritingFormatter): # def flush(self): if self.para: finalize(self.para) WritingFormatter.flush(self) # Surrounds *bold words* and _italic text_ in a paragraph with # appropriate markers, fixing the size (assuming these characters' # width is 1). openchar = \ {'b':'*', 'i':'_', 'u':'_', 'q':'`', 'B':'*', 'I':'_', 'U':'_', 'Q':'`'} closechar = \ {'b':'*', 'i':'_', 'u':'_', 'q':'\'', 'B':'*', 'I':'_', 'U':'_', 'Q':'\''} def finalize(para): oldfont = curfont = 'r' para.words.append(('r', '', 0, 0, 0, 0)) # temporary, deleted at end for i in range(len(para.words)): fo, te, wi = para.words[i][:3] if fo <> None: curfont = fo if curfont <> oldfont: if closechar.has_key(oldfont): c = closechar[oldfont] j = i-1 while j > 0 and para.words[j][1] == '': j = j-1 fo1, te1, wi1 = para.words[j][:3] te1 = te1 + c wi1 = wi1 + len(c) para.words[j] = (fo1, te1, wi1) + \ para.words[j][3:] if openchar.has_key(curfont) and te: c = openchar[curfont] te = c + te wi = len(c) + wi para.words[i] = (fo, te, wi) + \ para.words[i][3:] if te: oldfont = curfont else: oldfont = 'r' if curfont in string.uppercase: te = string.upper(te) para.words[i] = (fo, te, wi) + para.words[i][3:] del para.words[-1] # Formatter back-end to draw the text in a window. # This has an option to draw while the paragraphs are being added, # to minimize the delay before the user sees anything. # This manages the entire "document" of the window. class StdwinBackEnd(SavingBackEnd): # def __init__(self, window, drawnow): self.window = window self.drawnow = drawnow self.width = window.getwinsize()[0] self.selection = None self.height = 0 window.setorigin(0, 0) window.setdocsize(0, 0) self.d = window.begindrawing() SavingBackEnd.__init__(self) # def finish(self): self.d.close() self.d = None self.window.setdocsize(0, self.height) # def addpara(self, p): self.paralist.append(p) if self.drawnow: self.height = \ p.render(self.d, 0, self.height, self.width) else: p.layout(self.width) p.left = 0 p.top = self.height p.right = self.width p.bottom = self.height + p.height self.height = p.bottom # def resize(self): self.window.change((0, 0), (self.width, self.height)) self.width = self.window.getwinsize()[0] self.height = 0 for p in self.paralist: p.layout(self.width) p.left = 0 p.top = self.height p.right = self.width p.bottom = self.height + p.height self.height = p.bottom self.window.change((0, 0), (self.width, self.height)) self.window.setdocsize(0, self.height) # def redraw(self, area): d = self.window.begindrawing() (left, top), (right, bottom) = area d.erase(area) d.cliprect(area) for p in self.paralist: if top < p.bottom and p.top < bottom: v = p.render(d, p.left, p.top, p.right) if self.selection: self.invert(d, self.selection) d.close() # def setselection(self, new): if new: long1, long2 = new pos1 = long1[:3] pos2 = long2[:3] new = pos1, pos2 if new <> self.selection: d = self.window.begindrawing() if self.selection: self.invert(d, self.selection) if new: self.invert(d, new) d.close() self.selection = new # def getselection(self): return self.selection # def extractselection(self): if self.selection: a, b = self.selection return self.extractpart(a, b) else: return None # def invert(self, d, region): long1, long2 = region if long1 > long2: long1, long2 = long2, long1 para1, pos1 = long1 para2, pos2 = long2 while para1 < para2: self.paralist[para1].invert(d, pos1, None) pos1 = None para1 = para1 + 1 self.paralist[para2].invert(d, pos1, pos2) # def search(self, prog): import regex, string if type(prog) == type(''): prog = regex.compile(string.lower(prog)) if self.selection: iold = self.selection[0][0] else: iold = -1 hit = None for i in range(len(self.paralist)): if i == iold or i < iold and hit: continue p = self.paralist[i] text = string.lower(p.extract()) if prog.search(text) >= 0: a, b = prog.regs[0] long1 = i, a long2 = i, b hit = long1, long2 if i > iold: break if hit: self.setselection(hit) i = hit[0][0] p = self.paralist[i] self.window.show((p.left, p.top), (p.right, p.bottom)) return 1 else: return 0 # def showanchor(self, id): for i in range(len(self.paralist)): p = self.paralist[i] if p.hasanchor(id): long1 = i, 0 long2 = i, len(p.extract()) hit = long1, long2 self.setselection(hit) self.window.show( \ (p.left, p.top), (p.right, p.bottom)) break # GL extensions class GLFontCache: # def __init__(self): self.reset() self.setfont('') # def reset(self): self.fontkey = None self.fonthandle = None self.fontinfo = None self.fontcache = {} # def close(self): self.reset() # def setfont(self, fontkey): if fontkey == '': fontkey = 'Times-Roman 12' elif ' ' not in fontkey: fontkey = fontkey + ' 12' if fontkey == self.fontkey: return if self.fontcache.has_key(fontkey): handle = self.fontcache[fontkey] else: import string i = string.index(fontkey, ' ') name, sizestr = fontkey[:i], fontkey[i:] size = eval(sizestr) key1 = name + ' 1' key = name + ' ' + `size` # NB key may differ from fontkey! if self.fontcache.has_key(key): handle = self.fontcache[key] else: if self.fontcache.has_key(key1): handle = self.fontcache[key1] else: import fm handle = fm.findfont(name) self.fontcache[key1] = handle handle = handle.scalefont(size) self.fontcache[fontkey] = \ self.fontcache[key] = handle self.fontkey = fontkey if self.fonthandle <> handle: self.fonthandle = handle self.fontinfo = handle.getfontinfo() handle.setfont() class GLMeasurer(GLFontCache): # def textwidth(self, text): return self.fonthandle.getstrwidth(text) # def baseline(self): return self.fontinfo[6] - self.fontinfo[3] # def lineheight(self): return self.fontinfo[6] class GLWriter(GLFontCache): # # NOTES: # (1) Use gl.ortho2 to use X pixel coordinates! # def text(self, (h, v), text): import gl, fm gl.cmov2i(h, v + self.fontinfo[6] - self.fontinfo[3]) fm.prstr(text) # def setfont(self, fontkey): oldhandle = self.fonthandle GLFontCache.setfont(fontkey) if self.fonthandle <> oldhandle: handle.setfont() class GLMeasurerWriter(GLMeasurer, GLWriter): pass class GLBackEnd(SavingBackEnd): # def __init__(self, wid): import gl gl.winset(wid) self.wid = wid self.width = gl.getsize()[1] self.height = 0 self.d = GLMeasurerWriter() SavingBackEnd.__init__(self) # def finish(self): pass # def addpara(self, p): self.paralist.append(p) self.height = p.render(self.d, 0, self.height, self.width) # def redraw(self): import gl gl.winset(self.wid) width = gl.getsize()[1] if width <> self.width: setdocsize = 1 self.width = width for p in self.paralist: p.top = p.bottom = None d = self.d v = 0 for p in self.paralist: v = p.render(d, 0, v, width)