lib/chunky_png/vector.rb



module ChunkyPNG

  # Factory method for {ChunkyPNG::Vector} instances.
  #
  # @overload Vector(x0, y0, x1, y1, x2, y2, ...)
  #   Creates a vector by parsing two subsequent values in the argument list 
  #   as x- and y-coordinate of a point.
  #   @return [ChunkyPNG::Vector] The instantiated vector.
  # @overload Vector(string)
  #   Creates a vector by parsing coordinates from the input string.
  #   @return [ChunkyPNG::Vector] The instantiated vector.
  # @overload Vector(pointlike, pointlike, pointlike, ...)
  #   Creates a vector by converting every argument to a point using {ChunkyPNG.Point}.
  #   @return [ChunkyPNG::Vector] The instantiated vector.
  def self.Vector(*args)
    
    return args.first if args.length == 1 && args.first.kind_of?(ChunkyPNG::Vector)
    
    if args.length == 1 && args.first.respond_to?(:scan)
      ChunkyPNG::Vector.new(ChunkyPNG::Vector.multiple_from_string(args.first)) # e.g. ['1,1 2,2 3,3']
    else
      ChunkyPNG::Vector.new(ChunkyPNG::Vector.multiple_from_array(args)) # e.g. [[1,1], [2,2], [3,3]] or [1,1,2,2,3,3]
    end
  end
  
  # Class that represents a vector of points, i.e. a list of {ChunkyPNG::Point} instances.
  class Vector
    
    include Enumerable
    
    attr_reader :points
    
    def initialize(points = [])
      @points = points
    end
    
    def each_edge(close = true)
      raise ChunkyPNG::ExpectationFailed, "Not enough points in this path to draw an edge!" if length < 2
      points.each_cons(2) { |a, b| yield(a, b) }
      yield(points.last, points.first) if close
    end
    
    def edges(close = true)
      Enumerator.new(self, :each_edge, close)
    end
    
    def length
      points.length
    end
    
    def each(&block)
      points.each(&block)
    end
    
    def eql?(other)
      other.points == points
    end
    
    alias_method :==, :eql?
    
    def to_a
      edges
    end
    
    alias_method :to_ary, :to_a

    def x_range
      Range.new(*points.map { |p| p.x }.minmax)
    end
    
    def y_range
      Range.new(*points.map { |p| p.y }.minmax)
    end
    
    def self.multiple_from_array(source)
      return [] if source.empty?
      if source.first.kind_of?(Numeric) || source.first =~ /^\d+$/
        raise ChunkyPNG::ExpectationFailed, "The points array is expected to have an even number of items!" if source.length % 2 != 0

        points = []
        source.each_slice(2) { |x, y| points << ChunkyPNG::Point.new(x, y) }
        return points
      else
        source.map { |p| ChunkyPNG::Point(p) }
      end
    end
    
    def self.multiple_from_string(source_str)
      multiple_from_array(source_str.scan(/[\(\[\{]?(\d+)\s*[,x]?\s*(\d+)[\)\]\}]?/))
    end
  end
end