require 'rexml/document' module Phantom module SVG module Parser # SVG writer. class SVGWriter # Construct SVGWriter object. def initialize(path = nil, object = nil) write(path, object) end # Write svg file from object to path. # Return write size. def write(path, object) return 0 if path.nil? || path.empty? || object.nil? reset # Parse object. if object.is_a?(Base) then write_animation_svg(object) elsif object.is_a?(Frame) then write_svg(object) else return 0 end # Add svg version. @root.elements['svg'].add_attribute('version', '1.1') # Write to file. File.open(path, 'w') { |file| @root.write(file, 2) } end private # Reset SVGWriter object. def reset @root = REXML::Document.new @root << REXML::XMLDecl.new('1.0', 'UTF-8') @root << REXML::Comment.new(' Generated by phantom_svg. ') end # Write no animation svg. def write_svg(frame) write_image(frame, @root) end # Write animation svg. def write_animation_svg(base) svg = @root.add_element('svg', 'id' => 'phantom_svg') defs = svg.add_element('defs') # Header. write_size(base, svg) svg.add_namespace('http://www.w3.org/2000/svg') svg.add_namespace('xlink', 'http://www.w3.org/1999/xlink') # Images. write_images(base.frames, defs) # Animation. write_animation(base, defs) # Show control. write_show_control(base, svg) end # Write image size. def write_size(s, d) d.add_attribute('width', s.width.is_a?(String) ? s.width : "#{s.width.to_i}px") d.add_attribute('height', s.height.is_a?(String) ? s.height : "#{s.height.to_i}px") d.add_attribute('viewBox', s.viewbox.to_s) if s.instance_variable_defined?(:@viewbox) end # Write namespaces from src to dest. def write_namespaces(src, dest) src.namespaces.each do |key, val| if key == 'xmlns' then dest.add_namespace(val) else dest.add_namespace(key, val) end end end # Write surfaces to dest. def write_surfaces(surfaces, dest) surfaces.each { |surface| dest.add_element(surface) } end # Write image. def write_image(frame, parent_node, id = nil) svg = parent_node.add_element('svg') svg.add_attribute('id', id) unless id.nil? write_size(frame, svg) write_namespaces(frame, svg) write_surfaces(frame.surfaces, svg) end # Write images. def write_images(frames, parent_node) REXML::Comment.new(' Images. ', parent_node) frames.each_with_index { |frame, i| write_image(frame, parent_node, "frame#{i}") } end # Write animation. def write_animation(base, parent_node) REXML::Comment.new(' Animation. ', parent_node) symbol = parent_node.add_element('symbol', 'id' => 'animation') begin_text = "0s;frame#{base.frames.length - 1}_anim.end" base.frames.each_with_index do |frame, i| next if i == 0 && base.skip_first write_animation_frame(frame, "frame#{i}", begin_text, symbol) begin_text = "frame#{i}_anim.end" end end def write_animation_frame(frame, id, begin_text, parent) use = parent.add_element('use', 'xlink:href' => "##{id}", 'visibility' => 'hidden') use.add_element('set', 'id' => "#{id}_anim", 'attributeName' => 'visibility', 'to' => 'visible', 'begin' => begin_text, 'dur' => "#{frame.duration}s") end # Write show control. def write_show_control(base, parent_node) REXML::Comment.new(' Main control. ', parent_node) write_show_control_header(base, parent_node) write_show_control_main(base, parent_node) end # Write show control header. def write_show_control_header(base, parent_node) repeat_count = base.loops.to_i == 0 ? 'indefinite' : base.loops.to_i.to_s parent_node.add_element('animate', 'id' => 'controller', 'begin' => '0s', 'dur' => "#{base.total_duration}s", 'repeatCount' => repeat_count) end # Write show control main. def write_show_control_main(base, parent_node) use = parent_node.add_element('use', 'xlink:href' => '#frame0') use.add_element('set', 'attributeName' => 'xlink:href', 'to' => '#animation', 'begin' => 'controller.begin') use.add_element('set', 'attributeName' => 'xlink:href', 'to' => "#frame#{base.frames.length - 1}", 'begin' => 'controller.end') end end # class SVGWriter end # module Parser end # module SVG end # module Phantom