hexapdf 1.3.0 → 1.4.1
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 +68 -0
- data/lib/hexapdf/cli/form.rb +10 -5
- data/lib/hexapdf/cli/images.rb +5 -1
- data/lib/hexapdf/cli.rb +3 -0
- 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 +10 -1
- data/lib/hexapdf/encryption/standard_security_handler.rb +7 -2
- data/lib/hexapdf/font/encoding/base.rb +27 -0
- data/lib/hexapdf/font/type1_wrapper.rb +1 -3
- 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/serializer.rb +7 -7
- 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/font_type1.rb +12 -1
- data/lib/hexapdf/type/measure.rb +57 -0
- data/lib/hexapdf/type.rb +1 -0
- data/lib/hexapdf/utils/sorted_tree_node.rb +4 -1
- 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/font/encoding/test_base.rb +20 -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 +20 -1
- data/test/hexapdf/layout/test_table_box.rb +14 -1
- data/test/hexapdf/test_dictionary_fields.rb +1 -0
- data/test/hexapdf/test_document.rb +1 -0
- data/test/hexapdf/test_serializer.rb +2 -1
- 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
- data/test/hexapdf/type/annotations/test_widget.rb +8 -0
- data/test/hexapdf/type/test_font_type1.rb +14 -0
- data/test/hexapdf/utils/test_sorted_tree_node.rb +11 -1
- 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: b88ce85ee9bc603011b9f5a278829d588da10f53614c0b84a57e2d7fa38f52dc
|
4
|
+
data.tar.gz: ec2c8739ed69038e1297435550371bf329e516e9ef970fa0456502b15720d07b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 103edc366ef9f48ddd6579f7137b3ab23b4266dc2df0a77ee5b89cb4256419a00727776158a7c7570a0b10075e4f062506d524ec71ee801c00fdd9e4726c8232
|
7
|
+
data.tar.gz: f1f4a1af54445b2e7c3c9fc1adfa81fb9fad84f32461f58378fc550bd5aec16e16b276dadc7516ca5b0b6212394886d06f2cc2a3506dc2b2745b5a1f4c8136d1
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,71 @@
|
|
1
|
+
## 1.4.1 - 2025-09-23
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* [HexaPDF::Font::Encoding::Base#to_compact_array] for creating a compact array
|
6
|
+
representation of the encoding
|
7
|
+
|
8
|
+
### Changed
|
9
|
+
|
10
|
+
- CLI to handle missing file errors better
|
11
|
+
|
12
|
+
### Fixed
|
13
|
+
|
14
|
+
* Serialization of strings that need to be UTF-16 encoded when using encryption
|
15
|
+
* [HexaPDF::Document#write_to_string] to pass on arguments to `#write`
|
16
|
+
* [HexaPDF::Type::FontType1] validation to handle PDFs with an invalid value of
|
17
|
+
/SymbolEncoding for the /Encoding key
|
18
|
+
* [HexaPDF::Type::FontType1] validation to handle PDFs with an invalid value of
|
19
|
+
/StandardEncoding for the /Encoding key
|
20
|
+
* CLI command `hexapdf form` to ignore widgets that don't belong to any field
|
21
|
+
* Validation of invalid sorted tree root nodes with odd number of direct entries
|
22
|
+
|
23
|
+
|
24
|
+
## 1.4.0 - 2025-08-03
|
25
|
+
|
26
|
+
### Added
|
27
|
+
|
28
|
+
* [HexaPDF::Type::Annotations::Polygon] for polygon annotations as well as
|
29
|
+
[HexaPDF::Document::Annotations#create_polygon]
|
30
|
+
* [HexaPDF::Type::Annotations::Polyline] for polyline annotations as well as
|
31
|
+
[HexaPDF::Document::Annotations#create_polyline]
|
32
|
+
* [HexaPDF::Layout::ContainerBox#splitable] for specifying whether the container
|
33
|
+
box may be split
|
34
|
+
* [HexaPDF::Layout::Style::Layers#layers] for retrieving the list of defined
|
35
|
+
layers
|
36
|
+
* [HexaPDF::Document::Layout#resolve_font] for resolving the font style property
|
37
|
+
* [HexaPDF::Type::Measure] for representing the measure dictionary
|
38
|
+
* [HexaPDF::Layout::Box::FitResult#failure!] for setting the result status to
|
39
|
+
failure
|
40
|
+
|
41
|
+
### Changed
|
42
|
+
|
43
|
+
* **Breaking change**: Extracted `#line_ending_style` and associated data class
|
44
|
+
from [HexaPDF::Type::Annotations::Line] into
|
45
|
+
[HexaPDF::Type::Annotations::LineEndingStyling]
|
46
|
+
* [HexaPDF::Layout::TableBox] implementation to allow setting the minimum height
|
47
|
+
of a table cell
|
48
|
+
* [HexaPDF::Layout::Style::Quad#set] to allow setting a subset of values using a
|
49
|
+
hash
|
50
|
+
* CLI command `hexapdf form` to show the names of radio button widgets
|
51
|
+
* CLI command `hexapdf form` to show position and size of widgets in easier to
|
52
|
+
understand form
|
53
|
+
* Default signing handler to not set /DigestMethod entry on signature reference
|
54
|
+
dictionary anymore
|
55
|
+
|
56
|
+
### Fixed
|
57
|
+
|
58
|
+
* Parsing and writing the /ModDate and /CreationDate trailer info fields in case
|
59
|
+
of string values when using the XMP metadata handler
|
60
|
+
* [HexaPDF::Layout::Style] to not accidentally set subscript or superscript
|
61
|
+
values
|
62
|
+
* [HexaPDF::DictionaryFields::DateConverter] to handle invalid dates with two
|
63
|
+
trailing apostrophes
|
64
|
+
* [HexaPDF::Document::Layout::CellArgumentCollector#retrieve_arguments_for] to
|
65
|
+
not change the stored data
|
66
|
+
* Encryption when using AES with 256bits and an owner password
|
67
|
+
|
68
|
+
|
1
69
|
## 1.3.0 - 2025-04-23
|
2
70
|
|
3
71
|
### 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?
|
@@ -285,7 +290,7 @@ module HexaPDF
|
|
285
290
|
page.each_annotation do |annotation|
|
286
291
|
next unless annotation[:Subtype] == :Widget
|
287
292
|
field = annotation.form_field
|
288
|
-
next if field.concrete_field_type == :push_button
|
293
|
+
next if !field.concrete_field_type || field.concrete_field_type == :push_button
|
289
294
|
if with_seen || !seen[field.full_field_name]
|
290
295
|
yield(page, page_index, field, annotation)
|
291
296
|
seen[field.full_field_name] = true
|
data/lib/hexapdf/cli/images.rb
CHANGED
@@ -132,7 +132,7 @@ module HexaPDF
|
|
132
132
|
printf("%5s %5s %9s %6s %6s %5s %4s %3s %5s %5s %6s %5s %8s\n",
|
133
133
|
"index", "page", "oid", "width", "height", "color", "comp", "bpc",
|
134
134
|
"x-ppi", "y-ppi", "size", "type", "writable")
|
135
|
-
puts("-" *
|
135
|
+
puts("-" * 84)
|
136
136
|
each_image(doc) do |image, index, pindex, (x_ppi, y_ppi)|
|
137
137
|
info = image.info
|
138
138
|
size = human_readable_file_size(image[:Length] + image[:SMask]&.[](:Length).to_i)
|
@@ -155,6 +155,10 @@ module HexaPDF
|
|
155
155
|
puts "Extracting #{path}..." if command_parser.verbosity_info?
|
156
156
|
image.write(path)
|
157
157
|
done << index
|
158
|
+
if info.color_space == :cmyk && info.type == :jpeg
|
159
|
+
$stderr.puts "Note (image #{path}): JPEG uses CMYK colorspace and may " \
|
160
|
+
"need color post-processing"
|
161
|
+
end
|
158
162
|
elsif command_parser.verbosity_warning?
|
159
163
|
$stderr.puts "Warning (image #{index}): PDF image format not supported for writing"
|
160
164
|
end
|
data/lib/hexapdf/cli.rb
CHANGED
@@ -61,6 +61,9 @@ module HexaPDF
|
|
61
61
|
# Runs the CLI application.
|
62
62
|
def self.run(args = ARGV)
|
63
63
|
Application.new.parse(args)
|
64
|
+
rescue Errno::ENOENT => e
|
65
|
+
path = e.message.scan(/(?<= - ).*?$/).first
|
66
|
+
$stderr.puts "Problem encountered: No such file - #{path}"
|
64
67
|
rescue StandardError => e
|
65
68
|
$stderr.puts "Problem encountered: #{e.message}"
|
66
69
|
unless e.kind_of?(HexaPDF::Error)
|
@@ -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::
|
@@ -814,7 +823,7 @@ module HexaPDF
|
|
814
823
|
# See #write for further information and details on the available arguments.
|
815
824
|
def write_to_string(**args)
|
816
825
|
io = StringIO.new(''.b)
|
817
|
-
write(io)
|
826
|
+
write(io, **args)
|
818
827
|
io.string
|
819
828
|
end
|
820
829
|
|
@@ -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)
|
@@ -81,6 +81,33 @@ module HexaPDF
|
|
81
81
|
@code_to_name.key(name)
|
82
82
|
end
|
83
83
|
|
84
|
+
# Returns the encoding in a compact array form.
|
85
|
+
#
|
86
|
+
# If the optional +base_encoding+ argument is specified, all codes that have the same value
|
87
|
+
# in the base encoding are ignored.
|
88
|
+
#
|
89
|
+
# The returned array is of the form:
|
90
|
+
#
|
91
|
+
# code1 name1 name2 ... code2 name3 name4 ...
|
92
|
+
#
|
93
|
+
# This means that name1 is associated with code1, name2 with code1 + 1 and so on.
|
94
|
+
#
|
95
|
+
# See: PDF 2.0 s9.6.5.1
|
96
|
+
def to_compact_array(base_encoding: nil)
|
97
|
+
result = []
|
98
|
+
last_code = -3
|
99
|
+
@code_to_name.sort.each do |code, name|
|
100
|
+
next if base_encoding&.name(code) == name
|
101
|
+
if last_code + 1 == code
|
102
|
+
result << name
|
103
|
+
else
|
104
|
+
result << code << name
|
105
|
+
end
|
106
|
+
last_code = code
|
107
|
+
end
|
108
|
+
result
|
109
|
+
end
|
110
|
+
|
84
111
|
end
|
85
112
|
|
86
113
|
end
|
@@ -279,9 +279,7 @@ module HexaPDF
|
|
279
279
|
if VALID_ENCODING_NAMES.include?(@encoding.encoding_name)
|
280
280
|
dict[:Encoding] = @encoding.encoding_name
|
281
281
|
elsif @encoding != @wrapped_font.encoding
|
282
|
-
|
283
|
-
(min..max).each {|code| differences << @encoding.name(code) }
|
284
|
-
dict[:Encoding] = {Differences: differences}
|
282
|
+
dict[:Encoding] = {Differences: @encoding.to_compact_array}
|
285
283
|
end
|
286
284
|
end
|
287
285
|
|
data/lib/hexapdf/layout/box.rb
CHANGED