#-- # $Id: embellishable.rb,v 1.9 2009/02/28 23:52:13 rmagick Exp $ # Copyright (C) 2009 Timothy P. Hunter #++ module Magick class RVG # Parent class of Circle, Ellipse, Text, etc. class Shape #:nodoc: include Stylable include Transformable include Duplicatable # Each shape can have its own set of transforms and styles. def add_primitives(gc) gc.push add_transform_primitives(gc) add_style_primitives(gc) gc.__send__(@primitive, *@args) gc.pop end end # class Shape class Circle < Shape # Define a circle with radius +r+ and centered at [cx, cy]. # Use the RVG::ShapeConstructors#circle method to create Circle objects in a container. def initialize(r, cx=0, cy=0) super() r, cx, cy = Magick::RVG.convert_to_float(r, cx, cy) if r < 0 raise ArgumentError, "radius must be >= 0 (#{r} given)" end @primitive = :circle @args = [cx, cy, cx+r, cy] self end end # class Circle class Ellipse < Shape # Define an ellipse with a center at [cx, cy], a horizontal radius +rx+ # and a vertical radius +ry+. # Use the RVG::ShapeConstructors#ellipse method to create Ellipse objects in a container. def initialize(rx, ry, cx=0, cy=0) super() rx, ry, cx, cy = Magick::RVG.convert_to_float(rx, ry, cx, cy) if rx < 0 || ry < 0 raise ArgumentError, "radii must be >= 0 (#{rx}, #{ry} given)" end @primitive = :ellipse # Ellipses are always complete. @args = [cx, cy, rx, ry, 0, 360] end end # class Ellipse class Line < Shape # Define a line from [x1, y1] to [x2, y2]. # Use the RVG::ShapeConstructors#line method to create Line objects in a container. def initialize(x1=0, y1=0, x2=0, y2=0) super() @primitive = :line @args = [x1, y1, x2, y2] end end # class Line class Path < Shape # Define an SVG path. The argument can be either a path string # or a PathData object. # Use the RVG::ShapeConstructors#path method to create Path objects in a container. def initialize(path) super() @primitive = :path @args = [path.to_s] end end # class Path class Rect < Shape # Define a width x height rectangle. The upper-left corner is at [x, y]. # If either width or height is 0, the rectangle is not rendered. # Use the RVG::ShapeConstructors#rect method to create Rect objects in a container. def initialize(width, height, x=0, y=0) super() width, height, x, y = Magick::RVG.convert_to_float(width, height, x, y) if width < 0 || height < 0 raise ArgumentError, "width, height must be >= 0 (#{width}, #{height} given)" end @args = [x, y, x+width, y+height] @primitive = :rectangle end # Specify optional rounded corners for a rectangle. The arguments # are the x- and y-axis radii. If y is omitted it defaults to x. def round(rx, ry=nil) rx, ry = Magick::RVG.convert_to_float(rx, ry || rx) if rx < 0 || ry < 0 raise ArgumentError, "rx, ry must be >= 0 (#{rx}, #{ry} given)" end @args << rx << ry @primitive = :roundrectangle self end end # class Rect class PolyShape < Shape def polypoints(points) case points.length when 1 points = Array(points[0]) when 2 x_coords = Array(points[0]) y_coords = Array(points[1]) unless x_coords.length > 0 && y_coords.length > 0 raise ArgumentError, "array arguments must contain at least one point" end n = x_coords.length - y_coords.length short = n > 0 ? y_coords : x_coords olen = short.length n.abs.times {|x| short << short[x % olen]} points = x_coords.zip(y_coords).flatten end n = points.length if n < 4 || n % 2 != 0 raise ArgumentError, "insufficient/odd number of points specified: #{n}" end return Magick::RVG.convert_to_float(*points) end end # class PolyShape class Polygon < PolyShape # Draws a polygon. The arguments are [x, y] pairs that # define the points that make up the polygon. At least two # points must be specified. If the last point is not the # same as the first, adds an additional point to close # the polygon. # Use the RVG::ShapeConstructors#polygon method to create Polygon objects in a container. def initialize(*points) super() @primitive = :polygon @args = polypoints(points) end end # class Polygon class Polyline < PolyShape # Draws a polyline. The arguments are [x, y] pairs that # define the points that make up the polyline. At least two # points must be specified. # Use the RVG::ShapeConstructors#polyline method to create Polyline objects in a container. def initialize(*points) super() points = polypoints(points) @primitive = :polyline @args = Magick::RVG.convert_to_float(*points) end end # class Polyline class Image include Stylable include Transformable include Describable include PreserveAspectRatio include Duplicatable private def align_to_viewport(scale) tx = case @align when 'none', /\AxMin/ 0 when NilClass, /\AxMid/ (@width - @image.columns*scale) / 2.0 when /\AxMax/ @width - @image.columns*scale end ty = case @align when 'none', /YMin\z/ 0 when NilClass, /YMid\z/ (@height - @image.rows*scale) / 2.0 when /YMax\z/ @height - @image.rows*scale end return [tx, ty] end def add_composite_primitive(gc) if @align == 'none' # Let RMagick do the scaling scale = 1.0 width, height = @width, @height elsif @meet_or_slice == 'meet' scale = [@width/@image.columns, @height/@image.rows].min width, height = @image.columns, @image.rows else # Establish clipping path around the current viewport name = __id__.to_s gc.define_clip_path(name) do gc.path("M#{@x},#{@y} l#{@width},0 l0,#{@height} l-#{@width},0 l0,-#{@height}z") end gc.clip_path(name) scale = [@width/@image.columns, @height/@image.rows].max width, height = @image.columns, @image.rows end tx, ty = align_to_viewport(scale) gc.composite(@x+tx, @y+ty, width*scale, height*scale, @image) end def init_viewbox() @align = nil @vbx_width, @vbx_height = @image.columns, @image.rows @meet_or_slice = 'meet' end public # Composite a raster image in the viewport defined by [x,y] and # +width+ and +height+. # Use the RVG::ImageConstructors#image method to create Text objects in a container. def initialize(image, width=nil, height=nil, x=0, y=0) super() # run module initializers @image = image.copy # use a copy of the image in case app. re-uses the argument @x, @y, @width, @height = Magick::RVG.convert_to_float(x, y, width || @image.columns, height || @image.rows) if @width < 0 || @height < 0 raise ArgumentError, "width, height must be >= 0" end init_viewbox() end def add_primitives(gc) #:nodoc: # Do not render if width or height is 0 return if @width == 0 || @height == 0 gc.push add_transform_primitives(gc) add_style_primitives(gc) add_composite_primitive(gc) gc.pop end end # class Image # Methods that construct basic shapes within a container module ShapeConstructors # Draws a circle whose center is [cx, cy] and radius is +r+. def circle(r, cx=0, cy=0) circle = Circle.new(r, cx, cy) @content << circle return circle end # Draws an ellipse whose center is [cx, cy] and having # a horizontal radius +rx+ and vertical radius +ry+. def ellipse(rx, ry, cx=0, cy=0) ellipse = Ellipse.new(rx, ry, cx, cy) @content << ellipse return ellipse end # Draws a line from [x1, y1] to [x2, y2]. def line(x1=0, y1=0, x2=0, y2=0) line = Line.new(x1, y1, x2, y2) @content << line return line end # Draws a path defined by an SVG path string or a PathData # object. def path(path) path = Path.new(path) @content << path return path end # Draws a rectangle whose upper-left corner is [x, y] and # with the specified +width+ and +height+. Unless otherwise # specified the rectangle has square corners. Returns a # Rectangle object. # # Draw a rectangle with rounded corners by calling the #round # method on the Rectangle object. rx and ry are # the corner radii in the x- and y-directions. For example: # canvas.rect(width, height, x, y).round(8, 6) # If ry is omitted it defaults to rx. def rect(width, height, x=0, y=0) rect = Rect.new(width, height, x, y) @content << rect return rect end # Draws a polygon. The arguments are [x, y] pairs that # define the points that make up the polygon. At least two # points must be specified. If the last point is not the # same as the first, adds an additional point to close # the polygon. def polygon(*points) polygon = Polygon.new(*points) @content << polygon return polygon end # Draws a polyline. The arguments are [x, y] pairs that # define the points that make up the polyline. At least two # points must be specified. def polyline(*points) polyline = Polyline.new(*points) @content << polyline return polyline end end # module ShapeContent # Methods that reference ("use") other drawable objects within a container module UseConstructors # Reference an object to be inserted into the container's # content. [x,y] is the offset from the upper-left # corner. If the argument is an RVG or Image object and +width+ and +height+ # are specified, these values will override the +width+ and +height+ # attributes on the argument. def use(obj, x=0, y=0, width=nil, height=nil) use = Use.new(obj, x, y, width, height) @content << use return use end end # module UseConstructors # Methods that construct container objects within a container module StructureConstructors # Establishes a new viewport. [x, y] is the coordinate of the # upper-left corner within the containing viewport. This is a # _container_ method. Styles and # transforms specified on this object will be used by objects # contained within, unless overridden by an inner container or # the contained object itself. def rvg(cols, rows, x=0, y=0, &block) rvg = Magick::RVG.new(cols, rows, &block) begin x, y = Float(x), Float(y) rescue ArgumentError args = [cols, rows, x, y] raise ArgumentError, "at least one argument is not convertable to Float (got #{args.collect {|a| a.class}.join(', ')})" end rvg.corner(x, y) @content << rvg return rvg end # Defines a group. # # This method constructs a new # Group _container_ object. The styles and # transforms specified on this object will be used by objects # contained within, unless overridden by an inner container or # the contained object itself. # Define grouped elements by calling RVG::Embellishable # methods within the associated block. def g(&block) group = Group.new(&block) @content << group return group end end # module StructureConstructors # Methods that construct raster image objects within a container module ImageConstructors # Composite a raster image at [x,y] # in a viewport of the specified width and height. # If not specified, the width and height are the width and height # of the image. Use the RVG::PreserveAspectRatio#preserve_aspect_ratio method to # control the placement and scaling of the image within the # viewport. By default, the image is scaled to fit inside the # viewport and centered within the viewport. def image(image, width=nil, height=nil, x=0, y=0) img = Image.new(image, width, height, x, y) @content << img return img end end # module ImageConstructors # Methods that create shapes, text, and other drawable objects # within container objects such as ::Magick::RVG and # ::Magick::RVG::Group module Embellishable include StructureConstructors include ShapeConstructors include TextConstructors include UseConstructors include ImageConstructors end # module Embellishable end # class RVG end # module Magick