hexapdf 0.32.2 → 0.34.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +104 -1
- data/README.md +9 -0
- data/examples/002-graphics.rb +15 -17
- data/examples/003-arcs.rb +9 -9
- data/examples/009-text_layouter_alignment.rb +1 -1
- data/examples/010-text_layouter_inline_boxes.rb +2 -2
- data/examples/011-text_layouter_line_wrapping.rb +1 -1
- data/examples/012-text_layouter_styling.rb +7 -7
- data/examples/013-text_layouter_shapes.rb +1 -1
- data/examples/014-text_in_polygon.rb +1 -1
- data/examples/015-boxes.rb +8 -7
- data/examples/016-frame_automatic_box_placement.rb +2 -2
- data/examples/017-frame_text_flow.rb +2 -1
- data/examples/018-composer.rb +1 -1
- data/examples/020-column_box.rb +2 -1
- data/examples/025-table_box.rb +46 -0
- data/examples/026-optional_content.rb +55 -0
- data/examples/027-composer_optional_content.rb +83 -0
- data/lib/hexapdf/cli/command.rb +12 -3
- data/lib/hexapdf/cli/fonts.rb +1 -1
- data/lib/hexapdf/cli/form.rb +5 -5
- data/lib/hexapdf/cli/inspect.rb +5 -7
- data/lib/hexapdf/composer.rb +106 -53
- data/lib/hexapdf/configuration.rb +65 -40
- data/lib/hexapdf/content/canvas.rb +445 -267
- data/lib/hexapdf/content/color_space.rb +72 -25
- data/lib/hexapdf/content/graphic_object/arc.rb +57 -24
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -23
- data/lib/hexapdf/content/graphic_object/geom2d.rb +47 -6
- data/lib/hexapdf/content/graphic_object/solid_arc.rb +58 -36
- data/lib/hexapdf/content/graphic_object.rb +6 -7
- data/lib/hexapdf/content/graphics_state.rb +54 -45
- data/lib/hexapdf/content/operator.rb +54 -54
- data/lib/hexapdf/content/parser.rb +2 -2
- data/lib/hexapdf/content/processor.rb +15 -15
- data/lib/hexapdf/content/transformation_matrix.rb +1 -1
- data/lib/hexapdf/content.rb +5 -0
- data/lib/hexapdf/dictionary.rb +7 -5
- data/lib/hexapdf/dictionary_fields.rb +43 -16
- data/lib/hexapdf/digital_signature/cms_handler.rb +2 -2
- data/lib/hexapdf/digital_signature/handler.rb +1 -1
- data/lib/hexapdf/digital_signature/pkcs1_handler.rb +2 -3
- data/lib/hexapdf/digital_signature/signature.rb +6 -6
- data/lib/hexapdf/digital_signature/signatures.rb +13 -12
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +14 -5
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +2 -4
- data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +4 -4
- data/lib/hexapdf/digital_signature/signing.rb +4 -0
- data/lib/hexapdf/digital_signature/verification_result.rb +3 -4
- data/lib/hexapdf/digital_signature.rb +7 -2
- data/lib/hexapdf/document/destinations.rb +12 -11
- data/lib/hexapdf/document/files.rb +1 -1
- data/lib/hexapdf/document/fonts.rb +1 -1
- data/lib/hexapdf/document/layout.rb +170 -39
- data/lib/hexapdf/document/pages.rb +4 -3
- data/lib/hexapdf/document.rb +96 -55
- data/lib/hexapdf/encryption/aes.rb +5 -5
- data/lib/hexapdf/encryption/arc4.rb +1 -1
- data/lib/hexapdf/encryption/fast_aes.rb +2 -2
- data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
- data/lib/hexapdf/encryption/identity.rb +1 -1
- data/lib/hexapdf/encryption/ruby_aes.rb +11 -21
- data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
- data/lib/hexapdf/encryption/security_handler.rb +31 -24
- data/lib/hexapdf/encryption/standard_security_handler.rb +45 -36
- data/lib/hexapdf/encryption.rb +7 -2
- data/lib/hexapdf/error.rb +18 -0
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
- data/lib/hexapdf/filter/flate_decode.rb +1 -1
- data/lib/hexapdf/filter/lzw_decode.rb +1 -1
- data/lib/hexapdf/filter/pass_through.rb +1 -1
- data/lib/hexapdf/filter/predictor.rb +1 -1
- data/lib/hexapdf/filter/run_length_decode.rb +1 -1
- data/lib/hexapdf/filter.rb +55 -6
- data/lib/hexapdf/font/cmap/parser.rb +2 -2
- data/lib/hexapdf/font/cmap.rb +1 -1
- data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +2 -2
- data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +3 -3
- data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
- data/lib/hexapdf/font/invalid_glyph.rb +3 -0
- data/lib/hexapdf/font/true_type_wrapper.rb +17 -4
- data/lib/hexapdf/font/type1_wrapper.rb +19 -4
- data/lib/hexapdf/font_loader/from_configuration.rb +5 -2
- data/lib/hexapdf/font_loader/from_file.rb +5 -5
- data/lib/hexapdf/font_loader/standard14.rb +3 -3
- data/lib/hexapdf/font_loader.rb +3 -0
- data/lib/hexapdf/image_loader/jpeg.rb +2 -2
- data/lib/hexapdf/image_loader/pdf.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +2 -2
- data/lib/hexapdf/image_loader.rb +1 -1
- data/lib/hexapdf/importer.rb +13 -0
- data/lib/hexapdf/layout/box.rb +32 -5
- data/lib/hexapdf/layout/box_fitter.rb +2 -2
- data/lib/hexapdf/layout/column_box.rb +20 -5
- data/lib/hexapdf/layout/frame.rb +53 -18
- data/lib/hexapdf/layout/image_box.rb +5 -0
- data/lib/hexapdf/layout/inline_box.rb +21 -9
- data/lib/hexapdf/layout/list_box.rb +50 -20
- data/lib/hexapdf/layout/page_style.rb +6 -5
- data/lib/hexapdf/layout/style.rb +64 -9
- data/lib/hexapdf/layout/table_box.rb +684 -0
- data/lib/hexapdf/layout/text_box.rb +12 -3
- data/lib/hexapdf/layout/text_fragment.rb +29 -3
- data/lib/hexapdf/layout/text_layouter.rb +32 -8
- data/lib/hexapdf/layout.rb +1 -0
- data/lib/hexapdf/name_tree_node.rb +1 -1
- data/lib/hexapdf/number_tree_node.rb +1 -1
- data/lib/hexapdf/object.rb +18 -7
- data/lib/hexapdf/parser.rb +7 -7
- data/lib/hexapdf/pdf_array.rb +1 -1
- data/lib/hexapdf/rectangle.rb +1 -1
- data/lib/hexapdf/reference.rb +1 -1
- data/lib/hexapdf/revision.rb +1 -1
- data/lib/hexapdf/revisions.rb +3 -3
- data/lib/hexapdf/serializer.rb +15 -15
- data/lib/hexapdf/stream.rb +5 -4
- data/lib/hexapdf/tokenizer.rb +14 -14
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +22 -22
- data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/field.rb +2 -2
- data/lib/hexapdf/type/acro_form/form.rb +1 -1
- data/lib/hexapdf/type/acro_form/signature_field.rb +4 -4
- data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
- data/lib/hexapdf/type/acro_form.rb +1 -1
- data/lib/hexapdf/type/action.rb +1 -1
- data/lib/hexapdf/type/actions/go_to.rb +1 -1
- data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
- data/lib/hexapdf/type/actions/launch.rb +1 -1
- data/lib/hexapdf/type/actions/set_ocg_state.rb +86 -0
- data/lib/hexapdf/type/actions/uri.rb +1 -1
- data/lib/hexapdf/type/actions.rb +2 -1
- data/lib/hexapdf/type/annotation.rb +3 -3
- data/lib/hexapdf/type/annotations/link.rb +1 -1
- data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
- data/lib/hexapdf/type/annotations/text.rb +2 -3
- data/lib/hexapdf/type/annotations/widget.rb +2 -2
- data/lib/hexapdf/type/annotations.rb +1 -1
- data/lib/hexapdf/type/catalog.rb +11 -2
- data/lib/hexapdf/type/cid_font.rb +18 -4
- data/lib/hexapdf/type/embedded_file.rb +1 -1
- data/lib/hexapdf/type/file_specification.rb +2 -2
- data/lib/hexapdf/type/font_descriptor.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +2 -2
- data/lib/hexapdf/type/font_type0.rb +3 -3
- data/lib/hexapdf/type/font_type3.rb +1 -1
- data/lib/hexapdf/type/form.rb +76 -6
- data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
- data/lib/hexapdf/type/icon_fit.rb +1 -1
- data/lib/hexapdf/type/image.rb +1 -1
- data/lib/hexapdf/type/info.rb +1 -1
- data/lib/hexapdf/type/mark_information.rb +1 -1
- data/lib/hexapdf/type/names.rb +2 -2
- data/lib/hexapdf/type/object_stream.rb +2 -1
- data/lib/hexapdf/type/optional_content_configuration.rb +170 -0
- data/lib/hexapdf/type/optional_content_group.rb +370 -0
- data/lib/hexapdf/type/optional_content_membership.rb +63 -0
- data/lib/hexapdf/type/optional_content_properties.rb +158 -0
- data/lib/hexapdf/type/outline.rb +1 -1
- data/lib/hexapdf/type/outline_item.rb +1 -1
- data/lib/hexapdf/type/page.rb +46 -21
- data/lib/hexapdf/type/page_label.rb +5 -9
- data/lib/hexapdf/type/page_tree_node.rb +1 -1
- data/lib/hexapdf/type/resources.rb +1 -1
- data/lib/hexapdf/type/trailer.rb +2 -2
- data/lib/hexapdf/type/viewer_preferences.rb +1 -1
- data/lib/hexapdf/type/xref_stream.rb +2 -2
- data/lib/hexapdf/type.rb +4 -0
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -2
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +4 -4
- data/lib/hexapdf/xref_section.rb +2 -2
- data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +11 -1
- data/test/hexapdf/content/graphic_object/test_geom2d.rb +7 -0
- data/test/hexapdf/content/test_canvas.rb +49 -1
- data/test/hexapdf/digital_signature/test_signatures.rb +22 -0
- data/test/hexapdf/document/test_files.rb +2 -2
- data/test/hexapdf/document/test_layout.rb +105 -2
- data/test/hexapdf/document/test_pages.rb +6 -6
- data/test/hexapdf/encryption/test_security_handler.rb +12 -11
- data/test/hexapdf/encryption/test_standard_security_handler.rb +35 -23
- data/test/hexapdf/font/test_true_type_wrapper.rb +18 -1
- data/test/hexapdf/font/test_type1_wrapper.rb +15 -1
- data/test/hexapdf/layout/test_box.rb +14 -5
- data/test/hexapdf/layout/test_column_box.rb +65 -21
- data/test/hexapdf/layout/test_frame.rb +27 -15
- data/test/hexapdf/layout/test_image_box.rb +4 -0
- data/test/hexapdf/layout/test_inline_box.rb +17 -3
- data/test/hexapdf/layout/test_list_box.rb +84 -33
- data/test/hexapdf/layout/test_page_style.rb +3 -2
- data/test/hexapdf/layout/test_style.rb +60 -0
- data/test/hexapdf/layout/test_table_box.rb +728 -0
- data/test/hexapdf/layout/test_text_box.rb +26 -0
- data/test/hexapdf/layout/test_text_fragment.rb +33 -0
- data/test/hexapdf/layout/test_text_layouter.rb +36 -5
- data/test/hexapdf/test_composer.rb +10 -0
- data/test/hexapdf/test_dictionary.rb +10 -0
- data/test/hexapdf/test_dictionary_fields.rb +4 -1
- data/test/hexapdf/test_document.rb +5 -0
- data/test/hexapdf/test_filter.rb +8 -0
- data/test/hexapdf/test_importer.rb +9 -0
- data/test/hexapdf/test_object.rb +16 -5
- data/test/hexapdf/test_stream.rb +7 -0
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +13 -5
- data/test/hexapdf/type/acro_form/test_form.rb +4 -3
- data/test/hexapdf/type/actions/test_set_ocg_state.rb +40 -0
- data/test/hexapdf/type/test_catalog.rb +11 -0
- data/test/hexapdf/type/test_form.rb +119 -0
- data/test/hexapdf/type/test_optional_content_configuration.rb +112 -0
- data/test/hexapdf/type/test_optional_content_group.rb +158 -0
- data/test/hexapdf/type/test_optional_content_properties.rb +109 -0
- data/test/hexapdf/type/test_page.rb +20 -6
- metadata +28 -8
data/lib/hexapdf/layout/frame.rb
CHANGED
@@ -106,8 +106,8 @@ module HexaPDF
|
|
106
106
|
# The available height in the frame for this particular box.
|
107
107
|
attr_accessor :available_height
|
108
108
|
|
109
|
-
# The rectangle (a Geom2D::
|
110
|
-
# the box.
|
109
|
+
# The rectangle (a Geom2D::Rectangle object) that will be removed from the frame when
|
110
|
+
# drawing the box.
|
111
111
|
attr_accessor :mask
|
112
112
|
|
113
113
|
# Initialize the result object for the given box.
|
@@ -133,12 +133,18 @@ module HexaPDF
|
|
133
133
|
# The configuration option "debug" can be used to add visual debug output with respect to
|
134
134
|
# box placement.
|
135
135
|
def draw(canvas)
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
136
|
+
doc = canvas.context.document
|
137
|
+
if doc.config['debug']
|
138
|
+
name = "#{box.class} (#{x.to_i},#{y.to_i}-#{box.width.to_i}x#{box.height.to_i})"
|
139
|
+
ocg = doc.optional_content.ocg(name)
|
140
|
+
canvas.optional_content(ocg) do
|
141
|
+
canvas.save_graphics_state do
|
142
|
+
canvas.fill_color("green").stroke_color("darkgreen").
|
143
|
+
opacity(fill_alpha: 0.1, stroke_alpha: 0.2).
|
144
|
+
draw(:geom2d, object: mask, path_only: true).fill_stroke
|
145
|
+
end
|
141
146
|
end
|
147
|
+
doc.optional_content.default_configuration.add_ocg_to_ui(ocg, path: 'Debug')
|
142
148
|
end
|
143
149
|
box.draw(canvas, x, y)
|
144
150
|
end
|
@@ -157,7 +163,8 @@ module HexaPDF
|
|
157
163
|
# The height of the frame.
|
158
164
|
attr_reader :height
|
159
165
|
|
160
|
-
# The shape of the frame, a Geom2D::
|
166
|
+
# The shape of the frame, either a Geom2D::Rectangle in the simple case or a
|
167
|
+
# Geom2D::PolygonSet consisting of rectilinear polygons in the more complex case.
|
161
168
|
attr_reader :shape
|
162
169
|
|
163
170
|
# The x-coordinate where the next box will be placed.
|
@@ -181,15 +188,19 @@ module HexaPDF
|
|
181
188
|
# Also see the note in the #x documentation for further information.
|
182
189
|
attr_reader :available_height
|
183
190
|
|
191
|
+
# The context object (a HexaPDF::Type::Page or HexaPDF::Type::Form) for which this frame
|
192
|
+
# should be used.
|
193
|
+
attr_reader :context
|
194
|
+
|
184
195
|
# Creates a new Frame object for the given rectangular area.
|
185
|
-
def initialize(left, bottom, width, height, shape: nil)
|
196
|
+
def initialize(left, bottom, width, height, shape: nil, context: nil)
|
186
197
|
@left = left
|
187
198
|
@bottom = bottom
|
188
199
|
@width = width
|
189
200
|
@height = height
|
190
|
-
@shape = shape ||
|
191
|
-
|
192
|
-
|
201
|
+
@shape = shape || create_rectangle(left, bottom, left + width, bottom + height)
|
202
|
+
@context = context
|
203
|
+
|
193
204
|
@x = left
|
194
205
|
@y = bottom + height
|
195
206
|
@available_width = width
|
@@ -199,6 +210,12 @@ module HexaPDF
|
|
199
210
|
@region_selection = :max_height
|
200
211
|
end
|
201
212
|
|
213
|
+
# Returns the HexaPDF::Document instance (through #context) that is associated with this Frame
|
214
|
+
# object or +nil+ if no context object has been set.
|
215
|
+
def document
|
216
|
+
@context&.document
|
217
|
+
end
|
218
|
+
|
202
219
|
# Fits the given box into the current region of available space and returns a FitResult
|
203
220
|
# object.
|
204
221
|
#
|
@@ -319,8 +336,16 @@ module HexaPDF
|
|
319
336
|
def find_next_region
|
320
337
|
case @region_selection
|
321
338
|
when :max_width
|
322
|
-
|
323
|
-
|
339
|
+
if @shape.kind_of?(Geom2D::Rectangle)
|
340
|
+
@x = @shape.x
|
341
|
+
@y = @shape.y + @shape.height
|
342
|
+
@available_width = @shape.width
|
343
|
+
@available_height = @shape.height
|
344
|
+
@region_selection = :trim_shape
|
345
|
+
else
|
346
|
+
find_max_width_region
|
347
|
+
@region_selection = :max_height
|
348
|
+
end
|
324
349
|
when :max_height
|
325
350
|
x, y, aw, ah = @x, @y, @available_width, @available_height
|
326
351
|
find_max_height_region
|
@@ -338,7 +363,17 @@ module HexaPDF
|
|
338
363
|
|
339
364
|
# Removes the given *rectilinear* polygon from the frame's shape.
|
340
365
|
def remove_area(polygon)
|
341
|
-
@shape = Geom2D::
|
366
|
+
@shape = if @shape.kind_of?(Geom2D::Rectangle) && polygon.kind_of?(Geom2D::Rectangle) &&
|
367
|
+
float_equal(@shape.x, polygon.x) && float_equal(@shape.width, polygon.width) &&
|
368
|
+
float_equal(@shape.y + @shape.height, polygon.y + polygon.height)
|
369
|
+
if float_equal(@shape.height, polygon.height)
|
370
|
+
Geom2D::PolygonSet()
|
371
|
+
else
|
372
|
+
Geom2D::Rectangle(@shape.x, @shape.y, @shape.width, @shape.height - polygon.height)
|
373
|
+
end
|
374
|
+
else
|
375
|
+
Geom2D::Algorithms::PolygonOperation.run(@shape, polygon, :difference)
|
376
|
+
end
|
342
377
|
@region_selection = :max_width
|
343
378
|
find_next_region
|
344
379
|
end
|
@@ -369,8 +404,7 @@ module HexaPDF
|
|
369
404
|
# Creates a Geom2D::Polygon object representing the rectangle with the bottom left corner
|
370
405
|
# (blx, bly) and the top right corner (trx, try).
|
371
406
|
def create_rectangle(blx, bly, trx, try)
|
372
|
-
Geom2D::
|
373
|
-
Geom2D::Point(trx, try), Geom2D::Point(blx, try))
|
407
|
+
Geom2D::Rectangle(blx, bly, trx - blx, try - bly)
|
374
408
|
end
|
375
409
|
|
376
410
|
# Finds the region with the maximum width.
|
@@ -404,7 +438,8 @@ module HexaPDF
|
|
404
438
|
|
405
439
|
# Trims the frame's shape so that the next starting point is different.
|
406
440
|
def trim_shape
|
407
|
-
|
441
|
+
@x = @y = @available_width = @available_height = 0
|
442
|
+
return if @shape.kind_of?(Geom2D::Rectangle) || !(segments = find_starting_point)
|
408
443
|
|
409
444
|
# Just use the second top-most segment
|
410
445
|
# TODO: not the optimal solution!
|
@@ -74,6 +74,11 @@ module HexaPDF
|
|
74
74
|
@image = image
|
75
75
|
end
|
76
76
|
|
77
|
+
# Returns +false+ since the image is always drawn if it fits.
|
78
|
+
def empty?
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
77
82
|
# Fits the image into the available space, taking the initially set width and height into
|
78
83
|
# account (see the class description for details).
|
79
84
|
def fit(available_width, available_height, _frame)
|
@@ -47,9 +47,10 @@ module HexaPDF
|
|
47
47
|
# beforehand! This means the box *must* have at least its width set. The height may either also
|
48
48
|
# be set or determined during fitting.
|
49
49
|
#
|
50
|
-
# Fitting of the wrapped box
|
51
|
-
# a frame is used that has the width of the
|
52
|
-
# practically infinite height. In the latter case
|
50
|
+
# Fitting of the wrapped box via #fit_wrapped_box needs to be done before accessing any other
|
51
|
+
# method that uses the wrapped box. For fitting, a frame is used that has the width of the
|
52
|
+
# wrapped box and its height, or if not set, a practically infinite height. In the latter case
|
53
|
+
# the height *must* be set during fitting.
|
53
54
|
class InlineBox
|
54
55
|
|
55
56
|
# Creates an InlineBox that wraps a basic Box. All arguments (except +valign+) and the block
|
@@ -73,14 +74,14 @@ module HexaPDF
|
|
73
74
|
# The +valign+ argument can be used to specify the vertical alignment of the box relative to
|
74
75
|
# other items in the Line.
|
75
76
|
def initialize(box, valign: :baseline)
|
77
|
+
raise HexaPDF::Error, "Width of box not set" if box.width == 0
|
76
78
|
@box = box
|
77
79
|
@valign = valign
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the style of the wrapped box.
|
83
|
+
def style
|
84
|
+
box.style
|
84
85
|
end
|
85
86
|
|
86
87
|
# Returns +true+ if this inline box is just a placeholder without drawing operations.
|
@@ -125,6 +126,17 @@ module HexaPDF
|
|
125
126
|
height
|
126
127
|
end
|
127
128
|
|
129
|
+
# Fits the wrapped box, using the given context (see Frame#context).
|
130
|
+
def fit_wrapped_box(context)
|
131
|
+
@fit_result = Frame.new(0, 0, box.width, box.height == 0 ? 100_000 : box.height,
|
132
|
+
context: context).fit(box)
|
133
|
+
if !@fit_result.success?
|
134
|
+
raise HexaPDF::Error, "Box for inline use could not be fit"
|
135
|
+
elsif box.height > 99_000
|
136
|
+
raise HexaPDF::Error, "Box for inline use has no valid height set after fitting"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
128
140
|
end
|
129
141
|
|
130
142
|
end
|
@@ -60,6 +60,9 @@ module HexaPDF
|
|
60
60
|
# arguments are ignored.
|
61
61
|
class ListBox < Box
|
62
62
|
|
63
|
+
# Stores the information when fitting an item of the list box.
|
64
|
+
ItemResult = Struct.new(:box_fitter, :height, :marker, :marker_pos_x)
|
65
|
+
|
63
66
|
# The child boxes of this ListBox. They need to be finalized before #fit is called.
|
64
67
|
attr_reader :children
|
65
68
|
|
@@ -177,6 +180,11 @@ module HexaPDF
|
|
177
180
|
true
|
178
181
|
end
|
179
182
|
|
183
|
+
# Returns +true+ if no box was fitted into the list box.
|
184
|
+
def empty?
|
185
|
+
super && (!@results || @results.all? {|result| result.box_fitter.fit_results.empty? })
|
186
|
+
end
|
187
|
+
|
180
188
|
# Fits the list box into the available space.
|
181
189
|
def fit(available_width, available_height, frame)
|
182
190
|
@width = if @initial_width > 0
|
@@ -205,9 +213,10 @@ module HexaPDF
|
|
205
213
|
end
|
206
214
|
|
207
215
|
@results = []
|
208
|
-
@results_item_marker_x = []
|
209
216
|
|
210
|
-
@children.
|
217
|
+
@children.each_with_index do |child, index|
|
218
|
+
item_result = ItemResult.new
|
219
|
+
|
211
220
|
shape = Geom2D::Polygon([left, top - height],
|
212
221
|
[left + width, top - height],
|
213
222
|
[left + width, top],
|
@@ -217,24 +226,36 @@ module HexaPDF
|
|
217
226
|
remove_indent_from_frame_shape(shape) unless shape.polygons.empty?
|
218
227
|
end
|
219
228
|
|
220
|
-
item_frame = Frame.new(item_frame_left, top - height, item_frame_width, height,
|
221
|
-
|
229
|
+
item_frame = Frame.new(item_frame_left, top - height, item_frame_width, height,
|
230
|
+
shape: shape, context: frame.context)
|
231
|
+
|
232
|
+
if index != 0 || !split_box? || @split_box == :show_first_marker
|
233
|
+
box = item_marker_box(frame.document, index)
|
234
|
+
break unless box.fit(content_indentation, height, nil)
|
235
|
+
item_result.marker = box
|
236
|
+
item_result.marker_pos_x = item_frame.x - content_indentation
|
237
|
+
item_result.height = box.height
|
238
|
+
end
|
222
239
|
|
223
240
|
box_fitter = BoxFitter.new([item_frame])
|
224
241
|
Array(child).each {|box| box_fitter.fit(box) }
|
225
|
-
|
242
|
+
item_result.box_fitter = box_fitter
|
243
|
+
item_result.height = [item_result.height.to_i, box_fitter.content_heights[0]].max
|
244
|
+
@results << item_result
|
226
245
|
|
227
|
-
top -=
|
228
|
-
height -=
|
246
|
+
top -= item_result.height + item_spacing
|
247
|
+
height -= item_result.height + item_spacing
|
229
248
|
|
230
249
|
break if !box_fitter.fit_successful? || height <= 0
|
231
250
|
end
|
232
251
|
|
233
|
-
@height = @results.sum {|
|
252
|
+
@height = @results.sum {|item_result| item_result.height } +
|
234
253
|
(@results.count - 1) * item_spacing +
|
235
254
|
reserved_height
|
236
255
|
|
237
|
-
@
|
256
|
+
@draw_pos_x = frame.x + reserved_width_left
|
257
|
+
@draw_pos_y = frame.y - @height + reserved_height_bottom
|
258
|
+
@fit_successful = @results.all? {|r| r.box_fitter.fit_successful? } && @results.size == @children.size
|
238
259
|
end
|
239
260
|
|
240
261
|
private
|
@@ -285,7 +306,7 @@ module HexaPDF
|
|
285
306
|
|
286
307
|
# Splits the content of the list box. This method is called from Box#split.
|
287
308
|
def split_content(_available_width, _available_height, _frame)
|
288
|
-
remaining_boxes = @results[-1].remaining_boxes
|
309
|
+
remaining_boxes = @results[-1].box_fitter.remaining_boxes
|
289
310
|
first_is_split_box = remaining_boxes.first&.split_box?
|
290
311
|
children = (remaining_boxes.empty? ? [] : [remaining_boxes]) + @children[@results.size..-1]
|
291
312
|
|
@@ -294,7 +315,6 @@ module HexaPDF
|
|
294
315
|
box.instance_variable_set(:@start_number,
|
295
316
|
@start_number + @results.size + (first_is_split_box ? -1 : 0))
|
296
317
|
box.instance_variable_set(:@results, [])
|
297
|
-
box.instance_variable_set(:@results_item_marker_x, [])
|
298
318
|
|
299
319
|
[self, box]
|
300
320
|
end
|
@@ -308,20 +328,22 @@ module HexaPDF
|
|
308
328
|
fragment = case @item_type
|
309
329
|
when :disc
|
310
330
|
TextFragment.create("•", font: document.fonts.add("Times"),
|
311
|
-
font_size: style.font_size)
|
331
|
+
font_size: style.font_size, fill_color: style.fill_color)
|
312
332
|
when :circle
|
313
333
|
TextFragment.create("❍", font: document.fonts.add("ZapfDingbats"),
|
314
334
|
font_size: style.font_size / 2.0,
|
335
|
+
fill_color: style.fill_color,
|
315
336
|
text_rise: -style.font_size / 1.8)
|
316
337
|
when :square
|
317
338
|
TextFragment.create("■", font: document.fonts.add("ZapfDingbats"),
|
318
339
|
font_size: style.font_size / 2.0,
|
340
|
+
fill_color: style.fill_color,
|
319
341
|
text_rise: -style.font_size / 1.8)
|
320
342
|
when :decimal
|
321
343
|
text = (@start_number + index).to_s << "."
|
322
344
|
decimal_style = {
|
323
345
|
font: (style.font? ? style.font : document.fonts.add("Times")),
|
324
|
-
font_size: style.font_size || 10,
|
346
|
+
font_size: style.font_size || 10, fill_color: style.fill_color
|
325
347
|
}
|
326
348
|
TextFragment.create(text, decimal_style)
|
327
349
|
else
|
@@ -333,16 +355,24 @@ module HexaPDF
|
|
333
355
|
end
|
334
356
|
|
335
357
|
# Draws the list items onto the canvas at position [x, y].
|
336
|
-
def draw_content(canvas,
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
358
|
+
def draw_content(canvas, x, y)
|
359
|
+
translate = (style.position != :flow && (x != @draw_pos_x || y != @draw_pos_y))
|
360
|
+
|
361
|
+
if translate
|
362
|
+
canvas.save_graphics_state
|
363
|
+
canvas.translate(x - @draw_pos_x, y - @draw_pos_y)
|
364
|
+
end
|
365
|
+
|
366
|
+
@results.each do |item_result|
|
367
|
+
box_fitter = item_result.box_fitter
|
368
|
+
if (marker = item_result.marker)
|
369
|
+
marker.draw(canvas, item_result.marker_pos_x,
|
370
|
+
box_fitter.frames[0].bottom + box_fitter.frames[0].height - marker.height)
|
343
371
|
end
|
344
372
|
box_fitter.fit_results.each {|result| result.draw(canvas) }
|
345
373
|
end
|
374
|
+
|
375
|
+
canvas.restore_graphics_state if translate
|
346
376
|
end
|
347
377
|
|
348
378
|
end
|
@@ -95,8 +95,8 @@ module HexaPDF
|
|
95
95
|
# If this attribute is +nil+ (the default), it means that this style should be used again.
|
96
96
|
attr_accessor :next_style
|
97
97
|
|
98
|
-
# Creates a new page style instance for the given page size
|
99
|
-
# given, it is used as template for defining the initial content.
|
98
|
+
# Creates a new page style instance for the given page size, orientation and next style
|
99
|
+
# values. If a block is given, it is used as template for defining the initial content.
|
100
100
|
#
|
101
101
|
# Example:
|
102
102
|
#
|
@@ -105,12 +105,12 @@ module HexaPDF
|
|
105
105
|
# style.next_style = :other
|
106
106
|
# canvas.fill_color("fd0") { canvas.circle(100, 100, 50).fill }
|
107
107
|
# end
|
108
|
-
def initialize(page_size: :A4, orientation: :portrait, &block)
|
108
|
+
def initialize(page_size: :A4, orientation: :portrait, next_style: nil, &block)
|
109
109
|
@page_size = page_size
|
110
110
|
@orientation = orientation
|
111
111
|
@template = block
|
112
112
|
@frame = nil
|
113
|
-
@next_style =
|
113
|
+
@next_style = next_style
|
114
114
|
end
|
115
115
|
|
116
116
|
# Creates a new page in the given document with this page style and returns it.
|
@@ -135,7 +135,8 @@ module HexaPDF
|
|
135
135
|
Layout::Frame.new(box.left + margin.left,
|
136
136
|
box.bottom + margin.bottom,
|
137
137
|
box.width - margin.left - margin.right,
|
138
|
-
box.height - margin.bottom - margin.top
|
138
|
+
box.height - margin.bottom - margin.top,
|
139
|
+
context: page)
|
139
140
|
end
|
140
141
|
|
141
142
|
end
|
data/lib/hexapdf/layout/style.rb
CHANGED
@@ -206,11 +206,16 @@ module HexaPDF
|
|
206
206
|
# The styles of each edge. See Quad.
|
207
207
|
attr_reader :style
|
208
208
|
|
209
|
+
# Specifies whether the border should be drawn inside the provided rectangle (+false+,
|
210
|
+
# default) or on it (+true+).
|
211
|
+
attr_accessor :draw_on_bounds
|
212
|
+
|
209
213
|
# Creates a new border style. All arguments can be set to any value that a Quad can process.
|
210
|
-
def initialize(width: 0, color: 0, style: :solid)
|
214
|
+
def initialize(width: 0, color: 0, style: :solid, draw_on_bounds: false)
|
211
215
|
@width = Quad.new(width)
|
212
216
|
@color = Quad.new(color)
|
213
217
|
@style = Quad.new(style)
|
218
|
+
@draw_on_bounds = draw_on_bounds
|
214
219
|
end
|
215
220
|
|
216
221
|
# Duplicates a Border object's properties.
|
@@ -226,10 +231,20 @@ module HexaPDF
|
|
226
231
|
width.simple? && width.top == 0
|
227
232
|
end
|
228
233
|
|
229
|
-
# Draws the border onto the canvas
|
234
|
+
# Draws the border onto the canvas.
|
235
|
+
#
|
236
|
+
# Depending on #draw_on_bounds the border is drawn inside the rectangle (x, y, w, h) or on
|
237
|
+
# it.
|
230
238
|
def draw(canvas, x, y, w, h)
|
231
239
|
return if none?
|
232
240
|
|
241
|
+
if draw_on_bounds
|
242
|
+
x -= width.left / 2.0
|
243
|
+
y -= width.bottom / 2.0
|
244
|
+
w += (width.left + width.right) / 2.0
|
245
|
+
h += (width.top + width.bottom) / 2.0
|
246
|
+
end
|
247
|
+
|
233
248
|
canvas.save_graphics_state do
|
234
249
|
if width.simple? && color.simple? && style.simple?
|
235
250
|
draw_simple_border(canvas, x, y, w, h)
|
@@ -444,17 +459,17 @@ module HexaPDF
|
|
444
459
|
# Style objects using link annotations. Typical use cases would be linking to a (named)
|
445
460
|
# destination on a different page or executing a URI action.
|
446
461
|
#
|
447
|
-
# See:
|
462
|
+
# See: PDF2.0 s12.5.6.5, Layers, HexaPDF::Type::Annotations::Link
|
448
463
|
class LinkLayer
|
449
464
|
|
450
465
|
# Creates a new LinkLayer object.
|
451
466
|
#
|
452
|
-
# The following arguments are allowed (note that only *one* of +dest+, +uri
|
453
|
-
# be specified):
|
467
|
+
# The following arguments are allowed (note that only *one* of +dest+, +uri+, +file+ or
|
468
|
+
# +action+ may be specified):
|
454
469
|
#
|
455
470
|
# +dest+::
|
456
471
|
# The destination array or a name of a named destination for in-document links. If neither
|
457
|
-
# +dest
|
472
|
+
# +dest+, +uri+, +file+ nor +action+ is specified, it is assumed that the box has a custom
|
458
473
|
# property named 'link' which is used for the destination.
|
459
474
|
#
|
460
475
|
# +uri+::
|
@@ -465,6 +480,9 @@ module HexaPDF
|
|
465
480
|
# should be launched. Can either be a string or a Filespec object. Also see:
|
466
481
|
# HexaPDF::Type::FileSpecification.
|
467
482
|
#
|
483
|
+
# +action+::
|
484
|
+
# The PDF action that should be executed.
|
485
|
+
#
|
468
486
|
# +border+::
|
469
487
|
# If set to +true+, a standard border is used. Also accepts an array that adheres to the
|
470
488
|
# rules for annotation borders.
|
@@ -477,15 +495,17 @@ module HexaPDF
|
|
477
495
|
# LinkLayer.new(dest: [page, :XYZ, nil, nil, nil], border: true)
|
478
496
|
# LinkLayer.new(uri: "https://my.example.com/path", border: [5 5 2])
|
479
497
|
# LinkLayer.new # use 'link' custom box property for dest
|
480
|
-
def initialize(dest: nil, uri: nil, file: nil, border: false, border_color: nil)
|
481
|
-
if dest && (uri || file) || uri && file
|
482
|
-
raise ArgumentError, "Only one of dest, uri
|
498
|
+
def initialize(dest: nil, uri: nil, file: nil, action: nil, border: false, border_color: nil)
|
499
|
+
if dest && (uri || file || action) || uri && (file || action) || file && action
|
500
|
+
raise ArgumentError, "Only one of dest, uri, file or action is allowed"
|
483
501
|
end
|
484
502
|
@dest = dest
|
485
503
|
@action = if uri
|
486
504
|
{S: :URI, URI: uri}
|
487
505
|
elsif file
|
488
506
|
{S: :Launch, F: file, NewWindow: true}
|
507
|
+
elsif action
|
508
|
+
action
|
489
509
|
end
|
490
510
|
@border = case border
|
491
511
|
when false then [0, 0, 0]
|
@@ -1030,6 +1050,40 @@ module HexaPDF
|
|
1030
1050
|
# line_spacing: 1.5, last_line_gap: true)
|
1031
1051
|
# composer.text("There is spacing above this line due to last_line_gap.")
|
1032
1052
|
|
1053
|
+
##
|
1054
|
+
# :method: fill_horizontal
|
1055
|
+
# :call-seq:
|
1056
|
+
# fill_horizontal(factor = nil)
|
1057
|
+
#
|
1058
|
+
# If set to a positive number, it specifies that the content of the text item should be
|
1059
|
+
# repeated and appropriate spacing applied so that the remaining space of the line is
|
1060
|
+
# completely filled.
|
1061
|
+
#
|
1062
|
+
# If there are multiple text items with this property set for a single line, the remaining
|
1063
|
+
# space is split between those items using the set +factors+. For example, if item A has a
|
1064
|
+
# factor of 1 and item B a factor of 2, the remaining space will be split so that item
|
1065
|
+
# B will receive twice the space of A.
|
1066
|
+
#
|
1067
|
+
# Notes:
|
1068
|
+
#
|
1069
|
+
# * This property _must not_ be applied to inline boxes, it only works for text items.
|
1070
|
+
# * If the filling should be done with spaces, the non-breaking space character \u{00a0} has
|
1071
|
+
# to be used.
|
1072
|
+
#
|
1073
|
+
# Examples:
|
1074
|
+
#
|
1075
|
+
# #>pdf-composer100
|
1076
|
+
# composer.formatted_text(["Left", {text: "\u{00a0}", fill_horizontal: 1},
|
1077
|
+
# "Right"])
|
1078
|
+
# composer.formatted_text(["Typical table of contents entry",
|
1079
|
+
# {text: ".", fill_horizontal: 1}, "34"])
|
1080
|
+
# composer.formatted_text(["Factor 1", {text: "\u{00a0}", fill_horizontal: 1},
|
1081
|
+
# "Factor 3", {text: "\u{00a0}", fill_horizontal: 3}, "End"])
|
1082
|
+
# overlays = [proc {|c, b| c.line(0, b.height / 2.0, b.width, b.height / 2.0).stroke}]
|
1083
|
+
# composer.formatted_text([{text: "\u{00a0}", fill_horizontal: 1, overlays: overlays},
|
1084
|
+
# 'Centered',
|
1085
|
+
# {text: "\u{00a0}", fill_horizontal: 1, overlays: overlays}])
|
1086
|
+
|
1033
1087
|
##
|
1034
1088
|
# :method: background_color
|
1035
1089
|
# :call-seq:
|
@@ -1278,6 +1332,7 @@ module HexaPDF
|
|
1278
1332
|
"{type: value, value: extra_arg} : value))",
|
1279
1333
|
extra_args: ", extra_arg = nil"}],
|
1280
1334
|
[:last_line_gap, false, {valid_values: [true, false]}],
|
1335
|
+
[:fill_horizontal, nil],
|
1281
1336
|
[:background_color, nil],
|
1282
1337
|
[:background_alpha, 1],
|
1283
1338
|
[:padding, "Quad.new(0)", {setter: "Quad.new(value)"}],
|