#--############################################################################
# $Id: rvg.rb,v 1.10 2009/02/28 23:52:28 rmagick Exp $
#
# Copyright (C) 2009 by Timothy P. Hunter
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
#
# This software is OSI Certified Open Source Software.
# OSI Certified is a certification mark of the Open Source Initiative.
#
#++############################################################################
require 'rmagick'
require 'rvg/misc'
require 'rvg/describable'
require 'rvg/stylable'
require 'rvg/transformable'
require 'rvg/stretchable'
require 'rvg/text'
require 'rvg/embellishable'
require 'rvg/container'
require 'rvg/pathdata'
require 'rvg/clippath'
require 'rvg/paint'
require 'rvg/units'
require 'pp' if ENV['debug_rvg']
# RVG is the main class in this library. All graphic elements
# must be contained within an RVG object.
module Magick
class RVG
include Stylable
include Transformable
include Stretchable
include Embellishable
include Describable
include Duplicatable
private
# background_fill defaults to 'none'. If background_fill has been set to something
# else, combine it with the background_fill_opacity.
def bgfill
if @background_fill.nil?
color = Magick::Pixel.new(0, 0, 0, Magick::OpaqueAlpha)
else
color = @background_fill
color.alpha = @background_fill_opacity * Magick::OpaqueAlpha
end
color
end
def new_canvas
if @background_pattern
canvas = Magick::Image.new(@width, @height, @background_pattern)
elsif @background_image
canvas = if @width != @background_image.columns || @height != @background_image.rows
case @background_position
when :scaled
@background_image.resize(@width, @height)
when :tiled
Magick::Image.new(@width, @height, Magick::TextureFill.new(@background_image))
when :fit
width = @width
height = @height
bgcolor = bgfill
@background_image.change_geometry(Magick::Geometry.new(width, height)) do |new_cols, new_rows|
bg_image = @background_image.resize(new_cols, new_rows)
if bg_image.columns != width || bg_image.rows != height
bg = Magick::Image.new(width, height) { self.background_color = bgcolor }
bg_image = bg.composite!(bg_image, Magick::CenterGravity, Magick::OverCompositeOp)
end
bg_image
end
end
else
@background_image.copy
end
else
bgcolor = bgfill
canvas = Magick::Image.new(Integer(@width), Integer(@height)) { self.background_color = bgcolor }
end
canvas[:desc] = @desc if @desc
canvas[:title] = @title if @title
canvas[:metadata] = @metadata if @metadata
canvas
end
if ENV['debug_prim']
def print_gc(gc)
primitives = gc.inspect.split(/\n/)
indent = 0
primitives.each do |cmd|
indent -= 1 if cmd['pop ']
print((' ' * indent), cmd, "\n")
indent += 1 if cmd['push ']
end
end
end
public
WORD_SEP = / / # Regexp to separate words
# The background image specified by background_image=
attr_reader :background_image
# The background image layout specified by background_position=
attr_reader :background_position
# The background fill color specified by background_fill=
attr_reader :background_fill
# The background fill color opacity specified by background_fill_opacity=
attr_reader :background_fill_opacity
# The image after drawing has completed
attr_reader :canvas
# For embedded RVG objects, the x-axis coordinate of the upper-left corner
attr_reader :x
# For embedded RVG objects, the x-axis coordinate of the upper-left corner
attr_reader :y
attr_reader :width, :height
# Sets an image to use as the canvas background. See background_position= for layout options.
def background_image=(bg_image)
warn 'background_image= has no effect in nested RVG objects' if @nested
raise ArgumentError, "background image must be an Image (got #{bg_image.class})" if bg_image && !bg_image.is_a?(Magick::Image)
@background_image = bg_image
end
# Sets an object to use to fill the canvas background.
# The object must have a fill method. See the Fill Classes
# section in the RMagick doc for more information.
def background_pattern=(filler)
warn 'background_pattern= has no effect in nested RVG objects' if @nested
@background_pattern = filler
end
# How to position the background image on the canvas. One of the following symbols:
# [:scaled] Scale the image to the canvas width and height.
# [:tiled] Tile the image across the canvas.
# [:fit] Scale the image to fit within the canvas while retaining the
# image proportions. Center the image on the canvas. Color any part of
# the canvas not covered by the image with the background color.
def background_position=(pos)
warn 'background_position= has no effect in nested RVG objects' if @nested
bg_pos = pos.to_s.downcase
raise ArgumentError, "background position must be `scaled', `tiled', or `fit' (#{pos} given)" unless %w[scaled tiled fit].include?(bg_pos)
@background_position = bg_pos.to_sym
end
# Sets the canvas background color. Either a Magick::Pixel or a color name.
# The default fill is "none", that is, transparent black.
def background_fill=(color)
warn 'background_fill= has no effect in nested RVG objects' if @nested
if !color.is_a?(Magick::Pixel)
begin
@background_fill = Magick::Pixel.from_color(color)
rescue Magick::ImageMagickError
raise ArgumentError, "unknown color `#{color}'"
rescue TypeError
raise TypeError, "cannot convert #{color.class} into Pixel"
rescue StandardError
raise ArgumentError, "argument must be a color name or a Pixel (got #{color.class})"
end
else
@background_fill = color
end
end
# Opacity of the background fill color, a number between 0.0 (transparent) and
# 1.0 (opaque). The default is 1.0 when the background_fill= attribute has been set.
def background_fill_opacity=(opacity)
warn 'background_fill_opacity= has no effect in nested RVG objects' if @nested
begin
@background_fill_opacity = Float(opacity)
rescue ArgumentError
raise ArgumentError, "background_fill_opacity must be a number between 0 and 1 (#{opacity} given)"
end
end
# Draw a +width+ x +height+ image. The image is specified by calling
# one or more drawing methods on the RVG object.
# You can group the drawing method calls in the optional associated block.
# The +x+ and +y+ arguments have no meaning for the outermost RVG object.
# On nested RVG objects [+x+, +y+] is the coordinate of the upper-left
# corner in the containing canvas on which the nested RVG object is placed.
#
# Drawing occurs on a +canvas+ created by the #draw method. By default the
# canvas is transparent. You can specify a different canvas with the
# #background_fill= or #background_image= methods.
#
# RVG objects are _containers_. That is, styles and transforms defined
# on the object are used by contained objects such as shapes, text, and
# groups unless overridden by an inner container or the object itself.
def initialize(width = nil, height = nil)
super
@width = width
@height = height
@content = Content.new
@canvas = nil
@background_fill = nil
@background_fill_opacity = 1.0 # applies only if background_fill= is used
@background_position = :scaled
@background_pattern, @background_image, @desc, @title, @metadata = nil
@x = 0.0
@y = 0.0
@nested = false
yield(self) if block_given?
end
# Construct a canvas or reuse an existing canvas.
# Execute drawing commands. Return the canvas.
def draw
raise StandardError, 'draw not permitted in nested RVG objects' if @nested
@canvas ||= new_canvas # allow drawing over existing canvas
gc = Utility::GraphicContext.new
add_outermost_primitives(gc)
pp(self) if ENV['debug_rvg']
print_gc(gc) if ENV['debug_prim']
gc.draw(@canvas)
@canvas
end
# Accept #use arguments. Use (x,y) to generate an additional translate.
# Override @width and @height if new values are supplied.
# @private
def ref(x, y, rw, rh)
translate(x, y) if x != 0 || y != 0
@width = rw if rw
@height = rh if rh
end
# Used by Magick::Embellishable.rvg to set non-0 x- and y-coordinates
# @private
def corner(x, y)
@nested = true
@x = Float(x)
@y = Float(y)
translate(@x, @y) if @x != 0.0 || @y != 0.0
end
# Primitives for the outermost RVG object
# @private
def add_outermost_primitives(gc)
add_transform_primitives(gc)
gc.push
add_viewbox_primitives(@width, @height, gc)
add_style_primitives(gc)
@content.each { |element| element.add_primitives(gc) }
gc.pop
self
end
# Primitives for nested RVG objects
# @private
def add_primitives(gc)
raise ArgumentError, 'RVG width or height undefined' if @width.nil? || @height.nil?
return self if @width.zero? || @height.zero?
gc.push
add_outermost_primitives(gc)
gc.pop
end
end # end class RVG
end # end module Magick