idml 0.7.5 → 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 +4 -4
- data/TODO.pdf/116-applied-style-resolution.md +4 -1
- data/lib/idml/render/pipeline.rb +8 -4
- data/lib/idml/render/render_context.rb +1 -0
- data/lib/idml/render/renderers/text_frame_renderer.rb +2 -1
- data/lib/idml/render/spread_renderer.rb +3 -1
- data/lib/idml/render/style_lookup.rb +78 -0
- data/lib/idml/render/style_resolver.rb +94 -48
- data/lib/idml/render.rb +1 -0
- data/lib/idml/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bdf6a3aba5bbe199af39e5addabae082b795dfef1583dd14f33a9d7415086ead
|
|
4
|
+
data.tar.gz: eb1a78bd1a11fdc7500543b903b4d927114a96a70fea97a7e8b4c62db813451e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e47ca1b121cd27336a516f8b0cb863d5ad03edbdc43455bfa3efcb41807865a1dae4aec35ccbf380e003dc383d9f0d3704b42e23efa0d56c6925f316b68030c1
|
|
7
|
+
data.tar.gz: 3ac803ec48c5a0e2a2e3eec6f17845993a2a3194ddb8046bd5e8ea7225fcd8709abef22817684c12c51814b26a6ca9d3e07f2b4a735b519b9cb3d99e9eaaec5c
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# TODO PDF 116: AppliedParagraphStyle / AppliedCharacterStyle resolution from Resources/Styles.xml
|
|
2
2
|
|
|
3
|
-
## Status:
|
|
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.
|
|
4
7
|
|
|
5
8
|
## Problem
|
|
6
9
|
|
data/lib/idml/render/pipeline.rb
CHANGED
|
@@ -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,
|
|
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
|
|
|
@@ -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
|
|
126
|
-
space_after: psr
|
|
127
|
-
first_line_indent: psr
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
169
|
-
point_size: csr
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
|
175
|
-
capitalization: csr
|
|
176
|
-
position: csr
|
|
177
|
-
underline: csr
|
|
178
|
-
underline_offset: csr
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
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.
|
|
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-
|
|
11
|
+
date: 2026-08-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bigdecimal
|
|
@@ -469,6 +469,7 @@ files:
|
|
|
469
469
|
- lib/idml/render/stroke_style.rb
|
|
470
470
|
- lib/idml/render/structure_mapper.rb
|
|
471
471
|
- lib/idml/render/structure_tracker.rb
|
|
472
|
+
- lib/idml/render/style_lookup.rb
|
|
472
473
|
- lib/idml/render/style_resolver.rb
|
|
473
474
|
- lib/idml/text_engine.rb
|
|
474
475
|
- lib/idml/text_engine/cjk_layout.rb
|