Class: HexaPDF::Content::GraphicObject::Arc

Inherits:
Object
  • Object
show all
Includes:
Utils::MathHelpers
Defined in:
lib/hexapdf/content/graphic_object/arc.rb

Overview

This class describes an elliptical arc in center parameterization that is approximated using Bezier curves. It can be used to draw circles, circular arcs, ellipses and elliptical arcs, all either in clockwise or counterclockwise direction and optionally inclined in respect to the x-axis.

Note that only the path of the arc itself is added to the canvas. So depending on the use-case the path itself still has to be, for example, stroked.

This graphic object is registered under the :arc key for use with the HexaPDF::Content::Canvas class.

Examples:

#>pdf-center
arc = canvas.graphic_object(:arc, a: 100, b: 50, end_angle: 150)
canvas.draw(arc).stroke

See: ELL - spaceroots.org/documents/ellipse/elliptical-arc.pdf

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils::MathHelpers

deg_to_rad, rad_to_deg

Constructor Details

#initializeArc

Creates an elliptical arc with default values (a counterclockwise unit circle at the origin).

Examples:

#>pdf-center
canvas.draw(:arc).stroke


180
181
182
183
184
185
186
187
188
189
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 180

def initialize
  @max_curves = nil
  @cx = @cy = 0
  @a = @b = 1
  @start_angle = 0
  @end_angle = 360
  @inclination = 0
  @clockwise = false
  calculate_cached_values
end

Instance Attribute Details

#aObject (readonly)

Length of semi-major axis which (without altering the #inclination) is parallel to the x-axis, defaults to 1.

Examples:

#>pdf-center
arc = canvas.graphic_object(:arc, a: 30, b: 30)
canvas.draw(arc).stroke
canvas.stroke_color("hp-blue").draw(arc, a: 60).stroke


116
117
118
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 116

def a
  @a
end

#bObject (readonly)

Length of semi-minor axis which (without altering the #inclination) is parallel to the y-axis, defaults to 1.

Examples:

#>pdf-center
arc = canvas.graphic_object(:arc, a: 30, b: 30)
canvas.draw(arc).stroke
canvas.stroke_color("hp-blue").draw(arc, b: 60).stroke


127
128
129
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 127

def b
  @b
end

#clockwiseObject (readonly)

Direction of arc - if true in clockwise direction, else in counterclockwise direction (the default).

This is needed when filling paths using the nonzero winding number rule to achieve different effects.

Examples:

#>pdf-center
arc = canvas.graphic_object(:arc, a: 40, b: 40)
canvas.fill_color("hp-blue").
  draw(arc, cx: -50).draw(arc, cx: 50).
  draw(arc, cx: -50, b: 80).
  draw(arc, cx: 50, b: 80, clockwise: true).
  fill(:nonzero)


171
172
173
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 171

def clockwise
  @clockwise
end

#cxObject (readonly)

x-coordinate of center point, defaults to 0.

Examples:

#>pdf-center
arc = canvas.graphic_object(:arc, a: 30, b: 20)
canvas.draw(arc).stroke
canvas.stroke_color("hp-blue").draw(arc, cx: 50).stroke


95
96
97
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 95

def cx
  @cx
end

#cyObject (readonly)

y-coordinate of center point, defaults to 0.

Examples:

#>pdf-center
arc = canvas.graphic_object(:arc, a: 30, b: 20)
canvas.draw(arc).stroke
canvas.stroke_color("hp-blue").draw(arc, cy: 50).stroke


105
106
107
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 105

def cy
  @cy
end

#end_angleObject (readonly)

End angle of the arc in degrees, defaults to 0.

Examples:

#>pdf-center
arc = canvas.graphic_object(:arc, a: 30, b: 30)
canvas.draw(arc, end_angle: 160).stroke


145
146
147
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 145

def end_angle
  @end_angle
end

#inclinationObject (readonly)

Inclination in degrees of the semi-major axis with respect to the x-axis, defaults to 0.

Examples:

#>pdf-center
arc = canvas.graphic_object(:arc, a: 60, b: 30)
canvas.draw(arc, inclination: 45).stroke


154
155
156
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 154

def inclination
  @inclination
end

#max_curvesObject

The maximal number of curves used for approximating a complete ellipse.

The higher the value the better the approximation will be but it will also take longer to compute. The value should not be lower than 4. Default value is 6 which already provides a good approximation.

Examples:

#>pdf-center
arc = canvas.graphic_object(:arc, cx: -50, a: 40, b: 40, max_curves: 2)
canvas.draw(arc)
canvas.draw(arc, cx: 50, max_curves: 10)
canvas.stroke


85
86
87
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 85

def max_curves
  @max_curves
end

#start_angleObject (readonly)

Start angle of the arc in degrees, defaults to 0.

Examples:

#>pdf-center
arc = canvas.graphic_object(:arc, a: 30, b: 30)
canvas.draw(arc, start_angle: 110).stroke


136
137
138
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 136

def start_angle
  @start_angle
end

Class Method Details

.configure(**kwargs) ⇒ Object

Creates and configures a new elliptical arc object.

See #configure for the allowed keyword arguments.



68
69
70
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 68

def self.configure(**kwargs)
  new.configure(**kwargs)
end

Instance Method Details

#configure(cx: nil, cy: nil, a: nil, b: nil, start_angle: nil, end_angle: nil, inclination: nil, clockwise: nil, max_curves: nil) ⇒ Object

Configures the arc with

  • center point (cx, cy),

  • semi-major axis a,

  • semi-minor axis b,

  • start angle of start_angle degrees,

  • end angle of end_angle degrees and

  • an inclination in respect to the x-axis of inclination degrees,

  • as well as the maximal number of curves max_curves used for approximation.

The clockwise argument determines if the arc is drawn in the counterclockwise direction (false) or in the clockwise direction (true).

For the meaning of max_curves see the description of #max_curves.

Any arguments not specified are not modified and retain their old value, see #initialize for the inital values.

Returns self.

Examples:

#>pdf-center
arc = canvas.graphic_object(:arc)
arc.configure(cx: 50, a: 40, b: 20, inclination: 10)
canvas.draw(arc).stroke


217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 217

def configure(cx: nil, cy: nil, a: nil, b: nil, start_angle: nil, end_angle: nil,
              inclination: nil, clockwise: nil, max_curves: nil)
  @cx = cx if cx
  @cy = cy if cy
  @a = a.abs if a
  @b = b.abs if b
  if @a == 0 || @b == 0
    raise HexaPDF::Error, "Semi-major and semi-minor axes must be greater than zero"
  end
  @start_angle = start_angle if start_angle
  @end_angle = end_angle if end_angle
  @inclination = inclination if inclination
  @clockwise = clockwise unless clockwise.nil?
  @max_curves = max_curves if max_curves
  calculate_cached_values
  self
end

#curvesObject

Returns an array of arrays that contain the points for the Bezier curves which are used for approximating the elliptical arc between #start_point and #end_point.

One subarray consists of

[end_point_x, end_point_y, p1: control_point_1, p2: control_point_2]

The first start point is the one returned by #start_point, the other start points are the end points of the curve before.

The format of the subarray is chosen so that it can be fed to the Canvas#curve_to method by using array splatting.

See: ELL s3.4.1 (especially the last box on page 18)



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 313

def curves
  result = []

  # Number of curves to use, maximal segment angle is 2*PI/max_curves
  max_curves = @max_curves || 6
  n = [max_curves, ((@end_eta - @start_eta).abs / (2 * Math::PI / max_curves)).ceil].min
  d_eta = (@end_eta - @start_eta) / n

  alpha = Math.sin(d_eta) * (Math.sqrt(4 + 3 * Math.tan(d_eta / 2)**2) - 1) / 3

  eta2 = @start_eta
  p2x, p2y = evaluate(eta2)
  p2x_prime, p2y_prime = derivative_evaluate(eta2)
  1.upto(n) do
    p1x = p2x
    p1y = p2y
    p1x_prime = p2x_prime
    p1y_prime = p2y_prime

    eta2 += d_eta
    p2x, p2y = evaluate(eta2)
    p2x_prime, p2y_prime = derivative_evaluate(eta2)

    result << [p2x, p2y,
               {p1: [p1x + alpha * p1x_prime, p1y + alpha * p1y_prime],
                p2: [p2x - alpha * p2x_prime, p2y - alpha * p2y_prime]}]
  end

  result
end

#draw(canvas, move_to_start: true) ⇒ Object

Draws the arc on the given Canvas.

If the argument move_to_start is true, a Canvas#move_to operation is executed to move the current point to the start point of the arc. Otherwise it is assumed that the current point already coincides with the start point. This functionality is used, for example, by the SolidArc implementation.

The #max_curves value, if not already changed, is set to the value of the configuration option ‘graphic_object.arc.max_curves’ before drawing.

Examples:

#>pdf-center
arc = canvas.graphic_object(:arc, a: 40, b: 30)
canvas.stroke_color("hp-blue").move_to(-50, 0)
arc.draw(canvas, move_to_start: false)
canvas.stroke


293
294
295
296
297
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 293

def draw(canvas, move_to_start: true)
  @max_curves ||= canvas.context.document.config['graphic_object.arc.max_curves']
  canvas.move_to(*start_point) if move_to_start
  curves.each {|x, y, hash| canvas.curve_to(x, y, **hash) }
end

#end_pointObject

Returns the end point of the elliptical arc.

Examples:

#>pdf-center
arc = canvas.graphic_object(:arc, a: 40, b: 30, end_angle: 245)
canvas.draw(arc).stroke
canvas.fill_color("hp-blue").circle(*arc.end_point, 2).fill


255
256
257
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 255

def end_point
  evaluate(@end_eta)
end

#point_at(angle) ⇒ Object

Returns the point at angle degrees on the ellipse.

Note that the point may not lie on the arc itself!

Examples:

#>pdf-center
arc = canvas.graphic_object(:arc, a: 40, b: 30, end_angle: 245)
canvas.draw(arc).stroke
canvas.fill_color("hp-blue").
  circle(*arc.point_at(150), 2).
  circle(*arc.point_at(290), 2).
  fill


272
273
274
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 272

def point_at(angle)
  evaluate(angle_to_param(angle))
end

#start_pointObject

Returns the start point of the elliptical arc.

Examples:

#>pdf-center
arc = canvas.graphic_object(:arc, a: 40, b: 30, start_angle: 60)
canvas.draw(arc).stroke
canvas.fill_color("hp-blue").circle(*arc.start_point, 2).fill


243
244
245
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 243

def start_point
  evaluate(@start_eta)
end