Skip to content

Commit

Permalink
fix(tables): better image handling in table autosizing (#8)
Browse files Browse the repository at this point in the history
* fix(tables): better image handling in table autosizing
  • Loading branch information
as-op authored Jun 10, 2024
1 parent 7bc03bf commit 4770ea7
Show file tree
Hide file tree
Showing 13 changed files with 311 additions and 88 deletions.
4 changes: 2 additions & 2 deletions lib/md_to_pdf/elements/blockquote.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def draw_blockquote(node, opts)

def data_blockquote_paragraph(node, opts)
cell_data = data_node_children(node, opts)
[make_table_cell_or_subtable(cell_data, opts, :left)]
[make_table_cell_or_subtable(cell_data, opts, :left, 1)]
end

def data_blockquote_list(node, opts)
Expand All @@ -40,7 +40,7 @@ def data_blockquote_list(node, opts)
sub.to_a.each do |paragraph|
cell_data = data_node_children(paragraph, opts)
unless cell_data.empty?
cell = make_table_cell_or_subtable(cell_data, opts, :left)
cell = make_table_cell_or_subtable(cell_data, opts, :left, 1)
result.push(cell)
end
end
Expand Down
26 changes: 24 additions & 2 deletions lib/md_to_pdf/elements/html.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def data_inlinehtml_tag(tag, node, opts)
when 'comment'
# ignore html comments
when 'img'
result.push({ image: sub.attr('src') })
result.push(data_inline_image_tag(sub, node, opts))
when 'ul', 'ol'
result.concat(data_inlinehtml_list_tag(sub, node, opts))
when 'label', 'p', 'li'
Expand All @@ -126,6 +126,27 @@ def data_inlinehtml_tag(tag, node, opts)
result
end

def data_image_style_opts(tag, _node, _opts)
result = {}
if tag.attr("style")
image_styles = tag.attr("style").split(';').to_h do |pair|
k, v = pair.split(':', 2)
[k, v]
end
if image_styles['width']
custom_max_width = parse_pt(image_styles['width'])
result[:custom_max_width] = custom_max_width unless custom_max_width.nil? || custom_max_width <= 0
end
end
result[:image_classes] = tag.attr('class')
result[:image_caption] = find_img_caption(tag)
result
end

def data_inline_image_tag(tag, node, opts)
{ image: tag.attr('src') }.merge(data_image_style_opts(tag, node, opts))
end

def data_inlinehtml_list_tag(tag, node, opts)
result = []
points, level, _list_style, content_opts = data_html_list(tag, node, opts)
Expand Down Expand Up @@ -188,7 +209,8 @@ def draw_html_tag(tag, node, opts)
tag.children.each do |sub|
case sub.name
when 'img'
embed_image(sub.attr('src'), node, current_opts.merge({ image_classes: sub.attr('class'), image_caption: find_img_caption(sub) }))
embed_image(sub.attr('src'), node, current_opts
.merge(data_image_style_opts(sub, node, current_opts)))
when 'text'
@pdf.formatted_text([text_hash(sub.text, current_opts)])
when 'a'
Expand Down
4 changes: 1 addition & 3 deletions lib/md_to_pdf/elements/image.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,9 @@ def build_image_settings(style, image_info, opts)
end

def build_image_opts(style, image_info, image_margin_opts, opts)
max_width = opt_image_max_width(style)
options = opts_image(style)
width = @pdf.bounds.width - (image_margin_opts[:left_margin] || 0) - (image_margin_opts[:right_margin] || 0)
width = [max_width, width].min unless max_width.nil?
width = [image_info.width, width].min
width = [image_info.width, opt_image_max_width(style), opts[:custom_max_width], width].compact.min
options[:scale] = [width / image_info.width.to_f, 1].min
options[:position] = :right if (opts[:image_classes] || '') =~ /\bright\b/
options
Expand Down
12 changes: 6 additions & 6 deletions lib/md_to_pdf/elements/table.rb
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,15 @@ def build_pdf_table(table, cell_style, data, column_alignments)
end
end

def make_subtable(cell_data, opts, alignment)
def make_subtable(cell_data, opts, alignment, column_count)
rows = []
row = []
cell_data.each do |elem|
if elem.key?(:image)
rows.push([make_subtable_cell(row, opts)]) unless row.empty?
image_file = image_url_to_local_file(elem[:image])
unless image_file.nil? || image_file.empty?
rows.push([image_in_table_column(image_file, alignment).merge({ borders: [] })])
rows.push([image_in_table_column(image_file, alignment).merge({ borders: [], custom_max_width: elem[:custom_max_width] })])
end
row = []
else
Expand All @@ -165,7 +165,7 @@ def make_subtable(cell_data, opts, alignment)
rows.push([make_subtable_cell(row, opts)]) unless row.empty?
return make_table_cell([{ text: '' }], opts) if rows.empty?

@pdf.make_table(rows, {}) do
@pdf.make_table(rows, column_widths: [@pdf.bounds.width / column_count]) do
columns(0).align = alignment unless alignment == nil
end
end
Expand All @@ -180,20 +180,20 @@ def build_table_data(data_rows, column_alignments, opts)
data_rows.each do |data_row|
cells_row = []
data_row.each_with_index do |cell_data, index|
cells_row.push(make_table_cell_or_subtable(cell_data, opts, column_alignments[index]))
cells_row.push(make_table_cell_or_subtable(cell_data, opts, column_alignments[index], data_row.length))
end
data.push(cells_row)
end
data
end

def make_table_cell_or_subtable(cell_data, opts, alignment)
def make_table_cell_or_subtable(cell_data, opts, alignment, column_count)
image_data = cell_data.find { |elem| elem.key?(:image) }
if image_data
image_file = image_url_to_local_file(image_data[:image])
return image_in_table_column(image_file, alignment) if !(image_file.nil? || image_file.empty?) && cell_data.length === 1

return make_subtable(cell_data, opts, alignment)
return make_subtable(cell_data, opts, alignment, column_count)
end
make_table_cell(cell_data, opts)
end
Expand Down
14 changes: 10 additions & 4 deletions lib/md_to_pdf/ext/prawn-table/table/cell/cell/image.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
Prawn::Table::Cell::Image.prepend(Module.new do
def initialize(pdf, point, options = {})
@image_options = {}
custom_max_width = options.delete(:custom_max_width)
super

@pdf_object, @image_info = @pdf.build_image_object(@file)
@natural_width, @natural_height = @image_info.calc_image_dimensions(@image_options)
natural_width(custom_max_width) if custom_max_width && custom_max_width > 0 && custom_max_width < @natural_width
natural_width(@pdf.bounds.width) if @natural_width > @pdf.bounds.width
end

def natural_width(new_width)
@natural_height = @natural_height * (new_width / @natural_width)
@natural_width = new_width
end

def width=(new_width)
@width = new_width
@height = (@natural_height * (new_width / @natural_width))
@width = [new_width, @natural_width].min
@height = (@natural_height * (@width / @natural_width))
end

def natural_content_width
Expand Down
16 changes: 9 additions & 7 deletions lib/md_to_pdf/ext/prawn-table/table/table.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,23 @@ def distribute_to_available_space(needed)
end

def reduce_to_available_space(needed)
cols = natural_split_column_widths.each_with_index
.map { |w, index| { min: w, max: natural_column_widths[index], index: index } }
.sort_by { |e| -e[:max] }
steps = needed / natural_split_column_widths.length
split_widths = natural_split_column_widths
return [[20, split_widths[0] - needed].max] if split_widths.length == 1

col = cols.first
cols = split_widths.each_with_index
.map { |w, index| { min: w, max: natural_column_widths[index], index: index } }
steps = needed / (split_widths.length * 2)

col = cols.max_by { |e| e[:max] }
while needed > Prawn::FLOAT_PRECISION
reduce = [steps, needed].min
col[:min] -= reduce
col[:max] -= reduce
needed -= reduce
col = cols.max_by { |e| e[:max] }
col = cols.max_by { |e| e[:min] }
end

cols.sort_by { |e| e[:index] }.map { |e| e[:min] }
cols.map { |e| e[:min] }
end

def recalculate_positions
Expand Down
8 changes: 8 additions & 0 deletions lib/md_to_pdf/utils/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,19 @@ def parse_pt(something)
ft2pt(value)
when 'in'
in2pt(value)
when 'px'
csspx2pt(value)
else
value
end
end

def csspx2pt(css_px)
# only css pixels are supported, not device pixels
# https://github.com/prawnpdf/prawn/pull/879
css_px * 0.75
end

def number_unit(string)
value = string.to_f # => -123.45
unit = string[/[a-zA-Z]+/] # => "min"
Expand Down
3 changes: 3 additions & 0 deletions spec/fixtures/image/html.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@

<img src="demo.jpg" style="width: 100px;">

<img src="demo.jpg" class="left small">

# An image in the middle of <img src="demo.jpg"> a headline
Expand Down
2 changes: 1 addition & 1 deletion spec/fixtures/image/image.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Floating text above ![Dummy image](demo.jpg) and below image
Floating text above ![Dummy image](demo.jpg) and below image ![Dummy image](logo.png)
8 changes: 4 additions & 4 deletions spec/fixtures/image/in_table.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<tr>
<td><p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
sed diam nonumy eirmod tempor<br></p>
<p><img src="demo.jpg"></p>
<p><img src="logo.png"></p>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
sed diam nonumy eirmod tempor&nbsp;</p>
</td>
Expand All @@ -22,10 +22,10 @@
<td>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
sed diam nonumy eirmod tempor<br></p>
<p><img src="demo.jpg"></p>
<p><img src="logo.png"></p>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
sed diam nonumy eirmod tempor&nbsp;</p>
<p><img src="demo.jpg"></p>
<p><img src="logo.png"></p>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
sed diam nonumy eirmod tempor<br></p>
</td>
Expand All @@ -40,7 +40,7 @@
<td>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,&nbsp;<br></p>
<p><img src="demo.jpg"></p>
<p><img src="logo.png"></p>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
sed diam nonumy eirmod tempor&nbsp;</p>
</td>
Expand Down
8 changes: 8 additions & 0 deletions spec/fixtures/image/large_in_table.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
With image style.width:

<figure><table><tbody><tr><td><p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,&nbsp;<br></p><p><img style="width:262px;" src="demo.jpg"></p><p>sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.&nbsp;</p></td><td><p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est L</p></td><td><p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est L</p></td></tr><tr><td><p>Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p></td><td><p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est L</p></td><td><p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est L</p></td></tr><tr><td><p></p></td><td><p></p></td><td><p></p></td></tr></tbody></table></figure>


Without image style.width:

<figure><table><tbody><tr><td><p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,&nbsp;<br></p><p><img src="demo.jpg"></p><p>sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.&nbsp;</p></td><td><p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est L</p></td><td><p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est L</p></td></tr><tr><td><p>Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p></td><td><p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est L</p></td><td><p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est L</p></td></tr><tr><td><p></p></td><td><p></p></td><td><p></p></td></tr></tbody></table></figure>
Loading

0 comments on commit 4770ea7

Please sign in to comment.