home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pyos2bin.zip / Lib / fmt.py < prev    next >
Text File  |  1997-10-22  |  15KB  |  624 lines

  1. # Text formatting abstractions
  2. # Note -- this module is obsolete, it's too slow anyway
  3.  
  4.  
  5. import string
  6. import Para
  7.  
  8.  
  9. # A formatter back-end object has one method that is called by the formatter:
  10. # addpara(p), where p is a paragraph object.  For example:
  11.  
  12.  
  13. # Formatter back-end to do nothing at all with the paragraphs
  14. class NullBackEnd:
  15.     #
  16.     def __init__(self):
  17.         pass
  18.     #
  19.     def addpara(self, p):
  20.         pass
  21.     #
  22.     def bgn_anchor(self, id):
  23.         pass
  24.     #
  25.     def end_anchor(self, id):
  26.         pass
  27.  
  28.  
  29. # Formatter back-end to collect the paragraphs in a list
  30. class SavingBackEnd(NullBackEnd):
  31.     #
  32.     def __init__(self):
  33.         self.paralist = []
  34.     #
  35.     def addpara(self, p):
  36.         self.paralist.append(p)
  37.     #
  38.     def hitcheck(self, h, v):
  39.         hits = []
  40.         for p in self.paralist:
  41.             if p.top <= v <= p.bottom:
  42.                 for id in p.hitcheck(h, v):
  43.                     if id not in hits:
  44.                         hits.append(id)
  45.         return hits
  46.     #
  47.     def extract(self):
  48.         text = ''
  49.         for p in self.paralist:
  50.             text = text + (p.extract())
  51.         return text
  52.     #
  53.     def extractpart(self, long1, long2):
  54.         if long1 > long2: long1, long2 = long2, long1
  55.         para1, pos1 = long1
  56.         para2, pos2 = long2
  57.         text = ''
  58.         while para1 < para2:
  59.             ptext = self.paralist[para1].extract()
  60.             text = text + ptext[pos1:]
  61.             pos1 = 0
  62.             para1 = para1 + 1
  63.         ptext = self.paralist[para2].extract()
  64.         return text + ptext[pos1:pos2]
  65.     #
  66.     def whereis(self, d, h, v):
  67.         total = 0
  68.         for i in range(len(self.paralist)):
  69.             p = self.paralist[i]
  70.             result = p.whereis(d, h, v)
  71.             if result <> None:
  72.                 return i, result
  73.         return None
  74.     #
  75.     def roundtowords(self, long1, long2):
  76.         i, offset = long1
  77.         text = self.paralist[i].extract()
  78.         while offset > 0 and text[offset-1] <> ' ': offset = offset-1
  79.         long1 = i, offset
  80.         #
  81.         i, offset = long2
  82.         text = self.paralist[i].extract()
  83.         n = len(text)
  84.         while offset < n-1 and text[offset] <> ' ': offset = offset+1
  85.         long2 = i, offset
  86.         #
  87.         return long1, long2
  88.     #
  89.     def roundtoparagraphs(self, long1, long2):
  90.         long1 = long1[0], 0
  91.         long2 = long2[0], len(self.paralist[long2[0]].extract())
  92.         return long1, long2
  93.  
  94.  
  95. # Formatter back-end to send the text directly to the drawing object
  96. class WritingBackEnd(NullBackEnd):
  97.     #
  98.     def __init__(self, d, width):
  99.         self.d = d
  100.         self.width = width
  101.         self.lineno = 0
  102.     #
  103.     def addpara(self, p):
  104.         self.lineno = p.render(self.d, 0, self.lineno, self.width)
  105.  
  106.  
  107. # A formatter receives a stream of formatting instructions and assembles
  108. # these into a stream of paragraphs on to a back-end.  The assembly is
  109. # parametrized by a text measurement object, which must match the output
  110. # operations of the back-end.  The back-end is responsible for splitting
  111. # paragraphs up in lines of a given maximum width.  (This is done because
  112. # in a windowing environment, when the window size changes, there is no
  113. # need to redo the assembly into paragraphs, but the splitting into lines
  114. # must be done taking the new window size into account.)
  115.  
  116.  
  117. # Formatter base class.  Initialize it with a text measurement object,
  118. # which is used for text measurements, and a back-end object,
  119. # which receives the completed paragraphs.  The formatting methods are:
  120. # setfont(font)
  121. # setleftindent(nspaces)
  122. # setjust(type) where type is 'l', 'c', 'r', or 'lr'
  123. # flush()
  124. # vspace(nlines)
  125. # needvspace(nlines)
  126. # addword(word, nspaces)
  127. class BaseFormatter:
  128.     #
  129.     def __init__(self, d, b):
  130.         # Drawing object used for text measurements
  131.         self.d = d
  132.         #
  133.         # BackEnd object receiving completed paragraphs
  134.         self.b = b
  135.         #
  136.         # Parameters of the formatting model
  137.         self.leftindent = 0
  138.         self.just = 'l'
  139.         self.font = None
  140.         self.blanklines = 0
  141.         #
  142.         # Parameters derived from the current font
  143.         self.space = d.textwidth(' ')
  144.         self.line = d.lineheight()
  145.         self.ascent = d.baseline()
  146.         self.descent = self.line - self.ascent
  147.         #
  148.         # Parameter derived from the default font
  149.         self.n_space = self.space
  150.         #
  151.         # Current paragraph being built
  152.         self.para = None
  153.         self.nospace = 1
  154.         #
  155.         # Font to set on the next word
  156.         self.nextfont = None
  157.     #
  158.     def newpara(self):
  159.         return Para.Para()
  160.     #
  161.     def setfont(self, font):
  162.         if font == None: return
  163.         self.font = self.nextfont = font
  164.         d = self.d
  165.         d.setfont(font)
  166.         self.space = d.textwidth(' ')
  167.         self.line = d.lineheight()
  168.         self.ascent = d.baseline()
  169.         self.descent = self.line - self.ascent
  170.     #
  171.     def setleftindent(self, nspaces):
  172.         self.leftindent = int(self.n_space * nspaces)
  173.         if self.para:
  174.             hang = self.leftindent - self.para.indent_left
  175.             if hang > 0 and self.para.getlength() <= hang:
  176.                 self.para.makehangingtag(hang)
  177.                 self.nospace = 1
  178.             else:
  179.                 self.flush()
  180.     #
  181.     def setrightindent(self, nspaces):
  182.         self.rightindent = int(self.n_space * nspaces)
  183.         if self.para:
  184.             self.para.indent_right = self.rightindent
  185.             self.flush()
  186.     #
  187.     def setjust(self, just):
  188.         self.just = just
  189.         if self.para:
  190.             self.para.just = self.just
  191.     #
  192.     def flush(self):
  193.         if self.para:
  194.             self.b.addpara(self.para)
  195.             self.para = None
  196.             if self.font <> None:
  197.                 self.d.setfont(self.font)
  198.         self.nospace = 1
  199.     #
  200.     def vspace(self, nlines):
  201.         self.flush()
  202.         if nlines > 0:
  203.             self.para = self.newpara()
  204.             tuple = None, '', 0, 0, 0, int(nlines*self.line), 0
  205.             self.para.words.append(tuple)
  206.             self.flush()
  207.             self.blanklines = self.blanklines + nlines
  208.     #
  209.     def needvspace(self, nlines):
  210.         self.flush() # Just to be sure
  211.         if nlines > self.blanklines:
  212.             self.vspace(nlines - self.blanklines)
  213.     #
  214.     def addword(self, text, space):
  215.         if self.nospace and not text:
  216.             return
  217.         self.nospace = 0
  218.         self.blanklines = 0
  219.         if not self.para:
  220.             self.para = self.newpara()
  221.             self.para.indent_left = self.leftindent
  222.             self.para.just = self.just
  223.             self.nextfont = self.font
  224.         space = int(space * self.space)
  225.         self.para.words.append(self.nextfont, text, \
  226.             self.d.textwidth(text), space, space, \
  227.             self.ascent, self.descent)
  228.         self.nextfont = None
  229.     #
  230.     def bgn_anchor(self, id):
  231.         if not self.para:
  232.             self.nospace = 0
  233.             self.addword('', 0)
  234.         self.para.bgn_anchor(id)
  235.     #
  236.     def end_anchor(self, id):
  237.         if not self.para:
  238.             self.nospace = 0
  239.             self.addword('', 0)
  240.         self.para.end_anchor(id)
  241.  
  242.  
  243. # Measuring object for measuring text as viewed on a tty
  244. class NullMeasurer:
  245.     #
  246.     def __init__(self):
  247.         pass
  248.     #
  249.     def setfont(self, font):
  250.         pass
  251.     #
  252.     def textwidth(self, text):
  253.         return len(text)
  254.     #
  255.     def lineheight(self):
  256.         return 1
  257.     #
  258.     def baseline(self):
  259.         return 0
  260.  
  261.  
  262. # Drawing object for writing plain ASCII text to a file
  263. class FileWriter:
  264.     #
  265.     def __init__(self, fp):
  266.         self.fp = fp
  267.         self.lineno, self.colno = 0, 0
  268.     #
  269.     def setfont(self, font):
  270.         pass
  271.     #
  272.     def text(self, (h, v), str):
  273.         if not str: return
  274.         if '\n' in str:
  275.             raise ValueError, 'can\'t write \\n'
  276.         while self.lineno < v:
  277.             self.fp.write('\n')
  278.             self.colno, self.lineno = 0, self.lineno + 1
  279.         while self.lineno > v:
  280.             # XXX This should never happen...
  281.             self.fp.write('\033[A') # ANSI up arrow
  282.             self.lineno = self.lineno - 1
  283.         if self.colno < h:
  284.             self.fp.write(' ' * (h - self.colno))
  285.         elif self.colno > h:
  286.             self.fp.write('\b' * (self.colno - h))
  287.         self.colno = h
  288.         self.fp.write(str)
  289.         self.colno = h + len(str)
  290.  
  291.  
  292. # Formatting class to do nothing at all with the data
  293. class NullFormatter(BaseFormatter):
  294.     #
  295.     def __init__(self):
  296.         d = NullMeasurer()
  297.         b = NullBackEnd()
  298.         BaseFormatter.__init__(self, d, b)
  299.  
  300.  
  301. # Formatting class to write directly to a file
  302. class WritingFormatter(BaseFormatter):
  303.     #
  304.     def __init__(self, fp, width):
  305.         dm = NullMeasurer()
  306.         dw = FileWriter(fp)
  307.         b = WritingBackEnd(dw, width)
  308.         BaseFormatter.__init__(self, dm, b)
  309.         self.blanklines = 1
  310.     #
  311.     # Suppress multiple blank lines
  312.     def needvspace(self, nlines):
  313.         BaseFormatter.needvspace(self, min(1, nlines))
  314.  
  315.  
  316. # A "FunnyFormatter" writes ASCII text with a twist: *bold words*,
  317. # _italic text_ and _underlined words_, and `quoted text'.
  318. # It assumes that the fonts are 'r', 'i', 'b', 'u', 'q': (roman,
  319. # italic, bold, underline, quote).
  320. # Moreover, if the font is in upper case, the text is converted to
  321. # UPPER CASE.
  322. class FunnyFormatter(WritingFormatter):
  323.     #
  324.     def flush(self):
  325.         if self.para: finalize(self.para)
  326.         WritingFormatter.flush(self)
  327.  
  328.  
  329. # Surrounds *bold words* and _italic text_ in a paragraph with
  330. # appropriate markers, fixing the size (assuming these characters'
  331. # width is 1).
  332. openchar = \
  333.     {'b':'*', 'i':'_', 'u':'_', 'q':'`', 'B':'*', 'I':'_', 'U':'_', 'Q':'`'}
  334. closechar = \
  335.     {'b':'*', 'i':'_', 'u':'_', 'q':'\'', 'B':'*', 'I':'_', 'U':'_', 'Q':'\''}
  336. def finalize(para):
  337.     oldfont = curfont = 'r'
  338.     para.words.append(('r', '', 0, 0, 0, 0)) # temporary, deleted at end
  339.     for i in range(len(para.words)):
  340.         fo, te, wi = para.words[i][:3]
  341.         if fo <> None: curfont = fo
  342.         if curfont <> oldfont:
  343.             if closechar.has_key(oldfont):
  344.                 c = closechar[oldfont]
  345.                 j = i-1
  346.                 while j > 0 and para.words[j][1] == '': j = j-1
  347.                 fo1, te1, wi1 = para.words[j][:3]
  348.                 te1 = te1 + c
  349.                 wi1 = wi1 + len(c)
  350.                 para.words[j] = (fo1, te1, wi1) + \
  351.                     para.words[j][3:]
  352.             if openchar.has_key(curfont) and te:
  353.                 c = openchar[curfont]
  354.                 te = c + te
  355.                 wi = len(c) + wi
  356.                 para.words[i] = (fo, te, wi) + \
  357.                     para.words[i][3:]
  358.             if te: oldfont = curfont
  359.             else: oldfont = 'r'
  360.         if curfont in string.uppercase:
  361.             te = string.upper(te)
  362.             para.words[i] = (fo, te, wi) + para.words[i][3:]
  363.     del para.words[-1]
  364.  
  365.  
  366. # Formatter back-end to draw the text in a window.
  367. # This has an option to draw while the paragraphs are being added,
  368. # to minimize the delay before the user sees anything.
  369. # This manages the entire "document" of the window.
  370. class StdwinBackEnd(SavingBackEnd):
  371.     #
  372.     def __init__(self, window, drawnow):
  373.         self.window = window
  374.         self.drawnow = drawnow
  375.         self.width = window.getwinsize()[0]
  376.         self.selection = None
  377.         self.height = 0
  378.         window.setorigin(0, 0)
  379.         window.setdocsize(0, 0)
  380.         self.d = window.begindrawing()
  381.         SavingBackEnd.__init__(self)
  382.     #
  383.     def finish(self):
  384.         self.d.close()
  385.         self.d = None
  386.         self.window.setdocsize(0, self.height)
  387.     #
  388.     def addpara(self, p):
  389.         self.paralist.append(p)
  390.         if self.drawnow:
  391.             self.height = \
  392.                 p.render(self.d, 0, self.height, self.width)
  393.         else:
  394.             p.layout(self.width)
  395.             p.left = 0
  396.             p.top = self.height
  397.             p.right = self.width
  398.             p.bottom = self.height + p.height
  399.             self.height = p.bottom
  400.     #
  401.     def resize(self):
  402.         self.window.change((0, 0), (self.width, self.height))
  403.         self.width = self.window.getwinsize()[0]
  404.         self.height = 0
  405.         for p in self.paralist:
  406.             p.layout(self.width)
  407.             p.left = 0
  408.             p.top = self.height
  409.             p.right = self.width
  410.             p.bottom = self.height + p.height
  411.             self.height = p.bottom
  412.         self.window.change((0, 0), (self.width, self.height))
  413.         self.window.setdocsize(0, self.height)
  414.     #
  415.     def redraw(self, area):
  416.         d = self.window.begindrawing()
  417.         (left, top), (right, bottom) = area
  418.         d.erase(area)
  419.         d.cliprect(area)
  420.         for p in self.paralist:
  421.             if top < p.bottom and p.top < bottom:
  422.                 v = p.render(d, p.left, p.top, p.right)
  423.         if self.selection:
  424.             self.invert(d, self.selection)
  425.         d.close()
  426.     #
  427.     def setselection(self, new):
  428.         if new:
  429.             long1, long2 = new
  430.             pos1 = long1[:3]
  431.             pos2 = long2[:3]
  432.             new = pos1, pos2
  433.         if new <> self.selection:
  434.             d = self.window.begindrawing()
  435.             if self.selection:
  436.                 self.invert(d, self.selection)
  437.             if new:
  438.                 self.invert(d, new)
  439.             d.close()
  440.             self.selection = new
  441.     #
  442.     def getselection(self):
  443.         return self.selection
  444.     #
  445.     def extractselection(self):
  446.         if self.selection:
  447.             a, b = self.selection
  448.             return self.extractpart(a, b)
  449.         else:
  450.             return None
  451.     #
  452.     def invert(self, d, region):
  453.         long1, long2 = region
  454.         if long1 > long2: long1, long2 = long2, long1
  455.         para1, pos1 = long1
  456.         para2, pos2 = long2
  457.         while para1 < para2:
  458.             self.paralist[para1].invert(d, pos1, None)
  459.             pos1 = None
  460.             para1 = para1 + 1
  461.         self.paralist[para2].invert(d, pos1, pos2)
  462.     #
  463.     def search(self, prog):
  464.         import re, string
  465.         if type(prog) == type(''):
  466.             prog = re.compile(string.lower(prog))
  467.         if self.selection:
  468.             iold = self.selection[0][0]
  469.         else:
  470.             iold = -1
  471.         hit = None
  472.         for i in range(len(self.paralist)):
  473.             if i == iold or i < iold and hit:
  474.                 continue
  475.             p = self.paralist[i]
  476.             text = string.lower(p.extract())
  477.             match = prog.search(text)
  478.             if match:
  479.                 a, b = match.group(0)
  480.                 long1 = i, a
  481.                 long2 = i, b
  482.                 hit = long1, long2
  483.                 if i > iold:
  484.                     break
  485.         if hit:
  486.             self.setselection(hit)
  487.             i = hit[0][0]
  488.             p = self.paralist[i]
  489.             self.window.show((p.left, p.top), (p.right, p.bottom))
  490.             return 1
  491.         else:
  492.             return 0
  493.     #
  494.     def showanchor(self, id):
  495.         for i in range(len(self.paralist)):
  496.             p = self.paralist[i]
  497.             if p.hasanchor(id):
  498.                 long1 = i, 0
  499.                 long2 = i, len(p.extract())
  500.                 hit = long1, long2
  501.                 self.setselection(hit)
  502.                 self.window.show( \
  503.                     (p.left, p.top), (p.right, p.bottom))
  504.                 break
  505.  
  506.  
  507. # GL extensions
  508.  
  509. class GLFontCache:
  510.     #
  511.     def __init__(self):
  512.         self.reset()
  513.         self.setfont('')
  514.     #
  515.     def reset(self):
  516.         self.fontkey = None
  517.         self.fonthandle = None
  518.         self.fontinfo = None
  519.         self.fontcache = {}
  520.     #
  521.     def close(self):
  522.         self.reset()
  523.     #
  524.     def setfont(self, fontkey):
  525.         if fontkey == '':
  526.             fontkey = 'Times-Roman 12'
  527.         elif ' ' not in fontkey:
  528.             fontkey = fontkey + ' 12'
  529.         if fontkey == self.fontkey:
  530.             return
  531.         if self.fontcache.has_key(fontkey):
  532.             handle = self.fontcache[fontkey]
  533.         else:
  534.             import string
  535.             i = string.index(fontkey, ' ')
  536.             name, sizestr = fontkey[:i], fontkey[i:]
  537.             size = eval(sizestr)
  538.             key1 = name + ' 1'
  539.             key = name + ' ' + `size`
  540.             # NB key may differ from fontkey!
  541.             if self.fontcache.has_key(key):
  542.                 handle = self.fontcache[key]
  543.             else:
  544.                 if self.fontcache.has_key(key1):
  545.                     handle = self.fontcache[key1]
  546.                 else:
  547.                     import fm
  548.                     handle = fm.findfont(name)
  549.                     self.fontcache[key1] = handle
  550.                 handle = handle.scalefont(size)
  551.                 self.fontcache[fontkey] = \
  552.                     self.fontcache[key] = handle
  553.         self.fontkey = fontkey
  554.         if self.fonthandle <> handle:
  555.             self.fonthandle = handle
  556.             self.fontinfo = handle.getfontinfo()
  557.             handle.setfont()
  558.  
  559.  
  560. class GLMeasurer(GLFontCache):
  561.     #
  562.     def textwidth(self, text):
  563.         return self.fonthandle.getstrwidth(text)
  564.     #
  565.     def baseline(self):
  566.         return self.fontinfo[6] - self.fontinfo[3]
  567.     #
  568.     def lineheight(self):
  569.         return self.fontinfo[6]
  570.  
  571.  
  572. class GLWriter(GLFontCache):
  573.     #
  574.     # NOTES:
  575.     # (1) Use gl.ortho2 to use X pixel coordinates!
  576.     #
  577.     def text(self, (h, v), text):
  578.         import gl, fm
  579.         gl.cmov2i(h, v + self.fontinfo[6] - self.fontinfo[3])
  580.         fm.prstr(text)
  581.     #
  582.     def setfont(self, fontkey):
  583.         oldhandle = self.fonthandle
  584.         GLFontCache.setfont(fontkey)
  585.         if self.fonthandle <> oldhandle:
  586.             handle.setfont()
  587.  
  588.  
  589. class GLMeasurerWriter(GLMeasurer, GLWriter):
  590.     pass
  591.  
  592.  
  593. class GLBackEnd(SavingBackEnd):
  594.     #
  595.     def __init__(self, wid):
  596.         import gl
  597.         gl.winset(wid)
  598.         self.wid = wid
  599.         self.width = gl.getsize()[1]
  600.         self.height = 0
  601.         self.d = GLMeasurerWriter()
  602.         SavingBackEnd.__init__(self)
  603.     #
  604.     def finish(self):
  605.         pass
  606.     #
  607.     def addpara(self, p):
  608.         self.paralist.append(p)
  609.         self.height = p.render(self.d, 0, self.height, self.width)
  610.     #
  611.     def redraw(self):
  612.         import gl
  613.         gl.winset(self.wid)
  614.         width = gl.getsize()[1]
  615.         if width <> self.width:
  616.             setdocsize = 1
  617.             self.width = width
  618.             for p in self.paralist:
  619.                 p.top = p.bottom = None
  620.         d = self.d
  621.         v = 0
  622.         for p in self.paralist:
  623.             v = p.render(d, 0, v, width)
  624.