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:
-
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
Public Class Methods
# File lib/dyi/shape/path.rb, line 476 def draw(start_point, options={}, &block) path = new(start_point, options) yield path path end
# 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
# 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
# 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
# File lib/dyi/shape/path.rb, line 304 def bottom edge_coordinate(:bottom) end
# File lib/dyi/shape/path.rb, line 248 def close? @path_data.close? end
# File lib/dyi/shape/path.rb, line 252 def close_path push_command(:close_path) end
# File lib/dyi/shape/path.rb, line 284 def compatible_path_data @path_data.compatible_path_data end
# File lib/dyi/shape/path.rb, line 288 def concise_path_data @path_data.to_concise_syntax end
# File lib/dyi/shape/path.rb, line 260 def current_point @path_data.current_point end
# File lib/dyi/shape/path.rb, line 264 def current_start_point @path_data.current_start_point end
# 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
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
# File lib/dyi/shape/path.rb, line 292 def left edge_coordinate(:left) end
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
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
# File lib/dyi/shape/path.rb, line 280 def path_data @path_data end
# File lib/dyi/shape/path.rb, line 276 def path_points @path_data.path_points end
# File lib/dyi/shape/path.rb, line 272 def pop_command @path_data.pop end
# File lib/dyi/shape/path.rb, line 268 def push_command(command_type, *args) @path_data.push_command(command_type, *args) end
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
# 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
# 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
# File lib/dyi/shape/path.rb, line 296 def right edge_coordinate(:right) end
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
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
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
# File lib/dyi/shape/path.rb, line 256 def start_point @path_data.start_point end
# File lib/dyi/shape/path.rb, line 300 def top edge_coordinate(:top) end
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