hexapdf 0.32.2 → 0.34.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 +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
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# # Composer - Optional Content
|
|
2
|
+
#
|
|
3
|
+
# This example shows how to use the optional content feature to create a quiz
|
|
4
|
+
# where the answers can be individually shown and hidden. There is also a link
|
|
5
|
+
# after the questions to toggle all answers.
|
|
6
|
+
#
|
|
7
|
+
# Note: To provide the "All answers" layer switch functionality we need to make
|
|
8
|
+
# use of optional content membership dictionaries. However, this PDF feature is
|
|
9
|
+
# not supported by all PDF viewers. To enable the "All answers" switch in this
|
|
10
|
+
# example, use `a1m`, `a2m`, and `a3m` instead of `a1`, `a2`, and `a3` when
|
|
11
|
+
# defining the optional content for a box.
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# : `ruby composer_optional_content.rb`
|
|
15
|
+
#
|
|
16
|
+
require 'hexapdf'
|
|
17
|
+
|
|
18
|
+
HexaPDF::Composer.create('composer_optional_content.pdf') do |composer|
|
|
19
|
+
composer.style(:question, font_size: 16, margin: [0, 0, 16], fill_color: 'hp-blue')
|
|
20
|
+
composer.style(:answer, font: 'ZapfDingbats', fill_color: "green")
|
|
21
|
+
|
|
22
|
+
all = composer.document.optional_content.ocg('All answers')
|
|
23
|
+
a1 = composer.document.optional_content.ocg('Answer 1')
|
|
24
|
+
a1m = composer.document.optional_content.create_ocmd([a1, all], policy: :any_on)
|
|
25
|
+
a2 = composer.document.optional_content.ocg('Answer 2')
|
|
26
|
+
a2m = composer.document.optional_content.create_ocmd([a2, all], policy: :any_on)
|
|
27
|
+
a3 = composer.document.optional_content.ocg('Answer 3')
|
|
28
|
+
a3m = composer.document.optional_content.create_ocmd([a3, all], policy: :any_on)
|
|
29
|
+
|
|
30
|
+
composer.text('The Great Ruby Quiz', align: :center, margin: [0, 0, 24],
|
|
31
|
+
font: ['Helvetica', variant: :bold], font_size: 24)
|
|
32
|
+
|
|
33
|
+
composer.list(item_type: :decimal, item_spacing: 32, style: :question) do |listing|
|
|
34
|
+
listing.multiple do |item|
|
|
35
|
+
item.text('Who created Ruby?', style: :question)
|
|
36
|
+
item.column(columns: 3, gaps: 5) do |cols|
|
|
37
|
+
cols.list(item_type: :decimal) do |answers|
|
|
38
|
+
answers.text('Guido van Rossum')
|
|
39
|
+
answers.multiple do |answer|
|
|
40
|
+
answer.text('Yukihiro “Matz” Matsumoto', position: :float)
|
|
41
|
+
answer.text("\u{a0}\u{a0}4", style: :answer,
|
|
42
|
+
properties: {'optional_content' => a1})
|
|
43
|
+
end
|
|
44
|
+
answers.text('Rob Pike')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
listing.multiple do |item|
|
|
50
|
+
item.text('When was Ruby created?', style: :question)
|
|
51
|
+
item.column(columns: 3, gaps: 5) do |cols|
|
|
52
|
+
cols.list(item_type: :decimal) do |answers|
|
|
53
|
+
answers.text('1991')
|
|
54
|
+
answers.text('1992')
|
|
55
|
+
answers.multiple do |answer|
|
|
56
|
+
answer.text('1993', position: :float)
|
|
57
|
+
answer.text("\u{a0}\u{a0}4", style: :answer,
|
|
58
|
+
properties: {'optional_content' => a2})
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
listing.multiple do |item|
|
|
65
|
+
item.text('What is the best PDF library for Ruby?', style: :question)
|
|
66
|
+
answer = composer.document.layout.text('There are several PDF libraries for ' \
|
|
67
|
+
'Ruby but the best is HexaPDF! :)',
|
|
68
|
+
width: 400,
|
|
69
|
+
properties: {'optional_content' => a3})
|
|
70
|
+
item.formatted_text([{box: answer}], border: {width: [0, 0, 1]})
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
action = composer.document.wrap({Type: :Action, S: :SetOCGState})
|
|
75
|
+
action.add_state_change(:toggle, [a1, a2, a3])
|
|
76
|
+
composer.text("Click to toggle answers", border: {width: 1, color: "red"},
|
|
77
|
+
position_hint: :right, padding: 2, overlays: [[:link, action: action]])
|
|
78
|
+
|
|
79
|
+
composer.document.optional_content.default_configuration(
|
|
80
|
+
BaseState: :OFF,
|
|
81
|
+
Order: [all, a1, a2, a3],
|
|
82
|
+
)
|
|
83
|
+
end
|
data/lib/hexapdf/cli/command.rb
CHANGED
|
@@ -43,6 +43,9 @@ require 'hexapdf/font/true_type'
|
|
|
43
43
|
module HexaPDF
|
|
44
44
|
module CLI
|
|
45
45
|
|
|
46
|
+
# Raised when problems occur on the CLI side of things.
|
|
47
|
+
class Error < HexaPDF::Error; end
|
|
48
|
+
|
|
46
49
|
# Base class for all hexapdf commands. It provides utility methods needed by the individual
|
|
47
50
|
# commands.
|
|
48
51
|
class Command < CmdParse::Command
|
|
@@ -50,9 +53,15 @@ module HexaPDF
|
|
|
50
53
|
module Extensions #:nodoc:
|
|
51
54
|
def help_banner #:nodoc:
|
|
52
55
|
"hexapdf #{HexaPDF::VERSION} - Versatile PDF Manipulation Tool\n" \
|
|
53
|
-
"Copyright (c) 2014-
|
|
56
|
+
"Copyright (c) 2014-2023 Thomas Leitner; licensed under the AGPLv3\n\n" \
|
|
54
57
|
"#{format(usage, indent: 7)}\n\n"
|
|
55
58
|
end
|
|
59
|
+
|
|
60
|
+
def help #:nodoc:
|
|
61
|
+
super << format("See https://hexapdf.gettalong.org/documentation/hexapdf.1.html " \
|
|
62
|
+
"for the full manual page with examples.", indent: 0)
|
|
63
|
+
end
|
|
64
|
+
|
|
56
65
|
end
|
|
57
66
|
|
|
58
67
|
include Extensions
|
|
@@ -134,7 +143,7 @@ module HexaPDF
|
|
|
134
143
|
doc.trailer.update_id
|
|
135
144
|
doc.validate(auto_correct: true) do |msg, correctable, object|
|
|
136
145
|
if command_parser.strict && !correctable
|
|
137
|
-
raise "Validation error for object (#{object.oid},#{object.gen}): #{msg}"
|
|
146
|
+
raise Error, "Validation error for object (#{object.oid},#{object.gen}): #{msg}"
|
|
138
147
|
elsif command_parser.verbosity_info?
|
|
139
148
|
$stderr.puts "#{correctable ? 'Corrected' : 'Ignored'} validation problem " \
|
|
140
149
|
"for object (#{object.oid},#{object.gen}): #{msg}"
|
|
@@ -151,7 +160,7 @@ module HexaPDF
|
|
|
151
160
|
# HexaPDF::CLI#force is not set.
|
|
152
161
|
def maybe_raise_on_existing_file(filename)
|
|
153
162
|
if !command_parser.force && File.exist?(filename)
|
|
154
|
-
raise "Output file '#{filename}' already exists, not overwriting. Use --force to " \
|
|
163
|
+
raise Error, "Output file '#{filename}' already exists, not overwriting. Use --force to " \
|
|
155
164
|
"force writing"
|
|
156
165
|
end
|
|
157
166
|
end
|
data/lib/hexapdf/cli/fonts.rb
CHANGED
|
@@ -127,7 +127,7 @@ module HexaPDF
|
|
|
127
127
|
page.resources[:Font]&.each(&font_proc)
|
|
128
128
|
page.resources[:XObject]&.each do |_, xobj|
|
|
129
129
|
next unless xobj[:Subtype] == :Form
|
|
130
|
-
xobj.
|
|
130
|
+
xobj.resources[:Font]&.each(&font_proc)
|
|
131
131
|
end
|
|
132
132
|
page.each_annotation do |annotation|
|
|
133
133
|
appearance = annotation.appearance
|
data/lib/hexapdf/cli/form.rb
CHANGED
|
@@ -97,7 +97,7 @@ module HexaPDF
|
|
|
97
97
|
def execute(in_file, out_file = nil) #:nodoc:
|
|
98
98
|
maybe_raise_on_existing_file(out_file) if out_file
|
|
99
99
|
if (@fill || @flatten) && !out_file
|
|
100
|
-
raise "Output file missing"
|
|
100
|
+
raise Error, "Output file missing"
|
|
101
101
|
end
|
|
102
102
|
with_document(in_file, password: @password, out_file: out_file,
|
|
103
103
|
incremental: @incremental) do |doc|
|
|
@@ -106,7 +106,7 @@ module HexaPDF
|
|
|
106
106
|
end
|
|
107
107
|
|
|
108
108
|
if !doc.acro_form
|
|
109
|
-
raise "This PDF doesn't contain an interactive form"
|
|
109
|
+
raise Error, "This PDF doesn't contain an interactive form"
|
|
110
110
|
elsif out_file
|
|
111
111
|
doc.acro_form[:NeedAppearances] = @need_appearances unless @need_appearances.nil?
|
|
112
112
|
if @fill || !@flatten
|
|
@@ -220,7 +220,7 @@ module HexaPDF
|
|
|
220
220
|
form = doc.acro_form
|
|
221
221
|
data.each do |name, value|
|
|
222
222
|
field = form.field_by_name(name)
|
|
223
|
-
raise "Field '#{name}' not found in input PDF" unless field
|
|
223
|
+
raise Error, "Field '#{name}' not found in input PDF" unless field
|
|
224
224
|
apply_field_value(field, value)
|
|
225
225
|
end
|
|
226
226
|
end
|
|
@@ -268,10 +268,10 @@ module HexaPDF
|
|
|
268
268
|
when :radio_button
|
|
269
269
|
field.field_value = value.to_sym
|
|
270
270
|
else
|
|
271
|
-
raise "Field type #{field.concrete_field_type} not yet supported"
|
|
271
|
+
raise Error, "Field type #{field.concrete_field_type} not yet supported"
|
|
272
272
|
end
|
|
273
273
|
rescue StandardError
|
|
274
|
-
raise "Error while setting '#{field.full_field_name}': #{$!.message}"
|
|
274
|
+
raise Error, "Error while setting '#{field.full_field_name}': #{$!.message}"
|
|
275
275
|
end
|
|
276
276
|
|
|
277
277
|
# Iterates over all non-push button fields in page order. If a field appears on multiple
|
data/lib/hexapdf/cli/inspect.rb
CHANGED
|
@@ -93,11 +93,9 @@ module HexaPDF
|
|
|
93
93
|
|
|
94
94
|
private
|
|
95
95
|
|
|
96
|
-
# :nodoc:
|
|
97
|
-
COMMAND_LIST = %w[object recursive stream raw-stream xref catalog trailer pages
|
|
96
|
+
COMMAND_LIST = %w[object recursive stream raw-stream xref catalog trailer pages # :nodoc:
|
|
98
97
|
page-count search quit help]
|
|
99
|
-
# :nodoc:
|
|
100
|
-
RELINE_COMPLETION_PROC = proc do |s|
|
|
98
|
+
RELINE_COMPLETION_PROC = proc do |s| # :nodoc:
|
|
101
99
|
if s.empty?
|
|
102
100
|
COMMAND_DESCRIPTIONS.map {|cmd, desc| cmd.ljust(35) << desc }
|
|
103
101
|
else
|
|
@@ -266,11 +264,11 @@ module HexaPDF
|
|
|
266
264
|
# Resolves the PDF object from the given string reference and returns it.
|
|
267
265
|
def pdf_object_from_string_reference(str)
|
|
268
266
|
if str.nil?
|
|
269
|
-
raise "Error: Missing argument object identifier OID[,GEN]"
|
|
267
|
+
raise Error, "Error: Missing argument object identifier OID[,GEN]"
|
|
270
268
|
elsif !str.match?(/^\d+(,\d+)?$/)
|
|
271
|
-
raise "Error: Invalid argument: Must be of form OID[,GEN], not '#{str}'"
|
|
269
|
+
raise Error, "Error: Invalid argument: Must be of form OID[,GEN], not '#{str}'"
|
|
272
270
|
elsif !(obj = @doc.object(pdf_reference_from_string(str)))
|
|
273
|
-
raise "Error: No object with the given object identifier '#{str}' found"
|
|
271
|
+
raise Error, "Error: No object with the given object identifier '#{str}' found"
|
|
274
272
|
else
|
|
275
273
|
obj
|
|
276
274
|
end
|
data/lib/hexapdf/composer.rb
CHANGED
|
@@ -51,17 +51,18 @@ module HexaPDF
|
|
|
51
51
|
# On creation a HexaPDF::Document object is created as well the first page and an accompanying
|
|
52
52
|
# HexaPDF::Layout::Frame object. The frame is used by the various methods for general document
|
|
53
53
|
# layout tasks, like positioning of text, images, and so on. By default, it covers the whole page
|
|
54
|
-
# except the margin area. How the frame gets created can be customized by
|
|
55
|
-
# #
|
|
54
|
+
# except the margin area. How the frame gets created can be customized by defining a custom page
|
|
55
|
+
# style, see #page_style. Use the +skip_page_creation+ argument to avoid the initial page
|
|
56
|
+
# creation when creating a Composer instance.
|
|
56
57
|
#
|
|
57
58
|
# Once the Composer object is created, its methods can be used to draw text, images, ... on the
|
|
58
|
-
# page. Behind the scenes HexaPDF::Layout::Box (and subclass) objects are created
|
|
59
|
-
# page via the frame.
|
|
59
|
+
# page. Behind the scenes HexaPDF::Layout::Box (and subclass) objects are created using the
|
|
60
|
+
# HexaPDF::Document::Layout methods and drawn on the page via the frame.
|
|
60
61
|
#
|
|
61
62
|
# If the frame of a page is full and a box doesn't fit anymore, a new page is automatically
|
|
62
63
|
# created. The box is either split into two boxes where one fits on the first page and the other
|
|
63
64
|
# on the new page, or it is drawn completely on the new page. A new page can also be created by
|
|
64
|
-
# calling the #new_page method.
|
|
65
|
+
# calling the #new_page method, optionally providing a page style.
|
|
65
66
|
#
|
|
66
67
|
# The #x and #y methods provide the point where the next box would be drawn if it fits the
|
|
67
68
|
# available space. This information can be used, for example, for custom drawing operations
|
|
@@ -75,30 +76,36 @@ module HexaPDF
|
|
|
75
76
|
#
|
|
76
77
|
# == Example
|
|
77
78
|
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
79
|
+
# #>pdf-full
|
|
80
|
+
# HexaPDF::Composer.create('out.pdf', page_size: :A6, margin: 36) do |pdf|
|
|
81
|
+
# pdf.style(:base, font_size: 20, align: :center)
|
|
80
82
|
# pdf.text("Hello World", valign: :center)
|
|
81
83
|
# end
|
|
84
|
+
#
|
|
85
|
+
# See: HexaPDF::Document::Layout, HexaPDF::Layout::Frame, HexaPDF::Layout::Box
|
|
82
86
|
class Composer
|
|
83
87
|
|
|
84
|
-
# Creates a new PDF document and writes it to +output+. The +options+
|
|
88
|
+
# Creates a new PDF document and writes it to +output+. The argument +options+ and +block+ are
|
|
89
|
+
# passed to ::new.
|
|
85
90
|
#
|
|
86
91
|
# Example:
|
|
87
92
|
#
|
|
88
|
-
# HexaPDF::Composer.create('
|
|
93
|
+
# HexaPDF::Composer.create('out.pdf', margin: 36) do |pdf|
|
|
89
94
|
# ...
|
|
90
95
|
# end
|
|
91
96
|
def self.create(output, **options, &block)
|
|
92
97
|
new(**options, &block).write(output)
|
|
93
98
|
end
|
|
94
99
|
|
|
95
|
-
# The PDF document that is created.
|
|
100
|
+
# The PDF document (HexaPDF::Document) that is created.
|
|
96
101
|
attr_reader :document
|
|
97
102
|
|
|
98
103
|
# The current page (a HexaPDF::Type::Page object).
|
|
99
104
|
attr_reader :page
|
|
100
105
|
|
|
101
|
-
# The Content::Canvas of the current page.
|
|
106
|
+
# The canvas instance (a Content::Canvas object) of the current page.
|
|
107
|
+
#
|
|
108
|
+
# Can be used to perform arbitrary drawing operations.
|
|
102
109
|
attr_reader :canvas
|
|
103
110
|
|
|
104
111
|
# The HexaPDF::Layout::Frame for automatic box placement.
|
|
@@ -107,43 +114,53 @@ module HexaPDF
|
|
|
107
114
|
# Creates a new Composer object and optionally yields it to the given block.
|
|
108
115
|
#
|
|
109
116
|
# skip_page_creation::
|
|
110
|
-
# If this argument is +
|
|
111
|
-
# and +margin+ are used to create a page style with the name :default
|
|
112
|
-
# created
|
|
117
|
+
# If this argument is +false+ (the default), the arguments +page_size+, +page_orientation+
|
|
118
|
+
# and +margin+ are used to create a page style with the name :default. Additionally, an
|
|
119
|
+
# initial page/frame is created using this page style.
|
|
113
120
|
#
|
|
114
|
-
# Otherwise, i.e. when this argument is +
|
|
115
|
-
# created. This
|
|
121
|
+
# Otherwise, i.e. when this argument is +true+, no initial page or default page style is
|
|
122
|
+
# created. This is useful when the first page needs a custom page style. The #page_style
|
|
123
|
+
# method needs to be used to define a page style which is then used with the #new_page
|
|
124
|
+
# method to create the initial page/frame.
|
|
116
125
|
#
|
|
117
126
|
# page_size::
|
|
118
127
|
# Can be any valid predefined page size (see Type::Page::PAPER_SIZE) or an array [llx, lly,
|
|
119
128
|
# urx, ury] specifying a custom page size.
|
|
120
129
|
#
|
|
130
|
+
# Only used if +skip_page_creation+ is +false+.
|
|
131
|
+
#
|
|
121
132
|
# page_orientation::
|
|
122
|
-
# Specifies the orientation of the page, either +:portrait+ or +:landscape
|
|
123
|
-
#
|
|
133
|
+
# Specifies the orientation of the page, either +:portrait+ or +:landscape+, if +page_size+
|
|
134
|
+
# is one of the predefined page sizes.
|
|
135
|
+
#
|
|
136
|
+
# Only used if +skip_page_creation+ is +false+.
|
|
124
137
|
#
|
|
125
138
|
# margin::
|
|
126
139
|
# The margin to use. See HexaPDF::Layout::Style::Quad#set for possible values.
|
|
127
140
|
#
|
|
141
|
+
# Only used if +skip_page_creation+ is +false+.
|
|
142
|
+
#
|
|
128
143
|
# Example:
|
|
129
144
|
#
|
|
130
|
-
#
|
|
145
|
+
# # Uses the default values
|
|
146
|
+
# composer = HexaPDF::Composer.new
|
|
131
147
|
#
|
|
132
148
|
# HexaPDF::Composer.new(page_size: :Letter, margin: 72) do |composer|
|
|
133
149
|
# #...
|
|
134
150
|
# end
|
|
135
151
|
#
|
|
136
152
|
# HexaPDF::Composer.new(skip_page_creation: true) do |composer|
|
|
137
|
-
#
|
|
138
|
-
#
|
|
139
|
-
#
|
|
153
|
+
# composer.page_style(:default) do |canvas, style|
|
|
154
|
+
# style.frame = style.create_frame(canvas.context, 36)
|
|
155
|
+
# end
|
|
156
|
+
# composer.new_page
|
|
140
157
|
# # ...
|
|
141
158
|
# end
|
|
142
159
|
def initialize(skip_page_creation: false, page_size: :A4, page_orientation: :portrait,
|
|
143
160
|
margin: 36) #:yields: composer
|
|
144
161
|
@document = HexaPDF::Document.new
|
|
145
162
|
@page_styles = {}
|
|
146
|
-
@
|
|
163
|
+
@next_page_style = :default
|
|
147
164
|
unless skip_page_creation
|
|
148
165
|
page_style(:default, page_size: page_size, orientation: page_orientation) do |canvas, style|
|
|
149
166
|
style.frame = style.create_frame(canvas.context, margin)
|
|
@@ -155,41 +172,61 @@ module HexaPDF
|
|
|
155
172
|
|
|
156
173
|
# Creates a new page, making it the current one.
|
|
157
174
|
#
|
|
158
|
-
# The page style to use for the new page can be set via the +style+ argument.
|
|
159
|
-
# the currently set page style is used
|
|
175
|
+
# The page style (see #page_style) to use for the new page can be set via the +style+ argument.
|
|
176
|
+
# If not provided, the currently set page style is used (:default is the initial value for
|
|
177
|
+
# @next_page_style).
|
|
160
178
|
#
|
|
161
|
-
# The
|
|
162
|
-
# If this information is not provided
|
|
179
|
+
# The applied page style determines the page style that should be used for the following new
|
|
180
|
+
# pages (see Layout::PageStyle#next_style). If this information is not provided by the applied
|
|
181
|
+
# page style, that page style is used again.
|
|
163
182
|
#
|
|
164
183
|
# Examples:
|
|
165
184
|
#
|
|
166
|
-
#
|
|
185
|
+
# # Define two page styles
|
|
186
|
+
# composer.page_style(:cover, page_size: :A4, next_style: :content)
|
|
167
187
|
# composer.page_style(:content, page_size: :A4)
|
|
188
|
+
#
|
|
168
189
|
# composer.new_page(:cover) # uses the :cover style, set next style to :content
|
|
169
190
|
# composer.new_page # uses the :content style, next style again :content
|
|
170
|
-
def new_page(style = @
|
|
191
|
+
def new_page(style = @next_page_style)
|
|
171
192
|
page_style = @page_styles.fetch(style) do |key|
|
|
172
193
|
raise ArgumentError, "Page style #{key} has not been defined"
|
|
173
194
|
end
|
|
174
195
|
@page = @document.pages.add(page_style.create_page(@document))
|
|
175
196
|
@canvas = @page.canvas
|
|
176
197
|
@frame = page_style.frame
|
|
177
|
-
@
|
|
198
|
+
@next_page_style = page_style.next_style || style
|
|
178
199
|
end
|
|
179
200
|
|
|
180
|
-
# The x-position
|
|
201
|
+
# The x-position inside the current frame where the next box (provided it fits) will be placed.
|
|
202
|
+
#
|
|
203
|
+
# Example:
|
|
204
|
+
#
|
|
205
|
+
# #>pdf-composer
|
|
206
|
+
# composer.text("Hello", position: :float)
|
|
207
|
+
# composer.canvas.stroke_color("hp-blue").
|
|
208
|
+
# circle(composer.x, composer.y, 0.5).fill.
|
|
209
|
+
# circle(composer.x, composer.y, 5).stroke
|
|
181
210
|
def x
|
|
182
211
|
@frame.x
|
|
183
212
|
end
|
|
184
213
|
|
|
185
|
-
# The y-position
|
|
214
|
+
# The y-position inside the current frame.where the next box (provided it fits) will be placed.
|
|
215
|
+
#
|
|
216
|
+
# Example:
|
|
217
|
+
#
|
|
218
|
+
# #>pdf-composer
|
|
219
|
+
# composer.text("Hello", position: :float)
|
|
220
|
+
# composer.canvas.stroke_color("hp-blue").
|
|
221
|
+
# circle(composer.x, composer.y, 0.5).fill.
|
|
222
|
+
# circle(composer.x, composer.y, 5).stroke
|
|
186
223
|
def y
|
|
187
224
|
@frame.y
|
|
188
225
|
end
|
|
189
226
|
|
|
190
|
-
# Writes the PDF document to the given output.
|
|
227
|
+
# Writes the created PDF document to the given output.
|
|
191
228
|
#
|
|
192
|
-
# See Document#write for details.
|
|
229
|
+
# See HexaPDF::Document#write for details.
|
|
193
230
|
def write(output, optimize: true, **options)
|
|
194
231
|
@document.write(output, optimize: optimize, **options)
|
|
195
232
|
end
|
|
@@ -201,6 +238,8 @@ module HexaPDF
|
|
|
201
238
|
# Creates or updates the HexaPDF::Layout::Style object called +name+ with the given property
|
|
202
239
|
# values and returns it.
|
|
203
240
|
#
|
|
241
|
+
# If neither +base+ nor any style properties are specified, the style +name+ is just returned.
|
|
242
|
+
#
|
|
204
243
|
# See HexaPDF::Document::Layout#style for details; this method is just a thin wrapper around
|
|
205
244
|
# that method.
|
|
206
245
|
#
|
|
@@ -225,15 +264,15 @@ module HexaPDF
|
|
|
225
264
|
# +nil+ is returned.
|
|
226
265
|
#
|
|
227
266
|
# If one or more page style attributes are given, a new HexaPDF::Layout::PageStyle object with
|
|
228
|
-
# those attribute values is created, stored under +name+ and returned.
|
|
229
|
-
# it is used to define the page template.
|
|
267
|
+
# those attribute values is created, stored under +name+ and returned. Additionally, if a block
|
|
268
|
+
# is provided, it is used to define the page template.
|
|
230
269
|
#
|
|
231
270
|
# Example:
|
|
232
271
|
#
|
|
233
272
|
# composer.page_style(:default)
|
|
234
273
|
# composer.page_style(:cover, page_size: :A4) do |canvas, style|
|
|
235
274
|
# page_box = canvas.context.box
|
|
236
|
-
# canvas.fill_color("
|
|
275
|
+
# canvas.fill_color("green") do
|
|
237
276
|
# canvas.rectangle(0, 0, page_box.width, page_box.height).
|
|
238
277
|
# fill
|
|
239
278
|
# end
|
|
@@ -251,9 +290,9 @@ module HexaPDF
|
|
|
251
290
|
|
|
252
291
|
# Draws the given text at the current position into the current frame.
|
|
253
292
|
#
|
|
254
|
-
# The text will be positioned at the current position if possible. Otherwise the
|
|
255
|
-
# position is used. If the text doesn't fit onto the current page or only partially,
|
|
256
|
-
# are created automatically.
|
|
293
|
+
# The text will be positioned at the current position (see #x and #y) if possible. Otherwise the
|
|
294
|
+
# next best position is used. If the text doesn't fit onto the current page or only partially,
|
|
295
|
+
# one or more new pages are created automatically.
|
|
257
296
|
#
|
|
258
297
|
# This method is of the two main methods for creating text boxes, the other being
|
|
259
298
|
# #formatted_text. It uses HexaPDF::Document::Layout#text_box behind the scenes to create the
|
|
@@ -264,18 +303,21 @@ module HexaPDF
|
|
|
264
303
|
# Examples:
|
|
265
304
|
#
|
|
266
305
|
# #>pdf-composer
|
|
267
|
-
# composer.text("Test " * 15)
|
|
306
|
+
# composer.text("Test it now " * 15)
|
|
268
307
|
# composer.text("Now " * 7, width: 100)
|
|
269
|
-
# composer.text("Another test", font_size: 15, fill_color: "
|
|
308
|
+
# composer.text("Another test", font_size: 15, fill_color: "hp-blue")
|
|
270
309
|
# composer.text("Different box style", fill_color: 'white', box_style: {
|
|
271
310
|
# underlays: [->(c, b) { c.rectangle(0, 0, b.content_width, b.content_height).fill }]
|
|
272
311
|
# })
|
|
312
|
+
#
|
|
313
|
+
# See: #formatted_text, HexaPDF::Layout::TextBox, HexaPDF::Layout::TextFragment
|
|
273
314
|
def text(str, width: 0, height: 0, style: nil, box_style: nil, **style_properties)
|
|
274
315
|
draw_box(@document.layout.text_box(str, width: width, height: height, style: style,
|
|
275
316
|
box_style: box_style, **style_properties))
|
|
276
317
|
end
|
|
277
318
|
|
|
278
|
-
# Draws text like #text but allows parts of the text to be formatted differently
|
|
319
|
+
# Draws text like #text but allows parts of the text to be formatted differently and
|
|
320
|
+
# interspersing with inline boxes.
|
|
279
321
|
#
|
|
280
322
|
# It uses HexaPDF::Document::Layout#formatted_text_box behind the scenes to create the
|
|
281
323
|
# HexaPDF::Layout::TextBox that does the actual work. See that method for details on the
|
|
@@ -285,10 +327,13 @@ module HexaPDF
|
|
|
285
327
|
#
|
|
286
328
|
# #>pdf-composer
|
|
287
329
|
# composer.formatted_text(["Some string"])
|
|
288
|
-
# composer.formatted_text(["Some ", {text: "string", fill_color:
|
|
330
|
+
# composer.formatted_text(["Some ", {text: "string", fill_color: "hp-orange"}])
|
|
289
331
|
# composer.formatted_text(["Some ", {link: "https://example.com",
|
|
290
|
-
# fill_color: 'blue', text: "Example"}])
|
|
332
|
+
# fill_color: 'hp-blue', text: "Example"}])
|
|
291
333
|
# composer.formatted_text(["Some ", {text: "string", style: {font_size: 20}}])
|
|
334
|
+
# block = lambda {|list| list.text("First item"); list.text("Second item") }
|
|
335
|
+
# composer.formatted_text(["Some ", {box: :list, width: 50,
|
|
336
|
+
# valign: :bottom, block: block}])
|
|
292
337
|
#
|
|
293
338
|
# See: #text, HexaPDF::Layout::TextBox, HexaPDF::Layout::TextFragment
|
|
294
339
|
def formatted_text(data, width: 0, height: 0, style: nil, box_style: nil, **style_properties)
|
|
@@ -296,7 +341,7 @@ module HexaPDF
|
|
|
296
341
|
box_style: box_style, **style_properties))
|
|
297
342
|
end
|
|
298
343
|
|
|
299
|
-
# Draws the given image at the current position.
|
|
344
|
+
# Draws the given image at the current position (see #x and #y).
|
|
300
345
|
#
|
|
301
346
|
# It uses HexaPDF::Document::Layout#image_box behind the scenes to create the
|
|
302
347
|
# HexaPDF::Layout::ImageBox that does the actual work. See that method for details on the
|
|
@@ -314,7 +359,7 @@ module HexaPDF
|
|
|
314
359
|
style: style, **style_properties))
|
|
315
360
|
end
|
|
316
361
|
|
|
317
|
-
# Draws the named box at the current position.
|
|
362
|
+
# Draws the named box at the current position (see #x and #y).
|
|
318
363
|
#
|
|
319
364
|
# It uses HexaPDF::Document::Layout#box behind the scenes to create the named box. See that
|
|
320
365
|
# method for details on the arguments.
|
|
@@ -331,11 +376,19 @@ module HexaPDF
|
|
|
331
376
|
|
|
332
377
|
# Draws any custom box that can be created using HexaPDF::Document::Layout.
|
|
333
378
|
#
|
|
379
|
+
# This includes all named boxes defined in the 'layout.boxes.map' configuration option.
|
|
380
|
+
#
|
|
334
381
|
# Examples:
|
|
335
382
|
#
|
|
336
383
|
# #>pdf-composer
|
|
337
|
-
# composer.lorem_ipsum
|
|
338
|
-
# composer.
|
|
384
|
+
# composer.lorem_ipsum(sentences: 1, margin: [0, 0, 5])
|
|
385
|
+
# composer.list(item_spacing: 2) do |list|
|
|
386
|
+
# composer.document.config['layout.boxes.map'].each do |name, klass|
|
|
387
|
+
# list.formatted_text([{text: name.to_s, fill_color: "hp-blue-dark"}, "\n#{klass}"])
|
|
388
|
+
# end
|
|
389
|
+
# end
|
|
390
|
+
#
|
|
391
|
+
# See: HexaPDF::Document::Layout#box
|
|
339
392
|
def method_missing(name, *args, **kwargs, &block)
|
|
340
393
|
if @document.layout.box_creation_method?(name)
|
|
341
394
|
draw_box(@document.layout.send(name, *args, **kwargs, &block))
|
|
@@ -344,12 +397,11 @@ module HexaPDF
|
|
|
344
397
|
end
|
|
345
398
|
end
|
|
346
399
|
|
|
347
|
-
# :nodoc:
|
|
348
|
-
def respond_to_missing?(name, _private)
|
|
400
|
+
def respond_to_missing?(name, _private) # :nodoc:
|
|
349
401
|
@document.layout.box_creation_method?(name) || super
|
|
350
402
|
end
|
|
351
403
|
|
|
352
|
-
# Draws the given HexaPDF::Layout::Box.
|
|
404
|
+
# Draws the given HexaPDF::Layout::Box and returns the last drawn box.
|
|
353
405
|
#
|
|
354
406
|
# The box is drawn into the current frame if possible. If it doesn't fit, the box is split. If
|
|
355
407
|
# it still doesn't fit, a new region of the frame is determined and then the process starts
|
|
@@ -381,6 +433,7 @@ module HexaPDF
|
|
|
381
433
|
end
|
|
382
434
|
end
|
|
383
435
|
end
|
|
436
|
+
box
|
|
384
437
|
end
|
|
385
438
|
|
|
386
439
|
# Creates a stamp (Form XObject) which can be used like an image multiple times on a single page
|
|
@@ -393,7 +446,7 @@ module HexaPDF
|
|
|
393
446
|
#
|
|
394
447
|
# #>pdf-composer
|
|
395
448
|
# stamp = composer.create_stamp(50, 50) do |canvas|
|
|
396
|
-
# canvas.fill_color("
|
|
449
|
+
# canvas.fill_color("hp-blue").line_width(5).
|
|
397
450
|
# rectangle(10, 10, 30, 30).fill_stroke
|
|
398
451
|
# end
|
|
399
452
|
# composer.image(stamp, width: 20, height: 20)
|