sevgi-graphics 1.0.0 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7965ed7aa4c5960cca6a4ed927258d13a5b5be7d9e327f4bf7da5b3480b66b1f
4
- data.tar.gz: 88224f9179c50ceca0ae8e4ee7a92e5d8a6a0d2634ec3912356dc5e5d2d27053
3
+ metadata.gz: 9e205e418f5e60262a7e2cea0c4d32879942f7b238ea5fbf07bbbc01be5a9bbf
4
+ data.tar.gz: 889bb3fc5e72a27efba882624c3af23681a2811866d6c165f344edbb79173db7
5
5
  SHA512:
6
- metadata.gz: 063d2529fc5869ef25f27ed44b927d28ae3ba6c48aca2365c0be78dbda65b895eb065b0c61ca960e66f955e0a8dbea8cc7b581ddf9ad4ec437c727e0541b9e43
7
- data.tar.gz: fa1a68f7962e62bd991462e87bbbae0778321076af509ca2ede6b179a5eb1c133d707c3450d46688645ddf37050df49c63df608236fc1cfb3bcc811d20ff25a5
6
+ metadata.gz: 67bcec199b5f5a1e607a316da7dd618792e07df4b9db5bdd86104373ac1772324eb9f1b268e3ae1ce28db413bcefb68db4d531e82098ffa112c9da998de9e9dd
7
+ data.tar.gz: ad2787c76d899b961dc73da906dcfb57ed38f94fc2f317c8ce0c0692ebbdd181260b1e97ee02efa871350ba9239ba53341c755919a02dd1860b8d6431357c6fb
data/CHANGELOG.md CHANGED
@@ -3,6 +3,26 @@
3
3
  This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
+ ## Unreleased
7
+
8
+ ## 1.1.0 - 2026-10-29
9
+
10
+ ### Added
11
+
12
+ - `Classes` returns an element's distinct CSS class names without changing the element; `Classify` shares its attribute normalization.
13
+ - Generic document-local Style declarations with `Target`, `Param`, `Group`, and `Profiles`, and optional prepared PDF export through the native Mainz CLI.
14
+ - Backend-independent `fallback: false` for export failures and `F.executable` for executable path lookup.
15
+ - `F.path` for path validation and expansion, and `F.output_path` for default output names and directory targets.
16
+
17
+ ### Changed
18
+
19
+ - Moved Graphics XML validation into auxiliary helpers and kept built-in paper dimensions in `Paper`.
20
+ - Shared path validation between Graphics writers and Sundries exports while preserving output paths and error messages.
21
+
22
+ ### Fixed
23
+
24
+ - Stamp code placeholders in nested and shared PDF Forms, with cycle-safe resource traversal.
25
+
6
26
  ## 1.0.0 - 2026-09-16
7
27
 
8
28
  ### Added
data/README.md CHANGED
@@ -24,6 +24,57 @@ puts doc.Render
24
24
  This focused gem exposes `Sevgi::Graphics.SVG` and lowercase component constructors. Install the umbrella `sevgi` gem
25
25
  when you need the global `SVG` facade or the `.sevgi` script runner.
26
26
 
27
+ ## Editable PDF styles
28
+
29
+ `Style` attaches an immutable producer declaration to an SVG document.
30
+ It does not change the SVG or load a PDF backend.
31
+ The lowercase `style` method still creates an SVG CSS element.
32
+
33
+ ```ruby
34
+ doc = Sevgi::Graphics.SVG width: 20, height: 20 do
35
+ g id: "outline" do
36
+ rect width: 10, height: 10, fill: "none", stroke: "black"
37
+ end
38
+ Style do
39
+ Target "ink", selectors: ["#outline"]
40
+ Param "display", targets: ["ink"], property: "display",
41
+ schema: {type: "string", enum: %w[inline none]}
42
+ Param "opacity", targets: ["ink"], property: "stroke-opacity",
43
+ schema: {type: "number", minimum: 0, maximum: 1}
44
+ Group "Ink", parameters: ["opacity"]
45
+ Profiles "print", name: "Printer", parameters: ["opacity"], profiles: [
46
+ {id: "original", name: "Original", overrides: {}},
47
+ {id: "light", name: "Light", overrides: {opacity: 0.4}}
48
+ ]
49
+ end
50
+ end
51
+
52
+ doc.PDF "drawing.pdf"
53
+ doc.PDF "prepared.pdf", fallback: false
54
+ ```
55
+
56
+ `PDF` uses Mainz automatically when the document has a Style declaration.
57
+ If Mainz is unavailable or preparation fails, the default mode warns and exports an ordinary PDF.
58
+ `fallback: false` raises `Sevgi::Sundries::Mainz::Error` instead.
59
+ An unstyled PDF uses ordinary export without a Mainz warning.
60
+ PNG and SVG output retain their ordinary behavior.
61
+
62
+ `Target` selects whole SVG groups or direct `use` placements with `#id` or `#id > use.class` selectors.
63
+ Its optional `fill: true` allows eligible closed paths to acquire a fill.
64
+ `Param` binds one supported paint property to targets and supplies its value schema.
65
+ `Group` orders parameter references. `Profiles` records named sets of overrides.
66
+ Every target requires a `display` parameter.
67
+ `Param` accepts an optional `value:` to assign an override after Mainz captures original paint.
68
+ For an authored hidden target, `Target` accepts `display: "none"`.
69
+ Mainz owns selector resolution, supported properties, binding admission, and PDF baselines.
70
+ The [declaration contract](https://github.com/roktas/mainz/blob/v1.0.0/lib/doc/SPEC.md#71-svg-declarations) defines those fields.
71
+
72
+ `doc.Style` returns the declaration as deeply frozen JSON data with String keys.
73
+ `doc.Style declaration_hash` accepts the same fields without a builder block.
74
+ Applications supply their own parameter names, schemas, profiles, and publication requirements.
75
+ Separate documents have separate declarations. Document copies safely share an immutable declaration.
76
+ Invalid Ruby declarations raise `Sevgi::ArgumentError` before export.
77
+
27
78
  ## Ruby compatibility
28
79
 
29
80
  Requires Ruby 3.4.0 or newer. CI verifies the current Ruby 3.4 release and the development Ruby from `.ruby-version`.
@@ -166,6 +166,13 @@ module Sevgi
166
166
  XML.text(text, context: "XML attribute value")
167
167
  end
168
168
 
169
+ # Returns distinct class names in rendered order from nested attribute values.
170
+ # @param values [Array<Object>] class values to combine
171
+ # @return [Array<String>] owned class-name snapshot
172
+ # @raise [Sevgi::ArgumentError] when a value is invalid, cyclic, or cannot be stringified as XML
173
+ # @api private
174
+ def self.classes(*values) = xml_text(values).split.uniq
175
+
169
176
  # Mutable backing store for SVG attributes.
170
177
  # @api private
171
178
  class Store
@@ -180,7 +180,58 @@ module Sevgi
180
180
 
181
181
  @reserved = methods.map(&:to_sym).freeze
182
182
 
183
- PAPER_SIZES.each { |name, (width, height, unit)| define(name, width:, height:, unit:) }
183
+ {
184
+ a0: [841, 1189, "mm"],
185
+ a1: [594, 841, "mm"],
186
+ a2: [420, 594, "mm"],
187
+ a3: [297, 420, "mm"],
188
+ a4: [210, 297, "mm"],
189
+ a5: [148, 210, "mm"],
190
+ a6: [105, 148, "mm"],
191
+ a7: [74, 105, "mm"],
192
+ a8: [52, 74, "mm"],
193
+ a9: [37, 52, "mm"],
194
+ a10: [26, 37, "mm"],
195
+
196
+ b0: [1000, 1414, "mm"],
197
+ b1: [707, 1000, "mm"],
198
+ b2: [500, 707, "mm"],
199
+ b3: [353, 500, "mm"],
200
+ b4: [250, 353, "mm"],
201
+ b5: [176, 250, "mm"],
202
+ b6: [125, 176, "mm"],
203
+ b7: [88, 125, "mm"],
204
+ b8: [62, 88, "mm"],
205
+ b9: [44, 62, "mm"],
206
+ b10: [31, 44, "mm"],
207
+
208
+ c0: [917, 1297, "mm"],
209
+ c1: [648, 917, "mm"],
210
+ c2: [458, 648, "mm"],
211
+ c3: [324, 458, "mm"],
212
+ c4: [229, 324, "mm"],
213
+ c5: [162, 229, "mm"],
214
+ c6: [114, 162, "mm"],
215
+ c7: [81, 114, "mm"],
216
+ c8: [57, 81, "mm"],
217
+ c9: [40, 57, "mm"],
218
+ c10: [28, 40, "mm"],
219
+
220
+ business: [85, 55, "mm"],
221
+ large: [130, 210, "mm"],
222
+ passport: [88, 125, "mm"],
223
+ pocket: [90, 140, "mm"],
224
+ travelers: [110, 210, "mm"],
225
+ us: [216, 279, "mm"],
226
+ xlarge: [190, 250, "mm"],
227
+
228
+ icon16: [16, 16, "px"],
229
+ icon32: [32, 32, "px"],
230
+ icon64: [64, 64, "px"],
231
+ icon128: [128, 128, "px"],
232
+ icon256: [256, 256, "px"],
233
+ icon512: [512, 512, "px"]
234
+ }.each { |name, (width, height, unit)| define(name, width:, height:, unit:) }
184
235
 
185
236
  # Returns the default paper profile.
186
237
  # @return [Sevgi::Graphics::Paper]
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "auxiliary/xml"
3
4
  require_relative "auxiliary/scalar"
4
- require_relative "auxiliary/path"
5
5
  require_relative "auxiliary/margin"
6
- require_relative "auxiliary/sizes"
7
6
  require_relative "auxiliary/paper"
8
7
 
9
8
  require_relative "auxiliary/canvas"
@@ -16,6 +16,7 @@ module Sevgi
16
16
  mixture :Include
17
17
  mixture :Lint
18
18
  mixture :Save
19
+ mixture :Style
19
20
  mixture :Tile
20
21
  mixture :Transform
21
22
  mixture :Underscore
@@ -93,24 +93,19 @@ module Sevgi
93
93
  tap { Adoption.batch(elements, self, front: false) }
94
94
  end
95
95
 
96
+ # Returns distinct CSS class names without changing this element.
97
+ # An absent class attribute returns an empty Array. Strings are split on whitespace and nested Arrays are flattened.
98
+ # @return [Array<String>] owned class-name snapshot in rendered order
99
+ # @see #Classify
100
+ def Classes = Attribute.classes(self[:class])
101
+
96
102
  # Adds CSS classes without duplicating existing values.
97
103
  # @param classes [Array<String, Symbol, Array>] class tokens. Strings are split on whitespace
98
104
  # @return [Sevgi::Graphics::Element] self
105
+ # @raise [Sevgi::ArgumentError] when a class value is invalid, cyclic, or cannot be stringified as XML
106
+ # @see #Classes
99
107
  def Classify(*classes)
100
- tap do
101
- tokens = proc do |value|
102
- case value
103
- when nil
104
- []
105
- when ::Array
106
- value.flat_map { tokens.call(it) }
107
- else
108
- value.to_s.split
109
- end
110
- end
111
-
112
- self[:class] = [*tokens.call(self[:class]), *tokens.call(classes)].uniq
113
- end
108
+ tap { self[:class] = Attribute.classes(self[:class], classes) }
114
109
  end
115
110
 
116
111
  # Assigns default attributes only when they are absent.
@@ -10,7 +10,7 @@ module Sevgi
10
10
  # Validates the closed export option channel before optional components load or rendering starts.
11
11
  # @api private
12
12
  module Options
13
- KEYS = %i[css default dpi height width].freeze
13
+ KEYS = %i[css default dpi fallback height style width].freeze
14
14
  private_constant :KEYS
15
15
 
16
16
  # Returns validated export options.
@@ -36,6 +36,8 @@ module Sevgi
36
36
  # @option kwargs [Numeric, nil] :height finite positive target height in CSS pixels
37
37
  # @option kwargs [Numeric] :dpi (96.0) finite positive CSS pixel density
38
38
  # @option kwargs [String, nil] :css CSS inserted before rendering
39
+ # @option kwargs [Hash, nil] :style (document Style) producer declaration
40
+ # @option kwargs [Boolean] :fallback (true) warn and use ordinary PDF after preparation failure
39
41
  # @yield [svg] transforms SVG source before rendering
40
42
  # @yieldparam svg [String] rendered SVG source
41
43
  # @yieldreturn [String] transformed SVG source
@@ -46,6 +48,7 @@ module Sevgi
46
48
  # @raise [Sevgi::MissingComponentError] when sevgi/sundries is unavailable
47
49
  # @raise [Sevgi::MissingComponentError] when native export gems are unavailable
48
50
  # @raise [Sevgi::Sundries::Export::ExportError] when native export fails
51
+ # @raise [Sevgi::Sundries::Mainz::Error] when required preparation cannot complete
49
52
  # @raise [SystemCallError] when the output directory or file cannot be created or written
50
53
  def PDF(path = nil, **kwargs, &block)
51
54
  kwargs = Options.(kwargs)
@@ -58,6 +61,7 @@ module Sevgi
58
61
  MissingComponentError.("sevgi/sundries")
59
62
  end
60
63
 
64
+ kwargs[:style] = self.Style unless kwargs.key?(:style) || self.Style.nil?
61
65
  Export(path, **kwargs, format: :pdf, &block)
62
66
  end
63
67
 
@@ -104,7 +108,7 @@ module Sevgi
104
108
  default = F.subext(extension, caller_locations(2..2).first.path)
105
109
  end
106
110
 
107
- path = Path.resolve(path, default:, context: "Export")
111
+ path = F.output_path(path, default:, context: "Export")
108
112
 
109
113
  Sundries::Export.(call, path, **kwargs, &block)
110
114
  end
@@ -74,7 +74,7 @@ module Sevgi
74
74
  # @see Sevgi::Graphics::Document::Proto#call
75
75
  def Save(path = nil, default: nil, backup_suffix: nil, **kwargs, &filter)
76
76
  default = F.subext(EXT, caller_locations(1..1).first.path) if default.nil?
77
- path = Path.resolve(path, default:, context: "Save")
77
+ path = F.output_path(path, default:, context: "Save")
78
78
 
79
79
  Writer.(path, self.(**kwargs), backup_suffix:, &filter)
80
80
  end
@@ -94,7 +94,7 @@ module Sevgi
94
94
  # @raise [SystemCallError] when the destination cannot be read or written
95
95
  # @see Sevgi::Graphics::Document::Proto#call
96
96
  def Write(path, **kwargs, &filter)
97
- path = Path.(path, context: "Write path")
97
+ path = F.path(path, context: "Write path")
98
98
  ArgumentError.("Write path must name a file") if ::File.directory?(path)
99
99
 
100
100
  Writer.(path, self.(**kwargs), &filter)
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Sevgi
6
+ module Graphics
7
+ module Mixtures
8
+ module Style
9
+ # Immutable authoring declaration for editable PDF paint properties.
10
+ # Source selectors stay here; the producer resolves them to PDF targets and baselines.
11
+ # @api private
12
+ class Declaration
13
+ # Captures a declaration without changing the SVG or loading a PDF backend.
14
+ # @param definition [Hash] targets, parameters, optional groups and profileSets
15
+ # @yield evaluates authoring operations in a private builder
16
+ # @yieldreturn [Object] ignored block result
17
+ # @return [void]
18
+ # @raise [Sevgi::ArgumentError] when the declaration is not finite, acyclic JSON data
19
+ def initialize(definition = {}, &block)
20
+ ArgumentError.("Style declaration must be a Hash") unless definition.is_a?(::Hash)
21
+ ArgumentError.("Style accepts a declaration or a block") if block && !definition.empty?
22
+
23
+ definition = Builder.new.call(&block) if block
24
+ @definition = Snapshot.capture(definition)
25
+ validate
26
+ freeze
27
+ end
28
+
29
+ # Returns the owned, deeply frozen declaration with JSON String keys.
30
+ # @return [Hash{String => Object}] producer authoring declaration
31
+ def to_h = @definition
32
+
33
+ private
34
+
35
+ def validate
36
+ unknown = @definition.keys - %w[targets parameters groups profileSets]
37
+ ArgumentError.("Unknown Style field: #{unknown.first}") unless unknown.empty?
38
+ return if %w[targets parameters].all? { @definition[it].is_a?(::Hash) }
39
+ ArgumentError.("Style requires targets and parameters")
40
+ end
41
+
42
+ # Authoring operations kept outside the SVG method namespace.
43
+ # @api private
44
+ class Builder
45
+ def initialize
46
+ @definition = {targets: {}, parameters: {}, groups: [], profileSets: []}
47
+ end
48
+
49
+ def call(&block)
50
+ instance_exec(&block)
51
+ @definition
52
+ end
53
+
54
+ def Target(id, selectors:, fill: false, display: Undefined)
55
+ record = {selectors:, fill:}
56
+ record[:display] = display unless display.equal?(Undefined)
57
+ add(:targets, id, record)
58
+ end
59
+
60
+ def Param(id, targets:, property:, schema:, value: Undefined)
61
+ record = {schema:, binding: {targets:, property:}}
62
+ record[:value] = value unless value.equal?(Undefined)
63
+ add(:parameters, id, record)
64
+ end
65
+
66
+ def Group(title, parameters:, description: nil)
67
+ record = {title:, parameters:}
68
+ record[:description] = description unless description.nil?
69
+ @definition[:groups] << record
70
+ end
71
+
72
+ def Profiles(id, name:, parameters:, profiles:, description: nil)
73
+ record = {id:, name:, parameters:, profiles:}
74
+ record[:description] = description unless description.nil?
75
+ @definition[:profileSets] << record
76
+ end
77
+
78
+ private
79
+
80
+ def add(field, id, value)
81
+ unless (id.is_a?(::String) || id.is_a?(::Symbol)) && !id.to_s.empty?
82
+ ArgumentError.("Style id must be nonempty text")
83
+ end
84
+ id = id.to_s
85
+ ArgumentError.("Duplicate Style id: #{id}") if @definition[field].key?(id)
86
+ @definition[field][id] = value
87
+ end
88
+ end
89
+
90
+ # JSON ownership rules differ from XML profile metadata.
91
+ # @api private
92
+ module Snapshot
93
+ def self.capture(value)
94
+ json = JSON.generate(value, strict: true, max_nesting: 32)
95
+ freeze_tree(JSON.parse(json, max_nesting: 32))
96
+ rescue JSON::JSONError => e
97
+ ArgumentError.("Invalid Style JSON: #{e.message}")
98
+ end
99
+
100
+ def self.freeze_tree(value)
101
+ case value
102
+ when ::Hash then value.each_value { freeze_tree(it) }
103
+ when ::Array then value.each { freeze_tree(it) }
104
+ end
105
+ value.freeze
106
+ end
107
+
108
+ private_class_method :freeze_tree
109
+ end
110
+
111
+ private_constant :Builder, :Snapshot
112
+ end
113
+ private_constant :Declaration
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "style/declaration"
4
+
5
+ module Sevgi
6
+ module Graphics
7
+ module Mixtures
8
+ # Document-local declarations for editable PDF export.
9
+ module Style
10
+ # Reads or replaces this document's Style declaration.
11
+ # A declaration changes export metadata, not rendered SVG appearance.
12
+ # Ordinary document copies safely share the immutable declaration.
13
+ # @param definition [Hash, Sevgi::Undefined] declaration, or omitted for a read
14
+ # @yield defines Target, Param, Group, and Profiles records
15
+ # @yieldreturn [Object] ignored block result
16
+ # @return [Hash, nil, Sevgi::Graphics::Element] frozen JSON declaration on read, self on assignment
17
+ # @raise [Sevgi::ArgumentError] when called outside a document root or with an invalid declaration
18
+ # @example Declare an editable outline
19
+ # drawing.Style do
20
+ # Target "ink", selectors: ["#outline"]
21
+ # Param "display", targets: ["ink"], property: "display",
22
+ # schema: {type: "string", enum: %w[inline none]}
23
+ # Param "opacity", targets: ["ink"], property: "stroke-opacity",
24
+ # schema: {type: "number", minimum: 0, maximum: 1}
25
+ # end
26
+ def Style(definition = Undefined, &block)
27
+ ArgumentError.("Style belongs to a document root") unless Root?()
28
+ return @style&.to_h if definition.equal?(Undefined) && !block
29
+ definition = {} if definition.equal?(Undefined)
30
+ @style = Declaration.new(definition, &block)
31
+ self
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -63,6 +63,7 @@ require_relative "mixtures/polyfills"
63
63
  require_relative "mixtures/rdf"
64
64
  require_relative "mixtures/render"
65
65
  require_relative "mixtures/save"
66
+ require_relative "mixtures/style"
66
67
  require_relative "mixtures/symbols"
67
68
  require_relative "mixtures/tile"
68
69
  require_relative "mixtures/transform"
@@ -3,6 +3,6 @@
3
3
  module Sevgi
4
4
  module Graphics
5
5
  # Current graphics component version.
6
- VERSION = "1.0.0"
6
+ VERSION = "1.1.0"
7
7
  end
8
8
  end
@@ -2,9 +2,8 @@
2
2
 
3
3
  require "sevgi/function"
4
4
 
5
- require_relative "graphics/xml"
6
- require_relative "graphics/attribute"
7
5
  require_relative "graphics/auxiliary"
6
+ require_relative "graphics/attribute"
8
7
  require_relative "graphics/element"
9
8
  require_relative "graphics/mixtures"
10
9
 
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: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Recai Oktaş
@@ -9,20 +9,34 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: json
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '3.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '3.0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: sevgi-function
14
28
  requirement: !ruby/object:Gem::Requirement
15
29
  requirements:
16
30
  - - '='
17
31
  - !ruby/object:Gem::Version
18
- version: 1.0.0
32
+ version: 1.1.0
19
33
  type: :runtime
20
34
  prerelease: false
21
35
  version_requirements: !ruby/object:Gem::Requirement
22
36
  requirements:
23
37
  - - '='
24
38
  - !ruby/object:Gem::Version
25
- version: 1.0.0
39
+ version: 1.1.0
26
40
  description: Builds and renders SVG document trees from Ruby.
27
41
  email: roktas@gmail.com
28
42
  executables: []
@@ -39,9 +53,8 @@ files:
39
53
  - lib/sevgi/graphics/auxiliary/content.rb
40
54
  - lib/sevgi/graphics/auxiliary/margin.rb
41
55
  - lib/sevgi/graphics/auxiliary/paper.rb
42
- - lib/sevgi/graphics/auxiliary/path.rb
43
56
  - lib/sevgi/graphics/auxiliary/scalar.rb
44
- - lib/sevgi/graphics/auxiliary/sizes.rb
57
+ - lib/sevgi/graphics/auxiliary/xml.rb
45
58
  - lib/sevgi/graphics/document.rb
46
59
  - lib/sevgi/graphics/document/base.rb
47
60
  - lib/sevgi/graphics/document/default.rb
@@ -63,6 +76,8 @@ files:
63
76
  - lib/sevgi/graphics/mixtures/rdf.rb
64
77
  - lib/sevgi/graphics/mixtures/render.rb
65
78
  - lib/sevgi/graphics/mixtures/save.rb
79
+ - lib/sevgi/graphics/mixtures/style.rb
80
+ - lib/sevgi/graphics/mixtures/style/declaration.rb
66
81
  - lib/sevgi/graphics/mixtures/symbols.rb
67
82
  - lib/sevgi/graphics/mixtures/tile.rb
68
83
  - lib/sevgi/graphics/mixtures/transform.rb
@@ -70,7 +85,6 @@ files:
70
85
  - lib/sevgi/graphics/mixtures/validate.rb
71
86
  - lib/sevgi/graphics/mixtures/wrappers.rb
72
87
  - lib/sevgi/graphics/version.rb
73
- - lib/sevgi/graphics/xml.rb
74
88
  homepage: https://sevgi.roktas.dev
75
89
  licenses:
76
90
  - GPL-3.0-or-later
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Sevgi
4
- module Graphics
5
- # Output path normalization shared by Graphics writers and exporters.
6
- # @api private
7
- module Path
8
- # Converts a non-blank path to an expanded String.
9
- # @param value [String, #to_path] raw path value
10
- # @param context [String] public operation named in errors
11
- # @return [String] expanded path
12
- # @raise [Sevgi::ArgumentError] when value is blank, has an invalid type, or path conversion fails
13
- def self.call(value, context:)
14
- path = value.respond_to?(:to_path) ? value.to_path : value
15
- ArgumentError.("#{context} must be a String or path-like object") unless path.is_a?(::String)
16
- ArgumentError.("#{context} must be provided") if path.strip.empty?
17
-
18
- ::File.expand_path(path)
19
- rescue ::Sevgi::ArgumentError
20
- raise
21
- rescue ::StandardError => e
22
- ArgumentError.("#{context} must be a String or path-like object: #{e.message}")
23
- end
24
-
25
- # Resolves an optional path with the writer/exporter directory convention.
26
- # @param value [String, #to_path, nil] explicit path or directory
27
- # @param default [String, #to_path] default output path
28
- # @param context [String] public operation named in errors
29
- # @return [String] expanded output file path
30
- # @raise [Sevgi::ArgumentError] when a selected path/default is blank, invalid, or cannot be converted
31
- def self.resolve(value, default:, context:)
32
- return call(default, context: "#{context} default") if value.nil?
33
-
34
- path = call(value, context: "#{context} path")
35
- return path unless ::File.directory?(path)
36
-
37
- default = call(default, context: "#{context} default")
38
- ::File.join(path, ::File.basename(default))
39
- end
40
- end
41
-
42
- private_constant :Path
43
- end
44
- end
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Sevgi
4
- module Graphics
5
- # Built-in paper dimensions and units.
6
- # @api private
7
- PAPER_SIZES = {
8
- a0: [841, 1189, "mm"],
9
- a1: [594, 841, "mm"],
10
- a2: [420, 594, "mm"],
11
- a3: [297, 420, "mm"],
12
- a4: [210, 297, "mm"],
13
- a5: [148, 210, "mm"],
14
- a6: [105, 148, "mm"],
15
- a7: [74, 105, "mm"],
16
- a8: [52, 74, "mm"],
17
- a9: [37, 52, "mm"],
18
- a10: [26, 37, "mm"],
19
-
20
- b0: [1000, 1414, "mm"],
21
- b1: [707, 1000, "mm"],
22
- b2: [500, 707, "mm"],
23
- b3: [353, 500, "mm"],
24
- b4: [250, 353, "mm"],
25
- b5: [176, 250, "mm"],
26
- b6: [125, 176, "mm"],
27
- b7: [88, 125, "mm"],
28
- b8: [62, 88, "mm"],
29
- b9: [44, 62, "mm"],
30
- b10: [31, 44, "mm"],
31
-
32
- c0: [917, 1297, "mm"],
33
- c1: [648, 917, "mm"],
34
- c2: [458, 648, "mm"],
35
- c3: [324, 458, "mm"],
36
- c4: [229, 324, "mm"],
37
- c5: [162, 229, "mm"],
38
- c6: [114, 162, "mm"],
39
- c7: [81, 114, "mm"],
40
- c8: [57, 81, "mm"],
41
- c9: [40, 57, "mm"],
42
- c10: [28, 40, "mm"],
43
-
44
- business: [85, 55, "mm"],
45
- large: [130, 210, "mm"],
46
- passport: [88, 125, "mm"],
47
- pocket: [90, 140, "mm"],
48
- travelers: [110, 210, "mm"],
49
- us: [216, 279, "mm"],
50
- xlarge: [190, 250, "mm"],
51
-
52
- icon16: [16, 16, "px"],
53
- icon32: [32, 32, "px"],
54
- icon64: [64, 64, "px"],
55
- icon128: [128, 128, "px"],
56
- icon256: [256, 256, "px"],
57
- icon512: [512, 512, "px"]
58
- }.transform_values(&:freeze).freeze
59
-
60
- private_constant :PAPER_SIZES
61
- end
62
- end
File without changes