typstify 0.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 +7 -0
- data/CHANGELOG.md +49 -0
- data/LICENSE +21 -0
- data/README.md +235 -0
- data/SECURITY.md +47 -0
- data/fonts/inter/Inter-Regular.ttf +0 -0
- data/fonts/inter/Inter-SemiBold.ttf +0 -0
- data/fonts/inter/OFL.txt +92 -0
- data/lib/generators/typstify/install/install_generator.rb +39 -0
- data/lib/generators/typstify/install/templates/typstify.rb +43 -0
- data/lib/generators/typstify/template/template_generator.rb +67 -0
- data/lib/tasks/typstify.rake +43 -0
- data/lib/typstify/adapter.rb +56 -0
- data/lib/typstify/config.rb +106 -0
- data/lib/typstify/data.rb +103 -0
- data/lib/typstify/document.rb +63 -0
- data/lib/typstify/engine.rb +38 -0
- data/lib/typstify/erb_pipeline.rb +44 -0
- data/lib/typstify/errors.rb +68 -0
- data/lib/typstify/escaping.rb +45 -0
- data/lib/typstify/fonts.rb +175 -0
- data/lib/typstify/renderer.rb +43 -0
- data/lib/typstify/resolver.rb +62 -0
- data/lib/typstify/version.rb +5 -0
- data/lib/typstify/warnings.rb +65 -0
- data/lib/typstify/workspace.rb +85 -0
- data/lib/typstify.rb +104 -0
- data/templates/certificate/certificate.typ +70 -0
- data/templates/certificate/sample_data.json +13 -0
- data/templates/invoice/invoice.typ +57 -0
- data/templates/invoice/sample_data.json +21 -0
- data/templates/receipt/receipt.typ +69 -0
- data/templates/receipt/sample_data.json +17 -0
- data/templates/report/report.typ +103 -0
- data/templates/report/sample_data.json +49 -0
- data/templates/shared/branding.typ +140 -0
- metadata +164 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"number": "INV-2043",
|
|
3
|
+
"subtitle": "Thanks for your business",
|
|
4
|
+
"issued_on": "12 March 2026",
|
|
5
|
+
"due_on": "11 April 2026",
|
|
6
|
+
"total": "$4,860.00",
|
|
7
|
+
"bill_to": ["Northwind Trading Ltd.", "48 Harbour Road", "Bristol BS1 5TY", "United Kingdom"],
|
|
8
|
+
"bill_from": ["Your Company", "12 Example Street", "London EC1A 1AA", "VAT GB123456789"],
|
|
9
|
+
"line_items": [
|
|
10
|
+
{ "name": "Platform subscription — March", "qty": "1", "unit_price": "$2,400.00", "amount": "$2,400.00" },
|
|
11
|
+
{ "name": "Additional seats", "qty": "12", "unit_price": "$95.00", "amount": "$1,140.00" },
|
|
12
|
+
{ "name": "Onboarding workshop", "qty": "4", "unit_price": "$210.00", "amount": "$840.00" },
|
|
13
|
+
{ "name": "Priority support", "qty": "1", "unit_price": "$480.00", "amount": "$480.00" }
|
|
14
|
+
],
|
|
15
|
+
"summary": [
|
|
16
|
+
{ "label": "Subtotal", "value": "$4,860.00" },
|
|
17
|
+
{ "label": "VAT (0%)", "value": "$0.00" },
|
|
18
|
+
{ "label": "Paid to date", "value": "$0.00" }
|
|
19
|
+
],
|
|
20
|
+
"notes": "Payment by bank transfer within 30 days. Quote invoice number INV-2043 as the reference."
|
|
21
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Receipt — Typstify starter template.
|
|
2
|
+
//
|
|
3
|
+
// A payment confirmation: narrower page, no billing terms, a paid stamp.
|
|
4
|
+
// Data mode; see templates/invoice for the fuller commentary.
|
|
5
|
+
|
|
6
|
+
#import "shared/branding.typ": *
|
|
7
|
+
|
|
8
|
+
#let data = json("data.json")
|
|
9
|
+
|
|
10
|
+
#show: brand-page
|
|
11
|
+
|
|
12
|
+
#set page(paper: "a5", margin: (x: 14mm, y: 16mm))
|
|
13
|
+
|
|
14
|
+
#brand-header(
|
|
15
|
+
title: "Receipt",
|
|
16
|
+
subtitle: data.at("subtitle", default: none),
|
|
17
|
+
meta: (
|
|
18
|
+
("Receipt no.", data.number),
|
|
19
|
+
("Paid on", data.paid_on),
|
|
20
|
+
),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
#block(
|
|
24
|
+
width: 100%,
|
|
25
|
+
fill: rgb("#ecfdf5"),
|
|
26
|
+
stroke: 0.5pt + rgb("#a7f3d0"),
|
|
27
|
+
radius: 3pt,
|
|
28
|
+
inset: 10pt,
|
|
29
|
+
)[
|
|
30
|
+
#grid(
|
|
31
|
+
columns: (1fr, auto),
|
|
32
|
+
align: (left + horizon, right + horizon),
|
|
33
|
+
[
|
|
34
|
+
#text(size: 8pt, fill: rgb("#047857"))[#upper("Paid in full")]
|
|
35
|
+
#linebreak()
|
|
36
|
+
#text(size: 9pt, fill: brand.muted)[#data.method]
|
|
37
|
+
],
|
|
38
|
+
text(size: 16pt, weight: "semibold", fill: rgb("#047857"))[#data.total],
|
|
39
|
+
)
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
#v(12pt)
|
|
43
|
+
|
|
44
|
+
#brand-address(label: "Received from", lines: data.received_from)
|
|
45
|
+
|
|
46
|
+
#v(12pt)
|
|
47
|
+
|
|
48
|
+
#brand-table(
|
|
49
|
+
columns: (1fr, auto, auto),
|
|
50
|
+
aligns: (left, right, right),
|
|
51
|
+
header: ("Description", "Qty", "Amount"),
|
|
52
|
+
rows: data.line_items.map(item => (
|
|
53
|
+
[#item.name],
|
|
54
|
+
[#item.qty],
|
|
55
|
+
[#item.amount],
|
|
56
|
+
)),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
#v(10pt)
|
|
60
|
+
|
|
61
|
+
#brand-totals(
|
|
62
|
+
rows: data.at("summary", default: ()).map(row => (row.label, row.value)),
|
|
63
|
+
emphasis: ("Total paid", data.total),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
#if data.at("notes", default: none) != none [
|
|
67
|
+
#v(12pt)
|
|
68
|
+
#brand-note[#data.notes]
|
|
69
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"number": "RCP-8871",
|
|
3
|
+
"subtitle": "No payment is due",
|
|
4
|
+
"paid_on": "3 February 2026",
|
|
5
|
+
"method": "Visa ending 4242",
|
|
6
|
+
"total": "$1,290.00",
|
|
7
|
+
"received_from": ["Ada Okonkwo", "Harbour Analytics", "Dublin, Ireland"],
|
|
8
|
+
"line_items": [
|
|
9
|
+
{ "name": "Annual plan", "qty": "1", "amount": "$1,200.00" },
|
|
10
|
+
{ "name": "Extra storage (50 GB)", "qty": "1", "amount": "$90.00" }
|
|
11
|
+
],
|
|
12
|
+
"summary": [
|
|
13
|
+
{ "label": "Subtotal", "value": "$1,290.00" },
|
|
14
|
+
{ "label": "Tax", "value": "$0.00" }
|
|
15
|
+
],
|
|
16
|
+
"notes": "Keep this receipt for your records. Questions? Reply to your confirmation email."
|
|
17
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Report — Typstify starter template.
|
|
2
|
+
//
|
|
3
|
+
// A multi-page document: cover block, headline metrics, a data table and
|
|
4
|
+
// free-form sections. This is the one that shows why Typst beats a coordinate
|
|
5
|
+
// DSL — pagination, running headers and a table of contents come for free.
|
|
6
|
+
//
|
|
7
|
+
// Data mode; see templates/invoice for the fuller commentary.
|
|
8
|
+
|
|
9
|
+
#import "shared/branding.typ": *
|
|
10
|
+
|
|
11
|
+
#let data = json("data.json")
|
|
12
|
+
|
|
13
|
+
#show: brand-page
|
|
14
|
+
|
|
15
|
+
#brand-header(
|
|
16
|
+
title: data.title,
|
|
17
|
+
subtitle: data.at("subtitle", default: none),
|
|
18
|
+
meta: (
|
|
19
|
+
("Period", data.period),
|
|
20
|
+
("Prepared for", data.prepared_for),
|
|
21
|
+
("Generated", data.generated_on),
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
// Headline metrics.
|
|
26
|
+
#grid(
|
|
27
|
+
columns: data.metrics.len(),
|
|
28
|
+
gutter: 10pt,
|
|
29
|
+
..data.metrics.map(metric => block(
|
|
30
|
+
width: 100%,
|
|
31
|
+
fill: rgb("#f9fafb"),
|
|
32
|
+
stroke: 0.5pt + brand.rule,
|
|
33
|
+
radius: 3pt,
|
|
34
|
+
inset: 10pt,
|
|
35
|
+
)[
|
|
36
|
+
#text(size: 8pt, fill: brand.muted)[#upper(metric.label)]
|
|
37
|
+
#linebreak()
|
|
38
|
+
#text(size: 18pt, weight: "semibold")[#metric.value]
|
|
39
|
+
#linebreak()
|
|
40
|
+
// Always a third line, present or not, so the cards line up. `height: 100%`
|
|
41
|
+
// is the obvious alternative and the wrong one: inside a grid it resolves
|
|
42
|
+
// against the page, not the row, and pushes the whole report onto page two.
|
|
43
|
+
#if metric.at("change", default: none) == none [
|
|
44
|
+
#text(size: 9pt)[ ]
|
|
45
|
+
] else [
|
|
46
|
+
#text(size: 9pt, fill: if metric.at("positive", default: true) {
|
|
47
|
+
rgb("#047857")
|
|
48
|
+
} else {
|
|
49
|
+
rgb("#b91c1c")
|
|
50
|
+
})[#metric.change]
|
|
51
|
+
]
|
|
52
|
+
]),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
#v(16pt)
|
|
56
|
+
|
|
57
|
+
#heading(level: 1, outlined: false)[#data.table.heading]
|
|
58
|
+
#v(2pt)
|
|
59
|
+
|
|
60
|
+
#brand-table(
|
|
61
|
+
columns: (1fr,) + data.table.columns.slice(1).map(_ => auto),
|
|
62
|
+
aligns: (left,) + data.table.columns.slice(1).map(_ => right),
|
|
63
|
+
header: data.table.columns,
|
|
64
|
+
rows: data.table.rows.map(row => row.map(cell => [#cell])),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
// A simple bar chart, drawn with rectangles. No plotting library, no browser.
|
|
68
|
+
#if data.at("bars", default: none) != none [
|
|
69
|
+
#v(16pt)
|
|
70
|
+
#heading(level: 1, outlined: false)[#data.bars.heading]
|
|
71
|
+
#v(6pt)
|
|
72
|
+
#let peak = calc.max(..data.bars.series.map(bar => bar.value))
|
|
73
|
+
#for bar in data.bars.series [
|
|
74
|
+
#grid(
|
|
75
|
+
columns: (28mm, 1fr, 18mm),
|
|
76
|
+
gutter: 8pt,
|
|
77
|
+
align: (left + horizon, left + horizon, right + horizon),
|
|
78
|
+
text(size: 9pt)[#bar.label],
|
|
79
|
+
box(width: 100%)[
|
|
80
|
+
#rect(
|
|
81
|
+
width: 100% * (bar.value / peak),
|
|
82
|
+
height: 8pt,
|
|
83
|
+
radius: 2pt,
|
|
84
|
+
fill: brand.accent,
|
|
85
|
+
stroke: none,
|
|
86
|
+
)
|
|
87
|
+
],
|
|
88
|
+
text(size: 9pt, fill: brand.muted)[#bar.display],
|
|
89
|
+
)
|
|
90
|
+
#v(4pt)
|
|
91
|
+
]
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
#for section in data.sections [
|
|
95
|
+
#v(14pt)
|
|
96
|
+
#heading(level: 1, outlined: false)[#section.heading]
|
|
97
|
+
#v(2pt)
|
|
98
|
+
#set par(justify: true)
|
|
99
|
+
#for paragraph in section.body [
|
|
100
|
+
#paragraph
|
|
101
|
+
#parbreak()
|
|
102
|
+
]
|
|
103
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Monthly Operations Report",
|
|
3
|
+
"subtitle": "Platform reliability and usage",
|
|
4
|
+
"period": "February 2026",
|
|
5
|
+
"prepared_for": "Northwind Trading Ltd.",
|
|
6
|
+
"generated_on": "1 March 2026",
|
|
7
|
+
"metrics": [
|
|
8
|
+
{ "label": "Requests", "value": "48.2M", "change": "+12%", "positive": true },
|
|
9
|
+
{ "label": "p95 latency", "value": "184 ms", "change": "-9%", "positive": true },
|
|
10
|
+
{ "label": "Error rate", "value": "0.043%", "change": "+0.01pp", "positive": false },
|
|
11
|
+
{ "label": "Uptime", "value": "99.98%" }
|
|
12
|
+
],
|
|
13
|
+
"table": {
|
|
14
|
+
"heading": "Traffic by service",
|
|
15
|
+
"columns": ["Service", "Requests", "p95", "Errors"],
|
|
16
|
+
"rows": [
|
|
17
|
+
["Checkout API", "18,402,113", "142 ms", "0.02%"],
|
|
18
|
+
["Catalogue", "14,880,204", "96 ms", "0.01%"],
|
|
19
|
+
["Search", "9,113,776", "311 ms", "0.11%"],
|
|
20
|
+
["Webhooks", "4,204,882", "204 ms", "0.06%"],
|
|
21
|
+
["Reporting", "1,610,442", "884 ms", "0.09%"]
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"bars": {
|
|
25
|
+
"heading": "Requests by week",
|
|
26
|
+
"series": [
|
|
27
|
+
{ "label": "Week 1", "value": 10.8, "display": "10.8M" },
|
|
28
|
+
{ "label": "Week 2", "value": 11.9, "display": "11.9M" },
|
|
29
|
+
{ "label": "Week 3", "value": 12.4, "display": "12.4M" },
|
|
30
|
+
{ "label": "Week 4", "value": 13.1, "display": "13.1M" }
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"sections": [
|
|
34
|
+
{
|
|
35
|
+
"heading": "Summary",
|
|
36
|
+
"body": [
|
|
37
|
+
"Traffic grew 12% month on month, driven almost entirely by the checkout API following the February pricing launch. Latency improved despite the extra load, thanks to the connection-pool change shipped on the 8th.",
|
|
38
|
+
"One incident affected search for 41 minutes on 19 February. Root cause was an unbounded query introduced by a third-party integration; a query timeout has since been added."
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"heading": "Actions for next month",
|
|
43
|
+
"body": [
|
|
44
|
+
"Reporting remains the slowest service at a p95 of 884 ms. The team will move its aggregate queries onto the read replica and revisit the index on the events table.",
|
|
45
|
+
"Error rate rose slightly, entirely within the webhooks service. The retry policy will be changed from fixed to exponential backoff."
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Shared branding for every Typstify document.
|
|
2
|
+
//
|
|
3
|
+
// Change it once here and every invoice, receipt, report and certificate
|
|
4
|
+
// follows. Nothing in this file reads data.json — it is pure presentation, so
|
|
5
|
+
// it can be imported by any template.
|
|
6
|
+
|
|
7
|
+
#let brand = (
|
|
8
|
+
name: "Your Company",
|
|
9
|
+
tagline: "",
|
|
10
|
+
accent: rgb("#1f5eff"),
|
|
11
|
+
ink: rgb("#111827"),
|
|
12
|
+
muted: rgb("#6b7280"),
|
|
13
|
+
rule: rgb("#e5e7eb"),
|
|
14
|
+
font: "Inter",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
// Base page setup. Wrap a document with: #show: brand-page
|
|
18
|
+
#let brand-page(body) = {
|
|
19
|
+
set page(
|
|
20
|
+
paper: "a4",
|
|
21
|
+
margin: (x: 18mm, y: 20mm),
|
|
22
|
+
footer: context [
|
|
23
|
+
#set text(size: 8pt, fill: brand.muted)
|
|
24
|
+
#line(length: 100%, stroke: 0.5pt + brand.rule)
|
|
25
|
+
#v(2pt)
|
|
26
|
+
#grid(
|
|
27
|
+
columns: (1fr, auto),
|
|
28
|
+
align: (left, right),
|
|
29
|
+
[#brand.name],
|
|
30
|
+
[Page #counter(page).display() of #context counter(page).final().first()],
|
|
31
|
+
)
|
|
32
|
+
],
|
|
33
|
+
)
|
|
34
|
+
set text(font: brand.font, size: 10pt, fill: brand.ink)
|
|
35
|
+
set par(justify: false, leading: 0.65em)
|
|
36
|
+
show heading: set text(weight: "semibold")
|
|
37
|
+
body
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Document header: a title on the left, the company block on the right.
|
|
41
|
+
#let brand-header(title: "", subtitle: none, meta: ()) = {
|
|
42
|
+
grid(
|
|
43
|
+
columns: (1fr, auto),
|
|
44
|
+
align: (left + bottom, right + bottom),
|
|
45
|
+
[
|
|
46
|
+
#text(size: 22pt, weight: "semibold", fill: brand.accent)[#title]
|
|
47
|
+
#if subtitle != none [
|
|
48
|
+
#linebreak()
|
|
49
|
+
#text(size: 10pt, fill: brand.muted)[#subtitle]
|
|
50
|
+
]
|
|
51
|
+
],
|
|
52
|
+
[
|
|
53
|
+
#text(size: 11pt, weight: "semibold")[#brand.name]
|
|
54
|
+
#if brand.tagline != "" [
|
|
55
|
+
#linebreak()
|
|
56
|
+
#text(size: 9pt, fill: brand.muted)[#brand.tagline]
|
|
57
|
+
]
|
|
58
|
+
],
|
|
59
|
+
)
|
|
60
|
+
v(4pt)
|
|
61
|
+
line(length: 100%, stroke: 1pt + brand.accent)
|
|
62
|
+
if meta.len() > 0 {
|
|
63
|
+
v(6pt)
|
|
64
|
+
grid(
|
|
65
|
+
columns: meta.len(),
|
|
66
|
+
gutter: 16pt,
|
|
67
|
+
..meta.map(pair => [
|
|
68
|
+
#text(size: 8pt, fill: brand.muted, upper(pair.at(0)))
|
|
69
|
+
#linebreak()
|
|
70
|
+
#text(size: 10pt)[#pair.at(1)]
|
|
71
|
+
]),
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
v(10pt)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// A labelled block of lines, e.g. an address.
|
|
78
|
+
#let brand-address(label: "", lines: ()) = [
|
|
79
|
+
#text(size: 8pt, fill: brand.muted)[#upper(label)]
|
|
80
|
+
#linebreak()
|
|
81
|
+
#for line in lines [
|
|
82
|
+
#line
|
|
83
|
+
#linebreak()
|
|
84
|
+
]
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
// The standard table styling used by the invoice and receipt templates.
|
|
88
|
+
#let brand-table(columns: (), header: (), rows: (), aligns: none) = {
|
|
89
|
+
set text(size: 9.5pt)
|
|
90
|
+
table(
|
|
91
|
+
columns: columns,
|
|
92
|
+
align: if aligns == none { left } else { (col, _) => aligns.at(col) },
|
|
93
|
+
stroke: (x, y) => (
|
|
94
|
+
bottom: if y == 0 { 1pt + brand.rule } else { 0.5pt + brand.rule },
|
|
95
|
+
),
|
|
96
|
+
inset: (x: 6pt, y: 7pt),
|
|
97
|
+
table.header(..header.map(cell => text(
|
|
98
|
+
size: 8pt,
|
|
99
|
+
weight: "semibold",
|
|
100
|
+
fill: brand.muted,
|
|
101
|
+
)[#upper(cell)])),
|
|
102
|
+
..rows.flatten(),
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Right-aligned totals block.
|
|
107
|
+
#let brand-totals(rows: (), emphasis: none) = align(right)[
|
|
108
|
+
#block(width: 62mm)[
|
|
109
|
+
#for row in rows [
|
|
110
|
+
#grid(
|
|
111
|
+
columns: (1fr, auto),
|
|
112
|
+
gutter: 12pt,
|
|
113
|
+
align: (left, right),
|
|
114
|
+
text(fill: brand.muted)[#row.at(0)],
|
|
115
|
+
[#row.at(1)],
|
|
116
|
+
)
|
|
117
|
+
#v(2pt)
|
|
118
|
+
]
|
|
119
|
+
#if emphasis != none [
|
|
120
|
+
#line(length: 100%, stroke: 0.5pt + brand.rule)
|
|
121
|
+
#v(4pt)
|
|
122
|
+
#grid(
|
|
123
|
+
columns: (1fr, auto),
|
|
124
|
+
gutter: 12pt,
|
|
125
|
+
align: (left, right),
|
|
126
|
+
text(weight: "semibold")[#emphasis.at(0)],
|
|
127
|
+
text(weight: "semibold", size: 12pt, fill: brand.accent)[#emphasis.at(1)],
|
|
128
|
+
)
|
|
129
|
+
]
|
|
130
|
+
]
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
#let brand-note(body) = block(
|
|
134
|
+
width: 100%,
|
|
135
|
+
fill: rgb("#f9fafb"),
|
|
136
|
+
stroke: 0.5pt + brand.rule,
|
|
137
|
+
radius: 3pt,
|
|
138
|
+
inset: 10pt,
|
|
139
|
+
text(size: 9pt, fill: brand.muted)[#body],
|
|
140
|
+
)
|
metadata
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: typstify
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Nikhil Nelson
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-08-31 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: typst
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.15'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.15'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: actionpack
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '7.1'
|
|
34
|
+
- - "<"
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: '8.2'
|
|
37
|
+
type: :runtime
|
|
38
|
+
prerelease: false
|
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '7.1'
|
|
44
|
+
- - "<"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '8.2'
|
|
47
|
+
- !ruby/object:Gem::Dependency
|
|
48
|
+
name: activesupport
|
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '7.1'
|
|
54
|
+
- - "<"
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '8.2'
|
|
57
|
+
type: :runtime
|
|
58
|
+
prerelease: false
|
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: '7.1'
|
|
64
|
+
- - "<"
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '8.2'
|
|
67
|
+
- !ruby/object:Gem::Dependency
|
|
68
|
+
name: railties
|
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - ">="
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '7.1'
|
|
74
|
+
- - "<"
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: '8.2'
|
|
77
|
+
type: :runtime
|
|
78
|
+
prerelease: false
|
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - ">="
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: '7.1'
|
|
84
|
+
- - "<"
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '8.2'
|
|
87
|
+
description: Typstify gives Rails first-class PDF generation on Typst, the Rust typesetting
|
|
88
|
+
engine. Controllers get `render pdf:`, templates live in app/views like every other
|
|
89
|
+
view, and your data arrives as JSON so user content can never change the document
|
|
90
|
+
— injection-safe by construction, compiled in an isolated sandbox directory. Ships
|
|
91
|
+
professionally designed invoice, receipt, report and certificate templates, and
|
|
92
|
+
can emit accessible PDF/UA-1 output for EAA and ADA compliance. No headless Chrome,
|
|
93
|
+
no archived wkhtmltopdf binary.
|
|
94
|
+
email:
|
|
95
|
+
- thesolohacker47@gmail.com
|
|
96
|
+
executables: []
|
|
97
|
+
extensions: []
|
|
98
|
+
extra_rdoc_files: []
|
|
99
|
+
files:
|
|
100
|
+
- CHANGELOG.md
|
|
101
|
+
- LICENSE
|
|
102
|
+
- README.md
|
|
103
|
+
- SECURITY.md
|
|
104
|
+
- fonts/inter/Inter-Regular.ttf
|
|
105
|
+
- fonts/inter/Inter-SemiBold.ttf
|
|
106
|
+
- fonts/inter/OFL.txt
|
|
107
|
+
- lib/generators/typstify/install/install_generator.rb
|
|
108
|
+
- lib/generators/typstify/install/templates/typstify.rb
|
|
109
|
+
- lib/generators/typstify/template/template_generator.rb
|
|
110
|
+
- lib/tasks/typstify.rake
|
|
111
|
+
- lib/typstify.rb
|
|
112
|
+
- lib/typstify/adapter.rb
|
|
113
|
+
- lib/typstify/config.rb
|
|
114
|
+
- lib/typstify/data.rb
|
|
115
|
+
- lib/typstify/document.rb
|
|
116
|
+
- lib/typstify/engine.rb
|
|
117
|
+
- lib/typstify/erb_pipeline.rb
|
|
118
|
+
- lib/typstify/errors.rb
|
|
119
|
+
- lib/typstify/escaping.rb
|
|
120
|
+
- lib/typstify/fonts.rb
|
|
121
|
+
- lib/typstify/renderer.rb
|
|
122
|
+
- lib/typstify/resolver.rb
|
|
123
|
+
- lib/typstify/version.rb
|
|
124
|
+
- lib/typstify/warnings.rb
|
|
125
|
+
- lib/typstify/workspace.rb
|
|
126
|
+
- templates/certificate/certificate.typ
|
|
127
|
+
- templates/certificate/sample_data.json
|
|
128
|
+
- templates/invoice/invoice.typ
|
|
129
|
+
- templates/invoice/sample_data.json
|
|
130
|
+
- templates/receipt/receipt.typ
|
|
131
|
+
- templates/receipt/sample_data.json
|
|
132
|
+
- templates/report/report.typ
|
|
133
|
+
- templates/report/sample_data.json
|
|
134
|
+
- templates/shared/branding.typ
|
|
135
|
+
homepage: https://github.com/TheSoloHacker47/typstify
|
|
136
|
+
licenses:
|
|
137
|
+
- MIT
|
|
138
|
+
metadata:
|
|
139
|
+
homepage_uri: https://github.com/TheSoloHacker47/typstify
|
|
140
|
+
source_code_uri: https://github.com/TheSoloHacker47/typstify
|
|
141
|
+
changelog_uri: https://github.com/TheSoloHacker47/typstify/blob/main/CHANGELOG.md
|
|
142
|
+
bug_tracker_uri: https://github.com/TheSoloHacker47/typstify/issues
|
|
143
|
+
documentation_uri: https://github.com/TheSoloHacker47/typstify#readme
|
|
144
|
+
rubygems_mfa_required: 'true'
|
|
145
|
+
post_install_message:
|
|
146
|
+
rdoc_options: []
|
|
147
|
+
require_paths:
|
|
148
|
+
- lib
|
|
149
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
150
|
+
requirements:
|
|
151
|
+
- - ">="
|
|
152
|
+
- !ruby/object:Gem::Version
|
|
153
|
+
version: 3.1.0
|
|
154
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
155
|
+
requirements:
|
|
156
|
+
- - ">="
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
version: '0'
|
|
159
|
+
requirements: []
|
|
160
|
+
rubygems_version: 3.5.22
|
|
161
|
+
signing_key:
|
|
162
|
+
specification_version: 4
|
|
163
|
+
summary: PDF generation for Rails on the Typst engine — the wkhtmltopdf replacement.
|
|
164
|
+
test_files: []
|