hexapdf 0.44.0 → 0.46.0
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 +106 -47
- data/examples/019-acro_form.rb +5 -0
- data/examples/027-composer_optional_content.rb +6 -4
- data/examples/030-pdfa.rb +12 -11
- data/lib/hexapdf/cli/inspect.rb +5 -0
- data/lib/hexapdf/composer.rb +23 -1
- data/lib/hexapdf/configuration.rb +8 -0
- data/lib/hexapdf/content/canvas.rb +3 -3
- data/lib/hexapdf/content/canvas_composer.rb +1 -0
- data/lib/hexapdf/digital_signature/cms_handler.rb +31 -3
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +9 -1
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +5 -1
- data/lib/hexapdf/document/layout.rb +63 -30
- data/lib/hexapdf/document.rb +24 -2
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/importer.rb +15 -5
- data/lib/hexapdf/layout/box.rb +48 -36
- data/lib/hexapdf/layout/column_box.rb +3 -11
- data/lib/hexapdf/layout/container_box.rb +4 -4
- data/lib/hexapdf/layout/frame.rb +7 -6
- data/lib/hexapdf/layout/inline_box.rb +17 -23
- data/lib/hexapdf/layout/list_box.rb +27 -42
- data/lib/hexapdf/layout/page_style.rb +23 -16
- data/lib/hexapdf/layout/style.rb +5 -5
- data/lib/hexapdf/layout/table_box.rb +14 -10
- data/lib/hexapdf/layout/text_box.rb +60 -36
- data/lib/hexapdf/layout/text_fragment.rb +1 -1
- data/lib/hexapdf/layout/text_layouter.rb +7 -8
- data/lib/hexapdf/parser.rb +5 -1
- data/lib/hexapdf/rectangle.rb +4 -4
- data/lib/hexapdf/revisions.rb +1 -1
- data/lib/hexapdf/stream.rb +3 -3
- data/lib/hexapdf/tokenizer.rb +3 -2
- data/lib/hexapdf/type/acro_form/button_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
- data/lib/hexapdf/type/acro_form/field.rb +8 -0
- data/lib/hexapdf/type/acro_form/form.rb +2 -1
- data/lib/hexapdf/type/acro_form/text_field.rb +2 -0
- data/lib/hexapdf/type/form.rb +2 -2
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas_composer.rb +13 -8
- data/test/hexapdf/digital_signature/common.rb +66 -84
- data/test/hexapdf/digital_signature/signing/test_default_handler.rb +7 -0
- data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +9 -0
- data/test/hexapdf/digital_signature/test_cms_handler.rb +41 -1
- data/test/hexapdf/digital_signature/test_handler.rb +2 -1
- data/test/hexapdf/document/test_layout.rb +44 -5
- data/test/hexapdf/layout/test_box.rb +23 -5
- data/test/hexapdf/layout/test_frame.rb +21 -2
- data/test/hexapdf/layout/test_inline_box.rb +17 -28
- data/test/hexapdf/layout/test_list_box.rb +8 -8
- data/test/hexapdf/layout/test_page_style.rb +7 -2
- data/test/hexapdf/layout/test_table_box.rb +8 -1
- data/test/hexapdf/layout/test_text_box.rb +51 -29
- data/test/hexapdf/layout/test_text_layouter.rb +0 -3
- data/test/hexapdf/test_composer.rb +14 -5
- data/test/hexapdf/test_document.rb +27 -0
- data/test/hexapdf/test_importer.rb +17 -0
- data/test/hexapdf/test_revisions.rb +54 -41
- data/test/hexapdf/test_serializer.rb +1 -0
- data/test/hexapdf/type/acro_form/test_form.rb +9 -0
- metadata +2 -2
| @@ -43,14 +43,12 @@ module HexaPDF | |
| 43 43 | 
             
                # An InlineBox wraps a regular Box so that it can be used as an item for a Line. This enables
         | 
| 44 44 | 
             
                # inline graphics.
         | 
| 45 45 | 
             
                #
         | 
| 46 | 
            -
                #  | 
| 47 | 
            -
                #  | 
| 48 | 
            -
                #  | 
| 46 | 
            +
                # When an inline box gets placed on a line, the method #fit_wrapped_box is called to fit the
         | 
| 47 | 
            +
                # wrapped box. This allows the wrapped box to correctly set its width and height which are
         | 
| 48 | 
            +
                # needed by the TextLayouter algorithm.
         | 
| 49 49 | 
             
                #
         | 
| 50 | 
            -
                #  | 
| 51 | 
            -
                #  | 
| 52 | 
            -
                # wrapped box and its height, or if not set, a practically infinite height. In the latter case
         | 
| 53 | 
            -
                # the height *must* be set during fitting.
         | 
| 50 | 
            +
                # Note: It is *mandatory* that the wrapped box sets its width and height without relying on the
         | 
| 51 | 
            +
                # dimensions of the frame's current region.
         | 
| 54 52 | 
             
                class InlineBox
         | 
| 55 53 |  | 
| 56 54 | 
             
                  # Creates an InlineBox that wraps a basic Box. All arguments (except +valign+) and the block
         | 
| @@ -74,7 +72,6 @@ module HexaPDF | |
| 74 72 | 
             
                  # The +valign+ argument can be used to specify the vertical alignment of the box relative to
         | 
| 75 73 | 
             
                  # other items in the Line.
         | 
| 76 74 | 
             
                  def initialize(box, valign: :baseline)
         | 
| 77 | 
            -
                    raise HexaPDF::Error, "Width of box not set" if box.width == 0
         | 
| 78 75 | 
             
                    @box = box
         | 
| 79 76 | 
             
                    @valign = valign
         | 
| 80 77 | 
             
                  end
         | 
| @@ -102,8 +99,7 @@ module HexaPDF | |
| 102 99 | 
             
                  # Draws the wrapped box. If the box has margins specified, the x and y offsets are correctly
         | 
| 103 100 | 
             
                  # adjusted.
         | 
| 104 101 | 
             
                  def draw(canvas, x, y)
         | 
| 105 | 
            -
                     | 
| 106 | 
            -
                                     y - @fit_result.y + box.style.margin.bottom) { @fit_result.draw(canvas) }
         | 
| 102 | 
            +
                    @fit_result.draw(canvas, dx: x, dy: y)
         | 
| 107 103 | 
             
                  end
         | 
| 108 104 |  | 
| 109 105 | 
             
                  # The minimum x-coordinate which is always 0.
         | 
| @@ -129,19 +125,17 @@ module HexaPDF | |
| 129 125 | 
             
                  # Fits the wrapped box.
         | 
| 130 126 | 
             
                  #
         | 
| 131 127 | 
             
                  # If the +frame+ argument is +nil+, a custom frame is created. Otherwise the given +frame+ is
         | 
| 132 | 
            -
                  # used for the fitting operation.
         | 
| 133 | 
            -
                   | 
| 134 | 
            -
             | 
| 135 | 
            -
             | 
| 136 | 
            -
             | 
| 137 | 
            -
             | 
| 138 | 
            -
             | 
| 139 | 
            -
                    @fit_result =  | 
| 140 | 
            -
                     | 
| 141 | 
            -
             | 
| 142 | 
            -
             | 
| 143 | 
            -
                      raise HexaPDF::Error, "Box for inline use has no valid height set after fitting"
         | 
| 144 | 
            -
                    end
         | 
| 128 | 
            +
                  # used for creating an appropriate child frame for the fitting operation.
         | 
| 129 | 
            +
                  #
         | 
| 130 | 
            +
                  # After this operation the caller is responsible for checking the actual width and height of
         | 
| 131 | 
            +
                  # the inline box and whether it really fits.
         | 
| 132 | 
            +
                  def fit_wrapped_box(frame = nil)
         | 
| 133 | 
            +
                    @fit_result = box.fit(100_000, 100_000, frame || Frame.new(0, 0, 100_000, 100_000))
         | 
| 134 | 
            +
                    margin = box.style.margin if box.style.margin?
         | 
| 135 | 
            +
                    @fit_result.x = margin&.left.to_i
         | 
| 136 | 
            +
                    @fit_result.y = margin&.bottom.to_i
         | 
| 137 | 
            +
                    @fit_result.mask = Geom2D::Rectangle(0, 0, @fit_result.x + box.width + margin&.right.to_i,
         | 
| 138 | 
            +
                                                         @fit_result.y + box.height + margin&.top.to_i)
         | 
| 145 139 | 
             
                  end
         | 
| 146 140 |  | 
| 147 141 | 
             
                end
         | 
| @@ -189,19 +189,9 @@ module HexaPDF | |
| 189 189 | 
             
                  private
         | 
| 190 190 |  | 
| 191 191 | 
             
                  # Fits the list box into the current region of the frame.
         | 
| 192 | 
            -
                  def fit_content( | 
| 193 | 
            -
                    @width = if @initial_width > 0
         | 
| 194 | 
            -
                               @initial_width
         | 
| 195 | 
            -
                             else
         | 
| 196 | 
            -
                               (style.position == :flow ? frame.width : available_width)
         | 
| 197 | 
            -
                             end
         | 
| 198 | 
            -
                    height = if @initial_height > 0
         | 
| 199 | 
            -
                               @initial_height - reserved_height
         | 
| 200 | 
            -
                             else
         | 
| 201 | 
            -
                               (style.position == :flow ? frame.y - frame.bottom : available_height) - reserved_height
         | 
| 202 | 
            -
                             end
         | 
| 203 | 
            -
             | 
| 192 | 
            +
                  def fit_content(_available_width, _available_height, frame)
         | 
| 204 193 | 
             
                    width = @width - reserved_width
         | 
| 194 | 
            +
                    height = @height - reserved_height
         | 
| 205 195 | 
             
                    left = (style.position == :flow ? frame.left : frame.x) + reserved_width_left
         | 
| 206 196 | 
             
                    top = frame.y - reserved_height_top
         | 
| 207 197 |  | 
| @@ -245,7 +235,7 @@ module HexaPDF | |
| 245 235 | 
             
                      Array(child).each {|ibox| box_fitter.fit(ibox) }
         | 
| 246 236 | 
             
                      item_result.box_fitter = box_fitter
         | 
| 247 237 | 
             
                      item_result.height = [item_result.height.to_i, box_fitter.content_heights[0]].max
         | 
| 248 | 
            -
                      @results << item_result
         | 
| 238 | 
            +
                      @results << item_result unless box_fitter.fit_results.empty?
         | 
| 249 239 |  | 
| 250 240 | 
             
                      top -= item_result.height + item_spacing
         | 
| 251 241 | 
             
                      height -= item_result.height + item_spacing
         | 
| @@ -253,7 +243,7 @@ module HexaPDF | |
| 253 243 | 
             
                      break if !box_fitter.success? || height <= 0
         | 
| 254 244 | 
             
                    end
         | 
| 255 245 |  | 
| 256 | 
            -
                     | 
| 246 | 
            +
                    update_content_height { @results.sum(&:height) + (@results.count - 1) * item_spacing }
         | 
| 257 247 |  | 
| 258 248 | 
             
                    if @results.size == @children.size && @results.all? {|r| r.box_fitter.success? }
         | 
| 259 249 | 
             
                      fit_result.success!
         | 
| @@ -326,37 +316,32 @@ module HexaPDF | |
| 326 316 | 
             
                  def item_marker_box(document, index)
         | 
| 327 317 | 
             
                    return @marker_type.call(document, self, index) if @marker_type.kind_of?(Proc)
         | 
| 328 318 |  | 
| 329 | 
            -
                    unless ( | 
| 319 | 
            +
                    unless (items = @item_marker_items)
         | 
| 330 320 | 
             
                      marker_style = {
         | 
| 331 | 
            -
                         | 
| 332 | 
            -
                         | 
| 321 | 
            +
                        font_size: style.font_size || 10,
         | 
| 322 | 
            +
                        fill_color: style.fill_color,
         | 
| 333 323 | 
             
                      }
         | 
| 334 | 
            -
                       | 
| 335 | 
            -
             | 
| 336 | 
            -
             | 
| 337 | 
            -
             | 
| 338 | 
            -
             | 
| 339 | 
            -
             | 
| 340 | 
            -
             | 
| 341 | 
            -
             | 
| 342 | 
            -
             | 
| 343 | 
            -
             | 
| 344 | 
            -
             | 
| 345 | 
            -
             | 
| 346 | 
            -
             | 
| 347 | 
            -
             | 
| 348 | 
            -
             | 
| 349 | 
            -
             | 
| 350 | 
            -
             | 
| 351 | 
            -
             | 
| 352 | 
            -
             | 
| 353 | 
            -
                                   TextFragment.create(text, marker_style)
         | 
| 354 | 
            -
                                 else
         | 
| 355 | 
            -
                                   raise HexaPDF::Error, "Unknown list marker type #{@marker_type.inspect}"
         | 
| 356 | 
            -
                                 end
         | 
| 357 | 
            -
                      @item_marker_fragment = fragment unless @marker_type == :decimal
         | 
| 324 | 
            +
                      marker_style[:font] = style.font if style.font?
         | 
| 325 | 
            +
                      items = case @marker_type
         | 
| 326 | 
            +
                              when :disc
         | 
| 327 | 
            +
                                document.layout.text_fragments("•", style: marker_style)
         | 
| 328 | 
            +
                              when :circle
         | 
| 329 | 
            +
                                document.layout.text_fragments("❍", style: marker_style,
         | 
| 330 | 
            +
                                                               font_size: style.font_size / 2.0,
         | 
| 331 | 
            +
                                                               text_rise: -style.font_size / 1.8)
         | 
| 332 | 
            +
                              when :square
         | 
| 333 | 
            +
                                document.layout.text_fragments("■", style: marker_style,
         | 
| 334 | 
            +
                                                               font_size: style.font_size / 2.0,
         | 
| 335 | 
            +
                                                               text_rise: -style.font_size / 1.8)
         | 
| 336 | 
            +
                              when :decimal
         | 
| 337 | 
            +
                                text = (@start_number + index).to_s << "."
         | 
| 338 | 
            +
                                document.layout.text_fragments(text, style: marker_style)
         | 
| 339 | 
            +
                              else
         | 
| 340 | 
            +
                                raise HexaPDF::Error, "Unknown list marker type #{@marker_type.inspect}"
         | 
| 341 | 
            +
                              end
         | 
| 342 | 
            +
                      @item_marker_items = items unless @marker_type == :decimal
         | 
| 358 343 | 
             
                    end
         | 
| 359 | 
            -
                    TextBox.new(items:  | 
| 344 | 
            +
                    TextBox.new(items: items, style: {text_align: :right, padding: [0, 5, 0, 0]})
         | 
| 360 345 | 
             
                  end
         | 
| 361 346 |  | 
| 362 347 | 
             
                  # Draws the list items onto the canvas at position [x, y].
         | 
| @@ -41,7 +41,10 @@ require 'hexapdf/layout/frame' | |
| 41 41 | 
             
            module HexaPDF
         | 
| 42 42 | 
             
              module Layout
         | 
| 43 43 |  | 
| 44 | 
            -
                # A PageStyle defines the  | 
| 44 | 
            +
                # A PageStyle defines the dimensions of a page, its initial look, the Frame for object placement
         | 
| 45 | 
            +
                # and which page style should be used next.
         | 
| 46 | 
            +
                #
         | 
| 47 | 
            +
                # This class is used by HexaPDF::Composer to style the individual pages.
         | 
| 45 48 | 
             
                class PageStyle
         | 
| 46 49 |  | 
| 47 50 | 
             
                  # The page size.
         | 
| @@ -65,14 +68,15 @@ module HexaPDF | |
| 65 68 | 
             
                  # The callable object is given a canvas and the page style as arguments. It needs to draw the
         | 
| 66 69 | 
             
                  # initial content of the page. Note that the graphics state of the canvas is *not* saved
         | 
| 67 70 | 
             
                  # before executing the template code and restored afterwards. If this is needed, the object
         | 
| 68 | 
            -
                  # needs to do it itself.
         | 
| 71 | 
            +
                  # needs to do it itself. The #next_style attribute can optionally be set.
         | 
| 69 72 | 
             
                  #
         | 
| 70 | 
            -
                  # Furthermore  | 
| 71 | 
            -
                  #  | 
| 73 | 
            +
                  # Furthermore, the callable object should set the #frame that defines the area on the page
         | 
| 74 | 
            +
                  # where content should be placed. The #create_frame method can be used for easily creating a
         | 
| 75 | 
            +
                  # rectangular frame.
         | 
| 72 76 | 
             
                  #
         | 
| 73 77 | 
             
                  # Example:
         | 
| 74 78 | 
             
                  #
         | 
| 75 | 
            -
                  #   page_style.template = lambda do |canvas, style
         | 
| 79 | 
            +
                  #   page_style.template = lambda do |canvas, style|
         | 
| 76 80 | 
             
                  #     box = canvas.context.box
         | 
| 77 81 | 
             
                  #     canvas.fill_color("fd0") do
         | 
| 78 82 | 
             
                  #       canvas.rectangle(0, 0, box.width, box.height).fill
         | 
| @@ -81,22 +85,24 @@ module HexaPDF | |
| 81 85 | 
             
                  #   end
         | 
| 82 86 | 
             
                  attr_accessor :template
         | 
| 83 87 |  | 
| 84 | 
            -
                  # The  | 
| 85 | 
            -
                  # placed.
         | 
| 88 | 
            +
                  # The Frame object that defines the area for the last page created with #create_page where
         | 
| 89 | 
            +
                  # content should be placed.
         | 
| 86 90 | 
             
                  #
         | 
| 87 | 
            -
                  # This  | 
| 88 | 
            -
                  #
         | 
| 89 | 
            -
                  #  | 
| 90 | 
            -
                  # is set during #create_page.
         | 
| 91 | 
            +
                  # This value is usually updated during execution of the #template. If the value is not
         | 
| 92 | 
            +
                  # updated, a frame covering the page except for a default margin on all sides is set during
         | 
| 93 | 
            +
                  # #create_page.
         | 
| 91 94 | 
             
                  attr_accessor :frame
         | 
| 92 95 |  | 
| 93 96 | 
             
                  # Defines the name of the page style that should be used for the next page.
         | 
| 94 97 | 
             
                  #
         | 
| 98 | 
            +
                  # Note that this value can be different each time a new page is created via #create_page.
         | 
| 99 | 
            +
                  #
         | 
| 95 100 | 
             
                  # If this attribute is +nil+ (the default), it means that this style should be used again.
         | 
| 96 101 | 
             
                  attr_accessor :next_style
         | 
| 97 102 |  | 
| 98 103 | 
             
                  # Creates a new page style instance for the given page size, orientation and next style
         | 
| 99 | 
            -
                  # values. If a block is given, it is used as template for defining the initial content | 
| 104 | 
            +
                  # values. If a block is given, it is used as #template for defining the initial content of a
         | 
| 105 | 
            +
                  # page.
         | 
| 100 106 | 
             
                  #
         | 
| 101 107 | 
             
                  # Example:
         | 
| 102 108 | 
             
                  #
         | 
| @@ -113,14 +119,15 @@ module HexaPDF | |
| 113 119 | 
             
                    @next_style = next_style
         | 
| 114 120 | 
             
                  end
         | 
| 115 121 |  | 
| 116 | 
            -
                  # Creates a new page in the given document  | 
| 122 | 
            +
                  # Creates a new page in the given document using this page style and returns it.
         | 
| 117 123 | 
             
                  #
         | 
| 118 | 
            -
                  # If #frame has not  | 
| 119 | 
            -
                  #  | 
| 124 | 
            +
                  # If the #frame has not changed during execution of the #template, a default frame covering
         | 
| 125 | 
            +
                  # the whole page except a margin of 36 is assigned.
         | 
| 120 126 | 
             
                  def create_page(document)
         | 
| 127 | 
            +
                    frame_before = @frame
         | 
| 121 128 | 
             
                    page = document.pages.create(media_box: page_size, orientation: orientation)
         | 
| 122 129 | 
             
                    template&.call(page.canvas, self)
         | 
| 123 | 
            -
                    self.frame  | 
| 130 | 
            +
                    self.frame = create_frame(page, 36) if @frame.equal?(frame_before)
         | 
| 124 131 | 
             
                    page
         | 
| 125 132 | 
             
                  end
         | 
| 126 133 |  | 
    
        data/lib/hexapdf/layout/style.rb
    CHANGED
    
    | @@ -393,7 +393,7 @@ module HexaPDF | |
| 393 393 | 
             
                  # The object resolved in this way needs to respond to #call(canvas, box) where +canvas+ is the
         | 
| 394 394 | 
             
                  # HexaPDF::Content::Canvas object on which it should be drawn and +box+ is a box-like object
         | 
| 395 395 | 
             
                  # (e.g. Box or TextFragment). The coordinate system is translated so that the origin is at the
         | 
| 396 | 
            -
                  # bottom | 
| 396 | 
            +
                  # bottom-left corner of the box during the drawing operations.
         | 
| 397 397 | 
             
                  class Layers
         | 
| 398 398 |  | 
| 399 399 | 
             
                    # Creates a new Layers object popuplated with the given +layers+.
         | 
| @@ -1268,8 +1268,8 @@ module HexaPDF | |
| 1268 1268 | 
             
                  #       composer.lorem_ipsum(position: :flow)
         | 
| 1269 1269 | 
             
                  #
         | 
| 1270 1270 | 
             
                  # [x, y]::
         | 
| 1271 | 
            -
                  #     Position the box with the bottom | 
| 1272 | 
            -
                  #     the bottom | 
| 1271 | 
            +
                  #     Position the box with the bottom-left corner at the given absolute position relative to
         | 
| 1272 | 
            +
                  #     the bottom-left corner of the frame.
         | 
| 1273 1273 | 
             
                  #
         | 
| 1274 1274 | 
             
                  #     Examples:
         | 
| 1275 1275 | 
             
                  #
         | 
| @@ -1369,8 +1369,8 @@ module HexaPDF | |
| 1369 1369 | 
             
                  #       composer.text('Text underneath')
         | 
| 1370 1370 | 
             
                  #
         | 
| 1371 1371 | 
             
                  # :fill_frame_horizontal::
         | 
| 1372 | 
            -
                  #     The mask covers the box including the margin around the box  | 
| 1373 | 
            -
                  #      | 
| 1372 | 
            +
                  #     The mask covers the box including the margin around the box, the space to the left and
         | 
| 1373 | 
            +
                  #     right in the frame and the space to the top of the current region.
         | 
| 1374 1374 | 
             
                  #
         | 
| 1375 1375 | 
             
                  #     Examples:
         | 
| 1376 1376 | 
             
                  #
         | 
| @@ -228,18 +228,22 @@ module HexaPDF | |
| 228 228 | 
             
                      case children
         | 
| 229 229 | 
             
                      when Box
         | 
| 230 230 | 
             
                        child_result = frame.fit(children)
         | 
| 231 | 
            -
                         | 
| 232 | 
            -
             | 
| 233 | 
            -
             | 
| 234 | 
            -
             | 
| 231 | 
            +
                        if child_result.success?
         | 
| 232 | 
            +
                          @preferred_width = child_result.x + child_result.box.width + reserved_width
         | 
| 233 | 
            +
                          @height = @preferred_height = child_result.box.height + reserved_height
         | 
| 234 | 
            +
                          @fit_results = [child_result]
         | 
| 235 | 
            +
                          fit_result.success!
         | 
| 236 | 
            +
                        end
         | 
| 235 237 | 
             
                      when Array
         | 
| 236 238 | 
             
                        box_fitter = BoxFitter.new([frame])
         | 
| 237 239 | 
             
                        children.each {|box| box_fitter.fit(box) }
         | 
| 238 | 
            -
                         | 
| 239 | 
            -
             | 
| 240 | 
            -
             | 
| 241 | 
            -
             | 
| 242 | 
            -
             | 
| 240 | 
            +
                        if box_fitter.success?
         | 
| 241 | 
            +
                          max_x_result = box_fitter.fit_results.max_by {|result| result.x + result.box.width }
         | 
| 242 | 
            +
                          @preferred_width = max_x_result.x + max_x_result.box.width + reserved_width
         | 
| 243 | 
            +
                          @height = @preferred_height = box_fitter.content_heights[0] + reserved_height
         | 
| 244 | 
            +
                          @fit_results = box_fitter.fit_results
         | 
| 245 | 
            +
                          fit_result.success!
         | 
| 246 | 
            +
                        end
         | 
| 243 247 | 
             
                      else
         | 
| 244 248 | 
             
                        @preferred_width = reserved_width
         | 
| 245 249 | 
             
                        @height = @preferred_height = reserved_height
         | 
| @@ -590,7 +594,7 @@ module HexaPDF | |
| 590 594 | 
             
                  def fit_content(_available_width, _available_height, frame)
         | 
| 591 595 | 
             
                    # Adjust reserved width/height to include space used by the edge cells for their border
         | 
| 592 596 | 
             
                    # since cell borders are drawn on the bounds and not inside.
         | 
| 593 | 
            -
                    # This uses the top | 
| 597 | 
            +
                    # This uses the top-left and bottom-right cells and so might not be correct in all cases.
         | 
| 594 598 | 
             
                    @cell_tl_border_width = @cells[0, 0].style.border.width
         | 
| 595 599 | 
             
                    cell_br_border_width = @cells[-1, -1].style.border.width
         | 
| 596 600 | 
             
                    rw = (@cell_tl_border_width.left + cell_br_border_width.right) / 2.0
         | 
| @@ -42,12 +42,46 @@ module HexaPDF | |
| 42 42 | 
             
                # A TextBox is used for drawing text, either inside a rectangular box or by flowing it around
         | 
| 43 43 | 
             
                # objects of a Frame.
         | 
| 44 44 | 
             
                #
         | 
| 45 | 
            +
                # The standard usage is through the helper methods Document::Layout#text and
         | 
| 46 | 
            +
                # Document::Layout#formatted_text.
         | 
| 47 | 
            +
                #
         | 
| 45 48 | 
             
                # This class uses TextLayouter behind the scenes to do the hard work.
         | 
| 46 49 | 
             
                #
         | 
| 47 50 | 
             
                # == Used Box Properties
         | 
| 48 51 | 
             
                #
         | 
| 49 52 | 
             
                # The spacing after the last line can be controlled via the style property +last_line_gap+. Also
         | 
| 50 53 | 
             
                # see TextLayouter#style for other style properties taken into account.
         | 
| 54 | 
            +
                #
         | 
| 55 | 
            +
                # == Limitations
         | 
| 56 | 
            +
                #
         | 
| 57 | 
            +
                # When setting the style property 'position' to +:flow+, padding and border to the left and
         | 
| 58 | 
            +
                # right as well as a predefined fixed width are not respected and the result will look wrong.
         | 
| 59 | 
            +
                #
         | 
| 60 | 
            +
                # == Examples
         | 
| 61 | 
            +
                #
         | 
| 62 | 
            +
                # Showing some text:
         | 
| 63 | 
            +
                #
         | 
| 64 | 
            +
                #   #>pdf-composer
         | 
| 65 | 
            +
                #   composer.box(:text, items: layout.text_fragments("This is some text."))
         | 
| 66 | 
            +
                #   # Or easier with the provided convenience method
         | 
| 67 | 
            +
                #   composer.text("This is also some text")
         | 
| 68 | 
            +
                #
         | 
| 69 | 
            +
                # It is possible to flow the text around other objects by using the style property
         | 
| 70 | 
            +
                # 'position' with the value +:flow+:
         | 
| 71 | 
            +
                #
         | 
| 72 | 
            +
                #   #>pdf-composer
         | 
| 73 | 
            +
                #   composer.box(:base, width: 30, height: 30,
         | 
| 74 | 
            +
                #                style: {margin: 5, position: :float, background_color: "hp-blue-light"})
         | 
| 75 | 
            +
                #   composer.text("This is some text. " * 20, position: :flow)
         | 
| 76 | 
            +
                #
         | 
| 77 | 
            +
                # While top and bottom padding and border can be used with flow positioning, left and right
         | 
| 78 | 
            +
                # padding and border are not supported and the result will look wrong:
         | 
| 79 | 
            +
                #
         | 
| 80 | 
            +
                #   #>pdf-composer
         | 
| 81 | 
            +
                #   composer.box(:base, width: 30, height: 30,
         | 
| 82 | 
            +
                #                style: {margin: 5, position: :float, background_color: "hp-blue-light"})
         | 
| 83 | 
            +
                #   composer.text("This is some text. " * 20, padding: 10, position: :flow,
         | 
| 84 | 
            +
                #                 text_align: :justify)
         | 
| 51 85 | 
             
                class TextBox < Box
         | 
| 52 86 |  | 
| 53 87 | 
             
                  # Creates a new TextBox object with the given inline items (e.g. TextFragment and InlineBox
         | 
| @@ -72,11 +106,6 @@ module HexaPDF | |
| 72 106 | 
             
                    true
         | 
| 73 107 | 
             
                  end
         | 
| 74 108 |  | 
| 75 | 
            -
                  # :nodoc:
         | 
| 76 | 
            -
                  def draw(canvas, x, y)
         | 
| 77 | 
            -
                    super(canvas, x + @x_offset, y)
         | 
| 78 | 
            -
                  end
         | 
| 79 | 
            -
             | 
| 80 109 | 
             
                  # :nodoc:
         | 
| 81 110 | 
             
                  def empty?
         | 
| 82 111 | 
             
                    super && (!@result || @result.lines.empty?)
         | 
| @@ -89,40 +118,35 @@ module HexaPDF | |
| 89 118 | 
             
                  # Depending on the 'position' style property, the text is either fit into the current region
         | 
| 90 119 | 
             
                  # of the frame using +available_width+ and +available_height+, or fit to the shape of the
         | 
| 91 120 | 
             
                  # frame starting from the top (when 'position' is set to :flow).
         | 
| 92 | 
            -
                  def fit_content( | 
| 121 | 
            +
                  def fit_content(_available_width, _available_height, frame)
         | 
| 93 122 | 
             
                    frame = frame.child_frame(box: self)
         | 
| 94 | 
            -
                    @ | 
| 123 | 
            +
                    @x_offset = 0
         | 
| 95 124 |  | 
| 96 | 
            -
                     | 
| 97 | 
            -
             | 
| 125 | 
            +
                    if style.position == :flow
         | 
| 126 | 
            +
                      height = (@initial_height > 0 ? @initial_height : frame.shape.bbox.height) - reserved_height
         | 
| 127 | 
            +
                      @result = @tl.fit(@items, frame.width_specification(reserved_height_top), height,
         | 
| 128 | 
            +
                                        apply_first_text_indent: !split_box?, frame: frame)
         | 
| 129 | 
            +
                      min_x = +Float::INFINITY
         | 
| 130 | 
            +
                      max_x = -Float::INFINITY
         | 
| 131 | 
            +
                      @result.lines.each do |line|
         | 
| 132 | 
            +
                        min_x = [min_x, line.x_offset].min
         | 
| 133 | 
            +
                        max_x = [max_x, line.x_offset + line.width].max
         | 
| 134 | 
            +
                      end
         | 
| 135 | 
            +
                      @width = (min_x.finite? ? max_x - min_x : 0) + reserved_width
         | 
| 136 | 
            +
                      fit_result.x = @x_offset = min_x
         | 
| 137 | 
            +
                      @height = @initial_height > 0 ? @initial_height : @result.height + reserved_height
         | 
| 138 | 
            +
                    else
         | 
| 139 | 
            +
                      @result = @tl.fit(@items, @width - reserved_width, @height - reserved_height,
         | 
| 98 140 | 
             
                                        apply_first_text_indent: !split_box?, frame: frame)
         | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
             | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
                     | 
| 108 | 
            -
                                width
         | 
| 109 | 
            -
                              elsif style.position == :flow
         | 
| 110 | 
            -
                                min_x = +Float::INFINITY
         | 
| 111 | 
            -
                                max_x = -Float::INFINITY
         | 
| 112 | 
            -
                                @result.lines.each do |line|
         | 
| 113 | 
            -
                                  min_x = [min_x, line.x_offset].min
         | 
| 114 | 
            -
                                  max_x = [max_x, line.x_offset + line.width].max
         | 
| 115 | 
            -
                                end
         | 
| 116 | 
            -
                                min_x.finite? ? (@x_offset = min_x; max_x - min_x) : 0
         | 
| 117 | 
            -
                              else
         | 
| 118 | 
            -
                                @result.lines.max_by(&:width)&.width || 0
         | 
| 119 | 
            -
                              end
         | 
| 120 | 
            -
                    @height += if @initial_height > 0 || style.text_valign == :center || style.text_valign == :bottom
         | 
| 121 | 
            -
                                 height
         | 
| 122 | 
            -
                               else
         | 
| 123 | 
            -
                                 @result.height
         | 
| 124 | 
            -
                               end
         | 
| 125 | 
            -
                    if style.last_line_gap && @result.lines.last
         | 
| 141 | 
            +
                      if style.text_align == :left && @initial_width == 0
         | 
| 142 | 
            +
                        @width = (@result.lines.max_by(&:width)&.width || 0) + reserved_width
         | 
| 143 | 
            +
                      end
         | 
| 144 | 
            +
                      if style.text_valign == :top && @initial_height == 0
         | 
| 145 | 
            +
                        @height = @result.height + reserved_height
         | 
| 146 | 
            +
                      end
         | 
| 147 | 
            +
                    end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                    if style.last_line_gap && @result.lines.last && @initial_height == 0
         | 
| 126 150 | 
             
                      @height += style.line_spacing.gap(@result.lines.last, @result.lines.last)
         | 
| 127 151 | 
             
                    end
         | 
| 128 152 |  | 
| @@ -52,7 +52,7 @@ module HexaPDF | |
| 52 52 | 
             
                # The items of a text fragment may be frozen to indicate that the fragment is potentially used
         | 
| 53 53 | 
             
                # multiple times.
         | 
| 54 54 | 
             
                #
         | 
| 55 | 
            -
                # The rectangle with the bottom | 
| 55 | 
            +
                # The rectangle with the bottom-left corner (#x_min, #y_min) and the top-right corner (#x_max,
         | 
| 56 56 | 
             
                # #y_max) describes the minimum bounding box of the whole text fragment and is usually *not*
         | 
| 57 57 | 
             
                # equal to the box (0, 0)-(#width, #height).
         | 
| 58 58 | 
             
                class TextFragment
         | 
| @@ -218,7 +218,10 @@ module HexaPDF | |
| 218 218 |  | 
| 219 219 | 
             
                    # Breaks are detected at: space, tab, zero-width-space, non-breaking space, hyphen,
         | 
| 220 220 | 
             
                    # soft-hypen and any valid Unicode newline separator
         | 
| 221 | 
            -
                     | 
| 221 | 
            +
                    BREAK_CHARS = {}
         | 
| 222 | 
            +
                    " \u{A}\u{B}\u{C}\u{D}\u{85}\u{2028}\u{2029}\t\u{200B}\u{00AD}\u{00A0}-".each_char do |c|
         | 
| 223 | 
            +
                      BREAK_CHARS[c] = true
         | 
| 224 | 
            +
                    end
         | 
| 222 225 |  | 
| 223 226 | 
             
                    # Breaks the items (an array of InlineBox and TextFragment objects) into atomic pieces
         | 
| 224 227 | 
             
                    # wrapped by Box, Glue or Penalty items, and returns those as an array.
         | 
| @@ -235,7 +238,7 @@ module HexaPDF | |
| 235 238 | 
             
                            # Collect characters and kerning values until break character is encountered
         | 
| 236 239 | 
             
                            box_items = []
         | 
| 237 240 | 
             
                            while (glyph = item.items[i]) &&
         | 
| 238 | 
            -
                                (glyph.kind_of?(Numeric) || ! | 
| 241 | 
            +
                                (glyph.kind_of?(Numeric) || !BREAK_CHARS.key?(glyph.str))
         | 
| 239 242 | 
             
                              box_items << glyph
         | 
| 240 243 | 
             
                              i += 1
         | 
| 241 244 | 
             
                            end
         | 
| @@ -428,9 +431,7 @@ module HexaPDF | |
| 428 431 | 
             
                      end
         | 
| 429 432 |  | 
| 430 433 | 
             
                      line = create_unjustified_line
         | 
| 431 | 
            -
                      last_line_used = true
         | 
| 432 | 
            -
                      last_line_used = yield(line, nil) if item.nil? && !line.items.empty?
         | 
| 433 | 
            -
             | 
| 434 | 
            +
                      last_line_used = (item.nil? && !line.items.empty? ? yield(line, nil) : true)
         | 
| 434 435 | 
             
                      item.nil? && last_line_used ? [] : @items[@beginning_of_line_index..-1]
         | 
| 435 436 | 
             
                    end
         | 
| 436 437 |  | 
| @@ -500,9 +501,7 @@ module HexaPDF | |
| 500 501 | 
             
                      end
         | 
| 501 502 |  | 
| 502 503 | 
             
                      line = create_unjustified_line
         | 
| 503 | 
            -
                      last_line_used = true
         | 
| 504 | 
            -
                      last_line_used = yield(line, nil) if item.nil? && !line.items.empty?
         | 
| 505 | 
            -
             | 
| 504 | 
            +
                      last_line_used = (item.nil? && !line.items.empty? ? yield(line, nil) : true)
         | 
| 506 505 | 
             
                      item.nil? && last_line_used ? [] : @items[@beginning_of_line_index..-1]
         | 
| 507 506 | 
             
                    end
         | 
| 508 507 |  | 
    
        data/lib/hexapdf/parser.rb
    CHANGED
    
    | @@ -71,6 +71,10 @@ module HexaPDF | |
| 71 71 | 
             
                end
         | 
| 72 72 |  | 
| 73 73 | 
             
                # Returns +true+ if the PDF file is a linearized file.
         | 
| 74 | 
            +
                #
         | 
| 75 | 
            +
                # Note: The method uses heuristics to determine whether a PDF file is linearized. In case of
         | 
| 76 | 
            +
                # slightly invalid or damaged PDFs that HexaPDF can recover from it is possible that this method
         | 
| 77 | 
            +
                # returns +true+ even though the PDF isn't actually linearized.
         | 
| 74 78 | 
             
                def linearized?
         | 
| 75 79 | 
             
                  @linearized ||=
         | 
| 76 80 | 
             
                    begin
         | 
| @@ -293,7 +297,7 @@ module HexaPDF | |
| 293 297 | 
             
                        next
         | 
| 294 298 | 
             
                      elsif type == 'n'
         | 
| 295 299 | 
             
                        if pos == 0 || gen > 65535
         | 
| 296 | 
            -
                          maybe_raise("Invalid in use cross-reference entry",
         | 
| 300 | 
            +
                          maybe_raise("Invalid in use cross-reference entry for object number #{oid}",
         | 
| 297 301 | 
             
                                      pos: @tokenizer.pos)
         | 
| 298 302 | 
             
                          xref.add_free_entry(oid, gen)
         | 
| 299 303 | 
             
                        else
         | 
    
        data/lib/hexapdf/rectangle.rb
    CHANGED
    
    | @@ -48,8 +48,8 @@ module HexaPDF | |
| 48 48 | 
             
              #
         | 
| 49 49 | 
             
              #   [left, bottom, right, top]
         | 
| 50 50 | 
             
              #
         | 
| 51 | 
            -
              # where +left+ is the bottom | 
| 52 | 
            -
              # is the top | 
| 51 | 
            +
              # where +left+ is the bottom-left x-coordinate, +bottom+ is the bottom-left y-coordinate, +right+
         | 
| 52 | 
            +
              # is the top-right x-coordinate and +top+ is the top-right y-coordinate.
         | 
| 53 53 | 
             
              #
         | 
| 54 54 | 
             
              # See: PDF2.0 s7.9.5
         | 
| 55 55 | 
             
              class Rectangle < HexaPDF::PDFArray
         | 
| @@ -119,8 +119,8 @@ module HexaPDF | |
| 119 119 | 
             
                #:nodoc:
         | 
| 120 120 | 
             
                RECTANGLE_ERROR_MSG = "A PDF rectangle structure must contain an array of four numbers"
         | 
| 121 121 |  | 
| 122 | 
            -
                # Ensures that the value is an array containing four numbers that specify the bottom | 
| 123 | 
            -
                # top | 
| 122 | 
            +
                # Ensures that the value is an array containing four numbers that specify the bottom-left and
         | 
| 123 | 
            +
                # top-right corners.
         | 
| 124 124 | 
             
                def after_data_change
         | 
| 125 125 | 
             
                  super
         | 
| 126 126 | 
             
                  unless value.size == 4 && all? {|v| v.kind_of?(Numeric) }
         | 
    
        data/lib/hexapdf/revisions.rb
    CHANGED
    
    | @@ -97,7 +97,7 @@ module HexaPDF | |
| 97 97 | 
             
                          merge_revision = offset
         | 
| 98 98 | 
             
                        end
         | 
| 99 99 |  | 
| 100 | 
            -
                        if merge_revision == offset
         | 
| 100 | 
            +
                        if merge_revision == offset && !revisions.empty?
         | 
| 101 101 | 
             
                          xref_section.merge!(revisions.first.xref_section)
         | 
| 102 102 | 
             
                          offset = trailer[:Prev] # Get possible next offset before overwriting trailer
         | 
| 103 103 | 
             
                          trailer = revisions.first.trailer
         | 
    
        data/lib/hexapdf/stream.rb
    CHANGED
    
    | @@ -51,6 +51,9 @@ module HexaPDF | |
| 51 51 | 
             
              # normalized to arrays on assignment to ease further processing.
         | 
| 52 52 | 
             
              class StreamData
         | 
| 53 53 |  | 
| 54 | 
            +
                # The source.
         | 
| 55 | 
            +
                attr_reader :source
         | 
| 56 | 
            +
             | 
| 54 57 | 
             
                # The filter(s) that need to be applied for getting the decoded stream data.
         | 
| 55 58 | 
             
                attr_reader :filter
         | 
| 56 59 |  | 
| @@ -110,9 +113,6 @@ module HexaPDF | |
| 110 113 |  | 
| 111 114 | 
             
                protected
         | 
| 112 115 |  | 
| 113 | 
            -
                # The source.
         | 
| 114 | 
            -
                attr_reader :source
         | 
| 115 | 
            -
             | 
| 116 116 | 
             
                # The optional offset into the bytes provided by source.
         | 
| 117 117 | 
             
                attr_reader :offset
         | 
| 118 118 |  | 
    
        data/lib/hexapdf/tokenizer.rb
    CHANGED
    
    | @@ -82,6 +82,7 @@ module HexaPDF | |
| 82 82 | 
             
                # correctable situations are only raised if the return value of calling the object is +true+.
         | 
| 83 83 | 
             
                def initialize(io, on_correctable_error: nil)
         | 
| 84 84 | 
             
                  @io = io
         | 
| 85 | 
            +
                  @io_chunk = String.new(''.b)
         | 
| 85 86 | 
             
                  @ss = StringScanner.new(''.b)
         | 
| 86 87 | 
             
                  @original_pos = -1
         | 
| 87 88 | 
             
                  @on_correctable_error = on_correctable_error || proc { false }
         | 
| @@ -439,9 +440,9 @@ module HexaPDF | |
| 439 440 | 
             
                  @io.seek(@next_read_pos)
         | 
| 440 441 | 
             
                  return false if @io.eof?
         | 
| 441 442 |  | 
| 442 | 
            -
                  @ss << @io.read(8192)
         | 
| 443 | 
            +
                  @ss << @io.read(8192, @io_chunk)
         | 
| 443 444 | 
             
                  if @ss.pos > 8192 && @ss.string.length > 16384
         | 
| 444 | 
            -
                    @ss.string. | 
| 445 | 
            +
                    @ss.string.replace(@ss.string.byteslice(8192..-1))
         | 
| 445 446 | 
             
                    @ss.pos -= 8192
         | 
| 446 447 | 
             
                    @original_pos += 8192
         | 
| 447 448 | 
             
                  end
         | 
| @@ -65,6 +65,8 @@ module HexaPDF | |
| 65 65 | 
             
                  #
         | 
| 66 66 | 
             
                  # == Type Specific Field Flags
         | 
| 67 67 | 
             
                  #
         | 
| 68 | 
            +
                  # See the class description for Field for the general field flags.
         | 
| 69 | 
            +
                  #
         | 
| 68 70 | 
             
                  # :no_toggle_to_off:: Only used with radio buttons fields. If this flag is set, one button
         | 
| 69 71 | 
             
                  #                     needs to be selected at all times. Otherwise, clicking on the selected
         | 
| 70 72 | 
             
                  #                     button deselects it.
         | 
| @@ -51,6 +51,8 @@ module HexaPDF | |
| 51 51 | 
             
                  #
         | 
| 52 52 | 
             
                  # == Type Specific Field Flags
         | 
| 53 53 | 
             
                  #
         | 
| 54 | 
            +
                  # See the class description for Field for the general field flags.
         | 
| 55 | 
            +
                  #
         | 
| 54 56 | 
             
                  # :combo:: If set, the field represents a combo box.
         | 
| 55 57 | 
             
                  #
         | 
| 56 58 | 
             
                  # :edit:: If set, the combo box includes an editable text box for entering arbitrary values.
         |