Class: Sevgi::Graphics::Paper
- Inherits:
-
Data
- Object
- Data
- Sevgi::Graphics::Paper
- Includes:
- Comparable
- Defined in:
- lib/sevgi/graphics/auxiliary/paper.rb,
lib/sevgi/graphics/auxiliary/paper.rb
Overview
Paper size and unit profile.
Instance Attribute Summary collapse
-
#height ⇒ Float
readonly
Paper height.
-
#name ⇒ Symbol
readonly
Profile name.
-
#unit ⇒ Symbol
readonly
SVG unit.
-
#width ⇒ Float
readonly
Paper width.
Class Method Summary collapse
-
.[](width, height, unit = "mm", name = :custom) ⇒ Sevgi::Graphics::Paper
Creates a paper profile from dimensions and optional metadata.
-
.default ⇒ Sevgi::Graphics::Paper
Returns the default paper profile.
-
.define(name, overwrite: false, **spec) ⇒ Sevgi::Graphics::Paper
Defines a named paper profile after complete validation.
-
.exist?(name) ⇒ Boolean
Reports whether a normalizable named paper profile exists.
-
.fetch(name) ⇒ Sevgi::Graphics::Paper
Returns a registered paper profile by name.
-
.keys ⇒ Array<Symbol>
Returns registered profile names.
Instance Method Summary collapse
-
#<=>(other) ⇒ Integer?
Compares papers by width, height, unit, then name.
-
#eql?(other) ⇒ Boolean
(also: #==)
Reports strict paper equality.
-
#hash ⇒ Integer
Returns a hash compatible with strict equality.
-
#initialize(width:, height:, unit: "mm", name: :custom, **options) ⇒ void
constructor
Creates a paper profile.
-
#longest ⇒ Float
Returns the longer side.
-
#shortest ⇒ Float
Returns the shorter side.
Constructor Details
#initialize(width:, height:, unit: "mm", name: :custom, **options) ⇒ void
Creates a paper profile. Dimensions must be finite real numbers greater than zero.
39 40 41 42 43 44 45 46 47 |
# File 'lib/sevgi/graphics/auxiliary/paper.rb', line 39 def initialize(width:, height:, unit: "mm", name: :custom, **) self.class.send(:options!, ) super( width: self.class.send(:dimension!, :width, width), height: self.class.send(:dimension!, :height, height), unit: self.class.send(:normalize!, :unit, unit), name: self.class.send(:normalize!, :name, name) ) end |
Instance Attribute Details
#height ⇒ Float (readonly)
Returns paper height.
19 20 21 |
# File 'lib/sevgi/graphics/auxiliary/paper.rb', line 19 def height @height end |
#name ⇒ Symbol (readonly)
Returns profile name.
19 20 21 |
# File 'lib/sevgi/graphics/auxiliary/paper.rb', line 19 def name @name end |
#unit ⇒ Symbol (readonly)
Returns SVG unit.
19 20 21 |
# File 'lib/sevgi/graphics/auxiliary/paper.rb', line 19 def unit @unit end |
#width ⇒ Float (readonly)
Returns paper width.
19 20 21 |
# File 'lib/sevgi/graphics/auxiliary/paper.rb', line 19 def width @width end |
Class Method Details
.[](width, height, unit = "mm", name = :custom) ⇒ Sevgi::Graphics::Paper
Creates a paper profile from dimensions and optional metadata.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/sevgi/graphics/auxiliary/paper.rb', line 19 Paper = Data.define(:width, :height, :unit, :name) do include Comparable # @!attribute [r] width # @return [Float] paper width # @!attribute [r] height # @return [Float] paper height # @!attribute [r] unit # @return [Symbol] SVG unit # @!attribute [r] name # @return [Symbol] profile name # Creates a paper profile. Dimensions must be finite real numbers greater than zero. # @param width [Numeric] paper width # @param height [Numeric] paper height # @param unit [Symbol, String] SVG unit # @param name [Symbol, String] profile name # @param options [Hash] unsupported extra options # @return [void] # @raise [Sevgi::ArgumentError] when dimensions, unit, name, or options are invalid def initialize(width:, height:, unit: "mm", name: :custom, **) self.class.send(:options!, ) super( width: self.class.send(:dimension!, :width, width), height: self.class.send(:dimension!, :height, height), unit: self.class.send(:normalize!, :unit, unit), name: self.class.send(:normalize!, :name, name) ) end # Compares papers by width, height, unit, then name. # @param other [Sevgi::Graphics::Paper] paper to compare # @return [Integer, nil] comparison result, or nil for a non-Paper operand def <=>(other) deconstruct <=> other.deconstruct if other.is_a?(self.class) end # Reports strict paper equality. # @param other [Object] object to compare # @return [Boolean] def eql?(other) = self.class == other.class && deconstruct == other.deconstruct # Returns a hash compatible with strict equality. # @return [Integer] def hash = [self.class, *deconstruct].hash # Returns the longer side. # @return [Float] def longest = [width, height].max # Returns the shorter side. # @return [Float] def shortest = [width, height].min alias_method :==, :eql? @profiles = {} @accessors = {} @mutex = ::Mutex.new # Reports whether a normalizable named paper profile exists. Invalid converters return false. # @param name [Object] profile name # @return [Boolean] def self.exist?(name) name = normalize(name) name ? @mutex.synchronize { @profiles.key?(name) } : false end # Returns a registered paper profile by name. # @param name [Symbol, String] profile name, including names that are not Ruby identifiers # @return [Sevgi::Graphics::Paper] registered profile # @raise [Sevgi::ArgumentError] when name is invalid or no profile is registered # @example Look up a non-identifier profile name # Sevgi::Graphics::Paper.define("business-card", width: 90, height: 50) # Sevgi::Graphics::Paper.fetch("business-card") def self.fetch(name) name = normalize!(:name, name) @mutex.synchronize { @profiles.fetch(name) { ArgumentError.("Unknown paper profile: #{name}") } } end # Returns registered profile names. # @return [Array<Symbol>] frozen name snapshot def self.keys = @mutex.synchronize { @profiles.keys.freeze } # Defines a named paper profile after complete validation. Registration is process-global and thread-atomic. # Identical definitions return the canonical profile and conflicting definitions raise unless replacement is # explicitly requested. Names that are not Ruby call syntax remain accessible through {.fetch}. # @param name [Symbol, String] profile name # @param overwrite [Boolean] true to replace an existing profile # @param spec [Hash] paper dimensions and unit # @option spec [Numeric] :width paper width # @option spec [Numeric] :height paper height # @option spec [Symbol, String] :unit SVG unit # @return [Sevgi::Graphics::Paper] # @raise [Sevgi::ArgumentError] when the name, dimensions, unit, overwrite flag, or options are reserved or invalid, # or a non-bang definition conflicts with the registered profile # @example Define or reuse a matching profile # Sevgi::Graphics::Paper.define(:card, width: 90, height: 50) # @example Replace a profile explicitly # Sevgi::Graphics::Paper.define(:card, width: 100, height: 60, overwrite: true) def self.define(name, overwrite: false, **spec) name = normalize!(:name, name) ArgumentError.("Paper name is reserved: #{name}") if reserved?(name) overwrite = overwrite!(overwrite) profile = new(name:, **spec) register(name, profile, overwrite:) end class << self private def install(name) return if @accessors.key?(name) define_singleton_method(name) { @mutex.synchronize { @profiles.fetch(name) } } @accessors[name] = true end def register(name, profile, overwrite:) @mutex.synchronize do if !overwrite && (current = @profiles[name]) ArgumentError.("Paper already defined differently: #{name}") unless current == profile next current end install(name) @profiles[name] = profile end end def reserved?(name) = @reserved.include?(name) def dimension!(field, value) Scalar.finite(value, context: "paper", field:, positive: true) end def () return if .empty? ArgumentError.("Unknown paper options: #{.keys.join(", ")}") end def overwrite!(value) return value if [true, false].include?(value) ArgumentError.("Paper overwrite must be true or false") end def normalize(value) normalized = value.to_sym if value.respond_to?(:to_sym) normalized if normalized.is_a?(::Symbol) rescue ::StandardError nil end def normalize!(field, value) normalize(value) || ArgumentError.("Invalid paper #{field}") end end @reserved = methods.map(&:to_sym).freeze PAPER_SIZES.each { |name, (width, height, unit)| define(name, width:, height:, unit:) } # Returns the default paper profile. # @return [Sevgi::Graphics::Paper] def self.default @mutex.synchronize { @profiles.fetch(:default) } end @accessors[:default] = true @profiles[:default] = @profiles.fetch(:a4) end |
.default ⇒ Sevgi::Graphics::Paper
Returns the default paper profile.
187 188 189 |
# File 'lib/sevgi/graphics/auxiliary/paper.rb', line 187 def self.default @mutex.synchronize { @profiles.fetch(:default) } end |
.define(name, overwrite: false, **spec) ⇒ Sevgi::Graphics::Paper
Defines a named paper profile after complete validation. Registration is process-global and thread-atomic. Identical definitions return the canonical profile and conflicting definitions raise unless replacement is explicitly requested. Names that are not Ruby call syntax remain accessible through fetch.
119 120 121 122 123 124 125 126 |
# File 'lib/sevgi/graphics/auxiliary/paper.rb', line 119 def self.define(name, overwrite: false, **spec) name = normalize!(:name, name) ArgumentError.("Paper name is reserved: #{name}") if reserved?(name) overwrite = overwrite!(overwrite) profile = new(name:, **spec) register(name, profile, overwrite:) end |
.exist?(name) ⇒ Boolean
Reports whether a normalizable named paper profile exists. Invalid converters return false.
82 83 84 85 |
# File 'lib/sevgi/graphics/auxiliary/paper.rb', line 82 def self.exist?(name) name = normalize(name) name ? @mutex.synchronize { @profiles.key?(name) } : false end |
.fetch(name) ⇒ Sevgi::Graphics::Paper
Returns a registered paper profile by name.
94 95 96 97 |
# File 'lib/sevgi/graphics/auxiliary/paper.rb', line 94 def self.fetch(name) name = normalize!(:name, name) @mutex.synchronize { @profiles.fetch(name) { ArgumentError.("Unknown paper profile: #{name}") } } end |
.keys ⇒ Array<Symbol>
Returns registered profile names.
101 |
# File 'lib/sevgi/graphics/auxiliary/paper.rb', line 101 def self.keys = @mutex.synchronize { @profiles.keys.freeze } |
Instance Method Details
#<=>(other) ⇒ Integer?
Compares papers by width, height, unit, then name.
52 53 54 |
# File 'lib/sevgi/graphics/auxiliary/paper.rb', line 52 def <=>(other) deconstruct <=> other.deconstruct if other.is_a?(self.class) end |
#eql?(other) ⇒ Boolean Also known as: ==
Reports strict paper equality.
59 |
# File 'lib/sevgi/graphics/auxiliary/paper.rb', line 59 def eql?(other) = self.class == other.class && deconstruct == other.deconstruct |
#hash ⇒ Integer
Returns a hash compatible with strict equality.
63 |
# File 'lib/sevgi/graphics/auxiliary/paper.rb', line 63 def hash = [self.class, *deconstruct].hash |
#longest ⇒ Float
Returns the longer side.
67 |
# File 'lib/sevgi/graphics/auxiliary/paper.rb', line 67 def longest = [width, height].max |
#shortest ⇒ Float
Returns the shorter side.
71 |
# File 'lib/sevgi/graphics/auxiliary/paper.rb', line 71 def shortest = [width, height].min |