class DYI::Shape::Path

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:

See the documentation of each method for more infomation.

@since 0.0.0

Public Class Methods

draw(start_point, options={}) { |path| ... } click to toggle source
# File lib/dyi/shape/path.rb, line 476
def draw(start_point, options={}, &block)
  path = new(start_point, options)
  yield path
  path
end
draw_and_close(start_point, options={}, &block) click to toggle source
# File lib/dyi/shape/path.rb, line 482
def draw_and_close(start_point, options={}, &block)
  path = draw(start_point, options, &block)
  path.close_path unless path.close?
  path
end
new(start_point, options={}) click to toggle source
# File lib/dyi/shape/path.rb, line 59
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

Public Instance Methods

arc_to(point, radius_x, radius_y, rotation=0, is_large_arc=false, is_clockwise=true) click to toggle source
# File lib/dyi/shape/path.rb, line 240
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
bottom() click to toggle source
# File lib/dyi/shape/path.rb, line 304
def bottom
  edge_coordinate(:bottom)
end
close?() click to toggle source
# File lib/dyi/shape/path.rb, line 248
def close?
  @path_data.close?
end
close_path() click to toggle source
# File lib/dyi/shape/path.rb, line 252
def close_path
  push_command(:close_path)
end
compatible_path_data() click to toggle source
# File lib/dyi/shape/path.rb, line 284
def compatible_path_data
  @path_data.compatible_path_data
end
concise_path_data() click to toggle source
# File lib/dyi/shape/path.rb, line 288
def concise_path_data
  @path_data.to_concise_syntax
end
current_point() click to toggle source
# File lib/dyi/shape/path.rb, line 260
def current_point
  @path_data.current_point
end
current_start_point() click to toggle source
# File lib/dyi/shape/path.rb, line 264
def current_start_point
  @path_data.current_start_point
end
curve_to(*points) click to toggle source
# File lib/dyi/shape/path.rb, line 228
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) click to toggle source

Returns whether this shape has a marker symbol. @param [Symbol] point_type the type of marker point. Specifies the

following values: +:start+, +:mid+, +:end+

@return [Boolean] true if the shape has a marker at the cpecified point,

false otherwise

@since 1.2.0

# File lib/dyi/shape/path.rb, line 314
def has_marker?(point_type)
  !@marker[point_type].nil?
end
left() click to toggle source
# File lib/dyi/shape/path.rb, line 292
def left
  edge_coordinate(:left)
end
line_to(*points) click to toggle source

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. @param [Coordinate] point the absolute coordinate which the line is

drawn from current point to

@example

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])
}

@see rline_to

# File lib/dyi/shape/path.rb, line 138
def line_to(*points)
  push_command(:line_to, *points)
end
move_to(*points) click to toggle source

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. @param [Coordinate] point 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

@example

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])
}

@see rmove_to

# File lib/dyi/shape/path.rb, line 89
def move_to(*points)
  push_command(:move_to, *points)
end
path_data() click to toggle source
# File lib/dyi/shape/path.rb, line 280
def path_data
  @path_data
end
path_points() click to toggle source
# File lib/dyi/shape/path.rb, line 276
def path_points
  @path_data.path_points
end
pop_command() click to toggle source
# File lib/dyi/shape/path.rb, line 272
def pop_command
  @path_data.pop
end
push_command(command_type, *args) click to toggle source
# File lib/dyi/shape/path.rb, line 268
def push_command(command_type, *args)
  @path_data.push_command(command_type, *args)
end
quadratic_curve_to(*points) click to toggle source

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. @param [Coordinate] point0 the absolute coordinate of the control-point

of the quadratic Bézier curve

@param [Coordinate] point1 the absolute coordinate which the curve is

drawn from current point to

@example

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].
}

@see rquadratic_curve_to

# File lib/dyi/shape/path.rb, line 190
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) click to toggle source
# File lib/dyi/shape/path.rb, line 244
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) click to toggle source
# File lib/dyi/shape/path.rb, line 234
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
right() click to toggle source
# File lib/dyi/shape/path.rb, line 296
def right
  edge_coordinate(:right)
end
rline_to(*points) click to toggle source

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. @param [Coordinate] point the relavive coordinate which the line is

drawn from current point to

@example

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])
}

@see line_to

# File lib/dyi/shape/path.rb, line 161
def rline_to(*points)
  push_command(:rline_to, *points)
end
rmove_to(*points) click to toggle source

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. @param [Coordinate] point 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

@example

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])
}

@see move_to

# File lib/dyi/shape/path.rb, line 115
def rmove_to(*points)
  push_command(:rmove_to, *points)
end
rquadratic_curve_to(*points) click to toggle source

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. @param [Coordinate] point0 the relative coordinate of the control-point

of the quadratic Bézier curve

@param [Coordinate] point1 the relative coordinate which the curve is

drawn from current point to

@example

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])
}

@see rquadratic_curve_to

# File lib/dyi/shape/path.rb, line 222
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_point() click to toggle source
# File lib/dyi/shape/path.rb, line 256
def start_point
  @path_data.start_point
end
top() click to toggle source
# File lib/dyi/shape/path.rb, line 300
def top
  edge_coordinate(:top)
end
write_as(formatter, io=$>) click to toggle source

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

# File lib/dyi/shape/path.rb, line 423
def write_as(formatter, io=$>)
  formatter.write_path(self, io, &(block_given? ? Proc.new : nil))
end