From 6d63af6442601f57b6e0bbc0af2a719335f96027 Mon Sep 17 00:00:00 2001
From: Andrej Sandorf <77627197+as-op@users.noreply.github.com>
Date: Wed, 29 Jan 2025 15:52:03 +0100
Subject: [PATCH] even better support for html tables (#18)
split styles definition for html table vs markdown table; no forced borders for html tables
---
.github/workflows/test.yml | 2 +-
demo/demo.md | 10 +-
docs/STYLES.md | 1 +
lib/md_to_pdf/elements/html.rb | 18 +-
lib/md_to_pdf/elements/table.rb | 36 ++--
.../prawn-table/table/cell/cell/subtable.rb | 13 ++
lib/md_to_pdf/style/schema_styles.json | 3 +
lib/md_to_pdf/style/styles.rb | 14 ++
.../image/{in_table.md => in_table.html} | 0
...{large_in_table.md => large_in_table.html} | 0
spec/fixtures/table/html_borders.html | 30 +--
.../table/{html.md => html_checklist.html} | 0
.../{html_figure.md => html_figure.html} | 0
spec/fixtures/table/html_no_borders.html | 8 +
.../table/subtable_in_header_row.html | 21 ++
spec/fixtures/table/subtable_in_header_row.md | 21 --
spec/markdown_to_pdf/image_spec.rb | 6 +-
spec/markdown_to_pdf/table_spec.rb | 187 ++++++++----------
spec/pdf_helpers.rb | 26 ++-
19 files changed, 213 insertions(+), 183 deletions(-)
rename spec/fixtures/image/{in_table.md => in_table.html} (100%)
rename spec/fixtures/image/{large_in_table.md => large_in_table.html} (100%)
rename spec/fixtures/table/{html.md => html_checklist.html} (100%)
rename spec/fixtures/table/{html_figure.md => html_figure.html} (100%)
create mode 100644 spec/fixtures/table/html_no_borders.html
create mode 100644 spec/fixtures/table/subtable_in_header_row.html
delete mode 100644 spec/fixtures/table/subtable_in_header_row.md
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index fd3a3b3..a089b65 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -18,7 +18,7 @@ jobs:
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
- ruby-version: '3.2'
+ ruby-version: '3.2.1'
bundler-cache: true
- name: Run rspec
run: bundle exec rspec
diff --git a/demo/demo.md b/demo/demo.md
index 671dd68..ddf36c4 100644
--- a/demo/demo.md
+++ b/demo/demo.md
@@ -31,22 +31,24 @@ pdf_fields:
# Horizontal Rules
-`___`
+Code: `___`
___
-`---`
+Code: `---`
---
-`***`
+Code: `***`
***
-`_________________`
+Code: `_________________`
_________________
+
Styling to denote a paragraph as quote
See [Blockquote](#blockquote) | object |
| `link` | **Link**
Styling a clickable link
See [Link](#link) | object |
| `table` | **Table**
See [Table](#table) | object |
+| `html_table` | **Table**
See [Table](#table) | object |
| `headless_table` | **Headless table**
Tables without or empty header rows can be styled differently.
See [Headless table](#headless-table) | object |
| `image` | **Image**
Styling of images
See [Image](#image) | object |
| `image_classes` | **Image classes**
Styling for images by class names
See [Image classes](#image-classes) | object |
diff --git a/lib/md_to_pdf/elements/html.rb b/lib/md_to_pdf/elements/html.rb
index 7f1773c..3ec505e 100644
--- a/lib/md_to_pdf/elements/html.rb
+++ b/lib/md_to_pdf/elements/html.rb
@@ -192,7 +192,7 @@ def collect_html_table_tag_rows(tag, table_font_opts, opts)
end
def draw_html_table_tag(tag, opts)
- current_opts = opts.merge({ is_in_table: true })
+ current_opts = opts.merge({ is_in_table: true, is_html_table: true })
table_font_opts = build_table_font_opts(current_opts)
rows = collect_html_table_tag_rows(tag, table_font_opts, current_opts)
column_count = 0
@@ -329,12 +329,15 @@ def remove_font_style(opts, style)
def collect_html_table_tag_cell(tag, opts)
cell_data = data_inlinehtml_tag(tag, nil, opts)
- if tag.key?('style')
- style = tag.get_attribute('style') || ''
- cell_styling = parse_css_stylings(style)
- cell_data = [{}] if cell_data.empty?
- cell_data[0] = cell_data[0].merge(cell_styling)
- end
+ cell_styling =
+ if tag.key?('style')
+ style = tag.get_attribute('style') || ''
+ parse_css_stylings(style)
+ else
+ { cell_borders: [] }
+ end
+ cell_data = [{}] if cell_data.empty?
+ cell_data[0] = cell_data[0].merge(cell_styling)
cell_data
end
@@ -360,6 +363,7 @@ def parse_css_stylings(style)
end
end
{
+ cell_borders: cell_border_color || cell_border_width || cell_border_style ? %i[left right top bottom] : [],
cell_background_color: cell_background_color,
cell_border_color: cell_border_color,
cell_border_width: cell_border_width,
diff --git a/lib/md_to_pdf/elements/table.rb b/lib/md_to_pdf/elements/table.rb
index fd1ecef..2ba7dd5 100644
--- a/lib/md_to_pdf/elements/table.rb
+++ b/lib/md_to_pdf/elements/table.rb
@@ -31,6 +31,7 @@ def make_table_cell(cell_data, opts)
border_colors: additional_cell_settings[:cell_border_color] || opts.dig(:opts_cell, :border_colors),
border_widths: additional_cell_settings[:cell_border_width] || opts.dig(:opts_cell, :border_widths),
border_line: additional_cell_settings[:cell_border_style] ? [additional_cell_settings[:cell_border_style]] * 4 : opts.dig(:opts_cell, :border_line),
+ borders: additional_cell_settings[:cell_borders],
inline_format: true
}.compact
Prawn::Table::Cell::Text.new(@pdf, [0, 0], cell_opts)
@@ -40,7 +41,7 @@ def draw_table_data(table, data_rows, column_alignments, opts)
return if data_rows.empty?
data = build_table_data(data_rows, column_alignments, opts)
- pdf_tables = try_build_table(table, data, column_alignments)
+ pdf_tables = try_build_table(table, data, column_alignments, opts)
pdf_tables.each do |pdf_table|
optional_break_before_table(table, pdf_table)
with_block_margin_all(table[:margins]) do
@@ -60,7 +61,11 @@ def build_table_settings(header_row_count, opts)
cell_style = @styles.table_cell
header_style = @styles.table_header
- if header_row_count == 0
+ if opts[:is_html_table]
+ table_style = @styles.html_table
+ cell_style = @styles.html_table_cell
+ header_style = @styles.html_table_header
+ elsif header_row_count == 0
table_style = @styles.headless_table
cell_style = @styles.headless_table_cell
header_style = @styles.headless_table_header
@@ -82,7 +87,7 @@ def build_table_settings(header_row_count, opts)
private
- def build_split_tables(table, data, column_alignments)
+ def build_split_tables(table, data, column_alignments, opts)
range = 4
header_row_count = table[:header_row_count]
pdf_tables = []
@@ -96,16 +101,16 @@ def build_split_tables(table, data, column_alignments)
end
new_column_alignments = column_alignments.slice(start, range)
new_column_alignments.unshift :left
- pdf_table = build_pdf_table(table, table[:opts_cell], new_rows, new_column_alignments)
+ pdf_table = build_pdf_table(table, table[:opts_cell], new_rows, new_column_alignments, opts)
pdf_tables.push pdf_table
end
pdf_tables
end
- def try_build_table(table, data, column_alignments)
- [build_pdf_table(table, table[:opts_cell], data, column_alignments)]
+ def try_build_table(table, data, column_alignments, opts)
+ [build_pdf_table(table, table[:opts_cell], data, column_alignments, opts)]
rescue Prawn::Errors::CannotFit
- build_split_tables(table, data, column_alignments)
+ build_split_tables(table, data, column_alignments, opts)
end
def merge_cell_data(cell_data)
@@ -125,12 +130,10 @@ def optional_break_before_table(table, pdf_table)
end
end
- def build_pdf_table(table, cell_style, data, column_alignments)
+ def build_pdf_table(table, cell_style, data, column_alignments, opts)
column_count = column_alignments.length
column_widths = Array.new(column_count, @pdf.bounds.right / column_count)
- # the default border width/color property overrides the already set cell border widths/colors
- # so we remove it and set it manually for each cell on creation
- default_cell_style = cell_style.except(:border_widths, :border_colors)
+ default_cell_style = opts[:is_html_table] ? cell_style.except(:borders, :border_widths, :border_colors) : cell_style
@pdf.make_table(
data,
width: @pdf.bounds.right,
@@ -172,7 +175,16 @@ def make_subtable(cell_data, opts, alignment, column_count)
rows.push([make_subtable_cell(row, opts)]) unless row.empty?
return make_table_cell([{ text: '' }], opts) if rows.empty?
- @pdf.make_table(rows, column_widths: [@pdf.bounds.width / column_count]) do
+ additional_cell_settings = cell_data[0] || {}
+ subtable_cell_style = {
+ border_colors: additional_cell_settings[:cell_border_color] || opts.dig(:opts_cell, :border_colors),
+ border_widths: additional_cell_settings[:cell_border_width] || opts.dig(:opts_cell, :border_widths),
+ border_line: additional_cell_settings[:cell_border_style] ? [additional_cell_settings[:cell_border_style]] * 4 : opts.dig(:opts_cell, :border_line),
+ borders: additional_cell_settings[:cell_borders]
+ }.compact
+ @pdf.make_table(rows,
+ cell_style: subtable_cell_style,
+ column_widths: [@pdf.bounds.width / column_count]) do
columns(0).align = alignment unless alignment == nil
end
end
diff --git a/lib/md_to_pdf/ext/prawn-table/table/cell/cell/subtable.rb b/lib/md_to_pdf/ext/prawn-table/table/cell/cell/subtable.rb
index ea2367d..ef7e384 100644
--- a/lib/md_to_pdf/ext/prawn-table/table/cell/cell/subtable.rb
+++ b/lib/md_to_pdf/ext/prawn-table/table/cell/cell/subtable.rb
@@ -1,4 +1,17 @@
Prawn::Table::Cell::Subtable.prepend(Module.new do
+ def initialize(pdf, point, options = {})
+ super
+
+ border_cell = @subtable.cells[0, 0]
+ if border_cell
+ @borders = border_cell.borders
+ @border_colors = border_cell.border_colors
+ @border_lines = border_cell.border_lines
+ @border_widths = border_cell.border_widths
+ end
+ @subtable.cells.borders = []
+ end
+
def width=(new_width)
@width = @min_width = @max_width = new_width
@height = nil
diff --git a/lib/md_to_pdf/style/schema_styles.json b/lib/md_to_pdf/style/schema_styles.json
index 25eb411..9ce0e04 100644
--- a/lib/md_to_pdf/style/schema_styles.json
+++ b/lib/md_to_pdf/style/schema_styles.json
@@ -171,6 +171,9 @@
"table": {
"$ref": "#/$defs/table"
},
+ "html_table": {
+ "$ref": "#/$defs/table"
+ },
"headless_table": {
"$ref": "#/$defs/headless_table"
},
diff --git a/lib/md_to_pdf/style/styles.rb b/lib/md_to_pdf/style/styles.rb
index fc6eb3a..adb7c2a 100644
--- a/lib/md_to_pdf/style/styles.rb
+++ b/lib/md_to_pdf/style/styles.rb
@@ -155,6 +155,20 @@ def headless_table_header
headless_table[:header] || {}
end
+ def html_table
+ return table if @styling[:html_table].nil?
+
+ get_style(:html_table)
+ end
+
+ def html_table_cell
+ html_table[:cell] || {}
+ end
+
+ def html_table_header
+ html_table[:header] || {}
+ end
+
def codeblock
get_style(:codeblock)
end
diff --git a/spec/fixtures/image/in_table.md b/spec/fixtures/image/in_table.html
similarity index 100%
rename from spec/fixtures/image/in_table.md
rename to spec/fixtures/image/in_table.html
diff --git a/spec/fixtures/image/large_in_table.md b/spec/fixtures/image/large_in_table.html
similarity index 100%
rename from spec/fixtures/image/large_in_table.md
rename to spec/fixtures/image/large_in_table.html
diff --git a/spec/fixtures/table/html_borders.html b/spec/fixtures/table/html_borders.html
index 24eabde..04366e8 100644
--- a/spec/fixtures/table/html_borders.html
+++ b/spec/fixtures/table/html_borders.html
@@ -3,39 +3,29 @@
column1
column1
column2
column2
column3
column4
column4
No | +Borders! | +
Header 1 | +Header 2 | +|
---|---|---|
Entry 1 | ++ | + |
Entry 2 | ++ | + |
Header 1 | -Header 2 | -|
---|---|---|
Entry 1 | -- | - |
Entry 2 | -- | - |