idml 0.8.9 → 0.9.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/101-paragraph-rules-borders.md +36 -39
- data/lib/idml/elements/character_style_range.rb +2 -0
- data/lib/idml/elements/paragraph_style_range.rb +2 -0
- data/lib/idml/elements/properties.rb +16 -3
- data/lib/idml/elements/typed_value.rb +20 -0
- data/lib/idml/elements.rb +1 -0
- data/lib/idml/render/paragraph_rules.rb +125 -0
- data/lib/idml/render/renderers/text_frame_renderer.rb +24 -0
- data/lib/idml/render/style_resolver.rb +84 -2
- data/lib/idml/text_engine/measurement.rb +40 -0
- data/lib/idml/text_engine.rb +2 -1
- data/lib/idml/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ff487a296f9cbd5b926b963c7acb7d4864d43060ffed5b1ec7438a75bac1446a
|
|
4
|
+
data.tar.gz: f4926fe3add1833a954628aace986839f101722deafde891783c54afdd509a58
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 043ed38d8ebdcf1af8506e83d8548b6590d923f0b4b1ef01687dca5c7528bc5d9a16c79c67e06dc626abe70334afd72882aefac2f15f8253c71d56bc40224274
|
|
7
|
+
data.tar.gz: 0fefb80560e396290585e3677ca395abd1096511b78321c7ff05ff3add4b5bf0242a4c21ea3321244b7da5290651e369b376696400dc19429ecc547b1c534842
|
|
@@ -1,47 +1,44 @@
|
|
|
1
1
|
# TODO PDF 101: Paragraph borders and rules (RuleAbove, RuleBelow, ParagraphBorder)
|
|
2
2
|
|
|
3
|
-
## Status:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
1. After laying out a paragraph, emit RuleAbove as a horizontal line
|
|
33
|
-
above the first line, with the rule's weight/tint/color/offset/indent.
|
|
34
|
-
2. Emit RuleBelow similarly below the last line.
|
|
35
|
-
3. Optionally emit ParagraphBorder as a rectangle around the paragraph.
|
|
36
|
-
4. Optionally emit ParagraphShading as a fill behind the paragraph.
|
|
3
|
+
## Status: COMPLETE — implemented 2026-08-17
|
|
4
|
+
|
|
5
|
+
## What was built
|
|
6
|
+
|
|
7
|
+
- RuleAbove / RuleBelow (earlier milestone): horizontal rules at
|
|
8
|
+
paragraph top/bottom with weight / tint / offset / indent /
|
|
9
|
+
TextWidth-ColumnWidth modes, via `Render::ParagraphRules`.
|
|
10
|
+
- Rule colors now resolve from `Properties > RuleAboveColor` /
|
|
11
|
+
`RuleBelowColor` (typed value elements) with StrokeColor fallback —
|
|
12
|
+
modeled via `Elements::TypedValue` + Properties mapping, PSR/CSR
|
|
13
|
+
now carry `properties` collections.
|
|
14
|
+
- ParagraphShading: fill rect behind the paragraph block. Color from
|
|
15
|
+
`Properties > ParagraphShadingColor`, tint scaling, ParagraphShading
|
|
16
|
+
width mode (ColumnWidth default / TextWidth), four outward offsets.
|
|
17
|
+
`TextEngine::Measurement` pre-measures the paragraph's height so
|
|
18
|
+
the rect is known before the lines are laid out (clamped to the
|
|
19
|
+
frame's bottom limit when the paragraph splits).
|
|
20
|
+
- ParagraphBorder: per-side strokes around the same block rect, each
|
|
21
|
+
side with its own line weight and the border offsets/tint/color
|
|
22
|
+
(Properties > ParagraphBorderColor). Shading and border share one
|
|
23
|
+
extent so they always agree.
|
|
24
|
+
|
|
25
|
+
## Known limitations
|
|
26
|
+
|
|
27
|
+
- Border corner radii and gap colors are parsed on the model but not
|
|
28
|
+
rendered; MergeConsecutiveParaBorders (merged rects across like-
|
|
29
|
+
bordered paragraphs) is not modeled.
|
|
30
|
+
- DisplayIfSplits: when a paragraph splits across frames, both
|
|
31
|
+
shading and border are drawn around the rendered portion only.
|
|
37
32
|
|
|
38
33
|
## Acceptance criteria
|
|
39
34
|
|
|
40
|
-
- [
|
|
41
|
-
|
|
42
|
-
- [
|
|
43
|
-
- [
|
|
35
|
+
- [x] PSR with RuleAbove=true and RuleAboveLineWeight=2 draws the
|
|
36
|
+
rule above the paragraph.
|
|
37
|
+
- [x] PSR with RuleAboveOffset=4 leaves a 4pt gap.
|
|
38
|
+
- [x] PSR with ParagraphShadingOn=true and tint fills the paragraph
|
|
39
|
+
rect.
|
|
40
|
+
- [x] ParagraphBorderOn draws per-side strokes.
|
|
44
41
|
|
|
45
42
|
## Dependencies
|
|
46
43
|
|
|
47
|
-
- TODO 94 (VerticalLayout knows the paragraph's rect).
|
|
44
|
+
- TODO 94 (VerticalLayout knows the paragraph's rect) — satisfied.
|
|
@@ -298,6 +298,7 @@ module Idml
|
|
|
298
298
|
attribute :content, Idml::Elements::Content, collection: true
|
|
299
299
|
attribute :hyperlink_text_source, Idml::Elements::HyperlinkTextSource,
|
|
300
300
|
collection: true
|
|
301
|
+
attribute :properties, Idml::Elements::Properties, collection: true
|
|
301
302
|
attribute :table, Idml::Elements::Table, collection: true
|
|
302
303
|
attribute :xml_element, Idml::Elements::XmlElement, collection: true
|
|
303
304
|
attribute :footnote, Idml::Elements::Footnote, collection: true
|
|
@@ -664,6 +665,7 @@ module Idml
|
|
|
664
665
|
map_element "CharacterStyleRange", to: :character_style_range
|
|
665
666
|
map_element "Content", to: :content
|
|
666
667
|
map_element "HyperlinkTextSource", to: :hyperlink_text_source
|
|
668
|
+
map_element "Properties", to: :properties
|
|
667
669
|
map_element "Table", to: :table
|
|
668
670
|
map_element "XMLElement", to: :xml_element
|
|
669
671
|
map_element "Footnote", to: :footnote
|
|
@@ -298,6 +298,7 @@ module Idml
|
|
|
298
298
|
collection: true
|
|
299
299
|
attribute :content, Idml::Elements::Content, collection: true
|
|
300
300
|
attribute :xml_element, Idml::Elements::XmlElement, collection: true
|
|
301
|
+
attribute :properties, Idml::Elements::Properties, collection: true
|
|
301
302
|
|
|
302
303
|
xml do
|
|
303
304
|
root "ParagraphStyleRange"
|
|
@@ -651,6 +652,7 @@ module Idml
|
|
|
651
652
|
map_element "ParagraphStyleRange", to: :paragraph_style_range
|
|
652
653
|
map_element "Content", to: :content
|
|
653
654
|
map_element "XMLElement", to: :xml_element
|
|
655
|
+
map_element "Properties", to: :properties
|
|
654
656
|
end
|
|
655
657
|
|
|
656
658
|
def text_content
|
|
@@ -2,18 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
module Idml
|
|
4
4
|
module Elements
|
|
5
|
-
# `<Properties>` — a common child element on page items
|
|
6
|
-
#
|
|
7
|
-
#
|
|
5
|
+
# `<Properties>` — a common child element on page items and
|
|
6
|
+
# story style ranges. Carries optional PathBoundingBox,
|
|
7
|
+
# PathGeometry, Label, and the paragraph-decoration color value
|
|
8
|
+
# elements (RuleAboveColor, ParagraphShadingColor, …) used on
|
|
9
|
+
# PSR/CSR Properties. Only the modeled children are populated
|
|
10
|
+
# from XML; others are ignored.
|
|
8
11
|
class Properties < Lutaml::Model::Serializable
|
|
9
12
|
attribute :path_bounding_box, :string
|
|
10
13
|
attribute :path_geometry, Idml::Elements::PathGeometry,
|
|
11
14
|
collection: true
|
|
15
|
+
attribute :rule_above_color, Idml::Elements::TypedValue
|
|
16
|
+
attribute :rule_below_color, Idml::Elements::TypedValue
|
|
17
|
+
attribute :paragraph_shading_color, Idml::Elements::TypedValue
|
|
18
|
+
attribute :paragraph_border_color, Idml::Elements::TypedValue
|
|
19
|
+
attribute :paragraph_border_gap_color, Idml::Elements::TypedValue
|
|
12
20
|
|
|
13
21
|
xml do
|
|
14
22
|
root "Properties"
|
|
15
23
|
map_element "PathBoundingBox", to: :path_bounding_box
|
|
16
24
|
map_element "PathGeometry", to: :path_geometry
|
|
25
|
+
map_element "RuleAboveColor", to: :rule_above_color
|
|
26
|
+
map_element "RuleBelowColor", to: :rule_below_color
|
|
27
|
+
map_element "ParagraphShadingColor", to: :paragraph_shading_color
|
|
28
|
+
map_element "ParagraphBorderColor", to: :paragraph_border_color
|
|
29
|
+
map_element "ParagraphBorderGapColor", to: :paragraph_border_gap_color
|
|
17
30
|
end
|
|
18
31
|
|
|
19
32
|
def first_geometry
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Idml
|
|
4
|
+
module Elements
|
|
5
|
+
# Typed value child element — IDML's `(object_type, xsd:string) |
|
|
6
|
+
# (string_type, xsd:string)` wrapper used inside Properties for
|
|
7
|
+
# values like RuleAboveColor and ParagraphShadingColor: a `type`
|
|
8
|
+
# attribute plus the value as text content.
|
|
9
|
+
class TypedValue < Lutaml::Model::Serializable
|
|
10
|
+
attribute :type, :string
|
|
11
|
+
attribute :value, :string
|
|
12
|
+
|
|
13
|
+
xml do
|
|
14
|
+
root "TypedValue"
|
|
15
|
+
map_attribute "type", to: :type
|
|
16
|
+
map_content to: :value
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/idml/elements.rb
CHANGED
|
@@ -147,6 +147,7 @@ module Idml
|
|
|
147
147
|
autoload :TOCStyle, "idml/elements/toc_style"
|
|
148
148
|
autoload :TOCStyleEntry, "idml/elements/toc_style_entry"
|
|
149
149
|
autoload :Table, "idml/elements/table"
|
|
150
|
+
autoload :TypedValue, "idml/elements/typed_value"
|
|
150
151
|
autoload :TableCell, "idml/elements/table_cell"
|
|
151
152
|
autoload :TableRow, "idml/elements/table_row"
|
|
152
153
|
autoload :TableStyle, "idml/elements/table_style"
|
|
@@ -82,6 +82,131 @@ module Idml
|
|
|
82
82
|
end
|
|
83
83
|
private_class_method :emit_rule
|
|
84
84
|
|
|
85
|
+
# Emits the paragraph's shading: a fill rect behind the
|
|
86
|
+
# paragraph block (top_y..bottom_y within the column). The
|
|
87
|
+
# ParagraphShading* offsets expand the rect outward; tint
|
|
88
|
+
# scales the color. Color comes from Properties >
|
|
89
|
+
# ParagraphShadingColor (black when absent, per InDesign's
|
|
90
|
+
# default shading color).
|
|
91
|
+
def self.emit_shading(canvas, paragraph, context, top_y, bottom_y,
|
|
92
|
+
frame_left, frame_right)
|
|
93
|
+
return unless paragraph.paragraph_shading_on
|
|
94
|
+
return unless top_y > bottom_y
|
|
95
|
+
|
|
96
|
+
tint_value = decoration_tint(paragraph.paragraph_shading_tint)
|
|
97
|
+
return unless tint_value
|
|
98
|
+
|
|
99
|
+
color = resolve_color(paragraph.paragraph_shading_color,
|
|
100
|
+
tint_value, context)
|
|
101
|
+
return unless color
|
|
102
|
+
|
|
103
|
+
left, right, top, bottom = decoration_rect(
|
|
104
|
+
paragraph, top_y, bottom_y, frame_left, frame_right,
|
|
105
|
+
left_offset: paragraph.paragraph_shading_left_offset,
|
|
106
|
+
right_offset: paragraph.paragraph_shading_right_offset,
|
|
107
|
+
top_offset: paragraph.paragraph_shading_top_offset,
|
|
108
|
+
bottom_offset: paragraph.paragraph_shading_bottom_offset
|
|
109
|
+
)
|
|
110
|
+
canvas.fill_color(color)
|
|
111
|
+
canvas.rectangle(left, bottom, right - left, top - bottom)
|
|
112
|
+
canvas.fill
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Emits the paragraph's border: per-side strokes around the
|
|
116
|
+
# paragraph block, each side with its own line weight, drawn
|
|
117
|
+
# at the block rect expanded by the border offsets.
|
|
118
|
+
def self.emit_border(canvas, paragraph, context, top_y, bottom_y,
|
|
119
|
+
frame_left, frame_right)
|
|
120
|
+
return unless paragraph.paragraph_border_on
|
|
121
|
+
return unless top_y > bottom_y
|
|
122
|
+
|
|
123
|
+
tint_value = decoration_tint(paragraph.paragraph_border_tint)
|
|
124
|
+
return unless tint_value
|
|
125
|
+
|
|
126
|
+
color = resolve_color(paragraph.paragraph_border_color,
|
|
127
|
+
tint_value, context)
|
|
128
|
+
return unless color
|
|
129
|
+
|
|
130
|
+
left, right, top, bottom = decoration_rect(
|
|
131
|
+
paragraph, top_y, bottom_y, frame_left, frame_right,
|
|
132
|
+
left_offset: paragraph.paragraph_border_left_offset,
|
|
133
|
+
right_offset: paragraph.paragraph_border_right_offset,
|
|
134
|
+
top_offset: paragraph.paragraph_border_top_offset,
|
|
135
|
+
bottom_offset: paragraph.paragraph_border_bottom_offset
|
|
136
|
+
)
|
|
137
|
+
apply_rule_color(canvas, paragraph.paragraph_border_color,
|
|
138
|
+
tint_value, context)
|
|
139
|
+
stroke_border_sides(canvas, paragraph, left, right, top, bottom)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Tint value for a decoration, or nil when zero/absent.
|
|
143
|
+
def self.decoration_tint(tint)
|
|
144
|
+
value = (tint || DEFAULT_TINT).to_f
|
|
145
|
+
value.positive? ? value : nil
|
|
146
|
+
end
|
|
147
|
+
private_class_method :decoration_tint
|
|
148
|
+
|
|
149
|
+
# The decoration rect [left, right, top, bottom]: the column
|
|
150
|
+
# (or text) extents expanded outward by the given offsets.
|
|
151
|
+
def self.decoration_rect(paragraph, top_y, bottom_y, frame_left,
|
|
152
|
+
frame_right, left_offset:, right_offset:,
|
|
153
|
+
top_offset:, bottom_offset:)
|
|
154
|
+
left, right = decoration_extents(paragraph, frame_left, frame_right)
|
|
155
|
+
[left - (left_offset || 0).to_f,
|
|
156
|
+
right + (right_offset || 0).to_f,
|
|
157
|
+
top_y + (top_offset || 0).to_f,
|
|
158
|
+
bottom_y - (bottom_offset || 0).to_f]
|
|
159
|
+
end
|
|
160
|
+
private_class_method :decoration_rect
|
|
161
|
+
|
|
162
|
+
def self.stroke_border_sides(canvas, paragraph, left, right,
|
|
163
|
+
top, bottom)
|
|
164
|
+
stroke_horizontal(canvas, left, right, top,
|
|
165
|
+
paragraph.paragraph_border_top_line_weight)
|
|
166
|
+
stroke_horizontal(canvas, left, right, bottom,
|
|
167
|
+
paragraph.paragraph_border_bottom_line_weight)
|
|
168
|
+
stroke_vertical(canvas, bottom, top, left,
|
|
169
|
+
paragraph.paragraph_border_left_line_weight)
|
|
170
|
+
stroke_vertical(canvas, bottom, top, right,
|
|
171
|
+
paragraph.paragraph_border_right_line_weight)
|
|
172
|
+
end
|
|
173
|
+
private_class_method :stroke_border_sides
|
|
174
|
+
|
|
175
|
+
def self.stroke_horizontal(canvas, x1, x2, y, weight)
|
|
176
|
+
thickness = weight.to_f
|
|
177
|
+
return unless thickness.positive?
|
|
178
|
+
|
|
179
|
+
canvas.line_width = thickness
|
|
180
|
+
canvas.move_to(x1, y)
|
|
181
|
+
canvas.line_to(x2, y)
|
|
182
|
+
canvas.stroke
|
|
183
|
+
end
|
|
184
|
+
private_class_method :stroke_horizontal
|
|
185
|
+
|
|
186
|
+
def self.stroke_vertical(canvas, y1, y2, x, weight)
|
|
187
|
+
thickness = weight.to_f
|
|
188
|
+
return unless thickness.positive?
|
|
189
|
+
|
|
190
|
+
canvas.line_width = thickness
|
|
191
|
+
canvas.move_to(x, y1)
|
|
192
|
+
canvas.line_to(x, y2)
|
|
193
|
+
canvas.stroke
|
|
194
|
+
end
|
|
195
|
+
private_class_method :stroke_vertical
|
|
196
|
+
|
|
197
|
+
# Horizontal extents for shading/border: the column width, or
|
|
198
|
+
# the text width (indented by the paragraph's indents) when
|
|
199
|
+
# the shading width mode is "TextWidth".
|
|
200
|
+
def self.decoration_extents(paragraph, frame_left, frame_right)
|
|
201
|
+
if paragraph.paragraph_shading_width == "TextWidth"
|
|
202
|
+
[frame_left + (paragraph.left_indent || 0).to_f,
|
|
203
|
+
frame_right - (paragraph.right_indent || 0).to_f]
|
|
204
|
+
else
|
|
205
|
+
[frame_left, frame_right]
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
private_class_method :decoration_extents
|
|
209
|
+
|
|
85
210
|
# Returns `[x1, x2]` for the rule. Width mode "Text" indents
|
|
86
211
|
# the rule by the paragraph's left/right indent (matches the
|
|
87
212
|
# text bounds); "Column" (default) spans the full column.
|
|
@@ -445,6 +445,12 @@ module Idml
|
|
|
445
445
|
|
|
446
446
|
emit_paragraph_top_rule(canvas, paragraph, context, cursor_y,
|
|
447
447
|
frame_left, frame_right, first_paragraph)
|
|
448
|
+
block_top, block_bottom = paragraph_block_extent(
|
|
449
|
+
paragraph, font, wrap_width, cursor_y, bottom_limit
|
|
450
|
+
)
|
|
451
|
+
ParagraphRules.emit_shading(canvas, paragraph, context,
|
|
452
|
+
block_top, block_bottom,
|
|
453
|
+
frame_left, frame_right)
|
|
448
454
|
drop_cap = emit_drop_cap(canvas, paragraph, context, frame_left,
|
|
449
455
|
cursor_y, first_paragraph)
|
|
450
456
|
|
|
@@ -463,10 +469,28 @@ module Idml
|
|
|
463
469
|
cursor_y, frame_left,
|
|
464
470
|
frame_right, para_attrs,
|
|
465
471
|
remaining_runs)
|
|
472
|
+
ParagraphRules.emit_border(canvas, paragraph, context,
|
|
473
|
+
block_top, block_bottom,
|
|
474
|
+
frame_left, frame_right)
|
|
466
475
|
[remaining_runs, cursor_y, char_cursor]
|
|
467
476
|
end
|
|
468
477
|
private_class_method :render_runs_for_paragraph
|
|
469
478
|
|
|
479
|
+
# The paragraph's block extent: top at the current cursor,
|
|
480
|
+
# bottom at the pre-measured height clamped to the frame's
|
|
481
|
+
# bottom limit. Shading and border both use this one rect so
|
|
482
|
+
# they agree even when the paragraph splits across frames
|
|
483
|
+
# (the split case shades only the rendered part).
|
|
484
|
+
def self.paragraph_block_extent(paragraph, font, wrap_width,
|
|
485
|
+
cursor_y, bottom_limit)
|
|
486
|
+
height = TextEngine::Measurement.paragraph_height(
|
|
487
|
+
paragraph, font, wrap_width
|
|
488
|
+
)
|
|
489
|
+
block_bottom = [cursor_y - height, bottom_limit].max
|
|
490
|
+
[cursor_y, block_bottom]
|
|
491
|
+
end
|
|
492
|
+
private_class_method :paragraph_block_extent
|
|
493
|
+
|
|
470
494
|
def self.iterate_paragraph_runs(canvas, paragraph, runs, context,
|
|
471
495
|
layout_frame, font, wrap_width,
|
|
472
496
|
cursor_y, bottom_limit, char_cursor,
|
|
@@ -82,6 +82,28 @@ module Idml
|
|
|
82
82
|
:rule_below_left_indent,
|
|
83
83
|
:rule_below_right_indent,
|
|
84
84
|
:rule_below_width,
|
|
85
|
+
# ParagraphShading — fill rect behind the paragraph block.
|
|
86
|
+
# Color comes from Properties > ParagraphShadingColor.
|
|
87
|
+
:paragraph_shading_on,
|
|
88
|
+
:paragraph_shading_color,
|
|
89
|
+
:paragraph_shading_tint,
|
|
90
|
+
:paragraph_shading_width,
|
|
91
|
+
:paragraph_shading_left_offset,
|
|
92
|
+
:paragraph_shading_right_offset,
|
|
93
|
+
:paragraph_shading_top_offset,
|
|
94
|
+
:paragraph_shading_bottom_offset,
|
|
95
|
+
# ParagraphBorder — per-side strokes around the paragraph.
|
|
96
|
+
:paragraph_border_on,
|
|
97
|
+
:paragraph_border_color,
|
|
98
|
+
:paragraph_border_tint,
|
|
99
|
+
:paragraph_border_top_line_weight,
|
|
100
|
+
:paragraph_border_left_line_weight,
|
|
101
|
+
:paragraph_border_right_line_weight,
|
|
102
|
+
:paragraph_border_bottom_line_weight,
|
|
103
|
+
:paragraph_border_top_offset,
|
|
104
|
+
:paragraph_border_left_offset,
|
|
105
|
+
:paragraph_border_right_offset,
|
|
106
|
+
:paragraph_border_bottom_offset,
|
|
85
107
|
keyword_init: true,
|
|
86
108
|
)
|
|
87
109
|
|
|
@@ -171,7 +193,8 @@ module Idml
|
|
|
171
193
|
rule_above: resolve_attr(style_lookup, psr, :rule_above),
|
|
172
194
|
rule_above_line_weight: resolve_attr(style_lookup, psr,
|
|
173
195
|
:rule_above_line_weight),
|
|
174
|
-
rule_above_color:
|
|
196
|
+
rule_above_color: properties_color(psr, :rule_above_color) ||
|
|
197
|
+
resolve_attr(style_lookup, psr, :stroke_color),
|
|
175
198
|
rule_above_tint: resolve_attr(style_lookup, psr,
|
|
176
199
|
:rule_above_tint),
|
|
177
200
|
rule_above_offset: resolve_attr(style_lookup, psr,
|
|
@@ -185,7 +208,8 @@ module Idml
|
|
|
185
208
|
rule_below: resolve_attr(style_lookup, psr, :rule_below),
|
|
186
209
|
rule_below_line_weight: resolve_attr(style_lookup, psr,
|
|
187
210
|
:rule_below_line_weight),
|
|
188
|
-
rule_below_color:
|
|
211
|
+
rule_below_color: properties_color(psr, :rule_below_color) ||
|
|
212
|
+
resolve_attr(style_lookup, psr, :stroke_color),
|
|
189
213
|
rule_below_tint: resolve_attr(style_lookup, psr,
|
|
190
214
|
:rule_below_tint),
|
|
191
215
|
rule_below_offset: resolve_attr(style_lookup, psr,
|
|
@@ -196,12 +220,70 @@ module Idml
|
|
|
196
220
|
:rule_below_right_indent),
|
|
197
221
|
rule_below_width: resolve_attr(style_lookup, psr,
|
|
198
222
|
:rule_below_width),
|
|
223
|
+
paragraph_shading_on: resolve_attr(style_lookup, psr,
|
|
224
|
+
:paragraph_shading_on),
|
|
225
|
+
paragraph_shading_color: properties_color(psr,
|
|
226
|
+
:paragraph_shading_color),
|
|
227
|
+
paragraph_shading_tint: resolve_attr(style_lookup, psr,
|
|
228
|
+
:paragraph_shading_tint),
|
|
229
|
+
paragraph_shading_width: resolve_attr(style_lookup, psr,
|
|
230
|
+
:paragraph_shading_width),
|
|
231
|
+
paragraph_shading_left_offset: resolve_attr(
|
|
232
|
+
style_lookup, psr, :paragraph_shading_left_offset
|
|
233
|
+
),
|
|
234
|
+
paragraph_shading_right_offset: resolve_attr(
|
|
235
|
+
style_lookup, psr, :paragraph_shading_right_offset
|
|
236
|
+
),
|
|
237
|
+
paragraph_shading_top_offset: resolve_attr(
|
|
238
|
+
style_lookup, psr, :paragraph_shading_top_offset
|
|
239
|
+
),
|
|
240
|
+
paragraph_shading_bottom_offset: resolve_attr(
|
|
241
|
+
style_lookup, psr, :paragraph_shading_bottom_offset
|
|
242
|
+
),
|
|
243
|
+
paragraph_border_on: resolve_attr(style_lookup, psr,
|
|
244
|
+
:paragraph_border_on),
|
|
245
|
+
paragraph_border_color: properties_color(psr,
|
|
246
|
+
:paragraph_border_color),
|
|
247
|
+
paragraph_border_tint: resolve_attr(style_lookup, psr,
|
|
248
|
+
:paragraph_border_tint),
|
|
249
|
+
paragraph_border_top_line_weight: resolve_attr(
|
|
250
|
+
style_lookup, psr, :paragraph_border_top_line_weight
|
|
251
|
+
),
|
|
252
|
+
paragraph_border_left_line_weight: resolve_attr(
|
|
253
|
+
style_lookup, psr, :paragraph_border_left_line_weight
|
|
254
|
+
),
|
|
255
|
+
paragraph_border_right_line_weight: resolve_attr(
|
|
256
|
+
style_lookup, psr, :paragraph_border_right_line_weight
|
|
257
|
+
),
|
|
258
|
+
paragraph_border_bottom_line_weight: resolve_attr(
|
|
259
|
+
style_lookup, psr, :paragraph_border_bottom_line_weight
|
|
260
|
+
),
|
|
261
|
+
paragraph_border_top_offset: resolve_attr(
|
|
262
|
+
style_lookup, psr, :paragraph_border_top_offset
|
|
263
|
+
),
|
|
264
|
+
paragraph_border_left_offset: resolve_attr(
|
|
265
|
+
style_lookup, psr, :paragraph_border_left_offset
|
|
266
|
+
),
|
|
267
|
+
paragraph_border_right_offset: resolve_attr(
|
|
268
|
+
style_lookup, psr, :paragraph_border_right_offset
|
|
269
|
+
),
|
|
270
|
+
paragraph_border_bottom_offset: resolve_attr(
|
|
271
|
+
style_lookup, psr, :paragraph_border_bottom_offset
|
|
272
|
+
),
|
|
199
273
|
)
|
|
200
274
|
prepend_list_marker(paragraph)
|
|
201
275
|
paragraph
|
|
202
276
|
end
|
|
203
277
|
end
|
|
204
278
|
|
|
279
|
+
# Reads a color value element from the PSR's Properties (e.g.
|
|
280
|
+
# Properties > ParagraphShadingColor). Returns the color name
|
|
281
|
+
# string, or nil when the element is absent.
|
|
282
|
+
def self.properties_color(psr, element_attr)
|
|
283
|
+
psr.properties.first&.public_send(element_attr)&.value
|
|
284
|
+
end
|
|
285
|
+
private_class_method :properties_color
|
|
286
|
+
|
|
205
287
|
def self.csr_runs(psr, condition_filter: nil, style_lookup: nil,
|
|
206
288
|
footnote_counter: nil, footnote_option: nil)
|
|
207
289
|
counter = footnote_counter || Footnote.counter_for(footnote_option)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Idml
|
|
4
|
+
module TextEngine
|
|
5
|
+
# Height measurement for paragraphs — shape + wrap each run and
|
|
6
|
+
# sum the line count × leading. Used where a paragraph's full
|
|
7
|
+
# extent must be known before its lines are laid out (paragraph
|
|
8
|
+
# shading/border rects).
|
|
9
|
+
module Measurement
|
|
10
|
+
DEFAULT_POINT_SIZE = 12.0
|
|
11
|
+
|
|
12
|
+
# Returns the paragraph's rendered height at `wrap_width`:
|
|
13
|
+
# every run's wrapped line count × leading, plus paragraph
|
|
14
|
+
# SpaceBefore / SpaceAfter.
|
|
15
|
+
def self.paragraph_height(paragraph, font, wrap_width)
|
|
16
|
+
lines_height = paragraph.runs.sum do |run|
|
|
17
|
+
size = run.point_size || DEFAULT_POINT_SIZE
|
|
18
|
+
lines = wrapped_lines(run, font, wrap_width, size)
|
|
19
|
+
lines.length *
|
|
20
|
+
TextEngine::VerticalLayout.leading_for(paragraph.auto_leading,
|
|
21
|
+
size)
|
|
22
|
+
end
|
|
23
|
+
lines_height + space_before_after(paragraph)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.wrapped_lines(run, font, wrap_width, size)
|
|
27
|
+
glyphs = TextEngine::Shaper.shape(text: run.text, font: font,
|
|
28
|
+
size: size)
|
|
29
|
+
TextEngine::LineBreaker.break(glyphs: glyphs,
|
|
30
|
+
frame_width: wrap_width)
|
|
31
|
+
end
|
|
32
|
+
private_class_method :wrapped_lines
|
|
33
|
+
|
|
34
|
+
def self.space_before_after(paragraph)
|
|
35
|
+
(paragraph.space_before || 0) + (paragraph.space_after || 0)
|
|
36
|
+
end
|
|
37
|
+
private_class_method :space_before_after
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/idml/text_engine.rb
CHANGED
|
@@ -6,7 +6,8 @@ module Idml
|
|
|
6
6
|
# .ttf/.otf files. IDML-agnostic — works on any styled text.
|
|
7
7
|
module TextEngine
|
|
8
8
|
autoload :PdfrbFontMetrics, "#{__dir__}/text_engine/pdfrb_font_metrics"
|
|
9
|
-
autoload :Shaper,
|
|
9
|
+
autoload :Shaper, "#{__dir__}/text_engine/shaper"
|
|
10
|
+
autoload :Measurement, "#{__dir__}/text_engine/measurement"
|
|
10
11
|
autoload :ShapedGlyph, "#{__dir__}/text_engine/shaper"
|
|
11
12
|
autoload :LineBreaker, "#{__dir__}/text_engine/line_breaker"
|
|
12
13
|
autoload :Line, "#{__dir__}/text_engine/line_breaker"
|
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
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose
|
|
@@ -407,6 +407,7 @@ files:
|
|
|
407
407
|
- lib/idml/elements/transform_attribute_option.rb
|
|
408
408
|
- lib/idml/elements/transparency_setting.rb
|
|
409
409
|
- lib/idml/elements/trap_preset.rb
|
|
410
|
+
- lib/idml/elements/typed_value.rb
|
|
410
411
|
- lib/idml/elements/xml_element.rb
|
|
411
412
|
- lib/idml/elements/xml_export_map.rb
|
|
412
413
|
- lib/idml/elements/xml_import_map.rb
|
|
@@ -484,6 +485,7 @@ files:
|
|
|
484
485
|
- lib/idml/text_engine/cjk_layout.rb
|
|
485
486
|
- lib/idml/text_engine/justifier.rb
|
|
486
487
|
- lib/idml/text_engine/line_breaker.rb
|
|
488
|
+
- lib/idml/text_engine/measurement.rb
|
|
487
489
|
- lib/idml/text_engine/pdfrb_font_metrics.rb
|
|
488
490
|
- lib/idml/text_engine/shaper.rb
|
|
489
491
|
- lib/idml/text_engine/vertical_layout.rb
|