title: Reportlab PDF: cannot space paragraphs proportionally across a frame
body: <p>I am trying to make a script that pulls event data from Google Calendar and for each day displays the days' events in two columns with breaks between them that are proportional to time (the more time between events, the more space between paragraphs). Every event is a Reportlab Platypus paragraph and there are spacers between them. <a href="https://github.com/pearupung/GoogleCalToPDF" rel="nofollow noreferrer">Full code here</a> (will be commented soon, I promise).</p>
  
  <p>My problem is that I can't calculate the exact spacing needed in order to fit every paragraph into a frame exactly: I always over- or underestimate, no matter the formula, the last paragraph never lands exactly on the bottom of the frame.</p>
  
  <p>The main calculation starts in add_events_to_frame(). The paragraphs are made for every event and the available space is stored, spacer height values are requested and the story written to canvas:</p>
  
  <pre><code>def add_events_to_frame(frame, events, canvas):
  
      story = []
      free_space = frame.height
  
      for event in events:
          paragraph = get_event_paragraph(event)
          story.append(paragraph)
          free_space -= paragraph.wrap(frame_width, frame_height)[1] - 1
  
      real_story = []
  
      if free_space &gt; 0:
          spaces = Collector.get_space_after(events, free_space)
      for i, paragraph in enumerate(story):
          real_story.append(paragraph)
          real_story.append(Spacer(1, spaces[i] + 1))
      frame.addFromList(real_story, canvas)
      return frame
  </code></pre>
  
  <p>The get_event_paragraph() looks like this:</p>
  
  <pre><code>def get_event_paragraph(event, spaceAfter=0, borderPadding=2):
  
      event_style = ParagraphStyle(
          "event",
          fontName="Times-Roman",
          fontSize=5,
          backColor=event['color'],
          textColor='white',
          leading=6,
          borderPadding=borderPadding,
          spaceAfter=spaceAfter + 2 * borderPadding
      )
  
      beginTime = event['start'].strftime("%H:%M")
      endTime = event['end'].strftime("%H:%M")
  
      eventStringList = [f"&lt;b&gt;{beginTime}-{endTime}&lt;/b&gt;"]
  
      eventStringList.append(event['summary'])
  
      return Paragraph(' '.join(eventStringList), event_style)
  </code></pre>
  
  <p>And the spacer height calculator:</p>
  
  <pre><code>def get_space_after(events, free_space):
      """
      Calculate spaces between events in percentages based on sum of free time and constraints.
  
      :param events:
      :return:
      """
      timesum = timedelta(seconds=1)
      time_list = []
  
      for i in range(len(events) - 1):
          time_diff = events[i + 1]['start'] - events[i]['end']
          timesum += time_diff
          time_list.append(time_diff)
  
      time_list.append(timedelta(hours=0))
  
      spaces = []
  
      for diff in time_list:
          spaces.append(round(diff/timesum * free_space, 4))
      return spaces
  </code></pre>
  
  <p>I have tried KeepInFrame(), but when it does exert itself, it leaves the design uneven and ugly. Also I have tried to exclude the frame padding, the paragraph padding and the 1px space between paragraphs from the free_space variable in various combinations, but again, no exact match. What do?</p>