hexapdf 1.1.1 → 1.3.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 +79 -0
- data/README.md +1 -1
- data/lib/hexapdf/cli/command.rb +63 -63
- data/lib/hexapdf/cli/inspect.rb +14 -5
- data/lib/hexapdf/cli/modify.rb +0 -1
- data/lib/hexapdf/cli/optimize.rb +5 -5
- data/lib/hexapdf/composer.rb +14 -0
- data/lib/hexapdf/configuration.rb +26 -0
- data/lib/hexapdf/content/graphics_state.rb +1 -1
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +1 -1
- data/lib/hexapdf/document/annotations.rb +173 -0
- data/lib/hexapdf/document/layout.rb +45 -6
- data/lib/hexapdf/document.rb +28 -7
- data/lib/hexapdf/error.rb +11 -3
- data/lib/hexapdf/font/true_type/subsetter.rb +15 -2
- data/lib/hexapdf/font/true_type_wrapper.rb +1 -0
- data/lib/hexapdf/font/type1_wrapper.rb +1 -0
- data/lib/hexapdf/layout/style.rb +101 -7
- data/lib/hexapdf/object.rb +2 -2
- data/lib/hexapdf/pdf_array.rb +25 -3
- data/lib/hexapdf/tokenizer.rb +4 -1
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +57 -8
- data/lib/hexapdf/type/acro_form/field.rb +1 -0
- data/lib/hexapdf/type/acro_form/form.rb +7 -6
- data/lib/hexapdf/type/acro_form/java_script_actions.rb +9 -2
- data/lib/hexapdf/type/acro_form/text_field.rb +9 -2
- data/lib/hexapdf/type/annotation.rb +71 -1
- data/lib/hexapdf/type/annotations/appearance_generator.rb +348 -0
- data/lib/hexapdf/type/annotations/border_effect.rb +99 -0
- data/lib/hexapdf/type/annotations/border_styling.rb +160 -0
- data/lib/hexapdf/type/annotations/circle.rb +65 -0
- data/lib/hexapdf/type/annotations/interior_color.rb +84 -0
- data/lib/hexapdf/type/annotations/line.rb +490 -0
- data/lib/hexapdf/type/annotations/square.rb +65 -0
- data/lib/hexapdf/type/annotations/square_circle.rb +77 -0
- data/lib/hexapdf/type/annotations/widget.rb +52 -116
- data/lib/hexapdf/type/annotations.rb +8 -0
- data/lib/hexapdf/type/form.rb +2 -2
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +0 -1
- data/lib/hexapdf/xref_section.rb +7 -4
- data/test/hexapdf/content/test_graphics_state.rb +2 -3
- data/test/hexapdf/content/test_operator.rb +4 -5
- data/test/hexapdf/digital_signature/test_cms_handler.rb +7 -8
- data/test/hexapdf/digital_signature/test_handler.rb +2 -3
- data/test/hexapdf/digital_signature/test_pkcs1_handler.rb +1 -2
- data/test/hexapdf/document/test_annotations.rb +55 -0
- data/test/hexapdf/document/test_layout.rb +24 -2
- data/test/hexapdf/font/test_true_type_wrapper.rb +7 -0
- data/test/hexapdf/font/test_type1_wrapper.rb +7 -0
- data/test/hexapdf/font/true_type/test_subsetter.rb +10 -0
- data/test/hexapdf/layout/test_style.rb +27 -2
- data/test/hexapdf/task/test_optimize.rb +1 -1
- data/test/hexapdf/test_composer.rb +7 -0
- data/test/hexapdf/test_document.rb +11 -3
- data/test/hexapdf/test_object.rb +1 -1
- data/test/hexapdf/test_pdf_array.rb +36 -3
- data/test/hexapdf/test_stream.rb +1 -2
- data/test/hexapdf/test_xref_section.rb +1 -1
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +78 -3
- data/test/hexapdf/type/acro_form/test_button_field.rb +7 -6
- data/test/hexapdf/type/acro_form/test_field.rb +5 -0
- data/test/hexapdf/type/acro_form/test_form.rb +17 -1
- data/test/hexapdf/type/acro_form/test_java_script_actions.rb +21 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +7 -1
- data/test/hexapdf/type/annotations/test_appearance_generator.rb +482 -0
- data/test/hexapdf/type/annotations/test_border_effect.rb +59 -0
- data/test/hexapdf/type/annotations/test_border_styling.rb +114 -0
- data/test/hexapdf/type/annotations/test_interior_color.rb +37 -0
- data/test/hexapdf/type/annotations/test_line.rb +169 -0
- data/test/hexapdf/type/annotations/test_widget.rb +35 -81
- data/test/hexapdf/type/test_annotation.rb +55 -0
- data/test/hexapdf/type/test_form.rb +6 -0
- metadata +17 -2
| @@ -0,0 +1,173 @@ | |
| 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-2025 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 | 
            +
             | 
| 37 | 
            +
            require 'hexapdf/dictionary'
         | 
| 38 | 
            +
            require 'hexapdf/error'
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            module HexaPDF
         | 
| 41 | 
            +
              class Document
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                # This class provides methods for creating and managing the annotations of a PDF file.
         | 
| 44 | 
            +
                #
         | 
| 45 | 
            +
                # An annotation is an object that can be added to a certain location on a page, provides a
         | 
| 46 | 
            +
                # visual appearance and allows for interaction with the user via keyboard and mouse.
         | 
| 47 | 
            +
                #
         | 
| 48 | 
            +
                # == Usage
         | 
| 49 | 
            +
                #
         | 
| 50 | 
            +
                # To create an annotation either call the general #create method or a specific creation method
         | 
| 51 | 
            +
                # for an annotation type. After the annotation has been created customize it using the
         | 
| 52 | 
            +
                # convenience methods on the annotation object. The last step should be the call to
         | 
| 53 | 
            +
                # +regenerate_appearance+ so that the appearance is generated.
         | 
| 54 | 
            +
                #
         | 
| 55 | 
            +
                # See: PDF2.0 s12.5
         | 
| 56 | 
            +
                class Annotations
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  include Enumerable
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  # Creates a new Annotations object for the given PDF document.
         | 
| 61 | 
            +
                  def initialize(document)
         | 
| 62 | 
            +
                    @document = document
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  # :call-seq:
         | 
| 66 | 
            +
                  #   annotations.create(type, page, **options)      -> annotation
         | 
| 67 | 
            +
                  #
         | 
| 68 | 
            +
                  # Creates a new annotation object with the given +type+ and +page+ by calling the respective
         | 
| 69 | 
            +
                  # +create_type+ method.
         | 
| 70 | 
            +
                  #
         | 
| 71 | 
            +
                  # The +options+ are passed on the specific annotation creation method.
         | 
| 72 | 
            +
                  def create(type, page, *args, **options)
         | 
| 73 | 
            +
                    method_name = "create_#{type}"
         | 
| 74 | 
            +
                    unless respond_to?(method_name)
         | 
| 75 | 
            +
                      raise ArgumentError, "Invalid type specified"
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
                    send("create_#{type}", page, *args, **options)
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  # :call-seq:
         | 
| 81 | 
            +
                  #   annotations.create_line(page, start_point:, end_point:)  -> annotation
         | 
| 82 | 
            +
                  #
         | 
| 83 | 
            +
                  # Creates a line annotation from +start_point+ to +end_point+ on the given page and returns
         | 
| 84 | 
            +
                  # it.
         | 
| 85 | 
            +
                  #
         | 
| 86 | 
            +
                  # The line uses a black color and a width of 1pt. It can be further styled using the
         | 
| 87 | 
            +
                  # convenience methods on the returned annotation object.
         | 
| 88 | 
            +
                  #
         | 
| 89 | 
            +
                  # Example:
         | 
| 90 | 
            +
                  #
         | 
| 91 | 
            +
                  #   doc.annotations.create_line(doc.pages[0], start_point: [100, 100], end_point: [130, 180]).
         | 
| 92 | 
            +
                  #     border_style(color: "blue", width: 2).
         | 
| 93 | 
            +
                  #     leader_line_length(10).
         | 
| 94 | 
            +
                  #     regenerate_appearance
         | 
| 95 | 
            +
                  #
         | 
| 96 | 
            +
                  # See: Type::Annotations::Line
         | 
| 97 | 
            +
                  def create_line(page, start_point:, end_point:)
         | 
| 98 | 
            +
                    create_and_add_to_page(:Line, page).
         | 
| 99 | 
            +
                      line(*start_point, *end_point).
         | 
| 100 | 
            +
                      border_style(color: 0, width: 1)
         | 
| 101 | 
            +
                  end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  # :call-seq:
         | 
| 104 | 
            +
                  #   annotations.create_rectangle(page, x, y, width, height)  -> annotation
         | 
| 105 | 
            +
                  #
         | 
| 106 | 
            +
                  # Creates a rectangle (called "square" in the PDF specification) annotation with the
         | 
| 107 | 
            +
                  # lower-left corner at (+x+, +y+) and the given +width+ and +height+.
         | 
| 108 | 
            +
                  #
         | 
| 109 | 
            +
                  # The rectangle uses a black stroke color, no interior color and a line width of 1pt by
         | 
| 110 | 
            +
                  # default. It can be further styled using the convenience methods on the returned annotation
         | 
| 111 | 
            +
                  # object.
         | 
| 112 | 
            +
                  #
         | 
| 113 | 
            +
                  # Example:
         | 
| 114 | 
            +
                  #
         | 
| 115 | 
            +
                  #   #>pdf-small
         | 
| 116 | 
            +
                  #   doc.annotations.create_rectangle(doc.pages[0], 20, 20, 20, 60).
         | 
| 117 | 
            +
                  #     regenerate_appearance
         | 
| 118 | 
            +
                  #
         | 
| 119 | 
            +
                  #   doc.annotations.create_rectangle(doc.pages[0], 60, 20, 20, 60).
         | 
| 120 | 
            +
                  #     border_style(color: "hp-blue", width: 2).
         | 
| 121 | 
            +
                  #     interior_color("hp-orange").
         | 
| 122 | 
            +
                  #     regenerate_appearance
         | 
| 123 | 
            +
                  #
         | 
| 124 | 
            +
                  # See: Type::Annotations::Square
         | 
| 125 | 
            +
                  def create_rectangle(page, x, y, w, h)
         | 
| 126 | 
            +
                    annot = create_and_add_to_page(:Square, page)
         | 
| 127 | 
            +
                    annot[:Rect] = [x, y, x + w, y + h]
         | 
| 128 | 
            +
                    annot.border_style(color: 0, width: 1)
         | 
| 129 | 
            +
                    annot
         | 
| 130 | 
            +
                  end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                  # :call-seq:
         | 
| 133 | 
            +
                  #   annotations.create_ellipse(page, cx, cy, a:, b:)  -> annotation
         | 
| 134 | 
            +
                  #
         | 
| 135 | 
            +
                  # Creates an ellipse (called "circle" in the PDF specification) annotation with the center
         | 
| 136 | 
            +
                  # point at (+cx+, +cy+), the semi-major axis +a+ and the semi-minor axis +b+.
         | 
| 137 | 
            +
                  #
         | 
| 138 | 
            +
                  # The ellipse uses a black stroke color, no interior color and a line width of 1pt by
         | 
| 139 | 
            +
                  # default. It can be further styled using the convenience methods on the returned annotation
         | 
| 140 | 
            +
                  # object.
         | 
| 141 | 
            +
                  #
         | 
| 142 | 
            +
                  # Example:
         | 
| 143 | 
            +
                  #
         | 
| 144 | 
            +
                  #   #>pdf-small
         | 
| 145 | 
            +
                  #   doc.annotations.create_ellipse(doc.pages[0], 30, 50, a: 15, b: 20).
         | 
| 146 | 
            +
                  #     regenerate_appearance
         | 
| 147 | 
            +
                  #
         | 
| 148 | 
            +
                  #   doc.annotations.create_ellipse(doc.pages[0], 70, 50, a: 15, b: 20).
         | 
| 149 | 
            +
                  #     border_style(color: "hp-blue", width: 2).
         | 
| 150 | 
            +
                  #     interior_color("hp-orange").
         | 
| 151 | 
            +
                  #     regenerate_appearance
         | 
| 152 | 
            +
                  #
         | 
| 153 | 
            +
                  # See: Type::Annotations::Circle
         | 
| 154 | 
            +
                  def create_ellipse(page, x, y, a:, b:)
         | 
| 155 | 
            +
                    annot = create_and_add_to_page(:Circle, page)
         | 
| 156 | 
            +
                    annot[:Rect] = [x - a, y - b, x + a, y + b]
         | 
| 157 | 
            +
                    annot.border_style(color: 0, width: 1)
         | 
| 158 | 
            +
                    annot
         | 
| 159 | 
            +
                  end
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                  private
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                  # Returns the root of the destinations name tree.
         | 
| 164 | 
            +
                  def create_and_add_to_page(subtype, page)
         | 
| 165 | 
            +
                    annot = @document.add({Type: :Annot, Subtype: subtype})
         | 
| 166 | 
            +
                    (page[:Annots] ||= []) << annot
         | 
| 167 | 
            +
                    annot
         | 
| 168 | 
            +
                  end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
              end
         | 
| 173 | 
            +
            end
         | 
| @@ -218,6 +218,19 @@ module HexaPDF | |
| 218 218 | 
             
                    style
         | 
| 219 219 | 
             
                  end
         | 
| 220 220 |  | 
| 221 | 
            +
                  # Returns +true+ if a style with the given +name+ exists, else +false+.
         | 
| 222 | 
            +
                  #
         | 
| 223 | 
            +
                  # Example:
         | 
| 224 | 
            +
                  #
         | 
| 225 | 
            +
                  #   layout.style(:header, font: 'Helvetica')
         | 
| 226 | 
            +
                  #   layout.style?(:header)     # => true
         | 
| 227 | 
            +
                  #   layout.style?(:paragraph)  # => false
         | 
| 228 | 
            +
                  #
         | 
| 229 | 
            +
                  # See: #style
         | 
| 230 | 
            +
                  def style?(name)
         | 
| 231 | 
            +
                    @styles.key?(name)
         | 
| 232 | 
            +
                  end
         | 
| 233 | 
            +
             | 
| 221 234 | 
             
                  # :call-seq:
         | 
| 222 235 | 
             
                  #    layout.styles            -> styles
         | 
| 223 236 | 
             
                  #    layout.styles(**mapping)   -> styles
         | 
| @@ -286,8 +299,9 @@ module HexaPDF | |
| 286 299 | 
             
                        box_options[:children] = ChildrenCollector.collect(self, &block)
         | 
| 287 300 | 
             
                      end
         | 
| 288 301 | 
             
                    end
         | 
| 302 | 
            +
                    style = retrieve_style(style)
         | 
| 289 303 | 
             
                    box_class_for_name(name).new(width: width, height: height,
         | 
| 290 | 
            -
                                                 style:  | 
| 304 | 
            +
                                                 style: style, **style.box_options, **box_options, &box_block)
         | 
| 291 305 | 
             
                  end
         | 
| 292 306 |  | 
| 293 307 | 
             
                  # Creates an array of HexaPDF::Layout::TextFragment objects for the given +text+.
         | 
| @@ -354,7 +368,7 @@ module HexaPDF | |
| 354 368 | 
             
                    box_style = (box_style ? retrieve_style(box_style) : style)
         | 
| 355 369 | 
             
                    box_class_for_name(:text).new(items: text_fragments(text, style: style),
         | 
| 356 370 | 
             
                                                  width: width, height: height, properties: properties,
         | 
| 357 | 
            -
                                                  style: box_style)
         | 
| 371 | 
            +
                                                  style: box_style, **box_style.box_options)
         | 
| 358 372 | 
             
                  end
         | 
| 359 373 | 
             
                  alias text text_box
         | 
| 360 374 |  | 
| @@ -457,7 +471,8 @@ module HexaPDF | |
| 457 471 | 
             
                      end
         | 
| 458 472 | 
             
                    end
         | 
| 459 473 | 
             
                    box_class_for_name(:text).new(items: data, width: width, height: height,
         | 
| 460 | 
            -
                                                  properties: properties, style: box_style | 
| 474 | 
            +
                                                  properties: properties, style: box_style,
         | 
| 475 | 
            +
                                                  **box_style.box_options)
         | 
| 461 476 | 
             
                  end
         | 
| 462 477 | 
             
                  alias formatted_text formatted_text_box
         | 
| 463 478 |  | 
| @@ -479,7 +494,7 @@ module HexaPDF | |
| 479 494 | 
             
                    style = retrieve_style(style, style_properties)
         | 
| 480 495 | 
             
                    image = file.kind_of?(HexaPDF::Stream) ? file : @document.images.add(file)
         | 
| 481 496 | 
             
                    box_class_for_name(:image).new(image: image, width: width, height: height,
         | 
| 482 | 
            -
                                                   properties: properties, style: style)
         | 
| 497 | 
            +
                                                   properties: properties, style: style, **style.box_options)
         | 
| 483 498 | 
             
                  end
         | 
| 484 499 | 
             
                  alias image image_box
         | 
| 485 500 |  | 
| @@ -608,7 +623,8 @@ module HexaPDF | |
| 608 623 | 
             
                    end
         | 
| 609 624 | 
             
                    box_class_for_name(:table).new(cells: cells, column_widths: column_widths, header: header,
         | 
| 610 625 | 
             
                                                   footer: footer, cell_style: cell_style, width: width,
         | 
| 611 | 
            -
                                                   height: height, properties: properties, style: style | 
| 626 | 
            +
                                                   height: height, properties: properties, style: style,
         | 
| 627 | 
            +
                                                   **style.box_options)
         | 
| 612 628 | 
             
                  end
         | 
| 613 629 | 
             
                  alias table table_box
         | 
| 614 630 |  | 
| @@ -666,6 +682,22 @@ module HexaPDF | |
| 666 682 | 
             
                    end
         | 
| 667 683 | 
             
                  end
         | 
| 668 684 |  | 
| 685 | 
            +
                  FONT_BOLD_VARIANT_MAPPER = { #:nodoc:
         | 
| 686 | 
            +
                    nil => {true => :bold, false: :none},
         | 
| 687 | 
            +
                    none: {true => :bold, false: :none},
         | 
| 688 | 
            +
                    bold: {true => :bold, false: :none},
         | 
| 689 | 
            +
                    italic: {true => :bold_italic, false: :italic},
         | 
| 690 | 
            +
                    bold_italic: {true => :bold_italic, false: :italic},
         | 
| 691 | 
            +
                  }
         | 
| 692 | 
            +
             | 
| 693 | 
            +
                  FONT_ITALIC_VARIANT_MAPPER = { #:nodoc:
         | 
| 694 | 
            +
                    nil => {true => :italic, false: :none},
         | 
| 695 | 
            +
                    none: {true => :italic, false: :none},
         | 
| 696 | 
            +
                    italic: {true => :italic, false: :none},
         | 
| 697 | 
            +
                    bold: {true => :bold_italic, false: :bold},
         | 
| 698 | 
            +
                    bold_italic: {true => :bold_italic, false: :bold},
         | 
| 699 | 
            +
                  }
         | 
| 700 | 
            +
             | 
| 669 701 | 
             
                  # Retrieves the appropriate HexaPDF::Layout::Style object based on the +style+ and
         | 
| 670 702 | 
             
                  # +properties+ arguments.
         | 
| 671 703 | 
             
                  #
         | 
| @@ -690,7 +722,14 @@ module HexaPDF | |
| 690 722 | 
             
                    end
         | 
| 691 723 | 
             
                    unless style.font.respond_to?(:pdf_object)
         | 
| 692 724 | 
             
                      name, options = *style.font
         | 
| 693 | 
            -
                       | 
| 725 | 
            +
                      options ||= {}
         | 
| 726 | 
            +
                      if style.font_bold?
         | 
| 727 | 
            +
                        options[:variant] = FONT_BOLD_VARIANT_MAPPER.dig(options[:variant], style.font_bold)
         | 
| 728 | 
            +
                      end
         | 
| 729 | 
            +
                      if style.font_italic?
         | 
| 730 | 
            +
                        options[:variant] = FONT_ITALIC_VARIANT_MAPPER.dig(options[:variant], style.font_italic)
         | 
| 731 | 
            +
                      end
         | 
| 732 | 
            +
                      style.font(@document.fonts.add(name, **options))
         | 
| 694 733 | 
             
                    end
         | 
| 695 734 | 
             
                    style
         | 
| 696 735 | 
             
                  end
         | 
    
        data/lib/hexapdf/document.rb
    CHANGED
    
    | @@ -123,6 +123,7 @@ module HexaPDF | |
| 123 123 | 
             
                autoload(:Destinations, 'hexapdf/document/destinations')
         | 
| 124 124 | 
             
                autoload(:Layout, 'hexapdf/document/layout')
         | 
| 125 125 | 
             
                autoload(:Metadata, 'hexapdf/document/metadata')
         | 
| 126 | 
            +
                autoload(:Annotations, 'hexapdf/document/annotations')
         | 
| 126 127 |  | 
| 127 128 | 
             
                # :call-seq:
         | 
| 128 129 | 
             
                #   Document.open(filename, **docargs)                   -> doc
         | 
| @@ -539,6 +540,12 @@ module HexaPDF | |
| 539 540 | 
             
                  @destinations ||= Destinations.new(self)
         | 
| 540 541 | 
             
                end
         | 
| 541 542 |  | 
| 543 | 
            +
                # Returns the Annotations object that provides convenience methods for working with annotation
         | 
| 544 | 
            +
                # objects.
         | 
| 545 | 
            +
                def annotations
         | 
| 546 | 
            +
                  @annotations ||= Annotations.new(self)
         | 
| 547 | 
            +
                end
         | 
| 548 | 
            +
             | 
| 542 549 | 
             
                # Returns the Layout object that provides convenience methods for working with the
         | 
| 543 550 | 
             
                # HexaPDF::Layout classes for document layout.
         | 
| 544 551 | 
             
                def layout
         | 
| @@ -726,8 +733,8 @@ module HexaPDF | |
| 726 733 | 
             
                end
         | 
| 727 734 |  | 
| 728 735 | 
             
                # :call-seq:
         | 
| 729 | 
            -
                #   doc.write(filename, incremental: false, validate: true, update_fields: true, optimize: false) -> [start_xref, section]
         | 
| 730 | 
            -
                #   doc.write(io, incremental: false, validate: true, update_fields: true, optimize: false) -> [start_xref, section]
         | 
| 736 | 
            +
                #   doc.write(filename, incremental: false, validate: true, update_fields: true, optimize: false, compact: true) -> [start_xref, section]
         | 
| 737 | 
            +
                #   doc.write(io, incremental: false, validate: true, update_fields: true, optimize: false, compact: true) -> [start_xref, section]
         | 
| 731 738 | 
             
                #
         | 
| 732 739 | 
             
                # Writes the document to the given file (in case +io+ is a String) or IO stream. Returns the
         | 
| 733 740 | 
             
                # file position of the start of the last cross-reference section and the last XRefSection object
         | 
| @@ -755,7 +762,20 @@ module HexaPDF | |
| 755 762 | 
             
                # optimize::
         | 
| 756 763 | 
             
                #   Optimize the file size by using object and cross-reference streams. This will raise the PDF
         | 
| 757 764 | 
             
                #   version to at least 1.5.
         | 
| 758 | 
            -
                 | 
| 765 | 
            +
                #
         | 
| 766 | 
            +
                # compact::
         | 
| 767 | 
            +
                #   Compact the document by reducing it to a single revision and removing null and unused
         | 
| 768 | 
            +
                #   objects.
         | 
| 769 | 
            +
                #
         | 
| 770 | 
            +
                #   The initial revision of a document has to contain objects with continuous numbering. If some
         | 
| 771 | 
            +
                #   object numbers refer to free entries, other PDF libraries/viewers might not work
         | 
| 772 | 
            +
                #   correctly. So continuous object numbers are assigned to stay compliant with the
         | 
| 773 | 
            +
                #   specification.
         | 
| 774 | 
            +
                #
         | 
| 775 | 
            +
                #   Only change this argument to +false+ if you run the optimization task with 'compact: true'
         | 
| 776 | 
            +
                #   beforehand or if you know exactly what you do and what not compacting implies.
         | 
| 777 | 
            +
                def write(file_or_io, incremental: false, validate: true, update_fields: true, optimize: false,
         | 
| 778 | 
            +
                          compact: true)
         | 
| 759 779 | 
             
                  if update_fields
         | 
| 760 780 | 
             
                    trailer.update_id
         | 
| 761 781 | 
             
                    if @metadata
         | 
| @@ -774,10 +794,11 @@ module HexaPDF | |
| 774 794 | 
             
                    end
         | 
| 775 795 | 
             
                  end
         | 
| 776 796 |  | 
| 777 | 
            -
                   | 
| 778 | 
            -
             | 
| 779 | 
            -
             | 
| 780 | 
            -
                   | 
| 797 | 
            +
                  optimize_opts = {}
         | 
| 798 | 
            +
                  optimize_opts[:object_streams] = :generate if optimize
         | 
| 799 | 
            +
                  optimize_opts[:compact] = true if compact && !incremental
         | 
| 800 | 
            +
                  task(:optimize, **optimize_opts) unless optimize_opts.empty?
         | 
| 801 | 
            +
                  self.version = '1.5' if version < '1.5' if optimize
         | 
| 781 802 |  | 
| 782 803 | 
             
                  dispatch_message(:before_write)
         | 
| 783 804 |  | 
    
        data/lib/hexapdf/error.rb
    CHANGED
    
    | @@ -94,9 +94,17 @@ module HexaPDF | |
| 94 94 | 
             
                end
         | 
| 95 95 |  | 
| 96 96 | 
             
                def message # :nodoc:
         | 
| 97 | 
            -
                  "No glyph for #{glyph.str.inspect} in font '#{glyph.font_wrapper.wrapped_font.full_name}' " \
         | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 97 | 
            +
                  str = "No glyph for #{glyph.str.inspect} in font '#{glyph.font_wrapper.wrapped_font.full_name}' " \
         | 
| 98 | 
            +
                        "found. \n\n"
         | 
| 99 | 
            +
                  str << if glyph.font_wrapper.font_type == :Type1
         | 
| 100 | 
            +
                           "The used Type1 font only contains a very limited number of glyphs. TrueType " \
         | 
| 101 | 
            +
                           "fonts usually provide a much wider array of glyphs. Use the configuration option " \
         | 
| 102 | 
            +
                           "'font.map' to register appropriate font files. Also have a look at the " \
         | 
| 103 | 
            +
                           "'font.default' and 'font.fallback' options. "
         | 
| 104 | 
            +
                         else
         | 
| 105 | 
            +
                           "Maybe register another #{glyph.font_wrapper.font_type} font that contains the " \
         | 
| 106 | 
            +
                           "needed glyph and use it as fallback via the configuration option 'font.fallback'."
         | 
| 107 | 
            +
                         end
         | 
| 100 108 | 
             
                end
         | 
| 101 109 |  | 
| 102 110 | 
             
              end
         | 
| @@ -63,6 +63,16 @@ module HexaPDF | |
| 63 63 | 
             
                    def use_glyph(glyph_id)
         | 
| 64 64 | 
             
                      return @glyph_map[glyph_id] if @glyph_map.key?(glyph_id)
         | 
| 65 65 | 
             
                      @last_id += 1
         | 
| 66 | 
            +
                      # Handle codes for ASCII characters \r (13), (, ) (40, 41) and \ (92) specially so that
         | 
| 67 | 
            +
                      # they never appear in the output (PDF serialization would need to escape them)
         | 
| 68 | 
            +
                      if @last_id == 13 || @last_id == 40 || @last_id == 92
         | 
| 69 | 
            +
                        @glyph_map[:"s#{@last_id}"] = @last_id
         | 
| 70 | 
            +
                        if @last_id == 40
         | 
| 71 | 
            +
                          @last_id += 1
         | 
| 72 | 
            +
                          @glyph_map[:"s#{@last_id}"] = @last_id
         | 
| 73 | 
            +
                        end
         | 
| 74 | 
            +
                        @last_id += 1
         | 
| 75 | 
            +
                      end
         | 
| 66 76 | 
             
                      @glyph_map[glyph_id] = @last_id
         | 
| 67 77 | 
             
                    end
         | 
| 68 78 |  | 
| @@ -107,7 +117,7 @@ module HexaPDF | |
| 107 117 | 
             
                      locations = []
         | 
| 108 118 |  | 
| 109 119 | 
             
                      @glyph_map.each_key do |old_gid|
         | 
| 110 | 
            -
                        glyph = orig_glyf[old_gid]
         | 
| 120 | 
            +
                        glyph = orig_glyf[old_gid.kind_of?(Symbol) ? 0 : old_gid]
         | 
| 111 121 | 
             
                        locations << table.size
         | 
| 112 122 | 
             
                        data = glyph.raw_data
         | 
| 113 123 | 
             
                        if glyph.compound?
         | 
| @@ -166,7 +176,10 @@ module HexaPDF | |
| 166 176 | 
             
                    # Adds the components of compound glyphs to the subset.
         | 
| 167 177 | 
             
                    def add_glyph_components
         | 
| 168 178 | 
             
                      glyf = @font[:glyf]
         | 
| 169 | 
            -
                      @glyph_map.keys.each  | 
| 179 | 
            +
                      @glyph_map.keys.each do |gid|
         | 
| 180 | 
            +
                        next if gid.kind_of?(Symbol)
         | 
| 181 | 
            +
                        glyf[gid].components&.each {|cgid| use_glyph(cgid) }
         | 
| 182 | 
            +
                      end
         | 
| 170 183 | 
             
                    end
         | 
| 171 184 |  | 
| 172 185 | 
             
                  end
         | 
    
        data/lib/hexapdf/layout/style.rb
    CHANGED
    
    | @@ -603,11 +603,36 @@ module HexaPDF | |
| 603 603 | 
             
                  #   style.update(**properties)    -> style
         | 
| 604 604 | 
             
                  #
         | 
| 605 605 | 
             
                  # Updates the style's properties using the key-value pairs specified by the +properties+ hash.
         | 
| 606 | 
            +
                  #
         | 
| 607 | 
            +
                  # Also see: #merge
         | 
| 606 608 | 
             
                  def update(**properties)
         | 
| 607 609 | 
             
                    properties.each {|key, value| send(key, value) }
         | 
| 608 610 | 
             
                    self
         | 
| 609 611 | 
             
                  end
         | 
| 610 612 |  | 
| 613 | 
            +
                  # Yields all set properties.
         | 
| 614 | 
            +
                  def each_property # :yield: property, value
         | 
| 615 | 
            +
                    return to_enum(__method__) unless block_given?
         | 
| 616 | 
            +
                    instance_variables.each do |iv|
         | 
| 617 | 
            +
                      (val = PROPERTIES[iv]) && yield(val, instance_variable_get(iv))
         | 
| 618 | 
            +
                    end
         | 
| 619 | 
            +
                  end
         | 
| 620 | 
            +
             | 
| 621 | 
            +
                  # :call-seq:
         | 
| 622 | 
            +
                  #   style.merge(other_style)   -> style
         | 
| 623 | 
            +
                  #
         | 
| 624 | 
            +
                  # Merges the set properties of the +other_style+ object into this one.
         | 
| 625 | 
            +
                  #
         | 
| 626 | 
            +
                  # Note that merging is done on a per-property basis. So if a complex property is set on
         | 
| 627 | 
            +
                  # +other_style+ and also on +self+, the +other_style+ value completely overwrites the one from
         | 
| 628 | 
            +
                  # +self+.
         | 
| 629 | 
            +
                  #
         | 
| 630 | 
            +
                  # Also see: #update
         | 
| 631 | 
            +
                  def merge(other)
         | 
| 632 | 
            +
                    other.each_property {|property, value| send(property, value) }
         | 
| 633 | 
            +
                    self
         | 
| 634 | 
            +
                  end
         | 
| 635 | 
            +
             | 
| 611 636 | 
             
                  ##
         | 
| 612 637 | 
             
                  # :method: font
         | 
| 613 638 | 
             
                  # :call-seq:
         | 
| @@ -615,8 +640,9 @@ module HexaPDF | |
| 615 640 | 
             
                  #
         | 
| 616 641 | 
             
                  # The font to be used, must be set to a valid font wrapper object before it can be used.
         | 
| 617 642 | 
             
                  #
         | 
| 618 | 
            -
                  # HexaPDF:: | 
| 619 | 
            -
                  # to a font wrapper object before doing | 
| 643 | 
            +
                  # HexaPDF::Document::Layout handles this property - together with #font_bold and #font_italic
         | 
| 644 | 
            +
                  # - specially in that it resolves a set string or array to a font wrapper object before doing
         | 
| 645 | 
            +
                  # anything else with the style object.
         | 
| 620 646 | 
             
                  #
         | 
| 621 647 | 
             
                  # This is the only style property without a default value!
         | 
| 622 648 | 
             
                  #
         | 
| @@ -633,6 +659,48 @@ module HexaPDF | |
| 633 659 | 
             
                  #   composer.text("Courier Bold", font: "Courier bold")
         | 
| 634 660 | 
             
                  #   composer.text("Courier Bold also", font: ["Courier", variant: :bold])
         | 
| 635 661 |  | 
| 662 | 
            +
                  ##
         | 
| 663 | 
            +
                  # :method: font_bold
         | 
| 664 | 
            +
                  # :call-seq:
         | 
| 665 | 
            +
                  #   font_bold(bold = false)
         | 
| 666 | 
            +
                  #
         | 
| 667 | 
            +
                  # Specifies whether the bold variant of the font is used.
         | 
| 668 | 
            +
                  #
         | 
| 669 | 
            +
                  # Note that this property only has affect if #font is not already set to a font wrapper
         | 
| 670 | 
            +
                  # object and if it is set explicitly (i.e. #font_bold? returns +true+).
         | 
| 671 | 
            +
                  #
         | 
| 672 | 
            +
                  # See #font, #font_italic
         | 
| 673 | 
            +
                  #
         | 
| 674 | 
            +
                  # Examples:
         | 
| 675 | 
            +
                  #
         | 
| 676 | 
            +
                  #   #>pdf-composer100
         | 
| 677 | 
            +
                  #   composer.text("Helvetica bold", font: "Helvetica", font_bold: true)
         | 
| 678 | 
            +
                  #
         | 
| 679 | 
            +
                  #   helvetica_bold = composer.document.fonts.add("Helvetica", variant: :bold)
         | 
| 680 | 
            +
                  #   composer.text("Helvetica bold", font: helvetica_bold, font_bold: false)
         | 
| 681 | 
            +
                  #   composer.text("Helvetica", font: ["Helvetica", {variant: :bold}], font_bold: false)
         | 
| 682 | 
            +
             | 
| 683 | 
            +
                  ##
         | 
| 684 | 
            +
                  # :method: font_italic
         | 
| 685 | 
            +
                  # :call-seq:
         | 
| 686 | 
            +
                  #   font_italic(bold = false)
         | 
| 687 | 
            +
                  #
         | 
| 688 | 
            +
                  # Specifies whether the italic variant of the font is used.
         | 
| 689 | 
            +
                  #
         | 
| 690 | 
            +
                  # Note that this property only has affect if #font is not already set to a font wrapper
         | 
| 691 | 
            +
                  # object and if it is set explicitly (i.e. #font_italic? returns +true+).
         | 
| 692 | 
            +
                  #
         | 
| 693 | 
            +
                  # See #font, #font_bold.
         | 
| 694 | 
            +
                  #
         | 
| 695 | 
            +
                  # Examples:
         | 
| 696 | 
            +
                  #
         | 
| 697 | 
            +
                  #   #>pdf-composer100
         | 
| 698 | 
            +
                  #   composer.text("Helvetica italic", font: "Helvetica", font_italic: true)
         | 
| 699 | 
            +
                  #
         | 
| 700 | 
            +
                  #   helvetica_bold = composer.document.fonts.add("Helvetica", variant: :italic)
         | 
| 701 | 
            +
                  #   composer.text("Helvetica italic", font: helvetica_bold, font_italic: false)
         | 
| 702 | 
            +
                  #   composer.text("Helvetica", font: ["Helvetica", {variant: :italic}], font_italic: false)
         | 
| 703 | 
            +
             | 
| 636 704 | 
             
                  ##
         | 
| 637 705 | 
             
                  # :method: font_size
         | 
| 638 706 | 
             
                  # :call-seq:
         | 
| @@ -1021,7 +1089,7 @@ module HexaPDF | |
| 1021 1089 | 
             
                  #
         | 
| 1022 1090 | 
             
                  # This method can set the line spacing in two ways:
         | 
| 1023 1091 | 
             
                  #
         | 
| 1024 | 
            -
                  # * Using  | 
| 1092 | 
            +
                  # * Using the positional, mandatory argument +type+ and the optional +value+.
         | 
| 1025 1093 | 
             
                  # * Or a hash with the keys +type+ and +value+.
         | 
| 1026 1094 | 
             
                  #
         | 
| 1027 1095 | 
             
                  # Note that the last line has no additional spacing after it by default. Set #last_line_gap
         | 
| @@ -1422,8 +1490,33 @@ module HexaPDF | |
| 1422 1490 | 
             
                  #   composer.text("This is some longer text that does not appear in two lines.",
         | 
| 1423 1491 | 
             
                  #                 height: 15, overflow: :truncate)
         | 
| 1424 1492 |  | 
| 1425 | 
            -
                   | 
| 1493 | 
            +
                  ##
         | 
| 1494 | 
            +
                  # :method: box_options
         | 
| 1495 | 
            +
                  # :call-seq:
         | 
| 1496 | 
            +
                  #   box_options(**options)
         | 
| 1497 | 
            +
                  #
         | 
| 1498 | 
            +
                  # Contains initialization arguments for the box instance that is created with this
         | 
| 1499 | 
            +
                  # style. Together with the other style properties this allows the complete specification of a
         | 
| 1500 | 
            +
                  # box instance just via a Style instance.
         | 
| 1501 | 
            +
                  #
         | 
| 1502 | 
            +
                  # Note that this property is only used by the HexaPDF::Document::Layout methods when a box
         | 
| 1503 | 
            +
                  # instance is created. If a box instance is created directly, this property has no effect.
         | 
| 1504 | 
            +
                  #
         | 
| 1505 | 
            +
                  # Examples:
         | 
| 1506 | 
            +
                  #
         | 
| 1507 | 
            +
                  #   #>pdf-composer100
         | 
| 1508 | 
            +
                  #   composer.style(:my_list, box_options: {marker_type: :decimal, item_spacing: 15})
         | 
| 1509 | 
            +
                  #   composer.list(style: :my_list) do |list|
         | 
| 1510 | 
            +
                  #     list.text("This is some text.")
         | 
| 1511 | 
            +
                  #     list.text("This is some other text.")
         | 
| 1512 | 
            +
                  #   end
         | 
| 1513 | 
            +
             | 
| 1514 | 
            +
             | 
| 1515 | 
            +
                  # :nodoc:
         | 
| 1516 | 
            +
                  PROPERTIES = [
         | 
| 1426 1517 | 
             
                    [:font, "raise HexaPDF::Error, 'No font set'"],
         | 
| 1518 | 
            +
                    [:font_bold, false],
         | 
| 1519 | 
            +
                    [:font_italic, false],
         | 
| 1427 1520 | 
             
                    [:font_size, 10],
         | 
| 1428 1521 | 
             
                    [:line_height, nil],
         | 
| 1429 1522 | 
             
                    [:character_spacing, 0],
         | 
| @@ -1457,8 +1550,8 @@ module HexaPDF | |
| 1457 1550 | 
             
                    [:text_valign, :top, {valid_values: [:top, :center, :bottom]}],
         | 
| 1458 1551 | 
             
                    [:text_indent, 0],
         | 
| 1459 1552 | 
             
                    [:line_spacing, "LineSpacing.new(type: :single)",
         | 
| 1460 | 
            -
                     {setter: "LineSpacing.new(**(value.kind_of?(Symbol) || value.kind_of?(Numeric)  | 
| 1461 | 
            -
                       "{type: value, value: extra_arg} : value))",
         | 
| 1553 | 
            +
                     {setter: "LineSpacing.new(**(value.kind_of?(Symbol) || value.kind_of?(Numeric) || " \
         | 
| 1554 | 
            +
                       "value.kind_of?(LineSpacing) ? {type: value, value: extra_arg} : value))",
         | 
| 1462 1555 | 
             
                      extra_args: ", extra_arg = nil"}],
         | 
| 1463 1556 | 
             
                    [:last_line_gap, false, {valid_values: [true, false]}],
         | 
| 1464 1557 | 
             
                    [:fill_horizontal, nil],
         | 
| @@ -1475,6 +1568,7 @@ module HexaPDF | |
| 1475 1568 | 
             
                    [:mask_mode, :default, {valid_values: [:default, :none, :box, :fill_horizontal,
         | 
| 1476 1569 | 
             
                                                           :fill_frame_horizontal, :fill_vertical, :fill]}],
         | 
| 1477 1570 | 
             
                    [:overflow, :error],
         | 
| 1571 | 
            +
                    [:box_options, {}],
         | 
| 1478 1572 | 
             
                  ].each do |name, default, options = {}|
         | 
| 1479 1573 | 
             
                    default = default.inspect unless default.kind_of?(String)
         | 
| 1480 1574 | 
             
                    setter = options.delete(:setter) || "value"
         | 
| @@ -1500,7 +1594,7 @@ module HexaPDF | |
| 1500 1594 | 
             
                      end
         | 
| 1501 1595 | 
             
                    EOF
         | 
| 1502 1596 | 
             
                    alias_method("#{name}=", name)
         | 
| 1503 | 
            -
                  end
         | 
| 1597 | 
            +
                  end.each_with_object({}) {|arr, hash| hash[:"@#{arr.first}"] = arr.first }
         | 
| 1504 1598 |  | 
| 1505 1599 | 
             
                  ##
         | 
| 1506 1600 | 
             
                  # :method: text_segmentation_algorithm
         | 
    
        data/lib/hexapdf/object.rb
    CHANGED
    
    | @@ -305,8 +305,8 @@ module HexaPDF | |
| 305 305 | 
             
                  result
         | 
| 306 306 | 
             
                rescue HexaPDF::Error
         | 
| 307 307 | 
             
                  raise
         | 
| 308 | 
            -
                rescue StandardError
         | 
| 309 | 
            -
                  yield(" | 
| 308 | 
            +
                rescue StandardError => e
         | 
| 309 | 
            +
                  yield("Unexpected error encountered: #{e.message}", false, self) if block_given?
         | 
| 310 310 | 
             
                  false
         | 
| 311 311 | 
             
                end
         | 
| 312 312 |  | 
    
        data/lib/hexapdf/pdf_array.rb
    CHANGED
    
    | @@ -143,10 +143,32 @@ module HexaPDF | |
| 143 143 | 
             
                #   array.reject! {|item| block }   -> array or nil
         | 
| 144 144 | 
             
                #   array.reject!                   -> Enumerator
         | 
| 145 145 | 
             
                #
         | 
| 146 | 
            -
                # Deletes all elements from the array for which the block returns +true | 
| 147 | 
            -
                # done, returns +nil+.
         | 
| 146 | 
            +
                # Deletes all elements from the array for which the block returns +true+ and returns +self+. If
         | 
| 147 | 
            +
                # no changes were done, returns +nil+.
         | 
| 148 148 | 
             
                def reject!
         | 
| 149 | 
            -
                   | 
| 149 | 
            +
                  return to_enum(__method__) unless block_given?
         | 
| 150 | 
            +
                  value.reject! {|item| yield(process_entry(item)) } && self
         | 
| 151 | 
            +
                end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                # :call-seq:
         | 
| 154 | 
            +
                #   array.map! {|item| block }   -> array
         | 
| 155 | 
            +
                #   array.map!                   -> Enumerator
         | 
| 156 | 
            +
                #
         | 
| 157 | 
            +
                # Maps all elements from the array in-place to the respective return value of the block+ and
         | 
| 158 | 
            +
                # returns +self+.
         | 
| 159 | 
            +
                def map!
         | 
| 160 | 
            +
                  return to_enum(__method__) unless block_given?
         | 
| 161 | 
            +
                  value.map! {|item| yield(process_entry(item)) }
         | 
| 162 | 
            +
                  self
         | 
| 163 | 
            +
                end
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                # :call-seq:
         | 
| 166 | 
            +
                #   array.compact!   -> array or nil
         | 
| 167 | 
            +
                #
         | 
| 168 | 
            +
                # Removes all +nil+ elements from the array. Returns +self+ if any elements were removed, +nil+
         | 
| 169 | 
            +
                # otherwise.
         | 
| 170 | 
            +
                def compact!
         | 
| 171 | 
            +
                  value.compact! && self
         | 
| 150 172 | 
             
                end
         | 
| 151 173 |  | 
| 152 174 | 
             
                # :call-seq:
         | 
    
        data/lib/hexapdf/tokenizer.rb
    CHANGED
    
    | @@ -278,6 +278,9 @@ module HexaPDF | |
| 278 278 |  | 
| 279 279 | 
             
                REFERENCE_RE = /[#{WHITESPACE}]+([+]?\d+)[#{WHITESPACE}]+R#{WHITESPACE_OR_DELIMITER_RE}/ # :nodoc:
         | 
| 280 280 |  | 
| 281 | 
            +
                WHITESPACE_OR_DELIMITER_LUT = [] # :nodoc:
         | 
| 282 | 
            +
                (WHITESPACE + DELIMITER).each_byte {|x| WHITESPACE_OR_DELIMITER_LUT[x] = true }
         | 
| 283 | 
            +
             | 
| 281 284 | 
             
                # Parses the number (integer or real) at the current position.
         | 
| 282 285 | 
             
                #
         | 
| 283 286 | 
             
                # See: PDF2.0 s7.3.3
         | 
| @@ -285,7 +288,7 @@ module HexaPDF | |
| 285 288 | 
             
                  prepare_string_scanner(40)
         | 
| 286 289 | 
             
                  pos = self.pos
         | 
| 287 290 | 
             
                  if (tmp = @ss.scan_integer)
         | 
| 288 | 
            -
                    if @ss.eos? || @ss. | 
| 291 | 
            +
                    if @ss.eos? || WHITESPACE_OR_DELIMITER_LUT[@ss.peek_byte]
         | 
| 289 292 | 
             
                      # Handle object references, see PDF2.0 s7.3.10
         | 
| 290 293 | 
             
                      prepare_string_scanner(10)
         | 
| 291 294 | 
             
                      if @ss.scan(REFERENCE_RE)
         |