Class: DYI::Shape::Path
- Inherits:
-
Base
- Object
- Element
- GraphicalElement
- Base
- DYI::Shape::Path
- 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.
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
Instance Attribute Summary
Attributes inherited from Base
#anchor_href, #anchor_target, #attributes, #clipping, #parent
Attributes inherited from GraphicalElement
Attributes inherited from Element
Class Method Summary collapse
- .draw(start_point, options = {}) {|path| ... } ⇒ Object
- .draw_and_close(start_point, options = {}, &block) ⇒ Object
Instance Method Summary collapse
- #arc_to(point, radius_x, radius_y, rotation = 0, is_large_arc = false, is_clockwise = true) ⇒ Object
- #bottom ⇒ Object
- #close? ⇒ Boolean
- #close_path ⇒ Object
- #compatible_path_data ⇒ Object
- #concise_path_data ⇒ Object
- #current_point ⇒ Object
- #current_start_point ⇒ Object
- #curve_to(*points) ⇒ Object
-
#has_marker?(point_type) ⇒ Boolean
Returns whether this shape has a marker symbol.
-
#initialize(start_point, options = {}) ⇒ Path
constructor
A new instance of Path.
- #left ⇒ Object
-
#line_to(*points) ⇒ Object
Draws straight lines from the current point to a given point, which is specified a absolute coordinate.
-
#move_to(*points) ⇒ Object
Starts a new sub-path at a given point, which is specified a absolute coordinate.
- #path_data ⇒ Object
- #path_points ⇒ Object
- #pop_command ⇒ Object
- #push_command(command_type, *args) ⇒ Object
-
#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.
- #rarc_to(point, radius_x, radius_y, rotation = 0, is_large_arc = false, is_clockwise = true) ⇒ Object
- #rcurve_to(*points) ⇒ Object
- #right ⇒ Object
-
#rline_to(*points) ⇒ Object
Draws straight lines from the current point to a given point, which is specified a relative coordinate to current point.
-
#rmove_to(*points) ⇒ Object
Starts a new sub-path at a given point, which is specified a relative coordinate to current point.
-
#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.
- #start_point ⇒ Object
- #top ⇒ Object
-
#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 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, (last_ctrl_point = current_point + path_point), (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 last_point = path_point when ‘q’ last_ctrl_point = current_point path_point last_point = current_point + path_point when ‘T’ last_ctrl_point = current_point * 2 - last_ctrl_point last_point = path_point when ‘t’ last_ctrl_point = current_point * 2 - last_ctrl_point last_point = current_point + path_point 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 last_point = path_point when ‘s’ ctrl_point1 = current_point * 2 - last_ctrl_point ctrl_point2 = current_point + path_point last_point = current_point + path_point 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 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 = last_point current_point = last_point curves.each do |c| result << [‘C’, c, c, c] end end result end end.
Methods included from Markable
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.
60 61 62 63 64 65 66 67 |
# File 'lib/dyi/shape/path.rb', line 60 def initialize(start_point, ={}) @path_data = case start_point when PathData then start_point else PathData.new(start_point) end @attributes = init_attributes() @marker = {} end |
Class Method Details
.draw(start_point, options = {}) {|path| ... } ⇒ Object
477 478 479 480 481 |
# File 'lib/dyi/shape/path.rb', line 477 def draw(start_point, ={}, &block) path = new(start_point, ) yield path path end |
.draw_and_close(start_point, options = {}, &block) ⇒ Object
483 484 485 486 487 |
# File 'lib/dyi/shape/path.rb', line 483 def draw_and_close(start_point, ={}, &block) path = draw(start_point, , &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
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 |
#bottom ⇒ Object
305 306 307 |
# File 'lib/dyi/shape/path.rb', line 305 def bottom edge_coordinate(:bottom) end |
#close? ⇒ Boolean
249 250 251 |
# File 'lib/dyi/shape/path.rb', line 249 def close? @path_data.close? end |
#close_path ⇒ Object
253 254 255 |
# File 'lib/dyi/shape/path.rb', line 253 def close_path push_command(:close_path) end |
#compatible_path_data ⇒ Object
285 286 287 |
# File 'lib/dyi/shape/path.rb', line 285 def compatible_path_data @path_data.compatible_path_data end |
#concise_path_data ⇒ Object
289 290 291 |
# File 'lib/dyi/shape/path.rb', line 289 def concise_path_data @path_data.to_concise_syntax end |
#current_point ⇒ Object
261 262 263 |
# File 'lib/dyi/shape/path.rb', line 261 def current_point @path_data.current_point end |
#current_start_point ⇒ Object
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
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.
315 316 317 |
# File 'lib/dyi/shape/path.rb', line 315 def has_marker?(point_type) !@marker[point_type].nil? end |
#left ⇒ Object
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.
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.
90 91 92 |
# File 'lib/dyi/shape/path.rb', line 90 def move_to(*points) push_command(:move_to, *points) end |
#path_data ⇒ Object
281 282 283 |
# File 'lib/dyi/shape/path.rb', line 281 def path_data @path_data end |
#path_points ⇒ Object
277 278 279 |
# File 'lib/dyi/shape/path.rb', line 277 def path_points @path_data.path_points end |
#pop_command ⇒ Object
273 274 275 |
# File 'lib/dyi/shape/path.rb', line 273 def pop_command @path_data.pop end |
#push_command(command_type, *args) ⇒ Object
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.
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
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
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 |
#right ⇒ Object
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.
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.
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.
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_point ⇒ Object
257 258 259 |
# File 'lib/dyi/shape/path.rb', line 257 def start_point @path_data.start_point end |
#top ⇒ Object
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
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 |