# # Prawn::SVG::Interface makes a Prawn::SVG::Document instance, uses that object to parse the supplied # SVG into Prawn-compatible method calls, and then calls the Prawn methods. # module Prawn module SVG class Interface VALID_OPTIONS = [:at, :position, :vposition, :width, :height, :cache_images, :enable_web_requests, :enable_file_requests_with_root, :fallback_font_name] attr_reader :data, :prawn, :document, :options # # Creates a Prawn::SVG object. # # +data+ is the SVG data to convert. +prawn+ is your Prawn::Document object. # # See README.md for the options that can be passed to this method. # def initialize(data, prawn, options, &block) Prawn.verify_options VALID_OPTIONS, options @data = data @prawn = prawn @options = options font_registry = Prawn::SVG::FontRegistry.new(prawn.font_families) @document = Document.new(data, [prawn.bounds.width, prawn.bounds.height], options, font_registry: font_registry, &block) end # # Draws the SVG to the Prawn::Document object. # def draw if @document.sizing.invalid? @document.warnings << "Zero or negative sizing data means this SVG cannot be rendered" return end @document.warnings.clear prawn.save_font do prawn.bounding_box(position, :width => @document.sizing.output_width, :height => @document.sizing.output_height) do prawn.save_graphics_state do clip_rectangle 0, 0, @document.sizing.output_width, @document.sizing.output_height calls = [] root_element = Prawn::SVG::Elements::Root.new(@document, @document.root, calls) root_element.process proc_creator(prawn, calls).call end end end end def sizing document.sizing end def resize(width: nil, height: nil) document.calculate_sizing(requested_width: width, requested_height: height) end def position @options[:at] || [x_based_on_requested_alignment, y_based_on_requested_alignment] end def self.font_path # backwards support for when the font_path used to be stored on this class Prawn::SVG::FontRegistry.font_path end private def x_based_on_requested_alignment case options[:position] when :left, nil 0 when :center, :centre (@document.sizing.bounds[0] - @document.sizing.output_width) / 2.0 when :right @document.sizing.bounds[0] - @document.sizing.output_width when Numeric options[:position] else raise ArgumentError, "options[:position] must be one of nil, :left, :right, :center or a number" end end def y_based_on_requested_alignment case options[:vposition] when nil prawn.cursor when :top @document.sizing.bounds[1] when :center, :centre @document.sizing.bounds[1] - (@document.sizing.bounds[1] - @document.sizing.output_height) / 2.0 when :bottom @document.sizing.output_height when Numeric @document.sizing.bounds[1] - options[:vposition] else raise ArgumentError, "options[:vposition] must be one of nil, :top, :right, :bottom or a number" end end def proc_creator(prawn, calls) Proc.new {issue_prawn_command(prawn, calls)} end def issue_prawn_command(prawn, calls) calls.each do |call, arguments, children| skip = false rewrite_call_arguments(prawn, call, arguments) do issue_prawn_command(prawn, children) if children.any? skip = true end if skip # the call has been overridden elsif children.empty? prawn.send(call, *arguments) else prawn.send(call, *arguments, &proc_creator(prawn, children)) end end end def rewrite_call_arguments(prawn, call, arguments) case call when 'text_group' @cursor = [0, document.sizing.output_height] yield when 'draw_text' text, options = arguments at = options.fetch(:at) at[0] = @cursor[0] if at[0] == :relative at[1] = @cursor[1] if at[1] == :relative if offset = options.delete(:offset) at[0] += offset[0] at[1] -= offset[1] end width = prawn.width_of(text, options.merge(kerning: true)) case options.delete(:text_anchor) when 'middle' at[0] -= width / 2 @cursor = [at[0] + width / 2, at[1]] when 'end' at[0] -= width @cursor = at.dup else @cursor = [at[0] + width, at[1]] end when 'transformation_matrix' left = prawn.bounds.absolute_left top = prawn.bounds.absolute_top arguments[4] += left - (left * arguments[0] + top * arguments[2]) arguments[5] += top - (left * arguments[1] + top * arguments[3]) when 'clip' prawn.add_content "W n" # clip to path yield when 'save' prawn.save_graphics_state yield when 'restore' prawn.restore_graphics_state yield when "end_path" yield prawn.add_content "n" # end path when 'fill_and_stroke' yield # prawn (as at 2.0.1 anyway) uses 'b' for its fill_and_stroke. 'b' is 'h' (closepath) + 'B', and we # never want closepath to be automatically run as it stuffs up many drawing operations, such as dashes # and line caps, and makes paths close that we didn't ask to be closed when fill is specified. prawn.add_content 'B' when 'noop' yield end end def clip_rectangle(x, y, width, height) prawn.move_to x, y prawn.line_to x + width, y prawn.line_to x + width, y + height prawn.line_to x, y + height prawn.close_path prawn.add_content "W n" # clip to path end end end end