Guido van Rossum | a759f64 | 1991-05-14 12:25:35 +0000 | [diff] [blame] | 1 | # A class to help applications that do fancy text formatting. |
| 2 | # You create an instance each time you must redraw the window. |
| 3 | # Set the initial left, top and right coordinates; |
| 4 | # then feed it words, font changes and vertical movements. |
| 5 | # |
| 6 | # This class should eventually be extended to support much fancier |
| 7 | # formatting, along the lines of TeX; for now, a very simple model |
| 8 | # is sufficient. |
| 9 | # |
Guido van Rossum | 2234bc9 | 1991-12-26 13:03:52 +0000 | [diff] [blame] | 10 | class formatter: |
Guido van Rossum | a759f64 | 1991-05-14 12:25:35 +0000 | [diff] [blame] | 11 | # |
| 12 | # Initialize a formatter instance. |
| 13 | # Pass the window's drawing object, and left, top, right |
| 14 | # coordinates of the drawing space as arguments. |
| 15 | # |
| 16 | def init(self, (d, left, top, right)): |
| 17 | self.d = d # Drawing object |
| 18 | self.left = left # Left margin |
| 19 | self.right = right # Right margin |
| 20 | self.v = top # Top of current line |
| 21 | self.center = 0 |
| 22 | self.justify = 1 |
Guido van Rossum | 14d53bf | 1991-08-16 13:24:20 +0000 | [diff] [blame] | 23 | self.setfont('') # Default font |
Guido van Rossum | a759f64 | 1991-05-14 12:25:35 +0000 | [diff] [blame] | 24 | self._reset() # Prepare for new line |
| 25 | return self |
| 26 | # |
| 27 | # Reset for start of fresh line. |
| 28 | # |
| 29 | def _reset(self): |
| 30 | self.boxes = [] # Boxes and glue still to be output |
| 31 | self.sum_width = 0 # Total width of boxes |
| 32 | self.sum_space = 0 # Total space between boxes |
| 33 | self.sum_stretch = 0 # Total stretch for space between boxes |
| 34 | self.max_ascent = 0 # Max ascent of current line |
| 35 | self.max_descent = 0 # Max descent of current line |
| 36 | self.avail_width = self.right - self.left |
| 37 | self.hang_indent = 0 |
| 38 | # |
| 39 | # Set the current font, and compute some values from it. |
| 40 | # |
| 41 | def setfont(self, font): |
| 42 | self.font = font |
| 43 | self.d.setfont(font) |
| 44 | self.font_space = self.d.textwidth(' ') |
| 45 | self.font_ascent = self.d.baseline() |
| 46 | self.font_descent = self.d.lineheight() - self.font_ascent |
| 47 | # |
| 48 | # Add a word to the list of boxes; first flush if line is full. |
| 49 | # Space and stretch factors are expressed in fractions |
| 50 | # of the current font's space width. |
| 51 | # (Two variations: one without, one with explicit stretch factor.) |
| 52 | # |
| 53 | def addword(self, (word, spacefactor)): |
| 54 | self.addwordstretch(word, spacefactor, spacefactor) |
| 55 | # |
| 56 | def addwordstretch(self, (word, spacefactor, stretchfactor)): |
| 57 | width = self.d.textwidth(word) |
| 58 | if width > self.avail_width: |
| 59 | self._flush(1) |
| 60 | space = int(float(self.font_space) * float(spacefactor)) |
| 61 | stretch = int(float(self.font_space) * float(stretchfactor)) |
| 62 | box = (self.font, word, width, space, stretch) |
| 63 | self.boxes.append(box) |
| 64 | self.sum_width = self.sum_width + width |
| 65 | self.sum_space = self.sum_space + space |
| 66 | self.sum_stretch = self.sum_stretch + stretch |
| 67 | self.max_ascent = max(self.font_ascent, self.max_ascent) |
| 68 | self.max_descent = max(self.font_descent, self.max_descent) |
| 69 | self.avail_width = self.avail_width - width - space |
| 70 | # |
| 71 | # Flush current line and start a new one. |
| 72 | # Flushing twice is harmless (i.e. does not introduce a blank line). |
| 73 | # (Two versions: the internal one has a parameter for justification.) |
| 74 | # |
| 75 | def flush(self): |
| 76 | self._flush(0) |
| 77 | # |
| 78 | def _flush(self, justify): |
| 79 | if not self.boxes: |
| 80 | return |
| 81 | # |
| 82 | # Compute amount of stretch needed. |
| 83 | # |
| 84 | if justify and self.justify or self.center: |
| 85 | # |
| 86 | # Compute extra space to fill; |
| 87 | # this is avail_width plus glue from last box. |
| 88 | # Also compute available stretch. |
| 89 | # |
| 90 | last_box = self.boxes[len(self.boxes)-1] |
| 91 | font, word, width, space, stretch = last_box |
| 92 | tot_extra = self.avail_width + space |
| 93 | tot_stretch = self.sum_stretch - stretch |
| 94 | else: |
| 95 | tot_extra = tot_stretch = 0 |
| 96 | # |
| 97 | # Output the boxes. |
| 98 | # |
| 99 | baseline = self.v + self.max_ascent |
| 100 | h = self.left + self.hang_indent |
| 101 | if self.center: |
| 102 | h = h + tot_extra / 2 |
| 103 | tot_extra = tot_stretch = 0 |
| 104 | for font, word, width, space, stretch in self.boxes: |
| 105 | self.d.setfont(font) |
| 106 | v = baseline - self.d.baseline() |
| 107 | self.d.text((h, v), word) |
| 108 | h = h + width + space |
| 109 | if tot_extra > 0 and tot_stretch > 0: |
| 110 | extra = stretch * tot_extra / tot_stretch |
| 111 | h = h + extra |
| 112 | tot_extra = tot_extra - extra |
| 113 | tot_stretch = tot_stretch - stretch |
| 114 | # |
| 115 | # Prepare for next line. |
| 116 | # |
| 117 | self.v = baseline + self.max_descent |
| 118 | self.d.setfont(self.font) |
| 119 | self._reset() |
| 120 | # |
| 121 | # Add vertical space; first flush. |
| 122 | # Vertical space is expressed in fractions of the current |
| 123 | # font's line height. |
| 124 | # |
Guido van Rossum | 14d53bf | 1991-08-16 13:24:20 +0000 | [diff] [blame] | 125 | def vspace(self, lines): |
| 126 | self.vspacepixels(int(lines * self.d.lineheight())) |
| 127 | # |
| 128 | # Add vertical space given in pixels. |
| 129 | # |
| 130 | def vspacepixels(self, dv): |
Guido van Rossum | a759f64 | 1991-05-14 12:25:35 +0000 | [diff] [blame] | 131 | self.flush() |
Guido van Rossum | 14d53bf | 1991-08-16 13:24:20 +0000 | [diff] [blame] | 132 | self.v = self.v + dv |
Guido van Rossum | a759f64 | 1991-05-14 12:25:35 +0000 | [diff] [blame] | 133 | # |
| 134 | # Set temporary (hanging) indent, for paragraph start. |
| 135 | # First flush. |
| 136 | # |
| 137 | def tempindent(self, space): |
| 138 | self.flush() |
| 139 | hang = int(float(self.font_space) * float(space)) |
| 140 | self.hang_indent = hang |
| 141 | self.avail_width = self.avail_width - hang |
| 142 | # |
| 143 | # Add (permanent) left indentation. First flush. |
| 144 | # |
| 145 | def addleftindent(self, space): |
| 146 | self.flush() |
| 147 | self.left = self.left \ |
| 148 | + int(float(self.font_space) * float(space)) |
| 149 | self._reset() |
| 150 | # |
| 151 | |
| 152 | |
| 153 | # Test procedure |
| 154 | # |
| 155 | def test(): |
Guido van Rossum | 14d53bf | 1991-08-16 13:24:20 +0000 | [diff] [blame] | 156 | import stdwin, stdwinq |
Guido van Rossum | a759f64 | 1991-05-14 12:25:35 +0000 | [diff] [blame] | 157 | from stdwinevents import * |
| 158 | try: |
| 159 | import mac |
| 160 | # Mac font assignments: |
| 161 | font1 = 'times', '', 12 |
| 162 | font2 = 'times', 'b', 14 |
Guido van Rossum | 2234bc9 | 1991-12-26 13:03:52 +0000 | [diff] [blame] | 163 | except ImportError: |
Guido van Rossum | a759f64 | 1991-05-14 12:25:35 +0000 | [diff] [blame] | 164 | # X11R4 font assignments |
| 165 | font1 = '*times-medium-r-*-120-*' |
| 166 | font2 = '*times-bold-r-*-140-*' |
| 167 | words = \ |
| 168 | ['The','quick','brown','fox','jumps','over','the','lazy','dog.'] |
| 169 | words = words * 2 |
| 170 | stage = 0 |
| 171 | stages = [(0,0,'ragged'), (1,0,'justified'), (0,1,'centered')] |
| 172 | justify, center, title = stages[stage] |
| 173 | stdwin.setdefwinsize(300,200) |
| 174 | w = stdwin.open(title) |
| 175 | winsize = w.getwinsize() |
| 176 | while 1: |
Guido van Rossum | 14d53bf | 1991-08-16 13:24:20 +0000 | [diff] [blame] | 177 | type, window, detail = stdwinq.getevent() |
Guido van Rossum | bdfcfcc | 1992-01-01 19:35:13 +0000 | [diff] [blame] | 178 | if type == WE_CLOSE: |
Guido van Rossum | a759f64 | 1991-05-14 12:25:35 +0000 | [diff] [blame] | 179 | break |
Guido van Rossum | bdfcfcc | 1992-01-01 19:35:13 +0000 | [diff] [blame] | 180 | elif type == WE_SIZE: |
Guido van Rossum | a759f64 | 1991-05-14 12:25:35 +0000 | [diff] [blame] | 181 | newsize = w.getwinsize() |
| 182 | if newsize <> winsize: |
| 183 | w.change((0,0), winsize) |
| 184 | winsize = newsize |
| 185 | w.change((0,0), winsize) |
Guido van Rossum | bdfcfcc | 1992-01-01 19:35:13 +0000 | [diff] [blame] | 186 | elif type == WE_MOUSE_DOWN: |
Guido van Rossum | a759f64 | 1991-05-14 12:25:35 +0000 | [diff] [blame] | 187 | stage = (stage + 1) % len(stages) |
| 188 | justify, center, title = stages[stage] |
| 189 | w.settitle(title) |
| 190 | w.change((0, 0), (1000, 1000)) |
Guido van Rossum | bdfcfcc | 1992-01-01 19:35:13 +0000 | [diff] [blame] | 191 | elif type == WE_DRAW: |
Guido van Rossum | a759f64 | 1991-05-14 12:25:35 +0000 | [diff] [blame] | 192 | width, height = winsize |
| 193 | f = formatter().init(w.begindrawing(), 0, 0, width) |
| 194 | f.center = center |
| 195 | f.justify = justify |
| 196 | if not center: |
| 197 | f.tempindent(5) |
| 198 | for font in font1, font2, font1: |
| 199 | f.setfont(font) |
| 200 | for word in words: |
Guido van Rossum | bdfcfcc | 1992-01-01 19:35:13 +0000 | [diff] [blame] | 201 | space = 1 + (word[-1:] == '.') |
Guido van Rossum | a759f64 | 1991-05-14 12:25:35 +0000 | [diff] [blame] | 202 | f.addword(word, space) |
| 203 | if center and space > 1: |
| 204 | f.flush() |
| 205 | f.flush() |
| 206 | height = f.v |
| 207 | del f |
| 208 | w.setdocsize(0, height) |