Class: DYI::Shape::Path

Inherits:
Base show all
Includes:
Markable
Defined in:
lib/dyi/shape/path.rb

Overview

Path represent the outline of a shape.

Path object has a concept of a current point. In an analogy with drawing on paper, the current point can be thought of as the location of the pen. The position of the pen can be changed, and the outline of a shape (open or closed) can be traced by dragging the pen in either straight lines or curves.

Commands of Drawing Paths

Lines or curves is drawn using following commad:

  • move_to commands – Method #move_to and #rmove_to establish a new current point. The effect is as if the “pen” were lifted and moved to a new location.

  • close_path command – Method #close_path ends the current subpath and causes an automatic straight line to be drawn from the current point to the initial point of the current subpath.

  • line_to commands – Method #line_to and #rline_to draw straight lines from the current point to a new point.

  • Cubic Bézier Curve commands – Method #curve_to and #rcurve_to draw a cubic Bézier curve from the current point.

  • Quadratic Bézier Curve commands – Method #quadratic_curve_to and #rquadratic_curve_to draw a quadratic Bézier curve from the current point.

  • Elliptical Arc Curve commands – Method #arc_to and #rarc_to draw an elliptical arc from the current point.

See the documentation of each method for more infomation.

Since:

  • 0.0.0

Defined Under Namespace

Classes: ArcCommand, CloseCommand, CommandBase, CurveCommand, CurveCommandBase, HorizontalLineCommand, LineCommand, MoveCommand, PathData, QuadraticCurveCommand, ShorthandCurveCommand, ShorthandQuadraticCurveCommand, VerticalLineCommand

Constant Summary

Constants inherited from GraphicalElement

GraphicalElement::CLASS_REGEXP

Constants inherited from Element

Element::ID_REGEXP

Instance Attribute Summary

Attributes inherited from Base

#anchor_href, #anchor_target, #attributes, #clipping, #parent

Attributes inherited from GraphicalElement

#css_class

Attributes inherited from Element

#description, #title

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Markable

#marker, #set_marker

Methods inherited from Base

#add_animation, #add_painting_animation, #add_transform_animation, #animate?, #animations, #clear_clipping, #draw_on, #has_uri_reference?, #root_element?, #root_node?, #rotate, #scale, #set_clipping, #set_clipping_shapes, #set_event, #skew_x, #skew_y, #transform, #translate

Methods inherited from GraphicalElement

#add_css_class, #add_event_listener, #css_classes, #event_listeners, #event_target?, #remove_css_class, #remove_event_listener, #set_event, #to_reused_source

Methods inherited from Element

#canvas, #child_elements, #has_uri_reference?, #id, #id=, #include_external_file?, #inner_id

Constructor Details

#initialize(start_point, options = {}) ⇒ Path

Returns a new instance of Path.

Since:

  • 0.0.0



60
61
62
63
64
65
66
67
# File 'lib/dyi/shape/path.rb', line 60

def initialize(start_point, options={})
  @path_data = case start_point
                 when PathData then start_point
                 else PathData.new(start_point)
               end
  @attributes = init_attributes(options)
  @marker = {}
end

Class Method Details

.draw(start_point, options = {}) {|path| ... } ⇒ Object

Yields:

  • (path)

Since:

  • 0.0.0



477
478
479
480
481
# File 'lib/dyi/shape/path.rb', line 477

def draw(start_point, options={}, &block)
  path = new(start_point, options)
  yield path
  path
end

.draw_and_close(start_point, options = {}, &block) ⇒ Object

Since:

  • 0.0.0



483
484
485
486
487
# File 'lib/dyi/shape/path.rb', line 483

def draw_and_close(start_point, options={}, &block)
  path = draw(start_point, options, &block)
  path.close_path unless path.close?
  path
end

Instance Method Details

#arc_to(point, radius_x, radius_y, rotation = 0, is_large_arc = false, is_clockwise = true) ⇒ Object

Since:

  • 0.0.0



241
242
243
# File 'lib/dyi/shape/path.rb', line 241

def arc_to(point, radius_x, radius_y, rotation=0, is_large_arc=false, is_clockwise=true)
  push_command(:arc_to, radius_x, radius_y, rotation, is_large_arc, is_clockwise, point)
end

#bottomObject

Since:

  • 0.0.0



305
306
307
# File 'lib/dyi/shape/path.rb', line 305

def bottom
  edge_coordinate(:bottom)
end

#close?Boolean

Returns:

  • (Boolean)

Since:

  • 0.0.0



249
250
251
# File 'lib/dyi/shape/path.rb', line 249

def close?
  @path_data.close?
end

#close_pathObject

Since:

  • 0.0.0



253
254
255
# File 'lib/dyi/shape/path.rb', line 253

def close_path
  push_command(:close_path)
end

#compatible_path_dataObject

Since:

  • 0.0.0



285
286
287
# File 'lib/dyi/shape/path.rb', line 285

def compatible_path_data
  @path_data.compatible_path_data
end

#concise_path_dataObject

Since:

  • 0.0.0



289
290
291
# File 'lib/dyi/shape/path.rb', line 289

def concise_path_data
  @path_data.to_concise_syntax
end

#current_pointObject

Since:

  • 0.0.0



261
262
263
# File 'lib/dyi/shape/path.rb', line 261

def current_point
  @path_data.current_point
end

#current_start_pointObject

Since:

  • 0.0.0



265
266
267
# File 'lib/dyi/shape/path.rb', line 265

def current_start_point
  @path_data.current_start_point
end

#curve_to(*points) ⇒ Object

Raises:

  • (ArgumentError)

Since:

  • 0.0.0



229
230
231
232
233
# File 'lib/dyi/shape/path.rb', line 229

def curve_to(*points)
  raise ArgumentError, "number of points must be odd number of 3 or more" if points.size % 2 == 0 || points.size < 3
  push_command(:curve_to, points[0], points[1], points[2])
  push_command(:shorthand_curve_to, *points[3..-1]) if points.size > 3
end

#has_marker?(point_type) ⇒ Boolean

Returns whether this shape has a marker symbol.

Parameters:

  • point_type (Symbol)

    the type of marker point. Specifies the following values: :start, :mid, :end

Returns:

  • (Boolean)

    true if the shape has a marker at the cpecified point, false otherwise

Since:

  • 1.2.0



315
316
317
# File 'lib/dyi/shape/path.rb', line 315

def has_marker?(point_type)
  !@marker[point_type].nil?
end

#leftObject

Since:

  • 0.0.0



293
294
295
# File 'lib/dyi/shape/path.rb', line 293

def left
  edge_coordinate(:left)
end

#line_to(*points) ⇒ Object

Draws straight lines from the current point to a given point, which is specified a absolute coordinate. The new current point becomes the finally given point.

When multiple points is given as argument, draws a polyline. see example.

Examples:

canvas = DYI::Canvas.new(100,100)
pen = DYI::Drawing::Pen.black_pen
pen.draw_path(canvas, [20, 20]) {|path|
  path.line_to([20, 50], [30, 20], [30, 50])
  # The last expression equals to following expressions
  # path.line_to([20, 50])
  # path.line_to([30, 20])
  # path.line_to([30, 50])
}

Parameters:

  • point (Coordinate)

    the absolute coordinate which the line is drawn from current point to

See Also:

Since:

  • 0.0.0



139
140
141
# File 'lib/dyi/shape/path.rb', line 139

def line_to(*points)
  push_command(:line_to, *points)
end

#move_to(*points) ⇒ Object

Starts a new sub-path at a given point, which is specified a absolute coordinate. The new current points become the given point.

When multiple points is given as arguments, starts a new sub-path at the first point and draws straight line to the subsequent points. see example.

Examples:

canvas = DYI::Canvas.new(100,100)
pen = DYI::Drawing::Pen.black_pen
pen.draw_path(canvas, [20, 20]) {|path|
  path.line_to([20, 50])
  path.move_to([30, 20], [30, 50], [40, 50])
  # The last expression equals to following expressions
  # path.move_to([30, 20])
  # path.line_to([30, 50])
  # path.line_to([40, 50])
}

Parameters:

  • point (Coordinate)

    the absolute coordinate of the start point of the new sub-path. The second and subsequent arguments are the absolute point to which the line is drawn from previous point

See Also:

Since:

  • 0.0.0



90
91
92
# File 'lib/dyi/shape/path.rb', line 90

def move_to(*points)
  push_command(:move_to, *points)
end

#path_dataObject

Since:

  • 0.0.0



281
282
283
# File 'lib/dyi/shape/path.rb', line 281

def path_data
  @path_data
end

#path_pointsObject

Since:

  • 0.0.0



277
278
279
# File 'lib/dyi/shape/path.rb', line 277

def path_points
  @path_data.path_points
end

#pop_commandObject

Since:

  • 0.0.0



273
274
275
# File 'lib/dyi/shape/path.rb', line 273

def pop_command
  @path_data.pop
end

#push_command(command_type, *args) ⇒ Object

Since:

  • 0.0.0



269
270
271
# File 'lib/dyi/shape/path.rb', line 269

def push_command(command_type, *args)
  @path_data.push_command(command_type, *args)
end

#quadratic_curve_to(*points) ⇒ Object

Draws quadratic Bézier curves from the current point to the second argument point using first argument point as control-point. The control-point and pass-point are specified a absolute coordinate. The new current point becomes the point to specify in second argument.

When three or more points is given as the argument, draws polybézier-curves. In this case, the control-point is assumed to be the reflection of the control-point on the previouse quadratic Bézier curve relative to the current point. see example.

Examples:

canvas = DYI::Canvas.new(100,100)
pen = DYI::Drawing::Pen.black_pen
pen.draw_path(canvas, [20, 20]) {|path|
  path.quadratic_curve_to([40, 20], [60, 50], [60, 80])
  # The last expression equals to following expressions
  # path.quadratic_curve_to([40, 20], [60, 50])
  # path.quadratic_curve_to([80, 80], [60, 80])
  #     control-point [80,80] is reflection of first curve's control-point [40, 20]
  #     across current point [60, 50].
}

Parameters:

  • point0 (Coordinate)

    the absolute coordinate of the control-point of the quadratic Bézier curve

  • point1 (Coordinate)

    the absolute coordinate which the curve is drawn from current point to

Raises:

  • (ArgumentError)

See Also:

Since:

  • 0.0.0



191
192
193
194
195
# File 'lib/dyi/shape/path.rb', line 191

def quadratic_curve_to(*points)
  raise ArgumentError, "number of points must be 2 or more" if points.size < 2
  push_command(:quadratic_curve_to, points[0], points[1])
  push_command(:shorthand_quadratic_curve_to, *points[2..-1]) if points.size > 2
end

#rarc_to(point, radius_x, radius_y, rotation = 0, is_large_arc = false, is_clockwise = true) ⇒ Object

Since:

  • 0.0.0



245
246
247
# File 'lib/dyi/shape/path.rb', line 245

def rarc_to(point, radius_x, radius_y, rotation=0, is_large_arc=false, is_clockwise=true)
  push_command(:rarc_to, radius_x, radius_y, rotation, is_large_arc, is_clockwise, point)
end

#rcurve_to(*points) ⇒ Object

Raises:

  • (ArgumentError)

Since:

  • 0.0.0



235
236
237
238
239
# File 'lib/dyi/shape/path.rb', line 235

def rcurve_to(*points)
  raise ArgumentError, "number of points must be odd number of 3 or more" if points.size % 2 == 0 || points.size < 3
  push_command(:rcurve_to, points[0], points[1], points[2])
  push_command(:rshorthand_curve_to, *points[3..-1]) if points.size > 3
end

#rightObject

Since:

  • 0.0.0



297
298
299
# File 'lib/dyi/shape/path.rb', line 297

def right
  edge_coordinate(:right)
end

#rline_to(*points) ⇒ Object

Draws straight lines from the current point to a given point, which is specified a relative coordinate to current point. The new current point becomes the finally given point.

When multiple points is given as arguments, draws a polyline. see example.

Examples:

canvas = DYI::Canvas.new(100,100)
pen = DYI::Drawing::Pen.black_pen
pen.draw_path(canvas, [20, 20]) {|path|
  path.rline_to([0, 30], [10, -30], [0, 30])
  # The last expression equals to following expressions
  # path.rline_to([0, 30])
  # path.rline_to([10, -30])
  # path.rline_to([0, 30])
}

Parameters:

  • point (Coordinate)

    the relavive coordinate which the line is drawn from current point to

See Also:

Since:

  • 0.0.0



162
163
164
# File 'lib/dyi/shape/path.rb', line 162

def rline_to(*points)
  push_command(:rline_to, *points)
end

#rmove_to(*points) ⇒ Object

Starts a new sub-path at a given point, which is specified a relative coordinate to current point. The new current point becomes the finally given point.

When multiple points is given as arguments, starts a new sub-path at the first point and draws straight line to the subsequent points. see example.

Examples:

canvas = DYI::Canvas.new(100,100)
pen = DYI::Drawing::Pen.black_pen
pen.draw_path(canvas, [20, 20]) {|path|
  path.rline_to([0, 30])
  path.rmove_to([10, -30], [0, 30], [10, 0])
  # The last expression equals to following expressions
  # path.rmove_to([10, -30])
  # path.rline_to([0, 30])
  # path.rline_to([10, 0])
}

Parameters:

  • point (Coordinate)

    the relative coordinate of the start point of the new sub-path. The second and subsequent arguments are the relative point to which the line is drawn from previous point

See Also:

Since:

  • 0.0.0



116
117
118
# File 'lib/dyi/shape/path.rb', line 116

def rmove_to(*points)
  push_command(:rmove_to, *points)
end

#rquadratic_curve_to(*points) ⇒ Object

Draws quadratic Bézier curves from the current point to the second argument point using first argument point as control-point. The control-point and pass-point are specified a relative coordinate to current point. The new current point becomes the point to specify in second argument.

When three or more points is given as the argument, draws polybézier-curves. In this case, the control-point is assumed to be the reflection of the control-point on the previouse quadratic Bézier curve relative to the current point. see example.

Examples:

canvas = DYI::Canvas.new(100,100)
pen = DYI::Drawing::Pen.black_pen
pen.draw_path(canvas, [20, 20]) {|path|
  path.rquadratic_curve_to([20, 0], [40, 30], [0, 30])
  # The last expression equals to following expressions
  # path.quadratic_curve_to([20, 0], [40, 30])
  # path.quadratic_curve_to([20, 30], [0, 30])
  #     control-point [20, 30] is reflection of first curve's control-point [-20, -30].
  #     (that is relative coordinate to current point. i.e. [20, 0] - [40, 30])
}

Parameters:

  • point0 (Coordinate)

    the relative coordinate of the control-point of the quadratic Bézier curve

  • point1 (Coordinate)

    the relative coordinate which the curve is drawn from current point to

Raises:

  • (ArgumentError)

See Also:

Since:

  • 0.0.0



223
224
225
226
227
# File 'lib/dyi/shape/path.rb', line 223

def rquadratic_curve_to(*points)
  raise ArgumentError, "number of points must be 2 or more" if points.size < 2
  push_command(:rquadratic_curve_to, points[0], points[1])
  push_command(:rshorthand_quadratic_curve_to, *points[2..-1]) if points.size > 2
end

#start_pointObject

Since:

  • 0.0.0



257
258
259
# File 'lib/dyi/shape/path.rb', line 257

def start_point
  @path_data.start_point
end

#topObject

Since:

  • 0.0.0



301
302
303
# File 'lib/dyi/shape/path.rb', line 301

def top
  edge_coordinate(:top)
end

#write_as(formatter, io = $>) ⇒ Object

def line_bezier_paths

  start_point = Coordinate::ZERO
  current_point = Coordinate::ZERO
  last_ctrl_point = nil
  @path_data.inject([]) do |result, path_point|
    case path_point.first
    when 'M', 'L', 'C'
      last_ctrl_point = path_point[2]
      current_point = path_point.last
      result << path_point
      start_point = current_point if path_point.first == 'M'
    when 'm', 'l'
      result << [path_point.first.upcase, (current_point += path_point.last)]
      start_point = current_point if path_point.first == 'm'
    when 'c'
      result << [path_point.first.upcase, current_point + path_point[1], (last_ctrl_point = current_point + path_point[2]), (current_point += path_point.last)]
    when 'Z'
      result << path_point
      current_point = start_point
    when 'Q', 'q', 'T', 't'
      case path_point.first
      when 'Q'
        last_ctrl_point = path_point[1]
        last_point = path_point[2]
      when 'q'
        last_ctrl_point = current_point + path_point[1]
        last_point = current_point + path_point[2]
      when 'T'
        last_ctrl_point = current_point * 2 - last_ctrl_point
        last_point = path_point[1]
      when 't'
        last_ctrl_point = current_point * 2 - last_ctrl_point
        last_point = current_point + path_point[1]
      end
      ctrl_point1 = (current_point + last_ctrl_point * 2).quo(3)
      ctrl_point2 = (last_point + last_ctrl_point * 2).quo(3)
      result << ['C', ctrl_point1, ctrl_point2, (current_point = last_point)]
    when 'S', 's'
      case path_point.first
      when 'S'
        ctrl_point1 = current_point * 2 - last_ctrl_point
        ctrl_point2 = path_point[1]
        last_point = path_point[2]
      when 's'
        ctrl_point1 = current_point * 2 - last_ctrl_point
        ctrl_point2 = current_point + path_point[1]
        last_point = current_point + path_point[2]
      end
      result << ['C', ctrl_point1, (last_ctrl_point = ctrl_point2), (current_point = last_point)]
    when 'A', 'a'
      rx, ry, lotate, large_arc, clockwise, last_point = path_point[1..-1]
      last_point += current_point if path_point.first == 'a'
      rx = rx.to_f
      ry = ry.to_f
      lotate = lotate * Math::PI / 180
      cu_pt = Coordinate.new(
        current_point.x * Math.cos(lotate) / rx + current_point.y * Math.sin(lotate) / rx,
        current_point.y * Math.cos(lotate) / ry - current_point.x * Math.sin(lotate) / ry)
      en_pt = Coordinate.new(
        last_point.x * Math.cos(lotate) / rx + last_point.y * Math.sin(lotate) / rx,
        last_point.y * Math.cos(lotate) / ry - last_point.x * Math.sin(lotate) / ry)
      begin
        k = Math.sqrt(4.quo((en_pt.x.to_f - cu_pt.x.to_f) ** 2 + (en_pt.y.to_f - cu_pt.y.to_f) ** 2) - 1) * (large_arc == clockwise ? 1 : -1)
        center_pt = Coordinate.new(
          cu_pt.x - cu_pt.y * k + en_pt.x + en_pt.y * k,
          cu_pt.y + cu_pt.x * k + en_pt.y - en_pt.x * k) * 0.5
        cu_pt -= center_pt
        en_pt -= center_pt
        theta = Math.acos(cu_pt.x.to_f * en_pt.x.to_f + cu_pt.y.to_f * en_pt.y.to_f)
        theta = 2 * Math::PI - theta if large_arc == 1
      rescue
        center_pt = Coordinate.new(cu_pt.x + en_pt.x, cu_pt.y + en_pt.y) * 0.5
        cu_pt -= center_pt
        en_pt -= center_pt
        theta = Math::PI
      end
      d_count = theta.quo(Math::PI / 8).ceil
      d_t = theta / d_count * (clockwise == 1 ? 1 : -1)
      curves = []
      cos = Math.cos(d_t)
      sin = Math.sin(d_t)
      tan = Math.tan(d_t / 4)
      mat = Matrix.new(
        rx * Math.cos(lotate), rx * Math.sin(lotate),
        -ry * Math.sin(lotate), ry * Math.cos(lotate),
        center_pt.x * rx * Math.cos(lotate) - center_pt.y * ry * Math.sin(lotate),
        center_pt.y * ry * Math.cos(lotate) + center_pt.x * rx * Math.sin(lotate))
      d_count.times do |i|
        ne_pt = Coordinate.new(cu_pt.x * cos - cu_pt.y * sin, cu_pt.y * cos + cu_pt.x * sin)
        curves << [
          mat.translate(Coordinate.new(cu_pt.x - cu_pt.y * 4 * tan / 3, cu_pt.y + cu_pt.x * 4 * tan / 3)),
          mat.translate(Coordinate.new(ne_pt.x + ne_pt.y * 4 * tan / 3, ne_pt.y - ne_pt.x * 4 * tan / 3)),
          mat.translate(ne_pt)]
        cu_pt = ne_pt
      end
      curves.last[2] = last_point
      current_point = last_point
      curves.each do |c|
        result << ['C', c[0], c[1], c[2]]
      end
    end
    result
  end
end

Since:

  • 0.0.0



424
425
426
# File 'lib/dyi/shape/path.rb', line 424

def write_as(formatter, io=$>)
  formatter.write_path(self, io, &(block_given? ? Proc.new : nil))
end