Biblioteca estándar
CSV, estadística y gráficos
-- Doc example: CSV parsing, descriptive statistics and native SVG charts (all pure).
intent: "doc example: csv, stats and charts"
let raw be "month,total\njan,10\nfeb,25\nmar,17\napr,25\n"
let rows be csv_parse(raw, {"types": {"total": "int"}})
let svg be chart_svg("bar", rows, {"x": "month", "y": "total", "title": "Sales"})
print("rows = " + text(length(rows)) + ", median = " + text(median([10, 25, 17, 25])) + ", svg = " + text(starts_with(svg, "<svg")))
test "csv_parse: rows are maps (same shape sql() returns), lossless by default"
let plain be csv_parse("a,b\n00123,x\n")
assert_eq(plain[0]["a"], "00123")
assert_eq(type_of(plain[0]["a"]), "text")
assert_eq(rows[1]["total"], 25)
test "csv round-trip survives embedded commas and quotes"
let tricky be [{"name": "a,b", "note": "say \"hi\""}]
assert_eq(csv_parse(csv_encode(tricky)), tricky)
test "median / percentile / histogram (NumPy semantics)"
assert_eq(median([1, 2, 3, 4]), 2.5)
assert_eq(percentile([1, 2, 3, 4], 25), 1.75)
let h be histogram(range(10), 5)
assert_eq(h["counts"], [2, 2, 2, 2, 2])
assert_eq(length(h["edges"]), 6)
test "chart_svg is deterministic and escapes data text"
assert(contains(svg, "<svg"))
assert_eq(svg, chart_svg("bar", rows, {"x": "month", "y": "total", "title": "Sales"}))
let hostile be chart_svg("pie", {"<b>x</b>": 4, "y": 2})
assert(contains(hostile, "<b>"))
test "chart errors are clear and catchable"
let msg be ""
try
chart_svg("bar", [])
recover e
set msg to e
assert(contains(msg, "data is empty"))
test "every chart option, exercised: title/x_label/y_label/legend/colors/width/height/background"
let multi be [{"m": "a", "sales": 10, "costs": 6}, {"m": "b", "sales": 25, "costs": 12}]
let full be chart_svg("bar", multi, {
"x": "m", "y": ["sales", "costs"],
"title": "Sales vs costs", "x_label": "Month", "y_label": "USD",
"legend": true, "colors": ["#112233", "#445566"],
"width": 400, "height": 300, "background": "#fcfcfb"
})
assert(contains(full, "Sales vs costs"))
assert(contains(full, "Month") and contains(full, "USD"))
assert(contains(full, "sales") and contains(full, "costs"))
assert(contains(full, "#112233") and contains(full, "#445566"))
assert(contains(full, "viewBox=\"0 0 400 300\""))
-- legend: false hides the series names; then the other data shapes:
assert(contains(chart_svg("line", multi, {"x": "m", "y": "sales", "legend": false}), ">sales<") == false)
assert(contains(chart_svg("pie", {"a": 3, "b": 1}), "<path"))
assert(contains(chart_svg("line", [1, 2, 3]), "<polyline"))
assert(contains(chart_svg("scatter", [[1, 2], [3, 4]]), "<circle"))
test "csv options, exercised: headers/delimiter/numbers/eol"
let semi be csv_parse("a;b\n1;2\n", {"delimiter": ";", "numbers": true})
assert_eq(semi[0]["b"], 2)
let positional be csv_parse("1,2\n3,4\n", {"headers": false})
assert_eq(positional[1][0], "3")
assert_eq(csv_encode([{"a": "1", "b": "2"}], {"headers": ["b"], "eol": "\n"}), "b\n2\n")
test "histogram bins: integer or explicit edges (last bin closed, out-of-range dropped)"
assert_eq(histogram([1, 2, 3, 4], [0, 2, 4])["counts"], [1, 3])
assert_eq(histogram([-5, 1, 99], [0, 2])["counts"], [1])
test "business kinds: area/heatmap/histogram/boxplot/donut/waterfall + stack"
let multi be [{"m": "a", "sales": 10, "costs": 6}, {"m": "b", "sales": 25, "costs": 12}]
-- stack is an OPT on bar/area (there is no "stacked_bar" kind)
assert(contains(chart_svg("bar", multi, {"x": "m", "y": ["sales", "costs"], "stack": true}), "<rect"))
assert(contains(chart_svg("area", multi, {"x": "m", "y": ["sales", "costs"], "stack": true}), "<polygon"))
-- heatmap: tidy rows (x/y/value) or a matrix (+ x_labels/y_labels); scale auto/sequential/diverging
let cells be [{"d": "mon", "h": "9", "v": 1}, {"d": "mon", "h": "10", "v": 5}, {"d": "tue", "h": "9", "v": 9}]
assert(contains(chart_svg("heatmap", cells, {"x": "h", "y": "d", "value": "v"}), "<rect"))
let diverging be chart_svg("heatmap", [[-8, 0], [4, 9]], {"x_labels": ["q1", "q2"], "y_labels": ["a", "b"], "scale": "diverging", "center": 0})
assert(contains(diverging, "<rect"))
-- histogram plots raw numbers OR the exact map histogram() returns (same binning)
let data be [1, 2, 2, 3, 3, 3, 9]
assert_eq(chart_svg("histogram", data, {"bins": 4}), chart_svg("histogram", histogram(data, 4)))
-- boxplot: >= 2 values per group; quartiles match percentile()
assert(contains(chart_svg("boxplot", {"web": [1, 2, 3, 4, 100], "tel": [2, 3, 8, 9]}), "<circle"))
-- donut = pie with a hole (same rules); waterfall takes DELTAS, total is opt-in
assert(contains(chart_svg("donut", {"a": 4, "b": 2}), "<path"))
assert(contains(chart_svg("waterfall", {"sales": 100, "costs": -40}, {"total": true}), ">Total<"))
test "theme dark + kind/opt validation errors are clear and catchable"
let multi be [{"m": "a", "sales": 10, "costs": 6}, {"m": "b", "sales": 25, "costs": 12}]
let dark be chart_svg("bar", multi, {"x": "m", "y": "sales", "theme": "dark"})
assert(contains(dark, "<svg"))
assert(dark != chart_svg("bar", multi, {"x": "m", "y": "sales"}))
let msg be ""
try
chart_svg("treemap", [1])
recover e
set msg to e
assert(contains(msg, "valid kinds are: area, bar, boxplot, donut, heatmap, histogram, line, pie, scatter, waterfall"))
set msg to ""
try
chart_svg("pie", {"a": 1}, {"stack": true})
recover e
set msg to e
assert(contains(msg, "bar, area"))
set msg to ""
try
chart_svg("heatmap", [[1]], {"center": 5})
recover e
set msg to e
assert(contains(msg, "diverging"))
test "svg_to_png / svg_to_pdf: every option, real deterministic bytes"
let png be svg_to_png(svg, {"scale": 2})
assert(contains(decode(png, "hex"), "89504e470d0a1a0a"))
assert_eq(png, svg_to_png(svg, {"scale": 2}))
let sized be svg_to_png(svg, {"width": 320, "background": "#ffffff", "max_pixels": 1000000})
assert(contains(decode(sized, "hex"), "89504e470d0a1a0a"))
let pdf be svg_to_pdf(svg, {"width": 400})
assert(contains(decode(pdf, "hex"), "255044462d"))
-- the anti-DoS ceiling names its option:
let msg be ""
try
svg_to_png(svg, {"width": 10000000})
recover e
set msg to e
assert(contains(msg, "max_pixels"))
test "stats: percentile 0..100, quantile 0..1, std sample, nothing skipped, NaN propagates"
assert_eq(quantile([1, 2, 3, 4], 0.25), 1.75)
assert_eq(percentile([1, 2, 3, 4], 50), quantile([1, 2, 3, 4], 0.5))
assert_error(() => percentile([1, 2], 101))
assert_error(() => quantile([1, 2], 50))
assert_eq(std([1, 2, 3, 4]), 1.2909944487358056)
assert_eq(std([1, 2, 3, 4], ddof = 0), 1.118033988749895)
assert_eq(median([1, nothing, 3]), 2.0)
assert(is_nan(median([1, nan, 3])))
assert_error(() => histogram([1, nothing, 3])) -- histogram takes numbers only
assert_error(() => histogram([1, nan, 3]))
assert_error(() => histogram([]))
test "charts take summarize output; a nothing in a numeric field is an error"
let sales be [{"r": "n", "v": 10}, {"r": "s", "v": 5}, {"r": "n", "v": 1}]
let by_r be summarize(sales, "r", {"total": sum_of("v")})
assert(contains(chart_svg("bar", by_r, {"x": "r", "y": "total"}), "<rect"))
assert_error(() => chart_svg("bar", [{"x": "a", "y": nothing}], {"x": "x", "y": "y"}))
assert(contains(chart_svg("bar", fill_missing([{"x": "a", "y": nothing}], 0), {"x": "x", "y": "y"}), "<svg"))
-- The message of an error, or nothing when the call did not fail.
task error_of(f)
try
f()
recover e
give e
give nothing
test "x_axis rotates, thins and formats the category labels (engine v0.6.45+)"
let days be apply((i) => {"day": add_days(date(2026, 1, 1), i), "n": i}, range(0, 30))
let c be chart_svg("bar", days, {"x": "day", "y": "n", "x_axis": {"angle": 30, "every": 7, "format": "%d/%m"}})
assert(contains(c, ">08/01<")) -- every 7th label, formatted with strftime
assert(not contains(c, ">02/01<")) -- the ones in between are skipped
assert(contains(c, "rotate(-30 "))
-- without x_axis the SVG is byte for byte the one it always was
assert_eq(svg, chart_svg("bar", rows, {"x": "month", "y": "total", "title": "Sales"}))
assert(contains(error_of(() => chart_svg("pie", {"a": 1}, {"x_axis": {"angle": 45}})), "only applies to: bar, line, area, heatmap, boxplot, waterfall"))
assert(contains(error_of(() => chart_svg("bar", rows, {"x": "month", "y": "total", "x_axis": {"angel": 45}})), "unknown x_axis key \"angel\" (valid: angle, every, format)"))
test "qr_matrix / qr_svg encode a QR code (ISO/IEC 18004)"
let m be qr_matrix("https://synsema.dev")
assert_eq(length(m), length(m[0])) -- square: rows of 0/1
assert_eq(m[0][0], 1) -- the finder pattern's corner
assert_eq(length(qr_matrix("hi", {"version": 5})), 37) -- version v is 17 + 4v modules a side
let q be qr_svg("hi", {"ecc": "H", "module": 4, "margin": 4, "dark": "#112233", "light": "#ffffff"})
assert(starts_with(q, "<svg"))
assert(contains(q, "#112233"))
assert_eq(q, qr_svg("hi", {"ecc": "H", "module": 4, "margin": 4, "dark": "#112233", "light": "#ffffff"}))
assert_eq(error_of(() => qr_svg("hi", {"ecc": "X"})), "qr_svg: ecc must be \"L\", \"M\", \"Q\" or \"H\", got \"X\"")
assert(contains(error_of(() => qr_svg("hi", {"size": 4})), "valid: ecc, version, module, margin, dark, light"))
-- the SVG converts like any other
assert(contains(decode(svg_to_png(q), "hex"), "89504e470d0a1a0a"))
test "svg_to_png refuses a <text> over 16 384 characters unless you raise max_text_chars"
let long be join(apply((i) => "a", range(0, 20000)), "")
let doc be `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="20"><text x="0" y="15">{long}</text></svg>`
let e be error_of(() => svg_to_png(doc))
assert(contains(e, "a <text> has 20000 characters, above the 16384 character limit"))
assert(contains(e, "max_text_chars"))
assert_eq(type_of(svg_to_png(doc, {"max_text_chars": 20000})), "bytes")El pipeline de reportes es nativo y puro (sin capability — también funciona dentro de sandbox): datos → agregación → gráfico. Es agnóstico de la fuente de datos: todo consume valores planos, así que las filas de sql(), mongo_find, csv_parse, parquet_read, summarize o un literal se grafican igual.
CSV§
csv_parse(text, opts?) devuelve una lista de maps (primera fila = cabeceras — la misma forma que devuelve sql()), así que un CSV alimenta summarize y los gráficos directamente. Tipá las columnas que necesitás; un campo vacío es nothing (faltante):
let rows be csv_parse(read_file("sales.csv"), {"types": {"month": "text", "total": "decimal"}})
write_file("out.csv", csv_encode(rows))
Todas las opciones de csv_parse y csv_encode (types, missing, headers, delimiter, numbers, eol, escape_formulas), Parquet y JSON Lines están en Archivos e I/O. Agrupar, juntar y pivotear filas antes de graficarlas: Análisis de datos.
Estadística descriptiva§
median(x), percentile(x, p), quantile(x, q), std(x), var(x) e histogram(x, bins?) funcionan sobre una lista de números o un array numérico, con semántica NumPy.
percentile(x, p)tomapen 0–100;quantile(x, q)tomaqen 0–1. Los dos interpolan linealmente;percentile(x, 50) == quantile(x, 0.5) == median(x). Unpfuera de su rango es un error que nombra la otra función.stdyvarson estadísticos muestrales (ddof = 1, como pandas yDESVEST.Mde Excel); la cifra poblacional esstd(x, ddof = 0).- Las reducciones saltean
nothing(faltante) y propagan NaN:median([1, nothing, 3])→2.0,median([1, nan, 3])→nan. Las reglas completas, conaxis =y decimales exactos: Bytes, matemática y arrays. histogramtoma solo números: unnothingo un NaN en sus datos es un error (drop_missing/fill_nanantes).binses un entero (default 10, equiespaciado sobre[min, max]) o una lista explícita de bordes crecientes; el resultado es{"counts": [..], "edges": [..]}conlength(edges) == length(counts) + 1, el último bin cerrado, y los valores fuera de rango se descartan cuando los bordes son explícitos. Datos vacíos → error claro.- También:
mode,corr,cov,polyfit/polyval,lstsq(Bytes, matemática y arrays).
Gráficos§
chart_svg(kind, data, opts?) devuelve texto SVG plano — incrustalo con { raw svg } en un template de render(), servilo con respond(svg, "image/svg+xml") o guardalo con write_file. Determinista: mismo input, salida idéntica byte a byte.
Kinds (un kind desconocido da error listando exactamente este set): "area", "bar", "boxplot", "donut", "heatmap", "histogram", "line", "pie", "scatter", "waterfall". No existe un kind "stacked_bar": el apilado es la opción {"stack": true} sobre bar/area.
Formas de datos: lista de mapas + {"x": "campo", "y": "campo"} (multi-serie: "y": [..]; heatmap usa {"x", "y", "value"}; boxplot agrupa por x), mapa label→valor (bar/line/area/pie/donut; en waterfall el valor es el DELTA, no el acumulado; en boxplot es label→lista de números), lista de números (bar/line/area/histogram, x = índice; boxplot = una sola caja), pares [x, y] (line/scatter/area), array 1-D, matriz (lista de listas o array 2-D — solo heatmap; filas = y, columnas = x, con x_labels/y_labels opcionales), o el mapa {"counts", "edges"} que devuelve histogram() (solo el kind histogram, que comparte el binning con el builtin). Pie/donut toman una sola serie de valores no negativos.
Opciones comunes a todos los kinds (cada nombre está verificado por el doctest):
| Opción | Significado |
|---|---|
"title" | título del gráfico (también el <title> accesible del SVG) |
"x" / "y" | nombres de campo cuando data es lista de mapas; "y" puede ser una lista → una serie por campo. Con cualquier otra forma de datos dan error en vez de ignorarse en silencio |
"x_label" / "y_label" | labels de los ejes |
"legend" | true/false; default: se muestra automáticamente con ≥2 series o pie/donut. En heatmap la leyenda es la barra de gradiente |
"width" / "height" | lienzo en px (defaults 640×360) |
"colors" | lista de colores hex que reemplaza la paleta default. En heatmap: ≥2 stops del gradiente; en waterfall: [sube, baja, total] |
"background" | relleno hex (default: transparente) |
"theme" | "light" (default) o "dark" — series aclaradas, tinta/grid/escalas para fondo oscuro. Un "colors" explícito le gana al tema |
Opciones por kind (usarlas en el kind equivocado da error nombrando cuáles sí las aceptan; una opción desconocida da error listando todas las válidas — un typo nunca pasa en silencio):
| Kind | Opciones extra | Semántica |
|---|---|---|
bar, area | "stack" | true apila las series. Bar: positivos hacia arriba, negativos hacia abajo. Area apilada con signos mezclados en un mismo x → error (sugiere bar apilado). Una sola serie + stack = no-op inofensivo |
boxplot | — (x/y = grupo/valor) | Tukey: caja q1–q3 (la misma interpolación lineal de percentile()), bigotes a 1,5×IQR, outliers como puntos. Mínimo 2 valores por grupo |
heatmap | "value", "x_labels", "y_labels", "scale", "center" | "scale": "auto" (default: secuencial si todos los valores tienen el mismo signo; divergente centrada en 0 si cruzan el cero), "sequential" o "diverging". "center" exige {"scale": "diverging"} explícito. Celda ausente (forma tidy) = transparente; celda duplicada → error |
histogram | "bins" | entero (default 10) o lista de bordes crecientes — un float como 4.0 da error. chart_svg("histogram", datos, {"bins": n}) produce el mismo SVG que chart_svg("histogram", histogram(datos, n)) |
waterfall | "total" | los valores son deltas; el acumulado se calcula solo. true agrega la barra "Total" (o pasá un texto como label). Delta 0 es válido. Colores semánticos CVD-safe: azul sube / naranja baja / tinta el total (no verde/rojo — sobreescribible con "colors") |
bar, line, area, heatmap, boxplot, waterfall | "x_axis" (motor v0.6.45+) | las etiquetas de categoría bajo el eje X: {"angle": 45, "every": 2, "format": "%b %d"}. angle las rota (0–90 grados); every muestra una etiqueta de cada n (sin él, el salteo automático de siempre, unas 12 visibles); format es el strftime de format_time, aplicado cuando los valores de x son fechas o datetimes. Cada clave es opcional; una clave desconocida es un error que lista angle, every, format. Sin x_axis el SVG es byte a byte el de antes |
Los defaults incluyen una paleta de 8 colores segura para daltonismo en orden fijo (más de 8 series/slices es un error — los colores nunca se ciclan; agrupá en "Other" o pasá {"colors": [..]}), un solo eje Y, barras que siempre incluyen el cero, y escapado XSS-safe de todo texto de datos. NaN/infinitos en los datos ploteados → error claro, y también un nothing en un campo numérico — drop_missing(rows, "col") o fill_missing antes. Un secret como label sale [redacted]; como valor numérico es error de tipo.
Gráficos que los agentes pueden leer§
Dentro de content(), el nodo chart(...) negocia por cliente — misma URL:
- HTML → el SVG inline
- Markdown (
.mdoAccept: text/markdown) → el título del gráfico + una tabla con los datos - JSON (
.json) →{"type": "chart", "kind", "title", ...}+ los campos de datos del kind
Salida exacta para agentes, por kind (las cabeceras de las tablas Markdown están en inglés aunque tus datos no lo estén — son output de runtime, estables para parsear):
| Kind | Markdown | Campos JSON |
|---|---|---|
| bar/line/pie/scatter/area/donut | tabla: columna x + una por serie | "series": [{"name", "points": [[x, y], ..]}] (+ "stack": true solo si está apilado) |
| heatmap | matriz: filas = y_labels, columnas = x_labels | "x_labels", "y_labels", "values": [[..]] (celda ausente = null) |
| histogram | | range | count | — rangos [a, b), el último [a, b] | "counts", "edges" |
| boxplot | | group | min | q1 | median | q3 | max | outliers | | "groups": [{"name", "min", "q1", "median", "q3", "max", "outliers": [..]}] |
| waterfall | | label | delta | running | (+ fila de total si se pidió) | "steps": [{"label", "delta", "running"}], "total" |
route "GET /report/:name"
let rows be sql("SELECT month, total FROM sales ORDER BY month")
give content(page([
heading(1, "Sales 2026"),
chart("bar", rows, {"x": "month", "y": "total", "title": "Sales by month"})
], {"title": "Report"}))
Un humano ve el gráfico; un agente que pide .md obtiene los números. Ningún otro lenguaje hace esto de forma nativa.
Export PNG / PDF§
svg_to_png(svg, opts?) y svg_to_pdf(svg, opts?) convierten cualquier texto SVG (un gráfico, un SVG a mano) a bytes — para adjuntos de email, descargas o impresión:
write_file("report.png", svg_to_png(svg, {"scale": 2})) -- needs file.write
route "GET /report.pdf"
give binary(svg_to_pdf(svg), "application/pdf")
- Opts de PNG (todas verificadas por el doctest):
"width"/"height"en px (una sola mantiene el aspecto),"scale"(p. ej.2para retina — entra en conflicto con width/height, error explícito),"background"(hex; default transparente),"max_pixels"(techo de seguridad sobreescribible, default ~16,7M — el error nombra la opción),"max_text_chars"y"fonts"(las dos abajo). "max_text_chars"(motor v0.6.45+, PNG y PDF): un<text>de más de 16 384 caracteres se rechaza — contando sus<tspan>y el texto que trae un<tref>, con las entidades expandidas, también adentro de un SVG embebido en una URLdata:(y para los íconos desynsema build --iconysynsema init --pwa). El renderizador acomoda un texto en un tiempo que crece con el cuadrado de su largo: 16 k caracteres tardan 0,3 s, 64 k 2,6 s, 128 k 10 s, 400 k varios minutos — un título que viene en los datos podía tener ocupado un worker deservedurante minutos. El error dice cuántos caracteres hay:svg_to_png: a <text> has 20000 characters, above the 16384 character limit (its layout time grows with the square of its length); split it into several <text> or raise it with {"max_text_chars": n}. El límite es por<text>, así que un documento largo con un<text>por línea no se ve afectado. El contenido que el renderizador dibuja muchas veces (<use>, markers, máscaras) multiplica el costo del texto que tiene adentro; un límite para eso llega en una versión próxima.- Opts de PDF:
"width"/"height"en puntos — una sola escala proporcionalmente; las dos juntas deben respetar el aspecto del SVG (error claro si no). El PDF es vectorial de una página: nítido a cualquier zoom. - Determinista: una fuente sans embebida (DejaVu Sans), así el texto rasteriza idéntico en toda plataforma. Mismo input → salida idéntica byte a byte. Tus propias fuentes (engine v0.6.20+):
opts.fontses una lista de rutas.ttf/.otf— cada una necesitafile.read(una fuente empaquetada porsynsema build, ninguna) — cargadas solo para esa llamada, así que unfont-family="Arial"del SVG resuelve cuando pasásC:/Windows/Fonts/arial.ttf; las familias desconocidas siguen cayendo a DejaVu. Mismo SVG + mismas fuentes → mismos bytes. - Fallback por glifo (motor v0.6.45+): con
opts.fonts, un carácter que a tu fuente le falta (una flecha, un emoji, un carácter CJK) cae solo, y el resto del texto conserva tu fuente, como en un navegador (antes, todo el tramo salía en la fuente de fallback). Sinopts.fontsla salida es byte a byte la de antes. Cuando el acomodo por glifo dejaría un glifo faltante (tu fuente forma una ligadura o una marca combinante en ese texto, o el fallback es una colección.ttc), todo el documento se acomoda como antes, donde el fallback reemplaza el tramo. - Límites de fuentes (motor v0.6.45+): como mucho 16 fuentes con 64 caras en total por llamada (una colección cuenta todas sus caras: pasá las caras sueltas que usás en vez de una "Super OTC" entera), y como mucho 16 fuentes de fallback probadas para un tramo de texto (más allá, el resto sale como glifos faltantes). Una fuente que haría lenta cada búsqueda (más de 16 subtablas de character map, una subtabla formato 13 con más de 4096 grupos, una cara con más de 64 tablas) se rechaza, y el error nombra la fuente. Las fuentes reales — una colección CJK por peso, emoji de color, fuentes variables, la LastResort de Apple — se aceptan.
- Seguro por construcción: un
<script>embebido jamás se ejecuta, y un<image href>externo (URLs o rutas locales) nunca se descarga — un builtin puro no toca red ni disco. Ambos son puros, así que también funcionan dentro desandbox. - Límites honestos: el PNG es el estado estático (scripts/animaciones se ignoran); las familias de fuente desconocidas caen a la embebida; los glifos que le faltan (CJK completo, emoji de color) salen como tofu, salvo que pases en
opts.fontsuna fuente que los tenga.
Códigos QR (motor v0.6.45+)§
qr_matrix(data, opts?) codifica un texto como código QR (ISO/IEC 18004) y devuelve sus módulos como filas de 0/1 (1 es oscuro), sin la zona de silencio. qr_svg(data, opts?) lo dibuja como texto SVG, con la zona de silencio, listo para svg_to_png/svg_to_pdf, una página o un template. Los dos son puros y deterministas.
"ecc": corrección de errores"L","M"(default),"Q"o"H"— un nivel más alto aguanta más daño y necesita más módulos."version": de 1 a 40 (un código de versión v tiene 17 + 4v módulos de lado); sin ella, la más chica que entra.- Solo
qr_svg:"module"(px por módulo),"margin"(la zona de silencio, en módulos),"dark"y"light"(colores hex).
Una opción desconocida es un error que lista las válidas (valid: ecc, version, module, margin, dark, light); un valor malo dice qué acepta (qr_svg: ecc must be "L", "M", "Q" or "H", got "X").
let link be "https://synsema.dev"
let rows be qr_matrix(link) -- [[1, 1, 1, …], …]: 1 = dark module
let svg be qr_svg(link, {"ecc": "Q", "module": 8, "margin": 4, "dark": "#111111", "light": "#ffffff"})
let png be svg_to_png(svg) -- a PNG for an email or a label printer
print(length(rows)) -- 25: modules a side