# # 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, :fallback_font_name] DEFAULT_FONT_PATHS = ["/Library/Fonts", "/System/Library/Fonts", "#{ENV["HOME"]}/Library/Fonts", "/usr/share/fonts/truetype"] @font_path = [] DEFAULT_FONT_PATHS.each {|path| @font_path << path if File.exists?(path)} class << self; attr_accessor :font_path; end attr_reader :data, :prawn, :document, :options # # Creates a Prawn::Svg object. # # +data+ is the SVG data to convert. +prawn+ is your Prawn::Document object. # # Options: # :at:: an array [x,y] specifying the location of the top-left corner of the SVG. # :position:: one of (nil, :left, :center, :right) or an x-offset # :vposition:: one of (nil, :top, :center, :bottom) or a y-offset # :width:: the width that the SVG is to be rendered # :height:: the height that the SVG is to be rendered # # If :at is provided, the SVG will be placed in the current page but # the text position will not be changed. # # If both :width and :height are specified, only the width will be used. # def initialize(data, prawn, options, &block) Prawn.verify_options VALID_OPTIONS, options @data = data @prawn = prawn @options = options Prawn::Svg::Font.load_external_fonts(prawn.font_families) @document = Document.new(data, [prawn.bounds.width, prawn.bounds.height], options, &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 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 proc_creator(prawn, Parser.new(@document).parse).call end end end def position @options[:at] || [x_based_on_requested_alignment, y_based_on_requested_alignment] 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) if call == 'relative_draw_text' call.replace "draw_text" arguments.last[:at][0] = @relative_text_position if @relative_text_position end case call when 'text_group' @relative_text_position = nil yield when 'draw_text' text, options = arguments width = prawn.width_of(text, options.merge(:kerning => true)) if (anchor = options.delete(:text_anchor)) && %w(middle end).include?(anchor) width /= 2 if anchor == 'middle' options[:at][0] -= width end space_width = prawn.width_of("n", options) @relative_text_position = options[:at][0] + width + space_width 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' 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