# primitive.rb: direct use of graphics primitives for tioga # copyright (c) 2006, 2007, 2008, 2009 by Vincent Fourmond # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details (in the COPYING file). require 'ctioga2/utils' require 'ctioga2/log' require 'ctioga2/graphics/types' require 'ctioga2/graphics/styles' require 'shellwords' # This module contains all the classes used by ctioga module CTioga2 module Graphics module Elements # A TiogaElement that represents a graphics primitive. # # @todo Most of the objects here should rely on getting a # BasicStyle object from the options hash and use it to # draw. There is no need to make cumbersome and hard to extend # hashes. class TiogaPrimitiveCall < TiogaElement # Some kind of reimplementation of Command for graphics # primitives class TiogaPrimitive # A name (not very useful, but, well, we never know) attr_accessor :name # An array of compulsory arguments (type specifications) attr_accessor :compulsory_arguments # A hash of optional arguments attr_accessor :optional_arguments # A block that will receive a FigureMaker object, the # compulsory arguments and a hash containing optional ones. attr_accessor :funcall # Creates a TiogaPrimitive object def initialize(name, comp, opts = {}, &code) @name = name @compulsory_arguments = comp @optional_arguments = opts @funcall = code end end # A TiogaPrimitive object describing the current primitive attr_accessor :primitive # An array containing the values of the compulsory arguments attr_accessor :arguments # A hash containing the values of the optional arguments attr_accessor :options # The last curve's style... attr_accessor :last_curve_style # Creates a new TiogaPrimitiveCall object. def initialize(primitive, arguments, options) @primitive = primitive @arguments = arguments @options = options end undef :clipped, :clipped= def clipped if @options.key? 'clipped' return @options['clipped'] else return true # Defaults to clipped end end undef :depth, :depth= def depth @options['depth'] || 50 end @known_primitives = {} PrimitiveCommands = {} PrimitiveGroup = CmdGroup.new('tioga-primitives', "Graphics primitives", "Tioga graphics primitives", 3) # Creates a new primitive with the given parameters, and makes # it immediately available as a command. def self.primitive(name, long_name, comp, opts = {}, desc = nil, &code) primitive = TiogaPrimitive.new(name, comp, opts, &code) @known_primitives[name] = primitive # Now, create the command cmd_args = comp.map do |x| CmdArg.new(x) end cmd_opts = {} for k,v in opts cmd_opts[k] = if v.respond_to?(:type) v else CmdArg.new(v) end end cmd_opts['clipped'] = CmdArg.new('boolean') cmd_opts['depth'] = CmdArg.new('integer') cmd = Cmd.new("draw-#{name}",nil,"--draw-#{name}", cmd_args, cmd_opts) do |plotmaker, *rest| options = rest.pop call = Elements:: TiogaPrimitiveCall.new(primitive, rest, options) call.last_curve_style = plotmaker.curve_style_stack.last plotmaker.root_object.current_plot. add_element(call) end if ! desc desc = "Directly draws #{long_name} on the current plot" end cmd.describe("Draws #{long_name}", desc, PrimitiveGroup) PrimitiveCommands[name] = cmd end # This creates a primitive base on a style object, given a # _style_class_, the base _style_name_ for the underlying # styling system, options to remove and options to add. # # The underlying code receives: # * the FigureMaker object # * the compulsory arguments # * the style # * the raw options def self.styled_primitive(name, long_name, comp, style_class, style_name, without = [], additional_options = {}, set_style_command = nil, # This # could be # removed &code) options = style_class.options_hash.without(without) options.merge!(additional_options) options['base-style'] = 'text' # the base style name set_style_command ||= style_name desc = <<"EOD" Draws #{long_name} on the current plot, using the given style. For more information on the available options, see the {command: define-#{set_style_command}-style} command. EOD self.primitive(name, long_name, comp, options, desc) do |*all| opts = all.pop st_name = opts['base-style'] || "base" style = Styles::StyleSheet.style_for(style_class,st_name) style.set_from_hash(opts) all << style << opts code.call(*all) end end # Returns a pair primitive/primitive command for the named # primitive, or [ _nil_, _nil_ ] def self.get_primitive(name) return [@known_primitives[name], PrimitiveCommands[name]] end # Now, a list of primitives, along with their code. styled_primitive("text", "text", [ 'point', 'text' ], Styles::FullTextStyle, 'text', ['text'], {'font' => 'latex-font'} ) do |t, point, string, style, options| # @todo add a way to specify fonts ??? options ||= {} if options['font'] string = options['font'].fontify(string) end style.draw_text(t, string, *(point.to_figure_xy(t))) end styled_primitive("marker", "marker", [ 'point', 'marker' ], Styles::MarkerStringStyle, 'marker', ['font'] # font doesn't make any sense with a # marker spec ) do |t, point, marker, style, options| style.draw_marker(t, marker, *point.to_figure_xy(t)) end styled_primitive("string-marker", "marker", [ 'point', 'text' ], Styles::MarkerStringStyle, 'marker-string', [], {}, 'marker' ) do |t, point, string, style, options| style.draw_string_marker(t, string, *point.to_figure_xy(t)) end # options for arrows (and therefore tangents) ArrowOptions = { 'color' => 'color', 'head_scale' => 'float', 'head_marker' => 'marker', 'head_color' => 'color', 'tail_scale' => 'float', 'tail_marker' => 'marker', 'tail_color' => 'color', 'line_width' => 'float', 'line_style' => 'line-style', } styled_primitive("arrow", "arrow", [ 'point', 'point' ], Styles::ArrowStyle, 'arrow') do |t, tail, head, style, options| style.draw_arrow(t, *( tail.to_figure_xy(t) + head.to_figure_xy(t) )) end styled_primitive("line", "line", [ 'point', 'point' ], Styles::StrokeStyle, 'line' ) do |t, tail, head, style, options| style.draw_line(t, *( tail.to_figure_xy(t) + head.to_figure_xy(t) )) end # Here, we need to add deprecated options for backward # compatibility for cmd in ['draw-line', 'draw-arrow'] Commands::make_alias_for_option cmd, 'width', 'line_width', true Commands::make_alias_for_option cmd, 'style', 'line_style', true end styled_primitive("box", "box", [ 'point', 'point' ], Styles::BoxStyle, 'box') do |t, tl, br, style, options| x1,y1 = tl.to_figure_xy(t) x2,y2 = br.to_figure_xy(t) style.draw_box(t, x1, y1, x2, y2) end Commands::make_alias_for_option 'draw-box', 'fill_color', 'fill-color' Commands::make_alias_for_option 'draw-box', 'fill_transparency', 'fill-transparency' protected # Draws the primitive def real_do(t) args = @arguments + [@options] ## @todo this is a really ugly hack for passing ## last_curve_style around $last_curve_style = @last_curve_style primitive.funcall.call(t, *args) end DrawingSpecType = CmdType.new('drawing-spec', :string, <