class HexaPDF::Content::GraphicObject::Arc

This class describes an elliptical in center parameterization arc 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.

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

Attributes

a[R]

Length of semi-major axis

b[R]

Length of semi-minor axis

clockwise[R]

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

cx[R]

x-coordinate of center point

cy[R]

y-coordinate of center point

end_angle[R]

End angle in degrees

inclination[R]

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

max_curves[RW]

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.

start_angle[R]

Start angle in degrees

Public Class Methods

configure(**kwargs) click to toggle source

Creates and configures a new elliptical arc object.

See configure for the allowed keyword arguments.

# File lib/hexapdf/content/graphic_object/arc.rb, line 55
def self.configure(**kwargs)
  new.configure(**kwargs)
end
new() click to toggle source

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

# File lib/hexapdf/content/graphic_object/arc.rb, line 92
def initialize
  @max_curves = 6
  @cx = @cy = 0
  @a = @b = 1
  @start_angle = 0
  @end_angle = 360
  @inclination = 0
  @clockwise = false
  calculate_cached_values
end

Public Instance Methods

configure(cx: nil, cy: nil, a: nil, b: nil, start_angle: nil, end_angle: nil, inclination: nil, clockwise: nil) click to toggle source

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.

# File lib/hexapdf/content/graphic_object/arc.rb, line 119
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
curves() click to toggle source

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)

# File lib/hexapdf/content/graphic_object/arc.rb, line 181
def curves
  result = []

  # Number of curves to use, maximal segment angle is 2*PI/max_curves
  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) click to toggle source

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 is set to the value of the configuration option 'graphic_object.arc.max_curves' before drawing.

# File lib/hexapdf/content/graphic_object/arc.rb, line 161
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_point() click to toggle source

Returns the end point of the elliptical arc.

# File lib/hexapdf/content/graphic_object/arc.rb, line 142
def end_point
  evaluate(@end_eta)
end
point_at(angle) click to toggle source

Returns the point at angle degrees on the ellipse.

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

# File lib/hexapdf/content/graphic_object/arc.rb, line 149
def point_at(angle)
  evaluate(angle_to_param(angle))
end
start_point() click to toggle source

Returns the start point of the elliptical arc.

# File lib/hexapdf/content/graphic_object/arc.rb, line 137
def start_point
  evaluate(@start_eta)
end

Private Instance Methods

angle_to_param(angle) click to toggle source

Converts the angle in degrees to the parameter used for the parametric function defining the ellipse.

The return value is between 0 and 2*PI.

# File lib/hexapdf/content/graphic_object/arc.rb, line 236
def angle_to_param(angle)
  angle = deg_to_rad(angle % 360)
  eta = Math.atan2(Math.sin(angle) / @b, Math.cos(angle) / @a)
  eta += 2 * Math::PI if eta < 0
  eta
end
calculate_cached_values() click to toggle source

Calculates the values that are derived from the input values and needed for the calculations

# File lib/hexapdf/content/graphic_object/arc.rb, line 215
def calculate_cached_values
  theta = deg_to_rad(@inclination)
  @cos_theta = Math.cos(theta)
  @sin_theta = Math.sin(theta)

  # (see ELL s2.2.1) Calculating start_eta and end_eta so that
  #   start_eta < end_eta   <= start_eta + 2*PI if counterclockwise
  #   end_eta   < start_eta <= end_eta + 2*PI   if clockwise
  @start_eta = angle_to_param(@start_angle)
  @end_eta = angle_to_param(@end_angle)
  if !@clockwise && @end_eta <= @start_eta
    @end_eta += 2 * Math::PI
  elsif @clockwise && @end_eta >= @start_eta
    @start_eta += 2 * Math::PI
  end
end
derivative_evaluate(eta) click to toggle source

Returns an array containing the derivative of the parametric function defining the ellipse evaluated at eta.

See: ELL s2.2.1 (4)

# File lib/hexapdf/content/graphic_object/arc.rb, line 258
def derivative_evaluate(eta)
  a_sin_eta = @a * Math.sin(eta)
  b_cos_eta = @b * Math.cos(eta)
  [- a_sin_eta * @cos_theta - b_cos_eta * @sin_theta,
   - a_sin_eta * @sin_theta + b_cos_eta * @cos_theta]
end
evaluate(eta) click to toggle source

Returns an array containing the x and y coordinates of the point on the elliptical arc specified by the parameter eta.

See: ELL s2.2.1 (3)

# File lib/hexapdf/content/graphic_object/arc.rb, line 247
def evaluate(eta)
  a_cos_eta = @a * Math.cos(eta)
  b_sin_eta = @b * Math.sin(eta)
  [@cx + a_cos_eta * @cos_theta - b_sin_eta * @sin_theta,
   @cy + a_cos_eta * @sin_theta + b_sin_eta * @cos_theta]
end