Source for: lesson9.py [raw]

  1from fonts import adjust_widths_by_letter
  2
  3
  4class Box():
  5
  6    def __init__(self, x=0, y=0, w=1, h=1, stretchy=False, letter="x"):
  7        """Accept arguments to define our box, and store them."""
  8        self.x = x
  9        self.y = y
 10        self.w = w
 11        self.h = h
 12        self.stretchy = stretchy
 13        self.letter = letter
 14
 15    def __repr__(self):
 16        return 'Box(%s, %s, %s, %s, "%s")' % (
 17            self.x, self.y, self.w, self.h, self.letter
 18        )
 19
 20
 21p_and_p = open("pride-and-prejudice.txt").read()
 22text_boxes = []
 23for l in p_and_p:
 24    text_boxes.append(Box(letter=l, stretchy=l == " "))
 25adjust_widths_by_letter(text_boxes)
 26
 27# A few pages all the same size
 28pages = [Box(i * 35, 0, 30, 50) for i in range(10)]
 29
 30# We add a "separation" constant so you can see the boxes individually
 31separation = .05
 32
 33
 34def layout(_boxes):
 35    """Layout boxes along pages.
 36
 37    Keep in mind that this function modifies the boxes themselves, so
 38    you should be very careful about trying to call layout() more than once
 39    on the same boxes.
 40
 41    Specifically, some spaces will become 0-width and not stretchy.
 42    """
 43
 44    # Because we modify the box list, we will work on a copy
 45    boxes = _boxes[:]
 46    # We start at page 0
 47    page = 0
 48    # The 1st box should be placed in the correct page
 49    previous = boxes.pop(0)
 50    previous.x = pages[page].x
 51    previous.y = pages[page].y
 52    row = []
 53    while boxes:
 54        # We take the new 1st box
 55        box = boxes.pop(0)
 56        # And put it next to the other
 57        box.x = previous.x + previous.w + separation
 58        # At the same vertical location
 59        box.y = previous.y
 60
 61        # Handle breaking on newlines
 62        break_line = False
 63        # But if it's a newline
 64        if (box.letter == "\n"):
 65            break_line = True
 66            # Newlines take no horizontal space ever
 67            box.w = 0
 68            box.stretchy = False
 69
 70        # Or if it's too far to the right...
 71        elif (box.x + box.w) > (pages[page].x + pages[page].w):
 72            break_line = True
 73            # We adjust the row
 74            # Remove all right-margin spaces
 75            while row[-1].letter == " ":
 76                row.pop()
 77            slack = (pages[page].x + pages[page].w) - (
 78                row[-1].x + row[-1].w
 79            )
 80            # Get a list of all the ones that are stretchy
 81            stretchies = [b for b in row if b.stretchy]
 82            if not stretchies:  # Nothing stretches do as before.
 83                bump = slack / len(row)
 84                # The 1st box gets 0 bumps, the 2nd gets 1 and so on
 85                for i, b in enumerate(row):
 86                    b.x += bump * i
 87            else:
 88                bump = slack / len(stretchies)
 89                # Each stretchy gets wider
 90                for b in stretchies:
 91                    b.w += bump
 92                # And we put each thing next to the previous one
 93                for j, b in enumerate(row[1:], 1):
 94                    b.x = row[j - 1].x + row[j - 1].w + separation
 95
 96        if break_line:
 97            # We start a new row
 98            row = []
 99            # We go all the way left and a little down
100            box.x = pages[page].x
101            box.y = previous.y + previous.h + separation
102
103        # But if we go too far down
104        if box.y + box.h > pages[page].y + pages[page].h:
105            # We go to the next page
106            page += 1
107            # And put the box at the top-left
108            box.x = pages[page].x
109            box.y = pages[page].y
110
111        # Put the box in the row
112        row.append(box)
113
114        # Collapse all left-margin space
115        if all(b.letter == " " for b in row):
116            box.w = 0
117            box.stretchy = False
118            box.x = pages[page].x
119
120        previous = box
121
122
123layout(text_boxes)
124
125
126import svgwrite
127
128
129def draw_boxes(boxes, fname, size, hide_boxes=False):
130    dwg = svgwrite.Drawing(fname, profile="full", size=size)
131    # Draw the pages
132    for page in pages:
133        dwg.add(
134            dwg.rect(
135                insert=(f"{page.x}cm", f"{page.y}cm"),
136                size=(f"{page.w}cm", f"{page.h}cm"),
137                fill="lightblue",
138            )
139        )
140    # Draw all the boxes
141    for box in boxes:
142        # The box color depends on its features
143        color = "green" if box.stretchy else "red"
144        # Make the colored boxes optional
145        if not hide_boxes:
146            dwg.add(
147                dwg.rect(
148                    insert=(f"{box.x}cm", f"{box.y}cm"),
149                    size=(f"{box.w}cm", f"{box.h}cm"),
150                    fill=color,
151                )
152            )
153        # Display the letter in the box
154        if box.letter:
155            dwg.add(
156                dwg.text(
157                    box.letter,
158                    insert=(f"{box.x}cm", f"{box.y + box.h}cm"),
159                    font_size=f"{box.h}cm",
160                    font_family="Arial",
161                )
162            )
163    dwg.save()
164
165
166draw_boxes(text_boxes, "lesson9.svg", ("30cm", "50cm"), hide_boxes=True)
167