sevgi-graphics 0.73.2 → 0.94.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 +4 -0
- data/LICENSE +5 -0
- data/README.md +37 -0
- data/lib/sevgi/graphics/attribute.rb +82 -10
- data/lib/sevgi/graphics/auxiliary/canvas.rb +153 -0
- data/lib/sevgi/graphics/auxiliary/content.rb +157 -0
- data/lib/sevgi/graphics/auxiliary/margin.rb +93 -0
- data/lib/sevgi/graphics/auxiliary/paper.rb +163 -0
- data/lib/sevgi/graphics/auxiliary.rb +7 -0
- data/lib/sevgi/graphics/document/base.rb +6 -0
- data/lib/sevgi/graphics/document/default.rb +1 -0
- data/lib/sevgi/graphics/document/html.rb +1 -0
- data/lib/sevgi/graphics/document/inkscape.rb +1 -0
- data/lib/sevgi/graphics/document/minimal.rb +1 -0
- data/lib/sevgi/graphics/document.rb +205 -13
- data/lib/sevgi/graphics/element.rb +69 -9
- data/lib/sevgi/graphics/mixtures/call.rb +62 -4
- data/lib/sevgi/graphics/mixtures/core.rb +146 -25
- data/lib/sevgi/graphics/mixtures/duplicate.rb +47 -3
- data/lib/sevgi/graphics/mixtures/export.rb +31 -6
- data/lib/sevgi/graphics/mixtures/hatch.rb +24 -3
- data/lib/sevgi/graphics/mixtures/identify.rb +26 -2
- data/lib/sevgi/graphics/mixtures/include.rb +40 -4
- data/lib/sevgi/graphics/mixtures/inkscape.rb +51 -5
- data/lib/sevgi/graphics/mixtures/lint.rb +12 -0
- data/lib/sevgi/graphics/mixtures/polyfills.rb +11 -0
- data/lib/sevgi/graphics/mixtures/rdf.rb +40 -1
- data/lib/sevgi/graphics/mixtures/render.rb +136 -14
- data/lib/sevgi/graphics/mixtures/save.rb +21 -3
- data/lib/sevgi/graphics/mixtures/symbols.rb +7 -0
- data/lib/sevgi/graphics/mixtures/tile.rb +52 -9
- data/lib/sevgi/graphics/mixtures/transform.rb +54 -16
- data/lib/sevgi/graphics/mixtures/underscore.rb +21 -1
- data/lib/sevgi/graphics/mixtures/validate.rb +15 -1
- data/lib/sevgi/graphics/mixtures/wrappers.rb +54 -17
- data/lib/sevgi/graphics/mixtures.rb +35 -3
- data/lib/sevgi/graphics/version.rb +2 -1
- data/lib/sevgi/graphics.rb +71 -5
- metadata +12 -9
- data/lib/sevgi/graphics/auxilary/canvas.rb +0 -72
- data/lib/sevgi/graphics/auxilary/content.rb +0 -81
- data/lib/sevgi/graphics/auxilary/margin.rb +0 -49
- data/lib/sevgi/graphics/auxilary/paper.rb +0 -95
- data/lib/sevgi/graphics/auxilary.rb +0 -7
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# DSL helpers for SVG transform attributes.
|
|
6
7
|
module Transform
|
|
8
|
+
# Aligns an inner box inside an outer box.
|
|
9
|
+
# @param position [Symbol, String, nil] alignment name
|
|
10
|
+
# @param inner [#width, #height, nil] inner box
|
|
11
|
+
# @param outer [#width, #height, nil] outer box
|
|
12
|
+
# @return [Sevgi::Graphics::Element] self
|
|
13
|
+
# @raise [Sevgi::ArgumentError] when alignment is unsupported
|
|
7
14
|
def Align(position, inner:, outer:)
|
|
8
15
|
return self unless position && inner && outer
|
|
9
16
|
|
|
@@ -15,34 +22,47 @@ module Sevgi
|
|
|
15
22
|
end
|
|
16
23
|
end
|
|
17
24
|
|
|
25
|
+
# Appends a scale(-1, -1) transform.
|
|
26
|
+
# @return [Sevgi::Graphics::Element] self
|
|
18
27
|
def Flip
|
|
19
28
|
tap do
|
|
20
|
-
attributes[:"transform#{ATTRIBUTE_UPDATE_SUFFIX}"] = "scale(-1, 1)
|
|
29
|
+
attributes[:"transform#{ATTRIBUTE_UPDATE_SUFFIX}"] = "scale(-1, -1)"
|
|
21
30
|
end
|
|
22
31
|
end
|
|
23
32
|
|
|
33
|
+
# Appends a horizontal flip transform.
|
|
34
|
+
# @return [Sevgi::Graphics::Element] self
|
|
24
35
|
def FlipX
|
|
25
36
|
tap do
|
|
26
37
|
attributes[:"transform#{ATTRIBUTE_UPDATE_SUFFIX}"] = "scale(-1, 1)"
|
|
27
38
|
end
|
|
28
39
|
end
|
|
29
40
|
|
|
41
|
+
# Appends a vertical flip transform.
|
|
42
|
+
# @return [Sevgi::Graphics::Element] self
|
|
30
43
|
def FlipY
|
|
31
44
|
tap do
|
|
32
|
-
attributes[:"transform#{ATTRIBUTE_UPDATE_SUFFIX}"] = "scale(1, -
|
|
45
|
+
attributes[:"transform#{ATTRIBUTE_UPDATE_SUFFIX}"] = "scale(1, -1)"
|
|
33
46
|
end
|
|
34
47
|
end
|
|
35
48
|
|
|
49
|
+
# Appends a six-value SVG matrix transform.
|
|
50
|
+
# @param values [Array<Numeric>] matrix values
|
|
51
|
+
# @return [Sevgi::Graphics::Element] self
|
|
52
|
+
# @raise [Sevgi::ArgumentError] when exactly six values are not supplied
|
|
36
53
|
def Matrix(*values)
|
|
37
54
|
tap do
|
|
38
55
|
ArgumentError.("Incorrect transform matrix (six values required): #{values}") if values.size != 6
|
|
39
56
|
|
|
40
|
-
next if values.map(&:to_f).all? { it == 0.0 }
|
|
41
|
-
|
|
42
57
|
attributes[:"transform#{ATTRIBUTE_UPDATE_SUFFIX}"] = "matrix(#{values.join(" ")})"
|
|
43
58
|
end
|
|
44
59
|
end
|
|
45
60
|
|
|
61
|
+
# Appends an SVG rotate transform.
|
|
62
|
+
# @param a [Numeric] angle in degrees
|
|
63
|
+
# @param origin [Array<Numeric>] optional x and y origin
|
|
64
|
+
# @return [Sevgi::Graphics::Element] self
|
|
65
|
+
# @raise [Sevgi::ArgumentError] when origin is not two coordinates
|
|
46
66
|
def Rotate(a, *origin)
|
|
47
67
|
tap do
|
|
48
68
|
if !origin.empty? && origin.size != 2
|
|
@@ -55,28 +75,39 @@ module Sevgi
|
|
|
55
75
|
end
|
|
56
76
|
end
|
|
57
77
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
78
|
+
# @overload RotateRight(*origin)
|
|
79
|
+
# Appends a 90-degree SVG rotate transform.
|
|
80
|
+
# @param origin [Array<Numeric>] optional x and y origin
|
|
81
|
+
# @return [Sevgi::Graphics::Element] self
|
|
82
|
+
def RotateRight(...) = Rotate(90, ...)
|
|
83
|
+
|
|
84
|
+
# @overload RotateLeft(*origin)
|
|
85
|
+
# Appends a -90-degree SVG rotate transform.
|
|
86
|
+
# @param origin [Array<Numeric>] optional x and y origin
|
|
87
|
+
# @return [Sevgi::Graphics::Element] self
|
|
88
|
+
def RotateLeft(...) = Rotate(-90, ...)
|
|
89
|
+
|
|
90
|
+
# Appends an SVG scale transform.
|
|
91
|
+
# @param x [Numeric] x scale, or uniform scale when y is nil
|
|
92
|
+
# @param y [Numeric, nil] y scale
|
|
93
|
+
# @return [Sevgi::Graphics::Element] self
|
|
62
94
|
def Scale(x, y = nil)
|
|
63
95
|
tap do
|
|
64
|
-
next if x.to_f == 0.0 && (y.nil? || y.to_f == 0.0)
|
|
65
|
-
|
|
66
96
|
attributes[:"transform#{ATTRIBUTE_UPDATE_SUFFIX}"] = "scale(#{(y ? [x, y] : [x]).join(", ")})"
|
|
67
97
|
end
|
|
68
98
|
end
|
|
69
99
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
end
|
|
75
|
-
|
|
100
|
+
# Appends a skew transform through an SVG matrix.
|
|
101
|
+
# @param ax [Numeric] x skew angle in degrees
|
|
102
|
+
# @param ay [Numeric] y skew angle in degrees
|
|
103
|
+
# @return [Sevgi::Graphics::Element] self
|
|
76
104
|
def Skew(ax, ay)
|
|
77
105
|
Matrix(1.0, ::Math.tan(ay / 180.0 * ::Math::PI), ::Math.tan(ax / 180.0 * ::Math::PI), 1.0, 0.0, 0.0)
|
|
78
106
|
end
|
|
79
107
|
|
|
108
|
+
# Appends an SVG skewX transform.
|
|
109
|
+
# @param a [Numeric] angle in degrees
|
|
110
|
+
# @return [Sevgi::Graphics::Element] self
|
|
80
111
|
def SkewX(a)
|
|
81
112
|
tap do
|
|
82
113
|
next if a.to_f == 0.0
|
|
@@ -85,6 +116,9 @@ module Sevgi
|
|
|
85
116
|
end
|
|
86
117
|
end
|
|
87
118
|
|
|
119
|
+
# Appends an SVG skewY transform.
|
|
120
|
+
# @param a [Numeric] angle in degrees
|
|
121
|
+
# @return [Sevgi::Graphics::Element] self
|
|
88
122
|
def SkewY(a)
|
|
89
123
|
tap do
|
|
90
124
|
next if a.to_f == 0.0
|
|
@@ -93,6 +127,10 @@ module Sevgi
|
|
|
93
127
|
end
|
|
94
128
|
end
|
|
95
129
|
|
|
130
|
+
# Appends an SVG translate transform.
|
|
131
|
+
# @param x [Numeric] x translation
|
|
132
|
+
# @param y [Numeric, nil] y translation
|
|
133
|
+
# @return [Sevgi::Graphics::Element] self
|
|
96
134
|
def Translate(x, y = nil)
|
|
97
135
|
tap do
|
|
98
136
|
next if x.to_f == 0.0 && (y.nil? || y.to_f == 0.0)
|
|
@@ -3,18 +3,38 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# DSL helpers for floating text, comments, and inherited internal attributes.
|
|
6
7
|
module Underscore
|
|
8
|
+
# Creates a floating content element.
|
|
9
|
+
# @param contents [Array<Object>] text or content objects
|
|
10
|
+
# @return [Sevgi::Graphics::Element] floating element
|
|
7
11
|
def _(*contents)
|
|
8
12
|
Element(:_, *contents)
|
|
9
13
|
end
|
|
10
14
|
|
|
15
|
+
# Adds an XML comment.
|
|
16
|
+
# @param comment [Object] comment text
|
|
17
|
+
# @return [Sevgi::Graphics::Element] floating comment element
|
|
18
|
+
# @raise [Sevgi::ArgumentError] when comment text would produce malformed XML
|
|
11
19
|
def Comment(comment)
|
|
20
|
+
comment = comment.to_s
|
|
21
|
+
|
|
22
|
+
ArgumentError.("XML comment must not contain '--'") if comment.include?("--")
|
|
23
|
+
ArgumentError.("XML comment must not end with '-'") if comment.end_with?("-")
|
|
24
|
+
|
|
12
25
|
_(Content.verbatim("<!-- #{comment} -->"))
|
|
13
26
|
end
|
|
14
27
|
|
|
28
|
+
# Merges internal attributes from the document root, ancestors, and this element.
|
|
29
|
+
# Only the direct root-to-self ancestor chain participates; sibling subtrees are ignored. When the same key is
|
|
30
|
+
# present on multiple chain elements, the nearest element to the receiver wins.
|
|
31
|
+
# @return [Hash] merged internal attributes
|
|
15
32
|
def Ancestral
|
|
33
|
+
chain = []
|
|
34
|
+
TraverseUp { |element| chain.unshift(element) }
|
|
35
|
+
|
|
16
36
|
{}.tap do |result|
|
|
17
|
-
|
|
37
|
+
chain.each { |element| result.merge!(element[:_]) if element.has?(:_) }
|
|
18
38
|
end
|
|
19
39
|
end
|
|
20
40
|
end
|
|
@@ -3,19 +3,28 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# DSL helpers for SVG standard validation.
|
|
6
7
|
module Validate
|
|
8
|
+
# Returns text content used as SVG character data.
|
|
9
|
+
# @return [String, nil] joined text content or nil
|
|
7
10
|
def CData
|
|
8
11
|
return if !contents || contents.empty?
|
|
9
12
|
|
|
10
13
|
Content.text(contents)
|
|
11
14
|
end
|
|
12
15
|
|
|
16
|
+
# Reports whether a namespace is available on this element or an ancestor.
|
|
17
|
+
# @param name [Symbol, String] namespace suffix without xmlns:
|
|
18
|
+
# @return [Boolean]
|
|
13
19
|
def NS?(name)
|
|
14
20
|
self.class.attributes.key?(key = :"xmlns:#{name}") || TraverseUp { Stay(true) if it.has?(key) } || false
|
|
15
21
|
end
|
|
16
22
|
|
|
17
23
|
require "sevgi/standard"
|
|
18
24
|
|
|
25
|
+
# Validates this subtree against the SVG standard metadata.
|
|
26
|
+
# @return [Sevgi::Graphics::Element] self
|
|
27
|
+
# @raise [Sevgi::ValidationError] when SVG validation fails
|
|
19
28
|
def Validate
|
|
20
29
|
Traverse do |element|
|
|
21
30
|
Standard.conform(
|
|
@@ -27,7 +36,12 @@ module Sevgi
|
|
|
27
36
|
end
|
|
28
37
|
end
|
|
29
38
|
|
|
30
|
-
rescue ::LoadError
|
|
39
|
+
rescue ::LoadError => e
|
|
40
|
+
raise unless e.path == "sevgi/standard"
|
|
41
|
+
|
|
42
|
+
# @overload Validate
|
|
43
|
+
# No-op validation fallback when sevgi/standard is unavailable.
|
|
44
|
+
# @return [nil]
|
|
31
45
|
def Validate(...)
|
|
32
46
|
end
|
|
33
47
|
end
|
|
@@ -3,15 +3,31 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# DSL wrappers for common SVG shapes and content patterns.
|
|
6
7
|
module Wrappers
|
|
7
|
-
|
|
8
|
+
# Builds a line path ending at an absolute point.
|
|
9
|
+
# @param x2 [Numeric] ending x coordinate
|
|
10
|
+
# @param y2 [Numeric] ending y coordinate
|
|
11
|
+
# @param x1 [Numeric] starting x coordinate
|
|
12
|
+
# @param y1 [Numeric] starting y coordinate
|
|
13
|
+
# @return [Sevgi::Graphics::Element] path element
|
|
14
|
+
def LineTo(x2:, y2:, x1: 0, y1: 0, **)
|
|
8
15
|
path(d: "M #{x1} #{y1} L #{x2} #{y2}", **)
|
|
9
16
|
end
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
# 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
|
|
22
|
+
# @return [Sevgi::Graphics::Element] path element
|
|
23
|
+
def HLineTo(x2:, x1: 0, y1: 0, **)
|
|
12
24
|
path(d: "M #{x1} #{y1} H #{x2}", **)
|
|
13
25
|
end
|
|
14
26
|
|
|
27
|
+
# Builds a titled SVG symbol.
|
|
28
|
+
# @param name [String] human-readable symbol name
|
|
29
|
+
# @param kwargs [Hash] symbol attributes
|
|
30
|
+
# @return [Sevgi::Graphics::Element] symbol element
|
|
15
31
|
def Symbol(name, **kwargs, &block)
|
|
16
32
|
id = (words = name.split).map(&:downcase).join("-")
|
|
17
33
|
title = words.map(&:capitalize).join(" ")
|
|
@@ -22,39 +38,60 @@ module Sevgi
|
|
|
22
38
|
end
|
|
23
39
|
end
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
# Builds a vertical line path ending at an absolute y coordinate.
|
|
42
|
+
# @param y2 [Numeric] ending y coordinate
|
|
43
|
+
# @param x1 [Numeric] starting x coordinate
|
|
44
|
+
# @param y1 [Numeric] starting y coordinate
|
|
45
|
+
# @return [Sevgi::Graphics::Element] path element
|
|
46
|
+
def VLineTo(y2:, x1: 0, y1: 0, **)
|
|
26
47
|
path(d: "M #{x1} #{y1} V #{y2}", **)
|
|
27
48
|
end
|
|
28
49
|
|
|
29
|
-
|
|
50
|
+
# Builds a relative line path from angle and length.
|
|
51
|
+
# @param angle [Numeric] angle in degrees
|
|
52
|
+
# @param length [Numeric] line length
|
|
53
|
+
# @param x [Numeric] starting x coordinate
|
|
54
|
+
# @param y [Numeric] starting y coordinate
|
|
55
|
+
# @return [Sevgi::Graphics::Element] path element
|
|
56
|
+
def LineBy(angle:, length:, x: 0, y: 0, **)
|
|
30
57
|
dx = length * ::Math.cos(angle.to_f / 180 * ::Math::PI)
|
|
31
58
|
dy = length * ::Math.sin(angle.to_f / 180 * ::Math::PI)
|
|
32
59
|
path(d: "M #{x} #{y} l #{dx} #{dy}", **)
|
|
33
60
|
end
|
|
34
61
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
# Builds a style element with CSS content.
|
|
63
|
+
# @param hash [Hash, nil] CSS rules
|
|
64
|
+
# @param attributes [Hash] style attributes or CSS rules when hash is nil
|
|
65
|
+
# @return [Sevgi::Graphics::Element] style element
|
|
66
|
+
# @raise [Sevgi::ArgumentError] when CSS content is not a hash
|
|
67
|
+
def css(hash = nil, **attributes)
|
|
68
|
+
hash, attributes = attributes, {} unless hash
|
|
38
69
|
|
|
39
|
-
|
|
40
|
-
dy = dx * ::Math.tan(angle.to_f / 180 * ::Math::PI)
|
|
41
|
-
path(d: "M #{x} #{y} l #{dx} #{dy}", **)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def cyline(angle:, dy:, x: 0, y: 0, **)
|
|
45
|
-
dx = dy / ::Math.tan(angle.to_f / 180 * ::Math::PI)
|
|
46
|
-
path(d: "M #{x} #{y} l #{dx} #{dy}", **)
|
|
70
|
+
style(Content.css(hash), type: "text/css", **attributes)
|
|
47
71
|
end
|
|
48
72
|
|
|
49
|
-
|
|
73
|
+
# Builds a relative horizontal line path.
|
|
74
|
+
# @param length [Numeric] line length
|
|
75
|
+
# @param x [Numeric] starting x coordinate
|
|
76
|
+
# @param y [Numeric] starting y coordinate
|
|
77
|
+
# @return [Sevgi::Graphics::Element] path element
|
|
78
|
+
def HLineBy(length:, x: 0, y: 0, **)
|
|
50
79
|
path(d: "M #{x} #{y} h #{length}", **)
|
|
51
80
|
end
|
|
52
81
|
|
|
82
|
+
# Builds a square rect.
|
|
83
|
+
# @param length [Numeric] side length
|
|
84
|
+
# @return [Sevgi::Graphics::Element] rect element
|
|
53
85
|
def square(length:, **)
|
|
54
86
|
rect(width: length, height: length, **)
|
|
55
87
|
end
|
|
56
88
|
|
|
57
|
-
|
|
89
|
+
# Builds a relative vertical line path.
|
|
90
|
+
# @param length [Numeric] line length
|
|
91
|
+
# @param x [Numeric] starting x coordinate
|
|
92
|
+
# @param y [Numeric] starting y coordinate
|
|
93
|
+
# @return [Sevgi::Graphics::Element] path element
|
|
94
|
+
def VLineBy(length:, x: 0, y: 0, **)
|
|
58
95
|
path(d: "M #{x} #{y} v #{length}", **)
|
|
59
96
|
end
|
|
60
97
|
end
|
|
@@ -2,11 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
|
+
# Mixins that extend document classes with DSL helper methods.
|
|
5
6
|
module Mixtures
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
# @overload mixin(mod, document = Graphics::Document::Base, &block)
|
|
8
|
+
# Includes a named mixture and optional anonymous extension into a document class.
|
|
9
|
+
# @param mod [Symbol, String] mixture constant name
|
|
10
|
+
# @param document [Class] document class receiving the mixture
|
|
11
|
+
# @return [Module, nil] optional anonymous mixture when a block is given
|
|
12
|
+
# @raise [NameError] when the mixture does not exist
|
|
13
|
+
# @overload mixin(document = Graphics::Document::Base, &block)
|
|
14
|
+
# Includes only an anonymous extension into a document class.
|
|
15
|
+
# @param document [Class] document class receiving the mixture
|
|
16
|
+
# @return [Module] anonymous mixture
|
|
17
|
+
# @raise [Sevgi::ArgumentError] when no named mixture or block is given
|
|
18
|
+
def self.mixin(mod = Undefined, document = Graphics::Document::Base, &block)
|
|
19
|
+
mod, document = normalize(mod, document, block)
|
|
20
|
+
ArgumentError.("Mixture name or block required") if mod == Undefined && !block
|
|
21
|
+
|
|
22
|
+
document.mixture(mod) unless mod == Undefined
|
|
23
|
+
include_anonymous(document, &block) if block
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.anonymous_document?(mod, block)
|
|
27
|
+
block && mod.is_a?(::Class) && mod <= Graphics::Document::Proto
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.include_anonymous(document, &block)
|
|
31
|
+
::Module.new(&block).tap do |anonymous|
|
|
32
|
+
document.send(:include, anonymous)
|
|
33
|
+
document.extend(anonymous.const_get(:ClassMethods)) if anonymous.const_defined?(:ClassMethods, false)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.normalize(mod, document, block)
|
|
38
|
+
anonymous_document?(mod, block) ? [Undefined, mod] : [mod, document]
|
|
9
39
|
end
|
|
40
|
+
|
|
41
|
+
private_class_method :anonymous_document?, :include_anonymous, :normalize
|
|
10
42
|
end
|
|
11
43
|
end
|
|
12
44
|
end
|
data/lib/sevgi/graphics.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "sevgi/function"
|
|
4
4
|
|
|
5
5
|
require_relative "graphics/attribute"
|
|
6
|
-
require_relative "graphics/
|
|
6
|
+
require_relative "graphics/auxiliary"
|
|
7
7
|
require_relative "graphics/element"
|
|
8
8
|
require_relative "graphics/mixtures"
|
|
9
9
|
|
|
@@ -12,23 +12,88 @@ require_relative "graphics/document"
|
|
|
12
12
|
require_relative "graphics/version"
|
|
13
13
|
|
|
14
14
|
module Sevgi
|
|
15
|
+
# SVG document builder and DSL namespace.
|
|
15
16
|
module Graphics
|
|
17
|
+
# @overload canvas(arg = Undefined, **kwargs)
|
|
18
|
+
# Builds a canvas from a paper profile or explicit size.
|
|
19
|
+
# @param arg [Sevgi::Graphics::Paper, Symbol, String, Sevgi::Undefined] paper profile or paper object
|
|
20
|
+
# @param kwargs [Hash] canvas keyword arguments
|
|
21
|
+
# @return [Sevgi::Graphics::Canvas]
|
|
22
|
+
# @raise [Sevgi::ArgumentError] when the paper profile is unknown
|
|
16
23
|
def canvas(...)
|
|
17
|
-
Graphics::Canvas.(...)
|
|
24
|
+
Graphics::Canvas.from_paper(...)
|
|
18
25
|
end
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
# @overload document(name)
|
|
28
|
+
# Looks up an existing document profile by name.
|
|
29
|
+
# @param name [Symbol, String] profile name
|
|
30
|
+
# @return [Class] document class
|
|
31
|
+
# @raise [Sevgi::ArgumentError] when the profile is unknown
|
|
32
|
+
# @overload document(name, preambles: Undefined, attributes: Undefined)
|
|
33
|
+
# Defines a named document profile, or returns an existing profile when every explicitly supplied field matches.
|
|
34
|
+
# Omitted fields are ignored during existing-profile comparison. Profile inputs are copied into the registry.
|
|
35
|
+
# @param name [Symbol, String] profile name
|
|
36
|
+
# @param preambles [Array<String>, nil, Sevgi::Undefined] document preamble lines
|
|
37
|
+
# @param attributes [Hash, Sevgi::Undefined] default root attributes
|
|
38
|
+
# @return [Class] document class
|
|
39
|
+
# @raise [Sevgi::ArgumentError] when a named profile conflicts with an existing profile
|
|
40
|
+
# @overload document(preambles: Undefined, attributes: Undefined)
|
|
41
|
+
# Defines an anonymous document profile without registering it globally.
|
|
42
|
+
# @param preambles [Array<String>, nil, Sevgi::Undefined] document preamble lines
|
|
43
|
+
# @param attributes [Hash, Sevgi::Undefined] default root attributes
|
|
44
|
+
# @return [Class] anonymous document class
|
|
45
|
+
# @raise [Sevgi::ArgumentError] when profile input is invalid
|
|
46
|
+
# @return [Class] document class
|
|
47
|
+
def document(name = Undefined, preambles: Undefined, attributes: Undefined)
|
|
48
|
+
Graphics::Document.define(name, preambles:, attributes:)
|
|
22
49
|
end
|
|
23
50
|
|
|
51
|
+
# Defines or replaces a document profile class.
|
|
52
|
+
# @param name [Symbol, String] profile name
|
|
53
|
+
# @param preambles [Array<String>, nil] document preamble lines
|
|
54
|
+
# @param attributes [Hash] default root attributes
|
|
55
|
+
# @return [Class] document class
|
|
56
|
+
# @raise [Sevgi::ArgumentError] when the profile name is invalid
|
|
57
|
+
def document!(name, preambles: [], attributes: {})
|
|
58
|
+
Graphics::Document.define(name, preambles:, attributes:, overwrite: true)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Defines a paper profile unless the same profile already exists.
|
|
62
|
+
# @param width [Numeric] paper width
|
|
63
|
+
# @param height [Numeric] paper height
|
|
64
|
+
# @param name [Symbol, String] profile name
|
|
65
|
+
# @param unit [Symbol, String] SVG unit
|
|
66
|
+
# @return [Symbol, String] original profile name
|
|
67
|
+
# @raise [Sevgi::ArgumentError] when the profile is invalid or an existing profile has different dimensions
|
|
24
68
|
def paper(width, height, name = :custom, unit: "mm")
|
|
25
|
-
|
|
69
|
+
profile = Graphics::Paper[width, height, unit, name]
|
|
70
|
+
|
|
71
|
+
if Graphics::Paper.exist?(name)
|
|
72
|
+
ArgumentError.("Paper already defined differently: #{name}") unless Graphics::Paper.public_send(name) == profile
|
|
73
|
+
else
|
|
74
|
+
Graphics::Paper.define(name, width:, height:, unit:)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
name
|
|
26
78
|
end
|
|
27
79
|
|
|
80
|
+
# Defines or replaces a paper profile.
|
|
81
|
+
# @param width [Numeric] paper width
|
|
82
|
+
# @param height [Numeric] paper height
|
|
83
|
+
# @param name [Symbol, String] profile name
|
|
84
|
+
# @param unit [Symbol, String] SVG unit
|
|
85
|
+
# @return [Symbol, String] original profile name
|
|
86
|
+
# @raise [Sevgi::ArgumentError] when the profile is invalid or the profile name is reserved
|
|
28
87
|
def paper!(width, height, name = :custom, unit: "mm")
|
|
29
88
|
name.tap { Graphics::Paper.define(name, width:, height:, unit:) }
|
|
30
89
|
end
|
|
31
90
|
|
|
91
|
+
# Builds an SVG root document.
|
|
92
|
+
# @param document [Symbol, String, Class] document profile name or document class
|
|
93
|
+
# @param canvas [Sevgi::Graphics::Canvas, Sevgi::Graphics::Paper, Symbol, String, Sevgi::Undefined, nil] canvas input
|
|
94
|
+
# @param block [Proc, nil] DSL block evaluated in the root element
|
|
95
|
+
# @return [Sevgi::Graphics::Document::Proto] SVG root element
|
|
96
|
+
# @raise [Sevgi::ArgumentError] when the document or canvas profile is unknown
|
|
32
97
|
def SVG(document = :default, canvas = Undefined, **, &block)
|
|
33
98
|
Graphics::Document.(document, canvas, **, &block)
|
|
34
99
|
end
|
|
@@ -36,5 +101,6 @@ module Sevgi
|
|
|
36
101
|
extend self
|
|
37
102
|
end
|
|
38
103
|
|
|
104
|
+
# Top-level alias for the graphics DSL namespace.
|
|
39
105
|
SVG = Graphics
|
|
40
106
|
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: 0.94.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Recai Oktaş
|
|
@@ -15,27 +15,30 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.94.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.
|
|
25
|
+
version: 0.94.0
|
|
26
26
|
description: Provides an extensible DSL for creating SVG content.
|
|
27
27
|
email: roktas@gmail.com
|
|
28
28
|
executables: []
|
|
29
29
|
extensions: []
|
|
30
30
|
extra_rdoc_files: []
|
|
31
31
|
files:
|
|
32
|
+
- CHANGELOG.md
|
|
33
|
+
- LICENSE
|
|
34
|
+
- README.md
|
|
32
35
|
- lib/sevgi/graphics.rb
|
|
33
36
|
- lib/sevgi/graphics/attribute.rb
|
|
34
|
-
- lib/sevgi/graphics/
|
|
35
|
-
- lib/sevgi/graphics/
|
|
36
|
-
- lib/sevgi/graphics/
|
|
37
|
-
- lib/sevgi/graphics/
|
|
38
|
-
- lib/sevgi/graphics/
|
|
37
|
+
- lib/sevgi/graphics/auxiliary.rb
|
|
38
|
+
- lib/sevgi/graphics/auxiliary/canvas.rb
|
|
39
|
+
- lib/sevgi/graphics/auxiliary/content.rb
|
|
40
|
+
- lib/sevgi/graphics/auxiliary/margin.rb
|
|
41
|
+
- lib/sevgi/graphics/auxiliary/paper.rb
|
|
39
42
|
- lib/sevgi/graphics/document.rb
|
|
40
43
|
- lib/sevgi/graphics/document/base.rb
|
|
41
44
|
- lib/sevgi/graphics/document/default.rb
|
|
@@ -79,7 +82,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
79
82
|
requirements:
|
|
80
83
|
- - ">="
|
|
81
84
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: 3.4.0
|
|
85
|
+
version: 3.4.0
|
|
83
86
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
87
|
requirements:
|
|
85
88
|
- - ">="
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "forwardable"
|
|
4
|
-
|
|
5
|
-
module Sevgi
|
|
6
|
-
module Graphics
|
|
7
|
-
class Canvas
|
|
8
|
-
def self.call(arg = Undefined, **kwargs)
|
|
9
|
-
case arg
|
|
10
|
-
when Undefined
|
|
11
|
-
new(**kwargs)
|
|
12
|
-
when ::Symbol, ::String
|
|
13
|
-
new(**Paper.public_send(arg).to_h, **kwargs)
|
|
14
|
-
else
|
|
15
|
-
ArgumentError.("Argument must be a Paper symbol: #{arg}")
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
extend Forwardable
|
|
20
|
-
|
|
21
|
-
def_delegators :@margin, *Margin.members
|
|
22
|
-
def_delegators :@size, *Paper.members
|
|
23
|
-
|
|
24
|
-
attr_reader :size, :margin, :inner
|
|
25
|
-
|
|
26
|
-
def initialize(width:, height:, unit: "mm", name: :custom, margins: [])
|
|
27
|
-
@size = Paper[width, height, unit, name]
|
|
28
|
-
@margin = Margin[*margins]
|
|
29
|
-
|
|
30
|
-
compute
|
|
31
|
-
freeze
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def attributes(...) = {**viewport, viewBox: viewbox(...)}
|
|
35
|
-
|
|
36
|
-
def conforming(...) = self.class.conforming(self, ...)
|
|
37
|
-
|
|
38
|
-
def viewport = {width: "#{width}#{unit}", height: "#{height}#{unit}"}
|
|
39
|
-
|
|
40
|
-
def viewbox(origin = Undefined) = prettify(*originate(origin), width, height).join(" ")
|
|
41
|
-
|
|
42
|
-
def with(**kwargs) = self.class.new(**size.to_h, margins: kwargs.fetch(:margins, margin.to_a))
|
|
43
|
-
|
|
44
|
-
private
|
|
45
|
-
|
|
46
|
-
def compute
|
|
47
|
-
@inner = size.with(width: width - margin.left - margin.right, height: height - margin.top - margin.bottom)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def originate(origin)
|
|
51
|
-
case origin
|
|
52
|
-
when Undefined
|
|
53
|
-
[-margin.left, -margin.top]
|
|
54
|
-
when ::Numeric, ::NilClass
|
|
55
|
-
[origin.to_f, origin.to_f]
|
|
56
|
-
when ::Array
|
|
57
|
-
pair(origin)
|
|
58
|
-
else
|
|
59
|
-
ArgumentError.("Canvas origin must be an Array")
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def prettify(*floats)
|
|
64
|
-
floats.map { (it % 1).zero? ? it.to_i : it }
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def pair(array)
|
|
68
|
-
array.size == 2 ? array.map(&:to_f) : ArgumentError.("Argument must be an Array of size 2")
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|