Support table and table column sizing
All checks were successful
CI Pipeline / build (pull_request) Successful in 13s

This commit is contained in:
2025-12-03 13:50:56 +01:00
parent 00cabb6dfb
commit 843466549a
13 changed files with 520 additions and 30 deletions

View File

@@ -163,12 +163,12 @@ module Notare
end
def render_table(xml, table)
column_count = table.rows.first&.cells&.size || 1
col_width = 5000 / column_count
column_widths = compute_column_widths(table)
xml["w"].tbl do
xml["w"].tblPr do
xml["w"].tblW("w:w" => "5000", "w:type" => "pct")
render_table_width(xml, column_widths)
render_table_layout(xml, table.layout)
if table.style
xml["w"].tblStyle("w:val" => table.style.style_id)
else
@@ -179,25 +179,89 @@ module Notare
end
end
end
xml["w"].tblGrid do
column_count.times do
xml["w"].gridCol("w:w" => col_width.to_s)
end
render_table_grid(xml, column_widths)
table.rows.each { |row| render_table_row(xml, row, column_widths) }
end
end
def compute_column_widths(table)
if table.columns
table.columns.map { |c| WidthParser.parse(c) }
elsif table.layout == :auto
first_row = table.rows.first
cell_count = first_row&.cells&.size || 1
Array.new(cell_count) { WidthParser::ParsedWidth.new(value: 0, type: "auto") }
else
infer_widths_from_first_row(table)
end
end
def infer_widths_from_first_row(table)
first_row = table.rows.first
return [WidthParser::ParsedWidth.new(value: 5000, type: "pct")] unless first_row
cells = first_row.cells
has_explicit_widths = cells.any?(&:width)
if has_explicit_widths
cells.map do |cell|
cell.width ? WidthParser.parse(cell.width) : WidthParser::ParsedWidth.new(value: 0, type: "auto")
end
table.rows.each { |row| render_table_row(xml, row, col_width) }
else
col_width = 5000 / cells.size
cells.map { WidthParser::ParsedWidth.new(value: col_width, type: "pct") }
end
end
def render_table_row(xml, row, col_width)
def render_table_width(xml, column_widths)
if column_widths.all? { |w| w.type == "pct" }
total = column_widths.sum(&:value)
xml["w"].tblW("w:w" => total.to_s, "w:type" => "pct")
elsif column_widths.all? { |w| w.type == "dxa" }
total = column_widths.sum(&:value)
xml["w"].tblW("w:w" => total.to_s, "w:type" => "dxa")
else
xml["w"].tblW("w:w" => "0", "w:type" => "auto")
end
end
def render_table_layout(xml, layout)
return unless layout
layout_type = layout == :auto ? "autofit" : "fixed"
xml["w"].tblLayout("w:type" => layout_type)
end
def render_table_grid(xml, column_widths)
xml["w"].tblGrid do
column_widths.each do |width|
grid_width = case width.type
when "pct" then pct_to_approximate_dxa(width.value)
when "auto" then 1440 # Default 1 inch for auto columns
else width.value
end
xml["w"].gridCol("w:w" => grid_width.to_s)
end
end
end
# Convert percentage (in fiftieths) to approximate twips
# Assumes 6.5 inch content width = 9360 twips
def pct_to_approximate_dxa(pct_value)
(pct_value * 9360 / 5000.0).to_i
end
def render_table_row(xml, row, column_widths)
xml["w"].tr do
row.cells.each { |cell| render_table_cell(xml, cell, col_width) }
row.cells.each_with_index { |cell, idx| render_table_cell(xml, cell, column_widths[idx]) }
end
end
def render_table_cell(xml, cell, col_width)
def render_table_cell(xml, cell, column_width)
width = column_width || WidthParser::ParsedWidth.new(value: 0, type: "auto")
xml["w"].tc do
xml["w"].tcPr do
xml["w"].tcW("w:w" => col_width.to_s, "w:type" => "pct")
xml["w"].tcW("w:w" => width.value.to_s, "w:type" => width.type)
end
xml["w"].p do
cell.runs.each { |run| render_run(xml, run) }

View File

@@ -32,6 +32,8 @@ module Notare
render_style(xml, style)
end
render_table_normal_style(xml) if @table_styles.any?
@table_styles.each_value do |style|
render_table_style(xml, style)
end
@@ -57,8 +59,12 @@ module Notare
xml["w"].pPr do
xml["w"].jc("w:val" => ALIGNMENT_MAP[style.align]) if style.align
xml["w"].ind("w:left" => style.indent.to_s) if style.indent
xml["w"].spacing("w:before" => style.spacing_before.to_s) if style.spacing_before
xml["w"].spacing("w:after" => style.spacing_after.to_s) if style.spacing_after
if style.spacing_before || style.spacing_after
spacing_attrs = {}
spacing_attrs["w:before"] = style.spacing_before.to_s if style.spacing_before
spacing_attrs["w:after"] = style.spacing_after.to_s if style.spacing_after
xml["w"].spacing(spacing_attrs)
end
end
end
@@ -75,9 +81,24 @@ module Notare
end
end
def render_table_normal_style(xml)
xml["w"].style("w:type" => "table", "w:default" => "1", "w:styleId" => "TableNormal") do
xml["w"].name("w:val" => "Normal Table")
xml["w"].tblPr do
xml["w"].tblCellMar do
xml["w"].top("w:w" => "0", "w:type" => "dxa")
xml["w"].left("w:w" => "108", "w:type" => "dxa")
xml["w"].bottom("w:w" => "0", "w:type" => "dxa")
xml["w"].right("w:w" => "108", "w:type" => "dxa")
end
end
end
end
def render_table_style(xml, style)
xml["w"].style("w:type" => "table", "w:styleId" => style.style_id) do
xml["w"].name("w:val" => style.display_name)
xml["w"].basedOn("w:val" => "TableNormal")
xml["w"].tblPr do
render_table_borders(xml, style.borders) if style.borders