diff --git a/lib/asciidoctor-pdf/converter.rb b/lib/asciidoctor-pdf/converter.rb
index c94dddc4d..e6345cda7 100644
--- a/lib/asciidoctor-pdf/converter.rb
+++ b/lib/asciidoctor-pdf/converter.rb
@@ -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
@@ -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 || :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 = %([#{NoBreakSpace}#{node.attr 'alt'}#{NoBreakSpace}] | #{target})
+ else
+ alt_text = %([#{NoBreakSpace}#{node.attr 'alt'}#{NoBreakSpace}] | #{target})
+ 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
@@ -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
@@ -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],
diff --git a/lib/asciidoctor-pdf/prawn_ext/extensions.rb b/lib/asciidoctor-pdf/prawn_ext/extensions.rb
index 969d718d0..49bcddaaa 100644
--- a/lib/asciidoctor-pdf/prawn_ext/extensions.rb
+++ b/lib/asciidoctor-pdf/prawn_ext/extensions.rb
@@ -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
@@ -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
diff --git a/lib/asciidoctor-pdf/theme_loader.rb b/lib/asciidoctor-pdf/theme_loader.rb
index d2afb020b..7d9c48825 100644
--- a/lib/asciidoctor-pdf/theme_loader.rb
+++ b/lib/asciidoctor-pdf/theme_loader.rb
@@ -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'