idml 0.2.8 → 0.2.9
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/Gemfile.lock +2 -2
- data/TODO.pdf/75-pdf-metadata-from-xmp.md +52 -90
- data/lib/idml/parts/xmp.rb +305 -0
- data/lib/idml/parts.rb +4 -0
- data/lib/idml/render/pipeline.rb +44 -1
- data/lib/idml/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 192799dde8a7c36a34473f5903934fa17a08c8ea05ae444797099ada2b03ff7c
|
|
4
|
+
data.tar.gz: 9b7659bb74cda7f6d8f9f47df07e2123605baa9df584138d3ef382934f999f60
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c113cb7d7a44f198c4c6746cde61f2301733f7f61f2ed5bfe6a3afd145e4446a2e144790916a5bad8106440e0f83b2901dff52e6ff2adf52dedbbe7fb8fcf655
|
|
7
|
+
data.tar.gz: 7960cfe58d64292369cfb23c87a5bcaeec495a7e20f3bbf8c87592040ec04d57630d61ecf7fe2916b1706c142d53c7561d3229c5563216eaf015119d28148a48
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
idml (0.2.
|
|
4
|
+
idml (0.2.9)
|
|
5
5
|
bigdecimal
|
|
6
6
|
fontisan
|
|
7
7
|
lutaml-model (~> 0.8.18)
|
|
@@ -198,7 +198,7 @@ CHECKSUMS
|
|
|
198
198
|
ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
|
|
199
199
|
ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
|
|
200
200
|
fontisan (0.4.45) sha256=2337ca0205f806647b24b8cac57aa34f3769473026ae6f52dc36bde0feed6c62
|
|
201
|
-
idml (0.2.
|
|
201
|
+
idml (0.2.9)
|
|
202
202
|
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
|
|
203
203
|
language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
|
|
204
204
|
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
|
@@ -1,103 +1,65 @@
|
|
|
1
1
|
# TODO PDF 75: PDF metadata enrichment from IDML XMP
|
|
2
2
|
|
|
3
|
-
## Status:
|
|
3
|
+
## Status: DONE
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## What was implemented
|
|
6
6
|
|
|
7
7
|
Pull IDML document metadata from `META-INF/metadata.xml` (XMP packet)
|
|
8
8
|
into the PDF Info dictionary via `PdfrbWriter#set_info`:
|
|
9
9
|
|
|
10
|
-
- `dc:title` → PDF `/Title`
|
|
11
|
-
- `dc:creator` → PDF `/Author`
|
|
12
|
-
- `dc:description` → PDF `/Subject`
|
|
13
|
-
- `dc:subject` → PDF `/Keywords`
|
|
10
|
+
- `dc:title` (rdf:Alt x-default) → PDF `/Title`
|
|
11
|
+
- `dc:creator` (rdf:Seq first li) → PDF `/Author`
|
|
12
|
+
- `dc:description` (rdf:Alt x-default) → PDF `/Subject`
|
|
13
|
+
- `dc:subject` (rdf:Bag joined) → PDF `/Keywords`
|
|
14
14
|
- `xmp:CreatorTool` → PDF `/Creator`
|
|
15
15
|
- `xmp:CreateDate` → PDF `/CreationDate`
|
|
16
16
|
- `xmp:ModifyDate` → PDF `/ModDate`
|
|
17
17
|
|
|
18
|
-
##
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
root "Description"
|
|
64
|
-
# …namespace-aware mappings once Lutaml supports them…
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# In Pipeline:
|
|
71
|
-
if @package.has_part?("META-INF/metadata.xml")
|
|
72
|
-
xmp = Idml::Parts::XmpMetadata.from_xml(
|
|
73
|
-
@package.read_part("META-INF/metadata.xml"),
|
|
74
|
-
)
|
|
75
|
-
writer.set_info(
|
|
76
|
-
Title: xmp.title,
|
|
77
|
-
Author: xmp.author,
|
|
78
|
-
Subject: xmp.subject,
|
|
79
|
-
Keywords: xmp.keywords,
|
|
80
|
-
Creator: xmp.creator_tool,
|
|
81
|
-
CreationDate: pdf_date(xmp.create_date),
|
|
82
|
-
ModDate: pdf_date(xmp.modify_date),
|
|
83
|
-
)
|
|
84
|
-
end
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
## Acceptance criteria (after blocker clears)
|
|
88
|
-
|
|
89
|
-
- [ ] Pipeline reads `META-INF/metadata.xml` when present.
|
|
90
|
-
- [ ] `XmpMetadata` typed model parses dc:title/creator/description/
|
|
91
|
-
subject plus xmp:CreatorTool/CreateDate/ModifyDate.
|
|
92
|
-
- [ ] Pipeline passes the parsed fields to `PdfrbWriter#set_info`.
|
|
93
|
-
- [ ] Existing Producer/CreationDate defaults still apply when XMP
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
XMP has multiple XML namespaces (`dc:`, `xmp:`, `pdf:`) inside one
|
|
21
|
+
`<rdf:Description>` element. Per Lutaml's namespace API, each
|
|
22
|
+
namespaced child is its own `Lutaml::Model::Serializable` subclass
|
|
23
|
+
carrying its own `namespace` declaration via a `Lutaml::Xml::Namespace`
|
|
24
|
+
class. The parent composes the children via `map_element`.
|
|
25
|
+
|
|
26
|
+
Layered models in `lib/idml/parts/xmp.rb`:
|
|
27
|
+
|
|
28
|
+
- `Xmp::RdfNamespace`, `DcNamespace`, `XmpNamespace`, `XmetaNamespace` —
|
|
29
|
+
`Lutaml::Xml::Namespace` subclasses with `uri` and `prefix_default`.
|
|
30
|
+
- `Xmp::Leaf` base + `Format`/`CreatorTool`/`CreateDate`/`ModifyDate`/
|
|
31
|
+
`MetadataDate` — single direct-value leaves.
|
|
32
|
+
- `Xmp::ListItem` (rdf:li), `Alt` (rdf:Alt with x-default lookup),
|
|
33
|
+
`Seq` (rdf:Seq), `Bag` (rdf:Bag) — RDF container types.
|
|
34
|
+
- `Xmp::Title`/`DcDescription`/`Creator`/`Subject` — dc: elements that
|
|
35
|
+
wrap the RDF containers.
|
|
36
|
+
- `XmpDescription` — single rdf:Description composing all the leaves
|
|
37
|
+
and containers above.
|
|
38
|
+
- `XmpRdf` — rdf:RDF with a collection of Descriptions plus merged
|
|
39
|
+
accessors (`title`, `author`, `keywords`, etc.) that pick the first
|
|
40
|
+
non-nil across Descriptions.
|
|
41
|
+
- `XmpMeta` — outer `<x:xmpmeta>` wrapper.
|
|
42
|
+
|
|
43
|
+
Pipeline threads this through `combined_metadata`, which starts from
|
|
44
|
+
the default Producer+CreationDate and overrides each field that the
|
|
45
|
+
XMP packet supplies.
|
|
46
|
+
|
|
47
|
+
## Verification
|
|
48
|
+
|
|
49
|
+
- `lib/idml/parts/xmp.rb` — typed XMP models.
|
|
50
|
+
- `lib/idml/render/pipeline.rb:193` — `combined_metadata` merge.
|
|
51
|
+
- `spec/idml/parts/xmp_meta_spec.rb` — 11 specs covering direct
|
|
52
|
+
elements, containers, missing fields, and the fixture.
|
|
53
|
+
- `spec/idml/render/render_pdfrb_pipeline_spec.rb` — integration spec
|
|
54
|
+
asserts `/Creator` from XMP lands in the PDF.
|
|
55
|
+
|
|
56
|
+
## Acceptance criteria
|
|
57
|
+
|
|
58
|
+
- [x] Pipeline reads `META-INF/metadata.xml` when present.
|
|
59
|
+
- [x] `XmpMeta` typed model parses dc:title/creator/description/subject
|
|
60
|
+
plus xmp:CreatorTool/CreateDate/ModifyDate.
|
|
61
|
+
- [x] Pipeline passes the parsed fields to `PdfrbWriter#set_info`.
|
|
62
|
+
- [x] Existing Producer/CreationDate defaults still apply when XMP
|
|
94
63
|
or individual fields are absent.
|
|
95
|
-
- [
|
|
64
|
+
- [x] Spec covers a synthetic XMP packet with all fields populated,
|
|
96
65
|
plus the no-metadata fallback.
|
|
97
|
-
|
|
98
|
-
## Dependencies
|
|
99
|
-
|
|
100
|
-
- Lutaml namespace API stable enough that
|
|
101
|
-
`map_element "format", namespace: <XmlNamespace>` works without
|
|
102
|
-
raising. Tracked at the Lutaml migration guide referenced in the
|
|
103
|
-
error message: `docs/_guides/xml-namespaces.adoc`.
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Idml
|
|
6
|
+
module Parts
|
|
7
|
+
# XMP (Extensible Metadata Platform) parser for IDML's
|
|
8
|
+
# `META-INF/metadata.xml`. The packet is RDF/XML with multiple
|
|
9
|
+
# namespaces (`dc:`, `xmp:`, `pdf:`, `xmpMM:`, etc.).
|
|
10
|
+
#
|
|
11
|
+
# Per Lutaml's namespace API, each namespaced element is a
|
|
12
|
+
# separate `Serializable` subclass that carries its own
|
|
13
|
+
# `namespace`. The parent `<rdf:Description>` composes the
|
|
14
|
+
# children via `map_element`, with the namespace binding handled
|
|
15
|
+
# on each child class.
|
|
16
|
+
module Xmp
|
|
17
|
+
class RdfNamespace < Lutaml::Xml::Namespace
|
|
18
|
+
uri "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
19
|
+
prefix_default "rdf"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class DcNamespace < Lutaml::Xml::Namespace
|
|
23
|
+
uri "http://purl.org/dc/elements/1.1/"
|
|
24
|
+
prefix_default "dc"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class XmpNamespace < Lutaml::Xml::Namespace
|
|
28
|
+
uri "http://ns.adobe.com/xap/1.0/"
|
|
29
|
+
prefix_default "xmp"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class XmetaNamespace < Lutaml::Xml::Namespace
|
|
33
|
+
uri "adobe:ns:meta/"
|
|
34
|
+
prefix_default "x"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Single direct-value element. Subclasses set the element name
|
|
38
|
+
# and namespace.
|
|
39
|
+
class Leaf < Lutaml::Model::Serializable
|
|
40
|
+
attribute :value, :string
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class Format < Leaf
|
|
44
|
+
xml do
|
|
45
|
+
root "format"
|
|
46
|
+
namespace DcNamespace
|
|
47
|
+
map_content to: :value
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class CreatorTool < Leaf
|
|
52
|
+
xml do
|
|
53
|
+
root "CreatorTool"
|
|
54
|
+
namespace XmpNamespace
|
|
55
|
+
map_content to: :value
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class CreateDate < Leaf
|
|
60
|
+
xml do
|
|
61
|
+
root "CreateDate"
|
|
62
|
+
namespace XmpNamespace
|
|
63
|
+
map_content to: :value
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class ModifyDate < Leaf
|
|
68
|
+
xml do
|
|
69
|
+
root "ModifyDate"
|
|
70
|
+
namespace XmpNamespace
|
|
71
|
+
map_content to: :value
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class MetadataDate < Leaf
|
|
76
|
+
xml do
|
|
77
|
+
root "MetadataDate"
|
|
78
|
+
namespace XmpNamespace
|
|
79
|
+
map_content to: :value
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# rdf:li element inside rdf:Alt / rdf:Seq / rdf:Bag containers.
|
|
84
|
+
class ListItem < Lutaml::Model::Serializable
|
|
85
|
+
attribute :lang, :string
|
|
86
|
+
attribute :value, :string
|
|
87
|
+
|
|
88
|
+
xml do
|
|
89
|
+
root "li"
|
|
90
|
+
namespace RdfNamespace
|
|
91
|
+
map_attribute "lang", to: :lang
|
|
92
|
+
map_content to: :value
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# rdf:Alt — alternative-language values. Used for dc:title and
|
|
97
|
+
# dc:description.
|
|
98
|
+
class Alt < Lutaml::Model::Serializable
|
|
99
|
+
attribute :items, ListItem, collection: true
|
|
100
|
+
|
|
101
|
+
xml do
|
|
102
|
+
root "Alt"
|
|
103
|
+
namespace RdfNamespace
|
|
104
|
+
map_element "li", to: :items
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def default_value
|
|
108
|
+
x_default = items.find { |i| i.lang == "x-default" }
|
|
109
|
+
(x_default || items.first)&.value
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# rdf:Seq — ordered sequence. Used for dc:creator.
|
|
114
|
+
class Seq < Lutaml::Model::Serializable
|
|
115
|
+
attribute :items, ListItem, collection: true
|
|
116
|
+
|
|
117
|
+
xml do
|
|
118
|
+
root "Seq"
|
|
119
|
+
namespace RdfNamespace
|
|
120
|
+
map_element "li", to: :items
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def values
|
|
124
|
+
items.filter_map(&:value)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# rdf:Bag — unordered set. Used for dc:subject.
|
|
129
|
+
class Bag < Lutaml::Model::Serializable
|
|
130
|
+
attribute :items, ListItem, collection: true
|
|
131
|
+
|
|
132
|
+
xml do
|
|
133
|
+
root "Bag"
|
|
134
|
+
namespace RdfNamespace
|
|
135
|
+
map_element "li", to: :items
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def values
|
|
139
|
+
items.filter_map(&:value)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Container-wrapped dc elements.
|
|
144
|
+
class Title < Lutaml::Model::Serializable
|
|
145
|
+
attribute :alt, Alt
|
|
146
|
+
|
|
147
|
+
xml do
|
|
148
|
+
root "title"
|
|
149
|
+
namespace DcNamespace
|
|
150
|
+
map_element "Alt", to: :alt
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def value
|
|
154
|
+
alt&.default_value
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
class DcDescription < Lutaml::Model::Serializable
|
|
159
|
+
attribute :alt, Alt
|
|
160
|
+
|
|
161
|
+
xml do
|
|
162
|
+
root "description"
|
|
163
|
+
namespace DcNamespace
|
|
164
|
+
map_element "Alt", to: :alt
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def value
|
|
168
|
+
alt&.default_value
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
class Creator < Lutaml::Model::Serializable
|
|
173
|
+
attribute :seq, Seq
|
|
174
|
+
|
|
175
|
+
xml do
|
|
176
|
+
root "creator"
|
|
177
|
+
namespace DcNamespace
|
|
178
|
+
map_element "Seq", to: :seq
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def values
|
|
182
|
+
seq&.values || []
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
class Subject < Lutaml::Model::Serializable
|
|
187
|
+
attribute :bag, Bag
|
|
188
|
+
|
|
189
|
+
xml do
|
|
190
|
+
root "subject"
|
|
191
|
+
namespace DcNamespace
|
|
192
|
+
map_element "Bag", to: :bag
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def values
|
|
196
|
+
bag&.values || []
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Single `<rdf:Description>` element from an XMP packet. Combines
|
|
202
|
+
# children from multiple namespaces (`dc:`, `xmp:`) by composing
|
|
203
|
+
# nested `Serializable` models — each child carries its own
|
|
204
|
+
# `namespace` declaration per Lutaml's namespace API.
|
|
205
|
+
class XmpDescription < Lutaml::Model::Serializable
|
|
206
|
+
attribute :format, Xmp::Format
|
|
207
|
+
attribute :title, Xmp::Title
|
|
208
|
+
attribute :creator, Xmp::Creator
|
|
209
|
+
attribute :description, Xmp::DcDescription
|
|
210
|
+
attribute :subject, Xmp::Subject
|
|
211
|
+
attribute :creator_tool, Xmp::CreatorTool
|
|
212
|
+
attribute :create_date, Xmp::CreateDate
|
|
213
|
+
attribute :modify_date, Xmp::ModifyDate
|
|
214
|
+
attribute :metadata_date, Xmp::MetadataDate
|
|
215
|
+
|
|
216
|
+
xml do
|
|
217
|
+
root "Description"
|
|
218
|
+
namespace Xmp::RdfNamespace
|
|
219
|
+
map_element "format", to: :format
|
|
220
|
+
map_element "title", to: :title
|
|
221
|
+
map_element "creator", to: :creator
|
|
222
|
+
map_element "description", to: :description
|
|
223
|
+
map_element "subject", to: :subject
|
|
224
|
+
map_element "CreatorTool", to: :creator_tool
|
|
225
|
+
map_element "CreateDate", to: :create_date
|
|
226
|
+
map_element "ModifyDate", to: :modify_date
|
|
227
|
+
map_element "MetadataDate", to: :metadata_date
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def author
|
|
231
|
+
creator&.values&.first
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def keywords
|
|
235
|
+
subject&.values&.join(", ")
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# `<rdf:RDF>` — collection of `<rdf:Description>` siblings inside
|
|
240
|
+
# an XMP packet. Per IDML's META-INF/metadata.xml, there is
|
|
241
|
+
# typically one Description per schema group.
|
|
242
|
+
class XmpRdf < Lutaml::Model::Serializable
|
|
243
|
+
attribute :descriptions, XmpDescription, collection: true
|
|
244
|
+
|
|
245
|
+
xml do
|
|
246
|
+
root "RDF"
|
|
247
|
+
namespace Xmp::RdfNamespace
|
|
248
|
+
map_element "Description", to: :descriptions
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Merged view across all Descriptions. Each attribute returns
|
|
252
|
+
# the first non-nil value across the descriptions.
|
|
253
|
+
def first_description
|
|
254
|
+
descriptions.first
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def title
|
|
258
|
+
descriptions.map(&:title).find(&:itself)&.value
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def author
|
|
262
|
+
descriptions.filter_map(&:author).first
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def subject
|
|
266
|
+
descriptions.filter_map(&:subject).find(&:itself)&.values
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def keywords
|
|
270
|
+
subject&.join(", ")
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def description
|
|
274
|
+
descriptions.map(&:description).find(&:itself)&.value
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def creator_tool
|
|
278
|
+
descriptions.filter_map(&:creator_tool).first&.value
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def create_date
|
|
282
|
+
descriptions.filter_map(&:create_date).first&.value
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def modify_date
|
|
286
|
+
descriptions.filter_map(&:modify_date).first&.value
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# `<x:xmpmeta>` — outer wrapper of an XMP packet.
|
|
291
|
+
class XmpMeta < Lutaml::Model::Serializable
|
|
292
|
+
attribute :rdf, XmpRdf
|
|
293
|
+
|
|
294
|
+
xml do
|
|
295
|
+
root "xmpmeta"
|
|
296
|
+
namespace Xmp::XmetaNamespace
|
|
297
|
+
map_element "RDF", to: :rdf
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def description
|
|
301
|
+
rdf&.first_description
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|
data/lib/idml/parts.rb
CHANGED
|
@@ -15,6 +15,10 @@ module Idml
|
|
|
15
15
|
autoload :Preferences, "idml/parts/preferences"
|
|
16
16
|
autoload :Tags, "idml/parts/tags"
|
|
17
17
|
autoload :Mapping, "idml/parts/mapping"
|
|
18
|
+
autoload :Xmp, "idml/parts/xmp"
|
|
19
|
+
autoload :XmpDescription, "idml/parts/xmp"
|
|
20
|
+
autoload :XmpRdf, "idml/parts/xmp"
|
|
21
|
+
autoload :XmpMeta, "idml/parts/xmp"
|
|
18
22
|
|
|
19
23
|
@registry = {}
|
|
20
24
|
|
data/lib/idml/render/pipeline.rb
CHANGED
|
@@ -21,7 +21,7 @@ module Idml
|
|
|
21
21
|
|
|
22
22
|
def call
|
|
23
23
|
writer = PdfrbWriter.new
|
|
24
|
-
writer.set_info(
|
|
24
|
+
writer.set_info(combined_metadata)
|
|
25
25
|
writer.enable_tagged if @tagged
|
|
26
26
|
layer_filter = LayerFilter.from_designmap(@package.designmap)
|
|
27
27
|
font_ref_resolver = FontReferenceResolver.build(@package)
|
|
@@ -190,6 +190,46 @@ module Idml
|
|
|
190
190
|
nil
|
|
191
191
|
end
|
|
192
192
|
|
|
193
|
+
def combined_metadata
|
|
194
|
+
defaults = default_metadata
|
|
195
|
+
xmp_metadata.each do |key, value|
|
|
196
|
+
next if value.nil? || value.empty?
|
|
197
|
+
|
|
198
|
+
defaults[key] = value
|
|
199
|
+
end
|
|
200
|
+
defaults
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def xmp_metadata
|
|
204
|
+
return {} unless @package.has_part?(XMP_PATH)
|
|
205
|
+
|
|
206
|
+
xml = @package.read_part(XMP_PATH)
|
|
207
|
+
meta = Parts::XmpMeta.from_xml(xml)
|
|
208
|
+
rdf = meta.rdf
|
|
209
|
+
return {} unless rdf
|
|
210
|
+
|
|
211
|
+
{
|
|
212
|
+
Title: rdf.title,
|
|
213
|
+
Author: rdf.author,
|
|
214
|
+
Subject: rdf.description,
|
|
215
|
+
Keywords: rdf.keywords,
|
|
216
|
+
Creator: rdf.creator_tool,
|
|
217
|
+
CreationDate: pdf_date_string(rdf.create_date),
|
|
218
|
+
ModDate: pdf_date_string(rdf.modify_date),
|
|
219
|
+
}
|
|
220
|
+
rescue StandardError
|
|
221
|
+
{}
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def pdf_date_string(iso8601)
|
|
225
|
+
return nil unless iso8601
|
|
226
|
+
|
|
227
|
+
time = Time.iso8601(iso8601)
|
|
228
|
+
pdf_date(time)
|
|
229
|
+
rescue ArgumentError
|
|
230
|
+
nil
|
|
231
|
+
end
|
|
232
|
+
|
|
193
233
|
def default_metadata
|
|
194
234
|
{
|
|
195
235
|
Producer: "idml gem v#{Idml::VERSION}",
|
|
@@ -200,6 +240,9 @@ module Idml
|
|
|
200
240
|
def pdf_date(time)
|
|
201
241
|
time.strftime("D:%Y%m%d%H%M%S+00'00'")
|
|
202
242
|
end
|
|
243
|
+
|
|
244
|
+
XMP_PATH = "META-INF/metadata.xml"
|
|
245
|
+
private_constant :XMP_PATH
|
|
203
246
|
end
|
|
204
247
|
end
|
|
205
248
|
end
|
data/lib/idml/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: idml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose
|
|
@@ -381,6 +381,7 @@ files:
|
|
|
381
381
|
- lib/idml/parts/style.rb
|
|
382
382
|
- lib/idml/parts/style_mapping.rb
|
|
383
383
|
- lib/idml/parts/tags.rb
|
|
384
|
+
- lib/idml/parts/xmp.rb
|
|
384
385
|
- lib/idml/render.rb
|
|
385
386
|
- lib/idml/render/blending.rb
|
|
386
387
|
- lib/idml/render/color_helper.rb
|