Skip to content

Commit

Permalink
resolves asciidoctor#76 allow fine-grained control over image width
Browse files Browse the repository at this point in the history
- default to native image width scaled by 75% by default
- allow explicit width to be set using pdfwidth attribute
- enforce max width of 100%, except for pdfwidth or scaledwidth
- make alt text a link if image cannot be resolved and link attribute is present
- align alt text to honor image alignment
- add helper methods to convert to pt from string or number
- don't advance to new page if already at top of page
- code cleanups and optimizations
  • Loading branch information
mojavelinux committed Jun 24, 2015
1 parent f3a5653 commit 282e6cf
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 50 deletions.
104 changes: 54 additions & 50 deletions lib/asciidoctor-pdf/converter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -237,19 +237,9 @@ def build_pdf_options doc, theme
# dimension cannot be less than 0
dim > 0 ? dim : break
elsif ::String === dim && (m = (MeasurementPartsRx.match dim))
val = m[1].to_f
# TODO delegate to a pt calculation method
val = case m[2]
when 'in'
val * 72
when 'mm'
val * (72 / 25.4)
when 'cm'
val * (720 / 25.4)
else # pt or unitless
val
end
val = to_pt m[1].to_f, m[2]
# NOTE 4 is the max practical precision in PDFs
# QUESTION should we make rounding a feature of the to_pt method?
if (val = val.round 4) == (i_val = val.to_i)
val = i_val
end
Expand Down Expand Up @@ -761,11 +751,22 @@ def convert_image node
warn %(asciidoctor: WARNING: image to embed not found or not readable: #{image_path || target})
end

# QUESTION if we advance to new page, shouldn't dest point there too?
add_dest_for_block node if node.id
position = ((node.attr 'align') || @theme.image_align_default || :left).to_sym

unless valid_image
theme_margin :block, :top
layout_prose %(#{node.attr 'alt'} | #{target}), normalize: false, margin: 0, single_line: true
if (link = node.attr 'link')
alt_text = %(<a href="#{link}">[#{NoBreakSpace}#{node.attr 'alt'}#{NoBreakSpace}]</a> | <em>#{target}</em>)
else
alt_text = %([#{NoBreakSpace}#{node.attr 'alt'}#{NoBreakSpace}] | <em>#{target}</em>)
end
layout_prose alt_text,
normalize: false,
margin: 0,
single_line: true,
align: position
layout_caption node, position: :bottom if node.title?
theme_margin :block, :bottom
return
Expand All @@ -774,37 +775,35 @@ def convert_image node
theme_margin :block, :top

# TODO support cover (aka canvas) image layout using "canvas" (or "cover") role
width = if node.attr? 'scaledwidth'
((node.attr 'scaledwidth').to_f / 100.0) * bounds.width
elsif image_type == 'svg'
bounds.width
# NOTE height values are basically ignored (image is scaled proportionally based on width)
width = if node.attr? 'pdfwidth'
if (pdfwidth = node.attr 'pdfwidth').end_with? '%'
(pdfwidth.to_f / 100) * bounds.width
else
str_to_pt pdfwidth
end
elsif node.attr? 'scaledwidth'
((node.attr 'scaledwidth').to_f / 100) * bounds.width
elsif node.attr? 'width'
(node.attr 'width').to_f
else
bounds.width * (@theme.image_scaled_width_default || 0.75)
# NOTE width is pixels; scale by 75% with a max value of bounds.width
[bounds.width, (node.attr 'width').to_f * 0.75].min
end
position = ((node.attr 'align') || @theme.image_align_default || :left).to_sym

case image_type
when 'svg'
# NOTE prawn-svg can't position, so we have to do it manually (file issue?)
left = case position
when :left
0
when :right
bounds.width - width
when :center
((bounds.width - width) / 2.0).floor
end
begin
img_data = ::IO.read image_path
svg_obj = ::Prawn::Svg::Interface.new (::IO.read image_path), self, position: position, width: width
actual_w = svg_obj.document.sizing.output_width
actual_h = svg_obj.document.sizing.output_height
rel_left = { left: 0, right: (bounds.width - actual_w), center: ((bounds.width - actual_w) / 2.0) }[position]
# TODO layout SVG without using keep_together (since we know the dimensions already); always render caption
keep_together do |box_height = nil|
svg_obj.instance_variable_set :@prawn, self
svg_obj.draw
if box_height && (link = node.attr 'link')
result = svg img_data, at: [left, cursor], width: width
link_annotation [(abs_left = left + bounds.absolute_left), y, (abs_left + width), (y + result[:height])],
Border: [0, 0, 0],
A: { Type: :Action, S: :URI, URI: (str2pdfval link) }
else
svg img_data, at: [left, cursor], width: width
link_annotation [(abs_left = rel_left + bounds.absolute_left), y, (abs_left + actual_w), (y + actual_h)],
Border: [0, 0, 0],
A: { Type: :Action, S: :URI, URI: (str2pdfval link) }
end
layout_caption node, position: :bottom if node.title?
end
Expand All @@ -814,32 +813,37 @@ def convert_image node
else
begin
# FIXME temporary workaround to group caption & image
# Prawn doesn't provide access to rendered width and height before placing the
# image on the page
# Prawn doesn't provide access to rendered width & height before placing image on page
# FIXME this code really needs to be better organized!
image_obj, image_info = build_image_object image_path
rendered_w, rendered_h = image_info.calc_image_dimensions width: width
if width
rendered_w, rendered_h = image_info.calc_image_dimensions width: width
else
# NOTE scale native size by 75% with a max value of bounds.width
rendered_w = [bounds.width, image_info.width * 0.75].min
rendered_h = (rendered_w * image_info.height) / image_info.width
end
# TODO move this calculation into a method
caption_height = node.title? ?
(@theme.caption_margin_inside + @theme.caption_margin_outside + @theme.base_line_height_length) : 0
height = nil
if cursor < rendered_h + caption_height
start_new_page
if cursor < rendered_h + caption_height
height = (cursor - caption_height).floor
width = ((rendered_w * height) / rendered_h).floor
if rendered_h > (available_height = cursor - caption_height)
start_new_page unless at_page_top?
# NOTE shrink image so it fits on a single page
if rendered_h > (available_height = cursor - caption_height)
rendered_w = (rendered_w * available_height) / rendered_h
rendered_h = available_height
# FIXME workaround to fix Prawn not adding fill and stroke commands
# on page that only has an image; breakage occurs when line numbers are added
fill_color self.fill_color
stroke_color self.stroke_color
end
end
# NOTE must calculate link position before embedding to get proper boundaries
if (link = node.attr 'link')
actual_w, actual_h = [(width || rendered_w), (height || rendered_h)]
img_x, img_y = image_position actual_w, actual_h, position: position
link_box = [img_x, (img_y - actual_h), img_x + actual_w, img_y]
img_x, img_y = image_position rendered_w, rendered_h, position: position
link_box = [img_x, (img_y - rendered_h), (img_x + rendered_w), img_y]
end
embed_image image_obj, image_info, width: width, height: height, position: position
embed_image image_obj, image_info, width: rendered_w, position: position
if link
link_annotation link_box,
Border: [0, 0, 0],
Expand Down
33 changes: 33 additions & 0 deletions lib/asciidoctor-pdf/prawn_ext/extensions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ module Extensions
include ::Asciidoctor::Pdf::Sanitizer
include ::Asciidoctor::PdfCore::PdfObject

MeasurementValueRx = /(\d+|\d*\.\d+)(in|mm|cm|px|pt)?$/

# - :height is the height of a line
# - :leading is spacing between adjacent lines
# - :padding_top is half line spacing, plus any line_gap in the font
Expand Down Expand Up @@ -80,6 +82,37 @@ def at_page_top?
@y == @margin_box.absolute_top
end

# Converts the specified float value to a pt value from the
# specified unit of measurement (e.g., in, cm, mm, etc).
def to_pt num, units
case units
when nil, 'pt'
num
when 'in'
num * 72
when 'mm'
num * (72 / 25.4)
when 'cm'
num * (720 / 25.4)
when 'px'
num * 0.75
end
end

# Convert the specified string value to a pt value from the
# specified unit of measurement (e.g., in, cm, mm, etc).
#
# Examples:
#
# 0.5in => 36.0
# 100px => 75.0
#
def str_to_pt val
if MeasurementValueRx =~ val
to_pt $1.to_f, $2
end
end

# Destinations

# Generates a destination object that resolves to the top of the page
Expand Down
1 change: 1 addition & 0 deletions lib/asciidoctor-pdf/theme_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def evaluate_math expr
expr = %(1 - #{expr[1..-1]}) if expr.start_with? '-'
# expand measurement values (e.g., 0.5in)
expr = expr.gsub(MeasurementValueRx) {
# TODO extract to_pt method and use it here
val = $1.to_f
case $2
when 'in'
Expand Down

0 comments on commit 282e6cf

Please sign in to comment.