blob: 7ddfc1d6806253dabe004060fef0ee3e3d06721c [file] [log] [blame]
Guido van Rossuma759f641991-05-14 12:25:35 +00001# 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 Rossum2234bc91991-12-26 13:03:52 +000010class formatter:
Guido van Rossuma759f641991-05-14 12:25:35 +000011 #
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 #
Guido van Rossum7bc817d1993-12-17 15:25:27 +000016 def __init__(self, d, left, top, right):
Guido van Rossuma759f641991-05-14 12:25:35 +000017 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 Rossum14d53bf1991-08-16 13:24:20 +000023 self.setfont('') # Default font
Guido van Rossuma759f641991-05-14 12:25:35 +000024 self._reset() # Prepare for new line
Guido van Rossuma759f641991-05-14 12:25:35 +000025 #
26 # Reset for start of fresh line.
27 #
28 def _reset(self):
29 self.boxes = [] # Boxes and glue still to be output
30 self.sum_width = 0 # Total width of boxes
31 self.sum_space = 0 # Total space between boxes
32 self.sum_stretch = 0 # Total stretch for space between boxes
33 self.max_ascent = 0 # Max ascent of current line
34 self.max_descent = 0 # Max descent of current line
35 self.avail_width = self.right - self.left
36 self.hang_indent = 0
37 #
38 # Set the current font, and compute some values from it.
39 #
40 def setfont(self, font):
41 self.font = font
42 self.d.setfont(font)
43 self.font_space = self.d.textwidth(' ')
44 self.font_ascent = self.d.baseline()
45 self.font_descent = self.d.lineheight() - self.font_ascent
46 #
47 # Add a word to the list of boxes; first flush if line is full.
48 # Space and stretch factors are expressed in fractions
49 # of the current font's space width.
50 # (Two variations: one without, one with explicit stretch factor.)
51 #
Guido van Rossum89a78691992-12-14 12:57:56 +000052 def addword(self, word, spacefactor):
Guido van Rossuma759f641991-05-14 12:25:35 +000053 self.addwordstretch(word, spacefactor, spacefactor)
54 #
Guido van Rossum89a78691992-12-14 12:57:56 +000055 def addwordstretch(self, word, spacefactor, stretchfactor):
Guido van Rossuma759f641991-05-14 12:25:35 +000056 width = self.d.textwidth(word)
57 if width > self.avail_width:
58 self._flush(1)
59 space = int(float(self.font_space) * float(spacefactor))
60 stretch = int(float(self.font_space) * float(stretchfactor))
61 box = (self.font, word, width, space, stretch)
62 self.boxes.append(box)
63 self.sum_width = self.sum_width + width
64 self.sum_space = self.sum_space + space
65 self.sum_stretch = self.sum_stretch + stretch
66 self.max_ascent = max(self.font_ascent, self.max_ascent)
67 self.max_descent = max(self.font_descent, self.max_descent)
68 self.avail_width = self.avail_width - width - space
69 #
70 # Flush current line and start a new one.
71 # Flushing twice is harmless (i.e. does not introduce a blank line).
72 # (Two versions: the internal one has a parameter for justification.)
73 #
74 def flush(self):
75 self._flush(0)
76 #
77 def _flush(self, justify):
78 if not self.boxes:
79 return
80 #
81 # Compute amount of stretch needed.
82 #
83 if justify and self.justify or self.center:
84 #
85 # Compute extra space to fill;
86 # this is avail_width plus glue from last box.
87 # Also compute available stretch.
88 #
89 last_box = self.boxes[len(self.boxes)-1]
90 font, word, width, space, stretch = last_box
91 tot_extra = self.avail_width + space
92 tot_stretch = self.sum_stretch - stretch
93 else:
94 tot_extra = tot_stretch = 0
95 #
96 # Output the boxes.
97 #
98 baseline = self.v + self.max_ascent
99 h = self.left + self.hang_indent
100 if self.center:
101 h = h + tot_extra / 2
102 tot_extra = tot_stretch = 0
103 for font, word, width, space, stretch in self.boxes:
104 self.d.setfont(font)
105 v = baseline - self.d.baseline()
106 self.d.text((h, v), word)
107 h = h + width + space
108 if tot_extra > 0 and tot_stretch > 0:
109 extra = stretch * tot_extra / tot_stretch
110 h = h + extra
111 tot_extra = tot_extra - extra
112 tot_stretch = tot_stretch - stretch
113 #
114 # Prepare for next line.
115 #
116 self.v = baseline + self.max_descent
117 self.d.setfont(self.font)
118 self._reset()
119 #
120 # Add vertical space; first flush.
121 # Vertical space is expressed in fractions of the current
122 # font's line height.
123 #
Guido van Rossum14d53bf1991-08-16 13:24:20 +0000124 def vspace(self, lines):
125 self.vspacepixels(int(lines * self.d.lineheight()))
126 #
127 # Add vertical space given in pixels.
128 #
129 def vspacepixels(self, dv):
Guido van Rossuma759f641991-05-14 12:25:35 +0000130 self.flush()
Guido van Rossum14d53bf1991-08-16 13:24:20 +0000131 self.v = self.v + dv
Guido van Rossuma759f641991-05-14 12:25:35 +0000132 #
133 # Set temporary (hanging) indent, for paragraph start.
134 # First flush.
135 #
136 def tempindent(self, space):
137 self.flush()
138 hang = int(float(self.font_space) * float(space))
139 self.hang_indent = hang
140 self.avail_width = self.avail_width - hang
141 #
142 # Add (permanent) left indentation. First flush.
143 #
144 def addleftindent(self, space):
145 self.flush()
146 self.left = self.left \
147 + int(float(self.font_space) * float(space))
148 self._reset()
149 #
150
151
152# Test procedure
153#
154def test():
Guido van Rossum14d53bf1991-08-16 13:24:20 +0000155 import stdwin, stdwinq
Guido van Rossuma759f641991-05-14 12:25:35 +0000156 from stdwinevents import *
157 try:
158 import mac
159 # Mac font assignments:
160 font1 = 'times', '', 12
161 font2 = 'times', 'b', 14
Guido van Rossum2234bc91991-12-26 13:03:52 +0000162 except ImportError:
Guido van Rossuma759f641991-05-14 12:25:35 +0000163 # X11R4 font assignments
164 font1 = '*times-medium-r-*-120-*'
165 font2 = '*times-bold-r-*-140-*'
166 words = \
167 ['The','quick','brown','fox','jumps','over','the','lazy','dog.']
168 words = words * 2
169 stage = 0
170 stages = [(0,0,'ragged'), (1,0,'justified'), (0,1,'centered')]
171 justify, center, title = stages[stage]
172 stdwin.setdefwinsize(300,200)
173 w = stdwin.open(title)
174 winsize = w.getwinsize()
175 while 1:
Guido van Rossum14d53bf1991-08-16 13:24:20 +0000176 type, window, detail = stdwinq.getevent()
Guido van Rossumbdfcfcc1992-01-01 19:35:13 +0000177 if type == WE_CLOSE:
Guido van Rossuma759f641991-05-14 12:25:35 +0000178 break
Guido van Rossumbdfcfcc1992-01-01 19:35:13 +0000179 elif type == WE_SIZE:
Guido van Rossuma759f641991-05-14 12:25:35 +0000180 newsize = w.getwinsize()
181 if newsize <> winsize:
182 w.change((0,0), winsize)
183 winsize = newsize
184 w.change((0,0), winsize)
Guido van Rossumbdfcfcc1992-01-01 19:35:13 +0000185 elif type == WE_MOUSE_DOWN:
Guido van Rossuma759f641991-05-14 12:25:35 +0000186 stage = (stage + 1) % len(stages)
187 justify, center, title = stages[stage]
188 w.settitle(title)
189 w.change((0, 0), (1000, 1000))
Guido van Rossumbdfcfcc1992-01-01 19:35:13 +0000190 elif type == WE_DRAW:
Guido van Rossuma759f641991-05-14 12:25:35 +0000191 width, height = winsize
Guido van Rossum7bc817d1993-12-17 15:25:27 +0000192 f = formatter(w.begindrawing(), 0, 0, width)
Guido van Rossuma759f641991-05-14 12:25:35 +0000193 f.center = center
194 f.justify = justify
195 if not center:
196 f.tempindent(5)
197 for font in font1, font2, font1:
198 f.setfont(font)
199 for word in words:
Guido van Rossumbdfcfcc1992-01-01 19:35:13 +0000200 space = 1 + (word[-1:] == '.')
Guido van Rossuma759f641991-05-14 12:25:35 +0000201 f.addword(word, space)
202 if center and space > 1:
203 f.flush()
204 f.flush()
205 height = f.v
206 del f
207 w.setdocsize(0, height)