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
| @@ -48,6 +48,8 @@ module HexaPDF | |
| 48 48 | 
             
                  # See: PDF2.0 s12.5.6.19, HexaPDF::Type::Annotation
         | 
| 49 49 | 
             
                  class Widget < Annotation
         | 
| 50 50 |  | 
| 51 | 
            +
                    include BorderStyling
         | 
| 52 | 
            +
             | 
| 51 53 | 
             
                    # The dictionary used by the /MK key of the widget annotation.
         | 
| 52 54 | 
             
                    class AppearanceCharacteristics < Dictionary
         | 
| 53 55 |  | 
| @@ -122,111 +124,21 @@ module HexaPDF | |
| 122 124 | 
             
                      end
         | 
| 123 125 | 
             
                    end
         | 
| 124 126 |  | 
| 125 | 
            -
                    # Describes the  | 
| 126 | 
            -
                    #
         | 
| 127 | 
            -
                    # The +color+ property is either +nil+ if the border is transparent or else a device color
         | 
| 128 | 
            -
                    # object - see HexaPDF::Content::ColorSpace.
         | 
| 129 | 
            -
                    #
         | 
| 130 | 
            -
                    # The +style+ property can be one of the following:
         | 
| 131 | 
            -
                    #
         | 
| 132 | 
            -
                    # :solid::      Solid line.
         | 
| 133 | 
            -
                    # :beveled::    Embossed rectangle seemingly raised above the surface of the page.
         | 
| 134 | 
            -
                    # :inset::      Engraved rectangle receeding into the page.
         | 
| 135 | 
            -
                    # :underlined:: Underlined, i.e. only the bottom border is draw.
         | 
| 136 | 
            -
                    # Array:        Dash array describing how to dash the line.
         | 
| 137 | 
            -
                    BorderStyle = Struct.new(:width, :color, :style, :horizontal_corner_radius,
         | 
| 138 | 
            -
                                             :vertical_corner_radius)
         | 
| 139 | 
            -
             | 
| 140 | 
            -
                    # :call-seq:
         | 
| 141 | 
            -
                    #   widget.border_style                                      => border_style
         | 
| 142 | 
            -
                    #   widget.border_style(color: 0, width: 1, style: :solid)   => widget
         | 
| 143 | 
            -
                    #
         | 
| 144 | 
            -
                    # Returns a BorderStyle instance representing the border style of the widget when no
         | 
| 145 | 
            -
                    # argument is given. Otherwise sets the border style of the widget and returns self.
         | 
| 146 | 
            -
                    #
         | 
| 147 | 
            -
                    # When setting a border style, arguments that are not provided will use the default: a
         | 
| 148 | 
            -
                    # border with a solid, black, 1pt wide line. This also means that multiple invocations will
         | 
| 149 | 
            -
                    # reset *all* prior values.
         | 
| 150 | 
            -
                    #
         | 
| 151 | 
            -
                    # +color+:: The color of the border. See
         | 
| 152 | 
            -
                    #           HexaPDF::Content::ColorSpace.device_color_from_specification for information on
         | 
| 153 | 
            -
                    #           the allowed arguments.
         | 
| 154 | 
            -
                    #
         | 
| 155 | 
            -
                    #           If the special value +:transparent+ is used when setting the color, a
         | 
| 156 | 
            -
                    #           transparent is used. A transparent border will return a +nil+ value when getting
         | 
| 157 | 
            -
                    #           the border color.
         | 
| 158 | 
            -
                    #
         | 
| 159 | 
            -
                    # +width+:: The width of the border. If set to 0, no border is shown.
         | 
| 160 | 
            -
                    #
         | 
| 161 | 
            -
                    # +style+:: Defines how the border is drawn. can be one of the following:
         | 
| 162 | 
            -
                    #
         | 
| 163 | 
            -
                    #           +:solid+::      Draws a solid border.
         | 
| 164 | 
            -
                    #           +:beveled+::    Draws a beveled border.
         | 
| 165 | 
            -
                    #           +:inset+::      Draws an inset border.
         | 
| 166 | 
            -
                    #           +:underlined+:: Draws only the bottom border.
         | 
| 167 | 
            -
                    #           Array::         An array specifying a line dash pattern (see
         | 
| 168 | 
            -
                    #                           HexaPDF::Content::LineDashPattern)
         | 
| 169 | 
            -
                    def border_style(color: nil, width: nil, style: nil)
         | 
| 170 | 
            -
                      if color || width || style
         | 
| 171 | 
            -
                        color = if color == :transparent
         | 
| 172 | 
            -
                                  []
         | 
| 173 | 
            -
                                else
         | 
| 174 | 
            -
                                  Content::ColorSpace.device_color_from_specification(color || 0).components
         | 
| 175 | 
            -
                                end
         | 
| 176 | 
            -
                        width ||= 1
         | 
| 177 | 
            -
                        style ||= :solid
         | 
| 178 | 
            -
             | 
| 179 | 
            -
                        (self[:MK] ||= {})[:BC] = color
         | 
| 180 | 
            -
                        bs = self[:BS] = {W: width}
         | 
| 181 | 
            -
                        case style
         | 
| 182 | 
            -
                        when :solid then bs[:S] = :S
         | 
| 183 | 
            -
                        when :beveled then bs[:S] = :B
         | 
| 184 | 
            -
                        when :inset then bs[:S] = :I
         | 
| 185 | 
            -
                        when :underlined then bs[:S] = :U
         | 
| 186 | 
            -
                        when Array
         | 
| 187 | 
            -
                          bs[:S] = :D
         | 
| 188 | 
            -
                          bs[:D] = style
         | 
| 189 | 
            -
                        else
         | 
| 190 | 
            -
                          raise ArgumentError, "Unknown value #{style} for style argument"
         | 
| 191 | 
            -
                        end
         | 
| 192 | 
            -
                        self
         | 
| 193 | 
            -
                      else
         | 
| 194 | 
            -
                        result = BorderStyle.new(1, nil, :solid, 0, 0)
         | 
| 195 | 
            -
                        if (ac = self[:MK]) && (bc = ac[:BC]) && !bc.empty?
         | 
| 196 | 
            -
                          result.color = Content::ColorSpace.prenormalized_device_color(bc.value)
         | 
| 197 | 
            -
                        end
         | 
| 198 | 
            -
             | 
| 199 | 
            -
                        if (bs = self[:BS])
         | 
| 200 | 
            -
                          result.width = bs[:W] if bs.key?(:W)
         | 
| 201 | 
            -
                          result.style = case bs[:S]
         | 
| 202 | 
            -
                                         when :S then :solid
         | 
| 203 | 
            -
                                         when :B then :beveled
         | 
| 204 | 
            -
                                         when :I then :inset
         | 
| 205 | 
            -
                                         when :U then :underlined
         | 
| 206 | 
            -
                                         when :D then bs[:D].value
         | 
| 207 | 
            -
                                         else :solid
         | 
| 208 | 
            -
                                         end
         | 
| 209 | 
            -
                        elsif key?(:Border)
         | 
| 210 | 
            -
                          border = self[:Border]
         | 
| 211 | 
            -
                          result.horizontal_corner_radius = border[0]
         | 
| 212 | 
            -
                          result.vertical_corner_radius = border[1]
         | 
| 213 | 
            -
                          result.width = border[2]
         | 
| 214 | 
            -
                          result.style = border[3] if border[3]
         | 
| 215 | 
            -
                        end
         | 
| 216 | 
            -
             | 
| 217 | 
            -
                        result
         | 
| 218 | 
            -
                      end
         | 
| 219 | 
            -
                    end
         | 
| 220 | 
            -
             | 
| 221 | 
            -
                    # Describes the marker style of a check box or radio button widget.
         | 
| 127 | 
            +
                    # Describes the marker style of a check box, radio button or push button widget.
         | 
| 222 128 | 
             
                    class MarkerStyle
         | 
| 223 129 |  | 
| 224 | 
            -
                      # The kind of marker that is shown inside the widget. | 
| 225 | 
            -
                      # +:check+, +:circle+, +:cross+, +:diamond+, +:square+ or +:star+, or a one character
         | 
| 226 | 
            -
                      # string. The latter is interpreted using the ZapfDingbats font.
         | 
| 130 | 
            +
                      # The kind of marker that is shown inside the widget.
         | 
| 227 131 | 
             
                      #
         | 
| 228 | 
            -
                      #  | 
| 229 | 
            -
                      #  | 
| 132 | 
            +
                      # Radion buttons and check boxes::
         | 
| 133 | 
            +
                      #     Can either be one of the symbols +:check+, +:circle+, +:cross+, +:diamond+,
         | 
| 134 | 
            +
                      #     +:square+ or +:star+, or a one character string. The latter is interpreted using the
         | 
| 135 | 
            +
                      #     ZapfDingbats font.
         | 
| 136 | 
            +
                      #
         | 
| 137 | 
            +
                      #     If an empty string is set, it is treated as if +nil+ was set, i.e. it shows the
         | 
| 138 | 
            +
                      #     default marker for the field type.
         | 
| 139 | 
            +
                      #
         | 
| 140 | 
            +
                      # Push buttons:
         | 
| 141 | 
            +
                      #     The caption string.
         | 
| 230 142 | 
             
                      attr_reader :style
         | 
| 231 143 |  | 
| 232 144 | 
             
                      # The size of the marker in PDF points that is shown inside the widget. The special value
         | 
| @@ -237,27 +149,40 @@ module HexaPDF | |
| 237 149 | 
             
                      # HexaPDF::Content::ColorSpace.
         | 
| 238 150 | 
             
                      attr_reader :color
         | 
| 239 151 |  | 
| 152 | 
            +
                      # The resource name of the font that should be used for the caption.
         | 
| 153 | 
            +
                      #
         | 
| 154 | 
            +
                      # This is only used for push button widgets.
         | 
| 155 | 
            +
                      attr_reader :font_name
         | 
| 156 | 
            +
             | 
| 240 157 | 
             
                      # Creates a new instance with the given values.
         | 
| 241 | 
            -
                      def initialize(style, size, color)
         | 
| 158 | 
            +
                      def initialize(style, size, color, font_name)
         | 
| 242 159 | 
             
                        @style = style
         | 
| 243 160 | 
             
                        @size = size
         | 
| 244 161 | 
             
                        @color = color
         | 
| 162 | 
            +
                        @font_name = font_name
         | 
| 245 163 | 
             
                      end
         | 
| 246 164 |  | 
| 247 165 | 
             
                    end
         | 
| 248 166 |  | 
| 249 167 | 
             
                    # :call-seq:
         | 
| 250 | 
            -
                    #   widget.marker_style | 
| 251 | 
            -
                    #   widget.marker_style(style: nil, size: nil, color: nil)   => widget
         | 
| 168 | 
            +
                    #   widget.marker_style                                                      => marker_style
         | 
| 169 | 
            +
                    #   widget.marker_style(style: nil, size: nil, color: nil, font_name: nil)   => widget
         | 
| 252 170 | 
             
                    #
         | 
| 253 171 | 
             
                    # Returns a MarkerStyle instance representing the marker style of the widget when no
         | 
| 254 172 | 
             
                    # argument is given. Otherwise sets the button marker style of the widget and returns self.
         | 
| 255 173 | 
             
                    #
         | 
| 256 | 
            -
                    # This method returns valid information only for check boxes and  | 
| 174 | 
            +
                    # This method returns valid information only for check boxes, radio buttons and push buttons!
         | 
| 257 175 | 
             
                    #
         | 
| 258 | 
            -
                    # When setting a marker style, arguments that are not provided will use the default: | 
| 259 | 
            -
                    # | 
| 260 | 
            -
                    #  | 
| 176 | 
            +
                    # When setting a marker style, arguments that are not provided will use the default:
         | 
| 177 | 
            +
                    #
         | 
| 178 | 
            +
                    # * For check boxes a black auto-sized checkmark (i.e. :check)
         | 
| 179 | 
            +
                    # * For radio buttons a black auto-sized circle (i.e. :circle)
         | 
| 180 | 
            +
                    # * For push buttons a black 9pt empty text using Helvetica
         | 
| 181 | 
            +
                    #
         | 
| 182 | 
            +
                    # This also means that multiple invocations will reset *all* prior values.
         | 
| 183 | 
            +
                    #
         | 
| 184 | 
            +
                    # Note that the +font_name+ argument must be a valid HexaPDF font name (this is in contrast
         | 
| 185 | 
            +
                    # to MarkerStyle#font_name which returns the resource name of the font).
         | 
| 261 186 | 
             
                    #
         | 
| 262 187 | 
             
                    # Note: The marker is called "normal caption" in the PDF 2.0 spec and the /CA entry of the
         | 
| 263 188 | 
             
                    # associated appearance characteristics dictionary. The marker size and color are set using
         | 
| @@ -265,13 +190,18 @@ module HexaPDF | |
| 265 190 | 
             
                    # does it).
         | 
| 266 191 | 
             
                    #
         | 
| 267 192 | 
             
                    # See: PDF2.0 s12.5.6.19 and s12.7.4.3
         | 
| 268 | 
            -
                    def marker_style(style: nil, size: nil, color: nil)
         | 
| 193 | 
            +
                    def marker_style(style: nil, size: nil, color: nil, font_name: nil)
         | 
| 269 194 | 
             
                      field = form_field
         | 
| 270 | 
            -
                      if style || size || color
         | 
| 271 | 
            -
                        style ||=  | 
| 272 | 
            -
             | 
| 195 | 
            +
                      if style || size || color || font_name
         | 
| 196 | 
            +
                        style ||= case field.concrete_field_type
         | 
| 197 | 
            +
                                  when :check_box then :check
         | 
| 198 | 
            +
                                  when :radio_button then :circle
         | 
| 199 | 
            +
                                  when :push_button then ''
         | 
| 200 | 
            +
                                  end
         | 
| 201 | 
            +
                        size ||= (field.push_button? ? 9 : 0)
         | 
| 273 202 | 
             
                        color = Content::ColorSpace.device_color_from_specification(color || 0)
         | 
| 274 203 | 
             
                        serialized_color = Content::ColorSpace.serialize_device_color(color)
         | 
| 204 | 
            +
                        font_name ||= 'Helvetica'
         | 
| 275 205 |  | 
| 276 206 | 
             
                        self[:MK] ||= {}
         | 
| 277 207 | 
             
                        self[:MK][:CA] = case style
         | 
| @@ -285,7 +215,13 @@ module HexaPDF | |
| 285 215 | 
             
                                         else
         | 
| 286 216 | 
             
                                           raise ArgumentError, "Unknown value #{style} for argument 'style'"
         | 
| 287 217 | 
             
                                         end
         | 
| 288 | 
            -
                        self[:DA] =  | 
| 218 | 
            +
                        self[:DA] = if field.push_button?
         | 
| 219 | 
            +
                                      name = document.acro_form(create: true).default_resources.
         | 
| 220 | 
            +
                                        add_font(document.fonts.add(font_name).pdf_object)
         | 
| 221 | 
            +
                                      "/#{name} #{size} Tf #{serialized_color}".strip
         | 
| 222 | 
            +
                                    else
         | 
| 223 | 
            +
                                      "/ZaDb #{size} Tf #{serialized_color}".strip
         | 
| 224 | 
            +
                                    end
         | 
| 289 225 | 
             
                      else
         | 
| 290 226 | 
             
                        style = case self[:MK]&.[](:CA)
         | 
| 291 227 | 
             
                                when '4' then :check
         | 
| @@ -305,12 +241,12 @@ module HexaPDF | |
| 305 241 | 
             
                        size = 0
         | 
| 306 242 | 
             
                        color = HexaPDF::Content::ColorSpace.prenormalized_device_color([0])
         | 
| 307 243 | 
             
                        if (da = self[:DA] || field[:DA])
         | 
| 308 | 
            -
                           | 
| 244 | 
            +
                          font_name, da_size, da_color = AcroForm::VariableTextField.parse_appearance_string(da)
         | 
| 309 245 | 
             
                          size = da_size || size
         | 
| 310 246 | 
             
                          color = da_color || color
         | 
| 311 247 | 
             
                        end
         | 
| 312 248 |  | 
| 313 | 
            -
                        MarkerStyle.new(style, size, color)
         | 
| 249 | 
            +
                        MarkerStyle.new(style, size, color, font_name)
         | 
| 314 250 | 
             
                      end
         | 
| 315 251 | 
             
                    end
         | 
| 316 252 |  | 
| @@ -48,6 +48,14 @@ module HexaPDF | |
| 48 48 | 
             
                  autoload(:Text, 'hexapdf/type/annotations/text')
         | 
| 49 49 | 
             
                  autoload(:Link, 'hexapdf/type/annotations/link')
         | 
| 50 50 | 
             
                  autoload(:Widget, 'hexapdf/type/annotations/widget')
         | 
| 51 | 
            +
                  autoload(:BorderStyling, 'hexapdf/type/annotations/border_styling')
         | 
| 52 | 
            +
                  autoload(:Line, 'hexapdf/type/annotations/line')
         | 
| 53 | 
            +
                  autoload(:AppearanceGenerator, 'hexapdf/type/annotations/appearance_generator')
         | 
| 54 | 
            +
                  autoload(:BorderEffect, 'hexapdf/type/annotations/border_effect')
         | 
| 55 | 
            +
                  autoload(:InteriorColor, 'hexapdf/type/annotations/interior_color')
         | 
| 56 | 
            +
                  autoload(:SquareCircle, 'hexapdf/type/annotations/square_circle')
         | 
| 57 | 
            +
                  autoload(:Square, 'hexapdf/type/annotations/square')
         | 
| 58 | 
            +
                  autoload(:Circle, 'hexapdf/type/annotations/circle')
         | 
| 51 59 |  | 
| 52 60 | 
             
                end
         | 
| 53 61 |  | 
    
        data/lib/hexapdf/type/form.rb
    CHANGED
    
    | @@ -167,14 +167,14 @@ module HexaPDF | |
| 167 167 | 
             
                  # bounding box.
         | 
| 168 168 | 
             
                  #
         | 
| 169 169 | 
             
                  # *Note* that a canvas can only be retrieved for initially empty form XObjects!
         | 
| 170 | 
            -
                  def canvas
         | 
| 170 | 
            +
                  def canvas(translate: true)
         | 
| 171 171 | 
             
                    cache(:canvas) do
         | 
| 172 172 | 
             
                      unless stream.empty?
         | 
| 173 173 | 
             
                        raise HexaPDF::Error, "Cannot create a canvas for a form XObjects with contents"
         | 
| 174 174 | 
             
                      end
         | 
| 175 175 |  | 
| 176 176 | 
             
                      canvas = Content::Canvas.new(self)
         | 
| 177 | 
            -
                      if box.left != 0 || box.bottom != 0
         | 
| 177 | 
            +
                      if translate && (box.left != 0 || box.bottom != 0)
         | 
| 178 178 | 
             
                        canvas.save_graphics_state.translate(box.left, box.bottom)
         | 
| 179 179 | 
             
                      end
         | 
| 180 180 | 
             
                      self.stream = canvas.stream_data
         | 
    
        data/lib/hexapdf/version.rb
    CHANGED
    
    
    
        data/lib/hexapdf/writer.rb
    CHANGED
    
    | @@ -150,7 +150,6 @@ module HexaPDF | |
| 150 150 |  | 
| 151 151 | 
             
                  xref_section = XRefSection.new
         | 
| 152 152 | 
             
                  xref_section.mark_as_initial_section! unless previous_xref_pos
         | 
| 153 | 
            -
                  xref_section.add_free_entry(0, 65535) if previous_xref_pos.nil?
         | 
| 154 153 | 
             
                  rev.each do |obj|
         | 
| 155 154 | 
             
                    if obj.null?
         | 
| 156 155 | 
             
                      xref_section.add_free_entry(obj.oid, obj.gen)
         | 
    
        data/lib/hexapdf/xref_section.rb
    CHANGED
    
    | @@ -113,9 +113,10 @@ module HexaPDF | |
| 113 113 |  | 
| 114 114 | 
             
                # Marks this XRefSection object as being the first cross-reference section in a PDF file.
         | 
| 115 115 | 
             
                #
         | 
| 116 | 
            -
                # This has the consequence that only a single sub-section is created.
         | 
| 116 | 
            +
                # This has the consequence that only a single sub-section starting a zero is created.
         | 
| 117 117 | 
             
                def mark_as_initial_section!
         | 
| 118 118 | 
             
                  @initial_section = true
         | 
| 119 | 
            +
                  add_free_entry(0, 65535)
         | 
| 119 120 | 
             
                end
         | 
| 120 121 |  | 
| 121 122 | 
             
                # Adds an in-use entry to the cross-reference section.
         | 
| @@ -161,9 +162,10 @@ module HexaPDF | |
| 161 162 | 
             
                  return to_enum(__method__) unless block_given?
         | 
| 162 163 |  | 
| 163 164 | 
             
                  temp = []
         | 
| 164 | 
            -
                  oids.sort | 
| 165 | 
            -
             | 
| 166 | 
            -
             | 
| 165 | 
            +
                  sorted_oids = oids.sort
         | 
| 166 | 
            +
                  expected_next_oid = sorted_oids[0]
         | 
| 167 | 
            +
                  sorted_oids.each do |oid|
         | 
| 168 | 
            +
                    if expected_next_oid != oid
         | 
| 167 169 | 
             
                      if @initial_section
         | 
| 168 170 | 
             
                        expected_next_oid.upto(oid - 1) do |free_oid|
         | 
| 169 171 | 
             
                          temp << self.class.free_entry(free_oid, 0)
         | 
| @@ -174,6 +176,7 @@ module HexaPDF | |
| 174 176 | 
             
                      end
         | 
| 175 177 | 
             
                    end
         | 
| 176 178 | 
             
                    temp << self[oid]
         | 
| 179 | 
            +
                    expected_next_oid = oid + 1
         | 
| 177 180 | 
             
                  end
         | 
| 178 181 | 
             
                  yield(temp)
         | 
| 179 182 | 
             
                  self
         | 
| @@ -2,7 +2,6 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require 'test_helper'
         | 
| 4 4 | 
             
            require 'hexapdf/content/graphics_state'
         | 
| 5 | 
            -
            require 'ostruct'
         | 
| 6 5 |  | 
| 7 6 | 
             
            # Dummy class used as wrapper so that constant lookup works correctly
         | 
| 8 7 | 
             
            class GraphicsStateWrapper < Minitest::Spec
         | 
| @@ -149,8 +148,8 @@ class GraphicsStateWrapper < Minitest::Spec | |
| 149 148 | 
             
                end
         | 
| 150 149 |  | 
| 151 150 | 
             
                it "uses the correct glyph to text space scaling" do
         | 
| 152 | 
            -
                  font =  | 
| 153 | 
            -
                  font.glyph_scaling_factor  | 
| 151 | 
            +
                  font = Object.new
         | 
| 152 | 
            +
                  font.define_singleton_method(:glyph_scaling_factor) { 0.002 }
         | 
| 154 153 | 
             
                  @gs.font = font
         | 
| 155 154 | 
             
                  @gs.font_size = 10
         | 
| 156 155 | 
             
                  assert_equal(0.02, @gs.scaled_font_size)
         | 
| @@ -4,7 +4,6 @@ require 'test_helper' | |
| 4 4 | 
             
            require 'hexapdf/content/operator'
         | 
| 5 5 | 
             
            require 'hexapdf/content/processor'
         | 
| 6 6 | 
             
            require 'hexapdf/serializer'
         | 
| 7 | 
            -
            require 'ostruct'
         | 
| 8 7 |  | 
| 9 8 | 
             
            describe HexaPDF::Content::Operator::BaseOperator do
         | 
| 10 9 | 
             
              before do
         | 
| @@ -191,8 +190,8 @@ end | |
| 191 190 |  | 
| 192 191 | 
             
            describe_operator :SetGraphicsStateParameters, :gs do
         | 
| 193 192 | 
             
              it "applies parameters from an ExtGState dictionary" do
         | 
| 194 | 
            -
                font =  | 
| 195 | 
            -
                font.glyph_scaling_factor  | 
| 193 | 
            +
                font = Object.new
         | 
| 194 | 
            +
                font.define_singleton_method(:glyph_scaling_factor) { 0.01 }
         | 
| 196 195 | 
             
                @processor.resources[:ExtGState] = {Name: {LW: 10, LC: 2, LJ: 2, ML: 2, D: [[3, 5], 2],
         | 
| 197 196 | 
             
                                                           RI: 2, SA: true, BM: :Multiply, CA: 0.5, ca: 0.5,
         | 
| 198 197 | 
             
                                                           AIS: true, TK: false, Font: [font, 10],
         | 
| @@ -453,8 +452,8 @@ describe_operator :SetFontAndSize, :Tf do | |
| 453 452 | 
             
                  self[:Font] && self[:Font][name]
         | 
| 454 453 | 
             
                end
         | 
| 455 454 |  | 
| 456 | 
            -
                font =  | 
| 457 | 
            -
                font.glyph_scaling_factor  | 
| 455 | 
            +
                font = Object.new
         | 
| 456 | 
            +
                font.define_singleton_method(:glyph_scaling_factor) { 0.01 }
         | 
| 458 457 | 
             
                @processor.resources[:Font] = {F1: font}
         | 
| 459 458 | 
             
                invoke(:F1, 10)
         | 
| 460 459 | 
             
                assert_equal(@processor.resources.font(:F1), @processor.graphics_state.font)
         | 
| @@ -4,17 +4,16 @@ require 'digest' | |
| 4 4 | 
             
            require 'test_helper'
         | 
| 5 5 | 
             
            require_relative 'common'
         | 
| 6 6 | 
             
            require 'hexapdf/digital_signature'
         | 
| 7 | 
            -
            require 'ostruct'
         | 
| 8 7 |  | 
| 9 8 | 
             
            describe HexaPDF::DigitalSignature::CMSHandler do
         | 
| 10 9 | 
             
              before do
         | 
| 11 | 
            -
                @data = 'Some data'
         | 
| 12 | 
            -
                @dict =  | 
| 13 | 
            -
                @pkcs7 = OpenSSL::PKCS7.sign(CERTIFICATES.signer_certificate, CERTIFICATES.signer_key,
         | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
                @dict.contents =  | 
| 17 | 
            -
                @dict.signed_data =  | 
| 10 | 
            +
                @data = data = 'Some data'
         | 
| 11 | 
            +
                @dict = Struct.new(:contents, :signed_data, :signature_type, :Reference, :M).new
         | 
| 12 | 
            +
                @pkcs7 = pkcs7 = OpenSSL::PKCS7.sign(CERTIFICATES.signer_certificate, CERTIFICATES.signer_key,
         | 
| 13 | 
            +
                                                     @data, [CERTIFICATES.ca_certificate],
         | 
| 14 | 
            +
                                                     OpenSSL::PKCS7::DETACHED)
         | 
| 15 | 
            +
                @dict.contents = pkcs7.to_der
         | 
| 16 | 
            +
                @dict.signed_data = data
         | 
| 18 17 | 
             
                @handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
         | 
| 19 18 | 
             
              end
         | 
| 20 19 |  | 
| @@ -4,7 +4,6 @@ require 'test_helper' | |
| 4 4 | 
             
            require 'hexapdf/digital_signature'
         | 
| 5 5 | 
             
            require 'hexapdf/document'
         | 
| 6 6 | 
             
            require 'time'
         | 
| 7 | 
            -
            require 'ostruct'
         | 
| 8 7 | 
             
            require 'openssl'
         | 
| 9 8 |  | 
| 10 9 | 
             
            describe HexaPDF::DigitalSignature::Handler do
         | 
| @@ -33,7 +32,7 @@ describe HexaPDF::DigitalSignature::Handler do | |
| 33 32 |  | 
| 34 33 | 
             
              describe "store_verification_callback" do
         | 
| 35 34 | 
             
                before do
         | 
| 36 | 
            -
                  @context =  | 
| 35 | 
            +
                  @context = Struct.new(:error).new
         | 
| 37 36 | 
             
                end
         | 
| 38 37 |  | 
| 39 38 | 
             
                it "can allow self-signed certificates" do
         | 
| @@ -60,7 +59,7 @@ describe HexaPDF::DigitalSignature::Handler do | |
| 60 59 | 
             
                ].each do |success, not_before, not_after|
         | 
| 61 60 | 
             
                  @result.messages.clear
         | 
| 62 61 | 
             
                  @handler.define_singleton_method(:signer_certificate) do
         | 
| 63 | 
            -
                     | 
| 62 | 
            +
                    Struct.new(:not_before, :not_after).new.tap do |struct|
         | 
| 64 63 | 
             
                      struct.not_before = Time.parse("2021-11-14 #{not_before}")
         | 
| 65 64 | 
             
                      struct.not_after = Time.parse("2021-11-14 #{not_after}")
         | 
| 66 65 | 
             
                    end
         | 
| @@ -3,12 +3,11 @@ | |
| 3 3 | 
             
            require 'test_helper'
         | 
| 4 4 | 
             
            require_relative 'common'
         | 
| 5 5 | 
             
            require 'hexapdf/digital_signature'
         | 
| 6 | 
            -
            require 'ostruct'
         | 
| 7 6 |  | 
| 8 7 | 
             
            describe HexaPDF::DigitalSignature::PKCS1Handler do
         | 
| 9 8 | 
             
              before do
         | 
| 10 9 | 
             
                @data = 'Some data'
         | 
| 11 | 
            -
                @dict =  | 
| 10 | 
            +
                @dict = Struct.new(:signed_data, :contents, :Cert, :Reference, :M).new
         | 
| 12 11 | 
             
                @dict.signed_data = @data
         | 
| 13 12 | 
             
                encoded_data = CERTIFICATES.signer_key.sign(OpenSSL::Digest.new('SHA1'), @data)
         | 
| 14 13 | 
             
                @dict.contents = OpenSSL::ASN1::OctetString.new(encoded_data).to_der
         | 
| @@ -0,0 +1,55 @@ | |
| 1 | 
            +
            # -*- encoding: utf-8 -*-
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'test_helper'
         | 
| 4 | 
            +
            require 'hexapdf/document'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            describe HexaPDF::Document::Annotations do
         | 
| 7 | 
            +
              before do
         | 
| 8 | 
            +
                @doc = HexaPDF::Document.new
         | 
| 9 | 
            +
                @page = @doc.pages.add
         | 
| 10 | 
            +
                @annots = @doc.annotations
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              describe "create" do
         | 
| 14 | 
            +
                it "fails if the type argument doesn't refer to an implemented method" do
         | 
| 15 | 
            +
                  assert_raises(ArgumentError) { @annots.create(:unknown, @page) }
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                it "delegates to the actual create_TYPE implementation" do
         | 
| 19 | 
            +
                  annot = @annots.create(:line, @page, start_point: [0, 0], end_point: [10, 10])
         | 
| 20 | 
            +
                  assert_equal(:Line, annot[:Subtype])
         | 
| 21 | 
            +
                  annot = @annots.create(:rectangle, @page, 10, 20, 30, 40)
         | 
| 22 | 
            +
                  assert_equal(:Square, annot[:Subtype])
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              describe "create_line" do
         | 
| 27 | 
            +
                it "creates an appropriate line annotation object" do
         | 
| 28 | 
            +
                  annot = @annots.create(:line, @page, start_point: [0, 5], end_point: [10, 15])
         | 
| 29 | 
            +
                  assert_equal(:Annot, annot[:Type])
         | 
| 30 | 
            +
                  assert_equal(:Line, annot[:Subtype])
         | 
| 31 | 
            +
                  assert_equal([0, 5, 10, 15], annot.line)
         | 
| 32 | 
            +
                  assert_equal(annot, @page[:Annots].first)
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              describe "create_rectangle" do
         | 
| 37 | 
            +
                it "creates an appropriate square annotation object" do
         | 
| 38 | 
            +
                  annot = @annots.create(:rectangle, @page, 10, 20, 30, 40)
         | 
| 39 | 
            +
                  assert_equal(:Annot, annot[:Type])
         | 
| 40 | 
            +
                  assert_equal(:Square, annot[:Subtype])
         | 
| 41 | 
            +
                  assert_equal([10, 20, 40, 60], annot[:Rect])
         | 
| 42 | 
            +
                  assert_equal(annot, @page[:Annots].first)
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              describe "create_ellipse" do
         | 
| 47 | 
            +
                it "creates an appropriate circle annotation object" do
         | 
| 48 | 
            +
                  annot = @annots.create(:ellipse, @page, 100, 100, a: 30, b: 40)
         | 
| 49 | 
            +
                  assert_equal(:Annot, annot[:Type])
         | 
| 50 | 
            +
                  assert_equal(:Circle, annot[:Subtype])
         | 
| 51 | 
            +
                  assert_equal([70, 60, 130, 140], annot[:Rect])
         | 
| 52 | 
            +
                  assert_equal(annot, @page[:Annots].first)
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
            end
         | 
| @@ -146,6 +146,16 @@ describe HexaPDF::Document::Layout do | |
| 146 146 | 
             
                end
         | 
| 147 147 | 
             
              end
         | 
| 148 148 |  | 
| 149 | 
            +
              describe "style?" do
         | 
| 150 | 
            +
                it "returns true if a given style is defined" do
         | 
| 151 | 
            +
                  assert(@layout.style?(:base))
         | 
| 152 | 
            +
                end
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                it "returns false if a given style is not defined" do
         | 
| 155 | 
            +
                  refute(@layout.style?(:unknown))
         | 
| 156 | 
            +
                end
         | 
| 157 | 
            +
              end
         | 
| 158 | 
            +
             | 
| 149 159 | 
             
              describe "styles" do
         | 
| 150 160 | 
             
                it "returns the existing styles" do
         | 
| 151 161 | 
             
                  @layout.style(:test, font_size: 20)
         | 
| @@ -168,6 +178,16 @@ describe HexaPDF::Document::Layout do | |
| 168 178 | 
             
                  assert_kind_of(HexaPDF::Font::Type1Wrapper, style.font)
         | 
| 169 179 | 
             
                end
         | 
| 170 180 |  | 
| 181 | 
            +
                it "uses the font_bold property when resolving a font name to a font wrapper" do
         | 
| 182 | 
            +
                  style = @layout.send(:retrieve_style, {font: 'Helvetica', font_bold: true})
         | 
| 183 | 
            +
                  assert_equal('Helvetica-Bold', style.font.wrapped_font.font_name)
         | 
| 184 | 
            +
                end
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                it "uses the font_italic property when resolving a font name to a font wrapper" do
         | 
| 187 | 
            +
                  style = @layout.send(:retrieve_style, {font: 'Helvetica', font_italic: true})
         | 
| 188 | 
            +
                  assert_equal('Helvetica-Oblique', style.font.wrapped_font.font_name)
         | 
| 189 | 
            +
                end
         | 
| 190 | 
            +
             | 
| 171 191 | 
             
                it "sets the :base style's font if no font is set" do
         | 
| 172 192 | 
             
                  @layout.style(:base, font: 'Helvetica')
         | 
| 173 193 | 
             
                  style = @layout.send(:retrieve_style, {})
         | 
| @@ -203,7 +223,8 @@ describe HexaPDF::Document::Layout do | |
| 203 223 |  | 
| 204 224 | 
             
              describe "box" do
         | 
| 205 225 | 
             
                it "creates the request box" do
         | 
| 206 | 
            -
                  box = @layout.box(:column, columns: 3,  | 
| 226 | 
            +
                  box = @layout.box(:column, columns: 3, width: 15, height: 30,
         | 
| 227 | 
            +
                                    style: {font_size: 10, box_options: {gaps: 20}},
         | 
| 207 228 | 
             
                                    properties: {key: :value})
         | 
| 208 229 | 
             
                  assert_equal(15, box.width)
         | 
| 209 230 | 
             
                  assert_equal(30, box.height)
         | 
| @@ -431,9 +452,10 @@ describe HexaPDF::Document::Layout do | |
| 431 452 |  | 
| 432 453 | 
             
              describe "table_box" do
         | 
| 433 454 | 
             
                it "creates a table box" do
         | 
| 434 | 
            -
                  box = @layout.table_box([['m']],  | 
| 455 | 
            +
                  box = @layout.table_box([['m']], header: proc { [['a']] },
         | 
| 435 456 | 
             
                                          footer: proc { [['b']] }, cell_style: {background_color: "red"},
         | 
| 436 457 | 
             
                                          width: 100, height: 300, style: {background_color: "blue"},
         | 
| 458 | 
            +
                                          box_options: {column_widths: [100]},
         | 
| 437 459 | 
             
                                          properties: {key: :value}, border: {width: 1})
         | 
| 438 460 | 
             
                  assert_equal(100, box.width)
         | 
| 439 461 | 
             
                  assert_equal(300, box.height)
         | 
| @@ -209,6 +209,13 @@ describe HexaPDF::Font::TrueTypeWrapper do | |
| 209 209 | 
             
                               dict[:Encoding].stream)
         | 
| 210 210 | 
             
                  assert_equal([glyph.id, [glyph.width]], dict[:DescendantFonts][0][:W].value)
         | 
| 211 211 | 
             
                end
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                it "handles the case where the font is added but then not used and deleted" do
         | 
| 214 | 
            +
                  @doc.task(:optimize, compact: true)
         | 
| 215 | 
            +
                  assert(@font_wrapper.pdf_object.null?)
         | 
| 216 | 
            +
                  @doc.dispatch_message(:complete_objects)
         | 
| 217 | 
            +
                  assert(@font_wrapper.pdf_object.null?)
         | 
| 218 | 
            +
                end
         | 
| 212 219 | 
             
              end
         | 
| 213 220 |  | 
| 214 221 | 
             
              describe "font file embedding" do
         | 
| @@ -140,5 +140,12 @@ describe HexaPDF::Font::Type1Wrapper do | |
| 140 140 | 
             
                it "makes sure that the PDF dictionaries are indirect" do
         | 
| 141 141 | 
             
                  assert(@times_wrapper.pdf_object.indirect?)
         | 
| 142 142 | 
             
                end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                it "handles the case where the font is added but then not used and deleted" do
         | 
| 145 | 
            +
                  @doc.task(:optimize, compact: true)
         | 
| 146 | 
            +
                  assert(@times_wrapper.pdf_object.null?)
         | 
| 147 | 
            +
                  @doc.dispatch_message(:complete_objects)
         | 
| 148 | 
            +
                  assert(@times_wrapper.pdf_object.null?)
         | 
| 149 | 
            +
                end
         | 
| 143 150 | 
             
              end
         | 
| 144 151 | 
             
            end
         | 
| @@ -27,6 +27,16 @@ describe HexaPDF::Font::TrueType::Subsetter do | |
| 27 27 | 
             
                assert_equal(value, @subsetter.subset_glyph_id(5))
         | 
| 28 28 | 
             
              end
         | 
| 29 29 |  | 
| 30 | 
            +
              it "doesn't use certain subset glyph IDs for performance reasons" do
         | 
| 31 | 
            +
                1.upto(93) {|i| @subsetter.use_glyph(i) }
         | 
| 32 | 
            +
                # glyph 0, 93 used glyph, 4 special glyphs
         | 
| 33 | 
            +
                assert_equal(1 + 93 + 4, @subsetter.instance_variable_get(:@glyph_map).size)
         | 
| 34 | 
            +
                1.upto(12) {|i| assert_equal(i, @subsetter.subset_glyph_id(i), "id=#{i}") }
         | 
| 35 | 
            +
                13.upto(38) {|i| assert_equal(i + 1, @subsetter.subset_glyph_id(i), "id=#{i}") }
         | 
| 36 | 
            +
                39.upto(88) {|i| assert_equal(i + 3, @subsetter.subset_glyph_id(i), "id=#{i}") }
         | 
| 37 | 
            +
                89.upto(93) {|i| assert_equal(i + 4, @subsetter.subset_glyph_id(i), "id=#{i}") }
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 30 40 | 
             
              it "creates the subset font file" do
         | 
| 31 41 | 
             
                gid = @font[:cmap].preferred_table[0x41]
         | 
| 32 42 | 
             
                @subsetter.use_glyph(gid)
         | 
| @@ -738,6 +738,30 @@ describe HexaPDF::Layout::Style do | |
| 738 738 | 
             
                end
         | 
| 739 739 | 
             
              end
         | 
| 740 740 |  | 
| 741 | 
            +
              describe "each_property" do
         | 
| 742 | 
            +
                it "yields all set properties with their values" do
         | 
| 743 | 
            +
                  @style.font_size = 5
         | 
| 744 | 
            +
                  @style.line_spacing = 1.2
         | 
| 745 | 
            +
                  assert_equal(0.005, @style.scaled_font_size)
         | 
| 746 | 
            +
                  assert_equal([[:font, @style.font], [:font_size, 5], [:horizontal_scaling, 100],
         | 
| 747 | 
            +
                                [:line_spacing, @style.line_spacing], [:subscript, false], [:superscript, false]],
         | 
| 748 | 
            +
                               @style.each_property.to_a.sort)
         | 
| 749 | 
            +
                end
         | 
| 750 | 
            +
              end
         | 
| 751 | 
            +
             | 
| 752 | 
            +
              describe "merge" do
         | 
| 753 | 
            +
                it "merges all set properties" do
         | 
| 754 | 
            +
                  @style.font_size = 5
         | 
| 755 | 
            +
                  @style.line_spacing = 1.2
         | 
| 756 | 
            +
                  new_style = HexaPDF::Layout::Style.new
         | 
| 757 | 
            +
                  new_style.update(font_size: 3, line_spacing: {type: :fixed, value: 2.5})
         | 
| 758 | 
            +
                  new_style.merge(@style)
         | 
| 759 | 
            +
                  assert_equal(5, new_style.font_size)
         | 
| 760 | 
            +
                  assert_equal(:proportional, new_style.line_spacing.type)
         | 
| 761 | 
            +
                  assert_equal(1.2, new_style.line_spacing.value)
         | 
| 762 | 
            +
                end
         | 
| 763 | 
            +
              end
         | 
| 764 | 
            +
             | 
| 741 765 | 
             
              it "has several simple and dynamically generated properties with default values" do
         | 
| 742 766 | 
             
                @style = HexaPDF::Layout::Style.new
         | 
| 743 767 | 
             
                assert_raises(HexaPDF::Error) { @style.font }
         | 
| @@ -780,6 +804,7 @@ describe HexaPDF::Layout::Style do | |
| 780 804 | 
             
                assert_equal(:left, @style.align)
         | 
| 781 805 | 
             
                assert_equal(:top, @style.valign)
         | 
| 782 806 | 
             
                assert_equal(:default, @style.mask_mode)
         | 
| 807 | 
            +
                assert_equal({}, @style.box_options)
         | 
| 783 808 | 
             
              end
         | 
| 784 809 |  | 
| 785 810 | 
             
              it "allows using a non-standard setter for generated properties" do
         | 
| @@ -790,8 +815,8 @@ describe HexaPDF::Layout::Style do | |
| 790 815 | 
             
                @style.stroke_dash_pattern(5, 2)
         | 
| 791 816 | 
             
                assert_equal([[5], 2], @style.stroke_dash_pattern.to_operands)
         | 
| 792 817 |  | 
| 793 | 
            -
                @style.line_spacing( | 
| 794 | 
            -
                assert_equal([:proportional,  | 
| 818 | 
            +
                @style.line_spacing(HexaPDF::Layout::Style::LineSpacing.new(type: :double))
         | 
| 819 | 
            +
                assert_equal([:proportional, 2], [@style.line_spacing.type, @style.line_spacing.value])
         | 
| 795 820 | 
             
              end
         | 
| 796 821 |  | 
| 797 822 | 
             
              it "allows checking for valid values" do
         | 
| @@ -99,7 +99,7 @@ describe HexaPDF::Task::Optimize do | |
| 99 99 | 
             
                  objstm = @doc.add({}, type: HexaPDF::Type::ObjectStream)
         | 
| 100 100 | 
             
                  @doc.add({}, type: HexaPDF::Type::XRefStream)
         | 
| 101 101 | 
             
                  objstm.add_object(@doc.add({Type: :Test}))
         | 
| 102 | 
            -
                  @doc.write(io)
         | 
| 102 | 
            +
                  @doc.write(io, compact: false)
         | 
| 103 103 | 
             
                  io.rewind
         | 
| 104 104 | 
             
                  @doc = HexaPDF::Document.new(io: io)
         | 
| 105 105 | 
             
                end
         |