sevgi-sundries 0.98.2 → 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 +96 -2
- data/README.md +9 -3
- data/lib/sevgi/sundries/export/native.rb +40 -44
- data/lib/sevgi/sundries/export/system.rb +4 -2
- data/lib/sevgi/sundries/export.rb +17 -8
- data/lib/sevgi/sundries/grid.rb +12 -12
- data/lib/sevgi/sundries/ruler.rb +21 -7
- data/lib/sevgi/sundries/tile.rb +49 -14
- data/lib/sevgi/sundries/version.rb +1 -1
- data/lib/sevgi/sundries.rb +4 -3
- metadata +8 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 20ba96c5308412cca2773c3678698968da83f06814ccdcd9b2d64c9ee522312c
|
|
4
|
+
data.tar.gz: 52b7e80ff6e5828ca83359eeeca6e593440f1dcd74943142987a1a7f149a5a95
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a899ba87fcb53cd0652e27b587ecfe2ec5d8b184e23c27f114ae08b001aa30b3f9d06497ff921738c8fe7482e212aed5bd0cef71f9e221e9169e855c43faa99
|
|
7
|
+
data.tar.gz: 218c824c2748b155e8d2005df3e8176a6de4b9e80780cba56659ddac4987481d42c23e34b23b4c040d5f5a68d946436ae91640f43d68b94d3f5f841afd9273e8
|
data/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,101 @@
|
|
|
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
|
-
##
|
|
6
|
+
## 1.0.0 - 2026-09-16
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Added Hatch and Orb Showcase examples and displayed both arc lengths in the Arc example.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Matched documentation page widths to the homepage Showcase.
|
|
15
|
+
- Aligned Derender user guidance with UTF-8 normalization and safe subtree selection rules.
|
|
16
|
+
- Clarified the packaged skill's script invocation, subtree copying, and Derender selection and encoding rules.
|
|
17
|
+
- Allowed Canvas and Paper values as the first `SVG` argument when using the default document profile.
|
|
18
|
+
- Made element `dup` and `clone` own independent subtrees while retaining IDs and Ruby copy semantics.
|
|
19
|
+
- Made Segment equality compare length and direction; ordering still compares length only.
|
|
20
|
+
- Limited Tile cell, row, and column queries to the requested geometry and accelerated XML character validation.
|
|
21
|
+
- Simplified the first-run guide and aligned library constructor examples.
|
|
22
|
+
- Updated the packaged skill for canvas origins, alignment, Paper lookup, curved geometry, and export constraints.
|
|
23
|
+
- Revised DSL catalog examples and packaged Sevgi skill guidance to use statement-style calls and show inspection results.
|
|
24
|
+
- Refined Showcase example tabs, made Ruby and XML views fill their panels, and hid executable preambles from Ruby views.
|
|
25
|
+
- Documented non-rendering element data and clarified the related DSL catalog examples.
|
|
26
|
+
- Refined Showcase heading spacing, page-title colors, and DSL catalog cards across light and dark themes.
|
|
27
|
+
- Removed an obsolete ruler example and unused site code.
|
|
28
|
+
- Kept the Squared and Copperplate guidesheet examples together at the end of the Showcase.
|
|
29
|
+
- Updated the packaged skill to avoid duplicate semantic shapes and unnecessary paint passes.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- Kept horizontal scrollbars visible in Showcase example code panels.
|
|
34
|
+
- Read Derender input in its declared XML encoding and normalized generated source, XML declarations, and markup to UTF-8.
|
|
35
|
+
- Preserved open-path endpoints and derived edges independently of display precision.
|
|
36
|
+
- Kept near-axis and near-parallel line equations and intersections independent of angle and slope rounding.
|
|
37
|
+
- Corrected the Showcase social image dimensions.
|
|
38
|
+
- Added SVG namespaces to standalone drawing examples and regenerated their outputs.
|
|
39
|
+
- Made failed Sevgi Rake tasks stop dependent tasks and captured required-library failures in executor results.
|
|
40
|
+
- Removed executor signal-handler replacement so the host and shell retain SIGINT ownership.
|
|
41
|
+
- Escaped late export CSS as XML text and inserted it only at a supported final root closing tag.
|
|
42
|
+
- Preserved Derender processing instructions and rejected custom entity references and unsupported trailing document nodes before inclusion.
|
|
43
|
+
- Included both box origins in `Align`, unified Tile index validation, and isolated custom numeric attribute snapshots.
|
|
44
|
+
- Prevented avoidable overflow and underflow in point distances.
|
|
45
|
+
- Corrected browser source-panel expectations and added executable documentation, YARD, and packaged-skill checks.
|
|
46
|
+
- Rendered inline code in DSL catalog summaries and kept linked task themes visible below the sticky header.
|
|
47
|
+
- Restored Showcase tab icons with embedded Font Awesome SVGs.
|
|
48
|
+
|
|
49
|
+
## 0.100.0 - 2026-09-09
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- Added `ArcTo` and `ArcBy` for SVG-native elliptical arc paths.
|
|
54
|
+
- Added Geometry `Arc`, `Ellipse`, and `Circle` with bounds, length, predicates, affine transformations, and
|
|
55
|
+
linear/quadratic intersections. Closed ellipses and circles support sweeps and hatching.
|
|
56
|
+
- Added Geometry `Point.midpoint`, `Element#closed?`, lined `vertices`, open-path traversal, and `Rect#center`.
|
|
57
|
+
- Added Geometry aggregate bounding boxes through `Operation.box`, point collinearity through `Point.collinear?`, and
|
|
58
|
+
polygon `simple?`, `convex?`, and `concave?` predicates.
|
|
59
|
+
- Added Protractor and Arc examples and documented the drawing and calculation APIs.
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- Removed redundant parent scans while duplicating element subtrees.
|
|
64
|
+
- Reused source bounds across Tile cells to reduce repeated calculations and object allocations.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- Preserved Showcase card HTML through Markdown rendering so all examples and their tabs remain visible.
|
|
69
|
+
- Preserved XML declarations, pre-root comments, CDATA sections, inline comments, and namespace-only leaves in
|
|
70
|
+
Derender output and evaluation.
|
|
71
|
+
- Limited PDF stamp recoloring to replaced text and carried graphics state across a page's content streams.
|
|
72
|
+
- Corrected decimal interval fitting and made Grid point queries honor the active numeric precision.
|
|
73
|
+
- Prevented implicit shell interpretation of single executable arguments in `F.sh` and `F.sh!`, including paths with spaces or shell metacharacters.
|
|
74
|
+
- Preserved inherited namespaces on detached Derender nodes in generated source and direct evaluation, including imports through `EvaluateChildren` and `IncludeChildren`.
|
|
75
|
+
- Prevented sweep and Hatch lines from crossing the exterior between two tangent vertices of a concave polygon.
|
|
76
|
+
- Aligned Tile cells with their declared bounds for circles, rotated shapes, and polygons whose first vertex differs from their bounding-box origin.
|
|
77
|
+
- Limited `Load` chains to 128 active sources with `Executor::LoadDepthError`, preserving cycle detection and nested load diagnostics.
|
|
78
|
+
- Rejected nil and false aggregate-box inputs with the documented geometry error.
|
|
79
|
+
- Restored precision-aware rejection of zero-length Triangle sides.
|
|
80
|
+
- Made point-set collinearity independent of input order and removed eager polygon edge-pair storage.
|
|
81
|
+
- Applied the collinearity tolerance to every three-point subset, removing coordinate-axis-dependent baseline selection.
|
|
82
|
+
|
|
83
|
+
## 0.99.0 - 2026-08-09
|
|
84
|
+
|
|
85
|
+
### Added
|
|
86
|
+
|
|
87
|
+
- Added `SVG.Module` for building anonymous callable drawing modules with their contract installed before definition.
|
|
88
|
+
- Added responsive Mermaid diagrams for the Derender round trip and SVG validation lifecycle.
|
|
89
|
+
|
|
90
|
+
### Changed
|
|
91
|
+
|
|
92
|
+
- Reorganized the documentation around getting started, usage, composition, documents, layout, output, and examples.
|
|
93
|
+
- Documented how the `igves` and `igsev` command names reflect their conversion directions.
|
|
94
|
+
- Normalized DSL catalog provider labels to lowercase and simplified polyfill-backed editor entries to `inkscape`.
|
|
95
|
+
|
|
96
|
+
### Fixed
|
|
97
|
+
|
|
98
|
+
- Allowed A5-on-A4 pdfcpu conversion to replace an existing output file.
|
|
99
|
+
- Pre-rendered Mermaid diagrams as inline SVG instead of parsing them in site visitors' browsers.
|
|
100
|
+
- Removed duplicate provider and context labels from DSL catalog entries.
|
|
7
101
|
|
|
8
102
|
## 0.98.2 - 2026-07-25
|
|
9
103
|
|
|
@@ -212,7 +306,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
212
306
|
|
|
213
307
|
### Changed
|
|
214
308
|
|
|
215
|
-
- Added the
|
|
309
|
+
- Added the checkerboard example to the documentation showcase flow.
|
|
216
310
|
- Consolidated Victor Book attribution for adapted showcase examples into a single documentation note.
|
|
217
311
|
|
|
218
312
|
### Fixed
|
data/README.md
CHANGED
|
@@ -17,11 +17,17 @@ require "sevgi/sundries"
|
|
|
17
17
|
## Example
|
|
18
18
|
|
|
19
19
|
```ruby
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
x = Sevgi::Sundries::Ruler.new(brut: 80, unit: 1, multiple: 10, margins: [5])
|
|
21
|
+
y = Sevgi::Sundries::Ruler.new(brut: 50, unit: 1, multiple: 10, margins: [5])
|
|
22
|
+
grid = Sevgi::Sundries::Grid[x, y]
|
|
23
|
+
|
|
24
|
+
grid.x.major.lines.size # => 5
|
|
25
|
+
grid.canvas.margin.to_a # => [5.0, 5.0, 5.0, 5.0]
|
|
23
26
|
```
|
|
24
27
|
|
|
28
|
+
Rulers, grids, and tiles are inspectable Ruby values. They create no SVG elements by themselves. Pass their geometry
|
|
29
|
+
to Sevgi Graphics when the document needs it.
|
|
30
|
+
|
|
25
31
|
## Ruby compatibility
|
|
26
32
|
|
|
27
33
|
Requires Ruby 3.4.0 or newer. CI verifies the current Ruby 3.4 release and the development Ruby from `.ruby-version`.
|
|
@@ -26,7 +26,7 @@ module Sevgi
|
|
|
26
26
|
# @param format [Symbol, String, nil] explicit output format, or nil to infer from output extension
|
|
27
27
|
# @param width [Numeric, nil] finite positive target width in output pixels for PNG, or CSS pixels before PDF point conversion
|
|
28
28
|
# @param height [Numeric, nil] finite positive target height in output pixels for PNG, or CSS pixels before PDF point conversion
|
|
29
|
-
# @param dpi [Numeric] finite positive CSS pixel density
|
|
29
|
+
# @param dpi [Numeric] finite positive CSS pixel density. Omission uses {DEFAULT_DPI}, but explicit nil is invalid
|
|
30
30
|
# @param css [String, nil] CSS inserted before the closing svg tag before rendering
|
|
31
31
|
# @yield [svg] optional source transformation applied before rendering
|
|
32
32
|
# @yieldparam svg [String] SVG source after optional CSS injection
|
|
@@ -44,6 +44,7 @@ module Sevgi
|
|
|
44
44
|
height = dimension(height, "height")
|
|
45
45
|
dpi = dimension(dpi, "dpi", optional: false)
|
|
46
46
|
ArgumentError.("Export CSS must be a String") unless css.nil? || css.is_a?(String)
|
|
47
|
+
ArgumentError.("Export CSS must be valid text") if css && !css.valid_encoding?
|
|
47
48
|
|
|
48
49
|
svg = styled(svg, css) if css && !css.strip.empty?
|
|
49
50
|
svg = block.call(svg) if block
|
|
@@ -92,24 +93,18 @@ module Sevgi
|
|
|
92
93
|
# @param placeholder [String] placeholder text to replace
|
|
93
94
|
# @return [Boolean] true when at least one matching placeholder was replaced
|
|
94
95
|
# @raise [Sevgi::Sundries::Export::ExportError] when the PDF cannot be read, rewritten, or stamped
|
|
95
|
-
# @note
|
|
96
|
+
# @note Pages with inline images, unknown operators, or unbalanced graphics-state/text-object operators are left unchanged.
|
|
96
97
|
def stamp(infile, outfile, stamp:, placeholder:)
|
|
97
98
|
doc = HexaPDF::Document.open(infile)
|
|
98
99
|
replacements = 0
|
|
99
100
|
|
|
100
101
|
doc.pages.each do |page|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
next unless obj.respond_to?(:stream)
|
|
102
|
+
data, count = stamp_stream(page.contents, stamp:, placeholder:)
|
|
103
|
+
next if count.zero?
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
replacements += count
|
|
109
|
-
|
|
110
|
-
obj.stream = data
|
|
111
|
-
obj.set_filter(:FlateDecode)
|
|
112
|
-
end
|
|
105
|
+
replacements += count
|
|
106
|
+
page.contents = data
|
|
107
|
+
doc.deref(page[:Contents]).set_filter(:FlateDecode)
|
|
113
108
|
end
|
|
114
109
|
|
|
115
110
|
doc.write(outfile, optimize: true) if replacements.positive?
|
|
@@ -126,7 +121,7 @@ module Sevgi
|
|
|
126
121
|
# @param placeholder [String] placeholder text to replace
|
|
127
122
|
# @return [Boolean] true when at least one matching placeholder was replaced
|
|
128
123
|
# @raise [Sevgi::Sundries::Export::ExportError] when the PDF cannot be read, rewritten, stamped, or replaced
|
|
129
|
-
# @note
|
|
124
|
+
# @note Pages with inline images, unknown operators, or unbalanced graphics-state/text-object operators are left unchanged.
|
|
130
125
|
def stamp!(infile, stamp:, placeholder:)
|
|
131
126
|
temp = Tempfile.new(%w[stamp .pdf], File.dirname(infile))
|
|
132
127
|
stamped = stamp(infile, temp.path, stamp:, placeholder:)
|
|
@@ -185,7 +180,6 @@ module Sevgi
|
|
|
185
180
|
serializer = HexaPDF::Serializer.new
|
|
186
181
|
operands = []
|
|
187
182
|
state = {
|
|
188
|
-
fill_span: nil,
|
|
189
183
|
fill_white: false,
|
|
190
184
|
font_size: nil,
|
|
191
185
|
in_text: false,
|
|
@@ -206,8 +200,12 @@ module Sevgi
|
|
|
206
200
|
next
|
|
207
201
|
end
|
|
208
202
|
|
|
203
|
+
# Inline image bytes are not PDF tokens; never interpret them as operators.
|
|
204
|
+
operator = object.to_sym
|
|
205
|
+
return [{}, 0] if operator == :BI || !HexaPDF::Content::Processor::OPERATOR_MESSAGE_NAME_MAP.key?(operator)
|
|
206
|
+
|
|
209
207
|
count += process_operator(
|
|
210
|
-
|
|
208
|
+
operator,
|
|
211
209
|
operands,
|
|
212
210
|
finish,
|
|
213
211
|
data,
|
|
@@ -227,11 +225,13 @@ module Sevgi
|
|
|
227
225
|
def process_operator(operator, operands, finish, data, stamp:, placeholder:, serializer:, state:, replacements:)
|
|
228
226
|
case operator
|
|
229
227
|
when :q
|
|
230
|
-
state[:stack] << state.values_at(:
|
|
228
|
+
state[:stack] << state.values_at(:fill_white, :font_size)
|
|
231
229
|
when :Q
|
|
232
230
|
state[:stack].any? ? restore_state(state) : state[:valid] = false
|
|
233
231
|
when :rg
|
|
234
|
-
set_fill_state(state, operands
|
|
232
|
+
set_fill_state(state, operands)
|
|
233
|
+
when :g, :k, :cs, :sc, :scn
|
|
234
|
+
state[:fill_white] = false
|
|
235
235
|
when :BT
|
|
236
236
|
state[:valid] = false if state[:in_text]
|
|
237
237
|
state[:in_text] = true
|
|
@@ -245,6 +245,7 @@ module Sevgi
|
|
|
245
245
|
operator,
|
|
246
246
|
operands,
|
|
247
247
|
data,
|
|
248
|
+
operator_finish: finish,
|
|
248
249
|
stamp:,
|
|
249
250
|
placeholder:,
|
|
250
251
|
serializer:,
|
|
@@ -257,31 +258,38 @@ module Sevgi
|
|
|
257
258
|
end
|
|
258
259
|
|
|
259
260
|
def restore_state(state)
|
|
260
|
-
state[:
|
|
261
|
+
state[:fill_white], state[:font_size] = state[:stack].pop
|
|
261
262
|
end
|
|
262
263
|
|
|
263
|
-
def set_fill_state(state, operands
|
|
264
|
+
def set_fill_state(state, operands)
|
|
264
265
|
state[:fill_white] = operands.size == 3 && operands.all? { |value, _start, _finish| value == 1 }
|
|
265
|
-
state[:fill_span] = [operands.first[1], finish] if state[:fill_white]
|
|
266
|
-
state[:fill_span] = nil unless state[:fill_white]
|
|
267
266
|
end
|
|
268
267
|
|
|
269
|
-
def add_text_replacement(
|
|
268
|
+
def add_text_replacement(
|
|
269
|
+
operator,
|
|
270
|
+
operands,
|
|
271
|
+
data,
|
|
272
|
+
operator_finish:,
|
|
273
|
+
stamp:,
|
|
274
|
+
placeholder:,
|
|
275
|
+
serializer:,
|
|
276
|
+
state:,
|
|
277
|
+
replacements:
|
|
278
|
+
)
|
|
270
279
|
replacement = text_replacement(data, operator, operands, stamp:, placeholder:, serializer:, state:)
|
|
271
280
|
return 0 unless replacement
|
|
272
281
|
|
|
273
|
-
start,
|
|
274
|
-
|
|
275
|
-
|
|
282
|
+
start, text_finish, text = replacement
|
|
283
|
+
color_start = operands.first.fetch(1)
|
|
284
|
+
text = " 0.101961 0.101961 0.101961 rg#{text}" if start == color_start
|
|
285
|
+
replacements[[start, text_finish]] = text
|
|
286
|
+
add_color_replacement(color_start, replacements) unless start == color_start
|
|
287
|
+
replacements[[operator_finish, operator_finish]] ||= " 1 1 1 rg"
|
|
276
288
|
1
|
|
277
289
|
end
|
|
278
290
|
|
|
279
|
-
def add_color_replacement(
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
color_start, color_finish = state[:fill_span]
|
|
283
|
-
prefix = data[color_start...color_finish].to_s[/\A\s*/]
|
|
284
|
-
replacements[[color_start, color_finish]] ||= "#{prefix}0.101961 0.101961 0.101961 rg"
|
|
291
|
+
def add_color_replacement(start, replacements)
|
|
292
|
+
replacements[[start, start]] ||= " 0.101961 0.101961 0.101961 rg"
|
|
285
293
|
end
|
|
286
294
|
|
|
287
295
|
def text_replacement(data, operator, operands, stamp:, placeholder:, serializer:, state:)
|
|
@@ -426,18 +434,6 @@ module Sevgi
|
|
|
426
434
|
module Renderer
|
|
427
435
|
extend self
|
|
428
436
|
|
|
429
|
-
# Returns a renderer method for a format.
|
|
430
|
-
# @param format [Symbol, String, nil] format name
|
|
431
|
-
# @return [Method, nil]
|
|
432
|
-
def [](format)
|
|
433
|
-
case format&.to_sym
|
|
434
|
-
when :png
|
|
435
|
-
method(:png)
|
|
436
|
-
when :pdf
|
|
437
|
-
method(:pdf)
|
|
438
|
-
end
|
|
439
|
-
end
|
|
440
|
-
|
|
441
437
|
# Renders SVG data to a PDF surface.
|
|
442
438
|
# @param handle [Rsvg::Handle] parsed SVG handle
|
|
443
439
|
# @param output [String] output file path
|
|
@@ -22,8 +22,10 @@ module Sevgi
|
|
|
22
22
|
# @raise [Errno::ENOENT] when the executable cannot be spawned
|
|
23
23
|
def a5_on_a4!(infile)
|
|
24
24
|
temp = Tempfile.new(%w[output .pdf], File.dirname(infile))
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
outfile = temp.path
|
|
26
|
+
temp.close!
|
|
27
|
+
a5_on_a4(infile, outfile)
|
|
28
|
+
FileUtils.mv(outfile, infile)
|
|
27
29
|
ensure
|
|
28
30
|
temp&.close!
|
|
29
31
|
end
|
|
@@ -10,17 +10,21 @@ module Sevgi
|
|
|
10
10
|
#
|
|
11
11
|
# Native PDF/PNG rendering is loaded lazily so installing `sevgi-sundries` for SVG-only helpers does not require the
|
|
12
12
|
# Cairo, RSVG, or HexaPDF gems. Native export entrypoints raise {Sevgi::MissingComponentError} when those optional
|
|
13
|
-
# gems are unavailable. Omit `format:` to infer it from the output suffix
|
|
13
|
+
# gems are unavailable. Omit `format:` to infer it from the output suffix.
|
|
14
14
|
# width and height are output dimensions rather than changes to the SVG
|
|
15
15
|
# viewBox. The return value is the expanded path that was written.
|
|
16
|
+
# Export-only CSS is a last-minute adjustment, not a replacement for document styles or validation. Insertion
|
|
17
|
+
# requires well-formed SVG ending in an unprefixed `</svg>` followed only by XML whitespace. Self-closing roots,
|
|
18
|
+
# prefixed roots, and trailing comments are unsupported with CSS. CSS is XML-escaped but not parsed or validated.
|
|
19
|
+
# The native source callback runs after insertion and before conversion. External backends have no such callback.
|
|
16
20
|
#
|
|
17
21
|
# @example Export SVG source to a sized PNG
|
|
18
|
-
# svg = Sevgi::Graphics.SVG(:
|
|
22
|
+
# svg = Sevgi::Graphics.SVG(width: 10, height: 10) { circle cx: 5, cy: 5, r: 4 }.Render
|
|
19
23
|
# Sevgi::Sundries::Export.call(svg, "drawing.png", width: 320)
|
|
20
24
|
# @example Infer PDF output and inject export-only CSS
|
|
21
|
-
# svg = Sevgi::Graphics.SVG(:
|
|
25
|
+
# svg = Sevgi::Graphics.SVG(width: 10, height: 10) { circle class: "accent", cx: 5, cy: 5, r: 4 }.Render
|
|
22
26
|
# Sevgi::Sundries::Export.call(svg, "drawing.pdf", css: ".accent { fill: tomato; }")
|
|
23
|
-
# @see https://sevgi.roktas.dev/
|
|
27
|
+
# @see https://sevgi.roktas.dev/output/#export Export guide
|
|
24
28
|
module Export
|
|
25
29
|
# File extensions mapped to export format names.
|
|
26
30
|
# @api private
|
|
@@ -56,7 +60,7 @@ module Sevgi
|
|
|
56
60
|
# @param format [Symbol, String, nil] explicit output format, or nil to infer from output extension
|
|
57
61
|
# @param width [Numeric, nil] target width in output pixels for PNG, or CSS pixels before PDF point conversion
|
|
58
62
|
# @param height [Numeric, nil] target height in output pixels for PNG, or CSS pixels before PDF point conversion
|
|
59
|
-
# @param dpi [Numeric] finite positive CSS pixel density
|
|
63
|
+
# @param dpi [Numeric] finite positive CSS pixel density. Omission uses {DEFAULT_DPI}, but explicit nil is invalid
|
|
60
64
|
# @param css [String, nil] CSS inserted before the closing svg tag before rendering
|
|
61
65
|
# @yield [svg] optional source transformation applied before rendering
|
|
62
66
|
# @yieldparam svg [String] SVG source after optional CSS injection
|
|
@@ -84,10 +88,12 @@ module Sevgi
|
|
|
84
88
|
end
|
|
85
89
|
|
|
86
90
|
def styled(svg, css)
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
ArgumentError.("Export CSS must be valid text") unless css.is_a?(::String) && css.valid_encoding?
|
|
92
|
+
closing = %r{</svg>[ \t\r\n]*\z}
|
|
93
|
+
ExportError.("Cannot insert CSS: expected final </svg> root closing tag") unless closing.match?(svg)
|
|
89
94
|
|
|
90
|
-
|
|
95
|
+
text = css.gsub(/[&<>]/, "&" => "&", "<" => "<", ">" => ">")
|
|
96
|
+
svg.sub(closing) { |suffix| "<style>#{text}</style>#{suffix}" }
|
|
91
97
|
end
|
|
92
98
|
|
|
93
99
|
def normalize_format(format)
|
|
@@ -101,6 +107,8 @@ module Sevgi
|
|
|
101
107
|
private :format_for, :normalize_format, :styled
|
|
102
108
|
|
|
103
109
|
# Replaces exact placeholder text objects in PDF streams.
|
|
110
|
+
# The placeholder must be a PDF literal string inside a white text object that matches Sevgi's stamp pattern.
|
|
111
|
+
# Replacement text is escaped as a PDF literal string. When no match exists, the method writes no output file.
|
|
104
112
|
# @param infile [String] source PDF file path
|
|
105
113
|
# @param outfile [String] destination PDF file path
|
|
106
114
|
# @param stamp [String] replacement text
|
|
@@ -112,6 +120,7 @@ module Sevgi
|
|
|
112
120
|
def stamp(infile, outfile, stamp:, placeholder:) = native!.stamp(infile, outfile, stamp:, placeholder:)
|
|
113
121
|
|
|
114
122
|
# Replaces exact placeholder text objects inside a PDF file in place.
|
|
123
|
+
# The input file changes only after at least one exact match produces a nonempty output file.
|
|
115
124
|
# @param infile [String] PDF file path to modify
|
|
116
125
|
# @param stamp [String] replacement text
|
|
117
126
|
# @param placeholder [String] placeholder text to replace
|
data/lib/sevgi/sundries/grid.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Sevgi
|
|
|
8
8
|
#
|
|
9
9
|
# Axis names describe line direction, not the coordinate used to place a
|
|
10
10
|
# line: `grid.x` produces horizontal lines whose y positions come from the
|
|
11
|
-
# vertical ruler
|
|
11
|
+
# vertical ruler. `grid.y` produces vertical lines whose x positions come
|
|
12
12
|
# from the horizontal ruler. Each query can return geometry lines, Point
|
|
13
13
|
# endpoint pairs, or plain coordinate pairs for different consumers.
|
|
14
14
|
# @example Query fitted horizontal and vertical lines
|
|
@@ -54,7 +54,7 @@ module Sevgi
|
|
|
54
54
|
# Creates a grid from horizontal and vertical rulers.
|
|
55
55
|
# @param x [Sevgi::Sundries::Ruler] horizontal ruler
|
|
56
56
|
# @param y [Sevgi::Sundries::Ruler] vertical ruler
|
|
57
|
-
# @param canvas [Sevgi::Graphics::Canvas, nil] source canvas whose identity
|
|
57
|
+
# @param canvas [Sevgi::Graphics::Canvas, nil] optional source canvas whose identity the grid preserves
|
|
58
58
|
# @return [void]
|
|
59
59
|
# @raise [Sevgi::ArgumentError] when either argument is not a ruler
|
|
60
60
|
# @raise [Sevgi::ArgumentError] when the source canvas does not match the ruler spans
|
|
@@ -71,7 +71,7 @@ module Sevgi
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
# Returns a graphics canvas matching the ruler spans and fitted margins.
|
|
74
|
-
# Horizontal ruler margins become the canvas left/right margins
|
|
74
|
+
# Horizontal ruler margins become the canvas left/right margins. Vertical
|
|
75
75
|
# ruler margins become its top/bottom margins.
|
|
76
76
|
# @example Build a drawing with the fitted canvas
|
|
77
77
|
# x = Sevgi::Sundries::Ruler.new(brut: 80, unit: 1, multiple: 10, margins: [5])
|
|
@@ -130,7 +130,7 @@ module Sevgi
|
|
|
130
130
|
@minor = Minor.send(:new, self, other)
|
|
131
131
|
end
|
|
132
132
|
|
|
133
|
-
#
|
|
133
|
+
# Grid line query for an axis.
|
|
134
134
|
class Query
|
|
135
135
|
private_class_method :new
|
|
136
136
|
|
|
@@ -141,18 +141,18 @@ module Sevgi
|
|
|
141
141
|
# @api private
|
|
142
142
|
def initialize(this, other) = (@this, @other = this, other)
|
|
143
143
|
|
|
144
|
-
# Returns grid line endpoints as coordinate pairs.
|
|
145
|
-
# The outer and nested collections are frozen
|
|
144
|
+
# Returns grid line endpoints as coordinate pairs at the active {Sevgi::F} precision.
|
|
145
|
+
# Each call reads the current thread precision. The outer and nested collections are frozen.
|
|
146
146
|
# @return [Array<Array<Array<Float>>>] frozen coordinate pairs
|
|
147
|
-
def xys =
|
|
147
|
+
def xys = lines.map { it.points(true).map { |point| point.deconstruct.freeze }.freeze }.freeze
|
|
148
148
|
|
|
149
|
-
# Returns grid line endpoints as points.
|
|
150
|
-
# The outer and nested collections are frozen
|
|
149
|
+
# Returns grid line endpoints as points at the active {Sevgi::F} precision.
|
|
150
|
+
# Each call reads the current thread precision. The outer and nested collections are frozen.
|
|
151
151
|
# @return [Array<Array<Sevgi::Geometry::Point>>] frozen point pairs
|
|
152
|
-
def points =
|
|
152
|
+
def points = lines.map { it.points(true).freeze }.freeze
|
|
153
153
|
|
|
154
|
-
# Returns generated grid lines.
|
|
155
|
-
# The
|
|
154
|
+
# Returns exact generated grid lines.
|
|
155
|
+
# The collection is computed once and frozen. Use {#points} or {#xys} for precision-rounded coordinates.
|
|
156
156
|
# @return [Array<Sevgi::Geometry::Line>] frozen lines
|
|
157
157
|
def lines = @lines ||= build.freeze
|
|
158
158
|
|
data/lib/sevgi/sundries/ruler.rb
CHANGED
|
@@ -64,7 +64,7 @@ module Sevgi
|
|
|
64
64
|
# @raise [Sevgi::ArgumentError] when length is not numeric
|
|
65
65
|
# @raise [Sevgi::ArgumentError] when length is not finite
|
|
66
66
|
# @raise [Sevgi::ArgumentError] when length is not positive
|
|
67
|
-
def count(length) = (d
|
|
67
|
+
def count(length) = F.count(d, positive_number(length, "Interval count length"))
|
|
68
68
|
|
|
69
69
|
# Returns the total interval distance.
|
|
70
70
|
# @return [Float]
|
|
@@ -157,7 +157,7 @@ module Sevgi
|
|
|
157
157
|
# `brut` is the full available span. `unit * multiple` becomes the major
|
|
158
158
|
# interval, and `sd/su/sn` describe that source subinterval. Requested
|
|
159
159
|
# margins are minimums: leftover space is split between them while their
|
|
160
|
-
# start/end difference is preserved. The fitted `d` excludes those margins
|
|
160
|
+
# start/end difference is preserved. The fitted `d` excludes those margins.
|
|
161
161
|
# `waste` includes them and any remainder.
|
|
162
162
|
#
|
|
163
163
|
# @example Ruler geometry
|
|
@@ -225,13 +225,13 @@ module Sevgi
|
|
|
225
225
|
@start, @finish = fitted_margins(start, finish, span)
|
|
226
226
|
end
|
|
227
227
|
|
|
228
|
-
# Returns a ruler where the source subinterval is flattened into units.
|
|
228
|
+
# Returns a ruler where the source subinterval is flattened into units without refitting the span.
|
|
229
229
|
# @example Expand major intervals into individual units
|
|
230
230
|
# ruler = Sevgi::Sundries::Ruler.new(brut: 103, unit: 1, multiple: 10, margins: [5])
|
|
231
231
|
# ruler.expand.n # => 90
|
|
232
232
|
# ruler.ms.size # => 91
|
|
233
233
|
# @return [Sevgi::Sundries::Ruler]
|
|
234
|
-
def expand =
|
|
234
|
+
def expand = dup.expand!
|
|
235
235
|
|
|
236
236
|
# Returns fitted start and finish margins.
|
|
237
237
|
# @return [Array<Float>] frozen margin pair
|
|
@@ -244,7 +244,9 @@ module Sevgi
|
|
|
244
244
|
|
|
245
245
|
# Returns the unfitted distance distributed outside the fitted span.
|
|
246
246
|
# @return [Float]
|
|
247
|
-
def waste
|
|
247
|
+
def waste
|
|
248
|
+
@waste ||= (brut - d).then { it.negative? && d.prev_float <= brut ? 0.0 : it }
|
|
249
|
+
end
|
|
248
250
|
|
|
249
251
|
# Returns the source subinterval count.
|
|
250
252
|
# @return [Integer]
|
|
@@ -261,7 +263,9 @@ module Sevgi
|
|
|
261
263
|
private
|
|
262
264
|
|
|
263
265
|
def fitted_margins(start, finish, span)
|
|
264
|
-
extra =
|
|
266
|
+
extra = span - d
|
|
267
|
+
extra = 0.0 if extra.negative? && d.prev_float <= span
|
|
268
|
+
extra /= 2.0
|
|
265
269
|
[start + extra, finish + extra]
|
|
266
270
|
end
|
|
267
271
|
|
|
@@ -284,6 +288,16 @@ module Sevgi
|
|
|
284
288
|
|
|
285
289
|
protected
|
|
286
290
|
|
|
291
|
+
# Flattens a copied ruler and clears values derived from its old interval.
|
|
292
|
+
# @api private
|
|
293
|
+
def expand!
|
|
294
|
+
@n *= sn
|
|
295
|
+
@u = su
|
|
296
|
+
@sub = Interval.new(@u, 1)
|
|
297
|
+
@d = @ds = @h = @hs = @ms = @nds = @nhs = @waste = nil
|
|
298
|
+
self
|
|
299
|
+
end
|
|
300
|
+
|
|
287
301
|
# Computes the number of major intervals fitting in the available span.
|
|
288
302
|
# @param unit [Numeric] subinterval unit length
|
|
289
303
|
# @param multiple [Integer] number of subinterval units per major interval
|
|
@@ -296,7 +310,7 @@ module Sevgi
|
|
|
296
310
|
#
|
|
297
311
|
# If ordinary fitting produces an odd count, one complete major interval is
|
|
298
312
|
# removed and the additional space is distributed through the margins.
|
|
299
|
-
# @example Reserve symmetric waste when an odd count
|
|
313
|
+
# @example Reserve symmetric waste when ordinary fitting produces an odd count
|
|
300
314
|
# ruler = Sevgi::Sundries::RulerEven.new(brut: 50, unit: 1, multiple: 10)
|
|
301
315
|
# ruler.n # => 4
|
|
302
316
|
# ruler.margins # => [5.0, 5.0]
|
data/lib/sevgi/sundries/tile.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Sevgi
|
|
|
6
6
|
#
|
|
7
7
|
# The source element's bounding-box width and height become the cell pitch.
|
|
8
8
|
# Indexing is row-first: `tile[row][column]`. Generated cells and collection
|
|
9
|
-
# snapshots are immutable geometry values
|
|
9
|
+
# snapshots are immutable geometry values. No SVG elements are created.
|
|
10
10
|
# @example Address cells by row and column
|
|
11
11
|
# cell = Sevgi::Geometry::Rect[8, 4]
|
|
12
12
|
# tile = Sevgi::Sundries::Tile.new(cell, position: [10, 20], nx: 3, ny: 2)
|
|
@@ -29,7 +29,7 @@ module Sevgi
|
|
|
29
29
|
# @return [Sevgi::Geometry::Element]
|
|
30
30
|
attr_reader :element
|
|
31
31
|
|
|
32
|
-
# Returns the tile
|
|
32
|
+
# Returns the upper-left corner of the tile bounds.
|
|
33
33
|
# @return [Sevgi::Geometry::Point]
|
|
34
34
|
attr_reader :position
|
|
35
35
|
|
|
@@ -43,7 +43,7 @@ module Sevgi
|
|
|
43
43
|
|
|
44
44
|
# Creates a tile from a source geometry element.
|
|
45
45
|
# @param element [Sevgi::Geometry::Element] geometry element to repeat
|
|
46
|
-
# @param position [Sevgi::Geometry::Point, Array<Numeric>] tile
|
|
46
|
+
# @param position [Sevgi::Geometry::Point, Array<Numeric>] upper-left corner of the tile bounds
|
|
47
47
|
# @param nx [Integer] number of columns
|
|
48
48
|
# @param ny [Integer] number of rows
|
|
49
49
|
# @return [void]
|
|
@@ -57,6 +57,7 @@ module Sevgi
|
|
|
57
57
|
|
|
58
58
|
@element = element
|
|
59
59
|
@position = self.class.send(:position, position)
|
|
60
|
+
@bounds = element.box
|
|
60
61
|
|
|
61
62
|
@nx = nx
|
|
62
63
|
@ny = ny
|
|
@@ -65,20 +66,26 @@ module Sevgi
|
|
|
65
66
|
# Returns a row by index.
|
|
66
67
|
# @param i [Integer] row index
|
|
67
68
|
# @return [Array<Sevgi::Geometry::Element>, nil]
|
|
68
|
-
|
|
69
|
+
# @raise [Sevgi::ArgumentError] when the index is not an Integer
|
|
70
|
+
def [](i) = row(i)
|
|
69
71
|
|
|
70
72
|
# Returns the bounding rectangle of the whole tile.
|
|
71
73
|
# @return [Sevgi::Geometry::Rect]
|
|
72
|
-
def box = @box ||= Geometry::Rect[nx *
|
|
74
|
+
def box = @box ||= Geometry::Rect[nx * @bounds.width, ny * @bounds.height, position:]
|
|
73
75
|
|
|
74
76
|
# Returns the first cell in the tile.
|
|
75
77
|
# @return [Sevgi::Geometry::Element]
|
|
76
|
-
def cell =
|
|
78
|
+
def cell = @cell ||= cell_at(0, 0)
|
|
77
79
|
|
|
78
80
|
# Returns the bounding rectangle of a column.
|
|
79
81
|
# @param i [Integer] column index
|
|
80
|
-
#
|
|
81
|
-
|
|
82
|
+
# Negative indices count from the last column. Out-of-range indices return nil.
|
|
83
|
+
# @return [Sevgi::Geometry::Rect, nil]
|
|
84
|
+
# @raise [Sevgi::ArgumentError] when the index is not an Integer
|
|
85
|
+
def colbox(i = 0)
|
|
86
|
+
i = index(i, nx)
|
|
87
|
+
Geometry::Rect[@bounds.width, box.height, position: coordinate(0, i)] if i
|
|
88
|
+
end
|
|
82
89
|
|
|
83
90
|
# Returns cells grouped by column.
|
|
84
91
|
# The outer and nested collections are frozen and must be treated as immutable.
|
|
@@ -88,7 +95,13 @@ module Sevgi
|
|
|
88
95
|
# Returns a column by index.
|
|
89
96
|
# @param i [Integer] column index
|
|
90
97
|
# @return [Array<Sevgi::Geometry::Element>, nil]
|
|
91
|
-
|
|
98
|
+
# @raise [Sevgi::ArgumentError] when the index is not an Integer
|
|
99
|
+
def col(i = 0)
|
|
100
|
+
i = index(i, nx)
|
|
101
|
+
return unless i
|
|
102
|
+
|
|
103
|
+
@cols ? @cols[i] : Array.new(ny) { |j| cell_at(j, i) }.freeze
|
|
104
|
+
end
|
|
92
105
|
|
|
93
106
|
# Iterates over rows.
|
|
94
107
|
# @yield [row] each row
|
|
@@ -107,17 +120,28 @@ module Sevgi
|
|
|
107
120
|
# Returns a row by index.
|
|
108
121
|
# @param i [Integer] row index
|
|
109
122
|
# @return [Array<Sevgi::Geometry::Element>, nil]
|
|
110
|
-
|
|
123
|
+
# @raise [Sevgi::ArgumentError] when the index is not an Integer
|
|
124
|
+
def row(i = 0)
|
|
125
|
+
i = index(i, ny)
|
|
126
|
+
return unless i
|
|
127
|
+
|
|
128
|
+
@rows ? @rows[i] : Array.new(nx) { |j| cell_at(i, j) }.freeze
|
|
129
|
+
end
|
|
111
130
|
|
|
112
131
|
# Returns the bounding rectangle of a row.
|
|
113
132
|
# @param i [Integer] row index
|
|
114
|
-
#
|
|
115
|
-
|
|
133
|
+
# Negative indices count from the last row. Out-of-range indices return nil.
|
|
134
|
+
# @return [Sevgi::Geometry::Rect, nil]
|
|
135
|
+
# @raise [Sevgi::ArgumentError] when the index is not an Integer
|
|
136
|
+
def rowbox(i = 0)
|
|
137
|
+
i = index(i, ny)
|
|
138
|
+
Geometry::Rect[box.width, @bounds.height, position: coordinate(i)] if i
|
|
139
|
+
end
|
|
116
140
|
|
|
117
141
|
# Returns cells grouped by row.
|
|
118
142
|
# The outer and nested collections are frozen and must be treated as immutable.
|
|
119
143
|
# @return [Array<Array<Sevgi::Geometry::Element>>] frozen rows
|
|
120
|
-
def rows = @rows ||= (0...ny).map { |i| (0...nx).map { |j|
|
|
144
|
+
def rows = @rows ||= (0...ny).map { |i| (0...nx).map { |j| cell_at(i, j) }.freeze }.freeze
|
|
121
145
|
|
|
122
146
|
# Iterates over rows.
|
|
123
147
|
# @return [Enumerator, Array<Array<Sevgi::Geometry::Element>>] enumerator without a block, otherwise rows
|
|
@@ -125,6 +149,12 @@ module Sevgi
|
|
|
125
149
|
|
|
126
150
|
private
|
|
127
151
|
|
|
152
|
+
def index(value, count)
|
|
153
|
+
ArgumentError.("Tile index must be an Integer") unless value.is_a?(::Integer)
|
|
154
|
+
value += count if value.negative?
|
|
155
|
+
value if value.between?(0, count - 1)
|
|
156
|
+
end
|
|
157
|
+
|
|
128
158
|
# Coerces a public tile position.
|
|
129
159
|
# @param position [Sevgi::Geometry::Point, Array<Numeric>] tile origin
|
|
130
160
|
# @return [Sevgi::Geometry::Point]
|
|
@@ -141,7 +171,12 @@ module Sevgi
|
|
|
141
171
|
|
|
142
172
|
private_class_method :position
|
|
143
173
|
|
|
144
|
-
def
|
|
174
|
+
def cell_at(i, j)
|
|
175
|
+
point = coordinate(i, j)
|
|
176
|
+
element.translate(point.x - @bounds.position.x, point.y - @bounds.position.y)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def coordinate(i, j = 0) = position.translate(j * @bounds.width, i * @bounds.height)
|
|
145
180
|
end
|
|
146
181
|
end
|
|
147
182
|
end
|
data/lib/sevgi/sundries.rb
CHANGED
|
@@ -15,8 +15,8 @@ require_relative "sundries/version"
|
|
|
15
15
|
module Sevgi
|
|
16
16
|
# Layout, tiling, grid, and export helpers shared by Sevgi consumers.
|
|
17
17
|
#
|
|
18
|
-
# {Ruler} fits repeatable distances into a span
|
|
19
|
-
# and exposes drawable lines
|
|
18
|
+
# {Ruler} fits repeatable distances into a span. {Grid} combines two rulers
|
|
19
|
+
# and exposes drawable lines. {Tile} repeats geometry by rows and columns.
|
|
20
20
|
# These layout values can be computed without an SVG document and then passed
|
|
21
21
|
# to Graphics. Native PDF/PNG dependencies remain lazy and are loaded only by
|
|
22
22
|
# {Export}.
|
|
@@ -26,7 +26,8 @@ module Sevgi
|
|
|
26
26
|
# cell = Sevgi::Geometry::Rect[8, 4]
|
|
27
27
|
# Sevgi::Sundries::Tile.new(cell, nx: 3, ny: 2).box.deconstruct
|
|
28
28
|
# @see Sevgi::Graphics::Canvas
|
|
29
|
-
# @see https://sevgi.roktas.dev/
|
|
29
|
+
# @see https://sevgi.roktas.dev/layout/ Layout guide
|
|
30
|
+
# @see https://sevgi.roktas.dev/output/ Output guide
|
|
30
31
|
module Sundries
|
|
31
32
|
end
|
|
32
33
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sevgi-sundries
|
|
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,42 +15,42 @@ 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.
|
|
25
|
+
version: 1.0.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: sevgi-geometry
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - '='
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.
|
|
32
|
+
version: 1.0.0
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - '='
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.
|
|
39
|
+
version: 1.0.0
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: sevgi-graphics
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
44
|
- - '='
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: 0.
|
|
46
|
+
version: 1.0.0
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - '='
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: 0.
|
|
53
|
+
version: 1.0.0
|
|
54
54
|
description: Includes grid, ruler, tile, and optional PDF or PNG export support.
|
|
55
55
|
email: roktas@gmail.com
|
|
56
56
|
executables: []
|
|
@@ -90,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
90
90
|
- !ruby/object:Gem::Version
|
|
91
91
|
version: '0'
|
|
92
92
|
requirements: []
|
|
93
|
-
rubygems_version: 4.0.
|
|
93
|
+
rubygems_version: 4.0.20
|
|
94
94
|
specification_version: 4
|
|
95
95
|
summary: Layout helpers and export tools for Sevgi.
|
|
96
96
|
test_files: []
|