notare 0.0.4 → 0.0.5
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/README.md +72 -3
- data/lib/notare/builder.rb +4 -4
- data/lib/notare/document.rb +3 -3
- data/lib/notare/nodes/table.rb +4 -2
- data/lib/notare/nodes/table_cell.rb +4 -3
- data/lib/notare/version.rb +1 -1
- data/lib/notare/width_parser.rb +31 -0
- data/lib/notare/xml/document_xml.rb +76 -12
- data/lib/notare/xml/styles_xml.rb +23 -2
- data/lib/notare.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 959b59c2f2dc30ff265115057c1fd304f3b7747adfe16c9818e6de9e5c564fc1
|
|
4
|
+
data.tar.gz: 6c3da88bce319ed81fdb519308d6854c00f6a846343e37178e6bafab2b1fefbb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '00558f142269e3927cdb5aab15271a92533a48ff5ff2cf19c349e8757ccd9da77af9d27a249bc1a2551c910b8578a8bc9a354622cea7c20424183b7230bc06b9'
|
|
7
|
+
data.tar.gz: 2f179ca2e773afe314151b61fb9de4e48a6b0037bba3ce50397636b9f2a26790f5e0399c11f5d807c4f3a3090f562e094d0e02da40b4a77719f1bd4c3ea7ee8a
|
data/README.md
CHANGED
|
@@ -332,6 +332,75 @@ doc.table(style: :borderless) do
|
|
|
332
332
|
end
|
|
333
333
|
```
|
|
334
334
|
|
|
335
|
+
#### Column Sizing
|
|
336
|
+
|
|
337
|
+
Control table column widths with layout modes and explicit sizing.
|
|
338
|
+
|
|
339
|
+
**Auto-layout** - columns adjust to fit content:
|
|
340
|
+
|
|
341
|
+
```ruby
|
|
342
|
+
doc.table(layout: :auto) do
|
|
343
|
+
doc.tr do
|
|
344
|
+
doc.td "Short"
|
|
345
|
+
doc.td "This column expands to fit longer content"
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**Fixed column widths** - specify widths for all columns:
|
|
351
|
+
|
|
352
|
+
```ruby
|
|
353
|
+
# Inches
|
|
354
|
+
doc.table(columns: %w[2in 3in 1.5in]) do
|
|
355
|
+
doc.tr do
|
|
356
|
+
doc.td "2 inches"
|
|
357
|
+
doc.td "3 inches"
|
|
358
|
+
doc.td "1.5 inches"
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Centimeters
|
|
363
|
+
doc.table(columns: %w[5cm 10cm]) do
|
|
364
|
+
doc.tr { doc.td "5cm"; doc.td "10cm" }
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# Percentages
|
|
368
|
+
doc.table(columns: %w[25% 50% 25%]) do
|
|
369
|
+
doc.tr { doc.td "Quarter"; doc.td "Half"; doc.td "Quarter" }
|
|
370
|
+
end
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**Per-cell widths** - set width on individual cells:
|
|
374
|
+
|
|
375
|
+
```ruby
|
|
376
|
+
doc.table do
|
|
377
|
+
doc.tr do
|
|
378
|
+
doc.td("Narrow", width: "1in")
|
|
379
|
+
doc.td("Wide", width: "4in")
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**Combined layout and columns:**
|
|
385
|
+
|
|
386
|
+
```ruby
|
|
387
|
+
doc.table(layout: :fixed, columns: %w[2in 2in 2in]) do
|
|
388
|
+
doc.tr do
|
|
389
|
+
doc.td "A"
|
|
390
|
+
doc.td "B"
|
|
391
|
+
doc.td "C"
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**Width formats:**
|
|
397
|
+
|
|
398
|
+
| Format | Example | Description |
|
|
399
|
+
|--------|---------|-------------|
|
|
400
|
+
| Inches | `"2in"` | Fixed width in inches |
|
|
401
|
+
| Centimeters | `"5cm"` | Fixed width in centimeters |
|
|
402
|
+
| Percentage | `"50%"` | Percentage of table width |
|
|
403
|
+
|
|
335
404
|
### Images
|
|
336
405
|
|
|
337
406
|
Images can be added to paragraphs, table cells, and list items. Supports PNG and JPEG formats.
|
|
@@ -526,10 +595,10 @@ end
|
|
|
526
595
|
| `li(text)` | List item with text |
|
|
527
596
|
| `li { }` | List item with block content |
|
|
528
597
|
| `li(text) { }` | List item with text and nested content |
|
|
529
|
-
| `table(style:) { }` | Table with optional style |
|
|
598
|
+
| `table(style:, layout:, columns:) { }` | Table with optional style, layout (`:auto`/`:fixed`), and column widths |
|
|
530
599
|
| `tr { }` | Table row |
|
|
531
|
-
| `td(text)` | Table cell with text |
|
|
532
|
-
| `td { }` | Table cell with block content |
|
|
600
|
+
| `td(text, width:)` | Table cell with text and optional width |
|
|
601
|
+
| `td(width:) { }` | Table cell with block content and optional width |
|
|
533
602
|
| `image(path, width:, height:)` | Insert image (PNG/JPEG). Dimensions: `"2in"`, `"5cm"`, `"100px"`, or integer pixels |
|
|
534
603
|
|
|
535
604
|
## Development
|
data/lib/notare/builder.rb
CHANGED
|
@@ -100,8 +100,8 @@ module Notare
|
|
|
100
100
|
@current_list.add_item(item)
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
-
def table(style: nil, &block)
|
|
104
|
-
tbl = Nodes::Table.new(style: resolve_table_style(style))
|
|
103
|
+
def table(style: nil, layout: nil, columns: nil, &block)
|
|
104
|
+
tbl = Nodes::Table.new(style: resolve_table_style(style), layout: layout, columns: columns)
|
|
105
105
|
previous_table = @current_table
|
|
106
106
|
@current_table = tbl
|
|
107
107
|
block.call
|
|
@@ -118,8 +118,8 @@ module Notare
|
|
|
118
118
|
@current_table.add_row(row)
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
-
def td(text = nil, &block)
|
|
122
|
-
cell = Nodes::TableCell.new
|
|
121
|
+
def td(text = nil, width: nil, &block)
|
|
122
|
+
cell = Nodes::TableCell.new(width: width)
|
|
123
123
|
if block
|
|
124
124
|
with_target(cell, &block)
|
|
125
125
|
elsif text
|
data/lib/notare/document.rb
CHANGED
|
@@ -88,13 +88,13 @@ module Notare
|
|
|
88
88
|
def next_image_rid
|
|
89
89
|
# rId1 = styles.xml (always present)
|
|
90
90
|
# rId2 = numbering.xml (if lists present)
|
|
91
|
-
# rId3+ = images
|
|
91
|
+
# rId3+ = images and hyperlinks share the same ID space
|
|
92
92
|
base = @has_lists ? 3 : 2
|
|
93
|
-
"rId#{base + @images.size}"
|
|
93
|
+
"rId#{base + @images.size + @hyperlinks.size}"
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
def next_hyperlink_rid
|
|
97
|
-
#
|
|
97
|
+
# Images and hyperlinks share the same ID space
|
|
98
98
|
base = @has_lists ? 3 : 2
|
|
99
99
|
"rId#{base + @images.size + @hyperlinks.size}"
|
|
100
100
|
end
|
data/lib/notare/nodes/table.rb
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
module Notare
|
|
4
4
|
module Nodes
|
|
5
5
|
class Table < Base
|
|
6
|
-
attr_reader :rows, :style
|
|
6
|
+
attr_reader :rows, :style, :layout, :columns
|
|
7
7
|
|
|
8
|
-
def initialize(style: nil)
|
|
8
|
+
def initialize(style: nil, layout: nil, columns: nil)
|
|
9
9
|
super()
|
|
10
10
|
@rows = []
|
|
11
11
|
@style = style
|
|
12
|
+
@layout = layout
|
|
13
|
+
@columns = columns
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def add_row(row)
|
data/lib/notare/version.rb
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Notare
|
|
4
|
+
module WidthParser
|
|
5
|
+
TWIPS_PER_INCH = 1440
|
|
6
|
+
TWIPS_PER_CM = 567
|
|
7
|
+
PCT_MULTIPLIER = 50
|
|
8
|
+
|
|
9
|
+
ParsedWidth = Struct.new(:value, :type, keyword_init: true)
|
|
10
|
+
|
|
11
|
+
def self.parse(value)
|
|
12
|
+
case value
|
|
13
|
+
when :auto, nil
|
|
14
|
+
ParsedWidth.new(value: 0, type: "auto")
|
|
15
|
+
when Integer
|
|
16
|
+
ParsedWidth.new(value: value, type: "dxa")
|
|
17
|
+
when /\A(\d+(?:\.\d+)?)\s*in\z/i
|
|
18
|
+
twips = (::Regexp.last_match(1).to_f * TWIPS_PER_INCH).to_i
|
|
19
|
+
ParsedWidth.new(value: twips, type: "dxa")
|
|
20
|
+
when /\A(\d+(?:\.\d+)?)\s*cm\z/i
|
|
21
|
+
twips = (::Regexp.last_match(1).to_f * TWIPS_PER_CM).to_i
|
|
22
|
+
ParsedWidth.new(value: twips, type: "dxa")
|
|
23
|
+
when /\A(\d+(?:\.\d+)?)\s*%\z/
|
|
24
|
+
pct = (::Regexp.last_match(1).to_f * PCT_MULTIPLIER).to_i
|
|
25
|
+
ParsedWidth.new(value: pct, type: "pct")
|
|
26
|
+
else
|
|
27
|
+
raise ArgumentError, "Invalid width: #{value}. Use '2in', '5cm', '50%', :auto, or integer twips."
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -163,12 +163,12 @@ module Notare
|
|
|
163
163
|
end
|
|
164
164
|
|
|
165
165
|
def render_table(xml, table)
|
|
166
|
-
|
|
167
|
-
col_width = 5000 / column_count
|
|
166
|
+
column_widths = compute_column_widths(table)
|
|
168
167
|
|
|
169
168
|
xml["w"].tbl do
|
|
170
169
|
xml["w"].tblPr do
|
|
171
|
-
xml
|
|
170
|
+
render_table_width(xml, column_widths)
|
|
171
|
+
render_table_layout(xml, table.layout)
|
|
172
172
|
if table.style
|
|
173
173
|
xml["w"].tblStyle("w:val" => table.style.style_id)
|
|
174
174
|
else
|
|
@@ -179,25 +179,89 @@ module Notare
|
|
|
179
179
|
end
|
|
180
180
|
end
|
|
181
181
|
end
|
|
182
|
-
xml
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
render_table_grid(xml, column_widths)
|
|
183
|
+
table.rows.each { |row| render_table_row(xml, row, column_widths) }
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def compute_column_widths(table)
|
|
188
|
+
if table.columns
|
|
189
|
+
table.columns.map { |c| WidthParser.parse(c) }
|
|
190
|
+
elsif table.layout == :auto
|
|
191
|
+
first_row = table.rows.first
|
|
192
|
+
cell_count = first_row&.cells&.size || 1
|
|
193
|
+
Array.new(cell_count) { WidthParser::ParsedWidth.new(value: 0, type: "auto") }
|
|
194
|
+
else
|
|
195
|
+
infer_widths_from_first_row(table)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def infer_widths_from_first_row(table)
|
|
200
|
+
first_row = table.rows.first
|
|
201
|
+
return [WidthParser::ParsedWidth.new(value: 5000, type: "pct")] unless first_row
|
|
202
|
+
|
|
203
|
+
cells = first_row.cells
|
|
204
|
+
has_explicit_widths = cells.any?(&:width)
|
|
205
|
+
|
|
206
|
+
if has_explicit_widths
|
|
207
|
+
cells.map do |cell|
|
|
208
|
+
cell.width ? WidthParser.parse(cell.width) : WidthParser::ParsedWidth.new(value: 0, type: "auto")
|
|
186
209
|
end
|
|
187
|
-
|
|
210
|
+
else
|
|
211
|
+
col_width = 5000 / cells.size
|
|
212
|
+
cells.map { WidthParser::ParsedWidth.new(value: col_width, type: "pct") }
|
|
188
213
|
end
|
|
189
214
|
end
|
|
190
215
|
|
|
191
|
-
def
|
|
216
|
+
def render_table_width(xml, column_widths)
|
|
217
|
+
if column_widths.all? { |w| w.type == "pct" }
|
|
218
|
+
total = column_widths.sum(&:value)
|
|
219
|
+
xml["w"].tblW("w:w" => total.to_s, "w:type" => "pct")
|
|
220
|
+
elsif column_widths.all? { |w| w.type == "dxa" }
|
|
221
|
+
total = column_widths.sum(&:value)
|
|
222
|
+
xml["w"].tblW("w:w" => total.to_s, "w:type" => "dxa")
|
|
223
|
+
else
|
|
224
|
+
xml["w"].tblW("w:w" => "0", "w:type" => "auto")
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def render_table_layout(xml, layout)
|
|
229
|
+
return unless layout
|
|
230
|
+
|
|
231
|
+
layout_type = layout == :auto ? "autofit" : "fixed"
|
|
232
|
+
xml["w"].tblLayout("w:type" => layout_type)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def render_table_grid(xml, column_widths)
|
|
236
|
+
xml["w"].tblGrid do
|
|
237
|
+
column_widths.each do |width|
|
|
238
|
+
grid_width = case width.type
|
|
239
|
+
when "pct" then pct_to_approximate_dxa(width.value)
|
|
240
|
+
when "auto" then 1440 # Default 1 inch for auto columns
|
|
241
|
+
else width.value
|
|
242
|
+
end
|
|
243
|
+
xml["w"].gridCol("w:w" => grid_width.to_s)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Convert percentage (in fiftieths) to approximate twips
|
|
249
|
+
# Assumes 6.5 inch content width = 9360 twips
|
|
250
|
+
def pct_to_approximate_dxa(pct_value)
|
|
251
|
+
(pct_value * 9360 / 5000.0).to_i
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def render_table_row(xml, row, column_widths)
|
|
192
255
|
xml["w"].tr do
|
|
193
|
-
row.cells.
|
|
256
|
+
row.cells.each_with_index { |cell, idx| render_table_cell(xml, cell, column_widths[idx]) }
|
|
194
257
|
end
|
|
195
258
|
end
|
|
196
259
|
|
|
197
|
-
def render_table_cell(xml, cell,
|
|
260
|
+
def render_table_cell(xml, cell, column_width)
|
|
261
|
+
width = column_width || WidthParser::ParsedWidth.new(value: 0, type: "auto")
|
|
198
262
|
xml["w"].tc do
|
|
199
263
|
xml["w"].tcPr do
|
|
200
|
-
xml["w"].tcW("w:w" =>
|
|
264
|
+
xml["w"].tcW("w:w" => width.value.to_s, "w:type" => width.type)
|
|
201
265
|
end
|
|
202
266
|
xml["w"].p do
|
|
203
267
|
cell.runs.each { |run| render_run(xml, run) }
|
|
@@ -32,6 +32,8 @@ module Notare
|
|
|
32
32
|
render_style(xml, style)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
render_table_normal_style(xml) if @table_styles.any?
|
|
36
|
+
|
|
35
37
|
@table_styles.each_value do |style|
|
|
36
38
|
render_table_style(xml, style)
|
|
37
39
|
end
|
|
@@ -57,8 +59,12 @@ module Notare
|
|
|
57
59
|
xml["w"].pPr do
|
|
58
60
|
xml["w"].jc("w:val" => ALIGNMENT_MAP[style.align]) if style.align
|
|
59
61
|
xml["w"].ind("w:left" => style.indent.to_s) if style.indent
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
if style.spacing_before || style.spacing_after
|
|
63
|
+
spacing_attrs = {}
|
|
64
|
+
spacing_attrs["w:before"] = style.spacing_before.to_s if style.spacing_before
|
|
65
|
+
spacing_attrs["w:after"] = style.spacing_after.to_s if style.spacing_after
|
|
66
|
+
xml["w"].spacing(spacing_attrs)
|
|
67
|
+
end
|
|
62
68
|
end
|
|
63
69
|
end
|
|
64
70
|
|
|
@@ -75,9 +81,24 @@ module Notare
|
|
|
75
81
|
end
|
|
76
82
|
end
|
|
77
83
|
|
|
84
|
+
def render_table_normal_style(xml)
|
|
85
|
+
xml["w"].style("w:type" => "table", "w:default" => "1", "w:styleId" => "TableNormal") do
|
|
86
|
+
xml["w"].name("w:val" => "Normal Table")
|
|
87
|
+
xml["w"].tblPr do
|
|
88
|
+
xml["w"].tblCellMar do
|
|
89
|
+
xml["w"].top("w:w" => "0", "w:type" => "dxa")
|
|
90
|
+
xml["w"].left("w:w" => "108", "w:type" => "dxa")
|
|
91
|
+
xml["w"].bottom("w:w" => "0", "w:type" => "dxa")
|
|
92
|
+
xml["w"].right("w:w" => "108", "w:type" => "dxa")
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
78
98
|
def render_table_style(xml, style)
|
|
79
99
|
xml["w"].style("w:type" => "table", "w:styleId" => style.style_id) do
|
|
80
100
|
xml["w"].name("w:val" => style.display_name)
|
|
101
|
+
xml["w"].basedOn("w:val" => "TableNormal")
|
|
81
102
|
|
|
82
103
|
xml["w"].tblPr do
|
|
83
104
|
render_table_borders(xml, style.borders) if style.borders
|
data/lib/notare.rb
CHANGED
|
@@ -17,6 +17,7 @@ require_relative "notare/nodes/table_cell"
|
|
|
17
17
|
require_relative "notare/image_dimensions"
|
|
18
18
|
require_relative "notare/style"
|
|
19
19
|
require_relative "notare/table_style"
|
|
20
|
+
require_relative "notare/width_parser"
|
|
20
21
|
require_relative "notare/xml/content_types"
|
|
21
22
|
require_relative "notare/xml/relationships"
|
|
22
23
|
require_relative "notare/xml/document_xml"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: notare
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mathias
|
|
@@ -135,6 +135,7 @@ files:
|
|
|
135
135
|
- lib/notare/style.rb
|
|
136
136
|
- lib/notare/table_style.rb
|
|
137
137
|
- lib/notare/version.rb
|
|
138
|
+
- lib/notare/width_parser.rb
|
|
138
139
|
- lib/notare/xml/content_types.rb
|
|
139
140
|
- lib/notare/xml/document_xml.rb
|
|
140
141
|
- lib/notare/xml/numbering.rb
|