Skip to content

Commit

Permalink
add image loader option for more flexibility
Browse files Browse the repository at this point in the history
  • Loading branch information
codez committed Sep 3, 2018
1 parent 35f54a7 commit e00bdc2
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 23 deletions.
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -65,6 +65,7 @@ Beside these options handled by Prawn / prawn-table, the following values may be
* `[:list][:bullet][:margin]`: Margin before the bullet. Default is 10.
* `[:list][:content][:margin]`: Margin between the bullet and the content. Default is 10.
* `[:list][:placeholder][:too_large]`: If the list content does not fit into the current bounding box, this text/callable is rendered instead. Defaults to '[list content too large]'.
* `[:image][:loader]`: A callable that accepts the `src` attribute as an argument an returns a value understood by Prawn's `image` method. Loads `http(s)` URLs and base64 encoded data URIs by default.
* `[:image][:placeholder]`: If an image is not supported, this text/callable is rendered instead. Defaults to '[unsupported image]'.
* `[:iframe][:placeholder]`: If the HTML contains IFrames, this text/callable is rendered instead.
A callable gets the URL of the IFrame as an argument. Defaults to ignore iframes.
Expand All @@ -81,7 +82,7 @@ This gem parses the given HTML and layouts the following elements in a vertical

All other elements are ignored, their content is added to the parent element. With a few exceptions, no CSS is processed. One exception is the `width` property of `img`, `td` and `th`, which may contain values in `cm`, `mm`, `px`, `pt`, `%` or `auto`.

Images must be contained in the `src` attribute as base64 encoded data URIs. Prawn only supports `PNG` and `JPG`.
If no explicit loader is given (see above), images are loaded from `http(s)` addresses are may be contained in the `src` attribute as base64 encoded data URIs. Prawn only supports `PNG` and `JPG`.


## Development
Expand Down
6 changes: 3 additions & 3 deletions lib/prawn/markup/processor/headings.rb
Expand Up @@ -34,16 +34,16 @@ def end_heading(level)
add_cell_text_node(current_list_item, options)
else
add_current_text(options)
@margin_bottom = options[:margin_bottom]
put_bottom_margin(options[:margin_bottom])
end
end

private

def current_heading_margin_top(level)
top_margin = heading_options(level)[:margin_top] || 0
margin = [top_margin, @margin_bottom || 0].max
@margin_bottom = nil
margin = [top_margin, @bottom_margin || 0].max
put_bottom_margin(nil)
margin
end

Expand Down
36 changes: 27 additions & 9 deletions lib/prawn/markup/processor/images.rb
@@ -1,3 +1,5 @@
require 'open-uri'

module Prawn
module Markup
module Processor::Images
Expand All @@ -8,6 +10,7 @@ def self.prepended(base)
end

def start_img
add_current_text
add_image_or_placeholder(current_attrs['src'])
end

Expand All @@ -18,10 +21,9 @@ def start_iframe

private

def add_image_or_placeholder(data)
img = image_properties(data)
def add_image_or_placeholder(src)
img = image_properties(src)
if img
add_current_text
add_image(img)
else
append_text("\n#{invalid_image_placeholder}\n")
Expand All @@ -33,24 +35,40 @@ def add_image(img)
img[:width] = SizeConverter.new(pdf.bounds.width).parse(style_properties['width'])
pdf.image(img.delete(:image), img)
put_bottom_margin(text_margin_bottom)
rescue Prawn::Errors::UnsupportedImageType
append_text("\n#{invalid_image_placeholder}\n")
end

def image_properties(data)
img = decode_base64_image(data)
def image_properties(src)
img = load_image(src)
if img
props = style_properties
{
image: StringIO.new(img),
image: img,
width: props['width'],
position: convert_float_to_position(props['float'])
}
end
end

def decode_base64_image(data)
match = data.match(/^data:(.*?);(.*?),(.*)$/)
def load_image(src)
if options[:image] && options[:image][:loader]
options[:image][:loader].call(src)
else
decode_base64_image(src) || load_remote_image(src)
end
end

def decode_base64_image(src)
match = src.match(/^data:(.*?);(.*?),(.*)$/)
if match && ALLOWED_IMAGE_TYPES.include?(match[1])
Base64.decode64(match[3])
StringIO.new(Base64.decode64(match[3]))
end
end

def load_remote_image(src)
if src =~ %r{^https?:/}
URI.parse(src).open
end
end

Expand Down
4 changes: 3 additions & 1 deletion spec/fixtures/showcase.html
Expand Up @@ -21,7 +21,8 @@ <h2>Table features</h2>
<tr><th style="width: 1cm;">No</th><th>Description</th><th style="width: 25%;">State</th></tr>
<tr><td>1</td><td>Headers and custom widths are supported</td><td>Done</td></tr>
<tr><td>2</td><td><table><tr><td>subtables</td><td>so</td></tr><tr><td>very</td><td>crazy</td></tr></table></td><td>Done</td></tr>
<tr><td>1</td><td>Even lists <ol><li>one</li><li>two</li></ol></td><td>Check</td></tr>
<tr><td>3</td><td>Even lists <ol><li>one</li><li>two</li></ol></td><td>Check</td></tr>
<tr><td>4</td><td>and Images: <img src="https://github.com/puzzle/prawn-markup/blob/master/spec/fixtures/logo.png?raw=true" style="width: 50px;"></td><td>Yieha</td></tr>
</table>
This table has text underneath
</div>
Expand All @@ -40,5 +41,6 @@ <h3>Ordered</h3>
</ol>

<h2>So far</h2>
<img src="https://github.com/puzzle/prawn-markup/blob/master/spec/fixtures/logo.png?raw=true" style="width: 100%;">
<p>and thanks for all the fish</p>
<iframe src="http://codez.ch"></iframe>
2 changes: 1 addition & 1 deletion spec/prawn/markup/processor/headings_spec.rb
Expand Up @@ -38,7 +38,7 @@
processor.parse('<h1>hello</h1><h2>world</h2><p>bla</p><h5>earthlings</h5><div>blu</div><h2>universe</h2><p>bli</p>')
expect(text.strings).to eq(%w[hello world bla earthlings blu universe bli])
# values copied from visually controlled run
expect(top_positions).to eq([720, 666, 646, 620, 605, 562, 542])
expect(top_positions).to eq([720, 666, 641, 620, 605, 562, 537])
end
end

Expand Down
35 changes: 30 additions & 5 deletions spec/prawn/markup/processor/images_spec.rb
Expand Up @@ -4,25 +4,38 @@
RSpec.describe Prawn::Markup::Processor::Tables do
include_context 'pdf_helpers'

LOGO_DIMENSION = [100, 38].freeze

it 'renders an image on own line' do
processor.parse("hello <img src=\"#{encode_image('logo.png')}\" style=\"width: 50mm;\"> world")

orig_img_dimension = [100, 38]
scaled_height = 50.mm * 38 / 100
gap = doc.font.line_gap + doc.font.descender
scaled_height = 50.mm * LOGO_DIMENSION.last / LOGO_DIMENSION.first
expect(text.strings).to eq(%w[hello world])
expect(left_positions).to eq([left, left])
expect(top_positions).to eq([top, top - line - scaled_height - gap].map(&:round))
expect(top_positions).to eq([top, top - line - scaled_height - p_gap].map(&:round))
end

it 'renders placeholder for unknown format' do
processor.parse('hello <img src="data:image/bmp;bla,foobar"> world')

expect(text.strings).to eq(['hello ', '[unsupported image]', 'world'])
expect(text.strings).to eq(['hello', '[unsupported image]', 'world'])
expect(left_positions).to eq([left, left, left])
expect(top_positions).to eq([top, top - line, top - 2 * line].map(&:round))
end

it 'renders image for remote src' do
processor.parse('<p>hello</p><p><img src="https://github.com/puzzle/prawn-markup/blob/master/spec/fixtures/logo.png?raw=true"></p><p>world</p>')

expect(text.strings).to eq(['hello', 'world'])
expect(left_positions).to eq([left, left])
expect(top_positions).to eq([top, top - line - LOGO_DIMENSION.last - p_gap - 5].map(&:round))
end

it 'renders unsupported image for remote src' do
processor.parse('<p>hello</p><p><img src="https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif"></p><p>world</p>')
expect(text.strings).to eq(['hello', '[unsupported image]', 'world'])
end

it 'renders nothing for iframes' do
processor.parse('hello <iframe src="http://vimeo.com/42" /> world')

Expand All @@ -40,4 +53,16 @@
expect(top_positions).to eq([top, *([top - line] * 3), top - 2 * line].map(&:round))
end
end

context 'with custom loader' do
let(:options) { { image: { loader: ->(src) { "spec/fixtures/#{src}" } } } }

it 'render image with custom loader' do
processor.parse('<p>hello</p><img src="logo.png"><p>world</p>')

expect(left_positions).to eq([left, left])
expect(top_positions).to eq([top, top - line - LOGO_DIMENSION.last - p_gap - 5].map(&:round))
end

end
end
6 changes: 3 additions & 3 deletions spec/prawn/markup/showcase_spec.rb
Expand Up @@ -7,9 +7,9 @@
it 'renders showcase' do
html = File.read('spec/fixtures/showcase.html')
doc.markup_options = {
heading1: { margin_top: 30, margin_bottom: 5 },
heading2: { margin_top: 24, margin_bottom: 4 },
heading3: { margin_top: 20, margin_bottom: 3 },
heading1: { margin_top: 30, margin_bottom: 15 },
heading2: { margin_top: 24, margin_bottom: 10 },
heading3: { margin_top: 20, margin_bottom: 5 },
table: {
header: { background_color: 'DDDDDD', style: :italic }
},
Expand Down

0 comments on commit e00bdc2

Please sign in to comment.