hexapdf 0.37.2 → 0.39.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 +41 -0
- data/README.md +17 -13
- data/data/hexapdf/sRGB2014.icc +0 -0
- data/data/hexapdf/sRGB2014.icc.LICENSE +7 -0
- data/examples/030-pdfa.rb +89 -0
- data/lib/hexapdf/configuration.rb +11 -0
- data/lib/hexapdf/document/layout.rb +8 -4
- data/lib/hexapdf/document/metadata.rb +50 -13
- data/lib/hexapdf/layout/column_box.rb +2 -2
- data/lib/hexapdf/layout/container_box.rb +3 -2
- data/lib/hexapdf/layout/frame.rb +40 -6
- data/lib/hexapdf/layout/inline_box.rb +11 -4
- data/lib/hexapdf/layout/list_box.rb +17 -14
- data/lib/hexapdf/layout/style.rb +20 -0
- data/lib/hexapdf/layout/table_box.rb +2 -1
- data/lib/hexapdf/layout/text_box.rb +11 -2
- data/lib/hexapdf/layout/text_layouter.rb +12 -3
- data/lib/hexapdf/task/pdfa.rb +87 -0
- data/lib/hexapdf/task.rb +1 -0
- data/lib/hexapdf/type/optional_content_properties.rb +2 -2
- data/lib/hexapdf/type/output_intent.rb +85 -0
- data/lib/hexapdf/type.rb +1 -0
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/document/test_layout.rb +13 -0
- data/test/hexapdf/document/test_metadata.rb +60 -5
- data/test/hexapdf/layout/test_frame.rb +31 -2
- data/test/hexapdf/layout/test_inline_box.rb +8 -1
- data/test/hexapdf/layout/test_list_box.rb +20 -0
- data/test/hexapdf/layout/test_style.rb +1 -0
- data/test/hexapdf/layout/test_text_box.rb +22 -5
- data/test/hexapdf/task/test_pdfa.rb +41 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/test_optional_content_properties.rb +2 -2
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d0a43d232528df9bfc4a2c9baddd9cd1ddbd33fe7cce9d146b4e25760f25429b
|
4
|
+
data.tar.gz: 37cb10752d22bbc89fd18a779152ef7b5e02e6237318006a4b0e0a420d2890e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79a27b101c502261e1bca7a25fa26fa434d38cec09a2ccb28a698a4364d8ed337ee63d82ce8cd4dd3097334ed54f0877565c25e20c2494c0ae7029156dc188ef
|
7
|
+
data.tar.gz: 69befc3a7066eb90a58ad4a65243939930f26ecc461e0f6f7c9bf0894ddfa58bf1a02567563caa453b0c6e745421de75e7dd433926d2a56b96aa4d71557059f5
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,44 @@
|
|
1
|
+
## 0.39.0 - 2024-03-18
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* Hierarchical box information to the document layout engine
|
6
|
+
* Style property 'text_overflow' for controlling how overflowing text should be
|
7
|
+
handled
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
|
11
|
+
* [HexaPDF::Layout::Frame::FitResult#draw] to provide better optional content
|
12
|
+
group names
|
13
|
+
|
14
|
+
### Fixed
|
15
|
+
|
16
|
+
* [HexaPDF::Layout::TextBox] to correctly respect a set height
|
17
|
+
|
18
|
+
|
19
|
+
## 0.38.0 - 2024-03-10
|
20
|
+
|
21
|
+
### Added
|
22
|
+
|
23
|
+
* [HexaPDF::Task::PDFA] for creating PDF/A conforming PDF files
|
24
|
+
* [HexaPDF::Type::OutputIntent] for defining output intents
|
25
|
+
* [HexaPDF::Document::Metadata#delete] for deleting metadata properties
|
26
|
+
* PDF/A metadata properties definitions
|
27
|
+
* Added a /Name entry to the default optional content configuration dictionary
|
28
|
+
(needed by PDF/A)
|
29
|
+
|
30
|
+
### Changed
|
31
|
+
|
32
|
+
* Default language for XMP metadata from English to 'x-default'
|
33
|
+
* [HexaPDF::Layout::ListBox] to use the style's font for drawing markers and to
|
34
|
+
fall back to Times and ZapfDingbats if necessary
|
35
|
+
* [HexaPDF::Document::Layout#table_box] to merge the `:cell` keys that define
|
36
|
+
the cell style instead of using the last one
|
37
|
+
* [HexaPDF::Document::Layout] style retrieval to fall back to using the font of
|
38
|
+
the `:base` style and only if that doesn't exist to 'Times'
|
39
|
+
* XMP metadata stream contents to satisfy more PDF/A validators
|
40
|
+
|
41
|
+
|
1
42
|
## 0.37.2 - 2024-02-27
|
2
43
|
|
3
44
|
### Fixed
|
data/README.md
CHANGED
@@ -47,19 +47,19 @@ section](#License) for details.
|
|
47
47
|
* [`hexapdf` binary][hp] for most common PDF manipulation tasks
|
48
48
|
|
49
49
|
|
50
|
-
[canvas API]: https://hexapdf.gettalong.org/documentation/
|
51
|
-
[document composition engine]: https://hexapdf.gettalong.org/documentation/
|
50
|
+
[canvas API]: https://hexapdf.gettalong.org/documentation/api/HexaPDF/Content/Canvas.html
|
51
|
+
[document composition engine]: https://hexapdf.gettalong.org/documentation/document-creation/document-layout.html
|
52
52
|
[flowing text]: https://hexapdf.gettalong.org/examples/frame_text_flow.html
|
53
|
-
[styles]: https://hexapdf.gettalong.org/documentation/
|
54
|
-
[(un)ordered lists]: https://hexapdf.gettalong.org/documentation/
|
55
|
-
[multi-column layout]: https://hexapdf.gettalong.org/documentation/
|
56
|
-
[PDF forms]: https://hexapdf.gettalong.org/documentation/
|
57
|
-
[Document outline]: https://hexapdf.gettalong.org/documentation/
|
58
|
-
[attaching files]: https://hexapdf.gettalong.org/documentation/
|
59
|
-
[Encryption]: https://hexapdf.gettalong.org/documentation/
|
60
|
-
[Digital Signatures]: https://hexapdf.gettalong.org/documentation/
|
53
|
+
[styles]: https://hexapdf.gettalong.org/documentation/api/HexaPDF/Layout/Style/index.html
|
54
|
+
[(un)ordered lists]: https://hexapdf.gettalong.org/documentation/api/HexaPDF/Layout/ListBox.html
|
55
|
+
[multi-column layout]: https://hexapdf.gettalong.org/documentation/api/HexaPDF/Layout/ColumnBox.html
|
56
|
+
[PDF forms]: https://hexapdf.gettalong.org/documentation/interactive-forms/index.html
|
57
|
+
[Document outline]: https://hexapdf.gettalong.org/documentation/outline/index.html
|
58
|
+
[attaching files]: https://hexapdf.gettalong.org/documentation/api/HexaPDF/Document/Files.html
|
59
|
+
[Encryption]: https://hexapdf.gettalong.org/documentation/encryption/index.html
|
60
|
+
[Digital Signatures]: https://hexapdf.gettalong.org/documentation/digital-signatures/index.html
|
61
61
|
[File size optimization]: https://hexapdf.gettalong.org/documentation/benchmarks/optimization.html
|
62
|
-
[hp]: https://hexapdf.gettalong.org/documentation/
|
62
|
+
[hp]: https://hexapdf.gettalong.org/documentation/hexapdf.1.html
|
63
63
|
|
64
64
|
|
65
65
|
## Usage
|
@@ -126,7 +126,7 @@ featureful API when it comes to creating content, for individual pages as well a
|
|
126
126
|
If you want to migrate from Prawn to HexaPDF, there is the [migration guide] with detailed
|
127
127
|
information and examples, comparing the Prawn API to HexaPDF's equivalents.
|
128
128
|
|
129
|
-
[migration guide]: https://hexapdf.gettalong.org/documentation/
|
129
|
+
[migration guide]: https://hexapdf.gettalong.org/documentation/document-creation/migrating-from-prawn.html
|
130
130
|
|
131
131
|
Why use HexaPDF?
|
132
132
|
|
@@ -145,9 +145,10 @@ Why use HexaPDF?
|
|
145
145
|
manipulating PDFs. This tool is intended to be a replacement for tools like `pdftk` and the
|
146
146
|
various Poppler-based tools like `pdfinfo`, `pdfimages`, ...
|
147
147
|
|
148
|
-
[Prawn]:
|
148
|
+
[Prawn]: https://prawnpdf.org
|
149
149
|
[page canvas API]: https://hexapdf.gettalong.org/api/HexaPDF/Content/Canvas.html
|
150
150
|
|
151
|
+
|
151
152
|
## Development
|
152
153
|
|
153
154
|
Clone the repository and then run `rake dev:setup`. This will install the needed Rubygem
|
@@ -178,6 +179,9 @@ Some included files have a different license:
|
|
178
179
|
* The AES test vector files in `test/data/aes-test-vectors` have been created using the test vector
|
179
180
|
file available from <http://csrc.nist.gov/groups/STM/cavp/block-ciphers.html#test-vectors>.
|
180
181
|
|
182
|
+
* The license of the file `data/hexapdf/sRGB2014.icc` is available in the
|
183
|
+
`data/hexapdf/sRGB2014.icc.LICENSE` file.
|
184
|
+
|
181
185
|
|
182
186
|
## Contributing
|
183
187
|
|
Binary file
|
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2015 International Color Consortium
|
2
|
+
|
3
|
+
This profile is made available by the International Color Consortium,
|
4
|
+
and may be copied, distributed, embedded, made, used, and sold without
|
5
|
+
restriction. Altered versions of this profile shall have the original
|
6
|
+
identification and copyright information removed and shall not be
|
7
|
+
misrepresented as the original profile.
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# # PDF/A Conformance
|
2
|
+
#
|
3
|
+
# This example shows how to create a PDF file that is PDF/A compliant.
|
4
|
+
#
|
5
|
+
# In this case we are creating a simple invoice, with multiple line
|
6
|
+
# items that break across the page boundary.
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
# : `ruby pdfa.rb`
|
10
|
+
#
|
11
|
+
require 'hexapdf'
|
12
|
+
|
13
|
+
HexaPDF::Composer.create('pdfa.pdf') do |composer|
|
14
|
+
composer.document.task(:pdfa)
|
15
|
+
composer.document.config['font.map'] = {
|
16
|
+
'Lato' => {
|
17
|
+
none: '/usr/share/fonts/truetype/lato/Lato-Regular.ttf',
|
18
|
+
bold: '/usr/share/fonts/truetype/lato/Lato-Bold.ttf',
|
19
|
+
italic: '/usr/share/fonts/truetype/lato/Lato-Italic.ttf',
|
20
|
+
bold_italic: '/usr/share/fonts/truetype/lato/Lato-BoldItalic.ttf',
|
21
|
+
},
|
22
|
+
}
|
23
|
+
|
24
|
+
company = {
|
25
|
+
name: 'Sample Corp Limited',
|
26
|
+
address: ["Example Avenue 1", "12345 Runway"],
|
27
|
+
}
|
28
|
+
|
29
|
+
# Define all styles
|
30
|
+
composer.style(:base, font: 'Lato', font_size: 10, line_spacing: 1.3)
|
31
|
+
composer.style(:top, font_size: 8)
|
32
|
+
composer.style(:top_box, padding: [100, 0, 0], margin: [0, 0, 10], border: {width: [0, 0, 1]})
|
33
|
+
composer.style(:header, font: ['Lato', variant: :bold], font_size: 20, margin: [50, 0, 20])
|
34
|
+
composer.style(:line_items, border: {width: 1, color: "eee"}, margin: [20, 0])
|
35
|
+
composer.style(:line_item_cell, font_size: 8)
|
36
|
+
composer.style(:footer, border: {width: [1, 0, 0], color: "darkgrey"},
|
37
|
+
padding: [5, 0, 0], valign: :bottom)
|
38
|
+
composer.style(:footer_heading, font: ['Lato', variant: :bold],
|
39
|
+
font_size: 8, padding: [0, 0, 8])
|
40
|
+
composer.style(:footer_text, font_size: 8, fill_color: "darkgrey")
|
41
|
+
|
42
|
+
# Top part
|
43
|
+
composer.box(:container, style: :top_box) do |container|
|
44
|
+
container.formatted_text([{text: company[:name], font: ['Lato', variant: :bold]},
|
45
|
+
" - " + company[:address].join(' - ')], style: :top)
|
46
|
+
end
|
47
|
+
composer.text("Mega Client\nSmall Lane 5\n67890 Noonestown", mask_mode: :box)
|
48
|
+
cells = [["Invoice number:", "2024/01"],
|
49
|
+
["Invoice date", "2024-03-10"],
|
50
|
+
["Service date:", "2024-02-01"]]
|
51
|
+
composer.table(cells, column_widths: [150, 80], style: {align: :right}) do |args|
|
52
|
+
args[] = {cell: {border: {width: 0}, padding: 2}, text_align: :right}
|
53
|
+
args[0..-1, 0] = {font: ['Lato', variant: :bold]}
|
54
|
+
end
|
55
|
+
|
56
|
+
# Middle part
|
57
|
+
composer.text("Invoice - 2024/01", style: :header)
|
58
|
+
composer.text("Thank you for your order. Following are the items you purchased:")
|
59
|
+
|
60
|
+
cells = [["Description", "Price", "Amount", "Total"]]
|
61
|
+
max = 40
|
62
|
+
1.upto(max) do |index|
|
63
|
+
cells << ["Sample Item E.g. #{index}", "€ 250,00", index, "€ #{250 * index},00"]
|
64
|
+
end
|
65
|
+
cells << [nil, nil, nil, "€ #{250 * max * (max + 1) / 2},00"]
|
66
|
+
composer.table(cells, column_widths: [250, 80], style: :line_items) do |args|
|
67
|
+
args[] = {cell: {border: {width: 0}, padding: 8}, style: :line_item_cell}
|
68
|
+
args[0] = {cell: {background_color: "eee"}, font: ["Lato", variant: :bold]}
|
69
|
+
args[-1] = {cell: {background_color: "eee", border: {width: [2, 0, 0]}},
|
70
|
+
font: ["Lato", variant: :bold]}
|
71
|
+
args[0..-1, 1..-1] = {text_align: :right}
|
72
|
+
end
|
73
|
+
|
74
|
+
composer.text("Please transfer the total amount via SEPA transfer to the bank " \
|
75
|
+
"account below immediately after receiving the invoice - thank you.")
|
76
|
+
|
77
|
+
# Bottom part
|
78
|
+
l = composer.document.layout
|
79
|
+
cells = [
|
80
|
+
[l.text(company[:name], style: :footer_heading),
|
81
|
+
l.text(company[:address].join("\n"), style: :footer_text)],
|
82
|
+
[l.text('Contact', style: :footer_heading),
|
83
|
+
l.text("owner@samplecorp.com\nOwner: Me, Myself, And I", style: :footer_text)],
|
84
|
+
[l.text('Bank Account', style: :footer_heading),
|
85
|
+
l.text("Sample Corp Bank\nIBAN: SC01 2345 6789 0123 4567\nBIC: SACOZZB123",
|
86
|
+
style: :footer_text)],
|
87
|
+
]
|
88
|
+
composer.table([cells], cell_style: {border: {width: 0}}, style: :footer)
|
89
|
+
end
|
@@ -568,6 +568,7 @@ module HexaPDF
|
|
568
568
|
'task.map' => {
|
569
569
|
optimize: 'HexaPDF::Task::Optimize',
|
570
570
|
dereference: 'HexaPDF::Task::Dereference',
|
571
|
+
pdfa: 'HexaPDF::Task::PDFA',
|
571
572
|
})
|
572
573
|
|
573
574
|
# The global configuration object, providing the following options:
|
@@ -688,6 +689,8 @@ module HexaPDF
|
|
688
689
|
XXCIDSystemInfo: 'HexaPDF::Type::CIDFont::CIDSystemInfo',
|
689
690
|
Group: 'HexaPDF::Type::Form::Group',
|
690
691
|
Metadata: 'HexaPDF::Type::Metadata',
|
692
|
+
OutputIntent: 'HexaPDF::Type::OutputIntent',
|
693
|
+
XXDestOutputProfileRef: 'HexaPDF::Type::OutputIntent::DestOutputProfileRef',
|
691
694
|
},
|
692
695
|
'object.subtype_map' => {
|
693
696
|
nil => {
|
@@ -707,6 +710,9 @@ module HexaPDF
|
|
707
710
|
Link: 'HexaPDF::Type::Annotations::Link',
|
708
711
|
Widget: 'HexaPDF::Type::Annotations::Widget',
|
709
712
|
XML: 'HexaPDF::Type::Metadata',
|
713
|
+
GTS_PDFX: 'HexaPDF::Type::OutputIntent',
|
714
|
+
GTS_PDFA1: 'HexaPDF::Type::OutputIntent',
|
715
|
+
ISO_PDFE1: 'HexaPDF::Type::OutputIntent',
|
710
716
|
},
|
711
717
|
XObject: {
|
712
718
|
Image: 'HexaPDF::Type::Image',
|
@@ -738,6 +744,11 @@ module HexaPDF
|
|
738
744
|
Ch: 'HexaPDF::Type::AcroForm::ChoiceField',
|
739
745
|
Sig: 'HexaPDF::Type::AcroForm::SignatureField',
|
740
746
|
},
|
747
|
+
OutputIntent: {
|
748
|
+
GTS_PDFX: 'HexaPDF::Type::OutputIntent',
|
749
|
+
GTS_PDFA1: 'HexaPDF::Type::OutputIntent',
|
750
|
+
ISO_PDFE1: 'HexaPDF::Type::OutputIntent',
|
751
|
+
},
|
741
752
|
})
|
742
753
|
|
743
754
|
end
|
@@ -486,10 +486,14 @@ module HexaPDF
|
|
486
486
|
|
487
487
|
# Retrieves the merged keyword arguments for the cell in +row+ and +col+.
|
488
488
|
#
|
489
|
-
# Earlier defined arguments are overridden by later ones
|
489
|
+
# Earlier defined arguments are overridden by later ones, except for the +:cell+ key which
|
490
|
+
# is merged.
|
490
491
|
def retrieve_arguments_for(row, col)
|
491
492
|
@argument_infos.each_with_object({}) do |arg_info, result|
|
492
493
|
next unless arg_info.rows.cover?(row) && arg_info.cols.cover?(col)
|
494
|
+
if arg_info.args[:cell]
|
495
|
+
arg_info.args[:cell] = (result[:cell] || {}).merge(arg_info.args[:cell])
|
496
|
+
end
|
493
497
|
result.update(arg_info.args)
|
494
498
|
end
|
495
499
|
end
|
@@ -635,15 +639,15 @@ module HexaPDF
|
|
635
639
|
# If the +properties+ hash is not empty, the retrieved style is duplicated and the properties
|
636
640
|
# hash is applied to it.
|
637
641
|
#
|
638
|
-
# Finally, a default font
|
639
|
-
# cases.
|
642
|
+
# Finally, a default font (the one from the :base style or otherwise 'Times') is set if
|
643
|
+
# necessary to ensure that the style object works in all cases.
|
640
644
|
def retrieve_style(style, properties = nil)
|
641
645
|
if style.kind_of?(Symbol) && !@styles.key?(style)
|
642
646
|
raise HexaPDF::Error, "Style #{style} not defined"
|
643
647
|
end
|
644
648
|
style = HexaPDF::Layout::Style.create(@styles[style] || style || @styles[:base])
|
645
649
|
style = style.dup.update(**properties) unless properties.nil? || properties.empty?
|
646
|
-
style.font('Times') unless style.font?
|
650
|
+
style.font(@styles[:base].font? && @styles[:base].font || 'Times') unless style.font?
|
647
651
|
unless style.font.respond_to?(:pdf_object)
|
648
652
|
name, options = *style.font
|
649
653
|
style.font(@document.fonts.add(name, **(options || {})))
|
@@ -80,6 +80,10 @@ module HexaPDF
|
|
80
80
|
# String::
|
81
81
|
# Maps to the XMP simple string value. Values need to be of type String.
|
82
82
|
#
|
83
|
+
# Integer::
|
84
|
+
# Maps to the XMP integer core value type and gets formatted as string. Values need to be of
|
85
|
+
# type Integer.
|
86
|
+
#
|
83
87
|
# Date::
|
84
88
|
# Maps to the XMP simple string value, correctly formatted. Values need to be of type Time,
|
85
89
|
# Date, or DateTime
|
@@ -123,6 +127,7 @@ module HexaPDF
|
|
123
127
|
"pdf" => "http://ns.adobe.com/pdf/1.3/",
|
124
128
|
"dc" => "http://purl.org/dc/elements/1.1/",
|
125
129
|
"x" => "adobe:ns:meta/",
|
130
|
+
"pdfaid" => "http://www.aiim.org/pdfa/ns/id/",
|
126
131
|
}.freeze
|
127
132
|
|
128
133
|
# Contains a mapping of predefined XMP properties to their types, i.e. from namespace to
|
@@ -143,6 +148,10 @@ module HexaPDF
|
|
143
148
|
'description' => 'LanguageArray',
|
144
149
|
'title' => 'LanguageArray',
|
145
150
|
}.freeze,
|
151
|
+
"http://www.aiim.org/pdfa/ns/id/" => {
|
152
|
+
'part' => 'Integer',
|
153
|
+
'conformance' => 'String',
|
154
|
+
}.freeze,
|
146
155
|
}.freeze
|
147
156
|
|
148
157
|
# Creates a new Metadata object for the given PDF document.
|
@@ -150,7 +159,7 @@ module HexaPDF
|
|
150
159
|
@document = document
|
151
160
|
@namespaces = PREDEFINED_NAMESPACES.dup
|
152
161
|
@properties = PREDEFINED_PROPERTIES.transform_values(&:dup)
|
153
|
-
@default_language = document.catalog[:Lang] || '
|
162
|
+
@default_language = document.catalog[:Lang] || 'x-default'
|
154
163
|
@metadata = Hash.new {|h, k| h[k] = {} }
|
155
164
|
write_info_dict(true)
|
156
165
|
write_metadata_stream(true)
|
@@ -166,7 +175,7 @@ module HexaPDF
|
|
166
175
|
# is given. Otherwise sets the default language to the given language.
|
167
176
|
#
|
168
177
|
# The initial default lanuage is taken from the document catalog's /Lang entry. If that is not
|
169
|
-
# set, the default language is assumed to be
|
178
|
+
# set, the default language is assumed to be default language ('x-default').
|
170
179
|
def default_language(value = :UNSET)
|
171
180
|
if value == :UNSET
|
172
181
|
@default_language
|
@@ -213,8 +222,8 @@ module HexaPDF
|
|
213
222
|
|
214
223
|
# Registers the +property+ for the namespace specified via +prefix+ as the given +type+.
|
215
224
|
#
|
216
|
-
# The argument +type+ has to be one of the following: 'String', '
|
217
|
-
# 'OrderedArray', 'UnorderedArray', or 'LanguageArray'.
|
225
|
+
# The argument +type+ has to be one of the following: 'String', 'Integer', 'Date', 'URI',
|
226
|
+
# 'Boolean', 'OrderedArray', 'UnorderedArray', or 'LanguageArray'.
|
218
227
|
def register_property_type(prefix, property, type)
|
219
228
|
(@properties[namespace(prefix)] ||= {})[property] = type
|
220
229
|
end
|
@@ -240,13 +249,31 @@ module HexaPDF
|
|
240
249
|
end
|
241
250
|
|
242
251
|
# :call-seq:
|
243
|
-
# metadata.
|
244
|
-
# metadata.
|
252
|
+
# metadata.delete
|
253
|
+
# metadata.delete(ns_prefix)
|
254
|
+
# metadata.delete(ns_prefix, name)
|
255
|
+
#
|
256
|
+
# Deletes either all metadata properties, only the ones from a specific namespace, or a
|
257
|
+
# specific one.
|
258
|
+
def delete(ns = nil, property = nil)
|
259
|
+
if ns.nil? && property.nil?
|
260
|
+
@metadata.clear
|
261
|
+
elsif property.nil?
|
262
|
+
@metadata.delete(namespace(ns))
|
263
|
+
else
|
264
|
+
@metadata[namespace(ns)].delete(property)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# :call-seq:
|
269
|
+
# metadata.title -> title or nil
|
270
|
+
# metadata.title(value) -> value
|
245
271
|
#
|
246
272
|
# Returns the document's title if no argument is given. Otherwise sets the document's title to
|
247
273
|
# the given value.
|
248
274
|
#
|
249
|
-
#
|
275
|
+
# If the +value+ is a LocalizedString, the language for the title is taken from it. Otherwise
|
276
|
+
# the language specified via #default_language is used.
|
250
277
|
#
|
251
278
|
# The value +nil+ is returned if the property is not set. And by using +nil+ as +value+ the
|
252
279
|
# property is deleted from the metadata.
|
@@ -278,7 +305,8 @@ module HexaPDF
|
|
278
305
|
# Returns the subject of the document if no argument is given. Otherwise sets the subject to
|
279
306
|
# the given value.
|
280
307
|
#
|
281
|
-
#
|
308
|
+
# If the +value+ is a LocalizedString, the language for the subject is taken from it.
|
309
|
+
# Otherwise the language specified via #default_language is used.
|
282
310
|
#
|
283
311
|
# The value +nil+ is returned if the property ist not set. And by using +nil+ as +value+ the
|
284
312
|
# property is deleted from the metadata.
|
@@ -406,23 +434,30 @@ module HexaPDF
|
|
406
434
|
ns_xmp = namespace('xmp')
|
407
435
|
ns_pdf = namespace('pdf')
|
408
436
|
|
437
|
+
producer("HexaPDF version #{HexaPDF::VERSION}")
|
438
|
+
|
409
439
|
if write_info_dict?
|
410
440
|
info_dict = @document.trailer.info
|
411
441
|
info_dict[:Title] = Array(@metadata[ns_dc]['title']).first
|
412
|
-
|
442
|
+
if @metadata[ns_dc].key?('creator')
|
443
|
+
info_dict[:Author] = Array(@metadata[ns_dc]['creator']).join(', ')
|
444
|
+
end
|
413
445
|
info_dict[:Subject] = Array(@metadata[ns_dc]['description']).first
|
414
446
|
info_dict[:Creator] = @metadata[ns_xmp]['CreatorTool']
|
415
447
|
info_dict[:CreationDate] = @metadata[ns_xmp]['CreateDate']
|
416
448
|
info_dict[:ModDate] = @metadata[ns_xmp]['ModifyDate']
|
417
449
|
info_dict[:Keywords] = @metadata[ns_pdf]['Keywords']
|
418
450
|
info_dict[:Producer] = @metadata[ns_pdf]['Producer']
|
419
|
-
|
451
|
+
if @metadata[ns_pdf].key?('Trapped')
|
452
|
+
info_dict[:Trapped] = @metadata[ns_pdf]['Trapped'] ? :True : :False
|
453
|
+
end
|
420
454
|
end
|
421
455
|
|
422
456
|
if write_metadata_stream?
|
423
457
|
descriptions = @metadata.map do |namespace, values|
|
458
|
+
next if values.empty?
|
424
459
|
xmp_description(@namespaces.key(namespace), values)
|
425
|
-
end.join("\n")
|
460
|
+
end.compact.join("\n")
|
426
461
|
obj = @document.catalog[:Metadata] ||= @document.add({Type: :Metadata, Subtype: :XML})
|
427
462
|
obj.stream = xmp_packet(descriptions)
|
428
463
|
end
|
@@ -432,9 +467,11 @@ module HexaPDF
|
|
432
467
|
def xmp_packet(data)
|
433
468
|
<<~XMP
|
434
469
|
<?xpacket begin="\u{FEFF}" id="#{SecureRandom.uuid.tr('-', '')}"?>
|
470
|
+
<x:xmpmeta xmlns:x="adobe:ns:meta/">
|
435
471
|
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
436
472
|
#{data}
|
437
473
|
</rdf:RDF>
|
474
|
+
</x:xmpmeta>
|
438
475
|
<?xpacket end="r"?>
|
439
476
|
XMP
|
440
477
|
end
|
@@ -444,8 +481,8 @@ module HexaPDF
|
|
444
481
|
values = values.map do |name, value|
|
445
482
|
str = +"<#{ns_prefix}:#{name}"
|
446
483
|
case (property_type = @properties[namespace(ns_prefix)][name])
|
447
|
-
when 'String'
|
448
|
-
str << ">#{xmp_escape(value)}</#{ns_prefix}:#{name}>"
|
484
|
+
when 'String', 'Integer'
|
485
|
+
str << ">#{xmp_escape(value.to_s)}</#{ns_prefix}:#{name}>"
|
449
486
|
when 'Date'
|
450
487
|
str << ">#{xmp_date(value)}</#{ns_prefix}:#{name}>"
|
451
488
|
when 'URI'
|
@@ -179,8 +179,8 @@ module HexaPDF
|
|
179
179
|
[column_left, column_bottom + height])
|
180
180
|
shape = Geom2D::Algorithms::PolygonOperation.run(frame.shape, rect, :intersection)
|
181
181
|
end
|
182
|
-
column_frame =
|
183
|
-
|
182
|
+
column_frame = frame.child_frame(column_left, column_bottom, column_width, height,
|
183
|
+
shape: shape, box: self)
|
184
184
|
@box_fitter << column_frame
|
185
185
|
end
|
186
186
|
|
@@ -131,8 +131,9 @@ module HexaPDF
|
|
131
131
|
|
132
132
|
# Fits the children into the container.
|
133
133
|
def fit_content(_available_width, _available_height, frame)
|
134
|
-
my_frame =
|
135
|
-
|
134
|
+
my_frame = frame.child_frame(frame.x + reserved_width_left,
|
135
|
+
frame.y - @height + reserved_height_bottom,
|
136
|
+
content_width, content_height, box: self)
|
136
137
|
@box_fitter = BoxFitter.new([my_frame])
|
137
138
|
children.each {|box| @box_fitter.fit(box) }
|
138
139
|
|
data/lib/hexapdf/layout/frame.rb
CHANGED
@@ -91,6 +91,9 @@ module HexaPDF
|
|
91
91
|
# Stores the result of fitting a box in a Frame.
|
92
92
|
class FitResult
|
93
93
|
|
94
|
+
# The frame into which the box was fitted.
|
95
|
+
attr_accessor :frame
|
96
|
+
|
94
97
|
# The box that was fitted into the frame.
|
95
98
|
attr_accessor :box
|
96
99
|
|
@@ -110,8 +113,9 @@ module HexaPDF
|
|
110
113
|
# drawing the box.
|
111
114
|
attr_accessor :mask
|
112
115
|
|
113
|
-
# Initialize the result object for the given box.
|
114
|
-
def initialize(box)
|
116
|
+
# Initialize the result object for the given frame and box.
|
117
|
+
def initialize(frame, box)
|
118
|
+
@frame = frame
|
115
119
|
@box = box
|
116
120
|
@available_width = 0
|
117
121
|
@available_height = 0
|
@@ -138,7 +142,10 @@ module HexaPDF
|
|
138
142
|
def draw(canvas, dx: 0, dy: 0)
|
139
143
|
doc = canvas.context.document
|
140
144
|
if doc.config['debug']
|
141
|
-
name =
|
145
|
+
name = (frame.parent_boxes + [box]).map do |box|
|
146
|
+
box.class.to_s.sub(/.*::/, '')
|
147
|
+
end.join('-') << "##{box.object_id}"
|
148
|
+
name = "#{name} (#{(x + dx).to_i},#{(y + dy).to_i}-#{mask.width.to_i}x#{mask.height.to_i})"
|
142
149
|
ocg = doc.optional_content.ocg(name)
|
143
150
|
canvas.optional_content(ocg) do
|
144
151
|
canvas.translate(dx, dy) do
|
@@ -147,7 +154,8 @@ module HexaPDF
|
|
147
154
|
draw(:geom2d, object: mask, path_only: true).fill_stroke
|
148
155
|
end
|
149
156
|
end
|
150
|
-
|
157
|
+
page = "Page #{canvas.context.index + 1}" rescue "XObject"
|
158
|
+
doc.optional_content.default_configuration.add_ocg_to_ui(ocg, path: ['Debug', page])
|
151
159
|
end
|
152
160
|
box.draw(canvas, x + dx, y + dy)
|
153
161
|
end
|
@@ -195,14 +203,21 @@ module HexaPDF
|
|
195
203
|
# should be used.
|
196
204
|
attr_reader :context
|
197
205
|
|
206
|
+
# An array of box objects representing the parent boxes.
|
207
|
+
#
|
208
|
+
# The immediate parent is the last array entry, the top most parent the first one. All boxes
|
209
|
+
# that are fitted into this frame have to be child boxes of the immediate parent box.
|
210
|
+
attr_reader :parent_boxes
|
211
|
+
|
198
212
|
# Creates a new Frame object for the given rectangular area.
|
199
|
-
def initialize(left, bottom, width, height, shape: nil, context: nil)
|
213
|
+
def initialize(left, bottom, width, height, shape: nil, context: nil, parent_boxes: [])
|
200
214
|
@left = left
|
201
215
|
@bottom = bottom
|
202
216
|
@width = width
|
203
217
|
@height = height
|
204
218
|
@shape = shape || create_rectangle(left, bottom, left + width, bottom + height)
|
205
219
|
@context = context
|
220
|
+
@parent_boxes = parent_boxes.freeze
|
206
221
|
|
207
222
|
@x = left
|
208
223
|
@y = bottom + height
|
@@ -213,6 +228,25 @@ module HexaPDF
|
|
213
228
|
@region_selection = :max_height
|
214
229
|
end
|
215
230
|
|
231
|
+
# Creates a new Frame object based on this one.
|
232
|
+
#
|
233
|
+
# If the +init_args+ arguments are provided, a new Frame is created using the constructor. The
|
234
|
+
# optional +shape+ argument is then also passed to the constructor.
|
235
|
+
#
|
236
|
+
# Otherwise, this frame is duplicated. This kind of invocation is only useful if the +box+
|
237
|
+
# argument is provided (because otherwise there would be no difference to this frame).
|
238
|
+
#
|
239
|
+
# The +box+ argument can be used to add the appropriate parent box to the list of
|
240
|
+
# #parent_boxes for the newly created frame.
|
241
|
+
def child_frame(*init_args, shape: nil, box: nil)
|
242
|
+
parent_boxes = (box ? @parent_boxes.dup << box : @parent_boxes)
|
243
|
+
if init_args.empty?
|
244
|
+
dup.tap {|result| result.instance_variable_set(:@parent_boxes, parent_boxes) }
|
245
|
+
else
|
246
|
+
self.class.new(*init_args, shape: shape, context: @context, parent_boxes: parent_boxes)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
216
250
|
# Returns the HexaPDF::Document instance (through #context) that is associated with this Frame
|
217
251
|
# object or +nil+ if no context object has been set.
|
218
252
|
def document
|
@@ -227,7 +261,7 @@ module HexaPDF
|
|
227
261
|
#
|
228
262
|
# Use the FitResult#success? method to determine whether fitting was successful.
|
229
263
|
def fit(box)
|
230
|
-
fit_result = FitResult.new(box)
|
264
|
+
fit_result = FitResult.new(self, box)
|
231
265
|
return fit_result if full?
|
232
266
|
|
233
267
|
margin = box.style.margin if box.style.margin?
|
@@ -126,10 +126,17 @@ module HexaPDF
|
|
126
126
|
height
|
127
127
|
end
|
128
128
|
|
129
|
-
# Fits the wrapped box
|
130
|
-
|
131
|
-
|
132
|
-
|
129
|
+
# Fits the wrapped box.
|
130
|
+
#
|
131
|
+
# If the +frame+ argument is +nil+, a custom frame is created. Otherwise the given +frame+ is
|
132
|
+
# used for the fitting operation.
|
133
|
+
def fit_wrapped_box(frame)
|
134
|
+
frame = if frame
|
135
|
+
frame.child_frame(0, 0, box.width, box.height == 0 ? 100_000 : box.height)
|
136
|
+
else
|
137
|
+
Frame.new(0, 0, box.width, box.height == 0 ? 100_000 : box.height)
|
138
|
+
end
|
139
|
+
@fit_result = frame.fit(box)
|
133
140
|
if !@fit_result.success?
|
134
141
|
raise HexaPDF::Error, "Box for inline use could not be fit"
|
135
142
|
elsif box.height > 99_000
|