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
@@ -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)
|