hexapdf 0.37.2 → 0.39.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 +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
|