Class: Sevgi::Graphics::Canvas

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/sevgi/graphics/auxiliary/canvas.rb

Overview

Immutable SVG canvas size, margins, viewport, and viewBox.

size is the outer paper. inner subtracts the margins but remains a size value, not a positioned rectangle. By default the viewBox origin is the negative left/top margin, so drawing coordinates (0, 0) begin at the inner area's top-left while the SVG viewport still includes the margins. Pass nil to #viewbox or #attributes for a literal zero origin.

Examples:

Build from a paper profile or an explicit size

page = Sevgi::Graphics::Canvas.from_paper(:a4, margins: [10])
icon = Sevgi::Graphics::Canvas.call(width: 24, height: 24, unit: :px)
page.viewbox       # uses the negative left and top margins as its origin
icon.viewbox(nil)  # uses `0 0` as its origin

See Also:

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(width:, height:, unit: "mm", name: :custom, margins: []) ⇒ void

Returns a new instance of Canvas.

Creates a canvas with finite real dimensions greater than zero and margins that leave a positive inner area.

Parameters:

  • width (Numeric)

    canvas width

  • height (Numeric)

    canvas height

  • unit (Symbol, String) (defaults to: "mm")

    SVG unit

  • name (Symbol, String) (defaults to: :custom)

    paper name

  • margins (Array<Numeric>) (defaults to: [])

    margin shorthand values

Raises:

  • (Sevgi::ArgumentError)

    when a required field is omitted, an option is unknown, or a value is invalid



100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/sevgi/graphics/auxiliary/canvas.rb', line 100

def initialize(**fields)
  unknown = fields.keys - FIELDS
  ArgumentError.("Unknown canvas option: #{unknown.first}") unless unknown.empty?
  ArgumentError.("Canvas width and height are required") unless REQUIRED.all? { fields.key?(it) }

  fields = DEFAULTS.merge(fields)

  @size = Paper[*fields.values_at(:width, :height, :unit, :name)]
  @margin = Margin[*fields[:margins]]

  compute
  freeze
end

Instance Attribute Details

#innerSevgi::Graphics::Paper (readonly)

Returns the inner paper after margins.



89
90
91
# File 'lib/sevgi/graphics/auxiliary/canvas.rb', line 89

def inner
  @inner
end

#marginSevgi::Graphics::Margin (readonly)

Returns the canvas margins.



85
86
87
# File 'lib/sevgi/graphics/auxiliary/canvas.rb', line 85

def margin
  @margin
end

#sizeSevgi::Graphics::Paper (readonly)

Returns the paper size object.



81
82
83
# File 'lib/sevgi/graphics/auxiliary/canvas.rb', line 81

def size
  @size
end

Class Method Details

.call(paper, **overrides) ⇒ Sevgi::Graphics::Canvas .call(width:, height:, unit: "mm", name: :custom, margins: []) ⇒ Sevgi::Graphics::Canvas

Overloads:

  • .call(paper, **overrides) ⇒ Sevgi::Graphics::Canvas

    Builds a canvas from a paper profile with optional field overrides.

    Parameters:

    • paper (Sevgi::Graphics::Paper, Symbol, String)

      paper object or registered profile

    • overrides (Hash)

      canvas field overrides

    Returns:

    Raises:

    • (Sevgi::ArgumentError)

      when the paper or an override is invalid

  • .call(width:, height:, unit: "mm", name: :custom, margins: []) ⇒ Sevgi::Graphics::Canvas

    Builds a canvas from an explicit size.

    Parameters:

    • width (Numeric)

      canvas width

    • height (Numeric)

      canvas height

    • unit (Symbol, String) (defaults to: "mm")

      SVG unit

    • name (Symbol, String) (defaults to: :custom)

      paper name

    • margins (Array<Numeric>) (defaults to: [])

      margin shorthand values

    Returns:

    Raises:

    • (Sevgi::ArgumentError)

      when a required field is omitted or a value is invalid



43
44
45
# File 'lib/sevgi/graphics/auxiliary/canvas.rb', line 43

def self.call(paper = Undefined, **fields)
  paper.equal?(Undefined) ? new(**fields) : from_paper(paper, **fields)
end

.from_paper(paper, **overrides) ⇒ Sevgi::Graphics::Canvas

Examples:

Override a registered paper profile

Sevgi::Graphics::Canvas.from_paper(:a4, margins: [10])

Builds a canvas from a paper profile with optional field overrides.

Parameters:

  • paper (Sevgi::Graphics::Paper, Symbol, String)

    paper object or registered profile

  • overrides (Hash)

    canvas field overrides

Returns:

Raises:

  • (Sevgi::ArgumentError)

    when the paper is omitted or invalid, or an override is invalid



55
56
57
58
59
# File 'lib/sevgi/graphics/auxiliary/canvas.rb', line 55

def self.from_paper(paper = Undefined, **overrides)
  ArgumentError.("Paper is required") if paper.equal?(Undefined)

  new(**profile(paper).to_h, **overrides)
end

Instance Method Details

#attributes(origin = Undefined) ⇒ Hash{Symbol => String}

Returns SVG root viewport attributes. Omission uses the negative left and top margins; nil uses zero for both coordinates.

Examples:

Compare margin-aware and zero-origin viewBoxes

canvas = Sevgi::Graphics::Canvas.call(width: 80, height: 50, margins: [5, 10])
canvas.attributes[:viewBox] # => "-10 -5 80 50"
canvas.attributes(nil)[:viewBox] # => "0 0 80 50"

Parameters:

  • origin (Numeric, Array<Numeric>, nil, Sevgi::Undefined) (defaults to: Undefined)

    viewBox origin; a scalar sets both coordinates

Returns:

  • (Hash{Symbol => String})

    SVG viewport and viewBox attributes

Raises:

  • (Sevgi::ArgumentError)

    when origin is invalid



124
# File 'lib/sevgi/graphics/auxiliary/canvas.rb', line 124

def attributes(...) = {**viewport, viewBox: viewbox(...)}

#eql?(other) ⇒ Boolean Also known as: ==

Reports structural canvas equality by size and margins.

Examples:

Compare equivalent canvas values

Sevgi::Graphics::Canvas.from_paper(:a4, margins: [10]) ==
  Sevgi::Graphics::Canvas.from_paper("a4", margins: [10]) # => true

Parameters:

  • other (Object)

    object to compare

Returns:

  • (Boolean)


132
# File 'lib/sevgi/graphics/auxiliary/canvas.rb', line 132

def eql?(other) = self.class == other.class && size.eql?(other.size) && margin.eql?(other.margin)

#hashInteger

Returns a hash compatible with structural equality.

Returns:

  • (Integer)


136
# File 'lib/sevgi/graphics/auxiliary/canvas.rb', line 136

def hash = [self.class, size, margin].hash

#viewbox(origin = Undefined) ⇒ String

Returns the SVG viewBox string. Omission uses the negative left and top margins; nil uses zero for both coordinates.

Parameters:

  • origin (Numeric, Array<Numeric>, nil, Sevgi::Undefined) (defaults to: Undefined)

    viewBox origin; a scalar sets both coordinates

Returns:

  • (String)

Raises:

  • (Sevgi::ArgumentError)

    when origin is invalid



149
# File 'lib/sevgi/graphics/auxiliary/canvas.rb', line 149

def viewbox(origin = Undefined) = prettify(*originate(origin), width, height).join(" ")

#viewportHash{Symbol => String}

Returns SVG width and height attributes.

Returns:

  • (Hash{Symbol => String})

    SVG width and height attributes



142
# File 'lib/sevgi/graphics/auxiliary/canvas.rb', line 142

def viewport = {width: "#{width}#{unit}", height: "#{height}#{unit}"}

#with(**kwargs) ⇒ Sevgi::Graphics::Canvas

Returns a canvas with selected fields replaced. Unspecified fields, including margins, retain their current values.

Examples:

Derive a canvas while preserving the source

source = Sevgi::Graphics::Canvas.call(width: 80, height: 50, margins: [5])
derived = source.with(width: 100, margins: [10, 5])
source.width # => 80.0
derived.inner.deconstruct # => [90.0, 30.0, :mm, :custom]

Parameters:

  • kwargs (Hash)

    replacement options

Options Hash (**kwargs):

  • :width (Numeric)

    replacement canvas width

  • :height (Numeric)

    replacement canvas height

  • :unit (Symbol, String)

    replacement SVG unit

  • :name (Symbol, String)

    replacement paper name

  • :margins (Array<Numeric>)

    replacement margin shorthand values

Returns:

Raises:

  • (Sevgi::ArgumentError)

    when an unknown option is supplied

  • (Sevgi::ArgumentError)

    when a replacement value is invalid



167
168
169
170
171
172
173
174
175
176
# File 'lib/sevgi/graphics/auxiliary/canvas.rb', line 167

def with(**kwargs)
  unknown = kwargs.keys - FIELDS

  ArgumentError.("Unknown canvas option: #{unknown.first}") unless unknown.empty?

  margins = kwargs.fetch(:margins, margin.to_a)
  replacements = kwargs.dup.tap { it.delete(:margins) }

  self.class.new(**size.to_h, **replacements, margins:)
end