From 95cff196dc401d7908d826fbb571a817f4740cfd Mon Sep 17 00:00:00 2001 From: Dan Allen Date: Fri, 14 Oct 2016 23:45:43 -0600 Subject: [PATCH] resolves #625 honor pdfwidth attribute for image in running content - honor pdfwidth attribute for image in running content - position SVG correctly - only fit image to bounds if fit=contain or fit=scale-down attribute is used - calculate image width from percentage value based on column width, not content width --- lib/asciidoctor-pdf/converter.rb | 97 ++++++++++++++------- lib/asciidoctor-pdf/prawn_ext/extensions.rb | 7 ++ lib/asciidoctor-pdf/prawn_ext/images.rb | 2 - 3 files changed, 71 insertions(+), 35 deletions(-) diff --git a/lib/asciidoctor-pdf/converter.rb b/lib/asciidoctor-pdf/converter.rb index c1f7759f3..0dd9ad2c9 100644 --- a/lib/asciidoctor-pdf/converter.rb +++ b/lib/asciidoctor-pdf/converter.rb @@ -952,7 +952,7 @@ def convert_image node, opts = {} if !width && (svg_obj.document.root.attributes.key? 'width') # NOTE scale native width & height by 75% to convert px to pt; restrict width to bounds.width if (adjusted_w = [bounds.width, rendered_w * 0.75].min) != rendered_w - # FIXME would be nice to have a resize method (available as of prawn-svg 0.25.2); for now, just reconstruct + # FIXME use new resize method (available as of prawn-svg 0.25.2); reconstruct manually for now svg_obj = ::Prawn::Svg::Interface.new svg_data, self, position: alignment, width: (rendered_w = adjusted_w), @@ -1019,12 +1019,15 @@ def convert_image node, opts = {} img_x, img_y = image_position rendered_w, rendered_h, position: alignment link_box = [img_x, (img_y - rendered_h), (img_x + rendered_w), img_y] end + image_top = cursor embed_image image_obj, image_info, width: rendered_w, position: alignment if link link_annotation link_box, Border: [0, 0, 0], A: { Type: :Action, S: :URI, URI: link.as_pdf } end + # NOTE Asciidoctor disables automatic advancement of cursor, so handle it manually + move_down rendered_h if cursor == image_top rescue => e warn %(asciidoctor: WARNING: could not embed image: #{image_path}; #{e.message}) end @@ -2120,34 +2123,7 @@ def layout_running_content periphery, doc, opts = {} doc.set_attr 'document-title', doctitle.main doc.set_attr 'document-subtitle', doctitle.subtitle doc.set_attr 'page-count', num_pages - - # TODO move this to a method so it can be reused; cache results - content_dict = PageSides.inject({}) do |acc, side| - side_content = {} - ColumnPositions.each do |position| - if (val = @theme[%(#{periphery}_#{side}_#{position}_content)]) - # TODO support image URL (using resolve_image_path) - if (val.include? ':') && val =~ ImageAttributeValueRx && - ::File.readable?(path = (ThemeLoader.resolve_theme_asset $1, (doc.attr 'pdf-stylesdir'))) - attrs = (AttributeList.new $2).parse - unless (width = resolve_explicit_width attrs, bounds.width) - # QUESTION should we lookup and scale intrinsic width if explicit width is not given? - width = (intrinsic_image_dimensions path)[:width] * 0.75 - end - side_content[position] = { path: path, width: width, fit: (attrs.key? 'contain-option') } - else - side_content[position] = val - end - end - end - # NOTE set fallbacks if not explicitly disabled - if side_content.empty? && periphery == :footer && @theme[%(footer_#{side}_content)] != 'none' - side_content = { side == :recto ? :right : :left => '{page-number}' } - end - - acc[side] = side_content - acc - end + allow_uri_read = doc.attr? 'allow-uri-read' if periphery == :header trim_line_metrics = calc_line_metrics(@theme.header_line_height || @theme.base_line_height) @@ -2243,6 +2219,40 @@ def layout_running_content periphery, doc, opts = {} acc end + # TODO move this to a method so it can be reused; cache results + content_dict = PageSides.inject({}) do |acc, side| + side_content = {} + ColumnPositions.each do |position| + if (val = @theme[%(#{periphery}_#{side}_#{position}_content)]) + # TODO support image URL (using resolve_image_path) + if (val.include? ':') && val =~ ImageAttributeValueRx && + ::File.readable?(path = (ThemeLoader.resolve_theme_asset $1, (doc.attr 'pdf-stylesdir'))) + attrs = (AttributeList.new $2).parse + col_width = colspec_dict[side][position][:width] + if (fit = attrs['fit']) == 'contain' + width = col_width + else + unless (width = resolve_explicit_width attrs, col_width) + # QUESTION should we lookup and scale intrinsic width if explicit width is not given? + width = (intrinsic_image_dimensions path)[:width] * 0.75 + end + width = col_width if fit == 'scale-down' && width > col_width + end + side_content[position] = { path: path, width: width, fit: !!fit } + else + side_content[position] = val + end + end + end + # NOTE set fallbacks if not explicitly disabled + if side_content.empty? && periphery == :footer && @theme[%(footer_#{side}_content)] != 'none' + side_content = { side == :recto ? :right : :left => '{page-number}' } + end + + acc[side] = side_content + acc + end + stamps = {} if trim_bg_color || trim_border_color PageSides.each do |side| @@ -2306,11 +2316,32 @@ def layout_running_content periphery, doc, opts = {} float do # NOTE bounding_box is redundant if trim_v_padding is 0 bounding_box [colspec[:x], cursor - trim_padding[0]], width: colspec[:width], height: (bounds.height - trim_v_padding) do - if content[:fit] - # NOTE use :fit to prevent image from overflowing page (at the cost of scaling it) - image content[:path], vposition: trim_img_valign, position: colspec[:align], fit: [content[:width], bounds.height] + if (image_path = content[:path]).downcase.end_with? '.svg' + svg_data = ::IO.read image_path + svg_obj = ::Prawn::Svg::Interface.new svg_data, self, + position: (image_alignment = colspec[:align]), + width: (image_width = content[:width]), + fallback_font_name: (fallback_font_name = default_svg_font), + enable_web_requests: allow_uri_read, + # TODO enforce jail in safe mode + enable_file_requests_with_root: (file_request_root = ::File.dirname image_path) + # FIXME use new resize method (available as of prawn-svg 0.25.2); reconstruct manually for now + if content[:fit] && (rendered_h = svg_obj.document.sizing.output_height) > bounds.height + svg_obj = ::Prawn::Svg::Interface.new svg_data, self, + position: image_alignment, + width: image_width * (bounds.height / rendered_h), + fallback_font_name: fallback_font_name, + enable_web_requests: allow_uri_read, + # TODO enforce jail in safe mode + enable_file_requests_with_root: file_request_root + end + svg_obj.draw else - image content[:path], vposition: trim_img_valign, position: colspec[:align], width: content[:width] + if content[:fit] + image image_path, vposition: trim_img_valign, position: colspec[:align], fit: [content[:width], bounds.height] + else + image image_path, vposition: trim_img_valign, position: colspec[:align], width: content[:width] + end end end end diff --git a/lib/asciidoctor-pdf/prawn_ext/extensions.rb b/lib/asciidoctor-pdf/prawn_ext/extensions.rb index 1cd85562d..e6f194bb1 100644 --- a/lib/asciidoctor-pdf/prawn_ext/extensions.rb +++ b/lib/asciidoctor-pdf/prawn_ext/extensions.rb @@ -435,6 +435,13 @@ def move_up n super unless n == 0 end + # Override built-in move_text_position method to prevent Prawn from advancing + # to next page if image doesn't fit before rendering image. + #-- + # NOTE could use :at option when calling image/embed_image instead + def move_text_position h + end + # Short-circuits the call to the built-in move_down operation # when n is 0. # diff --git a/lib/asciidoctor-pdf/prawn_ext/images.rb b/lib/asciidoctor-pdf/prawn_ext/images.rb index 380a05187..84717c0aa 100644 --- a/lib/asciidoctor-pdf/prawn_ext/images.rb +++ b/lib/asciidoctor-pdf/prawn_ext/images.rb @@ -11,8 +11,6 @@ def extended base def image file, opts = {} # FIXME handle case when SVG is a File or IO object if ::String === file && (file.downcase.end_with? '.svg') - opts[:position] ||= :left - opts[:vposition] ||= cursor opts[:fallback_font_name] ||= default_svg_font if respond_to? :default_svg_font svg (::IO.read file), opts else