hexapdf 1.3.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 +45 -0
- data/lib/hexapdf/cli/form.rb +9 -4
- data/lib/hexapdf/configuration.rb +10 -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 +47 -0
- data/lib/hexapdf/document/layout.rb +73 -33
- 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/layout/box.rb +5 -0
- data/lib/hexapdf/layout/container_box.rb +63 -28
- data/lib/hexapdf/layout/style.rb +28 -13
- data/lib/hexapdf/layout/table_box.rb +20 -2
- data/lib/hexapdf/type/annotations/appearance_generator.rb +94 -16
- data/lib/hexapdf/type/annotations/interior_color.rb +1 -1
- data/lib/hexapdf/type/annotations/line.rb +1 -157
- 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.rb +4 -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 +20 -0
- data/test/hexapdf/document/test_layout.rb +16 -10
- data/test/hexapdf/document/test_metadata.rb +13 -1
- data/test/hexapdf/encryption/test_standard_security_handler.rb +2 -1
- 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 +20 -1
- data/test/hexapdf/layout/test_table_box.rb +14 -1
- data/test/hexapdf/test_dictionary_fields.rb +1 -0
- data/test/hexapdf/type/annotations/test_appearance_generator.rb +126 -0
- data/test/hexapdf/type/annotations/test_line.rb +0 -25
- data/test/hexapdf/type/annotations/test_line_ending_styling.rb +42 -0
- data/test/hexapdf/type/annotations/test_polygon_polyline.rb +29 -0
- metadata +9 -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,48 @@
|
|
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
|
+
|
1
46
|
## 1.3.0 - 2025-04-23
|
2
47
|
|
3
48
|
### Added
|
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?
|
@@ -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
|
#
|
@@ -748,6 +753,7 @@ module HexaPDF
|
|
748
753
|
Namespace: 'HexaPDF::Type::Namespace',
|
749
754
|
MCR: 'HexaPDF::Type::MarkedContentReference',
|
750
755
|
OBJR: 'HexaPDF::Type::ObjectReference',
|
756
|
+
Measure: 'HexaPDF::Type::Measure',
|
751
757
|
},
|
752
758
|
'object.subtype_map' => {
|
753
759
|
nil => {
|
@@ -769,6 +775,8 @@ module HexaPDF
|
|
769
775
|
Line: 'HexaPDF::Type::Annotations::Line',
|
770
776
|
Square: 'HexaPDF::Type::Annotations::Square',
|
771
777
|
Circle: 'HexaPDF::Type::Annotations::Circle',
|
778
|
+
Polygon: 'HexaPDF::Type::Annotations::Polygon',
|
779
|
+
PolyLine: 'HexaPDF::Type::Annotations::Polyline',
|
772
780
|
XML: 'HexaPDF::Type::Metadata',
|
773
781
|
GTS_PDFX: 'HexaPDF::Type::OutputIntent',
|
774
782
|
GTS_PDFA1: 'HexaPDF::Type::OutputIntent',
|
@@ -800,6 +808,8 @@ module HexaPDF
|
|
800
808
|
Line: 'HexaPDF::Type::Annotations::Line',
|
801
809
|
Square: 'HexaPDF::Type::Annotations::Square',
|
802
810
|
Circle: 'HexaPDF::Type::Annotations::Circle',
|
811
|
+
Polygon: 'HexaPDF::Type::Annotations::Polygon',
|
812
|
+
PolyLine: 'HexaPDF::Type::Annotations::Polyline',
|
803
813
|
},
|
804
814
|
XXAcroFormField: {
|
805
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
|
@@ -158,6 +158,53 @@ module HexaPDF
|
|
158
158
|
annot
|
159
159
|
end
|
160
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
|
+
|
161
208
|
private
|
162
209
|
|
163
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
|
@@ -231,6 +241,64 @@ module HexaPDF
|
|
231
241
|
@styles.key?(name)
|
232
242
|
end
|
233
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
|
+
|
234
302
|
# :call-seq:
|
235
303
|
# layout.styles -> styles
|
236
304
|
# layout.styles(**mapping) -> styles
|
@@ -548,9 +616,10 @@ module HexaPDF
|
|
548
616
|
@argument_infos.each_with_object({}) do |arg_info, result|
|
549
617
|
next unless arg_info.rows.include?(row) && arg_info.cols.include?(col)
|
550
618
|
if arg_info.args[:cell]
|
551
|
-
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)
|
552
622
|
end
|
553
|
-
result.update(arg_info.args)
|
554
623
|
end
|
555
624
|
end
|
556
625
|
|
@@ -682,22 +751,6 @@ module HexaPDF
|
|
682
751
|
end
|
683
752
|
end
|
684
753
|
|
685
|
-
FONT_BOLD_VARIANT_MAPPER = { #:nodoc:
|
686
|
-
nil => {true => :bold, false: :none},
|
687
|
-
none: {true => :bold, false: :none},
|
688
|
-
bold: {true => :bold, false: :none},
|
689
|
-
italic: {true => :bold_italic, false: :italic},
|
690
|
-
bold_italic: {true => :bold_italic, false: :italic},
|
691
|
-
}
|
692
|
-
|
693
|
-
FONT_ITALIC_VARIANT_MAPPER = { #:nodoc:
|
694
|
-
nil => {true => :italic, false: :none},
|
695
|
-
none: {true => :italic, false: :none},
|
696
|
-
italic: {true => :italic, false: :none},
|
697
|
-
bold: {true => :bold_italic, false: :bold},
|
698
|
-
bold_italic: {true => :bold_italic, false: :bold},
|
699
|
-
}
|
700
|
-
|
701
754
|
# Retrieves the appropriate HexaPDF::Layout::Style object based on the +style+ and
|
702
755
|
# +properties+ arguments.
|
703
756
|
#
|
@@ -717,20 +770,7 @@ module HexaPDF
|
|
717
770
|
end
|
718
771
|
style = HexaPDF::Layout::Style.create(@styles[style] || style || @styles[:base])
|
719
772
|
style = style.dup.update(**properties) unless properties.nil? || properties.empty?
|
720
|
-
|
721
|
-
style.font(@styles[:base].font? && @styles[:base].font || @document.config['font.default'])
|
722
|
-
end
|
723
|
-
unless style.font.respond_to?(:pdf_object)
|
724
|
-
name, options = *style.font
|
725
|
-
options ||= {}
|
726
|
-
if style.font_bold?
|
727
|
-
options[:variant] = FONT_BOLD_VARIANT_MAPPER.dig(options[:variant], style.font_bold)
|
728
|
-
end
|
729
|
-
if style.font_italic?
|
730
|
-
options[:variant] = FONT_ITALIC_VARIANT_MAPPER.dig(options[:variant], style.font_italic)
|
731
|
-
end
|
732
|
-
style.font(@document.fonts.add(name, **options))
|
733
|
-
end
|
773
|
+
resolve_font(style)
|
734
774
|
style
|
735
775
|
end
|
736
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/layout/box.rb
CHANGED
@@ -44,21 +44,22 @@ module HexaPDF
|
|
44
44
|
# under the :container name.
|
45
45
|
#
|
46
46
|
# The box does not support the value :flow for the style property position, so the child boxes
|
47
|
-
# are laid out in the current region only.
|
48
|
-
# box doesn't fit, the whole container doesn't fit. Splitting the container is also not possible
|
49
|
-
# for the same reason.
|
47
|
+
# are laid out in the current region only.
|
50
48
|
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
49
|
+
# If #splitable is +false+ (the default) and if any box doesn't fit, the whole container doesn't
|
50
|
+
# fit.
|
51
|
+
#
|
52
|
+
# By default the child boxes are laid out from top to bottom. By appropriately setting the style
|
53
|
+
# properties 'mask_mode', 'align' and 'valign', it is possible to lay out the children bottom to
|
54
|
+
# top, left to right, or right to left:
|
54
55
|
#
|
55
56
|
# * The standard top-to-bottom layout:
|
56
57
|
#
|
57
58
|
# #>pdf-composer100
|
58
59
|
# composer.container do |container|
|
59
|
-
# container.box(
|
60
|
-
# container.box(
|
61
|
-
# container.box(
|
60
|
+
# container.box(height: 20, style: {background_color: "hp-blue-dark"})
|
61
|
+
# container.box(height: 20, style: {background_color: "hp-blue"})
|
62
|
+
# container.box(height: 20, style: {background_color: "hp-blue-light"})
|
62
63
|
# end
|
63
64
|
#
|
64
65
|
# * The bottom-to-top layout (using valign = :bottom to fill up from the bottom and mask_mode =
|
@@ -66,12 +67,12 @@ module HexaPDF
|
|
66
67
|
#
|
67
68
|
# #>pdf-composer100
|
68
69
|
# composer.container do |container|
|
69
|
-
# container.box(
|
70
|
-
#
|
71
|
-
# container.box(
|
72
|
-
#
|
73
|
-
# container.box(
|
74
|
-
#
|
70
|
+
# container.box(height: 20, style: {background_color: "hp-blue-dark",
|
71
|
+
# mask_mode: :fill_horizontal, valign: :bottom})
|
72
|
+
# container.box(height: 20, style: {background_color: "hp-blue",
|
73
|
+
# mask_mode: :fill_horizontal, valign: :bottom})
|
74
|
+
# container.box(height: 20, style: {background_color: "hp-blue-light",
|
75
|
+
# mask_mode: :fill_horizontal, valign: :bottom})
|
75
76
|
# end
|
76
77
|
#
|
77
78
|
# * The left-to-right layout (using mask_mode = :fill_vertical to fill the area to the top and
|
@@ -79,12 +80,12 @@ module HexaPDF
|
|
79
80
|
#
|
80
81
|
# #>pdf-composer100
|
81
82
|
# composer.container do |container|
|
82
|
-
# container.box(
|
83
|
-
#
|
84
|
-
# container.box(
|
85
|
-
#
|
86
|
-
# container.box(
|
87
|
-
#
|
83
|
+
# container.box(width: 20, style: {background_color: "hp-blue-dark",
|
84
|
+
# mask_mode: :fill_vertical})
|
85
|
+
# container.box(width: 20, style: {background_color: "hp-blue",
|
86
|
+
# mask_mode: :fill_vertical})
|
87
|
+
# container.box(width: 20, style: {background_color: "hp-blue-light",
|
88
|
+
# mask_mode: :fill_vertical})
|
88
89
|
# end
|
89
90
|
#
|
90
91
|
# * The right-to-left layout (using align = :right to fill up from the right and mask_mode =
|
@@ -92,18 +93,42 @@ module HexaPDF
|
|
92
93
|
#
|
93
94
|
# #>pdf-composer100
|
94
95
|
# composer.container do |container|
|
95
|
-
# container.box(
|
96
|
-
#
|
97
|
-
# container.box(
|
98
|
-
#
|
99
|
-
# container.box(
|
100
|
-
#
|
96
|
+
# container.box(width: 20, style: {background_color: "hp-blue-dark",
|
97
|
+
# mask_mode: :fill_vertical, align: :right})
|
98
|
+
# container.box(width: 20, style: {background_color: "hp-blue",
|
99
|
+
# mask_mode: :fill_vertical, align: :right})
|
100
|
+
# container.box(width: 20, style: {background_color: "hp-blue-light",
|
101
|
+
# mask_mode: :fill_vertical, align: :right})
|
101
102
|
# end
|
102
103
|
class ContainerBox < Box
|
103
104
|
|
104
105
|
# The child boxes of this ContainerBox. They need to be finalized before #fit is called.
|
105
106
|
attr_reader :children
|
106
107
|
|
108
|
+
# Specifies whether the container box allows splitting its content.
|
109
|
+
#
|
110
|
+
# If splitting is not allowed (the default), all child boxes must fit together into one
|
111
|
+
# region.
|
112
|
+
#
|
113
|
+
# Examples:
|
114
|
+
#
|
115
|
+
# # Fails with an error because the content of the container box is too big
|
116
|
+
# composer.column do |col|
|
117
|
+
# col.container do |container|
|
118
|
+
# container.lorem_ipsum
|
119
|
+
# end
|
120
|
+
# end
|
121
|
+
#
|
122
|
+
# ---
|
123
|
+
#
|
124
|
+
# #>pdf-composer
|
125
|
+
# composer.column do |col|
|
126
|
+
# col.container(splitable: true) do |container|
|
127
|
+
# container.lorem_ipsum
|
128
|
+
# end
|
129
|
+
# end
|
130
|
+
attr_reader :splitable
|
131
|
+
|
107
132
|
# Creates a new container box, optionally accepting an array of child boxes.
|
108
133
|
#
|
109
134
|
# Example:
|
@@ -117,9 +142,10 @@ module HexaPDF
|
|
117
142
|
# container.text("here", mask_mode: :fill_vertical, valign: :bottom)
|
118
143
|
# end
|
119
144
|
# composer.text("Another paragraph")
|
120
|
-
def initialize(children: [], **kwargs)
|
145
|
+
def initialize(children: [], splitable: false, **kwargs)
|
121
146
|
super(**kwargs)
|
122
147
|
@children = children
|
148
|
+
@splitable = splitable
|
123
149
|
end
|
124
150
|
|
125
151
|
# Returns +true+ if no box was fitted into the container.
|
@@ -144,9 +170,18 @@ module HexaPDF
|
|
144
170
|
end
|
145
171
|
update_content_height { @box_fitter.content_heights.max }
|
146
172
|
fit_result.success!
|
173
|
+
elsif !@box_fitter.fit_results.empty? && @splitable
|
174
|
+
fit_result.overflow!
|
147
175
|
end
|
148
176
|
end
|
149
177
|
|
178
|
+
# Splits the content of the container box. This method is called from Box#split.
|
179
|
+
def split_content
|
180
|
+
box = create_split_box
|
181
|
+
box.instance_variable_set(:@children, @box_fitter.remaining_boxes)
|
182
|
+
[self, box]
|
183
|
+
end
|
184
|
+
|
150
185
|
# Draws the children onto the canvas at position [x, y].
|
151
186
|
def draw_content(canvas, x, y)
|
152
187
|
dx = x - @fit_x
|