Source for: original.py [raw]
1import svgwrite
2
3
4class Box():
5
6 def __init__(
7 self, x=0, y=0, w=1, h=1, red=False, blue=False, yellow=False, letter=None
8 ):
9 self.x = x
10 self.y = y
11 self.w = w
12 self.h = h
13 self.red = red
14 self.blue = blue
15 self.yellow = yellow
16 self.letter = letter
17
18 def __str__(self):
19 return self.letter or 'Box'
20
21 def __repr__(self):
22 return self.letter or 'Box'
23
24
25big_box = Box(0, 0, 80, 1000)
26pages = [big_box]
27
28
29def draw_boxes(boxes, with_boxes=True):
30 STYLES = """
31 .red { fill: red;}
32 .green { fill: green;}
33 .blue {fill: blue;}
34 .yellow {fill: yellow;}
35 .bigbox {fill: cyan; }
36 """
37 dwg = svgwrite.Drawing(
38 'test.svg', profile='full', size=('%d' % big_box.w, '%d' % big_box.w)
39 )
40 dwg.defs.add(dwg.style(STYLES))
41 for bb in pages:
42 dwg.add(dwg.rect(insert=(bb.x, bb.y), size=(bb.w, bb.h), class_='bigbox'))
43 for box in boxes:
44 if with_boxes:
45 if box.red:
46 dwg.add(
47 dwg.rect(insert=(box.x, box.y), size=(box.w, box.h), class_='red')
48 )
49 elif box.blue:
50 dwg.add(
51 dwg.rect(insert=(box.x, box.y), size=(box.w, box.h), class_='blue')
52 )
53 elif box.yellow:
54 dwg.add(
55 dwg.rect(
56 insert=(box.x, box.y), size=(box.w, box.h), class_='yellow'
57 )
58 )
59 else:
60 dwg.add(
61 dwg.rect(insert=(box.x, box.y), size=(box.w, box.h), class_='green')
62 )
63 if box.letter:
64 dwg.add(
65 dwg.text(
66 box.letter,
67 insert=(box.x, box.y + box.h),
68 font_size=box.h,
69 font_family='Arial',
70 )
71 )
72 dwg.save()
73
74
75# So, how could we layout the many boxes inside the big_box?
76many_boxes = [Box(0, 0, 1, 1) for _ in range(5000)]
77
78# Do nothing ...
79# They are all in the same place
80# draw_boxes(many_boxes)
81
82# Try to lay them out side by side
83# They just go too wide
84# We add a "separation" constant so you can see the boxes individually
85separation = .2
86
87
88def layout1(boxes):
89 for i, box in enumerate(boxes):
90 box.x = i * (1 + separation)
91
92
93# Put each one next to the other until they reach a width,
94# then go down. This is a monospaced layout.
95
96
97def layout2(boxes):
98 for i, box in enumerate(boxes[1:]):
99 box.x = boxes[i - 1].x + 1 + separation
100 box.y = boxes[i - 1].y
101 if box.x > big_box.w:
102 box.x = 0
103 box.y = boxes[i - 1].y + 1.1
104
105
106# Now, what happens if some boxes are wider than the others?
107# layout2 will make them overlap or have wide breaks between them
108import random
109
110many_boxes = [Box(0, 0, 1 + random.randint(-2, 2) / 10, 1) for _ in range(5000)]
111
112# So, we use each box's width instead of a fixed width
113
114
115def layout3(boxes):
116 for i, box in enumerate(boxes[1:], 1):
117 prev_box = boxes[i - 1]
118 box.x = prev_box.x + prev_box.w + separation
119 box.y = prev_box.y
120 if box.x > big_box.w:
121 box.x = 0
122 box.y = prev_box.y + 1.1
123
124
125# layout3(many_boxes)
126# But the right side is ragged
127
128# We can, when we break, put the remaining space spread between boxes
129# This is a "justified" layout
130
131
132def layout4(boxes):
133 last_break = 0
134 for i, box in enumerate(boxes[1:], 1):
135 prev_box = boxes[i - 1]
136 box.x = prev_box.x + prev_box.w + separation
137 box.y = prev_box.y
138 if box.x > big_box.w:
139 box.x = 0
140 box.y = prev_box.y + 1.1
141 slack = big_box.w - (prev_box.x + prev_box.w)
142 mini_slack = slack / (i - last_break)
143 for j, b in enumerate(boxes[last_break:i]):
144 b.x += j * mini_slack
145 last_break = i
146
147
148# layout4(many_boxes)
149
150# But what happens if some boxes are red?
151for box in many_boxes:
152 if random.randint(1, 6) > 5:
153 box.red = True
154# Well, nothing much, other than they are red
155
156# But what if red means "stretchy" and only those can stretch?
157
158
159def layout5(boxes):
160 last_break = 0
161 for i, box in enumerate(boxes[1:], 1):
162 prev_box = boxes[i - 1]
163 box.x = prev_box.x + prev_box.w + separation
164 box.y = prev_box.y
165 if box.x > big_box.w:
166 box.x = 0
167 box.y = prev_box.y + 1.1
168 slack = big_box.w - (prev_box.x + prev_box.w)
169 row = boxes[last_break:i]
170 reds = [b for b in row if b.red]
171 # sometimes there is no red in the row. Do nothing.
172 if reds:
173 mini_slack = slack / len(reds)
174 for b in reds:
175 b.w += mini_slack
176 for j, b in enumerate(row[1:], 1):
177 b.x = row[j - 1].x + row[j - 1].w + separation
178 last_break = i
179
180
181# But what happens if a few are blue?
182for box in many_boxes:
183 if random.randint(1, 150) > 149:
184 box.blue = True
185
186# Well, nothing much, other than they are blue
187
188# But what if blue means "this row ends here"?
189
190
191def layout6(boxes):
192 last_break = 0
193 for i, box in enumerate(boxes[1:], 1):
194 prev_box = boxes[i - 1]
195 box.x = prev_box.x + prev_box.w + separation
196 box.y = prev_box.y
197 if prev_box.blue or box.x > big_box.w:
198 box.x = 0
199 box.y = prev_box.y + 1.1
200 slack = big_box.w - (prev_box.x + prev_box.w)
201 row = boxes[last_break:i]
202 reds = [b for b in row if b.red]
203 # sometimes there is no red in the row. Do nothing.
204 if reds:
205 mini_slack = slack / len(reds)
206 for b in reds:
207 b.w += mini_slack
208 for j, b in enumerate(row[1:], 1):
209 b.x = row[j - 1].x + row[j - 1].w + separation
210 last_break = i
211
212
213# Some reds get reeeeeeealy stretchy! That is because rows that
214# end because of a blue have very few boxes. So maybe we don't stretch those?
215
216
217def layout7(boxes):
218 last_break = 0
219 for i, box in enumerate(boxes[1:], 1):
220 prev_box = boxes[i - 1]
221 box.x = prev_box.x + prev_box.w + separation
222 box.y = prev_box.y
223 if prev_box.blue or box.x > big_box.w:
224 box.x = 0
225 box.y = prev_box.y + 1.1
226 if not prev_box.blue:
227 row = boxes[last_break:i]
228 slack = big_box.w - (row[-1].x + row[-1].w)
229 reds = [b for b in row if b.red]
230 # sometimes there is no red in the row. Do nothing.
231 if reds:
232 mini_slack = slack / len(reds)
233 for b in reds:
234 b.w += mini_slack
235 for j, b in enumerate(row[1:], 1):
236 b.x = row[j - 1].x + row[j - 1].w + separation
237 last_break = i
238
239
240# What if we want blue boxes break lines but also separate lines a little?
241
242
243def layout8(boxes):
244 last_break = 0
245 for i, box in enumerate(boxes[1:], 1):
246 prev_box = boxes[i - 1]
247 box.x = prev_box.x + prev_box.w + separation
248 box.y = prev_box.y
249 if prev_box.blue or box.x > big_box.w:
250 box.x = 0
251 if prev_box.blue:
252 box.y = prev_box.y + 2.1
253 else: # not blue
254 box.y = prev_box.y + 1.1
255 row = boxes[last_break:i]
256 slack = big_box.w - (row[-1].x + row[-1].w)
257 reds = [b for b in row if b.red]
258 # sometimes there is no red in the row. Do nothing.
259 if reds:
260 mini_slack = slack / len(reds)
261 for b in reds:
262 b.w += mini_slack
263 for j, b in enumerate(row[1:], 1):
264 b.x = row[j - 1].x + row[j - 1].w + separation
265 last_break = i
266
267
268# So ... what if each box has a letter or a space inside it?
269for box in many_boxes:
270 # More than one space so they appear often
271 box.letter = random.choice(' abcdefghijklmnopqrstuvwxyz')
272
273# Maybe we should make the box sizes depend on the letter inside it?
274# This is complicated, sorry
275import harfbuzz as hb
276import freetype2 as ft
277
278
279def adjust_widths_by_letter(boxes):
280 buf = hb.Buffer.create()
281 buf.add_str(''.join(b.letter for b in boxes))
282 buf.guess_segment_properties()
283 font_lib = ft.get_default_lib()
284 face = font_lib.find_face('Arial')
285 face.set_char_size(size=1, resolution=64)
286 font = hb.Font.ft_create(face)
287 hb.shape(font, buf)
288 # at this point buf.glyph_positions has all the data we need
289 for box, position in zip(boxes, buf.glyph_positions):
290 box.w = position.x_advance
291
292
293adjust_widths_by_letter(many_boxes)
294# layout8(many_boxes)
295
296# There is all that space between letters we added when they were boxes. Let's remove it.
297
298separation = 0
299layout8(many_boxes)
300
301# How about we get the letters from a text instead of randomly?
302p_and_p = open('pride-and-prejudice.txt').read()
303text_boxes = []
304for l in p_and_p:
305 text_boxes.append(Box(letter=l))
306adjust_widths_by_letter(text_boxes)
307# layout8(text_boxes)
308# Oh, it's all green now, and it's all one thing after another. We should make newlines blue!
309
310for b in text_boxes:
311 if b.letter == '\n':
312 b.blue = True
313# layout8(text_boxes)
314
315# Better, but newlines should not really take any space should they?
316
317
318def add_blue(boxes):
319 for b in boxes:
320 if b.letter == '\n':
321 b.blue = True
322 b.w = 0
323
324
325add_blue(text_boxes)
326# layout8(text_boxes)
327
328# Our big_box is very wide now, that is why we have long lines. Let's make it narrower
329big_box = Box(0, 0, 30, 1000)
330# layout8(text_boxes)
331
332# But our right side is ragged again! We should make spaces red.
333
334
335def add_red(boxes):
336 for b in boxes:
337 if b.letter == ' ':
338 b.red = True
339
340
341add_red(text_boxes)
342# layout8(text_boxes)
343
344# The second paragraph of Chapter 1 shows a red space as first thing in the row, and that looks bad!
345# So, when the 1st letter in a row is a space, let's make it take no width and not stretch
346
347
348def layout9(boxes):
349 last_break = 0
350 for i, box in enumerate(boxes[1:], 1):
351 prev_box = boxes[i - 1]
352 box.x = prev_box.x + prev_box.w + separation
353 box.y = prev_box.y
354 if prev_box.blue or box.x > big_box.w:
355 box.x = 0
356 if box.red:
357 box.w = 0
358 if prev_box.blue:
359 box.y = prev_box.y + 2.1
360 else: # not blue
361 box.y = prev_box.y + 1.1
362 row = boxes[last_break:i]
363 slack = big_box.w - (row[-1].x + row[-1].w)
364 # If the 1st thing is a red, that one doesn't stretch
365 reds = [b for b in row[1:] if b.red]
366 # sometimes there is no red in the row. Do nothing.
367 if reds:
368 mini_slack = slack / len(reds)
369 for b in reds:
370 b.w += mini_slack
371 for j, b in enumerate(row[1:], 1):
372 b.x = row[j - 1].x + row[j - 1].w + separation
373 last_break = i
374
375
376# Just for fun, let's draw it without the colored boxes
377# layout9(text_boxes)
378# draw_boxes(text_boxes, False)
379
380# XXX layout9 rewritten using pop(), need to backport this version
381
382
383def layout10(_boxes):
384 boxes = _boxes[:] # Work on a copy
385 prev_box = boxes.pop(0)
386 row = [prev_box]
387 while (boxes):
388 box = boxes.pop(0)
389 row.append(box)
390 box.x = prev_box.x + prev_box.w + separation
391 box.y = prev_box.y
392 if prev_box.blue or box.x > big_box.w:
393 box.x = 0
394 row.pop() # our box will go in the next row
395 if box.red:
396 box.w = 0
397 if prev_box.blue:
398 box.y = prev_box.y + 2.1
399
400 else: # not blue
401 box.y = prev_box.y + 1.1
402 slack = big_box.w - (row[-1].x + row[-1].w)
403 # If the 1st thing is a red, that one doesn't stretch
404 reds = [b for b in row[1:] if b.red]
405 # sometimes there is no red in the row. Do nothing.
406 if reds:
407 mini_slack = slack / len(reds)
408 for b in reds:
409 b.w += mini_slack
410 for j, b in enumerate(row[1:], 1):
411 b.x = row[j - 1].x + row[j - 1].w + separation
412 row = [box]
413 prev_box = box
414
415
416# layout10(text_boxes)
417
418# Looks good, except that the words are broken wrong. You can't break good like: g
419# ood!
420
421# What if we only break on spaces?
422
423
424def layout11(_boxes):
425 boxes = _boxes[:] # Work on a copy
426 prev_box = boxes.pop(0)
427 row = [prev_box]
428 while (boxes):
429 box = boxes.pop(0)
430 row.append(box)
431 box.x = prev_box.x + prev_box.w + separation
432 box.y = prev_box.y
433 if prev_box.blue or (box.x > big_box.w and box.red):
434 row.pop() # our box will go in the next row
435 box.x = 0
436 if box.red:
437 box.w = 0
438 if prev_box.blue:
439 box.y = prev_box.y + 2.1
440
441 else: # not blue
442 box.y = prev_box.y + 1.1
443 slack = big_box.w - (row[-1].x + row[-1].w)
444 # If the 1st thing is a red, that one doesn't stretch
445 reds = [b for b in row[1:] if b.red]
446 # sometimes there is no red in the row. Do nothing.
447 if reds:
448 mini_slack = slack / len(reds)
449 for b in reds:
450 b.w += mini_slack
451 for j, b in enumerate(row[1:], 1):
452 b.x = row[j - 1].x + row[j - 1].w + separation
453 row = [box]
454 prev_box = box
455
456
457# layout11(text_boxes)
458
459# That actually ... worked? Except that when we need to fit something wider than
460# big_box because we did not break the row, the slack is NEGATIVE and words get
461# smushed together!
462
463# What we actually need is hyphenation.
464# We can use pyphen to insert soft-hyphen characters wherever words can break.
465# And we can mark those positions yellow.
466
467import pyphen
468
469hyphenator = pyphen.Pyphen(lang='en_GB') # These things are language dependent
470
471p_and_p = open('pride-and-prejudice.txt').readlines()
472for i, l in enumerate(p_and_p):
473 words = l.split(' ')
474 p_and_p[i] = ' '.join(hyphenator.inserted(w, '\u00AD') for w in words)
475p_and_p = ''.join(p_and_p)
476
477text_boxes = []
478for l in p_and_p:
479 text_boxes.append(Box(letter=l))
480
481
482# This makes the characters '\u00AD' (soft-hyphen) yellow.
483
484
485def add_yellow(boxes):
486 for b in boxes:
487 if b.letter == '\u00AD':
488 b.yellow = True
489
490
491add_blue(text_boxes)
492add_red(text_boxes)
493add_yellow(text_boxes)
494adjust_widths_by_letter(text_boxes)
495
496# And create a new layout function that also breaks on yellow boxes.
497
498
499def layout12(_boxes):
500 boxes = _boxes[:] # Work on a copy
501 prev_box = boxes.pop(0)
502 row = [prev_box]
503 while (boxes):
504 box = boxes.pop(0)
505 row.append(box)
506 box.x = prev_box.x + prev_box.w + separation
507 box.y = prev_box.y
508 if prev_box.blue or (box.x > big_box.w and (box.red or box.yellow)):
509 row.pop() # our box will go in the next row
510 box.x = 0
511 if box.red:
512 box.w = 0
513 if prev_box.blue:
514 box.y = prev_box.y + 2.1
515
516 else: # not blue
517 box.y = prev_box.y + 1.1
518 slack = big_box.w - (row[-1].x + row[-1].w)
519 # If the 1st thing is a red, that one doesn't stretch
520 reds = [b for b in row[1:] if b.red]
521 # sometimes there is no red in the row. Do nothing.
522 if reds:
523 mini_slack = slack / len(reds)
524 for b in reds:
525 b.w += mini_slack
526 for j, b in enumerate(row[1:], 1):
527 b.x = row[j - 1].x + row[j - 1].w + separation
528 row = [box]
529 prev_box = box
530
531
532# layout12(text_boxes)
533
534# Better! Since we have more break chances, there is less word-smushing.
535# However our typography is wrong, because we are hyphenating but not showing a hyphen!
536# So, we need to ADD a box when we hyphenate. That special box is hyphenbox():
537
538
539def hyphenbox(): # Yes, this is not optimal
540 b = Box(letter='-', yellow=True)
541 adjust_widths_by_letter([b])
542 return b
543
544
545def layout13(_boxes):
546 boxes = _boxes[:] # Work on a copy
547 prev_box = boxes.pop(0)
548 row = [prev_box]
549 while (boxes):
550 box = boxes.pop(0)
551 row.append(box)
552 box.x = prev_box.x + prev_box.w + separation
553 box.y = prev_box.y
554 if prev_box.blue or (box.x > big_box.w and (box.red or box.yellow)):
555 row.pop() # our box will go in the next row
556 if box.yellow: # We need to insert the hyphen!
557 h_b = hyphenbox()
558 h_b.x = prev_box.x + prev_box.w + separation
559 h_b.y = prev_box.y
560 _boxes.append(h_b) # So it's drawn
561 row.append(h_b) # So it's justified
562 box.x = 0
563 if box.red:
564 box.w = 0
565 if prev_box.blue:
566 box.y = prev_box.y + 2.1
567
568 else: # not blue
569 box.y = prev_box.y + 1.1
570 slack = big_box.w - (row[-1].x + row[-1].w)
571 # If the 1st thing is a red, that one doesn't stretch
572 reds = [b for b in row[1:] if b.red]
573 # sometimes there is no red in the row. Do nothing.
574 if reds:
575 mini_slack = slack / len(reds)
576 for b in reds:
577 b.w += mini_slack
578 for j, b in enumerate(row[1:], 1):
579 b.x = row[j - 1].x + row[j - 1].w + separation
580 row = [box]
581 prev_box = box
582
583
584# layout13(text_boxes)
585
586# Good. However, we still have smushing. It's usually considered better to make spaces
587# between words grow, rather than shrink. So, what we should do is, instead of breaking
588# in the 1st yellow/red PAST the break, go back and break in the last BEFORE the break!
589
590# XXX backport simplifications from layout14: separation of blue case
591
592
593def layout14(_boxes):
594 boxes = _boxes[:] # Work on a copy
595 row = [boxes.pop(0)]
596 while (boxes):
597 prev_box = row[-1]
598 box = boxes.pop(0)
599 row.append(box)
600 box.x = prev_box.x + prev_box.w + separation
601 box.y = prev_box.y
602 if prev_box.blue:
603 box.x = 0
604 box.y = prev_box.y + 2.1
605 row.pop()
606 row = [box]
607
608 elif box.x > big_box.w:
609 while not (box.red or box.yellow): # backtrack onw
610 boxes.insert(0, row.pop())
611 # tip of row is now box, previous one is prev_box
612 box = row[-1]
613 prev_box = row[-2]
614
615 row.pop() # Breaking box goes in next row
616 if box.yellow: # We need to insert the hyphen!
617 h_b = hyphenbox()
618 h_b.x = prev_box.x + prev_box.w + separation
619 h_b.y = prev_box.y
620 _boxes.append(h_b) # So it's drawn
621 row.append(h_b) # So it's justified
622 box.x = 0
623 box.y = prev_box.y + 1.1
624 if box.red:
625 box.w = 0
626 slack = big_box.w - (row[-1].x + row[-1].w)
627 # If the 1st thing is a red, that one doesn't stretch
628 reds = [b for b in row[1:] if b.red]
629 # sometimes there is no red in the row. Do nothing.
630 if reds:
631 mini_slack = slack / len(reds)
632 for b in reds:
633 b.w += mini_slack
634 for j, b in enumerate(row[1:], 1):
635 b.x = row[j - 1].x + row[j - 1].w + separation
636 row = [box]
637
638
639# With a little separation for niceness, the result is not half bad!
640separation = 0.05
641# layout14(text_boxes)
642
643# So far, our big_box has been very, very tall. What would happen if it were short?
644big_box = Box(0, 0, 30, 60)
645# layout14(text_boxes)
646
647# Well, that our text flows straight out of the box! And our initial problem was
648# "So, how could we layout the many boxes inside the big_box?" !
649# That won't do. Clearly, if we have more text than fits in a box, we need more than one
650# box. Like pages.
651
652# So, let's make many big boxes.
653
654
655pages = [Box(0, i * 65, 30, 60) for i in range(50)]
656
657# layout14(text_boxes)
658
659# Of course our layout engine does nothing with those pages. Let's fix that.
660
661
662def layout15(_boxes):
663 boxes = _boxes[:] # Work on a copy
664 row = [boxes.pop(0)]
665
666 # Put the 1st box in the beginning of the 1st page
667 page = 0
668 row[0].x = pages[page].x
669 row[0].y = pages[page].y
670
671 while (boxes):
672 prev_box = row[-1]
673 box = boxes.pop(0)
674 row.append(box)
675 box.x = prev_box.x + prev_box.w + separation
676 box.y = prev_box.y
677 if prev_box.blue:
678 box.x = 0
679 box.y = prev_box.y + 2.1
680 row.pop()
681 row = [box]
682
683 elif box.x > pages[page].w:
684 while not (box.red or box.yellow): # backtrack onw
685 boxes.insert(0, row.pop())
686 # tip of row is now box, previous one is prev_box
687 box = row[-1]
688 prev_box = row[-2]
689
690 row.pop() # Breaking box goes in next row
691 if box.yellow: # We need to insert the hyphen!
692 h_b = hyphenbox()
693 h_b.x = prev_box.x + prev_box.w + separation
694 h_b.y = prev_box.y
695 _boxes.append(h_b) # So it's drawn
696 row.append(h_b) # So it's justified
697 box.x = 0
698 box.y = prev_box.y + 1.1
699 if box.red:
700 box.w = 0
701 slack = pages[page].w - (row[-1].x + row[-1].w)
702 # If the 1st thing is a red, that one doesn't stretch
703 reds = [b for b in row[1:] if b.red]
704 # sometimes there is no red in the row. Do nothing.
705 if reds:
706 mini_slack = slack / len(reds)
707 for b in reds:
708 b.w += mini_slack
709 for j, b in enumerate(row[1:], 1):
710 b.x = row[j - 1].x + row[j - 1].w + separation
711 row = [box]
712
713 if box.y + box.h > pages[page].y + pages[page].h:
714 # We need to go to the next page
715 page = page + 1
716 box.y = pages[page].y
717
718
719# layout15(text_boxes)
720# And there you go, pagination
721
722# What happens if pages are organized differently?
723# Let's put them left-to-right
724pages = [Box(i * 35, 0, 30, 60) for i in range(50)]
725# layout15(text_boxes)
726
727# Everything is in the same page, because we are resetting x to 0 on line breaks.
728# You always need to find your hidden assumptions.
729
730
731def layout16(_boxes):
732 boxes = _boxes[:] # Work on a copy
733 row = [boxes.pop(0)]
734
735 # Put the 1st box in the beginning of the 1st page
736 page = 0
737 row[0].x = pages[page].x
738 row[0].y = pages[page].y
739
740 while (boxes):
741 prev_box = row[-1]
742 box = boxes.pop(0)
743 row.append(box)
744 box.x = prev_box.x + prev_box.w + separation
745 box.y = prev_box.y
746 if prev_box.blue:
747 box.x = pages[page].x
748 box.y = prev_box.y + 2.1
749 row.pop()
750 row = [box]
751
752 elif box.x > pages[page].w + pages[page].x:
753 while not (box.red or box.yellow): # backtrack onw
754 boxes.insert(0, row.pop())
755 # tip of row is now box, previous one is prev_box
756 box = row[-1]
757 prev_box = row[-2]
758
759 row.pop() # Breaking box goes in next row
760 if box.yellow: # We need to insert the hyphen!
761 h_b = hyphenbox()
762 h_b.x = prev_box.x + prev_box.w + separation
763 h_b.y = prev_box.y
764 _boxes.append(h_b) # So it's drawn
765 row.append(h_b) # So it's justified
766 box.x = pages[page].x
767 box.y = prev_box.y + 1.1
768 if box.red:
769 box.w = 0
770 slack = (pages[page].w + pages[page].x) - (row[-1].x + row[-1].w)
771 # If the 1st thing is a red, that one doesn't stretch
772 reds = [b for b in row[1:] if b.red]
773 # sometimes there is no red in the row. Do nothing.
774 if reds:
775 mini_slack = slack / len(reds)
776 for b in reds:
777 b.w += mini_slack
778 for j, b in enumerate(row[1:], 1):
779 b.x = row[j - 1].x + row[j - 1].w + separation
780 row = [box]
781
782 # We may need to go to the next page
783 if box.y + box.h > pages[page].y + pages[page].h:
784 page = page + 1
785 # Since the location of a page is arbitrary, both coordinates are reset
786 box.y = pages[page].y
787 box.x = pages[page].x
788
789
790# This may even work with oddly-sized pages!
791pages = [Box(i * 35, 0, 20, 60 + random.randint(-20, 20)) for i in range(50)]
792
793layout16(text_boxes)
794draw_boxes(text_boxes, False)
795
796# This is a good point to STOP. Look at the code, and clean house.
797