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.

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


177
178
179
180
181
182
183
184
185
186
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 177

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

Examples:

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


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

def a
  @a
end

#bObject (readonly)

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

Examples:

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


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

def b
  @b
end

#clockwiseObject (readonly)

Direction of arc - if true in clockwise direction, else in counterclockwise direction

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.draw(arc, cx: -50).draw(arc, cx: 50).
  draw(arc, cx: -50, b: 80).
  draw(arc, cx: 50, b: 80, clockwise: true).
  fill(:nonzero)


168
169
170
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 168

def clockwise
  @clockwise
end

#cxObject (readonly)

x-coordinate of center point

Examples:

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


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

def cx
  @cx
end

#cyObject (readonly)

y-coordinate of center point

Examples:

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


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

def cy
  @cy
end

#end_angleObject (readonly)

End angle of the arc in degrees

Examples:

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


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

def end_angle
  @end_angle
end

#inclinationObject (readonly)

Inclination in degrees of the semi-major axis with respect to the x-axis

Examples:

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


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

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)
arc.max_curves = 2
canvas.draw(arc)
arc.max_curves = 10
canvas.draw(arc, cx: 50)
canvas.stroke


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

def max_curves
  @max_curves
end

#start_angleObject (readonly)

Start angle of the arc in degrees

Examples:

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


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

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.



65
66
67
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 65

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) ⇒ 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.

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

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

Returns self.



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 204

def configure(cx: nil, cy: nil, a: nil, b: nil, start_angle: nil, end_angle: nil,
              inclination: nil, clockwise: 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?
  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)



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 280

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

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



260
261
262
263
264
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 260

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("red").circle(*arc.end_point, 2).fill


241
242
243
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 241

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!



248
249
250
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 248

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("red").circle(*arc.start_point, 2).fill


229
230
231
# File 'lib/hexapdf/content/graphic_object/arc.rb', line 229

def start_point
  evaluate(@start_eta)
end