#-- # $Id: stretchable.rb,v 1.4.4.1 2008/02/24 23:23:04 rmagick Exp $ # Copyright (C) 2008 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) 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 # return the user-coordinate space attributes if defined class << self if not defined? @redefined then @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