idml 0.7.4 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f60e795f4325dcc4006a28915a18baf7069e21767694ca670c41f84db62d98d
4
- data.tar.gz: 4f1f4846d41050b518c8add338ce431951b45ea58891b2c56154b47e59478bc7
3
+ metadata.gz: bdf6a3aba5bbe199af39e5addabae082b795dfef1583dd14f33a9d7415086ead
4
+ data.tar.gz: eb1a78bd1a11fdc7500543b903b4d927114a96a70fea97a7e8b4c62db813451e
5
5
  SHA512:
6
- metadata.gz: bbf7caac497ce2ab01a8c35af7bad78a24b7a706db6baa582b7d416cae1ce4995f18a3903379c4e994ab49ca1f28efd33e162821dead95250ae8248c041d16cd
7
- data.tar.gz: ed5d2ae0abf40c0c6bf101d3c05ca35f59bc48aefed14366de7f5ac0f15979ff458ca7cf8493d012c4f6a78b0c1889815f987ea3dc9f24971f31ae34583f6fc4
6
+ metadata.gz: e47ca1b121cd27336a516f8b0cb863d5ad03edbdc43455bfa3efcb41807865a1dae4aec35ccbf380e003dc383d9f0d3704b42e23efa0d56c6925f316b68030c1
7
+ data.tar.gz: 3ac803ec48c5a0e2a2e3eec6f17845993a2a3194ddb8046bd5e8ea7225fcd8709abef22817684c12c51814b26a6ca9d3e07f2b4a735b519b9cb3d99e9eaaec5c
@@ -0,0 +1,65 @@
1
+ # TODO PDF 116: AppliedParagraphStyle / AppliedCharacterStyle resolution from Resources/Styles.xml
2
+
3
+ ## Status: DONE — `Render::StyleLookup` indexes ParagraphStyle and
4
+ CharacterStyle from Resources/Styles.xml by Self. StyleResolver
5
+ uses it to fill in formatting attributes that PSR/CSR elements don't
6
+ declare inline. PSR/CSR inline attributes override style values.
7
+
8
+ ## Problem
9
+
10
+ In real IDML documents, the `<ParagraphStyleRange>` element often
11
+ carries ONLY an `AppliedParagraphStyle` reference and NO inline
12
+ formatting attributes. The actual formatting (Justification,
13
+ SpaceBefore, PointSize, etc.) lives in the referenced
14
+ `<ParagraphStyle>` definition in `Resources/Styles.xml`.
15
+
16
+ Example from the sample-with-table-more fixture:
17
+ ```xml
18
+ <ParagraphStyleRange AppliedParagraphStyle="ParagraphStyle/$ID/NormalParagraphStyle">
19
+ <CharacterStyleRange AppliedCharacterStyle="CharacterStyle/$ID/[No character style]">
20
+ ...
21
+ </CharacterStyleRange>
22
+ </ParagraphStyleRange>
23
+ ```
24
+
25
+ The PSR has NO inline Justification, SpaceBefore, etc. Those values
26
+ are on `ParagraphStyle Self="ParagraphStyle/$ID/NormalParagraphStyle"`
27
+ in Styles.xml.
28
+
29
+ Today StyleResolver reads attributes directly from the PSR element.
30
+ When the PSR has only a style reference (common in real documents),
31
+ the renderer uses default values for everything.
32
+
33
+ ## Impact
34
+
35
+ For the `sample-with-table-more` fixture, every PSR uses
36
+ `NormalParagraphStyle` — which in InDesign maps to 12pt Regular with
37
+ left alignment. The renderer happens to produce approximately right
38
+ output because the defaults match. But for documents that use custom
39
+ paragraph styles (Heading 1 = 24pt Bold Centered, Body Text = 11pt
40
+ Justified with 6pt SpaceAfter), the output would be wrong.
41
+
42
+ ## What needs to happen
43
+
44
+ 1. `Render::StyleResolver` resolves `AppliedParagraphStyle` to a
45
+ `ParagraphStyle` from `Parts::Style` (Resources/Styles.xml).
46
+ 2. PSR inline attributes override the style's attributes (when both
47
+ are present — this is the IDML "local override" mechanism).
48
+ 3. Same for `AppliedCharacterStyle` → `CharacterStyle` resolution.
49
+ 4. The merged attributes become the Paragraph/StyledRun's values.
50
+
51
+ Architecture: `Parts::Style` already parses `<ParagraphStyle>` and
52
+ `<CharacterStyle>` entries. StyleResolver needs access to the style
53
+ part (via `context.package.styles` or similar).
54
+
55
+ ## Acceptance criteria
56
+
57
+ - [ ] PSR with only AppliedParagraphStyle resolves formatting from
58
+ the referenced ParagraphStyle.
59
+ - [ ] PSR inline attributes override style attributes.
60
+ - [ ] CharacterStyle resolution for CSR's AppliedCharacterStyle.
61
+ - [ ] Spec: fixture with custom paragraph style renders correctly.
62
+
63
+ ## Dependencies
64
+
65
+ - None — pure feature work on existing models.
@@ -1,10 +1,10 @@
1
- # TODO PDF 99: Table column widths from ColumnAttributes
1
+ # TODO PDF 99: Table column widths from Column elements
2
2
 
3
- ## Status: DONE — Table now models `column_count` and
4
- `single_column_width`; SchemaLayout uses SingleColumnWidth when
5
- declared, else falls back to even division. (IDML has no separate
6
- ColumnAttributes element column widths live on Table directly
7
- when uniform.)
3
+ ## Status: DONE — Table now models both `single_column_width`
4
+ (uniform) and per-column `<Column>` elements with individual
5
+ `SingleColumnWidth` values. SchemaLayout uses per-column widths
6
+ when `<Column>` children are present; falls back to uniform when
7
+ not.
8
8
 
9
9
  ## Problem
10
10
 
@@ -0,0 +1,45 @@
1
+ # frozen_string: true
2
+
3
+ module Idml
4
+ module Elements
5
+ # `<Column>` — a column definition within an IDML Table. Carries
6
+ # the column's width (SingleColumnWidth), type (HeaderColumn /
7
+ # BodyColumn), insets, and visual properties (fill, diagonal lines).
8
+ #
9
+ # Schema: `Column_Object` in
10
+ # `reference-docs/schemas/package/Stories/Story.rnc`. Real IDML
11
+ # tables have `<Column>` children alongside `<Row>` and `<Cell>`
12
+ # siblings. The `Name` attribute is the column index as a string
13
+ # (e.g., "0", "1", "2").
14
+ #
15
+ # The renderer's SchemaLayout reads `SingleColumnWidth` per column
16
+ # to compute per-column x positions and widths (replaces the
17
+ # even-division / uniform-width fallback).
18
+ class Column < Lutaml::Model::Serializable
19
+ attribute :self_attr, :string
20
+ attribute :name, :string
21
+ attribute :single_column_width, :float
22
+ attribute :column_type, :string
23
+ attribute :fill_color, :string
24
+ attribute :fill_tint, :float
25
+
26
+ xml do
27
+ root "Column"
28
+ map_attribute "Self", to: :self_attr
29
+ map_attribute "Name", to: :name
30
+ map_attribute "SingleColumnWidth", to: :single_column_width
31
+ map_attribute "ColumnType", to: :column_type
32
+ map_attribute "FillColor", to: :fill_color
33
+ map_attribute "FillTint", to: :fill_tint
34
+ end
35
+
36
+ # Column Name is a string integer (e.g., "0", "1").
37
+ # Returns the integer index or nil if Name is missing/invalid.
38
+ def index
39
+ return nil unless name
40
+
41
+ Integer(name, 10, exception: false) # rubocop:disable Style/RescueModifier
42
+ end
43
+ end
44
+ end
45
+ end
@@ -46,6 +46,7 @@ module Idml
46
46
  attribute :table_row, Idml::Elements::TableRow, collection: true
47
47
  attribute :cell, Idml::Elements::Cell, collection: true
48
48
  attribute :row, Idml::Elements::Row, collection: true
49
+ attribute :column, Idml::Elements::Column, collection: true
49
50
  attribute :properties, Idml::Elements::Properties, collection: true
50
51
 
51
52
  xml do
@@ -77,6 +78,7 @@ module Idml
77
78
  map_element "TableRow", to: :table_row
78
79
  map_element "Cell", to: :cell
79
80
  map_element "Row", to: :row
81
+ map_element "Column", to: :column
80
82
  map_element "Properties", to: :properties
81
83
  end
82
84
 
data/lib/idml/elements.rb CHANGED
@@ -17,6 +17,7 @@ module Idml
17
17
  autoload :ButtonPreference, "idml/elements/pref_button_preference"
18
18
  autoload :Bookmark, "idml/elements/bookmark"
19
19
  autoload :Cell, "idml/elements/cell"
20
+ autoload :Column, "idml/elements/column"
20
21
  autoload :Condition, "idml/elements/condition"
21
22
  autoload :CellStyle, "idml/elements/cell_style"
22
23
  autoload :CellStyleGroup, "idml/elements/cell_style_group"
@@ -39,6 +39,7 @@ module Idml
39
39
  position_tracker = PositionTracker.new
40
40
  layer_filter = LayerFilter.from_designmap(@package.designmap)
41
41
  condition_filter = ConditionFilter.from_designmap(@package.designmap)
42
+ style_lookup = StyleLookup.from_package(@package)
42
43
  font_ref_resolver = FontReferenceResolver.build(@package)
43
44
  font_setup = FontSetup.new(package: @package,
44
45
  font_search_paths: @font_search_paths)
@@ -52,7 +53,7 @@ module Idml
52
53
  font_ref_resolver, font_resource,
53
54
  font_metrics, font_map, structure,
54
55
  position_tracker, condition_filter,
55
- page_index)
56
+ style_lookup, page_index)
56
57
  end
57
58
 
58
59
  structure.flush(writer)
@@ -92,14 +93,16 @@ module Idml
92
93
 
93
94
  def render_spread(writer, spread, layer_filter, font_ref_resolver,
94
95
  font_resource, font_metrics, font_map, structure,
95
- position_tracker, condition_filter, page_offset)
96
+ position_tracker, condition_filter, style_lookup,
97
+ page_offset)
96
98
  pages = spread.spread.flat_map(&:page)
97
99
  image_refs = ImageCollector.new(writer: writer,
98
100
  base_dir: File.dirname(@package.path),
99
101
  page_height: DEFAULT_HEIGHT).collect(spread)
100
102
  renderer = build_renderer(layer_filter, font_ref_resolver,
101
103
  font_resource, font_metrics, font_map,
102
- structure, position_tracker, condition_filter)
104
+ structure, position_tracker, condition_filter,
105
+ style_lookup)
103
106
  current = page_offset
104
107
 
105
108
  pages.each do |page|
@@ -129,7 +132,7 @@ module Idml
129
132
 
130
133
  def build_renderer(layer_filter, font_ref_resolver, font_resource,
131
134
  font_metrics, font_map, structure, position_tracker,
132
- condition_filter)
135
+ condition_filter, style_lookup)
133
136
  SpreadRenderer.new(
134
137
  font_metrics: font_metrics,
135
138
  font_ps_name: font_resource,
@@ -140,6 +143,7 @@ module Idml
140
143
  structure: structure,
141
144
  position_tracker: position_tracker,
142
145
  condition_filter: condition_filter,
146
+ style_lookup: style_lookup,
143
147
  )
144
148
  end
145
149
 
@@ -22,6 +22,7 @@ module Idml
22
22
  :position_tracker,
23
23
  :chain_controller,
24
24
  :condition_filter,
25
+ :style_lookup,
25
26
  keyword_init: true,
26
27
  )
27
28
  end
@@ -243,7 +243,8 @@ module Idml
243
243
  mark_covered(covered, col_idx, row_idx, col_span, row_span)
244
244
 
245
245
  yield cell_x(col_idx), cell_y(row_idx, col_span, row_span),
246
- cell_w(col_span), cell_h(row_idx, row_span), cell
246
+ cell_w(col_idx, col_span), cell_h(row_idx, row_span),
247
+ cell
247
248
  end
248
249
  end
249
250
  end
@@ -286,7 +287,7 @@ module Idml
286
287
  private :mark_covered
287
288
 
288
289
  def cell_x(col)
289
- box[:x] + (col * per_col_width)
290
+ box[:x] + cumulative_col_width(col)
290
291
  end
291
292
 
292
293
  # PDF-coordinate bottom y of the cell's rectangle. For a
@@ -297,9 +298,11 @@ module Idml
297
298
  box[:y] + total_height - cumulative_height(last_row + 1)
298
299
  end
299
300
 
300
- # Width covered by `span` columns starting at the cell's col.
301
- def cell_w(span)
302
- per_col_width * (span || 1)
301
+ # Width covered by `span` columns starting at `col`. Uses
302
+ # per-column widths from Column elements when available;
303
+ # falls back to uniform width when not.
304
+ def cell_w(col, span)
305
+ span.to_i.times.sum { |i| column_width_at(col + i) }
303
306
  end
304
307
 
305
308
  # Total height covered by `span` rows starting at `row_idx`.
@@ -318,6 +321,41 @@ module Idml
318
321
  end
319
322
  end
320
323
 
324
+ # Returns per-column widths from Column elements when
325
+ # present; empty array otherwise. When non-empty, the
326
+ # renderer uses per-column widths for x positions and
327
+ # span widths instead of uniform per_col_width.
328
+ def per_column_widths
329
+ return @per_column_widths if defined?(@per_column_widths)
330
+
331
+ cols = table.column
332
+ @per_column_widths = if cols.empty?
333
+ []
334
+ else
335
+ cols.filter_map do |col|
336
+ w = col.single_column_width
337
+ w&.positive? ? w : nil
338
+ end
339
+ end
340
+ end
341
+
342
+ # Width of a single column at index `idx`. Uses per-column
343
+ # widths when available; falls back to uniform.
344
+ def column_width_at(idx)
345
+ return per_col_width if per_column_widths.empty?
346
+ return per_col_width unless idx.between?(0, per_column_widths.length - 1)
347
+
348
+ per_column_widths[idx]
349
+ end
350
+
351
+ # Cumulative width from column 0 up to (but not including)
352
+ # column `col`. Used for x positioning.
353
+ def cumulative_col_width(col)
354
+ return col * per_col_width if per_column_widths.empty?
355
+
356
+ col.times.sum { |i| column_width_at(i) }
357
+ end
358
+
321
359
  def row_height(idx)
322
360
  return 0.0 unless idx.between?(0, row_count - 1)
323
361
 
@@ -71,7 +71,8 @@ module Idml
71
71
 
72
72
  def self.fresh_state(story, context)
73
73
  paragraphs = StyleResolver.extract_paragraphs(
74
- story, condition_filter: context.condition_filter
74
+ story, condition_filter: context.condition_filter,
75
+ style_lookup: context.style_lookup
75
76
  )
76
77
  return nil if paragraphs.empty?
77
78
 
@@ -8,7 +8,7 @@ module Idml
8
8
  package: nil, layer_filter: LayerFilter::EXCLUDE_NONE,
9
9
  font_ref_resolver: nil, structure: nil,
10
10
  position_tracker: nil, font_map: {},
11
- condition_filter: nil)
11
+ condition_filter: nil, style_lookup: nil)
12
12
  @font_metrics = font_metrics
13
13
  @font_ps_name = font_ps_name
14
14
  @package = package
@@ -18,6 +18,7 @@ module Idml
18
18
  @position_tracker = position_tracker
19
19
  @font_map = font_map
20
20
  @condition_filter = condition_filter
21
+ @style_lookup = style_lookup
21
22
  end
22
23
 
23
24
  def render(canvas, spread, page_width:, page_height:, image_refs: [],
@@ -37,6 +38,7 @@ module Idml
37
38
  position_tracker: @position_tracker,
38
39
  chain_controller: StoryChainController.new,
39
40
  condition_filter: @condition_filter,
41
+ style_lookup: @style_lookup,
40
42
  }
41
43
 
42
44
  canvas.save_graphics_state do
@@ -0,0 +1,78 @@
1
+ # frozen_string: true
2
+
3
+ module Idml
4
+ module Render
5
+ # Resolves `AppliedParagraphStyle` and `AppliedCharacterStyle`
6
+ # references to their style definitions in Resources/Styles.xml.
7
+ # Built once per render pass from `Parts::Style`; StyleResolver
8
+ # consults it to fill in formatting attributes that the PSR/CSR
9
+ # element doesn't declare inline.
10
+ #
11
+ # Merge semantics: PSR/CSR inline attributes override style
12
+ # attributes. When the PSR/CSR declares a value (non-nil), that
13
+ # wins. When it doesn't, the style definition's value is used
14
+ # as a default. When neither declares a value, the attribute
15
+ # remains nil and the renderer applies its own default.
16
+ class StyleLookup
17
+ def self.from_package(package)
18
+ styles = package&.style
19
+ return new({}, {}) unless styles
20
+
21
+ para_styles = index_styles(styles.paragraph_style)
22
+ char_styles = index_styles(styles.character_style)
23
+ new(para_styles, char_styles)
24
+ end
25
+
26
+ def initialize(para_styles, char_styles)
27
+ @para_styles = para_styles
28
+ @char_styles = char_styles
29
+ end
30
+
31
+ # Returns the ParagraphStyle referenced by `self_ref`, or nil.
32
+ # `self_ref` is the PSR's AppliedParagraphStyle value (e.g.,
33
+ # "ParagraphStyle/$ID/NormalParagraphStyle").
34
+ def paragraph_style(self_ref)
35
+ return nil unless self_ref
36
+
37
+ @para_styles[self_ref]
38
+ end
39
+
40
+ # Returns the CharacterStyle referenced by `self_ref`, or nil.
41
+ def character_style(self_ref)
42
+ return nil unless self_ref
43
+
44
+ @char_styles[self_ref]
45
+ end
46
+
47
+ # Returns the value of `attr_name` from the PSR, or from the
48
+ # referenced ParagraphStyle when the PSR doesn't declare it.
49
+ # Uses `public_send` (not `send`) to stay within public API
50
+ # boundaries — the attribute readers generated by lutaml-model
51
+ # are public.
52
+ def resolve_para_attr(psr, attr_name)
53
+ psr_value = psr.public_send(attr_name)
54
+ return psr_value unless psr_value.nil?
55
+
56
+ style = paragraph_style(psr.applied_paragraph_style)
57
+ style&.public_send(attr_name)
58
+ end
59
+
60
+ # Returns the value of `attr_name` from the CSR, or from the
61
+ # referenced CharacterStyle when the CSR doesn't declare it.
62
+ def resolve_char_attr(csr, attr_name)
63
+ csr_value = csr.public_send(attr_name)
64
+ return csr_value unless csr_value.nil?
65
+
66
+ style = character_style(csr.applied_character_style)
67
+ style&.public_send(attr_name)
68
+ end
69
+
70
+ def self.index_styles(collection)
71
+ collection.each_with_object({}) do |style, hash|
72
+ hash[style.self_attr] = style if style.self_attr
73
+ end
74
+ end
75
+ private_class_method :index_styles
76
+ end
77
+ end
78
+ end
@@ -110,53 +110,74 @@ module Idml
110
110
  # its runs and carries the paragraph-level attributes from the
111
111
  # owning PSR. Empty paragraphs (no runs after filtering) are
112
112
  # skipped.
113
- def self.extract_paragraphs(story, condition_filter: nil)
113
+ def self.extract_paragraphs(story, condition_filter: nil,
114
+ style_lookup: nil)
114
115
  return [] unless story&.inner
115
116
 
116
117
  story.inner.paragraph_style_range.filter_map do |psr|
117
118
  next unless condition_visible?(psr, condition_filter)
118
119
 
119
- runs = csr_runs(psr, condition_filter: condition_filter)
120
+ runs = csr_runs(psr, condition_filter: condition_filter,
121
+ style_lookup: style_lookup)
120
122
  next if runs.empty?
121
123
 
122
124
  paragraph = Paragraph.new(
123
125
  runs: runs,
124
126
  alignment: runs.first.alignment,
125
- space_before: psr.space_before,
126
- space_after: psr.space_after,
127
- first_line_indent: psr.first_line_indent,
128
- left_indent: psr.left_indent,
129
- right_indent: psr.right_indent,
130
- auto_leading: psr.auto_leading,
131
- drop_cap_lines: psr.drop_cap_lines,
132
- drop_cap_characters: psr.drop_cap_characters,
133
- bullets_and_numbering_list_type: psr.bullets_and_numbering_list_type,
134
- bullet_character_value: psr.bullet_character_value,
135
- bullets_text_after: psr.bullets_text_after,
136
- numbering_expression: psr.numbering_expression,
137
- rule_above: psr.rule_above,
138
- rule_above_line_weight: psr.rule_above_line_weight,
139
- rule_above_color: psr.stroke_color,
140
- rule_above_tint: psr.rule_above_tint,
141
- rule_above_offset: psr.rule_above_offset,
142
- rule_above_left_indent: psr.rule_above_left_indent,
143
- rule_above_right_indent: psr.rule_above_right_indent,
144
- rule_above_width: psr.rule_above_width,
145
- rule_below: psr.rule_below,
146
- rule_below_line_weight: psr.rule_below_line_weight,
147
- rule_below_color: psr.stroke_color,
148
- rule_below_tint: psr.rule_below_tint,
149
- rule_below_offset: psr.rule_below_offset,
150
- rule_below_left_indent: psr.rule_below_left_indent,
151
- rule_below_right_indent: psr.rule_below_right_indent,
152
- rule_below_width: psr.rule_below_width,
127
+ space_before: resolve_attr(style_lookup, psr, :space_before),
128
+ space_after: resolve_attr(style_lookup, psr, :space_after),
129
+ first_line_indent: resolve_attr(style_lookup, psr,
130
+ :first_line_indent),
131
+ left_indent: resolve_attr(style_lookup, psr, :left_indent),
132
+ right_indent: resolve_attr(style_lookup, psr, :right_indent),
133
+ auto_leading: resolve_attr(style_lookup, psr, :auto_leading),
134
+ drop_cap_lines: resolve_attr(style_lookup, psr, :drop_cap_lines),
135
+ drop_cap_characters: resolve_attr(style_lookup, psr,
136
+ :drop_cap_characters),
137
+ bullets_and_numbering_list_type: resolve_attr(
138
+ style_lookup, psr, :bullets_and_numbering_list_type
139
+ ),
140
+ bullet_character_value: resolve_attr(style_lookup, psr,
141
+ :bullet_character_value),
142
+ bullets_text_after: resolve_attr(style_lookup, psr,
143
+ :bullets_text_after),
144
+ numbering_expression: resolve_attr(style_lookup, psr,
145
+ :numbering_expression),
146
+ rule_above: resolve_attr(style_lookup, psr, :rule_above),
147
+ rule_above_line_weight: resolve_attr(style_lookup, psr,
148
+ :rule_above_line_weight),
149
+ rule_above_color: resolve_attr(style_lookup, psr, :stroke_color),
150
+ rule_above_tint: resolve_attr(style_lookup, psr,
151
+ :rule_above_tint),
152
+ rule_above_offset: resolve_attr(style_lookup, psr,
153
+ :rule_above_offset),
154
+ rule_above_left_indent: resolve_attr(style_lookup, psr,
155
+ :rule_above_left_indent),
156
+ rule_above_right_indent: resolve_attr(style_lookup, psr,
157
+ :rule_above_right_indent),
158
+ rule_above_width: resolve_attr(style_lookup, psr,
159
+ :rule_above_width),
160
+ rule_below: resolve_attr(style_lookup, psr, :rule_below),
161
+ rule_below_line_weight: resolve_attr(style_lookup, psr,
162
+ :rule_below_line_weight),
163
+ rule_below_color: resolve_attr(style_lookup, psr, :stroke_color),
164
+ rule_below_tint: resolve_attr(style_lookup, psr,
165
+ :rule_below_tint),
166
+ rule_below_offset: resolve_attr(style_lookup, psr,
167
+ :rule_below_offset),
168
+ rule_below_left_indent: resolve_attr(style_lookup, psr,
169
+ :rule_below_left_indent),
170
+ rule_below_right_indent: resolve_attr(style_lookup, psr,
171
+ :rule_below_right_indent),
172
+ rule_below_width: resolve_attr(style_lookup, psr,
173
+ :rule_below_width),
153
174
  )
154
175
  prepend_list_marker(paragraph)
155
176
  paragraph
156
177
  end
157
178
  end
158
179
 
159
- def self.csr_runs(psr, condition_filter: nil)
180
+ def self.csr_runs(psr, condition_filter: nil, style_lookup: nil)
160
181
  psr.character_style_range.filter_map do |csr|
161
182
  next unless condition_visible?(csr, condition_filter)
162
183
 
@@ -165,29 +186,54 @@ module Idml
165
186
 
166
187
  StyledRun.new(
167
188
  text: text,
168
- font_style: csr.font_style,
169
- point_size: csr.point_size || DEFAULT_POINT_SIZE,
170
- fill_color: csr.fill_color,
171
- fill_tint: csr.fill_tint,
172
- applied_font: csr.applied_font,
189
+ font_style: resolve_csr(style_lookup, csr, :font_style),
190
+ point_size: resolve_csr(style_lookup, csr, :point_size) ||
191
+ DEFAULT_POINT_SIZE,
192
+ fill_color: resolve_csr(style_lookup, csr, :fill_color),
193
+ fill_tint: resolve_csr(style_lookup, csr, :fill_tint),
194
+ applied_font: resolve_csr(style_lookup, csr, :applied_font),
173
195
  alignment: alignment_for(csr),
174
- tracking: csr.tracking,
175
- capitalization: csr.capitalization,
176
- position: csr.position,
177
- underline: csr.underline,
178
- underline_offset: csr.underline_offset,
179
- underline_weight: csr.underline_weight,
180
- strike_thru: csr.strike_thru,
181
- strike_through_offset: csr.strike_through_offset,
182
- strike_through_weight: csr.strike_through_weight,
183
- horizontal_scale: csr.horizontal_scale,
184
- vertical_scale: csr.vertical_scale,
185
- baseline_shift: csr.baseline_shift,
196
+ tracking: resolve_csr(style_lookup, csr, :tracking),
197
+ capitalization: resolve_csr(style_lookup, csr, :capitalization),
198
+ position: resolve_csr(style_lookup, csr, :position),
199
+ underline: resolve_csr(style_lookup, csr, :underline),
200
+ underline_offset: resolve_csr(style_lookup, csr,
201
+ :underline_offset),
202
+ underline_weight: resolve_csr(style_lookup, csr,
203
+ :underline_weight),
204
+ strike_thru: resolve_csr(style_lookup, csr, :strike_thru),
205
+ strike_through_offset: resolve_csr(style_lookup, csr,
206
+ :strike_through_offset),
207
+ strike_through_weight: resolve_csr(style_lookup, csr,
208
+ :strike_through_weight),
209
+ horizontal_scale: resolve_csr(style_lookup, csr,
210
+ :horizontal_scale),
211
+ vertical_scale: resolve_csr(style_lookup, csr, :vertical_scale),
212
+ baseline_shift: resolve_csr(style_lookup, csr, :baseline_shift),
186
213
  )
187
214
  end
188
215
  end
189
216
  private_class_method :csr_runs
190
217
 
218
+ # Resolves a paragraph-level attribute from PSR or its
219
+ # referenced ParagraphStyle. When style_lookup is nil,
220
+ # falls back to reading the PSR directly (backward compat).
221
+ def self.resolve_attr(style_lookup, psr, attr_name)
222
+ return psr.public_send(attr_name) unless style_lookup
223
+
224
+ style_lookup.resolve_para_attr(psr, attr_name)
225
+ end
226
+ private_class_method :resolve_attr
227
+
228
+ # Resolves a character-level attribute from CSR or its
229
+ # referenced CharacterStyle.
230
+ def self.resolve_csr(style_lookup, csr, attr_name)
231
+ return csr.public_send(attr_name) unless style_lookup
232
+
233
+ style_lookup.resolve_char_attr(csr, attr_name)
234
+ end
235
+ private_class_method :resolve_csr
236
+
191
237
  # Drops the PSR/CSR when any of its AppliedConditions
192
238
  # references a hidden Condition. Returns true when no filter
193
239
  # is supplied (caller doesn't care about conditions).
data/lib/idml/render.rb CHANGED
@@ -8,6 +8,7 @@ module Idml
8
8
  autoload :Image, "#{__dir__}/render/image"
9
9
  autoload :ColorResolver, "#{__dir__}/render/color_resolver"
10
10
  autoload :StyleResolver, "#{__dir__}/render/style_resolver"
11
+ autoload :StyleLookup, "#{__dir__}/render/style_lookup"
11
12
  autoload :FontReferenceResolver,
12
13
  "#{__dir__}/render/font_reference_resolver"
13
14
  autoload :GradientResolver, "#{__dir__}/render/gradient_resolver"
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.7.4"
4
+ VERSION = "0.8.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: idml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.4
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-08-09 00:00:00.000000000 Z
11
+ date: 2026-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bigdecimal
@@ -172,6 +172,7 @@ files:
172
172
  - TODO.pdf/113-multi-column-text-frames.md
173
173
  - TODO.pdf/114-text-wrap-around-shapes.md
174
174
  - TODO.pdf/115-bullet-numbered-lists.md
175
+ - TODO.pdf/116-applied-style-resolution.md
175
176
  - TODO.pdf/12-idml-to-pdf-pipeline.md
176
177
  - TODO.pdf/13-cjk-text-layout.md
177
178
  - TODO.pdf/14-page-item-element-models.md
@@ -280,6 +281,7 @@ files:
280
281
  - lib/idml/elements/character_style_group.rb
281
282
  - lib/idml/elements/character_style_range.rb
282
283
  - lib/idml/elements/color.rb
284
+ - lib/idml/elements/column.rb
283
285
  - lib/idml/elements/condition.rb
284
286
  - lib/idml/elements/content.rb
285
287
  - lib/idml/elements/content_transparency_setting.rb
@@ -467,6 +469,7 @@ files:
467
469
  - lib/idml/render/stroke_style.rb
468
470
  - lib/idml/render/structure_mapper.rb
469
471
  - lib/idml/render/structure_tracker.rb
472
+ - lib/idml/render/style_lookup.rb
470
473
  - lib/idml/render/style_resolver.rb
471
474
  - lib/idml/text_engine.rb
472
475
  - lib/idml/text_engine/cjk_layout.rb