idml 0.9.2 → 0.9.4
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/118-table-band-fills.md +33 -0
- data/TODO.pdf/119-justification-spacing-limits.md +34 -0
- data/TODO.pdf/120-start-paragraph-breaks.md +26 -0
- data/TODO.pdf/121-cell-edge-strokes.md +29 -0
- data/lib/idml/elements/cell.rb +182 -0
- data/lib/idml/render/footnote.rb +6 -1
- data/lib/idml/render/renderers/table_renderer.rb +194 -6
- data/lib/idml/render/renderers/text_frame_renderer.rb +16 -1
- data/lib/idml/render/style_resolver.rb +14 -0
- data/lib/idml/text_engine/justifier.rb +43 -4
- data/lib/idml/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 41d28cdae1d32efe5815542e9f5f702affff8445c1c77b9e62e3c9229f974dca
|
|
4
|
+
data.tar.gz: d9144aafa235ef8364cd373bc7f62742bc95ed1b6646f159f00e67f72a1706e2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ca86d99a34bfb4cb1ab62580c3283dda29b21a060435f4dacce15e1069a49bf3009701d34c77700f5308b4c1fcd4e5d11788c89f6802398cb05a872516cae803
|
|
7
|
+
data.tar.gz: 6b7033ff88e4e5ea4edc4fbdfe7ebdb0024fbcc024107a7f5e5c18e3c7196b6323dcc66ec55485dbc743351c6d15f7bfd3eff922531d2d1ce6d713c9efa99dc7
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# TODO PDF 118: Table alternating row/column band fills
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE — implemented 2026-08-18
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
IDML tables declare table-level alternating fills —
|
|
8
|
+
StartRowFillColor/Count + EndRowFillColor/Count (row banding),
|
|
9
|
+
StartColumnFillColor/Count + EndColumnFillColor/Count (column
|
|
10
|
+
banding), ColumnFillsPriority (true = column banding wins),
|
|
11
|
+
SkipFirst/LastAlternatingFillRows/Columns (edge suppression), and
|
|
12
|
+
per-band tints. All attributes are modeled (TODO 104) but the
|
|
13
|
+
renderer only honored per-Cell FillColor, so banded tables (zebra
|
|
14
|
+
stripes, ledger shading) rendered without their backgrounds.
|
|
15
|
+
|
|
16
|
+
## What was done
|
|
17
|
+
|
|
18
|
+
- `SchemaLayout#band_fill(col, row)` computes the effective band
|
|
19
|
+
color + tint for a cell position: row banding by default; column
|
|
20
|
+
banding when ColumnFillsPriority is true or row banding is
|
|
21
|
+
unconfigured; SkipFirst/Last offsets suppress banding at the
|
|
22
|
+
table edges; "Color/None" band colors render nothing.
|
|
23
|
+
- `TableRenderer` paints the band rect behind each cell BEFORE the
|
|
24
|
+
cell's own background — an explicit Cell FillColor overrides the
|
|
25
|
+
band, matching InDesign.
|
|
26
|
+
- `each_cell` now also yields the cell's column/row indices.
|
|
27
|
+
|
|
28
|
+
## Acceptance criteria
|
|
29
|
+
|
|
30
|
+
- [x] Rows alternate Start/End colors in the declared counts.
|
|
31
|
+
- [x] Cell-level fill overrides the band.
|
|
32
|
+
- [x] Column banding wins when ColumnFillsPriority is true.
|
|
33
|
+
- [x] SkipFirstAlternatingFillRows suppresses leading bands.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# TODO PDF 119: Justification word/letter spacing limits
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE — implemented 2026-08-19
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
`TextEngine::Justifier` stretched inter-word spaces without limit
|
|
8
|
+
under full justification — a line with one space could widen that
|
|
9
|
+
gap across the whole column. IDML paragraphs declare
|
|
10
|
+
MaximumWordSpacing / MaximumLetterSpacing (percentages,
|
|
11
|
+
InDesign defaults 133% word, 0% letter) that cap how justification
|
|
12
|
+
slack is distributed.
|
|
13
|
+
|
|
14
|
+
## What was done
|
|
15
|
+
|
|
16
|
+
- `Justifier::SpacingLimits` (max_word_spacing, max_letter_spacing
|
|
17
|
+
percentages). `Justifier.justify` takes `limits:`.
|
|
18
|
+
- Distribution: spaces stretch up to the word-spacing cap
|
|
19
|
+
(capacity = Σ natural_space_width × (max−100)/100); any residual
|
|
20
|
+
slack goes to letter spacing — an even per-glyph addition capped
|
|
21
|
+
at max_letter_spacing% of the natural space width. With
|
|
22
|
+
InDesign's default letter cap of 0%, residual slack stays
|
|
23
|
+
unfilled and the line ends short, matching InDesign's behavior
|
|
24
|
+
when limits bind.
|
|
25
|
+
- `StyleResolver::Paragraph` carries maximum_word_spacing /
|
|
26
|
+
maximum_letter_spacing from the PSR; the frame renderer and the
|
|
27
|
+
footnote layout both pass the limits.
|
|
28
|
+
|
|
29
|
+
## Known limitations
|
|
30
|
+
|
|
31
|
+
- Glyph scaling (Minimum/Maximum/DesiredGlyphScaling) is not
|
|
32
|
+
applied as a last resort.
|
|
33
|
+
- Minimum word/letter spacing (line compression) is unused — this
|
|
34
|
+
justifier only ever stretches (slack ≥ 0).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# TODO PDF 120: StartParagraph frame/column breaks
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE — implemented 2026-08-19
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
PSR `StartParagraph` (Anywhere / NextPage / NextColumn / NextFrame
|
|
8
|
+
/ NextOddPage / NextEvenPage) was parsed but ignored — forced-break
|
|
9
|
+
paragraphs (chapter headings, magazine copy) rendered mid-frame.
|
|
10
|
+
|
|
11
|
+
## What was done
|
|
12
|
+
|
|
13
|
+
- `StyleResolver::Paragraph` carries `start_paragraph` from the
|
|
14
|
+
PSR.
|
|
15
|
+
- `TextFrameRenderer.consume_paragraphs` stops filling the current
|
|
16
|
+
frame/column before a paragraph that requests a break (NextPage /
|
|
17
|
+
NextColumn / NextFrame / NextOddPage / NextEvenPage) — as long as
|
|
18
|
+
at least one paragraph was already placed there; the paragraph
|
|
19
|
+
and everything after it flow to the next frame via the existing
|
|
20
|
+
chain state. Multi-column frames break per column.
|
|
21
|
+
|
|
22
|
+
## Known limitations
|
|
23
|
+
|
|
24
|
+
- All break flavors act at frame/column granularity (NextPage and
|
|
25
|
+
NextFrame are indistinguishable; odd/even page parity is not
|
|
26
|
+
modeled — the renderer has no page-parity notion at this layer).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# TODO PDF 121: Per-side cell edge strokes
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE — implemented 2026-08-19
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
`TableRenderer` stroked every cell as a uniform rectangle with the
|
|
8
|
+
canvas default line width. IDML cells declare per-side edge stroke
|
|
9
|
+
weights (TopEdgeStrokeWeight, LeftEdgeStrokeWeight,
|
|
10
|
+
BottomEdgeStrokeWeight, RightEdgeStrokeWeight — with the matching
|
|
11
|
+
stroke colors/tints), and the table declares fallback defaults
|
|
12
|
+
(DefaultRowStrokeWeight / DefaultColumnStrokeWeight).
|
|
13
|
+
|
|
14
|
+
## What was done
|
|
15
|
+
|
|
16
|
+
- Cell_Object schema completion: `Elements::Cell` declares all 95
|
|
17
|
+
schema attributes (77 added — per-side edge strokes + diagonal
|
|
18
|
+
lines + cell style overrides), generated in schema order like the
|
|
19
|
+
Table completion (TODO 104).
|
|
20
|
+
- `TableRenderer.render_cell_border` draws each side separately
|
|
21
|
+
with the cell's edge stroke weight, falling back to the table's
|
|
22
|
+
default row/column stroke weight, and resolves stroke colors via
|
|
23
|
+
the color resolver (black fallback). Sides with zero weight are
|
|
24
|
+
skipped, so weightless cells no longer draw hairlines.
|
|
25
|
+
|
|
26
|
+
## Known limitations
|
|
27
|
+
|
|
28
|
+
- Diagonal cell strokes (DiagonalLine*) are modeled but not drawn.
|
|
29
|
+
- Edge stroke types (dashes) fall back to solid.
|
data/lib/idml/elements/cell.rb
CHANGED
|
@@ -38,6 +38,88 @@ module Idml
|
|
|
38
38
|
attribute :paragraph_style_range, Idml::Elements::ParagraphStyleRange,
|
|
39
39
|
collection: true
|
|
40
40
|
|
|
41
|
+
# The remaining Cell_Object attributes (per-side edge strokes,
|
|
42
|
+
# diagonal lines, cell style overrides, …) in schema order —
|
|
43
|
+
# generated by `scripts/rnc_to_lutaml.rb` from
|
|
44
|
+
# reference-docs/schemas/package/Stories/Story.rnc.
|
|
45
|
+
attribute :top_inset, :float
|
|
46
|
+
attribute :left_inset, :float
|
|
47
|
+
attribute :bottom_inset, :float
|
|
48
|
+
attribute :right_inset, :float
|
|
49
|
+
attribute :top_left_diagonal_line, :boolean
|
|
50
|
+
attribute :top_right_diagonal_line, :boolean
|
|
51
|
+
attribute :diagonal_line_in_front, :boolean
|
|
52
|
+
attribute :diagonal_line_stroke_weight, :float
|
|
53
|
+
attribute :diagonal_line_stroke_type, :string
|
|
54
|
+
attribute :diagonal_line_stroke_color, :string
|
|
55
|
+
attribute :diagonal_line_stroke_tint, :float
|
|
56
|
+
attribute :diagonal_line_stroke_overprint, :boolean
|
|
57
|
+
attribute :diagonal_line_stroke_gap_color, :string
|
|
58
|
+
attribute :diagonal_line_stroke_gap_tint, :float
|
|
59
|
+
attribute :diagonal_line_stroke_gap_overprint, :boolean
|
|
60
|
+
attribute :clip_content_to_cell, :boolean
|
|
61
|
+
attribute :first_baseline_offset, :string
|
|
62
|
+
attribute :minimum_first_baseline_offset, :float
|
|
63
|
+
attribute :left_edge_stroke_weight, :float
|
|
64
|
+
attribute :left_edge_stroke_type, :string
|
|
65
|
+
attribute :left_edge_stroke_color, :string
|
|
66
|
+
attribute :left_edge_stroke_tint, :float
|
|
67
|
+
attribute :left_edge_stroke_overprint, :boolean
|
|
68
|
+
attribute :left_edge_stroke_gap_color, :string
|
|
69
|
+
attribute :left_edge_stroke_gap_tint, :float
|
|
70
|
+
attribute :left_edge_stroke_gap_overprint, :boolean
|
|
71
|
+
attribute :top_edge_stroke_weight, :float
|
|
72
|
+
attribute :top_edge_stroke_type, :string
|
|
73
|
+
attribute :top_edge_stroke_color, :string
|
|
74
|
+
attribute :top_edge_stroke_tint, :float
|
|
75
|
+
attribute :top_edge_stroke_overprint, :boolean
|
|
76
|
+
attribute :top_edge_stroke_gap_color, :string
|
|
77
|
+
attribute :top_edge_stroke_gap_tint, :float
|
|
78
|
+
attribute :top_edge_stroke_gap_overprint, :boolean
|
|
79
|
+
attribute :right_edge_stroke_weight, :float
|
|
80
|
+
attribute :right_edge_stroke_type, :string
|
|
81
|
+
attribute :right_edge_stroke_color, :string
|
|
82
|
+
attribute :right_edge_stroke_tint, :float
|
|
83
|
+
attribute :right_edge_stroke_overprint, :boolean
|
|
84
|
+
attribute :right_edge_stroke_gap_color, :string
|
|
85
|
+
attribute :right_edge_stroke_gap_tint, :float
|
|
86
|
+
attribute :right_edge_stroke_gap_overprint, :boolean
|
|
87
|
+
attribute :bottom_edge_stroke_weight, :float
|
|
88
|
+
attribute :bottom_edge_stroke_type, :string
|
|
89
|
+
attribute :bottom_edge_stroke_color, :string
|
|
90
|
+
attribute :bottom_edge_stroke_tint, :float
|
|
91
|
+
attribute :bottom_edge_stroke_overprint, :boolean
|
|
92
|
+
attribute :bottom_edge_stroke_gap_color, :string
|
|
93
|
+
attribute :bottom_edge_stroke_gap_tint, :float
|
|
94
|
+
attribute :bottom_edge_stroke_gap_overprint, :boolean
|
|
95
|
+
attribute :inner_row_stroke_weight, :float
|
|
96
|
+
attribute :inner_row_stroke_type, :string
|
|
97
|
+
attribute :inner_row_stroke_color, :string
|
|
98
|
+
attribute :inner_row_stroke_tint, :float
|
|
99
|
+
attribute :inner_row_stroke_overprint, :boolean
|
|
100
|
+
attribute :inner_row_stroke_gap_color, :string
|
|
101
|
+
attribute :inner_row_stroke_gap_tint, :float
|
|
102
|
+
attribute :inner_row_stroke_gap_overprint, :boolean
|
|
103
|
+
attribute :inner_column_stroke_weight, :float
|
|
104
|
+
attribute :inner_column_stroke_type, :string
|
|
105
|
+
attribute :inner_column_stroke_color, :string
|
|
106
|
+
attribute :inner_column_stroke_tint, :float
|
|
107
|
+
attribute :inner_column_stroke_overprint, :boolean
|
|
108
|
+
attribute :inner_column_stroke_gap_color, :string
|
|
109
|
+
attribute :inner_column_stroke_gap_tint, :float
|
|
110
|
+
attribute :inner_column_stroke_gap_overprint, :boolean
|
|
111
|
+
attribute :top_edge_stroke_priority, :integer
|
|
112
|
+
attribute :left_edge_stroke_priority, :integer
|
|
113
|
+
attribute :bottom_edge_stroke_priority, :integer
|
|
114
|
+
attribute :right_edge_stroke_priority, :integer
|
|
115
|
+
attribute :graphic_left_inset, :float
|
|
116
|
+
attribute :graphic_top_inset, :float
|
|
117
|
+
attribute :graphic_right_inset, :float
|
|
118
|
+
attribute :graphic_bottom_inset, :float
|
|
119
|
+
attribute :clip_content_to_graphic_cell, :boolean
|
|
120
|
+
attribute :writing_direction, :boolean
|
|
121
|
+
attribute :applied_cell_style_priority, :integer
|
|
122
|
+
|
|
41
123
|
xml do
|
|
42
124
|
root "Cell"
|
|
43
125
|
map_attribute "Self", to: :self_attr
|
|
@@ -58,6 +140,106 @@ module Idml
|
|
|
58
140
|
map_attribute "ParagraphSpacingLimit", to: :paragraph_spacing_limit
|
|
59
141
|
map_attribute "RotationAngle", to: :rotation_angle
|
|
60
142
|
map_attribute "AppliedCellStyle", to: :applied_cell_style
|
|
143
|
+
map_attribute "TopInset", to: :top_inset
|
|
144
|
+
map_attribute "LeftInset", to: :left_inset
|
|
145
|
+
map_attribute "BottomInset", to: :bottom_inset
|
|
146
|
+
map_attribute "RightInset", to: :right_inset
|
|
147
|
+
map_attribute "TopLeftDiagonalLine", to: :top_left_diagonal_line
|
|
148
|
+
map_attribute "TopRightDiagonalLine", to: :top_right_diagonal_line
|
|
149
|
+
map_attribute "DiagonalLineInFront", to: :diagonal_line_in_front
|
|
150
|
+
map_attribute "DiagonalLineStrokeWeight",
|
|
151
|
+
to: :diagonal_line_stroke_weight
|
|
152
|
+
map_attribute "DiagonalLineStrokeType", to: :diagonal_line_stroke_type
|
|
153
|
+
map_attribute "DiagonalLineStrokeColor", to: :diagonal_line_stroke_color
|
|
154
|
+
map_attribute "DiagonalLineStrokeTint", to: :diagonal_line_stroke_tint
|
|
155
|
+
map_attribute "DiagonalLineStrokeOverprint",
|
|
156
|
+
to: :diagonal_line_stroke_overprint
|
|
157
|
+
map_attribute "DiagonalLineStrokeGapColor",
|
|
158
|
+
to: :diagonal_line_stroke_gap_color
|
|
159
|
+
map_attribute "DiagonalLineStrokeGapTint",
|
|
160
|
+
to: :diagonal_line_stroke_gap_tint
|
|
161
|
+
map_attribute "DiagonalLineStrokeGapOverprint",
|
|
162
|
+
to: :diagonal_line_stroke_gap_overprint
|
|
163
|
+
map_attribute "ClipContentToCell", to: :clip_content_to_cell
|
|
164
|
+
map_attribute "FirstBaselineOffset", to: :first_baseline_offset
|
|
165
|
+
map_attribute "MinimumFirstBaselineOffset",
|
|
166
|
+
to: :minimum_first_baseline_offset
|
|
167
|
+
map_attribute "LeftEdgeStrokeWeight", to: :left_edge_stroke_weight
|
|
168
|
+
map_attribute "LeftEdgeStrokeType", to: :left_edge_stroke_type
|
|
169
|
+
map_attribute "LeftEdgeStrokeColor", to: :left_edge_stroke_color
|
|
170
|
+
map_attribute "LeftEdgeStrokeTint", to: :left_edge_stroke_tint
|
|
171
|
+
map_attribute "LeftEdgeStrokeOverprint", to: :left_edge_stroke_overprint
|
|
172
|
+
map_attribute "LeftEdgeStrokeGapColor", to: :left_edge_stroke_gap_color
|
|
173
|
+
map_attribute "LeftEdgeStrokeGapTint", to: :left_edge_stroke_gap_tint
|
|
174
|
+
map_attribute "LeftEdgeStrokeGapOverprint",
|
|
175
|
+
to: :left_edge_stroke_gap_overprint
|
|
176
|
+
map_attribute "TopEdgeStrokeWeight", to: :top_edge_stroke_weight
|
|
177
|
+
map_attribute "TopEdgeStrokeType", to: :top_edge_stroke_type
|
|
178
|
+
map_attribute "TopEdgeStrokeColor", to: :top_edge_stroke_color
|
|
179
|
+
map_attribute "TopEdgeStrokeTint", to: :top_edge_stroke_tint
|
|
180
|
+
map_attribute "TopEdgeStrokeOverprint", to: :top_edge_stroke_overprint
|
|
181
|
+
map_attribute "TopEdgeStrokeGapColor", to: :top_edge_stroke_gap_color
|
|
182
|
+
map_attribute "TopEdgeStrokeGapTint", to: :top_edge_stroke_gap_tint
|
|
183
|
+
map_attribute "TopEdgeStrokeGapOverprint",
|
|
184
|
+
to: :top_edge_stroke_gap_overprint
|
|
185
|
+
map_attribute "RightEdgeStrokeWeight", to: :right_edge_stroke_weight
|
|
186
|
+
map_attribute "RightEdgeStrokeType", to: :right_edge_stroke_type
|
|
187
|
+
map_attribute "RightEdgeStrokeColor", to: :right_edge_stroke_color
|
|
188
|
+
map_attribute "RightEdgeStrokeTint", to: :right_edge_stroke_tint
|
|
189
|
+
map_attribute "RightEdgeStrokeOverprint",
|
|
190
|
+
to: :right_edge_stroke_overprint
|
|
191
|
+
map_attribute "RightEdgeStrokeGapColor",
|
|
192
|
+
to: :right_edge_stroke_gap_color
|
|
193
|
+
map_attribute "RightEdgeStrokeGapTint", to: :right_edge_stroke_gap_tint
|
|
194
|
+
map_attribute "RightEdgeStrokeGapOverprint",
|
|
195
|
+
to: :right_edge_stroke_gap_overprint
|
|
196
|
+
map_attribute "BottomEdgeStrokeWeight", to: :bottom_edge_stroke_weight
|
|
197
|
+
map_attribute "BottomEdgeStrokeType", to: :bottom_edge_stroke_type
|
|
198
|
+
map_attribute "BottomEdgeStrokeColor", to: :bottom_edge_stroke_color
|
|
199
|
+
map_attribute "BottomEdgeStrokeTint", to: :bottom_edge_stroke_tint
|
|
200
|
+
map_attribute "BottomEdgeStrokeOverprint",
|
|
201
|
+
to: :bottom_edge_stroke_overprint
|
|
202
|
+
map_attribute "BottomEdgeStrokeGapColor",
|
|
203
|
+
to: :bottom_edge_stroke_gap_color
|
|
204
|
+
map_attribute "BottomEdgeStrokeGapTint",
|
|
205
|
+
to: :bottom_edge_stroke_gap_tint
|
|
206
|
+
map_attribute "BottomEdgeStrokeGapOverprint",
|
|
207
|
+
to: :bottom_edge_stroke_gap_overprint
|
|
208
|
+
map_attribute "InnerRowStrokeWeight", to: :inner_row_stroke_weight
|
|
209
|
+
map_attribute "InnerRowStrokeType", to: :inner_row_stroke_type
|
|
210
|
+
map_attribute "InnerRowStrokeColor", to: :inner_row_stroke_color
|
|
211
|
+
map_attribute "InnerRowStrokeTint", to: :inner_row_stroke_tint
|
|
212
|
+
map_attribute "InnerRowStrokeOverprint", to: :inner_row_stroke_overprint
|
|
213
|
+
map_attribute "InnerRowStrokeGapColor", to: :inner_row_stroke_gap_color
|
|
214
|
+
map_attribute "InnerRowStrokeGapTint", to: :inner_row_stroke_gap_tint
|
|
215
|
+
map_attribute "InnerRowStrokeGapOverprint",
|
|
216
|
+
to: :inner_row_stroke_gap_overprint
|
|
217
|
+
map_attribute "InnerColumnStrokeWeight", to: :inner_column_stroke_weight
|
|
218
|
+
map_attribute "InnerColumnStrokeType", to: :inner_column_stroke_type
|
|
219
|
+
map_attribute "InnerColumnStrokeColor", to: :inner_column_stroke_color
|
|
220
|
+
map_attribute "InnerColumnStrokeTint", to: :inner_column_stroke_tint
|
|
221
|
+
map_attribute "InnerColumnStrokeOverprint",
|
|
222
|
+
to: :inner_column_stroke_overprint
|
|
223
|
+
map_attribute "InnerColumnStrokeGapColor",
|
|
224
|
+
to: :inner_column_stroke_gap_color
|
|
225
|
+
map_attribute "InnerColumnStrokeGapTint",
|
|
226
|
+
to: :inner_column_stroke_gap_tint
|
|
227
|
+
map_attribute "InnerColumnStrokeGapOverprint",
|
|
228
|
+
to: :inner_column_stroke_gap_overprint
|
|
229
|
+
map_attribute "TopEdgeStrokePriority", to: :top_edge_stroke_priority
|
|
230
|
+
map_attribute "LeftEdgeStrokePriority", to: :left_edge_stroke_priority
|
|
231
|
+
map_attribute "BottomEdgeStrokePriority",
|
|
232
|
+
to: :bottom_edge_stroke_priority
|
|
233
|
+
map_attribute "RightEdgeStrokePriority", to: :right_edge_stroke_priority
|
|
234
|
+
map_attribute "GraphicLeftInset", to: :graphic_left_inset
|
|
235
|
+
map_attribute "GraphicTopInset", to: :graphic_top_inset
|
|
236
|
+
map_attribute "GraphicRightInset", to: :graphic_right_inset
|
|
237
|
+
map_attribute "GraphicBottomInset", to: :graphic_bottom_inset
|
|
238
|
+
map_attribute "ClipContentToGraphicCell",
|
|
239
|
+
to: :clip_content_to_graphic_cell
|
|
240
|
+
map_attribute "WritingDirection", to: :writing_direction
|
|
241
|
+
map_attribute "AppliedCellStylePriority",
|
|
242
|
+
to: :applied_cell_style_priority
|
|
61
243
|
map_element "ParagraphStyleRange", to: :paragraph_style_range
|
|
62
244
|
end
|
|
63
245
|
|
data/lib/idml/render/footnote.rb
CHANGED
|
@@ -128,11 +128,16 @@ module Idml
|
|
|
128
128
|
size: size)
|
|
129
129
|
lines = TextEngine::LineBreaker.break(glyphs: glyphs,
|
|
130
130
|
frame_width: wrap_width)
|
|
131
|
+
limits = TextEngine::Justifier::SpacingLimits.new(
|
|
132
|
+
max_word_spacing: paragraph.maximum_word_spacing,
|
|
133
|
+
max_letter_spacing: paragraph.maximum_letter_spacing,
|
|
134
|
+
)
|
|
131
135
|
lines.each_with_index do |line, index|
|
|
132
136
|
TextEngine::Justifier.justify(
|
|
133
137
|
line: line, frame_width: wrap_width,
|
|
134
138
|
alignment: paragraph.alignment || :left,
|
|
135
|
-
last_line: last_line?(paragraph, run, index, lines)
|
|
139
|
+
last_line: last_line?(paragraph, run, index, lines),
|
|
140
|
+
limits: limits
|
|
136
141
|
)
|
|
137
142
|
end
|
|
138
143
|
leading = TextEngine::VerticalLayout.leading_for(
|
|
@@ -70,15 +70,40 @@ module Idml
|
|
|
70
70
|
# derived from `column_count` attribute or max col + 1.
|
|
71
71
|
def self.render_schema_faithful(canvas, table, box, context)
|
|
72
72
|
layout = SchemaLayout.new(table: table, box: box)
|
|
73
|
-
layout.each_cell do |cell_x, cell_y, cell_w, cell_h, cell
|
|
73
|
+
layout.each_cell do |cell_x, cell_y, cell_w, cell_h, cell,
|
|
74
|
+
col_idx, row_idx|
|
|
75
|
+
render_band_background(canvas, cell, layout, col_idx, row_idx,
|
|
76
|
+
cell_x, cell_y, cell_w, cell_h, context)
|
|
74
77
|
render_cell_background(canvas, cell, cell_x, cell_y, cell_w, cell_h,
|
|
75
78
|
context)
|
|
76
|
-
render_cell_border(canvas,
|
|
79
|
+
render_cell_border(canvas, cell, table, cell_x, cell_y,
|
|
80
|
+
cell_w, cell_h, context)
|
|
77
81
|
render_cell_text(canvas, cell, cell_x, cell_y, cell_h, context)
|
|
78
82
|
end
|
|
79
83
|
end
|
|
80
84
|
private_class_method :render_schema_faithful
|
|
81
85
|
|
|
86
|
+
# Table-level alternating band fill behind a cell. An
|
|
87
|
+
# explicit Cell FillColor wins; Color/None bands and zero
|
|
88
|
+
# tints render nothing.
|
|
89
|
+
def self.render_band_background(canvas, cell, layout, col_idx,
|
|
90
|
+
row_idx, x, y, w, h, context)
|
|
91
|
+
return if cell_fill_color(cell, context)
|
|
92
|
+
|
|
93
|
+
name, tint = layout.band_fill(col_idx, row_idx)
|
|
94
|
+
return unless name
|
|
95
|
+
return if name == "Color/None"
|
|
96
|
+
|
|
97
|
+
color = context.color_resolver&.resolve(name)
|
|
98
|
+
return unless color
|
|
99
|
+
|
|
100
|
+
color = ColorHelper.apply_tint(color, tint)
|
|
101
|
+
canvas.fill_color(ColorHelper.to_canvas(color))
|
|
102
|
+
canvas.rectangle(x, y, w, h)
|
|
103
|
+
canvas.fill
|
|
104
|
+
end
|
|
105
|
+
private_class_method :render_band_background
|
|
106
|
+
|
|
82
107
|
def self.render_cell_background(canvas, cell, x, y, w, h, context)
|
|
83
108
|
color = cell_fill_color(cell, context)
|
|
84
109
|
return unless color
|
|
@@ -89,12 +114,102 @@ module Idml
|
|
|
89
114
|
end
|
|
90
115
|
private_class_method :render_cell_background
|
|
91
116
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
117
|
+
# Cell borders: per-side strokes when the cell or table
|
|
118
|
+
# declare edge weights (legacy single rect otherwise, so
|
|
119
|
+
# undeclared tables keep their existing look). Top/bottom
|
|
120
|
+
# fall back to the table's default row stroke, left/right
|
|
121
|
+
# to the default column stroke; zero-weight sides draw
|
|
122
|
+
# nothing.
|
|
123
|
+
def self.render_cell_border(canvas, cell, table, x, y, w, h, context)
|
|
124
|
+
sides = cell_edge_weights(cell, table)
|
|
125
|
+
if sides.nil?
|
|
126
|
+
canvas.rectangle(x, y, w, h)
|
|
127
|
+
canvas.stroke
|
|
128
|
+
return
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
top, right, bottom, left = sides
|
|
132
|
+
stroke_cell_side(canvas, context,
|
|
133
|
+
cell.top_edge_stroke_color,
|
|
134
|
+
table.default_row_stroke_color, top) do
|
|
135
|
+
canvas.move_to(x, y + h)
|
|
136
|
+
canvas.line_to(x + w, y + h)
|
|
137
|
+
end
|
|
138
|
+
stroke_cell_side(canvas, context,
|
|
139
|
+
cell.bottom_edge_stroke_color,
|
|
140
|
+
table.default_row_stroke_color, bottom) do
|
|
141
|
+
canvas.move_to(x, y)
|
|
142
|
+
canvas.line_to(x + w, y)
|
|
143
|
+
end
|
|
144
|
+
stroke_cell_side(canvas, context,
|
|
145
|
+
cell.left_edge_stroke_color,
|
|
146
|
+
table.default_column_stroke_color, left) do
|
|
147
|
+
canvas.move_to(x, y)
|
|
148
|
+
canvas.line_to(x, y + h)
|
|
149
|
+
end
|
|
150
|
+
stroke_cell_side(canvas, context,
|
|
151
|
+
cell.right_edge_stroke_color,
|
|
152
|
+
table.default_column_stroke_color, right) do
|
|
153
|
+
canvas.move_to(x + w, y)
|
|
154
|
+
canvas.line_to(x + w, y + h)
|
|
155
|
+
end
|
|
95
156
|
end
|
|
96
157
|
private_class_method :render_cell_border
|
|
97
158
|
|
|
159
|
+
# [top, right, bottom, left] effective weights, or nil when
|
|
160
|
+
# neither cell edges nor table defaults declare any stroke
|
|
161
|
+
# (the legacy uniform-rect case).
|
|
162
|
+
def self.cell_edge_weights(cell, table)
|
|
163
|
+
return nil unless stroke_declared?(cell, table)
|
|
164
|
+
|
|
165
|
+
[
|
|
166
|
+
side_weight(cell.top_edge_stroke_weight,
|
|
167
|
+
table.default_row_stroke_weight),
|
|
168
|
+
side_weight(cell.right_edge_stroke_weight,
|
|
169
|
+
table.default_column_stroke_weight),
|
|
170
|
+
side_weight(cell.bottom_edge_stroke_weight,
|
|
171
|
+
table.default_row_stroke_weight),
|
|
172
|
+
side_weight(cell.left_edge_stroke_weight,
|
|
173
|
+
table.default_column_stroke_weight),
|
|
174
|
+
]
|
|
175
|
+
end
|
|
176
|
+
private_class_method :cell_edge_weights
|
|
177
|
+
|
|
178
|
+
def self.stroke_declared?(cell, table)
|
|
179
|
+
[
|
|
180
|
+
cell.top_edge_stroke_weight, cell.right_edge_stroke_weight,
|
|
181
|
+
cell.bottom_edge_stroke_weight, cell.left_edge_stroke_weight,
|
|
182
|
+
table.default_row_stroke_weight,
|
|
183
|
+
table.default_column_stroke_weight
|
|
184
|
+
].compact.any?
|
|
185
|
+
end
|
|
186
|
+
private_class_method :stroke_declared?
|
|
187
|
+
|
|
188
|
+
def self.side_weight(cell_weight, default_weight)
|
|
189
|
+
cell_weight || default_weight || 0
|
|
190
|
+
end
|
|
191
|
+
private_class_method :side_weight
|
|
192
|
+
|
|
193
|
+
def self.stroke_cell_side(canvas, context, cell_color,
|
|
194
|
+
table_color, weight)
|
|
195
|
+
thickness = weight.to_f
|
|
196
|
+
return unless thickness.positive?
|
|
197
|
+
|
|
198
|
+
apply_cell_stroke_color(canvas, context, cell_color, table_color)
|
|
199
|
+
canvas.line_width = thickness
|
|
200
|
+
yield
|
|
201
|
+
canvas.stroke
|
|
202
|
+
end
|
|
203
|
+
private_class_method :stroke_cell_side
|
|
204
|
+
|
|
205
|
+
def self.apply_cell_stroke_color(canvas, context, cell_color,
|
|
206
|
+
table_color)
|
|
207
|
+
name = cell_color || table_color
|
|
208
|
+
color = name && context.color_resolver&.resolve(name)
|
|
209
|
+
canvas.stroke_color(color ? ColorHelper.to_canvas(color) : [:gray, 1.0])
|
|
210
|
+
end
|
|
211
|
+
private_class_method :apply_cell_stroke_color
|
|
212
|
+
|
|
98
213
|
def self.cell_fill_color(cell, context)
|
|
99
214
|
return nil unless cell.fill_color
|
|
100
215
|
return nil if cell.fill_color == "Color/None"
|
|
@@ -244,7 +359,7 @@ module Idml
|
|
|
244
359
|
|
|
245
360
|
yield cell_x(col_idx), cell_y(row_idx, col_span, row_span),
|
|
246
361
|
cell_w(col_idx, col_span), cell_h(row_idx, row_span),
|
|
247
|
-
cell
|
|
362
|
+
cell, col_idx, row_idx
|
|
248
363
|
end
|
|
249
364
|
end
|
|
250
365
|
end
|
|
@@ -269,6 +384,79 @@ module Idml
|
|
|
269
384
|
table.row
|
|
270
385
|
end
|
|
271
386
|
|
|
387
|
+
# Table-level alternating band fill for a cell position:
|
|
388
|
+
# [color_name, tint] or nil. Row banding by default; column
|
|
389
|
+
# banding when ColumnFillsPriority is true or rows are
|
|
390
|
+
# unconfigured.
|
|
391
|
+
def band_fill(col, row)
|
|
392
|
+
column_band = column_band_fill(col)
|
|
393
|
+
return column_band if column_band &&
|
|
394
|
+
(table.column_fills_priority || row_band_fill(row).nil?)
|
|
395
|
+
|
|
396
|
+
row_band_fill(row)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def row_band_fill(row)
|
|
400
|
+
band_pair(table.start_row_fill_color, table.start_row_fill_count,
|
|
401
|
+
table.end_row_fill_color, table.end_row_fill_count,
|
|
402
|
+
table.start_row_fill_tint, table.end_row_fill_tint,
|
|
403
|
+
row, row_count,
|
|
404
|
+
table.skip_first_alternating_fill_rows,
|
|
405
|
+
table.skip_last_alternating_fill_rows)
|
|
406
|
+
end
|
|
407
|
+
private :row_band_fill
|
|
408
|
+
|
|
409
|
+
def column_band_fill(col)
|
|
410
|
+
band_pair(table.start_column_fill_color,
|
|
411
|
+
table.start_column_fill_count,
|
|
412
|
+
table.end_column_fill_color,
|
|
413
|
+
table.end_column_fill_count,
|
|
414
|
+
table.start_column_fill_tint,
|
|
415
|
+
table.end_column_fill_tint,
|
|
416
|
+
col, col_count,
|
|
417
|
+
table.skip_first_alternating_fill_columns,
|
|
418
|
+
table.skip_last_alternating_fill_columns)
|
|
419
|
+
end
|
|
420
|
+
private :column_band_fill
|
|
421
|
+
|
|
422
|
+
# Alternating band for one axis: the first `start_count`
|
|
423
|
+
# positions take the start color, the next `end_count` the
|
|
424
|
+
# end color, repeating from after the skipped edge rows.
|
|
425
|
+
# Zero-count cycles band nothing.
|
|
426
|
+
def band_pair(start_color, start_count, end_color, end_count,
|
|
427
|
+
start_tint, end_tint, index, total,
|
|
428
|
+
skip_first, skip_last)
|
|
429
|
+
return nil unless start_color
|
|
430
|
+
return nil if suppressed?(index, total, skip_first, skip_last)
|
|
431
|
+
|
|
432
|
+
cycle = band_cycle(start_count, end_count)
|
|
433
|
+
return nil unless cycle.positive?
|
|
434
|
+
|
|
435
|
+
if in_start_band?(index, skip_first, cycle, start_count)
|
|
436
|
+
[start_color, start_tint]
|
|
437
|
+
else
|
|
438
|
+
[end_color, end_tint]
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
private :band_pair
|
|
442
|
+
|
|
443
|
+
def band_cycle(start_count, end_count)
|
|
444
|
+
(start_count || 1) + (end_count || 1)
|
|
445
|
+
end
|
|
446
|
+
private :band_cycle
|
|
447
|
+
|
|
448
|
+
def in_start_band?(index, skip_first, cycle, start_count)
|
|
449
|
+
((index - (skip_first || 0)) % cycle) < (start_count || 1)
|
|
450
|
+
end
|
|
451
|
+
private :in_start_band?
|
|
452
|
+
|
|
453
|
+
# Banding suppressed at the leading/trailing edges.
|
|
454
|
+
def suppressed?(index, total, skip_first, skip_last)
|
|
455
|
+
index < (skip_first || 0) ||
|
|
456
|
+
index >= total - (skip_last || 0)
|
|
457
|
+
end
|
|
458
|
+
private :suppressed?
|
|
459
|
+
|
|
272
460
|
def cells
|
|
273
461
|
table.cell
|
|
274
462
|
end
|
|
@@ -410,6 +410,7 @@ module Idml
|
|
|
410
410
|
pending = 0
|
|
411
411
|
state.paragraphs.each do |paragraph|
|
|
412
412
|
break if cursor_y < bottom_limit
|
|
413
|
+
break if paragraph_break?(paragraph) && pending.positive?
|
|
413
414
|
|
|
414
415
|
remaining_runs, cursor_y, char_cursor = render_runs_for_paragraph(
|
|
415
416
|
canvas, paragraph, paragraph.runs, context,
|
|
@@ -741,16 +742,30 @@ module Idml
|
|
|
741
742
|
end
|
|
742
743
|
private_class_method :layout_run
|
|
743
744
|
|
|
745
|
+
# True when the paragraph requests a forced break to the
|
|
746
|
+
# next frame/column (StartParagraph). All break flavors act
|
|
747
|
+
# at frame/column granularity.
|
|
748
|
+
def self.paragraph_break?(paragraph)
|
|
749
|
+
%w[NextPage NextColumn NextFrame NextOddPage NextEvenPage]
|
|
750
|
+
.include?(paragraph.start_paragraph)
|
|
751
|
+
end
|
|
752
|
+
private_class_method :paragraph_break?
|
|
753
|
+
|
|
744
754
|
# Justifies the run's lines; only the final line of the
|
|
745
755
|
# paragraph's final run stays ragged under full
|
|
746
756
|
# justification.
|
|
747
757
|
def self.justify_lines(lines, paragraph, wrap_width,
|
|
748
758
|
paragraph_last)
|
|
759
|
+
limits = TextEngine::Justifier::SpacingLimits.new(
|
|
760
|
+
max_word_spacing: paragraph.maximum_word_spacing,
|
|
761
|
+
max_letter_spacing: paragraph.maximum_letter_spacing,
|
|
762
|
+
)
|
|
749
763
|
lines.each_with_index do |line, index|
|
|
750
764
|
TextEngine::Justifier.justify(
|
|
751
765
|
line: line, frame_width: wrap_width,
|
|
752
766
|
alignment: paragraph.alignment || :left,
|
|
753
|
-
last_line: paragraph_last && index == lines.length - 1
|
|
767
|
+
last_line: paragraph_last && index == lines.length - 1,
|
|
768
|
+
limits: limits
|
|
754
769
|
)
|
|
755
770
|
end
|
|
756
771
|
end
|
|
@@ -64,6 +64,12 @@ module Idml
|
|
|
64
64
|
:right_indent,
|
|
65
65
|
:auto_leading,
|
|
66
66
|
:drop_cap_lines,
|
|
67
|
+
# Justification distribution caps (percent; from PSR).
|
|
68
|
+
:maximum_word_spacing,
|
|
69
|
+
:maximum_letter_spacing,
|
|
70
|
+
# Forced paragraph break (StartParagraph: NextPage /
|
|
71
|
+
# NextColumn / NextFrame / NextOddPage / NextEvenPage).
|
|
72
|
+
:start_paragraph,
|
|
67
73
|
:bullets_and_numbering_list_type,
|
|
68
74
|
:bullet_character_value,
|
|
69
75
|
:bullets_text_after,
|
|
@@ -183,6 +189,14 @@ module Idml
|
|
|
183
189
|
left_indent: resolve_attr(style_lookup, psr, :left_indent),
|
|
184
190
|
right_indent: resolve_attr(style_lookup, psr, :right_indent),
|
|
185
191
|
auto_leading: resolve_attr(style_lookup, psr, :auto_leading),
|
|
192
|
+
maximum_word_spacing: resolve_attr(
|
|
193
|
+
style_lookup, psr, :maximum_word_spacing
|
|
194
|
+
),
|
|
195
|
+
maximum_letter_spacing: resolve_attr(
|
|
196
|
+
style_lookup, psr, :maximum_letter_spacing
|
|
197
|
+
),
|
|
198
|
+
start_paragraph: resolve_attr(style_lookup, psr,
|
|
199
|
+
:start_paragraph),
|
|
186
200
|
drop_cap_lines: resolve_attr(style_lookup, psr, :drop_cap_lines),
|
|
187
201
|
drop_cap_characters: resolve_attr(style_lookup, psr,
|
|
188
202
|
:drop_cap_characters),
|
|
@@ -7,15 +7,25 @@ module Idml
|
|
|
7
7
|
# spaces for justified text. `last_line:` (the paragraph's final
|
|
8
8
|
# line) keeps ragged right under :justified, as InDesign does.
|
|
9
9
|
class Justifier
|
|
10
|
+
# Justification distribution caps, in percent (100 = natural).
|
|
11
|
+
# InDesign defaults: word spacing max 133%, letter spacing 0%.
|
|
12
|
+
SpacingLimits = Struct.new(
|
|
13
|
+
:max_word_spacing, :max_letter_spacing, keyword_init: true
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
DEFAULT_MAX_WORD_SPACING = 133.0
|
|
17
|
+
DEFAULT_MAX_LETTER_SPACING = 0.0
|
|
18
|
+
|
|
10
19
|
def self.justify(line:, frame_width:, alignment: :left,
|
|
11
|
-
last_line: false)
|
|
12
|
-
new(frame_width, alignment, last_line).justify(line)
|
|
20
|
+
last_line: false, limits: nil)
|
|
21
|
+
new(frame_width, alignment, last_line, limits).justify(line)
|
|
13
22
|
end
|
|
14
23
|
|
|
15
|
-
def initialize(frame_width, alignment, last_line)
|
|
24
|
+
def initialize(frame_width, alignment, last_line, limits)
|
|
16
25
|
@frame_width = frame_width
|
|
17
26
|
@alignment = alignment
|
|
18
27
|
@last_line = last_line
|
|
28
|
+
@limits = limits
|
|
19
29
|
end
|
|
20
30
|
|
|
21
31
|
def justify(line)
|
|
@@ -45,13 +55,42 @@ module Idml
|
|
|
45
55
|
slack = @frame_width - line.width
|
|
46
56
|
return if spaces.zero? || slack <= 0
|
|
47
57
|
|
|
48
|
-
|
|
58
|
+
word_share = [slack, word_capacity(line, spaces)].min
|
|
59
|
+
extra = word_share / spaces
|
|
49
60
|
line.glyphs.each do |glyph|
|
|
50
61
|
next unless glyph.is_space
|
|
51
62
|
|
|
52
63
|
glyph.width += extra
|
|
53
64
|
end
|
|
65
|
+
residual = slack - word_share
|
|
66
|
+
distribute_letter_spacing(line, residual) if residual.positive?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Total room the word spaces may grow within the max-word-
|
|
70
|
+
# spacing cap (percent of each space's natural width).
|
|
71
|
+
def word_capacity(line, _spaces)
|
|
72
|
+
max_pct = @limits&.max_word_spacing || DEFAULT_MAX_WORD_SPACING
|
|
73
|
+
grow = [(max_pct - 100) / 100.0, 0].max
|
|
74
|
+
line.glyphs.sum { |g| g.is_space ? g.width * grow : 0.0 } * 1.0
|
|
75
|
+
end
|
|
76
|
+
private :word_capacity
|
|
77
|
+
|
|
78
|
+
# Even per-glyph letter spacing for residual slack, capped at
|
|
79
|
+
# max_letter_spacing percent of the natural space width. With
|
|
80
|
+
# the default 0% cap nothing is added — the line ends short,
|
|
81
|
+
# as InDesign does when limits bind.
|
|
82
|
+
def distribute_letter_spacing(line, residual)
|
|
83
|
+
max_pct = @limits&.max_letter_spacing || DEFAULT_MAX_LETTER_SPACING
|
|
84
|
+
return unless max_pct.positive?
|
|
85
|
+
|
|
86
|
+
space_ref = line.glyphs.find(&:is_space)&.width || 0
|
|
87
|
+
return unless space_ref.positive?
|
|
88
|
+
|
|
89
|
+
cap = (max_pct / 100.0) * space_ref * line.glyphs.length
|
|
90
|
+
add = [residual, cap].min / line.glyphs.length
|
|
91
|
+
line.glyphs.each { |glyph| glyph.width += add }
|
|
54
92
|
end
|
|
93
|
+
private :distribute_letter_spacing
|
|
55
94
|
end
|
|
56
95
|
end
|
|
57
96
|
end
|
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.9.
|
|
4
|
+
version: 0.9.4
|
|
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-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bigdecimal
|
|
@@ -175,7 +175,11 @@ files:
|
|
|
175
175
|
- TODO.pdf/116-applied-style-resolution.md
|
|
176
176
|
- TODO.pdf/116-justification-last-line.md
|
|
177
177
|
- TODO.pdf/117-endnote-support.md
|
|
178
|
+
- TODO.pdf/118-table-band-fills.md
|
|
179
|
+
- TODO.pdf/119-justification-spacing-limits.md
|
|
178
180
|
- TODO.pdf/12-idml-to-pdf-pipeline.md
|
|
181
|
+
- TODO.pdf/120-start-paragraph-breaks.md
|
|
182
|
+
- TODO.pdf/121-cell-edge-strokes.md
|
|
179
183
|
- TODO.pdf/13-cjk-text-layout.md
|
|
180
184
|
- TODO.pdf/14-page-item-element-models.md
|
|
181
185
|
- TODO.pdf/15-wire-spread-children.md
|