#--
# $Id: stretchable.rb,v 1.7 2009/02/28 23:52:28 rmagick Exp $
# Copyright (C) 2009 Timothy P. Hunter
#++
module Magick
  class RVG
    module PreserveAspectRatio
      #--
      #   Included in Stretchable module and Image class
      #++
      # Specifies how the image within a viewport should be scaled.
      # [+align+] a combination of 'xMin', 'xMid', or 'xMax', followed by
      #           'YMin', 'YMid', or 'YMax'
      # [+meet_or_slice+] one of 'meet' or 'slice'
      def preserve_aspect_ratio(align, meet_or_slice='meet')
        @align = align.to_s
        if @align != 'none'
          m = /\A(xMin|xMid|xMax)(YMin|YMid|YMax)\z/.match(@align)
          fail(ArgumentError, "unknown alignment specifier: #{@align}") unless m
        end

        if meet_or_slice
          meet_or_slice = meet_or_slice.to_s.downcase
          if meet_or_slice == 'meet' || meet_or_slice == 'slice'
            @meet_or_slice = meet_or_slice
          else
            fail(ArgumentError, "specifier must be `meet' or `slice' (got #{meet_or_slice})")
          end
        end
        yield(self) if block_given?
        self
      end
    end     # module PreserveAspectRatio

    # The methods in this module describe the user-coordinate space.
    # RVG and Pattern objects are stretchable.
    module Stretchable
      private

      # Scale to fit
      def set_viewbox_none(width, height)
        sx = 1.0
        sy = 1.0

        if @vbx_width
          sx = width / @vbx_width
        end
        if @vbx_height
          sy = height / @vbx_height
        end

        [sx, sy]
      end

      # Use align attribute to compute x- and y-offset from viewport's upper-left corner.
      def align_to_viewport(width, height, sx, sy)
        tx = case @align
             when /\AxMin/
               0
             when NilClass, /\AxMid/
               (width - @vbx_width*sx) / 2.0
             when /\AxMax/
               width - @vbx_width*sx
        end

        ty = case @align
             when /YMin\z/
               0
             when NilClass, /YMid\z/
               (height - @vbx_height*sy) / 2.0
             when /YMax\z/
               height - @vbx_height*sy
        end
        [tx, ty]
      end

      # Scale to smaller viewbox dimension
      def set_viewbox_meet(width, height)
        sx = sy = [width / @vbx_width, height / @vbx_height].min
        [sx, sy]
      end

      # Scale to larger viewbox dimension
      def set_viewbox_slice(width, height)
        sx = sy = [width / @vbx_width, height / @vbx_height].max
        [sx, sy]
      end

      # Establish the viewbox as necessary
      def add_viewbox_primitives(width, height, gc)
        @vbx_width  ||= width
        @vbx_height ||= height
        @vbx_x ||= 0.0
        @vbx_y ||= 0.0

        if @align == 'none'
          sx, sy = set_viewbox_none(width, height)
          tx = 0
          ty = 0
        elsif @meet_or_slice == 'meet'
          sx, sy = set_viewbox_meet(width, height)
          tx, ty = align_to_viewport(width, height, sx, sy)
        else
          sx, sy = set_viewbox_slice(width, height)
          tx, ty = align_to_viewport(width, height, sx, sy)
        end

        # Establish clipping path around the current viewport
        name = __id__.to_s
        gc.define_clip_path(name) do
          gc.path("M0,0 l#{width},0 l0,#{height} l-#{width},0 l0,-#{height}z")
        end

        gc.clip_path(name)
        # Add a non-scaled translation if meet or slice
        gc.translate(tx, ty) if tx.abs > 1.0e-10 || ty.abs > 1.0e-10
        # Scale viewbox as necessary
        gc.scale(sx, sy) if sx != 1.0 || sy != 1.0
        # Add a scaled translation if non-0 origin
        gc.translate(-@vbx_x, -@vbx_y) if @vbx_x.abs != 0.0 || @vbx_y.abs != 0
      end

      def initialize(*args, &block)
        super()
        @vbx_x, @vbx_y, @vbx_width, @vbx_height = nil
        @meet_or_slice = 'meet'
        @align = nil
      end

      public

      include PreserveAspectRatio

      # Describe a user coordinate system to be imposed on the viewbox.
      # The arguments must be numbers and the +width+ and +height+
      # arguments must be positive.
      def viewbox(x, y, width, height)
        begin
          @vbx_x = Float(x)
          @vbx_y = Float(y)
          @vbx_width = Float(width)
          @vbx_height = Float(height)
        rescue ArgumentError
          raise ArgumentError, "arguments must be convertable to float (got #{x.class}, #{y.class}, #{width.class}, #{height.class})"
        end
        fail(ArgumentError, "viewbox width must be > 0 (#{width} given)") unless width >= 0
        fail(ArgumentError, "viewbox height must be > 0 (#{height} given)") unless height >= 0

        # return the user-coordinate space attributes if defined
        class << self
          unless defined? @redefined
          @redefined = true
          define_method(:x) { @vbx_x }
          define_method(:y) { @vbx_y }
          define_method(:width) { @vbx_width}
          define_method(:height) { @vbx_height }
          end
        end

        yield(self) if block_given?
        self
      end
    end # module Stretchable
  end # class RVG
end # module Magick
