#--
# $Id: stretchable.rb,v 1.3 2006/07/18 22:24:59 rmagick Exp $
# Copyright (C) 2006 Timothy P. Hunter
#++

class Magick::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)
                raise(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
                    raise(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.
    # Only RVG objects are stretchable.
    module Stretchable

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

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

            return [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
            return [tx, ty]
        end

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

        # Scale to larger viewbox dimension
        def set_viewbox_slice(width, height)
            sx = sy = [width / @vbx_width, height / @vbx_height].max
            return [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, ty = 0, 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
            raise(ArgumentError, "viewbox width must be > 0 (#{width} given)") unless width >= 0
            raise(ArgumentError, "viewbox height must be > 0 (#{height} given)") unless height >= 0
            yield(self) if block_given?
            self
        end

    end     # module Stretchable

end     # class Magick::RVG

