# frozen_string_literal: true
#--
# $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)
raise(ArgumentError, "unknown alignment specifier: #{@align}") unless m
end
if meet_or_slice
meet_or_slice = meet_or_slice.to_s.downcase
raise(ArgumentError, "specifier must be `meet' or `slice' (got #{meet_or_slice})") unless %w[meet slice].include?(meet_or_slice)
@meet_or_slice = meet_or_slice
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
sx = width / @vbx_width if @vbx_width
sy = height / @vbx_height if @vbx_height
[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)
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 (#{@vbx_width} given)") unless @vbx_width >= 0
raise(ArgumentError, "viewbox height must be > 0 (#{@vbx_height} given)") unless @vbx_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