sevgi-graphics 0.95.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +221 -2
- data/README.md +12 -9
- data/lib/sevgi/graphics/attribute.rb +166 -45
- data/lib/sevgi/graphics/auxiliary/canvas.rb +100 -43
- data/lib/sevgi/graphics/auxiliary/content.rb +56 -47
- data/lib/sevgi/graphics/auxiliary/margin.rb +19 -12
- data/lib/sevgi/graphics/auxiliary/paper.rb +74 -49
- data/lib/sevgi/graphics/auxiliary/path.rb +44 -0
- data/lib/sevgi/graphics/auxiliary/scalar.rb +36 -7
- data/lib/sevgi/graphics/auxiliary.rb +1 -0
- data/lib/sevgi/graphics/document/base.rb +6 -2
- data/lib/sevgi/graphics/document/default.rb +1 -1
- data/lib/sevgi/graphics/document.rb +239 -117
- data/lib/sevgi/graphics/element.rb +132 -34
- data/lib/sevgi/graphics/mixtures/call.rb +234 -88
- data/lib/sevgi/graphics/mixtures/core.rb +67 -27
- data/lib/sevgi/graphics/mixtures/duplicate.rb +47 -25
- data/lib/sevgi/graphics/mixtures/export.rb +54 -12
- data/lib/sevgi/graphics/mixtures/hatch.rb +49 -7
- data/lib/sevgi/graphics/mixtures/identify.rb +30 -17
- data/lib/sevgi/graphics/mixtures/include.rb +26 -8
- data/lib/sevgi/graphics/mixtures/inkscape.rb +214 -47
- data/lib/sevgi/graphics/mixtures/rdf.rb +59 -7
- data/lib/sevgi/graphics/mixtures/render.rb +60 -120
- data/lib/sevgi/graphics/mixtures/save.rb +79 -35
- data/lib/sevgi/graphics/mixtures/symbols.rb +81 -12
- data/lib/sevgi/graphics/mixtures/tile.rb +85 -67
- data/lib/sevgi/graphics/mixtures/transform.rb +89 -30
- data/lib/sevgi/graphics/mixtures/underscore.rb +16 -7
- data/lib/sevgi/graphics/mixtures/validate.rb +2 -2
- data/lib/sevgi/graphics/mixtures/wrappers.rb +111 -23
- data/lib/sevgi/graphics/mixtures.rb +15 -13
- data/lib/sevgi/graphics/version.rb +1 -1
- data/lib/sevgi/graphics/xml.rb +4 -9
- data/lib/sevgi/graphics.rb +69 -18
- metadata +7 -6
|
@@ -5,22 +5,84 @@ module Sevgi
|
|
|
5
5
|
module Mixtures
|
|
6
6
|
# DSL wrappers for common SVG shapes and content patterns.
|
|
7
7
|
module Wrappers
|
|
8
|
+
# rubocop:disable Metrics/ParameterLists
|
|
9
|
+
|
|
10
|
+
# Builds an elliptical arc path ending at an absolute point.
|
|
11
|
+
# SVG resolves the ellipse from its endpoints, radii, and flags, including radius correction and zero radii.
|
|
12
|
+
# @example Draw an upper semicircle in screen coordinates
|
|
13
|
+
# Sevgi::Graphics.SVG { ArcTo x1: 0, y1: 10, x2: 20, y2: 10, rx: 10, ry: 10, sweep: true }
|
|
14
|
+
# @param x2 [Numeric] finite ending x coordinate
|
|
15
|
+
# @param y2 [Numeric] finite ending y coordinate
|
|
16
|
+
# @param rx [Numeric] finite local x radius
|
|
17
|
+
# @param ry [Numeric] finite local y radius
|
|
18
|
+
# @param x1 [Numeric] finite starting x coordinate
|
|
19
|
+
# @param y1 [Numeric] finite starting y coordinate
|
|
20
|
+
# @param rotation [Numeric] clockwise ellipse rotation in degrees
|
|
21
|
+
# @param large [Boolean] select the arc exceeding a half turn
|
|
22
|
+
# @param sweep [Boolean] select increasing parameter angles
|
|
23
|
+
# @return [Sevgi::Graphics::Element] path element
|
|
24
|
+
# @raise [Sevgi::ArgumentError] when an operand is not finite real or a flag is not Boolean
|
|
25
|
+
def ArcTo(x2:, y2:, rx:, ry:, x1: 0, y1: 0, rotation: 0, large: false, sweep: false, **)
|
|
26
|
+
x1, y1, x2, y2, rx, ry, rotation = Scalar.numbers([x1, y1, x2, y2, rx, ry, rotation], context: "absolute arc")
|
|
27
|
+
unless [large, sweep].all? { it.equal?(true) || it.equal?(false) }
|
|
28
|
+
ArgumentError.("Arc flags must be Boolean")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
path(d: "M #{x1} #{y1} A #{rx} #{ry} #{rotation} #{large ? 1 : 0} #{sweep ? 1 : 0} #{x2} #{y2}", **)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Builds an elliptical arc path ending at a relative offset.
|
|
35
|
+
# @param dx [Numeric] finite x displacement from the starting point
|
|
36
|
+
# @param dy [Numeric] finite y displacement from the starting point
|
|
37
|
+
# @param rx [Numeric] finite local x radius
|
|
38
|
+
# @param ry [Numeric] finite local y radius
|
|
39
|
+
# @param x [Numeric] finite starting x coordinate
|
|
40
|
+
# @param y [Numeric] finite starting y coordinate
|
|
41
|
+
# @param rotation [Numeric] clockwise ellipse rotation in degrees
|
|
42
|
+
# @param large [Boolean] select the arc exceeding a half turn
|
|
43
|
+
# @param sweep [Boolean] select increasing parameter angles
|
|
44
|
+
# @return [Sevgi::Graphics::Element] path element
|
|
45
|
+
# @raise [Sevgi::ArgumentError] when an operand is not finite real or a flag is not Boolean
|
|
46
|
+
# @see #ArcTo
|
|
47
|
+
def ArcBy(dx:, dy:, rx:, ry:, x: 0, y: 0, rotation: 0, large: false, sweep: false, **)
|
|
48
|
+
x, y, dx, dy, rx, ry, rotation = Scalar.numbers([x, y, dx, dy, rx, ry, rotation], context: "relative arc")
|
|
49
|
+
unless [large, sweep].all? { it.equal?(true) || it.equal?(false) }
|
|
50
|
+
ArgumentError.("Arc flags must be Boolean")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
path(d: "M #{x} #{y} a #{rx} #{ry} #{rotation} #{large ? 1 : 0} #{sweep ? 1 : 0} #{dx} #{dy}", **)
|
|
54
|
+
end
|
|
55
|
+
# rubocop:enable Metrics/ParameterLists
|
|
56
|
+
|
|
8
57
|
# Builds a line path ending at an absolute point.
|
|
9
|
-
# @
|
|
10
|
-
#
|
|
11
|
-
# @param
|
|
12
|
-
# @param
|
|
58
|
+
# @example Render a rational coordinate as an SVG number
|
|
59
|
+
# Sevgi::Graphics.SVG { LineTo(x2: Rational(1, 2), y2: 1) }
|
|
60
|
+
# @param x2 [Numeric] finite ending x coordinate
|
|
61
|
+
# @param y2 [Numeric] finite ending y coordinate
|
|
62
|
+
# @param x1 [Numeric] finite starting x coordinate
|
|
63
|
+
# @param y1 [Numeric] finite starting y coordinate
|
|
13
64
|
# @return [Sevgi::Graphics::Element] path element
|
|
65
|
+
# @raise [Sevgi::ArgumentError] when a coordinate is not a finite real number
|
|
14
66
|
def LineTo(x2:, y2:, x1: 0, y1: 0, **)
|
|
67
|
+
x1 = Scalar.number(x1, context: "absolute line", field: :x1)
|
|
68
|
+
y1 = Scalar.number(y1, context: "absolute line", field: :y1)
|
|
69
|
+
x2 = Scalar.number(x2, context: "absolute line", field: :x2)
|
|
70
|
+
y2 = Scalar.number(y2, context: "absolute line", field: :y2)
|
|
71
|
+
|
|
15
72
|
path(d: "M #{x1} #{y1} L #{x2} #{y2}", **)
|
|
16
73
|
end
|
|
17
74
|
|
|
18
75
|
# Builds a horizontal line path ending at an absolute x coordinate.
|
|
19
|
-
# @param x2 [Numeric] ending x coordinate
|
|
20
|
-
# @param x1 [Numeric] starting x coordinate
|
|
21
|
-
# @param y1 [Numeric] starting y coordinate
|
|
76
|
+
# @param x2 [Numeric] finite ending x coordinate
|
|
77
|
+
# @param x1 [Numeric] finite starting x coordinate
|
|
78
|
+
# @param y1 [Numeric] finite starting y coordinate
|
|
22
79
|
# @return [Sevgi::Graphics::Element] path element
|
|
80
|
+
# @raise [Sevgi::ArgumentError] when a coordinate is not a finite real number
|
|
23
81
|
def HLineTo(x2:, x1: 0, y1: 0, **)
|
|
82
|
+
x1 = Scalar.number(x1, context: "horizontal line", field: :x1)
|
|
83
|
+
y1 = Scalar.number(y1, context: "horizontal line", field: :y1)
|
|
84
|
+
x2 = Scalar.number(x2, context: "horizontal line", field: :x2)
|
|
85
|
+
|
|
24
86
|
path(d: "M #{x1} #{y1} H #{x2}", **)
|
|
25
87
|
end
|
|
26
88
|
|
|
@@ -41,23 +103,36 @@ module Sevgi
|
|
|
41
103
|
end
|
|
42
104
|
|
|
43
105
|
# Builds a vertical line path ending at an absolute y coordinate.
|
|
44
|
-
# @param y2 [Numeric] ending y coordinate
|
|
45
|
-
# @param x1 [Numeric] starting x coordinate
|
|
46
|
-
# @param y1 [Numeric] starting y coordinate
|
|
106
|
+
# @param y2 [Numeric] finite ending y coordinate
|
|
107
|
+
# @param x1 [Numeric] finite starting x coordinate
|
|
108
|
+
# @param y1 [Numeric] finite starting y coordinate
|
|
47
109
|
# @return [Sevgi::Graphics::Element] path element
|
|
110
|
+
# @raise [Sevgi::ArgumentError] when a coordinate is not a finite real number
|
|
48
111
|
def VLineTo(y2:, x1: 0, y1: 0, **)
|
|
112
|
+
x1 = Scalar.number(x1, context: "vertical line", field: :x1)
|
|
113
|
+
y1 = Scalar.number(y1, context: "vertical line", field: :y1)
|
|
114
|
+
y2 = Scalar.number(y2, context: "vertical line", field: :y2)
|
|
115
|
+
|
|
49
116
|
path(d: "M #{x1} #{y1} V #{y2}", **)
|
|
50
117
|
end
|
|
51
118
|
|
|
52
119
|
# Builds a relative line path from angle and length.
|
|
53
|
-
# @param angle [Numeric] angle in degrees
|
|
54
|
-
# @param length [Numeric] line length
|
|
55
|
-
# @param x [Numeric] starting x coordinate
|
|
56
|
-
# @param y [Numeric] starting y coordinate
|
|
120
|
+
# @param angle [Numeric] finite angle in degrees, normalized before calculation
|
|
121
|
+
# @param length [Numeric] finite line length, normalized before calculation
|
|
122
|
+
# @param x [Numeric] finite starting x coordinate, normalized to an SVG number
|
|
123
|
+
# @param y [Numeric] finite starting y coordinate, normalized to an SVG number
|
|
57
124
|
# @return [Sevgi::Graphics::Element] path element
|
|
125
|
+
# @raise [Sevgi::ArgumentError] when an operand is not a finite real number
|
|
58
126
|
def LineBy(angle:, length:, x: 0, y: 0, **)
|
|
59
|
-
|
|
60
|
-
dy =
|
|
127
|
+
angle, length, x, y = Scalar.numbers([angle, length, x, y], context: "relative line")
|
|
128
|
+
dx, dy = Scalar.numbers(
|
|
129
|
+
[
|
|
130
|
+
length * ::Math.cos(angle.to_f / 180 * ::Math::PI),
|
|
131
|
+
length * ::Math.sin(angle.to_f / 180 * ::Math::PI)
|
|
132
|
+
],
|
|
133
|
+
context: "relative line result"
|
|
134
|
+
)
|
|
135
|
+
|
|
61
136
|
path(d: "M #{x} #{y} l #{dx} #{dy}", **)
|
|
62
137
|
end
|
|
63
138
|
|
|
@@ -74,27 +149,40 @@ module Sevgi
|
|
|
74
149
|
end
|
|
75
150
|
|
|
76
151
|
# Builds a relative horizontal line path.
|
|
77
|
-
# @param length [Numeric] line length
|
|
78
|
-
# @param x [Numeric] starting x coordinate
|
|
79
|
-
# @param y [Numeric] starting y coordinate
|
|
152
|
+
# @param length [Numeric] finite line length
|
|
153
|
+
# @param x [Numeric] finite starting x coordinate
|
|
154
|
+
# @param y [Numeric] finite starting y coordinate
|
|
80
155
|
# @return [Sevgi::Graphics::Element] path element
|
|
156
|
+
# @raise [Sevgi::ArgumentError] when an operand is not a finite real number
|
|
81
157
|
def HLineBy(length:, x: 0, y: 0, **)
|
|
158
|
+
length = Scalar.number(length, context: "horizontal line", field: :length)
|
|
159
|
+
x = Scalar.number(x, context: "horizontal line", field: :x)
|
|
160
|
+
y = Scalar.number(y, context: "horizontal line", field: :y)
|
|
161
|
+
|
|
82
162
|
path(d: "M #{x} #{y} h #{length}", **)
|
|
83
163
|
end
|
|
84
164
|
|
|
85
165
|
# Builds a square rect.
|
|
86
|
-
# @param length [Numeric] side length
|
|
166
|
+
# @param length [Numeric] finite side length
|
|
87
167
|
# @return [Sevgi::Graphics::Element] rect element
|
|
168
|
+
# @raise [Sevgi::ArgumentError] when length is not a finite real number
|
|
88
169
|
def square(length:, **)
|
|
170
|
+
length = Scalar.number(length, context: "square", field: :length)
|
|
171
|
+
|
|
89
172
|
rect(width: length, height: length, **)
|
|
90
173
|
end
|
|
91
174
|
|
|
92
175
|
# Builds a relative vertical line path.
|
|
93
|
-
# @param length [Numeric] line length
|
|
94
|
-
# @param x [Numeric] starting x coordinate
|
|
95
|
-
# @param y [Numeric] starting y coordinate
|
|
176
|
+
# @param length [Numeric] finite line length
|
|
177
|
+
# @param x [Numeric] finite starting x coordinate
|
|
178
|
+
# @param y [Numeric] finite starting y coordinate
|
|
96
179
|
# @return [Sevgi::Graphics::Element] path element
|
|
180
|
+
# @raise [Sevgi::ArgumentError] when an operand is not a finite real number
|
|
97
181
|
def VLineBy(length:, x: 0, y: 0, **)
|
|
182
|
+
length = Scalar.number(length, context: "vertical line", field: :length)
|
|
183
|
+
x = Scalar.number(x, context: "vertical line", field: :x)
|
|
184
|
+
y = Scalar.number(y, context: "vertical line", field: :y)
|
|
185
|
+
|
|
98
186
|
path(d: "M #{x} #{y} v #{length}", **)
|
|
99
187
|
end
|
|
100
188
|
end
|
|
@@ -23,26 +23,28 @@ module Sevgi
|
|
|
23
23
|
mod, document = normalize(mod, document, block)
|
|
24
24
|
ArgumentError.("Mixture name or block required") if mod == Undefined && !block
|
|
25
25
|
|
|
26
|
-
document.mixture
|
|
26
|
+
document.send(:mixture, mod) unless mod == Undefined
|
|
27
27
|
include_anonymous(document, &block) if block
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end
|
|
30
|
+
class << self
|
|
31
|
+
private
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
document.send(:include, anonymous)
|
|
37
|
-
document.extend(anonymous.const_get(:ClassMethods)) if anonymous.const_defined?(:ClassMethods, false)
|
|
33
|
+
def anonymous_document?(mod, block)
|
|
34
|
+
block && mod.is_a?(::Class) && mod <= Graphics::Document::Proto
|
|
38
35
|
end
|
|
39
|
-
end
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
def include_anonymous(document, &block)
|
|
38
|
+
::Module.new(&block).tap do |anonymous|
|
|
39
|
+
document.send(:include, anonymous)
|
|
40
|
+
document.extend(anonymous.const_get(:ClassMethods)) if anonymous.const_defined?(:ClassMethods, false)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
44
43
|
|
|
45
|
-
|
|
44
|
+
def normalize(mod, document, block)
|
|
45
|
+
anonymous_document?(mod, block) ? [Undefined, mod] : [mod, document]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
46
48
|
end
|
|
47
49
|
end
|
|
48
50
|
end
|
data/lib/sevgi/graphics/xml.rb
CHANGED
|
@@ -11,8 +11,9 @@ module Sevgi
|
|
|
11
11
|
NCNAME_CHAR = "#{NCNAME_START}\\-.0-9\u{B7}\u{300}-\u{36F}\u{203F}-\u{2040}".freeze
|
|
12
12
|
NCNAME = "[#{NCNAME_START}][#{NCNAME_CHAR}]*".freeze
|
|
13
13
|
QNAME = /\A#{NCNAME}(?::#{NCNAME})?\z/u
|
|
14
|
+
ILLEGAL_CHARACTER = /[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/u
|
|
14
15
|
|
|
15
|
-
private_constant :NCNAME, :NCNAME_CHAR, :NCNAME_START, :QNAME
|
|
16
|
+
private_constant :NCNAME, :NCNAME_CHAR, :NCNAME_START, :QNAME, :ILLEGAL_CHARACTER
|
|
16
17
|
|
|
17
18
|
class << self
|
|
18
19
|
# Validates an XML 1.0 string representation.
|
|
@@ -104,8 +105,8 @@ module Sevgi
|
|
|
104
105
|
ArgumentError.("#{context} must be valid UTF-8") unless value.valid_encoding?
|
|
105
106
|
|
|
106
107
|
text = value.encode("UTF-8")
|
|
107
|
-
if (
|
|
108
|
-
ArgumentError.("#{context} contains illegal character U+#{format("%04X",
|
|
108
|
+
if (match = ILLEGAL_CHARACTER.match(text))
|
|
109
|
+
ArgumentError.("#{context} contains illegal character U+#{format("%04X", match[0].ord)}")
|
|
109
110
|
end
|
|
110
111
|
|
|
111
112
|
text
|
|
@@ -113,12 +114,6 @@ module Sevgi
|
|
|
113
114
|
ArgumentError.("#{context} must be valid UTF-8: #{e.message}")
|
|
114
115
|
end
|
|
115
116
|
|
|
116
|
-
def legal_codepoint?(codepoint)
|
|
117
|
-
[0x9, 0xA, 0xD].include?(codepoint) ||
|
|
118
|
-
(0x20..0xD7FF).cover?(codepoint) ||
|
|
119
|
-
(0xE000..0xFFFD).cover?(codepoint) ||
|
|
120
|
-
(0x10000..0x10FFFF).cover?(codepoint)
|
|
121
|
-
end
|
|
122
117
|
end
|
|
123
118
|
end
|
|
124
119
|
|
data/lib/sevgi/graphics.rb
CHANGED
|
@@ -14,15 +14,44 @@ require_relative "graphics/version"
|
|
|
14
14
|
|
|
15
15
|
module Sevgi
|
|
16
16
|
# SVG document builder and DSL namespace.
|
|
17
|
+
#
|
|
18
|
+
# A document profile controls root metadata and preambles. A {Canvas} controls
|
|
19
|
+
# physical size, margins, viewport, and viewBox. {Graphics.SVG} combines them
|
|
20
|
+
# into an element tree. Library code keeps that tree as an object until it
|
|
21
|
+
# explicitly renders, validates, saves, or exports it.
|
|
22
|
+
#
|
|
23
|
+
# @example Build and render a minimal SVG document
|
|
24
|
+
# drawing = Sevgi::Graphics.SVG :minimal, width: 10, height: 10 do
|
|
25
|
+
# circle cx: 5, cy: 5, r: 4
|
|
26
|
+
# end
|
|
27
|
+
# drawing.Render #=> "<svg width=\"10\" height=\"10\">\n <circle cx=\"5\" cy=\"5\" r=\"4\"/>\n</svg>"
|
|
28
|
+
# @see https://sevgi.roktas.dev/documents/ SVG construction and profile guide
|
|
29
|
+
# @see https://sevgi.roktas.dev/compose/ Composition guide
|
|
30
|
+
# @see https://sevgi.roktas.dev/examples/ Runnable drawing examples
|
|
31
|
+
# @see Sevgi::Graphics::Document
|
|
32
|
+
# @see Sevgi::Graphics::Canvas
|
|
17
33
|
module Graphics
|
|
18
|
-
# @overload canvas(
|
|
19
|
-
# Builds a canvas from a paper profile
|
|
20
|
-
# @param
|
|
21
|
-
# @param
|
|
34
|
+
# @overload canvas(paper, **overrides)
|
|
35
|
+
# Builds a canvas from a paper profile with optional field overrides.
|
|
36
|
+
# @param paper [Sevgi::Graphics::Paper, Symbol, String] paper object or registered profile
|
|
37
|
+
# @param overrides [Hash] canvas field overrides
|
|
22
38
|
# @return [Sevgi::Graphics::Canvas]
|
|
23
|
-
# @raise [Sevgi::ArgumentError] when the paper
|
|
39
|
+
# @raise [Sevgi::ArgumentError] when the paper or an override is invalid
|
|
40
|
+
# @overload canvas(width:, height:, unit: "mm", name: :custom, margins: [])
|
|
41
|
+
# Builds a canvas from an explicit size.
|
|
42
|
+
# @param width [Numeric] canvas width
|
|
43
|
+
# @param height [Numeric] canvas height
|
|
44
|
+
# @param unit [Symbol, String] SVG unit
|
|
45
|
+
# @param name [Symbol, String] paper name
|
|
46
|
+
# @param margins [Array<Numeric>] margin shorthand values
|
|
47
|
+
# @return [Sevgi::Graphics::Canvas]
|
|
48
|
+
# @raise [Sevgi::ArgumentError] when a required field is omitted or a value is invalid
|
|
49
|
+
# @example Use the component constructor in library code
|
|
50
|
+
# page = Sevgi::Graphics.canvas(:a4, margins: [12, 10])
|
|
51
|
+
# icon = Sevgi::Graphics.canvas(width: 24, height: 24, unit: :px)
|
|
52
|
+
# [page.name, icon.viewport] # => [:a4, {width: "24.0px", height: "24.0px"}]
|
|
24
53
|
def canvas(...)
|
|
25
|
-
Graphics::Canvas.
|
|
54
|
+
Graphics::Canvas.call(...)
|
|
26
55
|
end
|
|
27
56
|
|
|
28
57
|
# @overload document(name)
|
|
@@ -34,37 +63,52 @@ module Sevgi
|
|
|
34
63
|
# Looks up a named profile when both definition keywords are omitted. Supplying `preambles:` or `attributes:`
|
|
35
64
|
# defines a named profile, or returns an existing profile when every explicitly supplied field matches. Omitted
|
|
36
65
|
# fields are ignored during existing-profile comparison. Profile containers and strings are copied into the
|
|
37
|
-
# process-global, thread-atomic registry
|
|
38
|
-
#
|
|
66
|
+
# process-global, thread-atomic registry. Attribute names and nested Hash keys are normalized. Nil attributes are
|
|
67
|
+
# omitted. Update suffixes remain available for document-class inheritance. Mutable non-container values are
|
|
68
|
+
# stringified once before registration. Concurrent identical definitions return the same registered class.
|
|
39
69
|
# @param name [Symbol, String] profile name
|
|
40
70
|
# @param preambles [Array<String>, nil, Sevgi::Undefined] document preamble lines
|
|
41
|
-
# @param attributes [Hash, nil, Sevgi::Undefined] default root attributes
|
|
71
|
+
# @param attributes [Hash, nil, Sevgi::Undefined] default root attributes. Nil means an empty Hash
|
|
42
72
|
# @return [Class] document class
|
|
43
73
|
# @raise [Sevgi::ArgumentError] when a profile conflicts or metadata is invalid XML, cyclic, or cannot be stringified
|
|
44
74
|
# @overload document(preambles: Undefined, attributes: Undefined)
|
|
45
75
|
# Defines an anonymous document profile without registering it globally.
|
|
46
76
|
# @param preambles [Array<String>, nil, Sevgi::Undefined] document preamble lines
|
|
47
|
-
# @param attributes [Hash, nil, Sevgi::Undefined] default root attributes
|
|
77
|
+
# @param attributes [Hash, nil, Sevgi::Undefined] default root attributes. Nil means an empty Hash
|
|
48
78
|
# @return [Class] anonymous document class
|
|
49
79
|
# @raise [Sevgi::ArgumentError] when metadata is invalid XML, cyclic, or cannot be stringified
|
|
50
80
|
# @return [Class] document class
|
|
81
|
+
# @example Define, look up, and inspect a named document
|
|
82
|
+
# Sevgi::Graphics.document(:card, attributes: {viewBox: "0 0 100 60"})
|
|
83
|
+
# Sevgi::Graphics::Document.fetch(:card) # => the registered document class
|
|
84
|
+
# Sevgi::Graphics::Document.profile(:card).attributes # => {viewBox: "0 0 100 60"}
|
|
85
|
+
# @example Define an anonymous document without changing the registry
|
|
86
|
+
# klass = Sevgi::Graphics.document(attributes: {viewBox: "0 0 10 10"})
|
|
87
|
+
# klass.profile.name # => nil
|
|
88
|
+
# @example Define and use a named profile from library code
|
|
89
|
+
# Sevgi::Graphics.document(:badge, attributes: {viewBox: "0 0 24 24"})
|
|
90
|
+
# drawing = Sevgi::Graphics.SVG(:badge) { circle cx: 12, cy: 12, r: 10 }
|
|
91
|
+
# drawing[:viewBox] # => "0 0 24 24"
|
|
51
92
|
def document(name = Undefined, preambles: Undefined, attributes: Undefined)
|
|
52
93
|
Graphics::Document.define(name, preambles:, attributes:)
|
|
53
94
|
end
|
|
54
95
|
|
|
55
96
|
# Defines or replaces a document profile class.
|
|
56
97
|
# Validation and snapshot capture complete before an existing registration is atomically replaced.
|
|
98
|
+
# Use the non-bang {#document} for ordinary idempotent definitions. This
|
|
99
|
+
# bang form is an explicit process-global replacement.
|
|
57
100
|
# @param name [Symbol, String] profile name
|
|
58
101
|
# @param preambles [Array<String>, nil] document preamble lines
|
|
59
|
-
# @param attributes [Hash, nil] default root attributes
|
|
102
|
+
# @param attributes [Hash, nil] default root attributes. Nil means an empty Hash
|
|
60
103
|
# @return [Class] document class
|
|
61
104
|
# @raise [Sevgi::ArgumentError] when the name or metadata is invalid XML, cyclic, or cannot be stringified
|
|
62
105
|
def document!(name, preambles: [], attributes: {})
|
|
63
106
|
Graphics::Document.define(name, preambles:, attributes:, overwrite: true)
|
|
64
107
|
end
|
|
65
108
|
|
|
66
|
-
# Defines a paper profile unless the same profile already exists.
|
|
67
|
-
#
|
|
109
|
+
# Defines a paper profile unless the same profile already exists.
|
|
110
|
+
# Registration is process-global and thread-atomic. Identical concurrent definitions are idempotent. Conflicting
|
|
111
|
+
# definitions are rejected.
|
|
68
112
|
# @param width [Numeric] paper width
|
|
69
113
|
# @param height [Numeric] paper height
|
|
70
114
|
# @param name [Symbol, String] profile name
|
|
@@ -78,6 +122,7 @@ module Sevgi
|
|
|
78
122
|
end
|
|
79
123
|
|
|
80
124
|
# Defines or replaces a paper profile.
|
|
125
|
+
# Use this only when replacing the process-global meaning of a name is intentional.
|
|
81
126
|
# @param width [Numeric] paper width
|
|
82
127
|
# @param height [Numeric] paper height
|
|
83
128
|
# @param name [Symbol, String] profile name
|
|
@@ -85,23 +130,29 @@ module Sevgi
|
|
|
85
130
|
# @return [Symbol, String] original profile name
|
|
86
131
|
# @raise [Sevgi::ArgumentError] when the profile is invalid or the profile name is reserved
|
|
87
132
|
def paper!(width, height, name = :custom, unit: "mm")
|
|
88
|
-
name.tap { Graphics::Paper.define(name, width:, height:, unit:) }
|
|
133
|
+
name.tap { Graphics::Paper.define(name, width:, height:, unit:, overwrite: true) }
|
|
89
134
|
end
|
|
90
135
|
|
|
91
|
-
# Builds an SVG root
|
|
92
|
-
#
|
|
136
|
+
# Builds an SVG root element tree without rendering it.
|
|
137
|
+
#
|
|
138
|
+
# The first argument selects root metadata, or supplies the canvas while using the default profile. The second
|
|
139
|
+
# supplies canvas attributes when the first argument is a profile. Keyword attributes are applied after both.
|
|
140
|
+
# @param document [Symbol, String, Class, Sevgi::Graphics::Canvas, Sevgi::Graphics::Paper] document profile,
|
|
141
|
+
# document class, or canvas input that uses the default profile
|
|
93
142
|
# @param canvas [Sevgi::Graphics::Canvas, Sevgi::Graphics::Paper, Symbol, String, Sevgi::Undefined, nil] canvas input
|
|
94
143
|
# @yield evaluates the drawing DSL in the root element
|
|
95
144
|
# @yieldreturn [Object] ignored block result
|
|
96
145
|
# @return [Sevgi::Graphics::Document::Proto] SVG root element
|
|
97
146
|
# @raise [Sevgi::ArgumentError] when the document/canvas profile or root XML attributes are invalid
|
|
98
147
|
def SVG(document = :default, canvas = Undefined, **, &block)
|
|
148
|
+
if canvas.equal?(Undefined) && (document.is_a?(Canvas) || document.is_a?(Paper))
|
|
149
|
+
document, canvas = :default, document
|
|
150
|
+
end
|
|
151
|
+
|
|
99
152
|
Graphics::Document.(document, canvas, **, &block)
|
|
100
153
|
end
|
|
101
154
|
|
|
102
155
|
extend self
|
|
103
156
|
end
|
|
104
157
|
|
|
105
|
-
# Top-level alias for the graphics DSL namespace.
|
|
106
|
-
SVG = Graphics
|
|
107
158
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sevgi-graphics
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Recai Oktaş
|
|
@@ -15,15 +15,15 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 1.0.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
26
|
-
description:
|
|
25
|
+
version: 1.0.0
|
|
26
|
+
description: Builds and renders SVG document trees from Ruby.
|
|
27
27
|
email: roktas@gmail.com
|
|
28
28
|
executables: []
|
|
29
29
|
extensions: []
|
|
@@ -39,6 +39,7 @@ files:
|
|
|
39
39
|
- lib/sevgi/graphics/auxiliary/content.rb
|
|
40
40
|
- lib/sevgi/graphics/auxiliary/margin.rb
|
|
41
41
|
- lib/sevgi/graphics/auxiliary/paper.rb
|
|
42
|
+
- lib/sevgi/graphics/auxiliary/path.rb
|
|
42
43
|
- lib/sevgi/graphics/auxiliary/scalar.rb
|
|
43
44
|
- lib/sevgi/graphics/auxiliary/sizes.rb
|
|
44
45
|
- lib/sevgi/graphics/document.rb
|
|
@@ -92,7 +93,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
92
93
|
- !ruby/object:Gem::Version
|
|
93
94
|
version: '0'
|
|
94
95
|
requirements: []
|
|
95
|
-
rubygems_version: 4.0.
|
|
96
|
+
rubygems_version: 4.0.20
|
|
96
97
|
specification_version: 4
|
|
97
|
-
summary: Core DSL for
|
|
98
|
+
summary: Core SVG DSL for Sevgi.
|
|
98
99
|
test_files: []
|