idml 0.4.6 → 0.4.8
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/Gemfile.lock +2 -2
- data/TODO.pdf/84-schema-faithful-table.md +15 -9
- data/lib/idml/render/renderers/table_renderer.rb +140 -20
- data/lib/idml/render/renderers/text_frame_renderer.rb +29 -1
- data/lib/idml/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bfe99408096a550d69277aae9bf709c0dcb0257f17756fcef498c9ea3982a603
|
|
4
|
+
data.tar.gz: 83fc58f40dccb82324b6098aab6665765cd0e1a08b12ade7e08f6d600c6e69c3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8b9646bd611f3618e6a7c3c689a402b368b231f66f6f06430098f9357179d4b79ae0884058b20c92df03dce6e65aa7cf74c245bb8100a772a9454c37f310b4bd
|
|
7
|
+
data.tar.gz: 7b639edc906f60c9e82128bf599966cd37e2fb0458e06e62158d14767d2293d918bed5fe75163cd2caa2cb20fed7174a0c18a709ac722a673b9099532e8f5b33
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
idml (0.4.
|
|
4
|
+
idml (0.4.8)
|
|
5
5
|
bigdecimal
|
|
6
6
|
lutaml-model (~> 0.8.18)
|
|
7
7
|
pdfrb
|
|
@@ -183,7 +183,7 @@ CHECKSUMS
|
|
|
183
183
|
ffi (1.17.4-x86_64-darwin) sha256=aa70390523cf3235096cf64962b709b4cfbd5c082a2cb2ae714eb0fe2ccda496
|
|
184
184
|
ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
|
|
185
185
|
ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
|
|
186
|
-
idml (0.4.
|
|
186
|
+
idml (0.4.8)
|
|
187
187
|
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
|
|
188
188
|
language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
|
|
189
189
|
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
|
@@ -48,15 +48,21 @@ The existing `Elements::Table`/`TableRow`/`TableCell` classes
|
|
|
48
48
|
`table_renderer_spec.rb` test. They are not removed because no
|
|
49
49
|
real IDML uses them and removing them would break the existing test.
|
|
50
50
|
|
|
51
|
-
##
|
|
52
|
-
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
## What was completed in this round
|
|
52
|
+
|
|
53
|
+
- `TableRenderer` migrated to auto-detect schema-faithful
|
|
54
|
+
(`cell`/`row` siblings) vs legacy (`table_row`/`table_cell`
|
|
55
|
+
nested) layout. New `SchemaLayout` Struct computes per-cell rects
|
|
56
|
+
from `Row#single_row_height` and `Cell#col_row` max col + 1.
|
|
57
|
+
- `TextFrameRenderer` discovers inline Tables via
|
|
58
|
+
`Story > PSR > CSR > Table` and renders each within the frame's
|
|
59
|
+
bounds via `TableRenderer.render_in_box`. This is the path that
|
|
60
|
+
real IDML fixtures use (Tables live in Stories, not Spreads).
|
|
61
|
+
- New `TableRenderer.render_in_box(canvas, table, box, context)`
|
|
62
|
+
API accepts caller-supplied bounds (real Tables have no own
|
|
63
|
+
geometry).
|
|
64
|
+
- The sample-with-table-more fixture's Table now renders 83 cell
|
|
65
|
+
rectangles end-to-end (verified by spec).
|
|
60
66
|
|
|
61
67
|
## Current state vs schema
|
|
62
68
|
|
|
@@ -3,10 +3,22 @@
|
|
|
3
3
|
module Idml
|
|
4
4
|
module Render
|
|
5
5
|
module Renderers
|
|
6
|
-
# Renders an IDML Table. Draws the cell grid via rectangle
|
|
7
|
-
# then renders inline
|
|
8
|
-
#
|
|
6
|
+
# Renders an IDML Table. Draws the cell grid via rectangle
|
|
7
|
+
# ops, then renders inline text in each cell via
|
|
8
|
+
# `canvas.text_rich`. Cells with no text render as empty
|
|
9
9
|
# rectangles.
|
|
10
|
+
#
|
|
11
|
+
# Two table layouts are supported:
|
|
12
|
+
#
|
|
13
|
+
# 1. **Schema-faithful** (real IDML): `Table` has `cell`
|
|
14
|
+
# and `row` sibling collections. Cell `Name` encodes
|
|
15
|
+
# column and row as `"col:row"`. Row order determines
|
|
16
|
+
# vertical position; cell Name determines horizontal.
|
|
17
|
+
# 2. **Legacy** (synthetic test fixture): `Table` has a
|
|
18
|
+
# `table_row` collection, each `TableRow` has a
|
|
19
|
+
# `table_cell` collection. Nested structure.
|
|
20
|
+
#
|
|
21
|
+
# The renderer auto-detects which layout is present.
|
|
10
22
|
class TableRenderer
|
|
11
23
|
DEFAULT_SIZE = 10.0
|
|
12
24
|
INSET = 4.0
|
|
@@ -14,37 +26,71 @@ module Idml
|
|
|
14
26
|
def self.render(canvas, context)
|
|
15
27
|
table = context.item
|
|
16
28
|
return if table.visible == false
|
|
17
|
-
return if table.table_row.empty?
|
|
18
29
|
|
|
19
30
|
box = Placement.box(table, context.page_height)
|
|
20
31
|
return unless box
|
|
21
32
|
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
render_in_box(canvas, table, box, context)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Renders a Table using caller-supplied bounds (no
|
|
37
|
+
# Placement.box lookup). Used by TextFrameRenderer to
|
|
38
|
+
# render Tables inlined in a story — real IDML Tables
|
|
39
|
+
# have no own geometry, so the containing TextFrame's
|
|
40
|
+
# bounds stand in for the Table's bounds.
|
|
41
|
+
def self.render_in_box(canvas, table, box, context)
|
|
42
|
+
return if table.visible == false
|
|
24
43
|
|
|
25
44
|
canvas.save_graphics_state do
|
|
26
|
-
table
|
|
27
|
-
|
|
28
|
-
|
|
45
|
+
if schema_faithful?(table)
|
|
46
|
+
render_schema_faithful(canvas, table, box, context)
|
|
47
|
+
else
|
|
48
|
+
render_legacy(canvas, table, box, context)
|
|
29
49
|
end
|
|
30
50
|
end
|
|
31
51
|
end
|
|
32
52
|
|
|
33
|
-
def self.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return unless cell_count.positive?
|
|
53
|
+
def self.schema_faithful?(table)
|
|
54
|
+
!table.cell.empty? || !table.row.empty?
|
|
55
|
+
end
|
|
56
|
+
private_class_method :schema_faithful?
|
|
38
57
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
# Real IDML: Table > {Cell, Row} siblings. Cell Name is
|
|
59
|
+
# "col:row". Row count from `row` collection; column count
|
|
60
|
+
# derived from max col + 1 across cells.
|
|
61
|
+
def self.render_schema_faithful(canvas, table, box, context)
|
|
62
|
+
layout = SchemaLayout.new(table: table, box: box)
|
|
63
|
+
layout.each_cell do |cell_x, cell_y, cell_w, cell_h, cell|
|
|
64
|
+
canvas.rectangle(cell_x, cell_y, cell_w, cell_h)
|
|
43
65
|
canvas.stroke
|
|
44
|
-
render_cell_text(canvas, cell, cell_x,
|
|
66
|
+
render_cell_text(canvas, cell, cell_x, cell_y, cell_h, context)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
private_class_method :render_schema_faithful
|
|
70
|
+
|
|
71
|
+
# Legacy: Table > TableRow > TableCell nested.
|
|
72
|
+
def self.render_legacy(canvas, table, box, context)
|
|
73
|
+
return if table.table_row.empty?
|
|
74
|
+
|
|
75
|
+
row_count = table.table_row.length
|
|
76
|
+
row_height = box[:height] / row_count
|
|
77
|
+
|
|
78
|
+
table.table_row.each_with_index do |row, row_index|
|
|
79
|
+
row_y = box[:y] + ((row_count - 1 - row_index) * row_height)
|
|
80
|
+
cell_count = row.table_cell.length
|
|
81
|
+
next unless cell_count.positive?
|
|
82
|
+
|
|
83
|
+
cell_width = box[:width] / cell_count
|
|
84
|
+
row.table_cell.each_with_index do |cell, cell_index|
|
|
85
|
+
cell_x = box[:x] + (cell_index * cell_width)
|
|
86
|
+
canvas.rectangle(cell_x, row_y, cell_width, row_height)
|
|
87
|
+
canvas.stroke
|
|
88
|
+
render_cell_text(canvas, cell, cell_x, row_y, row_height,
|
|
89
|
+
context)
|
|
90
|
+
end
|
|
45
91
|
end
|
|
46
92
|
end
|
|
47
|
-
private_class_method :
|
|
93
|
+
private_class_method :render_legacy
|
|
48
94
|
|
|
49
95
|
def self.render_cell_text(canvas, cell, x, y, height, context)
|
|
50
96
|
text = cell.text_content
|
|
@@ -59,6 +105,80 @@ module Idml
|
|
|
59
105
|
canvas.text_rich(runs, at: [x + INSET, baseline])
|
|
60
106
|
end
|
|
61
107
|
private_class_method :render_cell_text
|
|
108
|
+
|
|
109
|
+
# Computes per-cell rects for the schema-faithful layout.
|
|
110
|
+
# Row heights come from `Row#single_row_height` when present,
|
|
111
|
+
# else fall back to evenly-divided table height. Column
|
|
112
|
+
# widths are evenly divided (precise column widths would
|
|
113
|
+
# require ColumnAttributes lookup, deferred).
|
|
114
|
+
SchemaLayout = Struct.new(:table, :box, keyword_init: true) do
|
|
115
|
+
def each_cell
|
|
116
|
+
row_count.times do |row_idx|
|
|
117
|
+
col_count.times do |col_idx|
|
|
118
|
+
cell = cell_at(col_idx, row_idx)
|
|
119
|
+
next unless cell
|
|
120
|
+
|
|
121
|
+
yield cell_x(col_idx), cell_y(row_idx), cell_w, cell_h(row_idx),
|
|
122
|
+
cell
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def row_count
|
|
128
|
+
rows.length
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def col_count
|
|
132
|
+
@col_count ||= begin
|
|
133
|
+
max_col = cells.filter_map(&:col_row).map(&:first).max || 0
|
|
134
|
+
max_col + 1
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def rows
|
|
139
|
+
table.row
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def cells
|
|
143
|
+
table.cell
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def cell_at(col, row)
|
|
147
|
+
cells.find { |c| c.col_row == [col, row] }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def cell_x(col)
|
|
151
|
+
box[:x] + (col * cell_w)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def cell_y(row)
|
|
155
|
+
box[:y] + total_height - cumulative_height(row) - cell_h(row)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def cell_w
|
|
159
|
+
box[:width] / [col_count, 1].max
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def cell_h(row_idx)
|
|
163
|
+
row_heights[row_idx] || (box[:height] / [row_count, 1].max)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def row_heights
|
|
167
|
+
@row_heights ||= rows.map do |row|
|
|
168
|
+
h = row.single_row_height
|
|
169
|
+
h&.positive? ? h : nil
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def total_height
|
|
174
|
+
@total_height ||= row_count.times.sum { |i| cell_h(i) }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def cumulative_height(up_to_row)
|
|
178
|
+
up_to_row.times.sum { |i| cell_h(i) }
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
private_constant :SchemaLayout
|
|
62
182
|
end
|
|
63
183
|
end
|
|
64
184
|
end
|
|
@@ -20,13 +20,41 @@ module Idml
|
|
|
20
20
|
story = context.package&.story_by_id(frame.parent_story)
|
|
21
21
|
return unless story
|
|
22
22
|
|
|
23
|
+
box = frame_box(frame, context.page_height)
|
|
24
|
+
render_inline_tables(canvas, story, box, context)
|
|
25
|
+
|
|
23
26
|
runs = StyleResolver.extract_runs(story)
|
|
24
27
|
return if runs.empty?
|
|
25
28
|
|
|
26
|
-
box = frame_box(frame, context.page_height)
|
|
27
29
|
render_text(canvas, runs, context, box)
|
|
28
30
|
end
|
|
29
31
|
|
|
32
|
+
# Discovers Tables inlined in the story (Story > PSR > CSR >
|
|
33
|
+
# Table — the real IDML structure) and renders each within
|
|
34
|
+
# the frame's bounds. Real IDML Tables have no own geometry,
|
|
35
|
+
# so the containing TextFrame's box stands in. Synthetic
|
|
36
|
+
# spread-level Tables dispatch separately via
|
|
37
|
+
# PageItemRenderer.
|
|
38
|
+
def self.render_inline_tables(canvas, story, box, context)
|
|
39
|
+
tables = tables_in_story(story)
|
|
40
|
+
return if tables.empty?
|
|
41
|
+
|
|
42
|
+
tables.each do |table|
|
|
43
|
+
TableRenderer.render_in_box(canvas, table, box, context)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
private_class_method :render_inline_tables
|
|
47
|
+
|
|
48
|
+
def self.tables_in_story(story)
|
|
49
|
+
inner = story&.inner
|
|
50
|
+
return [] unless inner
|
|
51
|
+
|
|
52
|
+
inner.paragraph_style_range.flat_map do |psr|
|
|
53
|
+
psr.character_style_range.flat_map(&:table)
|
|
54
|
+
end.compact
|
|
55
|
+
end
|
|
56
|
+
private_class_method :tables_in_story
|
|
57
|
+
|
|
30
58
|
def self.render_text(canvas, runs, context, box)
|
|
31
59
|
font = context.font_metrics
|
|
32
60
|
if font
|
data/lib/idml/version.rb
CHANGED