fulgur_chart 0.3.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1feeff546e3edbf804f27aaf4e51fb50d1da3297286064a58e1775e36110bcfb
4
+ data.tar.gz: 89ee391afe75cd21d7f60ed05b533e00b280a3eea21e66f99369dc8f9f4a1d69
5
+ SHA512:
6
+ metadata.gz: bfe60580a7c4781031e81a20a74128b1d0d19fc421899703e61f0f44db9f76a448e9aa64440e4d9b69470d89b6ed599295b432928f1258748e28e1986a5913fa
7
+ data.tar.gz: 2e2d40dbeaefe0460c6c6703922c4f480b9a0903a395468d05d7c9c71ce41e0884947e1897e86cf40fbde8025b4ecd653d616f87d61ce20cff64e739922901f8
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # fulgur_chart (Ruby)
2
+
3
+ Ruby binding for [fulgur-chart](https://github.com/fulgur-rs/fulgur-chart) — render
4
+ chart.js v4 / Vega-Lite JSON specs to deterministic SVG/PNG via a Rust native extension
5
+ ([magnus](https://github.com/matsadler/magnus) / [rb-sys](https://github.com/oxidize-rb/rb-sys)).
6
+
7
+ ## Requirements
8
+
9
+ - Ruby >= 3.0
10
+ - A Rust toolchain (`cargo`) — the gem builds a native extension at install time.
11
+
12
+ ## Build / test from source
13
+
14
+ ```sh
15
+ cd crates/bindings/ruby
16
+ bundle install
17
+ bundle exec rake # compile the native extension + run the test suite
18
+ ```
19
+
20
+ `bundle exec rake` runs the `compile` task (which builds the Rust extension) followed by
21
+ the minitest suite.
22
+
23
+ ## Usage
24
+
25
+ The API is a fluent **builder**: `FulgurChart.build(spec)` returns a builder you configure with
26
+ chainable setters and finish with `render(:svg)` / `render(:png)`.
27
+
28
+ ```ruby
29
+ require "fulgur_chart"
30
+
31
+ spec = <<~JSON
32
+ {
33
+ "type": "bar",
34
+ "data": {
35
+ "labels": ["a", "b", "c"],
36
+ "datasets": [{ "data": [1, 3, 2] }]
37
+ }
38
+ }
39
+ JSON
40
+
41
+ # SVG (UTF-8 String)
42
+ svg = FulgurChart.build(spec).render(:svg)
43
+ File.write("chart.svg", svg)
44
+
45
+ # PNG (binary / ASCII-8BIT String) — write with binwrite to avoid encoding mangling
46
+ png = FulgurChart.build(spec).width(800).height(600).scale(2.0).render(:png)
47
+ File.binwrite("chart.png", png)
48
+
49
+ # Set a default format with .format, then call render with no argument
50
+ png2 = FulgurChart.build(spec).format(:png).render
51
+
52
+ # The builder is reusable and reconfigurable between renders
53
+ chart = FulgurChart.build(spec).dsl(:chartjs)
54
+ a = chart.width(400).render(:svg)
55
+ b = chart.width(1234).render(:svg)
56
+
57
+ # JSON Schema for a DSL (compact JSON String)
58
+ chartjs_schema = FulgurChart.schema(:chartjs)
59
+ vegalite_schema = FulgurChart.schema("vegalite")
60
+
61
+ # Library version (String, e.g. "0.1.0")
62
+ FulgurChart.version
63
+ ```
64
+
65
+ The DSL is auto-detected from the spec: a top-level `mark` key selects Vega-Lite, a
66
+ top-level `type` key selects chart.js. Use `.dsl(:chartjs)` / `.dsl(:vegalite)` to override.
67
+ Options accept either a Symbol or a String (`.dsl(:chartjs)` == `.dsl("chartjs")`).
68
+
69
+ ### API
70
+
71
+ | Method | Returns |
72
+ | --- | --- |
73
+ | `FulgurChart.build(spec_json)` | a `FulgurChart::Builder` |
74
+ | `builder.render(format = nil)` | `String` — `:svg` → UTF-8, `:png` → binary (ASCII-8BIT). Format precedence: argument > `.format()` > default `:svg` |
75
+ | `FulgurChart.render(spec_json, format, **opts)` | low-level primitive the builder calls; same return contract |
76
+ | `FulgurChart.schema(dsl)` | JSON Schema `String` for `:chartjs` or `:vegalite` |
77
+ | `FulgurChart.version` | version `String` |
78
+
79
+ ### Builder setters
80
+
81
+ Each setter returns the builder for chaining; all are optional.
82
+
83
+ | Setter | Type | Notes |
84
+ | --- | --- | --- |
85
+ | `.width(w)` / `.height(h)` | Float | Canvas size override (applied before input-limit validation). |
86
+ | `.scale(s)` | Float | Raster scale factor; raster output only (ignored when rendering `:svg`). Default `1.0`. |
87
+ | `.strict` / `.strict(bool)` | Bool | Reject unknown keys (raises `StrictError`). `.strict` ⇒ `true`. Default `false`. |
88
+ | `.dsl(d)` | `:chartjs` \| `:vegalite` (Symbol/String) | Override DSL auto-detection. |
89
+ | `.font(bytes)` | binary `String` | A TTF/OTF font to use instead of the bundled default (Noto Sans JP). |
90
+ | `.format(f)` | `:svg` \| `:png` (Symbol/String) | Default format for a terminal `render` with no argument. |
91
+
92
+ ### Errors
93
+
94
+ The error hierarchy lives in the native extension under the `FulgurChart` module
95
+ (the module is `FulgurChart`, not `Fulgur`, to avoid a top-level collision with the
96
+ Fulgur PDF library when both gems are loaded in the same process):
97
+
98
+ - `FulgurChart::ParseError < StandardError` — invalid JSON, undetectable DSL, unknown format,
99
+ input-limit violations.
100
+ - `FulgurChart::StrictError < FulgurChart::ParseError` — unknown key encountered under `.strict`.
101
+ - `FulgurChart::RenderError < StandardError` — raster rendering failure.
102
+
103
+ Note the font-error asymmetry: an invalid font raises `FulgurChart::ParseError` when rendering
104
+ `:svg` but `FulgurChart::RenderError` when rendering `:png`, because the two outputs go through
105
+ different render pipelines.
106
+
107
+ ## Note: packaging
108
+
109
+ The published-gem story — source-installing the path-dependent Rust core and shipping
110
+ cross-platform prebuilt gems — is tracked as a follow-up. Today the gem builds against the
111
+ in-repo core via a Cargo path dependency, so it is intended for use from within this
112
+ repository (build from source as shown above).
@@ -0,0 +1,15 @@
1
+ [package]
2
+ name = "fulgur_chart"
3
+ version = "0.3.0"
4
+ edition = "2021"
5
+ publish = false
6
+
7
+ [lib]
8
+ crate-type = ["cdylib"]
9
+
10
+ [dependencies]
11
+ magnus = "0.7"
12
+ fulgur-chart = "0.3.0"
13
+ serde = { version = "1", features = ["derive"] }
14
+ serde_json = "1"
15
+ schemars = { version = "1.2", features = ["preserve_order"] }
@@ -0,0 +1,4 @@
1
+ require "mkmf"
2
+ require "rb_sys/mkmf"
3
+
4
+ create_rust_makefile("fulgur_chart/fulgur_chart")
@@ -0,0 +1,255 @@
1
+ use fulgur_chart::guard::{validate_spec, InputLimits};
2
+ use magnus::{
3
+ function,
4
+ prelude::*,
5
+ scan_args::{get_kwargs, scan_args},
6
+ Error, ExceptionClass, RHash, RString, Ruby, Value,
7
+ };
8
+
9
+ // --- error helpers (classification is by CALL SITE, never by parsing the message) ---
10
+
11
+ fn exc_class(ruby: &Ruby, name: &str) -> ExceptionClass {
12
+ let module = ruby
13
+ .define_module("FulgurChart")
14
+ .expect("FulgurChart module defined in init");
15
+ module
16
+ .const_get::<_, ExceptionClass>(name)
17
+ .expect("error class defined in init")
18
+ }
19
+
20
+ fn parse_err(ruby: &Ruby, msg: impl Into<String>) -> Error {
21
+ Error::new(exc_class(ruby, "ParseError"), msg.into())
22
+ }
23
+
24
+ fn strict_err(ruby: &Ruby, msg: impl Into<String>) -> Error {
25
+ Error::new(exc_class(ruby, "StrictError"), msg.into())
26
+ }
27
+
28
+ // The image path classifies raster errors as RenderError (asymmetry vs the SVG path,
29
+ // which maps font/render failures to ParseError).
30
+ fn render_err(ruby: &Ruby, msg: impl Into<String>) -> Error {
31
+ Error::new(exc_class(ruby, "RenderError"), msg.into())
32
+ }
33
+
34
+ /// Coerce a Ruby argument to a String, accepting both String and Symbol (idiomatic Ruby lets
35
+ /// callers pass `dsl: :chartjs` / `format: :png`). magnus's String conversion rejects Symbols,
36
+ /// so without this `to_s` coercion a symbol would raise TypeError instead of being accepted.
37
+ fn coerce_string(v: Value) -> Result<String, Error> {
38
+ v.funcall("to_s", ())
39
+ }
40
+
41
+ // --- DSL detection + parse (mirrors fulgur-chart-cli `detect_dsl` / `parse_spec`) ---
42
+
43
+ /// Lightweight serde helper that only deserialises the top-level keys used for DSL detection.
44
+ #[derive(serde::Deserialize)]
45
+ struct DslDetector {
46
+ mark: Option<serde::de::IgnoredAny>,
47
+ #[serde(rename = "type")]
48
+ r#type: Option<serde::de::IgnoredAny>,
49
+ }
50
+
51
+ /// Infer DSL from spec JSON: `mark` key → vegalite, `type` key → chartjs, neither → Err.
52
+ fn detect_dsl(json: &str) -> Result<&'static str, String> {
53
+ let d: DslDetector = serde_json::from_str(json).map_err(|e| format!("invalid JSON: {e}"))?;
54
+ if d.mark.is_some() {
55
+ return Ok("vegalite");
56
+ }
57
+ if d.r#type.is_some() {
58
+ return Ok("chartjs");
59
+ }
60
+ Err("cannot auto-detect DSL: specify dsl: 'chartjs' or 'vegalite'".to_string())
61
+ }
62
+
63
+ /// Parse a spec JSON string to IR using the specified DSL (chartjs or vegalite).
64
+ fn parse_spec(json: &str, dsl: &str, strict: bool) -> Result<fulgur_chart::ir::ChartSpec, String> {
65
+ match dsl {
66
+ "vegalite" => fulgur_chart::frontend::vegalite::parse(json, strict),
67
+ _ => fulgur_chart::frontend::chartjs::parse(json, strict), // "chartjs"
68
+ }
69
+ }
70
+
71
+ // --- RenderOptions ---
72
+
73
+ #[derive(Default)]
74
+ struct Opts {
75
+ width: Option<f64>,
76
+ height: Option<f64>,
77
+ // Consumed by the image path (render_chart_to_png); the SVG path ignores scale.
78
+ scale: f32,
79
+ strict: bool,
80
+ dsl: Option<String>,
81
+ font: Option<Vec<u8>>,
82
+ }
83
+
84
+ /// Parse optional RenderOptions from the kwargs hash. Tolerates extra keys (e.g. `format:`
85
+ /// passed by render_image in Task 3) via the trailing `RHash` splat, so unknown keys do not
86
+ /// raise here.
87
+ fn parse_opts(ruby: &Ruby, kw: RHash) -> Result<Opts, Error> {
88
+ let args = get_kwargs::<
89
+ _,
90
+ (),
91
+ (
92
+ Option<f64>,
93
+ Option<f64>,
94
+ Option<f64>,
95
+ Option<bool>,
96
+ Option<Value>,
97
+ Option<RString>,
98
+ ),
99
+ RHash, // splat: collect + ignore unknown keys
100
+ >(
101
+ kw,
102
+ &[],
103
+ &["width", "height", "scale", "strict", "dsl", "font"],
104
+ )?;
105
+ let (width, height, scale, strict, dsl_val, font) = args.optional;
106
+
107
+ // Accept String or Symbol for `dsl`; an explicit nil arrives as None (→ auto-detect).
108
+ let dsl = match dsl_val {
109
+ Some(v) => {
110
+ let d = coerce_string(v)?;
111
+ if d != "chartjs" && d != "vegalite" {
112
+ return Err(parse_err(ruby, format!("unsupported DSL '{d}'")));
113
+ }
114
+ Some(d)
115
+ }
116
+ None => None,
117
+ };
118
+
119
+ // Copy the font bytes out of the Ruby string immediately; the borrow is unsafe and must
120
+ // not outlive any subsequent VM allocation.
121
+ let font = font.map(|s| unsafe { s.as_slice().to_vec() });
122
+
123
+ Ok(Opts {
124
+ width,
125
+ height,
126
+ scale: scale.map(|s| s as f32).unwrap_or(1.0),
127
+ strict: strict.unwrap_or(false),
128
+ dsl,
129
+ font,
130
+ })
131
+ }
132
+
133
+ /// Build and validate the IR, mirroring the processing order of `render_one`.
134
+ /// Reusable by render_svg / render_image (Task 3) / schema-less paths.
135
+ fn build_ir(
136
+ ruby: &Ruby,
137
+ spec_json: &str,
138
+ opts: &Opts,
139
+ ) -> Result<fulgur_chart::ir::ChartSpec, Error> {
140
+ // 1. Resolve DSL: explicit opts.dsl OR auto-detect.
141
+ let dsl: &str = match &opts.dsl {
142
+ Some(d) => d.as_str(),
143
+ None => detect_dsl(spec_json).map_err(|e| parse_err(ruby, e))?,
144
+ };
145
+
146
+ // 2. Parse NON-strict → IR (render from this).
147
+ // Contract §3: propagate the core's error String verbatim. The exception class — not a
148
+ // message prefix — conveys parse/strict/render, so no CLI-style "error: ..." decoration.
149
+ let mut ir = parse_spec(spec_json, dsl, false).map_err(|e| parse_err(ruby, e))?;
150
+
151
+ // 3. If strict, re-parse with strict=true (discard IR; unknown key → StrictError).
152
+ if opts.strict {
153
+ parse_spec(spec_json, dsl, true).map_err(|e| strict_err(ruby, e))?;
154
+ }
155
+
156
+ // 4. Apply width/height overrides BEFORE guard.
157
+ if let Some(w) = opts.width {
158
+ ir.width = w;
159
+ }
160
+ if let Some(h) = opts.height {
161
+ ir.height = h;
162
+ }
163
+
164
+ // 5. Guard (failure → ParseError).
165
+ validate_spec(&ir, &InputLimits::default()).map_err(|e| parse_err(ruby, e))?;
166
+
167
+ Ok(ir)
168
+ }
169
+
170
+ // --- public API: render (low-level primitive; the FulgurChart::Builder is the intended API) ---
171
+
172
+ /// `FulgurChart.render(spec_json, format, **opts)` → String.
173
+ ///
174
+ /// `format` is "svg" (→ UTF-8 String) or "png" (→ binary/ASCII-8BIT String), as a String or
175
+ /// Symbol. Unknown format → ParseError. `opts` are the RenderOptions kwargs
176
+ /// (width/height/scale/strict/dsl/font). Driven by `FulgurChart::Builder#render`, but also
177
+ /// callable directly: `FulgurChart.render(spec, :png, width: 800)`.
178
+ fn render(ruby: &Ruby, args: &[Value]) -> Result<RString, Error> {
179
+ let scanned = scan_args::<(String, Value), (), (), (), RHash, ()>(args)?;
180
+ let (spec_json, format_val) = scanned.required;
181
+ let format = coerce_string(format_val)?; // accept String or Symbol
182
+ let opts = parse_opts(ruby, scanned.keywords)?;
183
+ let ir = build_ir(ruby, &spec_json, &opts)?;
184
+
185
+ match format.as_str() {
186
+ "svg" => {
187
+ // Font present → render_chart_with_font (Err → ParseError on the SVG path);
188
+ // else the bundled-font render.
189
+ let svg = match &opts.font {
190
+ Some(bytes) => fulgur_chart::render::render_chart_with_font(&ir, bytes)
191
+ .map_err(|e| parse_err(ruby, e))?,
192
+ None => fulgur_chart::render::render_chart(&ir),
193
+ };
194
+ Ok(ruby.str_new(&svg)) // UTF-8 String
195
+ }
196
+ "png" => {
197
+ let fb: &[u8] = opts
198
+ .font
199
+ .as_deref()
200
+ .unwrap_or(fulgur_chart::font::DEFAULT_FONT);
201
+ // Invalid font on the image path → RenderError (the SVG path maps this to ParseError).
202
+ let png = fulgur_chart::raster_direct::render_chart_to_png(&ir, opts.scale, fb)
203
+ .map_err(|e| render_err(ruby, e))?;
204
+ Ok(ruby.str_from_slice(&png)) // ASCII-8BIT (BINARY) String
205
+ }
206
+ other => Err(parse_err(
207
+ ruby,
208
+ format!("unsupported format '{other}' (supported: svg, png)"),
209
+ )),
210
+ }
211
+ }
212
+
213
+ // --- public API: schema ---
214
+
215
+ /// Return the JSON Schema (compact JSON String) for the given DSL (String or Symbol).
216
+ /// Mirrors the CLI's `run_schema`; unknown DSL → ParseError (consistent with `parse_opts`).
217
+ fn schema(ruby: &Ruby, dsl: Value) -> Result<String, Error> {
218
+ let dsl = coerce_string(dsl)?;
219
+ let s = match dsl.as_str() {
220
+ "chartjs" => schemars::schema_for!(fulgur_chart::schema::ChartJsSpec),
221
+ "vegalite" => schemars::schema_for!(fulgur_chart::schema::VegaLiteSpec),
222
+ other => {
223
+ return Err(parse_err(
224
+ ruby,
225
+ format!("unsupported DSL '{other}' (supported: chartjs, vegalite)"),
226
+ ))
227
+ }
228
+ };
229
+ serde_json::to_string(&s).map_err(|e| render_err(ruby, format!("schema serialization: {e}")))
230
+ }
231
+
232
+ fn version() -> String {
233
+ fulgur_chart::version().to_string()
234
+ }
235
+
236
+ #[magnus::init]
237
+ fn init(ruby: &Ruby) -> Result<(), Error> {
238
+ // Module name is `FulgurChart` (NOT `Fulgur`): a top-level `Fulgur` would collide with the
239
+ // Fulgur PDF library if both gems are loaded in the same process.
240
+ let module = ruby.define_module("FulgurChart")?;
241
+
242
+ // Canonical error hierarchy (single source of truth). lib/fulgur_chart.rb does not redefine
243
+ // these or alias anything.
244
+ let std_err = ruby.exception_standard_error();
245
+ let parse = module.define_error("ParseError", std_err)?;
246
+ module.define_error("StrictError", parse)?;
247
+ module.define_error("RenderError", std_err)?;
248
+
249
+ module.define_module_function("version", function!(version, 0))?;
250
+ module.define_module_function("schema", function!(schema, 1))?;
251
+ // Low-level render primitive; the FulgurChart::Builder (FulgurChart.build(...)) is the
252
+ // intended API and calls this under the hood.
253
+ module.define_module_function("render", function!(render, -1))?;
254
+ Ok(())
255
+ }
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Native extension. Defines the FulgurChart module with:
4
+ # - FulgurChart.schema(dsl), FulgurChart.version
5
+ # - FulgurChart.render(spec_json, format, **opts) (low-level render primitive; the builder
6
+ # below is the intended API, but render is also callable directly)
7
+ # - errors: FulgurChart::ParseError / StrictError / RenderError
8
+ # Module name is `FulgurChart` (NOT `Fulgur`) to avoid a top-level collision with the
9
+ # Fulgur (PDF) library when both gems are loaded in the same process.
10
+ require_relative "fulgur_chart/fulgur_chart"
11
+
12
+ module FulgurChart
13
+ # Entry point for the builder API:
14
+ #
15
+ # FulgurChart.build(spec_json).width(800).dsl(:chartjs).render(:svg) # => String
16
+ # FulgurChart.build(spec_json).format(:png).render # => binary String
17
+ #
18
+ # `spec_json` is a chart.js v4 / Vega-Lite DSL JSON string. The behavior (DSL auto-detect,
19
+ # options, error classes, determinism) follows docs/binding-api-contract.md.
20
+ def self.build(spec_json)
21
+ Builder.new(spec_json)
22
+ end
23
+
24
+ # Fluent, reusable builder. Setters mutate and return self; `render` may be called multiple
25
+ # times and the builder may be reconfigured between calls.
26
+ class Builder
27
+ def initialize(spec_json)
28
+ @spec = spec_json
29
+ @opts = {}
30
+ end
31
+
32
+ # Override the chart width / height (px). Applied before input-limit validation.
33
+ def width(value)
34
+ set(:width, value)
35
+ end
36
+
37
+ def height(value)
38
+ set(:height, value)
39
+ end
40
+
41
+ # Raster scale factor (ignored when rendering SVG).
42
+ def scale(value)
43
+ set(:scale, value)
44
+ end
45
+
46
+ # Force the input DSL ("chartjs"/"vegalite" or the matching Symbol). Omit to auto-detect.
47
+ def dsl(value)
48
+ set(:dsl, value)
49
+ end
50
+
51
+ # TrueType/OpenType font bytes (binary String). Omit to use the bundled Noto Sans JP.
52
+ def font(bytes)
53
+ set(:font, bytes)
54
+ end
55
+
56
+ # Reject unknown keys. `strict` => true; `strict(false)` => false.
57
+ def strict(value = true)
58
+ set(:strict, value)
59
+ end
60
+
61
+ # Default output format for a terminal `render` with no argument (Symbol/String).
62
+ def format(value)
63
+ set(:format, value)
64
+ end
65
+
66
+ # Render to the given format ("svg"/"png" or the matching Symbol). Format precedence:
67
+ # explicit argument > `.format()` setter > default :svg. Returns a UTF-8 String for svg
68
+ # and a binary (ASCII-8BIT) String for png.
69
+ def render(fmt = nil)
70
+ # Distinguish "no explicit format" (nil) from a falsy-but-explicit one (e.g. false).
71
+ # An explicit argument always wins per the documented precedence; an invalid value is
72
+ # forwarded to the native layer to raise ParseError rather than silently falling back.
73
+ resolved =
74
+ if fmt.nil?
75
+ @opts.key?(:format) ? @opts[:format] : :svg
76
+ else
77
+ fmt
78
+ end
79
+ FulgurChart.render(@spec, resolved, **@opts.reject { |key, _| key == :format })
80
+ end
81
+
82
+ private
83
+
84
+ def set(key, value)
85
+ @opts[key] = value
86
+ self
87
+ end
88
+ end
89
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fulgur_chart
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Fulgur
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-06-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rb_sys
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.9'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.9'
27
+ description: Render chart.js / Vega-Lite specs to deterministic SVG/PNG (Rust core)
28
+ email:
29
+ executables: []
30
+ extensions:
31
+ - ext/fulgur_chart/extconf.rb
32
+ extra_rdoc_files: []
33
+ files:
34
+ - README.md
35
+ - ext/fulgur_chart/Cargo.toml
36
+ - ext/fulgur_chart/extconf.rb
37
+ - ext/fulgur_chart/src/lib.rs
38
+ - lib/fulgur_chart.rb
39
+ homepage: https://github.com/fulgur-rs/fulgur-chart
40
+ licenses:
41
+ - MIT OR Apache-2.0
42
+ metadata:
43
+ homepage_uri: https://github.com/fulgur-rs/fulgur-chart
44
+ source_code_uri: https://github.com/fulgur-rs/fulgur-chart
45
+ changelog_uri: https://github.com/fulgur-rs/fulgur-chart/blob/main/crates/fulgur-chart/CHANGELOG.md
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubygems_version: 3.5.22
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Render chart.js / Vega-Lite specs to deterministic SVG/PNG (Rust core)
65
+ test_files: []