hexapdf 1.2.0 → 1.4.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 +90 -0
- data/README.md +1 -1
- data/lib/hexapdf/cli/form.rb +9 -4
- data/lib/hexapdf/cli/inspect.rb +13 -4
- data/lib/hexapdf/composer.rb +14 -0
- data/lib/hexapdf/configuration.rb +15 -0
- data/lib/hexapdf/dictionary_fields.rb +1 -1
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +1 -2
- data/lib/hexapdf/document/annotations.rb +107 -2
- data/lib/hexapdf/document/layout.rb +94 -15
- data/lib/hexapdf/document/metadata.rb +10 -3
- data/lib/hexapdf/document.rb +9 -0
- data/lib/hexapdf/encryption/standard_security_handler.rb +7 -2
- data/lib/hexapdf/error.rb +11 -3
- data/lib/hexapdf/font/true_type/subsetter.rb +15 -2
- data/lib/hexapdf/layout/box.rb +5 -0
- data/lib/hexapdf/layout/container_box.rb +63 -28
- data/lib/hexapdf/layout/style.rb +129 -20
- data/lib/hexapdf/layout/table_box.rb +20 -2
- data/lib/hexapdf/object.rb +2 -2
- data/lib/hexapdf/pdf_array.rb +25 -3
- data/lib/hexapdf/tokenizer.rb +4 -1
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +57 -8
- data/lib/hexapdf/type/acro_form/field.rb +1 -0
- data/lib/hexapdf/type/acro_form/form.rb +7 -6
- data/lib/hexapdf/type/annotation.rb +12 -0
- data/lib/hexapdf/type/annotations/appearance_generator.rb +169 -16
- data/lib/hexapdf/type/annotations/border_effect.rb +99 -0
- data/lib/hexapdf/type/annotations/circle.rb +65 -0
- data/lib/hexapdf/type/annotations/interior_color.rb +84 -0
- data/lib/hexapdf/type/annotations/line.rb +5 -192
- data/lib/hexapdf/type/annotations/line_ending_styling.rb +208 -0
- data/lib/hexapdf/type/annotations/markup_annotation.rb +0 -1
- data/lib/hexapdf/type/annotations/polygon.rb +64 -0
- data/lib/hexapdf/type/annotations/polygon_polyline.rb +109 -0
- data/lib/hexapdf/type/annotations/polyline.rb +64 -0
- data/lib/hexapdf/type/annotations/square.rb +65 -0
- data/lib/hexapdf/type/annotations/square_circle.rb +77 -0
- data/lib/hexapdf/type/annotations/widget.rb +50 -20
- data/lib/hexapdf/type/annotations.rb +9 -0
- data/lib/hexapdf/type/measure.rb +57 -0
- data/lib/hexapdf/type.rb +1 -0
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/digital_signature/signing/test_default_handler.rb +0 -1
- data/test/hexapdf/document/test_annotations.rb +42 -0
- data/test/hexapdf/document/test_layout.rb +38 -10
- data/test/hexapdf/document/test_metadata.rb +13 -1
- data/test/hexapdf/encryption/test_standard_security_handler.rb +2 -1
- data/test/hexapdf/font/true_type/test_subsetter.rb +10 -0
- data/test/hexapdf/layout/test_box.rb +8 -0
- data/test/hexapdf/layout/test_container_box.rb +34 -6
- data/test/hexapdf/layout/test_page_style.rb +1 -1
- data/test/hexapdf/layout/test_style.rb +46 -2
- data/test/hexapdf/layout/test_table_box.rb +14 -1
- data/test/hexapdf/test_composer.rb +7 -0
- data/test/hexapdf/test_dictionary_fields.rb +1 -0
- data/test/hexapdf/test_object.rb +1 -1
- data/test/hexapdf/test_pdf_array.rb +36 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +78 -3
- data/test/hexapdf/type/acro_form/test_button_field.rb +7 -6
- data/test/hexapdf/type/acro_form/test_field.rb +5 -0
- data/test/hexapdf/type/acro_form/test_form.rb +17 -1
- data/test/hexapdf/type/annotations/test_appearance_generator.rb +210 -0
- data/test/hexapdf/type/annotations/test_border_effect.rb +59 -0
- data/test/hexapdf/type/annotations/test_interior_color.rb +37 -0
- data/test/hexapdf/type/annotations/test_line.rb +0 -45
- data/test/hexapdf/type/annotations/test_line_ending_styling.rb +42 -0
- data/test/hexapdf/type/annotations/test_polygon_polyline.rb +29 -0
- data/test/hexapdf/type/annotations/test_widget.rb +35 -0
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82d0430964f9f4c6925af5bb076a2e641908c3a4cb6796acc64dbe1303bc3407
|
4
|
+
data.tar.gz: 689a86b637a86331ca203d7d7ac90a9fb3c50c275f07b21d8109013faa509f07
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4cfe8038379e5dc7f3bebeb38b44525676b934cb2b2355ac7575fa7a4466a6c4ec9ab9e0a80a184aebab32b2aa87e06dff735c10915abebe11541ada95d8129c
|
7
|
+
data.tar.gz: 62562b4bae557ad3dac03cb51913a8d1d6796d6cad9ce0626bc901cc15afeb301e42d061c345a1ebcb30e09a018dbe4b7c26fc4c567423c262f67607d04b95c9
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,93 @@
|
|
1
|
+
## 1.4.0 - 2025-08-03
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* [HexaPDF::Type::Annotations::Polygon] for polygon annotations as well as
|
6
|
+
[HexaPDF::Document::Annotations#create_polygon]
|
7
|
+
* [HexaPDF::Type::Annotations::Polyline] for polyline annotations as well as
|
8
|
+
[HexaPDF::Document::Annotations#create_polyline]
|
9
|
+
* [HexaPDF::Layout::ContainerBox#splitable] for specifying whether the container
|
10
|
+
box may be split
|
11
|
+
* [HexaPDF::Layout::Style::Layers#layers] for retrieving the list of defined
|
12
|
+
layers
|
13
|
+
* [HexaPDF::Document::Layout#resolve_font] for resolving the font style property
|
14
|
+
* [HexaPDF::Type::Measure] for representing the measure dictionary
|
15
|
+
* [HexaPDF::Layout::Box::FitResult#failure!] for setting the result status to
|
16
|
+
failure
|
17
|
+
|
18
|
+
### Changed
|
19
|
+
|
20
|
+
* **Breaking change**: Extracted `#line_ending_style` and associated data class
|
21
|
+
from [HexaPDF::Type::Annotations::Line] into
|
22
|
+
[HexaPDF::Type::Annotations::LineEndingStyling]
|
23
|
+
* [HexaPDF::Layout::TableBox] implementation to allow setting the minimum height
|
24
|
+
of a table cell
|
25
|
+
* [HexaPDF::Layout::Style::Quad#set] to allow setting a subset of values using a
|
26
|
+
hash
|
27
|
+
* CLI command `hp form` to show the names of radio button widgets
|
28
|
+
* CLI command `hp form` to show position and size of widgets in easier to
|
29
|
+
understand form
|
30
|
+
* Default signing handler to not set /DigestMethod entry on signature reference
|
31
|
+
dictionary anymore
|
32
|
+
|
33
|
+
### Fixed
|
34
|
+
|
35
|
+
* Parsing and writing the /ModDate and /CreationDate trailer info fields in case
|
36
|
+
of string values when using the XMP metadata handler
|
37
|
+
* [HexaPDF::Layout::Style] to not accidentally set subscript or superscript
|
38
|
+
values
|
39
|
+
* [HexaPDF::DictionaryFields::DateConverter] to handle invalid dates with two
|
40
|
+
trailing apostrophes
|
41
|
+
* [HexaPDF::Document::Layout::CellArgumentCollector#retrieve_arguments_for] to
|
42
|
+
not change the stored data
|
43
|
+
* Encryption when using AES with 256bits and an owner password
|
44
|
+
|
45
|
+
|
46
|
+
## 1.3.0 - 2025-04-23
|
47
|
+
|
48
|
+
### Added
|
49
|
+
|
50
|
+
* [HexaPDF::Type::Annotations::Square] for rectangle annotations as well as
|
51
|
+
[HexaPDF::Document::Annotations#create_rectangle]
|
52
|
+
* [HexaPDF::Type::Annotations::Circle] for ellipse annotations as well as
|
53
|
+
[HexaPDF::Document::Annotations#create_ellipse]
|
54
|
+
* Basic appearance generation for push button fields
|
55
|
+
* [HexaPDF::Type::Annotation::BorderEffect] type class
|
56
|
+
* [HexaPDF::Type::Annotations::BorderEffect] module that provides convenience
|
57
|
+
access to the border effect dictionary
|
58
|
+
* [HexaPDF::Document::Layout#style?] and [HexaPDF::Composer#style?] for checking
|
59
|
+
whether a given style (name) exists
|
60
|
+
* [HexaPDF::Layout::Style#each_property] for iterating over all set properties
|
61
|
+
* [HexaPDF::Layout::Style#merge] for merging another style instance
|
62
|
+
* [HexaPDF::Layout::Style#box_options] for specifying box initialization options
|
63
|
+
* [HexaPDF::Layout::Style#font_bold] and [HexaPDF::Layout::Style#font_italic]
|
64
|
+
for setting bold and/or italic variants independently of the font name
|
65
|
+
* [HexaPDF::PDFArray#map!] for mapping elements in-place
|
66
|
+
* [HexaPDF::PDFArray#compact!] for removing `nil` elements
|
67
|
+
|
68
|
+
### Changed
|
69
|
+
|
70
|
+
* **Breaking change**: [HexaPDF::Type::Annotations::Widget::MarkerStyle::new]
|
71
|
+
got a new positional argument
|
72
|
+
* [HexaPDF::Type::Annotations::Widget#marker_style] to allow setting and
|
73
|
+
retrieving the font for push buttons
|
74
|
+
* Extracted `#interior_color` from [HexaPDF::Type::Annotations::Line] into
|
75
|
+
[HexaPDF::Type::Annotations::InteriorColor]
|
76
|
+
* CLI command `hexapdf inspect` to support decoding Form XObject streams
|
77
|
+
* [HexaPDF::Layout::Style#line_spacing] to accept a `LineSpacing` object when
|
78
|
+
setting the value
|
79
|
+
|
80
|
+
### Fixed
|
81
|
+
|
82
|
+
* Text extraction with macOS Preview due a bug in Preview
|
83
|
+
* [HexaPDF::PDFArray#reject!] to work according to documented method signature
|
84
|
+
* [HexaPDF::Type::AcroForm::Field#create_widget] to ensure the proper type
|
85
|
+
class is stored in the document in case an embedded widget is extracted
|
86
|
+
* [HexaPDF::Type::AcroForm::Form] validation to ensure that all field objects in
|
87
|
+
the field hierarchy are using a field type class
|
88
|
+
* [HexaPDF::Type::AcroForm::Form] validation to delete merged fields
|
89
|
+
|
90
|
+
|
1
91
|
## 1.2.0 - 2025-02-10
|
2
92
|
|
3
93
|
### Added
|
data/README.md
CHANGED
@@ -82,7 +82,7 @@ canvas.text("Hello World!", at: [20, 400])
|
|
82
82
|
doc.write("hello-world.pdf")
|
83
83
|
~~~
|
84
84
|
|
85
|
-
For detailed information have a look at the [HexaPDF website][website] where you will the API
|
85
|
+
For detailed information have a look at the [HexaPDF website][website] where you will find the API
|
86
86
|
documentation, example code and more.
|
87
87
|
|
88
88
|
It is recommend to use the HTML API documentation provided by the HexaPDF website as it is enhanced
|
data/lib/hexapdf/cli/form.rb
CHANGED
@@ -161,8 +161,8 @@ module HexaPDF
|
|
161
161
|
(field.alternate_field_name ? " (#{field.alternate_field_name})" : '')
|
162
162
|
concrete_field_type = field.concrete_field_type
|
163
163
|
nice_field_type = concrete_field_type.to_s.split('_').map(&:capitalize).join(' ')
|
164
|
-
size = "
|
165
|
-
position = "
|
164
|
+
size = "#{widget[:Rect].width.round(3)}x#{widget[:Rect].height.round(3)}"
|
165
|
+
position = "x=#{widget[:Rect].left}, y=#{widget[:Rect].bottom}"
|
166
166
|
field_value = if !field.field_value || concrete_field_type != :signature_field
|
167
167
|
field.field_value.inspect
|
168
168
|
else
|
@@ -172,10 +172,15 @@ module HexaPDF
|
|
172
172
|
temp
|
173
173
|
end
|
174
174
|
|
175
|
+
if concrete_field_type == :radio_button
|
176
|
+
rb_name = ((widget.appearance_dict&.normal_appearance&.value&.keys || []) - [:Off]).first
|
177
|
+
rb_name = " (#{rb_name.inspect})"
|
178
|
+
end
|
179
|
+
|
175
180
|
flags = field_flags(field)
|
176
|
-
puts " #{field_name}" << (flags.empty? ? '' : " (#{flags.join(', ')})")
|
181
|
+
puts " #{field_name}#{rb_name}" << (flags.empty? ? '' : " (#{flags.join(', ')})")
|
177
182
|
if command_parser.verbosity_info?
|
178
|
-
printf(" └─ %-22s | %-20s\n", nice_field_type, "#{
|
183
|
+
printf(" └─ %-22s | %-20s\n", nice_field_type, "#{position}, #{size} ")
|
179
184
|
end
|
180
185
|
puts " └─ #{field_value}"
|
181
186
|
if command_parser.verbosity_info?
|
data/lib/hexapdf/cli/inspect.rb
CHANGED
@@ -188,12 +188,20 @@ module HexaPDF
|
|
188
188
|
end
|
189
189
|
serialize(obj.value, recursive: true) if obj
|
190
190
|
|
191
|
-
when 's', 'stream', 'raw', 'raw-stream'
|
191
|
+
when 's', 'stream', 'raw', 'raw-stream', 'sd'
|
192
192
|
if (obj = pdf_object_from_string_reference(data.shift) rescue $stderr.puts($!.message)) &&
|
193
193
|
obj.kind_of?(HexaPDF::Stream)
|
194
|
-
|
195
|
-
|
196
|
-
|
194
|
+
if command == 'sd'
|
195
|
+
if obj.respond_to?(:process_contents)
|
196
|
+
obj.process_contents(ContentProcessor.new)
|
197
|
+
else
|
198
|
+
$stderr.puts("Error: The object is not a Form XObject or page")
|
199
|
+
end
|
200
|
+
else
|
201
|
+
source = (command.start_with?('raw') ? obj.stream_source : obj.stream_decoder)
|
202
|
+
while source.alive? && (stream_data = source.resume)
|
203
|
+
$stdout.write(stream_data)
|
204
|
+
end
|
197
205
|
end
|
198
206
|
elsif command_parser.verbosity_info?
|
199
207
|
$stderr.puts("Note: Object has no stream data")
|
@@ -427,6 +435,7 @@ module HexaPDF
|
|
427
435
|
["OID[,GEN] | o[bject] OID[,GEN]", "Print object"],
|
428
436
|
["r[ecursive] OID[,GEN]", "Print object recursively"],
|
429
437
|
["s[tream] OID[,GEN]", "Print filtered stream"],
|
438
|
+
["sd OID[,GEN]", "Print the decoded stream of a Form XObject or page"],
|
430
439
|
["raw[-stream] OID[,GEN]", "Print raw stream"],
|
431
440
|
["rev[ision] [NUMBER]", "Print or extract revision"],
|
432
441
|
["x[ref] OID[,GEN]", "Print the cross-reference entry"],
|
data/lib/hexapdf/composer.rb
CHANGED
@@ -261,6 +261,20 @@ module HexaPDF
|
|
261
261
|
@document.layout.style(name, base: base, **properties)
|
262
262
|
end
|
263
263
|
|
264
|
+
# Returns +true+ if a style with the given +name+ exists, else +false+.
|
265
|
+
#
|
266
|
+
# See HexaPDF::Document::Layout#style for details; this method is just a thin wrapper around
|
267
|
+
# that method.
|
268
|
+
#
|
269
|
+
# Example:
|
270
|
+
#
|
271
|
+
# composer.style(:header, font: 'Helvetica')
|
272
|
+
# composer.style?(:header) # => true
|
273
|
+
# composer.style?(:paragraph) # => false
|
274
|
+
def style?(name)
|
275
|
+
@document.layout.style?(name)
|
276
|
+
end
|
277
|
+
|
264
278
|
# :call-seq:
|
265
279
|
# composer.styles -> styles
|
266
280
|
# composer.styles(**mapping) -> styles
|
@@ -374,6 +374,11 @@ module HexaPDF
|
|
374
374
|
# The default implementation returns an object of class HexaPDF::Font::InvalidGlyph which, when
|
375
375
|
# not removed before encoding, will raise a HexaPDF::MissingGlyphError.
|
376
376
|
#
|
377
|
+
# Note: The 'font.on_invalid_glyph' configuration option does something similar but is used
|
378
|
+
# later and only by the layout engine. If this callback hook returns an invalid glyph instance,
|
379
|
+
# the 'font.on_invalid_glyph' callback hook is invoked when using the layout engine and it can
|
380
|
+
# return a substitute glyph in any font.
|
381
|
+
#
|
377
382
|
# If a replacement glyph should be displayed instead of an error, the following provides a good
|
378
383
|
# starting implementation:
|
379
384
|
#
|
@@ -709,6 +714,7 @@ module HexaPDF
|
|
709
714
|
XXAcroFormField: 'HexaPDF::Type::AcroForm::Field',
|
710
715
|
XXAppearanceDictionary: 'HexaPDF::Type::Annotation::AppearanceDictionary',
|
711
716
|
Border: 'HexaPDF::Type::Annotation::Border',
|
717
|
+
XXBorderEffect: 'HexaPDF::Type::Annotation::BorderEffect',
|
712
718
|
SigFieldLock: 'HexaPDF::Type::AcroForm::SignatureField::LockDictionary',
|
713
719
|
SV: 'HexaPDF::Type::AcroForm::SignatureField::SeedValueDictionary',
|
714
720
|
SVCert: 'HexaPDF::Type::AcroForm::SignatureField::CertificateSeedValueDictionary',
|
@@ -747,6 +753,7 @@ module HexaPDF
|
|
747
753
|
Namespace: 'HexaPDF::Type::Namespace',
|
748
754
|
MCR: 'HexaPDF::Type::MarkedContentReference',
|
749
755
|
OBJR: 'HexaPDF::Type::ObjectReference',
|
756
|
+
Measure: 'HexaPDF::Type::Measure',
|
750
757
|
},
|
751
758
|
'object.subtype_map' => {
|
752
759
|
nil => {
|
@@ -766,6 +773,10 @@ module HexaPDF
|
|
766
773
|
Link: 'HexaPDF::Type::Annotations::Link',
|
767
774
|
Widget: 'HexaPDF::Type::Annotations::Widget',
|
768
775
|
Line: 'HexaPDF::Type::Annotations::Line',
|
776
|
+
Square: 'HexaPDF::Type::Annotations::Square',
|
777
|
+
Circle: 'HexaPDF::Type::Annotations::Circle',
|
778
|
+
Polygon: 'HexaPDF::Type::Annotations::Polygon',
|
779
|
+
PolyLine: 'HexaPDF::Type::Annotations::Polyline',
|
769
780
|
XML: 'HexaPDF::Type::Metadata',
|
770
781
|
GTS_PDFX: 'HexaPDF::Type::OutputIntent',
|
771
782
|
GTS_PDFA1: 'HexaPDF::Type::OutputIntent',
|
@@ -795,6 +806,10 @@ module HexaPDF
|
|
795
806
|
Link: 'HexaPDF::Type::Annotations::Link',
|
796
807
|
Widget: 'HexaPDF::Type::Annotations::Widget',
|
797
808
|
Line: 'HexaPDF::Type::Annotations::Line',
|
809
|
+
Square: 'HexaPDF::Type::Annotations::Square',
|
810
|
+
Circle: 'HexaPDF::Type::Annotations::Circle',
|
811
|
+
Polygon: 'HexaPDF::Type::Annotations::Polygon',
|
812
|
+
PolyLine: 'HexaPDF::Type::Annotations::Polyline',
|
798
813
|
},
|
799
814
|
XXAcroFormField: {
|
800
815
|
Tx: 'HexaPDF::Type::AcroForm::TextField',
|
@@ -313,7 +313,7 @@ module HexaPDF
|
|
313
313
|
[String, Time, Date, DateTime]
|
314
314
|
end
|
315
315
|
|
316
|
-
DATE_RE = /\AD:(\d{4})(\d\d)?(\d\d)?(\d\d)?(\d\d)?(\d\d)?([Z+-])?(?:(\d+)(?:'|'(\d+)'?|\z)?)?\z/n # :nodoc:
|
316
|
+
DATE_RE = /\AD:(\d{4})(\d\d)?(\d\d)?(\d\d)?(\d\d)?(\d\d)?([Z+-])?(?:(\d+)(?:'|'(\d+)'?'?|\z)?)?\z/n # :nodoc:
|
317
317
|
|
318
318
|
# Checks if the given object is a string and converts into a Time object if possible.
|
319
319
|
# Otherwise returns +nil+.
|
@@ -297,8 +297,7 @@ module HexaPDF
|
|
297
297
|
raise HexaPDF::Error, "Can set DocMDP access permissions only on first signature"
|
298
298
|
end
|
299
299
|
params = doc.add({Type: :TransformParams, V: :'1.2', P: doc_mdp_permissions})
|
300
|
-
sigref = doc.add({Type: :SigRef, TransformMethod: :DocMDP,
|
301
|
-
TransformParams: params})
|
300
|
+
sigref = doc.add({Type: :SigRef, TransformMethod: :DocMDP, TransformParams: params})
|
302
301
|
signature[:Reference] = [sigref]
|
303
302
|
(doc.catalog[:Perms] ||= {})[:DocMDP] = signature
|
304
303
|
end
|
@@ -69,12 +69,12 @@ module HexaPDF
|
|
69
69
|
# +create_type+ method.
|
70
70
|
#
|
71
71
|
# The +options+ are passed on the specific annotation creation method.
|
72
|
-
def create(type, page, **options)
|
72
|
+
def create(type, page, *args, **options)
|
73
73
|
method_name = "create_#{type}"
|
74
74
|
unless respond_to?(method_name)
|
75
75
|
raise ArgumentError, "Invalid type specified"
|
76
76
|
end
|
77
|
-
send("create_#{type}", page, **options)
|
77
|
+
send("create_#{type}", page, *args, **options)
|
78
78
|
end
|
79
79
|
|
80
80
|
# :call-seq:
|
@@ -100,6 +100,111 @@ module HexaPDF
|
|
100
100
|
border_style(color: 0, width: 1)
|
101
101
|
end
|
102
102
|
|
103
|
+
# :call-seq:
|
104
|
+
# annotations.create_rectangle(page, x, y, width, height) -> annotation
|
105
|
+
#
|
106
|
+
# Creates a rectangle (called "square" in the PDF specification) annotation with the
|
107
|
+
# lower-left corner at (+x+, +y+) and the given +width+ and +height+.
|
108
|
+
#
|
109
|
+
# The rectangle uses a black stroke color, no interior color and a line width of 1pt by
|
110
|
+
# default. It can be further styled using the convenience methods on the returned annotation
|
111
|
+
# object.
|
112
|
+
#
|
113
|
+
# Example:
|
114
|
+
#
|
115
|
+
# #>pdf-small
|
116
|
+
# doc.annotations.create_rectangle(doc.pages[0], 20, 20, 20, 60).
|
117
|
+
# regenerate_appearance
|
118
|
+
#
|
119
|
+
# doc.annotations.create_rectangle(doc.pages[0], 60, 20, 20, 60).
|
120
|
+
# border_style(color: "hp-blue", width: 2).
|
121
|
+
# interior_color("hp-orange").
|
122
|
+
# regenerate_appearance
|
123
|
+
#
|
124
|
+
# See: Type::Annotations::Square
|
125
|
+
def create_rectangle(page, x, y, w, h)
|
126
|
+
annot = create_and_add_to_page(:Square, page)
|
127
|
+
annot[:Rect] = [x, y, x + w, y + h]
|
128
|
+
annot.border_style(color: 0, width: 1)
|
129
|
+
annot
|
130
|
+
end
|
131
|
+
|
132
|
+
# :call-seq:
|
133
|
+
# annotations.create_ellipse(page, cx, cy, a:, b:) -> annotation
|
134
|
+
#
|
135
|
+
# Creates an ellipse (called "circle" in the PDF specification) annotation with the center
|
136
|
+
# point at (+cx+, +cy+), the semi-major axis +a+ and the semi-minor axis +b+.
|
137
|
+
#
|
138
|
+
# The ellipse uses a black stroke color, no interior color and a line width of 1pt by
|
139
|
+
# default. It can be further styled using the convenience methods on the returned annotation
|
140
|
+
# object.
|
141
|
+
#
|
142
|
+
# Example:
|
143
|
+
#
|
144
|
+
# #>pdf-small
|
145
|
+
# doc.annotations.create_ellipse(doc.pages[0], 30, 50, a: 15, b: 20).
|
146
|
+
# regenerate_appearance
|
147
|
+
#
|
148
|
+
# doc.annotations.create_ellipse(doc.pages[0], 70, 50, a: 15, b: 20).
|
149
|
+
# border_style(color: "hp-blue", width: 2).
|
150
|
+
# interior_color("hp-orange").
|
151
|
+
# regenerate_appearance
|
152
|
+
#
|
153
|
+
# See: Type::Annotations::Circle
|
154
|
+
def create_ellipse(page, x, y, a:, b:)
|
155
|
+
annot = create_and_add_to_page(:Circle, page)
|
156
|
+
annot[:Rect] = [x - a, y - b, x + a, y + b]
|
157
|
+
annot.border_style(color: 0, width: 1)
|
158
|
+
annot
|
159
|
+
end
|
160
|
+
|
161
|
+
# :call-seq:
|
162
|
+
# annotations.create_polyline(page, *points) -> annotation
|
163
|
+
#
|
164
|
+
# Creates a polyline annotation for the given +points+ (alternating horizontal and vertical
|
165
|
+
# coordinates) on the given page and returns it.
|
166
|
+
#
|
167
|
+
# The polyline uses a black color and a width of 1pt. It can be further styled using the
|
168
|
+
# convenience methods on the returned annotation object.
|
169
|
+
#
|
170
|
+
# Example:
|
171
|
+
#
|
172
|
+
# #>pdf-small
|
173
|
+
# doc.annotations.create_polyline(doc.pages[0], 20, 20, 30, 70, 80, 60, 40, 30).
|
174
|
+
# border_style(color: "hp-blue", width: 2, style: [3, 1]).
|
175
|
+
# regenerate_appearance
|
176
|
+
#
|
177
|
+
# See: Type::Annotations::Polyline
|
178
|
+
def create_polyline(page, *points)
|
179
|
+
create_and_add_to_page(:PolyLine, page).
|
180
|
+
vertices(*points).
|
181
|
+
border_style(color: 0, width: 1)
|
182
|
+
end
|
183
|
+
|
184
|
+
# :call-seq:
|
185
|
+
# annotations.create_polygon(page, *points) -> annotation
|
186
|
+
#
|
187
|
+
# Creates a polygon annotation for the given +points+ (alternating horizontal and vertical
|
188
|
+
# coordinates) on the given page and returns it.
|
189
|
+
#
|
190
|
+
# The polygon uses a black color and a width of 1pt for the border and no interior color. It
|
191
|
+
# can be further styled using the convenience methods on the returned annotation object.
|
192
|
+
#
|
193
|
+
# Example:
|
194
|
+
#
|
195
|
+
# #>pdf-small
|
196
|
+
# doc.annotations.create_polygon(doc.pages[0], 20, 20, 30, 70, 80, 60, 40, 30).
|
197
|
+
# border_style(color: "hp-blue", width: 2, style: [3, 1]).
|
198
|
+
# interior_color("hp-orange").
|
199
|
+
# regenerate_appearance
|
200
|
+
#
|
201
|
+
# See: Type::Annotations::Polygon
|
202
|
+
def create_polygon(page, *points)
|
203
|
+
create_and_add_to_page(:Polygon, page).
|
204
|
+
vertices(*points).
|
205
|
+
border_style(color: 0, width: 1)
|
206
|
+
end
|
207
|
+
|
103
208
|
private
|
104
209
|
|
105
210
|
# Returns the root of the destinations name tree.
|
@@ -92,13 +92,23 @@ module HexaPDF
|
|
92
92
|
#
|
93
93
|
# style.font = ['Helvetica', variant: :bold]
|
94
94
|
#
|
95
|
-
# Helvetica in bold could also be set the
|
95
|
+
# Helvetica in bold could also be set in the following ways:
|
96
96
|
#
|
97
97
|
# style.font = 'Helvetica bold'
|
98
|
+
# # or
|
99
|
+
# style.font_bold = true
|
100
|
+
# style.font = 'Helvetica'
|
101
|
+
#
|
102
|
+
# The font_bold and font_italic style properties are always taken into account. For example,
|
103
|
+
# if the font is set to 'Helvetica italic' and font_bold to +true+, the actual font would be
|
104
|
+
# the bold _and_ italic Helvetica font.
|
98
105
|
#
|
99
106
|
# However, using an array it is also possible to specify other options when setting a font,
|
100
107
|
# like the :subset option.
|
101
108
|
#
|
109
|
+
# * It is possible to resolve the font of a style object manually by using the #resolve_font
|
110
|
+
# method.
|
111
|
+
#
|
102
112
|
class Layout
|
103
113
|
|
104
114
|
# This class is used when a box can contain child boxes and the creation of such boxes should
|
@@ -218,6 +228,77 @@ module HexaPDF
|
|
218
228
|
style
|
219
229
|
end
|
220
230
|
|
231
|
+
# Returns +true+ if a style with the given +name+ exists, else +false+.
|
232
|
+
#
|
233
|
+
# Example:
|
234
|
+
#
|
235
|
+
# layout.style(:header, font: 'Helvetica')
|
236
|
+
# layout.style?(:header) # => true
|
237
|
+
# layout.style?(:paragraph) # => false
|
238
|
+
#
|
239
|
+
# See: #style
|
240
|
+
def style?(name)
|
241
|
+
@styles.key?(name)
|
242
|
+
end
|
243
|
+
|
244
|
+
FONT_BOLD_VARIANT_MAPPER = { #:nodoc:
|
245
|
+
nil => {true => :bold, false: :none},
|
246
|
+
none: {true => :bold, false: :none},
|
247
|
+
bold: {true => :bold, false: :none},
|
248
|
+
italic: {true => :bold_italic, false: :italic},
|
249
|
+
bold_italic: {true => :bold_italic, false: :italic},
|
250
|
+
}
|
251
|
+
|
252
|
+
FONT_ITALIC_VARIANT_MAPPER = { #:nodoc:
|
253
|
+
nil => {true => :italic, false: :none},
|
254
|
+
none: {true => :italic, false: :none},
|
255
|
+
italic: {true => :italic, false: :none},
|
256
|
+
bold: {true => :bold_italic, false: :bold},
|
257
|
+
bold_italic: {true => :bold_italic, false: :bold},
|
258
|
+
}
|
259
|
+
|
260
|
+
# Resolves the font object for the given +style+ and applies the result to it.
|
261
|
+
#
|
262
|
+
# The Layout::Style#font property is the only one without a default value but is needed for
|
263
|
+
# many operations. This method ensures that the +style+ has a valid font object for the font
|
264
|
+
# property by resolving the font name.
|
265
|
+
#
|
266
|
+
# The font object is resolved in the following way:
|
267
|
+
#
|
268
|
+
# * If the font property is not set, the font value of the :base style is used and if that is
|
269
|
+
# also not set, the 'font.default' configuration value is used.
|
270
|
+
#
|
271
|
+
# * Afterwards, if the font property is a valid font object, nothing needs to be done.
|
272
|
+
#
|
273
|
+
# * Otherwise, if the font property is a single font name or a [font name, options hash]
|
274
|
+
# array, it is resolved to a font object, also taking the font_bold and font_italic style
|
275
|
+
# properties into account.
|
276
|
+
#
|
277
|
+
# Example:
|
278
|
+
#
|
279
|
+
# style = layout.style(:header, font: 'Helvetica')
|
280
|
+
# style.font # => 'Helvetica'
|
281
|
+
# layout.resolve_font(style)
|
282
|
+
# style.font # => #<HexaPDF::Font::Type1Wrapper>
|
283
|
+
#
|
284
|
+
# See: The "Box Styles" section in Layout for more details.
|
285
|
+
def resolve_font(style)
|
286
|
+
unless style.font?
|
287
|
+
style.font(@styles[:base].font? && @styles[:base].font || @document.config['font.default'])
|
288
|
+
end
|
289
|
+
unless style.font.respond_to?(:pdf_object)
|
290
|
+
name, options = *style.font
|
291
|
+
options ||= {}
|
292
|
+
if style.font_bold?
|
293
|
+
options[:variant] = FONT_BOLD_VARIANT_MAPPER.dig(options[:variant], style.font_bold)
|
294
|
+
end
|
295
|
+
if style.font_italic?
|
296
|
+
options[:variant] = FONT_ITALIC_VARIANT_MAPPER.dig(options[:variant], style.font_italic)
|
297
|
+
end
|
298
|
+
style.font(@document.fonts.add(name, **options))
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
221
302
|
# :call-seq:
|
222
303
|
# layout.styles -> styles
|
223
304
|
# layout.styles(**mapping) -> styles
|
@@ -286,8 +367,9 @@ module HexaPDF
|
|
286
367
|
box_options[:children] = ChildrenCollector.collect(self, &block)
|
287
368
|
end
|
288
369
|
end
|
370
|
+
style = retrieve_style(style)
|
289
371
|
box_class_for_name(name).new(width: width, height: height,
|
290
|
-
style:
|
372
|
+
style: style, **style.box_options, **box_options, &box_block)
|
291
373
|
end
|
292
374
|
|
293
375
|
# Creates an array of HexaPDF::Layout::TextFragment objects for the given +text+.
|
@@ -354,7 +436,7 @@ module HexaPDF
|
|
354
436
|
box_style = (box_style ? retrieve_style(box_style) : style)
|
355
437
|
box_class_for_name(:text).new(items: text_fragments(text, style: style),
|
356
438
|
width: width, height: height, properties: properties,
|
357
|
-
style: box_style)
|
439
|
+
style: box_style, **box_style.box_options)
|
358
440
|
end
|
359
441
|
alias text text_box
|
360
442
|
|
@@ -457,7 +539,8 @@ module HexaPDF
|
|
457
539
|
end
|
458
540
|
end
|
459
541
|
box_class_for_name(:text).new(items: data, width: width, height: height,
|
460
|
-
properties: properties, style: box_style
|
542
|
+
properties: properties, style: box_style,
|
543
|
+
**box_style.box_options)
|
461
544
|
end
|
462
545
|
alias formatted_text formatted_text_box
|
463
546
|
|
@@ -479,7 +562,7 @@ module HexaPDF
|
|
479
562
|
style = retrieve_style(style, style_properties)
|
480
563
|
image = file.kind_of?(HexaPDF::Stream) ? file : @document.images.add(file)
|
481
564
|
box_class_for_name(:image).new(image: image, width: width, height: height,
|
482
|
-
properties: properties, style: style)
|
565
|
+
properties: properties, style: style, **style.box_options)
|
483
566
|
end
|
484
567
|
alias image image_box
|
485
568
|
|
@@ -533,9 +616,10 @@ module HexaPDF
|
|
533
616
|
@argument_infos.each_with_object({}) do |arg_info, result|
|
534
617
|
next unless arg_info.rows.include?(row) && arg_info.cols.include?(col)
|
535
618
|
if arg_info.args[:cell]
|
536
|
-
arg_info.args
|
619
|
+
result.update(arg_info.args, cell: (result[:cell] || {}).merge(arg_info.args[:cell]))
|
620
|
+
else
|
621
|
+
result.update(arg_info.args)
|
537
622
|
end
|
538
|
-
result.update(arg_info.args)
|
539
623
|
end
|
540
624
|
end
|
541
625
|
|
@@ -608,7 +692,8 @@ module HexaPDF
|
|
608
692
|
end
|
609
693
|
box_class_for_name(:table).new(cells: cells, column_widths: column_widths, header: header,
|
610
694
|
footer: footer, cell_style: cell_style, width: width,
|
611
|
-
height: height, properties: properties, style: style
|
695
|
+
height: height, properties: properties, style: style,
|
696
|
+
**style.box_options)
|
612
697
|
end
|
613
698
|
alias table table_box
|
614
699
|
|
@@ -685,13 +770,7 @@ module HexaPDF
|
|
685
770
|
end
|
686
771
|
style = HexaPDF::Layout::Style.create(@styles[style] || style || @styles[:base])
|
687
772
|
style = style.dup.update(**properties) unless properties.nil? || properties.empty?
|
688
|
-
|
689
|
-
style.font(@styles[:base].font? && @styles[:base].font || @document.config['font.default'])
|
690
|
-
end
|
691
|
-
unless style.font.respond_to?(:pdf_object)
|
692
|
-
name, options = *style.font
|
693
|
-
style.font(@document.fonts.add(name, **(options || {})))
|
694
|
-
end
|
773
|
+
resolve_font(style)
|
695
774
|
style
|
696
775
|
end
|
697
776
|
|
@@ -430,8 +430,12 @@ module HexaPDF
|
|
430
430
|
@metadata[ns_dc]['creator'] = info_dict[:Author] if info_dict.key?(:Author)
|
431
431
|
@metadata[ns_dc]['description'] = info_dict[:Subject] if info_dict.key?(:Subject)
|
432
432
|
@metadata[ns_xmp]['CreatorTool'] = info_dict[:Creator] if info_dict.key?(:Creator)
|
433
|
-
|
434
|
-
|
433
|
+
if info_dict.key?(:CreationDate) && !info_dict[:CreationDate].kind_of?(String)
|
434
|
+
@metadata[ns_xmp]['CreateDate'] = info_dict[:CreationDate]
|
435
|
+
end
|
436
|
+
if info_dict.key?(:ModDate) && !info_dict[:ModDate].kind_of?(String)
|
437
|
+
@metadata[ns_xmp]['ModifyDate'] = info_dict[:ModDate] if info_dict.key?(:ModDate)
|
438
|
+
end
|
435
439
|
@metadata[ns_pdf]['Keywords'] = info_dict[:Keywords] if info_dict.key?(:Keywords)
|
436
440
|
@metadata[ns_pdf]['Producer'] = info_dict[:Producer] if info_dict.key?(:Producer)
|
437
441
|
if info_dict.key?(:Trapped) && info_dict[:Trapped] != :Unknown
|
@@ -528,7 +532,10 @@ module HexaPDF
|
|
528
532
|
# Formats the given date-time object (Time, Date, or DateTime) to be a valid XMP date-time
|
529
533
|
# value.
|
530
534
|
def xmp_date(date)
|
531
|
-
date
|
535
|
+
case date
|
536
|
+
when Time, Date, DateTime then date.strftime("%Y-%m-%dT%H:%M:%S%:z")
|
537
|
+
else ''
|
538
|
+
end
|
532
539
|
end
|
533
540
|
|
534
541
|
end
|
data/lib/hexapdf/document.rb
CHANGED
@@ -743,6 +743,15 @@ module HexaPDF
|
|
743
743
|
# Before the document is written, it is validated using #validate and an error is raised if the
|
744
744
|
# document is not valid. However, this step can be skipped if needed.
|
745
745
|
#
|
746
|
+
# The method dispatches two messages:
|
747
|
+
#
|
748
|
+
# :complete_objects::
|
749
|
+
# This message is dispatched before anything is done and should be used to finalize objects.
|
750
|
+
#
|
751
|
+
# :before_write::
|
752
|
+
# This message is dispatched directly before the document gets serialized and allows, for
|
753
|
+
# example, overriding automatic HexaPDF changes (e.g. forcefully setting a document version).
|
754
|
+
#
|
746
755
|
# Options:
|
747
756
|
#
|
748
757
|
# incremental::
|
@@ -325,8 +325,13 @@ module HexaPDF
|
|
325
325
|
options.user_password = prepare_password(options.user_password)
|
326
326
|
options.owner_password = prepare_password(options.owner_password)
|
327
327
|
|
328
|
-
dict[:
|
329
|
-
|
328
|
+
if dict[:R] <= 4
|
329
|
+
dict[:O] = compute_o_field(options.owner_password, options.user_password)
|
330
|
+
dict[:U] = compute_u_field(options.user_password)
|
331
|
+
else
|
332
|
+
dict[:U] = compute_u_field(options.user_password)
|
333
|
+
dict[:O] = compute_o_field(options.owner_password, options.user_password)
|
334
|
+
end
|
330
335
|
|
331
336
|
if dict[:R] <= 4
|
332
337
|
encryption_key = compute_user_encryption_key(options.user_password)
|
data/lib/hexapdf/error.rb
CHANGED
@@ -94,9 +94,17 @@ module HexaPDF
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def message # :nodoc:
|
97
|
-
"No glyph for #{glyph.str.inspect} in font '#{glyph.font_wrapper.wrapped_font.full_name}' " \
|
98
|
-
|
99
|
-
|
97
|
+
str = "No glyph for #{glyph.str.inspect} in font '#{glyph.font_wrapper.wrapped_font.full_name}' " \
|
98
|
+
"found. \n\n"
|
99
|
+
str << if glyph.font_wrapper.font_type == :Type1
|
100
|
+
"The used Type1 font only contains a very limited number of glyphs. TrueType " \
|
101
|
+
"fonts usually provide a much wider array of glyphs. Use the configuration option " \
|
102
|
+
"'font.map' to register appropriate font files. Also have a look at the " \
|
103
|
+
"'font.default' and 'font.fallback' options. "
|
104
|
+
else
|
105
|
+
"Maybe register another #{glyph.font_wrapper.font_type} font that contains the " \
|
106
|
+
"needed glyph and use it as fallback via the configuration option 'font.fallback'."
|
107
|
+
end
|
100
108
|
end
|
101
109
|
|
102
110
|
end
|