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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6cd95e24b0c6610673ad2c95308314a391471fa3912d28a660b8381d46f7b81
4
- data.tar.gz: f33fdd9480345a9de8f8fa0e60536b87423978a09732031ab7a4587b99fae59e
3
+ metadata.gz: d7defde95ef6dc98c93c16a27f09b93c17aaea161a6878b1b130b58b3d8ba9cd
4
+ data.tar.gz: 603a52445ffcb85e2e384e097d74810a302b045aecfaf4937af10d763c076c2d
5
5
  SHA512:
6
- metadata.gz: 26f80da839299b0fad35c4380511bca3c91d61aabb29c8699f82a50ee7f7a02d5a9ae314643d596a5e0847f91dcbd7782da2bf73892004995578f8d129c52521
7
- data.tar.gz: d416cb234f968d95ed9a5f23fecf7ddbe19a171986e58a98f8eee1483a09db4588496a674bfbcfe52be077c2c8c117978b35314ff335e5498e6cc70367dc11de
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.0)
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.5.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.0)
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.5.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
- yield item if item.is_a?(Idml::Elements::TextFrame)
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
@@ -21,7 +21,7 @@ module Idml
21
21
 
22
22
  def call
23
23
  writer = PdfrbWriter.new
24
- metadata = combined_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).emit_for(spread, page_index)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Idml
4
- VERSION = "0.4.0"
4
+ VERSION = "0.4.1"
5
5
  end
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.0
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