# A class to help applications that do fancy text formatting. # You create an instance each time you must redraw the window. # Set the initial left, top and right coordinates; # then feed it words, font changes and vertical movements. # # This class should eventually be extended to support much fancier # formatting, along the lines of TeX; for now, a very simple model # is sufficient. # class formatter: # # Initialize a formatter instance. # Pass the window's drawing object, and left, top, right # coordinates of the drawing space as arguments. # def init(self, (d, left, top, right)): self.d = d # Drawing object self.left = left # Left margin self.right = right # Right margin self.v = top # Top of current line self.center = 0 self.justify = 1 self.setfont('') # Default font self._reset() # Prepare for new line return self # # Reset for start of fresh line. # def _reset(self): self.boxes = [] # Boxes and glue still to be output self.sum_width = 0 # Total width of boxes self.sum_space = 0 # Total space between boxes self.sum_stretch = 0 # Total stretch for space between boxes self.max_ascent = 0 # Max ascent of current line self.max_descent = 0 # Max descent of current line self.avail_width = self.right - self.left self.hang_indent = 0 # # Set the current font, and compute some values from it. # def setfont(self, font): self.font = font self.d.setfont(font) self.font_space = self.d.textwidth(' ') self.font_ascent = self.d.baseline() self.font_descent = self.d.lineheight() - self.font_ascent # # Add a word to the list of boxes; first flush if line is full. # Space and stretch factors are expressed in fractions # of the current font's space width. # (Two variations: one without, one with explicit stretch factor.) # def addword(self, (word, spacefactor)): self.addwordstretch(word, spacefactor, spacefactor) # def addwordstretch(self, (word, spacefactor, stretchfactor)): width = self.d.textwidth(word) if width > self.avail_width: self._flush(1) space = int(float(self.font_space) * float(spacefactor)) stretch = int(float(self.font_space) * float(stretchfactor)) box = (self.font, word, width, space, stretch) self.boxes.append(box) self.sum_width = self.sum_width + width self.sum_space = self.sum_space + space self.sum_stretch = self.sum_stretch + stretch self.max_ascent = max(self.font_ascent, self.max_ascent) self.max_descent = max(self.font_descent, self.max_descent) self.avail_width = self.avail_width - width - space # # Flush current line and start a new one. # Flushing twice is harmless (i.e. does not introduce a blank line). # (Two versions: the internal one has a parameter for justification.) # def flush(self): self._flush(0) # def _flush(self, justify): if not self.boxes: return # # Compute amount of stretch needed. # if justify and self.justify or self.center: # # Compute extra space to fill; # this is avail_width plus glue from last box. # Also compute available stretch. # last_box = self.boxes[len(self.boxes)-1] font, word, width, space, stretch = last_box tot_extra = self.avail_width + space tot_stretch = self.sum_stretch - stretch else: tot_extra = tot_stretch = 0 # # Output the boxes. # baseline = self.v + self.max_ascent h = self.left + self.hang_indent if self.center: h = h + tot_extra / 2 tot_extra = tot_stretch = 0 for font, word, width, space, stretch in self.boxes: self.d.setfont(font) v = baseline - self.d.baseline() self.d.text((h, v), word) h = h + width + space if tot_extra > 0 and tot_stretch > 0: extra = stretch * tot_extra / tot_stretch h = h + extra tot_extra = tot_extra - extra tot_stretch = tot_stretch - stretch # # Prepare for next line. # self.v = baseline + self.max_descent self.d.setfont(self.font) self._reset() # # Add vertical space; first flush. # Vertical space is expressed in fractions of the current # font's line height. # def vspace(self, lines): self.vspacepixels(int(lines * self.d.lineheight())) # # Add vertical space given in pixels. # def vspacepixels(self, dv): self.flush() self.v = self.v + dv # # Set temporary (hanging) indent, for paragraph start. # First flush. # def tempindent(self, space): self.flush() hang = int(float(self.font_space) * float(space)) self.hang_indent = hang self.avail_width = self.avail_width - hang # # Add (permanent) left indentation. First flush. # def addleftindent(self, space): self.flush() self.left = self.left \ + int(float(self.font_space) * float(space)) self._reset() # # Test procedure # def test(): import stdwin, stdwinq from stdwinevents import * try: import mac # Mac font assignments: font1 = 'times', '', 12 font2 = 'times', 'b', 14 except ImportError: # X11R4 font assignments font1 = '*times-medium-r-*-120-*' font2 = '*times-bold-r-*-140-*' words = \ ['The','quick','brown','fox','jumps','over','the','lazy','dog.'] words = words * 2 stage = 0 stages = [(0,0,'ragged'), (1,0,'justified'), (0,1,'centered')] justify, center, title = stages[stage] stdwin.setdefwinsize(300,200) w = stdwin.open(title) winsize = w.getwinsize() while 1: type, window, detail = stdwinq.getevent() if type = WE_CLOSE: break elif type = WE_SIZE: newsize = w.getwinsize() if newsize <> winsize: w.change((0,0), winsize) winsize = newsize w.change((0,0), winsize) elif type = WE_MOUSE_DOWN: stage = (stage + 1) % len(stages) justify, center, title = stages[stage] w.settitle(title) w.change((0, 0), (1000, 1000)) elif type = WE_DRAW: width, height = winsize f = formatter().init(w.begindrawing(), 0, 0, width) f.center = center f.justify = justify if not center: f.tempindent(5) for font in font1, font2, font1: f.setfont(font) for word in words: space = 1 + (word[-1:] = '.') f.addword(word, space) if center and space > 1: f.flush() f.flush() height = f.v del f w.setdocsize(0, height)