idml 0.4.0 → 0.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/Gemfile.lock +4 -4
- data/TODO.pdf/83-pipeline-decomposition.md +38 -0
- data/lib/idml/elements/character_style_range.rb +3 -0
- data/lib/idml/elements/hyperlink_text_source.rb +31 -0
- data/lib/idml/elements.rb +2 -0
- data/lib/idml/render/hyperlink_emitter.rb +6 -2
- data/lib/idml/render/metadata_builder.rb +73 -0
- data/lib/idml/render/pipeline.rb +6 -58
- data/lib/idml/render.rb +1 -0
- data/lib/idml/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d7defde95ef6dc98c93c16a27f09b93c17aaea161a6878b1b130b58b3d8ba9cd
|
|
4
|
+
data.tar.gz: 603a52445ffcb85e2e384e097d74810a302b045aecfaf4937af10d763c076c2d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: da8441f49894dcce6656d429357421ee101c59061738e73527b9dc574d8905adf68b8a2fb78a5cc6bd5a4b67d2f4774893d96c9608ea7e6b91d945a1189b8e20
|
|
7
|
+
data.tar.gz: 5251dd9d4c5bbb4003dc2f159a87a8d6ba1a9c1e78beacdbce37df366edf7cdd89a339b27edb470b91641db9ba387a2964e9857d39900476b57507d87470b873
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
idml (0.4.
|
|
4
|
+
idml (0.4.1)
|
|
5
5
|
bigdecimal
|
|
6
6
|
lutaml-model (~> 0.8.18)
|
|
7
7
|
pdfrb
|
|
@@ -11,7 +11,7 @@ PATH
|
|
|
11
11
|
PATH
|
|
12
12
|
remote: /Users/mulgogi/src/claricle/pdfrb
|
|
13
13
|
specs:
|
|
14
|
-
pdfrb (0.
|
|
14
|
+
pdfrb (0.6.0)
|
|
15
15
|
thor
|
|
16
16
|
|
|
17
17
|
GEM
|
|
@@ -183,7 +183,7 @@ CHECKSUMS
|
|
|
183
183
|
ffi (1.17.4-x86_64-darwin) sha256=aa70390523cf3235096cf64962b709b4cfbd5c082a2cb2ae714eb0fe2ccda496
|
|
184
184
|
ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
|
|
185
185
|
ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
|
|
186
|
-
idml (0.4.
|
|
186
|
+
idml (0.4.1)
|
|
187
187
|
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
|
|
188
188
|
language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
|
|
189
189
|
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
|
@@ -203,7 +203,7 @@ CHECKSUMS
|
|
|
203
203
|
paint (2.3.0) sha256=327d623e4038619d5bd99ae5db07973859cd78400c7f0329eea283cef8e83be5
|
|
204
204
|
parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
|
|
205
205
|
parser (3.3.12.0) sha256=21a6d7f755d5a24dfbdc6e6b772e4e879a52e7631a88bc5a3a134606052c9828
|
|
206
|
-
pdfrb (0.
|
|
206
|
+
pdfrb (0.6.0)
|
|
207
207
|
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
|
|
208
208
|
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
|
209
209
|
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# TODO PDF 83: Pipeline decomposition
|
|
2
|
+
|
|
3
|
+
## Status: DONE
|
|
4
|
+
|
|
5
|
+
## What was done
|
|
6
|
+
|
|
7
|
+
Extracted metadata assembly from `Pipeline` into a dedicated
|
|
8
|
+
`Render::MetadataBuilder` class. Pipeline now calls
|
|
9
|
+
`MetadataBuilder.new(@package).build` instead of inlining the XMP
|
|
10
|
+
parsing, date conversion, and defaults logic.
|
|
11
|
+
|
|
12
|
+
## Why
|
|
13
|
+
|
|
14
|
+
Pipeline had five private methods dedicated to metadata
|
|
15
|
+
(`combined_metadata`, `xmp_metadata`, `default_metadata`,
|
|
16
|
+
`pdf_date_string`, `pdf_date`). Each was a single responsibility
|
|
17
|
+
unrelated to the pipeline's core orchestration role. Moving them
|
|
18
|
+
into a focused class:
|
|
19
|
+
|
|
20
|
+
- Shrinks Pipeline by ~55 lines.
|
|
21
|
+
- Makes MetadataBuilder independently testable.
|
|
22
|
+
- Makes Pipeline's intent clearer: orchestrate, not parse.
|
|
23
|
+
- Lets future metadata sources (e.g., IDML document properties,
|
|
24
|
+
custom user metadata) plug in without touching Pipeline.
|
|
25
|
+
|
|
26
|
+
## Verification
|
|
27
|
+
|
|
28
|
+
- `lib/idml/render/metadata_builder.rb` — extracted class.
|
|
29
|
+
- `lib/idml/render/pipeline.rb:25` — single-call usage.
|
|
30
|
+
- `spec/idml/render/metadata_builder_spec.rb` — 5 specs covering
|
|
31
|
+
defaults, XMP extraction, and the no-XMP fallback.
|
|
32
|
+
|
|
33
|
+
## Acceptance criteria
|
|
34
|
+
|
|
35
|
+
- [x] MetadataBuilder extracted as a separate class.
|
|
36
|
+
- [x] Pipeline no longer contains XMP/date helpers.
|
|
37
|
+
- [x] Existing pipeline specs still pass.
|
|
38
|
+
- [x] MetadataBuilder has dedicated specs.
|
|
@@ -296,6 +296,8 @@ module Idml
|
|
|
296
296
|
attribute :character_style_range, Idml::Elements::CharacterStyleRange,
|
|
297
297
|
collection: true
|
|
298
298
|
attribute :content, Idml::Elements::Content, collection: true
|
|
299
|
+
attribute :hyperlink_text_source, Idml::Elements::HyperlinkTextSource,
|
|
300
|
+
collection: true
|
|
299
301
|
attribute :xml_element, Idml::Elements::XmlElement, collection: true
|
|
300
302
|
|
|
301
303
|
xml do
|
|
@@ -649,6 +651,7 @@ module Idml
|
|
|
649
651
|
map_attribute "UpperLevel", to: :upper_level
|
|
650
652
|
map_element "CharacterStyleRange", to: :character_style_range
|
|
651
653
|
map_element "Content", to: :content
|
|
654
|
+
map_element "HyperlinkTextSource", to: :hyperlink_text_source
|
|
652
655
|
map_element "XMLElement", to: :xml_element
|
|
653
656
|
end
|
|
654
657
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Idml
|
|
4
|
+
module Elements
|
|
5
|
+
# `<HyperlinkTextSource>` — source anchor of a hyperlink in a
|
|
6
|
+
# story. Per `HyperlinkTextSource_Object` in
|
|
7
|
+
# `reference-docs/schemas/package/Stories/Story.rnc`. The `Self`
|
|
8
|
+
# attribute is referenced by `<Hyperlink Source="...">` in the
|
|
9
|
+
# document designmap.
|
|
10
|
+
#
|
|
11
|
+
# Note on text range: IDML's HyperlinkTextSource wraps the
|
|
12
|
+
# character content it covers (it is a sibling of `<Content>`
|
|
13
|
+
# inside a `<CharacterStyleRange>`). The wrapper's position in
|
|
14
|
+
# the story flow determines the range — there is no explicit
|
|
15
|
+
# `StartIndex`/`EndIndex` on the element itself.
|
|
16
|
+
class HyperlinkTextSource < Lutaml::Model::Serializable
|
|
17
|
+
attribute :self_attr, :string
|
|
18
|
+
attribute :name, :string
|
|
19
|
+
attribute :hidden, :boolean
|
|
20
|
+
attribute :applied_character_style, :string
|
|
21
|
+
|
|
22
|
+
xml do
|
|
23
|
+
root "HyperlinkTextSource"
|
|
24
|
+
map_attribute "Self", to: :self_attr
|
|
25
|
+
map_attribute "Name", to: :name
|
|
26
|
+
map_attribute "Hidden", to: :hidden
|
|
27
|
+
map_attribute "AppliedCharacterStyle", to: :applied_character_style
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/idml/elements.rb
CHANGED
|
@@ -64,6 +64,8 @@ module Idml
|
|
|
64
64
|
autoload :Hyperlink, "idml/elements/hyperlink"
|
|
65
65
|
autoload :HyperlinkPageDestination,
|
|
66
66
|
"idml/elements/hyperlink_page_destination"
|
|
67
|
+
autoload :HyperlinkTextSource,
|
|
68
|
+
"idml/elements/hyperlink_text_source"
|
|
67
69
|
autoload :HyperlinkURLDestination,
|
|
68
70
|
"idml/elements/hyperlink_url_destination"
|
|
69
71
|
autoload :Image, "idml/elements/image"
|
|
@@ -12,11 +12,12 @@ module Idml
|
|
|
12
12
|
# the source's TextRange. Precise per-range rects require deeper
|
|
13
13
|
# integration with the text engine — see TODO 78.
|
|
14
14
|
class HyperlinkEmitter
|
|
15
|
-
def initialize(writer:, package:, page_height:)
|
|
15
|
+
def initialize(writer:, package:, page_height:, layer_filter: nil)
|
|
16
16
|
@writer = writer
|
|
17
17
|
@package = package
|
|
18
18
|
@resolver = HyperlinkResolver.new(package)
|
|
19
19
|
@page_height = page_height
|
|
20
|
+
@layer_filter = layer_filter
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
def emit_for(spread, page_index)
|
|
@@ -29,7 +30,10 @@ module Idml
|
|
|
29
30
|
|
|
30
31
|
def each_text_frame_on(spread)
|
|
31
32
|
spread.each_page_item do |item|
|
|
32
|
-
|
|
33
|
+
next unless item.is_a?(Idml::Elements::TextFrame)
|
|
34
|
+
next if @layer_filter && !@layer_filter.visible?(item)
|
|
35
|
+
|
|
36
|
+
yield item
|
|
33
37
|
end
|
|
34
38
|
end
|
|
35
39
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module Idml
|
|
6
|
+
module Render
|
|
7
|
+
# Builds the PDF Info-dict metadata hash by merging XMP-extracted
|
|
8
|
+
# values from `META-INF/metadata.xml` over sensible defaults.
|
|
9
|
+
# Extracted from `Pipeline` for SRP — Pipeline orchestrates
|
|
10
|
+
# rendering, this module owns metadata assembly.
|
|
11
|
+
class MetadataBuilder
|
|
12
|
+
XMP_PATH = "META-INF/metadata.xml"
|
|
13
|
+
|
|
14
|
+
def initialize(package)
|
|
15
|
+
@package = package
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Returns a hash with Producer/CreationDate defaults, overridden
|
|
19
|
+
# by any XMP-supplied fields (Title, Author, Subject, Keywords,
|
|
20
|
+
# Creator, CreationDate, ModDate). XMP dates are converted from
|
|
21
|
+
# ISO 8601 to PDF `D:YYYYMMDDHHmmss` format.
|
|
22
|
+
def build
|
|
23
|
+
defaults.merge(xmp_fields) { |_key, default, xmp| xmp || default }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def defaults
|
|
29
|
+
{
|
|
30
|
+
Producer: "idml gem v#{Idml::VERSION}",
|
|
31
|
+
CreationDate: pdf_date(Time.now.utc),
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def xmp_fields
|
|
36
|
+
return {} unless xmp_packet
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
Title: xmp_packet.title,
|
|
40
|
+
Author: xmp_packet.author,
|
|
41
|
+
Subject: xmp_packet.description,
|
|
42
|
+
Keywords: xmp_packet.keywords,
|
|
43
|
+
Creator: xmp_packet.creator_tool,
|
|
44
|
+
CreationDate: pdf_date_string(xmp_packet.create_date),
|
|
45
|
+
ModDate: pdf_date_string(xmp_packet.modify_date),
|
|
46
|
+
}.compact
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def xmp_packet
|
|
50
|
+
return nil unless @package.has_part?(XMP_PATH)
|
|
51
|
+
|
|
52
|
+
@xmp_packet ||= begin
|
|
53
|
+
xml = @package.read_part(XMP_PATH)
|
|
54
|
+
Parts::XmpMeta.from_xml(xml).rdf
|
|
55
|
+
rescue StandardError
|
|
56
|
+
nil
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def pdf_date_string(iso8601)
|
|
61
|
+
return nil unless iso8601
|
|
62
|
+
|
|
63
|
+
pdf_date(Time.iso8601(iso8601))
|
|
64
|
+
rescue ArgumentError
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def pdf_date(time)
|
|
69
|
+
time.strftime("D:%Y%m%d%H%M%S+00'00'")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
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
|
-
metadata =
|
|
24
|
+
metadata = MetadataBuilder.new(@package).build
|
|
25
25
|
writer.set_info(metadata)
|
|
26
26
|
writer.enable_tagged if @tagged
|
|
27
27
|
if pdfa_requested?
|
|
@@ -95,14 +95,16 @@ module Idml
|
|
|
95
95
|
page_height: dims[:height],
|
|
96
96
|
image_refs: image_refs,
|
|
97
97
|
page_index: current)
|
|
98
|
-
emit_hyperlinks(writer, spread, current)
|
|
98
|
+
emit_hyperlinks(writer, spread, current, layer_filter)
|
|
99
99
|
end
|
|
100
100
|
current
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
-
def emit_hyperlinks(writer, spread, page_index)
|
|
103
|
+
def emit_hyperlinks(writer, spread, page_index, layer_filter)
|
|
104
104
|
HyperlinkEmitter.new(writer: writer, package: @package,
|
|
105
|
-
page_height: DEFAULT_HEIGHT
|
|
105
|
+
page_height: DEFAULT_HEIGHT,
|
|
106
|
+
layer_filter: layer_filter)
|
|
107
|
+
.emit_for(spread, page_index)
|
|
106
108
|
rescue StandardError
|
|
107
109
|
nil
|
|
108
110
|
end
|
|
@@ -167,60 +169,6 @@ module Idml
|
|
|
167
169
|
end
|
|
168
170
|
nil
|
|
169
171
|
end
|
|
170
|
-
|
|
171
|
-
def combined_metadata
|
|
172
|
-
defaults = default_metadata
|
|
173
|
-
xmp_metadata.each do |key, value|
|
|
174
|
-
next if value.nil? || value.empty?
|
|
175
|
-
|
|
176
|
-
defaults[key] = value
|
|
177
|
-
end
|
|
178
|
-
defaults
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def xmp_metadata
|
|
182
|
-
return {} unless @package.has_part?(XMP_PATH)
|
|
183
|
-
|
|
184
|
-
xml = @package.read_part(XMP_PATH)
|
|
185
|
-
meta = Parts::XmpMeta.from_xml(xml)
|
|
186
|
-
rdf = meta.rdf
|
|
187
|
-
return {} unless rdf
|
|
188
|
-
|
|
189
|
-
{
|
|
190
|
-
Title: rdf.title,
|
|
191
|
-
Author: rdf.author,
|
|
192
|
-
Subject: rdf.description,
|
|
193
|
-
Keywords: rdf.keywords,
|
|
194
|
-
Creator: rdf.creator_tool,
|
|
195
|
-
CreationDate: pdf_date_string(rdf.create_date),
|
|
196
|
-
ModDate: pdf_date_string(rdf.modify_date),
|
|
197
|
-
}
|
|
198
|
-
rescue StandardError
|
|
199
|
-
{}
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
def pdf_date_string(iso8601)
|
|
203
|
-
return nil unless iso8601
|
|
204
|
-
|
|
205
|
-
time = Time.iso8601(iso8601)
|
|
206
|
-
pdf_date(time)
|
|
207
|
-
rescue ArgumentError
|
|
208
|
-
nil
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
def default_metadata
|
|
212
|
-
{
|
|
213
|
-
Producer: "idml gem v#{Idml::VERSION}",
|
|
214
|
-
CreationDate: pdf_date(Time.now.utc),
|
|
215
|
-
}
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
def pdf_date(time)
|
|
219
|
-
time.strftime("D:%Y%m%d%H%M%S+00'00'")
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
XMP_PATH = "META-INF/metadata.xml"
|
|
223
|
-
private_constant :XMP_PATH
|
|
224
172
|
end
|
|
225
173
|
end
|
|
226
174
|
end
|
data/lib/idml/render.rb
CHANGED
|
@@ -23,6 +23,7 @@ module Idml
|
|
|
23
23
|
autoload :BookmarkResolver, "#{__dir__}/render/bookmark_resolver"
|
|
24
24
|
autoload :HyperlinkResolver, "#{__dir__}/render/hyperlink_resolver"
|
|
25
25
|
autoload :HyperlinkEmitter, "#{__dir__}/render/hyperlink_emitter"
|
|
26
|
+
autoload :MetadataBuilder, "#{__dir__}/render/metadata_builder"
|
|
26
27
|
autoload :StoryThreader, "#{__dir__}/render/story_threader"
|
|
27
28
|
autoload :PdfrbWriter, "#{__dir__}/render/pdfrb_writer"
|
|
28
29
|
autoload :SpreadRenderer, "#{__dir__}/render/spread_renderer"
|
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.4.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose
|
|
@@ -209,6 +209,7 @@ files:
|
|
|
209
209
|
- TODO.pdf/80-paragraph-alignment.md
|
|
210
210
|
- TODO.pdf/81-pdfa-icc-output-intent.md
|
|
211
211
|
- TODO.pdf/82-table-cell-text.md
|
|
212
|
+
- TODO.pdf/83-pipeline-decomposition.md
|
|
212
213
|
- TODO.pdf/README.md
|
|
213
214
|
- exe/idml
|
|
214
215
|
- idml.gemspec
|
|
@@ -255,6 +256,7 @@ files:
|
|
|
255
256
|
- lib/idml/elements/group.rb
|
|
256
257
|
- lib/idml/elements/hyperlink.rb
|
|
257
258
|
- lib/idml/elements/hyperlink_page_destination.rb
|
|
259
|
+
- lib/idml/elements/hyperlink_text_source.rb
|
|
258
260
|
- lib/idml/elements/hyperlink_url_destination.rb
|
|
259
261
|
- lib/idml/elements/image.rb
|
|
260
262
|
- lib/idml/elements/ink.rb
|
|
@@ -390,6 +392,7 @@ files:
|
|
|
390
392
|
- lib/idml/render/image.rb
|
|
391
393
|
- lib/idml/render/image_collector.rb
|
|
392
394
|
- lib/idml/render/layer_filter.rb
|
|
395
|
+
- lib/idml/render/metadata_builder.rb
|
|
393
396
|
- lib/idml/render/page_item_renderer.rb
|
|
394
397
|
- lib/idml/render/pdfa_packet.rb
|
|
395
398
|
- lib/idml/render/pdfrb_writer.rb
|