blob: b2d4add7e0a4e0355e41ba09fe78f1e9a18eb703 [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 #
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 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
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 Rossum14d53bf1991-08-16 13:24:20 +0000125 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 Rossuma759f641991-05-14 12:25:35 +0000131 self.flush()
Guido van Rossum14d53bf1991-08-16 13:24:20 +0000132 self.v = self.v + dv
Guido van Rossuma759f641991-05-14 12:25:35 +0000133 #
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#
155def test():
Guido van Rossum14d53bf1991-08-16 13:24:20 +0000156 import stdwin, stdwinq
Guido van Rossuma759f641991-05-14 12:25:35 +0000157 from stdwinevents import *
158 try:
159 import mac
160 # Mac font assignments:
161 font1 = 'times', '', 12
162 font2 = 'times', 'b', 14
Guido van Rossum2234bc91991-12-26 13:03:52 +0000163 except ImportError:
Guido van Rossuma759f641991-05-14 12:25:35 +0000164 # 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 Rossum14d53bf1991-08-16 13:24:20 +0000177 type, window, detail = stdwinq.getevent()
Guido van Rossumbdfcfcc1992-01-01 19:35:13 +0000178 if type == WE_CLOSE:
Guido van Rossuma759f641991-05-14 12:25:35 +0000179 break
Guido van Rossumbdfcfcc1992-01-01 19:35:13 +0000180 elif type == WE_SIZE:
Guido van Rossuma759f641991-05-14 12:25:35 +0000181 newsize = w.getwinsize()
182 if newsize <> winsize:
183 w.change((0,0), winsize)
184 winsize = newsize
185 w.change((0,0), winsize)
Guido van Rossumbdfcfcc1992-01-01 19:35:13 +0000186 elif type == WE_MOUSE_DOWN:
Guido van Rossuma759f641991-05-14 12:25:35 +0000187 stage = (stage + 1) % len(stages)
188 justify, center, title = stages[stage]
189 w.settitle(title)
190 w.change((0, 0), (1000, 1000))
Guido van Rossumbdfcfcc1992-01-01 19:35:13 +0000191 elif type == WE_DRAW:
Guido van Rossuma759f641991-05-14 12:25:35 +0000192 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 Rossumbdfcfcc1992-01-01 19:35:13 +0000201 space = 1 + (word[-1:] == '.')
Guido van Rossuma759f641991-05-14 12:25:35 +0000202 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)