hexapdf 0.9.1 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/CONTRIBUTERS +1 -1
- data/VERSION +1 -1
- data/lib/hexapdf/encryption/aes.rb +8 -3
- data/lib/hexapdf/filter/flate_decode.rb +5 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/encryption/test_aes.rb +12 -10
- data/test/hexapdf/filter/test_flate_decode.rb +11 -2
- data/test/hexapdf/test_writer.rb +2 -2
- metadata +2 -4
- data/examples/019-column_box.rb +0 -57
- data/lib/hexapdf/layout/column_box.rb +0 -161
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: ee462224ffa152978df2a0aebd05b52c7a75a21655880858bbb4b61e2eccd3dd
         | 
| 4 | 
            +
              data.tar.gz: dc2eedf3a556c7a85fbfb229372512043f33200c6ffb6223b344c96bf4aff91b
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: da87ec95143ad653a606c699dc4e29ac3e4abf6b4e1a0bfc535ede8221f047ab35f7b69556eaa7b7cf23d7da69909685c515d98ea67f1e1e0265ebbb460f2eb7
         | 
| 7 | 
            +
              data.tar.gz: f47ec5e10af27baf8a8ee06d4f496f53841b57acc93ce1111288576a473dfb73d73052aa65f21835e19ebadb6e87868fcc3d63f84c82dbfd81dca7ae56eb9975
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    
    
        data/CONTRIBUTERS
    CHANGED
    
    
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0.9. | 
| 1 | 
            +
            0.9.2
         | 
| @@ -170,13 +170,18 @@ module HexaPDF | |
| 170 170 | 
             
                    # Removes the padding from the data according to the PKCS#5 padding scheme and returns the
         | 
| 171 171 | 
             
                    # result.
         | 
| 172 172 | 
             
                    #
         | 
| 173 | 
            +
                    # In case the padding is not correct as per the specification, it is assumed that there is
         | 
| 174 | 
            +
                    # no padding and the input is returned as is.
         | 
| 175 | 
            +
                    #
         | 
| 173 176 | 
             
                    # See: PDF1.7 s7.6.2
         | 
| 174 177 | 
             
                    def unpad(data)
         | 
| 175 178 | 
             
                      padding_length = data.getbyte(-1)
         | 
| 176 | 
            -
                      if padding_length > BLOCK_SIZE || padding_length == 0
         | 
| 177 | 
            -
             | 
| 179 | 
            +
                      if padding_length > BLOCK_SIZE || padding_length == 0 ||
         | 
| 180 | 
            +
                          data[-padding_length, padding_length].each_byte.any? {|byte| byte != padding_length }
         | 
| 181 | 
            +
                        data
         | 
| 182 | 
            +
                      else
         | 
| 183 | 
            +
                        data[0...-padding_length]
         | 
| 178 184 | 
             
                      end
         | 
| 179 | 
            -
                      data[0...-padding_length]
         | 
| 180 185 | 
             
                    end
         | 
| 181 186 |  | 
| 182 187 | 
             
                  end
         | 
| @@ -49,10 +49,14 @@ module HexaPDF | |
| 49 49 | 
             
                module FlateDecode
         | 
| 50 50 |  | 
| 51 51 | 
             
                  # See HexaPDF::Filter
         | 
| 52 | 
            +
                  #
         | 
| 53 | 
            +
                  # The decoder also handles the case of an empty string not deflated to a correct flate stream
         | 
| 54 | 
            +
                  # but just output as an empty string.
         | 
| 52 55 | 
             
                  def self.decoder(source, options = nil)
         | 
| 53 56 | 
             
                    fib = Fiber.new do
         | 
| 54 57 | 
             
                      inflater = Zlib::Inflate.new
         | 
| 55 58 | 
             
                      while source.alive? && (data = source.resume)
         | 
| 59 | 
            +
                        next if data.empty?
         | 
| 56 60 | 
             
                        begin
         | 
| 57 61 | 
             
                          data = inflater.inflate(data)
         | 
| 58 62 | 
             
                        rescue StandardError => e
         | 
| @@ -61,7 +65,7 @@ module HexaPDF | |
| 61 65 | 
             
                        Fiber.yield(data)
         | 
| 62 66 | 
             
                      end
         | 
| 63 67 | 
             
                      begin
         | 
| 64 | 
            -
                        data = (data = inflater.finish).empty? ? nil : data
         | 
| 68 | 
            +
                        data = inflater.total_in == 0 || (data = inflater.finish).empty? ? nil : data
         | 
| 65 69 | 
             
                        inflater.close
         | 
| 66 70 | 
             
                        data
         | 
| 67 71 | 
             
                      rescue StandardError => e
         | 
    
        data/lib/hexapdf/version.rb
    CHANGED
    
    
| @@ -48,12 +48,6 @@ describe HexaPDF::Encryption::AES do | |
| 48 48 | 
             
                  end
         | 
| 49 49 | 
             
                end
         | 
| 50 50 |  | 
| 51 | 
            -
                it "fails on decryption if the padding is invalid" do
         | 
| 52 | 
            -
                  assert_raises(HexaPDF::EncryptionError) do
         | 
| 53 | 
            -
                    @algorithm_class.decrypt('some' * 4, 'iv' * 8 + 'somedata' * 4)
         | 
| 54 | 
            -
                  end
         | 
| 55 | 
            -
                end
         | 
| 56 | 
            -
             | 
| 57 51 | 
             
                it "fails on decryption if not enough bytes are provided" do
         | 
| 58 52 | 
             
                  assert_raises(HexaPDF::EncryptionError) do
         | 
| 59 53 | 
             
                    @algorithm_class.decrypt('some' * 4, 'no iv')
         | 
| @@ -102,10 +96,18 @@ describe HexaPDF::Encryption::AES do | |
| 102 96 | 
             
                  assert_equal('a' * 40, result)
         | 
| 103 97 | 
             
                end
         | 
| 104 98 |  | 
| 105 | 
            -
                it " | 
| 106 | 
            -
                   | 
| 107 | 
            -
             | 
| 108 | 
            -
                   | 
| 99 | 
            +
                it "decryption works if the padding is invalid" do
         | 
| 100 | 
            +
                  f = Fiber.new { 'a' * 32 }
         | 
| 101 | 
            +
                  result = TestHelper.collector(@algorithm_class.decryption_fiber('some' * 4, f))
         | 
| 102 | 
            +
                  assert_equal('a' * 16, result)
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                  f = Fiber.new { 'a' * 31 + "\x00" }
         | 
| 105 | 
            +
                  result = TestHelper.collector(@algorithm_class.decryption_fiber('some' * 4, f))
         | 
| 106 | 
            +
                  assert_equal('a' * 15 + "\x00", result)
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                  f = Fiber.new { 'a' * 29 + "\x00\x01\x03" }
         | 
| 109 | 
            +
                  result = TestHelper.collector(@algorithm_class.decryption_fiber('some' * 4, f))
         | 
| 110 | 
            +
                  assert_equal('a' * 13 + "\x00\x01\x03", result)
         | 
| 109 111 | 
             
                end
         | 
| 110 112 |  | 
| 111 113 | 
             
                it "fails on decryption if not enough bytes are provided" do
         | 
| @@ -17,13 +17,22 @@ describe HexaPDF::Filter::FlateDecode do | |
| 17 17 | 
             
              end
         | 
| 18 18 |  | 
| 19 19 | 
             
              describe "decoder" do
         | 
| 20 | 
            +
                it "works for empty input" do
         | 
| 21 | 
            +
                  assert_equal('', collector(@obj.decoder(Fiber.new { "" })))
         | 
| 22 | 
            +
                  assert_equal('', collector(@obj.decoder(Fiber.new {})))
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 20 25 | 
             
                it "applies the Predictor after decoding" do
         | 
| 21 26 | 
             
                  assert_equal(@decoded, collector(@obj.decoder(feeder(@encoded_predictor), @predictor_opts)))
         | 
| 22 27 | 
             
                end
         | 
| 23 28 |  | 
| 24 29 | 
             
                it "fails on invalid input" do
         | 
| 25 | 
            -
                  assert_raises(HexaPDF::FilterError)  | 
| 26 | 
            -
             | 
| 30 | 
            +
                  assert_raises(HexaPDF::FilterError) do
         | 
| 31 | 
            +
                    collector(@obj.decoder(feeder(@encoded[0..-2], @encoded.length - 3)))
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                  assert_raises(HexaPDF::FilterError) do
         | 
| 34 | 
            +
                    collector(@obj.decoder(feeder("some data")))
         | 
| 35 | 
            +
                  end
         | 
| 27 36 | 
             
                end
         | 
| 28 37 | 
             
              end
         | 
| 29 38 |  | 
    
        data/test/hexapdf/test_writer.rb
    CHANGED
    
    | @@ -40,7 +40,7 @@ describe HexaPDF::Writer do | |
| 40 40 | 
             
                  219
         | 
| 41 41 | 
             
                  %%EOF
         | 
| 42 42 | 
             
                  3 0 obj
         | 
| 43 | 
            -
                  <</Producer(HexaPDF version 0.9. | 
| 43 | 
            +
                  <</Producer(HexaPDF version 0.9.2)>>
         | 
| 44 44 | 
             
                  endobj
         | 
| 45 45 | 
             
                  xref
         | 
| 46 46 | 
             
                  3 1
         | 
| @@ -72,7 +72,7 @@ describe HexaPDF::Writer do | |
| 72 72 | 
             
                  141
         | 
| 73 73 | 
             
                  %%EOF
         | 
| 74 74 | 
             
                  6 0 obj
         | 
| 75 | 
            -
                  <</Producer(HexaPDF version 0.9. | 
| 75 | 
            +
                  <</Producer(HexaPDF version 0.9.2)>>
         | 
| 76 76 | 
             
                  endobj
         | 
| 77 77 | 
             
                  2 0 obj
         | 
| 78 78 | 
             
                  <</Length 10>>stream
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: hexapdf
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.9. | 
| 4 | 
            +
              version: 0.9.2
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Thomas Leitner
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2019- | 
| 11 | 
            +
            date: 2019-05-22 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: cmdparse
         | 
| @@ -206,7 +206,6 @@ files: | |
| 206 206 | 
             
            - examples/016-frame_automatic_box_placement.rb
         | 
| 207 207 | 
             
            - examples/017-frame_text_flow.rb
         | 
| 208 208 | 
             
            - examples/018-composer.rb
         | 
| 209 | 
            -
            - examples/019-column_box.rb
         | 
| 210 209 | 
             
            - examples/emoji-smile.png
         | 
| 211 210 | 
             
            - examples/emoji-wink.png
         | 
| 212 211 | 
             
            - examples/machupicchu.jpg
         | 
| @@ -318,7 +317,6 @@ files: | |
| 318 317 | 
             
            - lib/hexapdf/importer.rb
         | 
| 319 318 | 
             
            - lib/hexapdf/layout.rb
         | 
| 320 319 | 
             
            - lib/hexapdf/layout/box.rb
         | 
| 321 | 
            -
            - lib/hexapdf/layout/column_box.rb
         | 
| 322 320 | 
             
            - lib/hexapdf/layout/frame.rb
         | 
| 323 321 | 
             
            - lib/hexapdf/layout/image_box.rb
         | 
| 324 322 | 
             
            - lib/hexapdf/layout/inline_box.rb
         | 
    
        data/examples/019-column_box.rb
    DELETED
    
    | @@ -1,57 +0,0 @@ | |
| 1 | 
            -
            # ## Column Box
         | 
| 2 | 
            -
            #
         | 
| 3 | 
            -
            # This example shows how [HexaPDF::Layout::Frame] and [HexaPDF::Layout::TextBox]
         | 
| 4 | 
            -
            # can be used to flow text around objects.
         | 
| 5 | 
            -
            #
         | 
| 6 | 
            -
            # Three boxes are placed repeatedly onto the frame until it is filled: two
         | 
| 7 | 
            -
            # floating boxes (one left, one right) and a text box. The text box is styled to
         | 
| 8 | 
            -
            # flow its content around the other two boxes.
         | 
| 9 | 
            -
            #
         | 
| 10 | 
            -
            # Usage:
         | 
| 11 | 
            -
            # : `ruby frame_text_flow.rb`
         | 
| 12 | 
            -
            #
         | 
| 13 | 
            -
             | 
| 14 | 
            -
            require 'hexapdf'
         | 
| 15 | 
            -
            require 'hexapdf/utils/graphics_helpers'
         | 
| 16 | 
            -
             | 
| 17 | 
            -
            include HexaPDF::Layout
         | 
| 18 | 
            -
            include HexaPDF::Utils::GraphicsHelpers
         | 
| 19 | 
            -
             | 
| 20 | 
            -
            doc = HexaPDF::Document.new
         | 
| 21 | 
            -
             | 
| 22 | 
            -
            sample_text = "Lorem ipsum dolor sit amet, con\u{00AD}sectetur
         | 
| 23 | 
            -
            adipis\u{00AD}cing elit, sed do eiusmod tempor incididunt ut labore et
         | 
| 24 | 
            -
            dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
         | 
| 25 | 
            -
            ullamco laboris nisi ut aliquip ex ea commodo consequat.
         | 
| 26 | 
            -
            ".tr("\n", ' ') * 5
         | 
| 27 | 
            -
            items = [TextFragment.create(sample_text, font: doc.fonts.add("Times"))]
         | 
| 28 | 
            -
             | 
| 29 | 
            -
            page = doc.pages.add
         | 
| 30 | 
            -
            media_box = page.box(:media)
         | 
| 31 | 
            -
            canvas = page.canvas
         | 
| 32 | 
            -
            frame = Frame.new(media_box.left + 20, media_box.bottom + 20,
         | 
| 33 | 
            -
                              media_box.width - 40, media_box.height - 40)
         | 
| 34 | 
            -
             | 
| 35 | 
            -
            image = doc.images.add(File.join(__dir__, 'machupicchu.jpg'))
         | 
| 36 | 
            -
            iw, ih = calculate_dimensions(image.width, image.height, rwidth: 100)
         | 
| 37 | 
            -
             | 
| 38 | 
            -
            boxes = []
         | 
| 39 | 
            -
            3.times do
         | 
| 40 | 
            -
              boxes << Box.create(width: iw, height: ih,
         | 
| 41 | 
            -
                margin: [10, 30], position: :float) do |canv, box|
         | 
| 42 | 
            -
                canv.image(image, at: [0, 0], width: 100)
         | 
| 43 | 
            -
              end
         | 
| 44 | 
            -
              boxes << Box.create(width: 50, height: 50, margin: 20,
         | 
| 45 | 
            -
                position: :float, position_hint: :right,
         | 
| 46 | 
            -
                border: {width: 1, color: [[255, 0, 0]]})
         | 
| 47 | 
            -
              boxes << TextBox.new(items, style: {position: :flow, align: :justify})
         | 
| 48 | 
            -
            end
         | 
| 49 | 
            -
            columns = ColumnBox.new(boxes) #, style: {position: :flow})
         | 
| 50 | 
            -
            polygon = Geom2D::Polygon([250, 350], [350, 350], [350, 500], [250, 500])
         | 
| 51 | 
            -
            #frame.remove_area(polygon)
         | 
| 52 | 
            -
            #canvas.draw(:geom2d, object: polygon)
         | 
| 53 | 
            -
            result = frame.fit(columns)
         | 
| 54 | 
            -
            p result.success?
         | 
| 55 | 
            -
            frame.draw(canvas, result)
         | 
| 56 | 
            -
             | 
| 57 | 
            -
            doc.write("column_box.pdf", optimize: true)
         | 
| @@ -1,161 +0,0 @@ | |
| 1 | 
            -
            # -*- encoding: utf-8; frozen_string_literal: true -*-
         | 
| 2 | 
            -
            #
         | 
| 3 | 
            -
            #--
         | 
| 4 | 
            -
            # This file is part of HexaPDF.
         | 
| 5 | 
            -
            #
         | 
| 6 | 
            -
            # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
         | 
| 7 | 
            -
            # Copyright (C) 2014-2019 Thomas Leitner
         | 
| 8 | 
            -
            #
         | 
| 9 | 
            -
            # HexaPDF is free software: you can redistribute it and/or modify it
         | 
| 10 | 
            -
            # under the terms of the GNU Affero General Public License version 3 as
         | 
| 11 | 
            -
            # published by the Free Software Foundation with the addition of the
         | 
| 12 | 
            -
            # following permission added to Section 15 as permitted in Section 7(a):
         | 
| 13 | 
            -
            # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
         | 
| 14 | 
            -
            # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
         | 
| 15 | 
            -
            # INFRINGEMENT OF THIRD PARTY RIGHTS.
         | 
| 16 | 
            -
            #
         | 
| 17 | 
            -
            # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
         | 
| 18 | 
            -
            # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
         | 
| 19 | 
            -
            # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
         | 
| 20 | 
            -
            # License for more details.
         | 
| 21 | 
            -
            #
         | 
| 22 | 
            -
            # You should have received a copy of the GNU Affero General Public License
         | 
| 23 | 
            -
            # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
         | 
| 24 | 
            -
            #
         | 
| 25 | 
            -
            # The interactive user interfaces in modified source and object code
         | 
| 26 | 
            -
            # versions of HexaPDF must display Appropriate Legal Notices, as required
         | 
| 27 | 
            -
            # under Section 5 of the GNU Affero General Public License version 3.
         | 
| 28 | 
            -
            #
         | 
| 29 | 
            -
            # In accordance with Section 7(b) of the GNU Affero General Public
         | 
| 30 | 
            -
            # License, a covered work must retain the producer line in every PDF that
         | 
| 31 | 
            -
            # is created or manipulated using HexaPDF.
         | 
| 32 | 
            -
            #
         | 
| 33 | 
            -
            # If the GNU Affero General Public License doesn't fit your need,
         | 
| 34 | 
            -
            # commercial licenses are available at <https://gettalong.at/hexapdf/>.
         | 
| 35 | 
            -
            #++
         | 
| 36 | 
            -
            require 'hexapdf/layout/box'
         | 
| 37 | 
            -
             | 
| 38 | 
            -
            module HexaPDF
         | 
| 39 | 
            -
              module Layout
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                # A ColumnBox arranges boxes in one or more columns.
         | 
| 42 | 
            -
                #
         | 
| 43 | 
            -
                # The number of columns as well as the size of the gap between the columns can be modified.
         | 
| 44 | 
            -
                class ColumnBox < Box
         | 
| 45 | 
            -
             | 
| 46 | 
            -
                  # The child boxes of this ColumnBox.
         | 
| 47 | 
            -
                  attr_reader :children
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                  # The number of columns.
         | 
| 50 | 
            -
                  # TODO: allow array with column widths later like [100, :*, :*]; same for gaps
         | 
| 51 | 
            -
                  attr_reader :columns
         | 
| 52 | 
            -
             | 
| 53 | 
            -
                  # The size of the gap between the columns.
         | 
| 54 | 
            -
                  attr_reader :gap
         | 
| 55 | 
            -
             | 
| 56 | 
            -
                  # Creates a new ColumnBox object for the given +children+ boxes.
         | 
| 57 | 
            -
                  def initialize(children = [], columns = 2, gap: 36, **kwargs)
         | 
| 58 | 
            -
                    super(kwargs, &:unused_draw_block)
         | 
| 59 | 
            -
                    @children = children
         | 
| 60 | 
            -
                    @columns = columns
         | 
| 61 | 
            -
                    @gap = gap
         | 
| 62 | 
            -
                  end
         | 
| 63 | 
            -
             | 
| 64 | 
            -
                  # Fits the column box into the available space.
         | 
| 65 | 
            -
                  def fit(available_width, available_height, frame)
         | 
| 66 | 
            -
                    last_height_difference = 1_000_000
         | 
| 67 | 
            -
                    height = if style.position == :flow
         | 
| 68 | 
            -
                               frame.height
         | 
| 69 | 
            -
                             else
         | 
| 70 | 
            -
                               (@initial_height > 0 ? @initial_height : available_height) - reserved_height
         | 
| 71 | 
            -
                             end
         | 
| 72 | 
            -
                    while true
         | 
| 73 | 
            -
                      @frames = []
         | 
| 74 | 
            -
                      if style.position == :flow
         | 
| 75 | 
            -
                        column_width = (frame.width - gap * (@columns - 1)).to_f / @columns
         | 
| 76 | 
            -
                        @columns.times do |col_nr|
         | 
| 77 | 
            -
                          left = (column_width + gap) * col_nr + frame.left
         | 
| 78 | 
            -
                          bottom = frame.bottom
         | 
| 79 | 
            -
                          rect = Geom2D::Polygon([left, bottom],
         | 
| 80 | 
            -
                            [left + column_width, bottom],
         | 
| 81 | 
            -
                            [left + column_width, bottom + height],
         | 
| 82 | 
            -
                            [left, bottom + height])
         | 
| 83 | 
            -
                          shape = Geom2D::Algorithms::PolygonOperation.run(frame.shape, rect, :intersection)
         | 
| 84 | 
            -
                          col_frame = Frame.new(left, bottom, column_width, height)
         | 
| 85 | 
            -
                          col_frame.shape = shape
         | 
| 86 | 
            -
                          @frames << col_frame
         | 
| 87 | 
            -
                        end
         | 
| 88 | 
            -
                        @frame_index = 0
         | 
| 89 | 
            -
                        @results = @children.map {|child_box| fit_box(child_box) }
         | 
| 90 | 
            -
                        @width = frame.width
         | 
| 91 | 
            -
                        @height = frame.height - @frames.min_by(&:y).y
         | 
| 92 | 
            -
                      else
         | 
| 93 | 
            -
                        width = (@initial_width > 0 ? @initial_width : available_width) - reserved_width
         | 
| 94 | 
            -
                        column_width = (width - gap * (@columns - 1)).to_f / @columns
         | 
| 95 | 
            -
                        @columns.times do |col_nr|
         | 
| 96 | 
            -
                          @frames << Frame.new((column_width + gap) * col_nr, 0, column_width, height)
         | 
| 97 | 
            -
                        end
         | 
| 98 | 
            -
                        @frame_index = 0
         | 
| 99 | 
            -
                        @results = @children.map {|child_box| fit_box(child_box) }
         | 
| 100 | 
            -
                        @width = width
         | 
| 101 | 
            -
                        @height = height - @frames.min_by(&:y).y
         | 
| 102 | 
            -
                      end
         | 
| 103 | 
            -
                      min_y, max_y = @frames.minmax_by(&:y).map(&:y)
         | 
| 104 | 
            -
                      p [height, @frames.map(&:y), last_height_difference, min_y, max_y]
         | 
| 105 | 
            -
                      # TOOD: @result.any?(&:empty?) only for the first run!!!! if the first run fails, we
         | 
| 106 | 
            -
                      # cannot balance the columns because there is too much content.
         | 
| 107 | 
            -
                      # TODO: another break condition is if the @results didn't change since the last run
         | 
| 108 | 
            -
                      break if max_y != height && (@results.any?(&:empty?) ||
         | 
| 109 | 
            -
                                                   max_y - min_y >= last_height_difference ||
         | 
| 110 | 
            -
                                                   max_y - min_y < 0.5)
         | 
| 111 | 
            -
                      last_height_difference = max_y - min_y
         | 
| 112 | 
            -
                      height -= last_height_difference / 2.0
         | 
| 113 | 
            -
                      p [height]
         | 
| 114 | 
            -
                    end
         | 
| 115 | 
            -
                    @results.all? {|res| res.length == 1 }
         | 
| 116 | 
            -
                  end
         | 
| 117 | 
            -
             | 
| 118 | 
            -
                  private
         | 
| 119 | 
            -
             | 
| 120 | 
            -
                  def fit_box(box)
         | 
| 121 | 
            -
                    cur_frame = @frames[@frame_index]
         | 
| 122 | 
            -
                    fitted_boxes = []
         | 
| 123 | 
            -
                    while true && cur_frame
         | 
| 124 | 
            -
                      result = cur_frame.fit(box)
         | 
| 125 | 
            -
                      if result.success?
         | 
| 126 | 
            -
                        cur_frame.remove_area(result.mask)
         | 
| 127 | 
            -
                        fitted_boxes << result
         | 
| 128 | 
            -
                        break
         | 
| 129 | 
            -
                      elsif cur_frame.full?
         | 
| 130 | 
            -
                        @frame_index += 1
         | 
| 131 | 
            -
                        break if @frame_index == @frames.length
         | 
| 132 | 
            -
                        cur_frame = @frames[@frame_index]
         | 
| 133 | 
            -
                      else
         | 
| 134 | 
            -
                        draw_box, box = cur_frame.split(result)
         | 
| 135 | 
            -
                        if draw_box
         | 
| 136 | 
            -
                          cur_frame.remove_area(result.mask)
         | 
| 137 | 
            -
                          fitted_boxes << result
         | 
| 138 | 
            -
                        elsif !cur_frame.find_next_region
         | 
| 139 | 
            -
                          @frame_index += 1
         | 
| 140 | 
            -
                          break if @frame_index == @frames.length
         | 
| 141 | 
            -
                          cur_frame = @frames[@frame_index]
         | 
| 142 | 
            -
                        end
         | 
| 143 | 
            -
                      end
         | 
| 144 | 
            -
                    end
         | 
| 145 | 
            -
                    fitted_boxes
         | 
| 146 | 
            -
                  end
         | 
| 147 | 
            -
             | 
| 148 | 
            -
                  # Draws the child boxes onto the canvas at position [x, y].
         | 
| 149 | 
            -
                  def draw_content(canvas, x, y)
         | 
| 150 | 
            -
                    x = y = 0 if style.position == :flow
         | 
| 151 | 
            -
                    @results.each do |result_boxes|
         | 
| 152 | 
            -
                      result_boxes.each do |result|
         | 
| 153 | 
            -
                        result.box.draw(canvas, x + result.x, y + result.y)
         | 
| 154 | 
            -
                      end
         | 
| 155 | 
            -
                    end
         | 
| 156 | 
            -
                  end
         | 
| 157 | 
            -
             | 
| 158 | 
            -
                end
         | 
| 159 | 
            -
             | 
| 160 | 
            -
              end
         | 
| 161 | 
            -
            end
         |