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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 02ec11e818e2f0b65a784efb1db7e5e6aeb45f95f3e06189a8bf1ebc56d24811
4
+ data.tar.gz: ff48f408265ec390b57cc9f071edda050747b11795e15d2a6f07a34ff2037ddf
5
+ SHA512:
6
+ metadata.gz: 37e3c8c203c967dbfbec72dc0023dae2428919a3606938b00a88ae29b0fd6034cace8481d1db610d51496d0f78e83cf75c2eedcc0f6f18484bbc6eaebd88f6ca
7
+ data.tar.gz: 29960b89f82e52cd8878a6fdac2a8a978c9acbefa3e11f8e69a96e3fb6d94dadfd52c795d513fe13e1c46a1ad2007ef3985c1047643ace49ed314db70d0581f1
data/CHANGELOG.md ADDED
@@ -0,0 +1,49 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] — 2026-08-31
10
+
11
+ First release.
12
+
13
+ ### Added
14
+
15
+ - `render pdf:` renderer for any Rails controller, honouring `data:`, `filename:`,
16
+ `disposition:` and `status:`.
17
+ - `Typstify.render` and `Typstify.render_and_attach` for jobs, mailers and ActiveStorage.
18
+ - **Data mode**: templates are pure `.typ` and read their input from `data.json`, so user
19
+ content is data rather than source and cannot alter the document.
20
+ - **Workspace isolation**: every render compiles in a fresh temporary directory containing only
21
+ the template, its `shared/` partials and `data.json`, with Typst rooted there. Symlinks in the
22
+ shared tree are skipped rather than followed out of the root.
23
+ - **ERB mode** (`.typ.erb`) with the `typ()` escaping helper, one spec per escaped character.
24
+ - Starter template pack: invoice, receipt, report and certificate, each with sample data and a
25
+ shared `branding.typ`.
26
+ - `rails g typstify:install` and `rails g typstify:template <name>`.
27
+ - `rake typstify:preview[invoices/show]` and `rake typstify:templates`.
28
+ - PDF standard selection: `:ua_1` (PDF/UA-1), `:a_1b`, `:a_2b`, `:a_3b`, `:a_4`, `:pdf_1_7`,
29
+ `:pdf_2_0`.
30
+ - Strict font checking: a family a template names but nothing can supply raises
31
+ `FontMissingError` in development and test, and warns in production.
32
+ - `ignore_system_fonts`, which skips the operating system's font directories. Worth knowing:
33
+ that scan costs roughly 50 ms per render — the starter invoice goes from 58 ms to 4 ms.
34
+ - `package_cache` for vendored Typst Universe packages, for builds with no network.
35
+ - Inter (SIL OFL) bundled, so the starter templates render with no configuration.
36
+
37
+ ### Known limitations
38
+
39
+ - `on_warning` does not fire for warnings from a **successful** compile. The `typst` binding
40
+ discards them; it only formats warnings into the message when compilation fails. The hook
41
+ works for failure-path warnings and for this gem's own font check. Fix opened upstream:
42
+ [actsasflinn/typst-rb#10](https://github.com/actsasflinn/typst-rb/pull/10).
43
+ - `package_cache` is effective on Linux, where the platform data directory follows
44
+ `XDG_DATA_HOME`. macOS uses a fixed location that no environment variable redirects.
45
+ - Ruby threads do not speed up rendering: the binding holds the GVL through compilation. Use
46
+ processes for throughput.
47
+
48
+ [Unreleased]: https://github.com/TheSoloHacker47/typstify/compare/v0.1.0...HEAD
49
+ [0.1.0]: https://github.com/TheSoloHacker47/typstify/releases/tag/v0.1.0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nikhil Nelson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,235 @@
1
+ # typstify
2
+
3
+ > PDF generation for Rails on the [Typst](https://typst.app) engine — the wkhtmltopdf replacement.
4
+ > Invoices, reports, certificates: fast, beautiful, accessible (PDF/UA), and no headless Chrome in your Docker image.
5
+
6
+ [![Gem Version](https://badge.fury.io/rb/typstify.svg)](https://rubygems.org/gems/typstify)
7
+ [![CI](https://github.com/TheSoloHacker47/typstify/actions/workflows/ci.yml/badge.svg)](https://github.com/TheSoloHacker47/typstify/actions)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/TheSoloHacker47/typstify/blob/main/LICENSE)
9
+
10
+ ## Why
11
+
12
+ Your Rails app generates PDFs. Your options until now:
13
+
14
+ | | problem |
15
+ |---|---|
16
+ | wicked_pdf / pdfkit | built on **wkhtmltopdf, which is archived and unmaintained** — no security fixes, ancient WebKit |
17
+ | grover / Chrome-based | ships a **full browser** in your production image: hundreds of MB, memory spikes, cold starts |
18
+ | prawn | hand-positioning a coordinate DSL for every invoice line |
19
+
20
+ [Typst](https://typst.app) is the Rust-based successor to LaTeX: a markup language that
21
+ compiles to PDF in **milliseconds**, reads **JSON natively**, and can emit **PDF/UA-1
22
+ accessible PDFs** — which the European Accessibility Act now effectively requires for
23
+ machine-generated customer documents. `typstify` makes it feel like Rails.
24
+
25
+ ## Quickstart
26
+
27
+ ```ruby
28
+ # Gemfile
29
+ gem "typstify"
30
+ ```
31
+
32
+ ```bash
33
+ bundle && rails g typstify:install && rails g typstify:template invoice
34
+ ```
35
+
36
+ ```ruby
37
+ # app/controllers/invoices_controller.rb
38
+ def show
39
+ invoice = Invoice.find(params[:id])
40
+ render pdf: "invoices/show",
41
+ data: { number: invoice.number,
42
+ total: invoice.total.format,
43
+ line_items: invoice.line_items.map { |li| { name: li.name, qty: li.qty, amount: li.amount.format } } },
44
+ filename: "invoice-#{invoice.number}.pdf"
45
+ end
46
+ ```
47
+
48
+ ```typ
49
+ // app/views/invoices/show.typ
50
+ #let data = json("data.json")
51
+
52
+ = Invoice #data.number
53
+
54
+ #table(
55
+ columns: (1fr, auto, auto),
56
+ [*Item*], [*Qty*], [*Amount*],
57
+ ..data.line_items.map(i => (i.name, str(i.qty), i.amount)).flatten()
58
+ )
59
+
60
+ #align(right)[*Total: #data.total*]
61
+ ```
62
+
63
+ That's a complete, styled, paginated invoice. No browser. No dead binaries.
64
+
65
+ ## The data-first design (and why it's safer)
66
+
67
+ Notice what the template does: it reads your data as **JSON**, it doesn't interpolate strings.
68
+ Your Ruby data can contain anything — Typst markup, `#` directives, quotes — and it renders as
69
+ literal text, because content never becomes code.
70
+
71
+ Each render also happens in a fresh temporary directory containing only the template, its
72
+ `shared/` partials, and `data.json`. Typst is invoked with that directory as its compile root,
73
+ so a template physically cannot read your `.env`, your credentials, or any other view. Not
74
+ because we filter the path — because the file is not there.
75
+
76
+ `spec/security/` asserts both properties directly; they are the specs worth reading first.
77
+
78
+ Prefer classic ERB? `.typ.erb` templates work too — pass every dynamic value through the
79
+ `typ()` escaping helper:
80
+
81
+ ```erb
82
+ = Hello <%= typ(@user.display_name) %>
83
+ ```
84
+
85
+ But use data mode. Future-you says thanks.
86
+
87
+ ## Everywhere you need a PDF
88
+
89
+ ```ruby
90
+ # Background jobs / plain Ruby
91
+ pdf_bytes = Typstify.render(template: "reports/monthly", data: payload)
92
+
93
+ # Mailers
94
+ attachments["report.pdf"] = Typstify.render(template: "reports/monthly", data: payload)
95
+
96
+ # ActiveStorage
97
+ Typstify.render_and_attach(user.documents, template: "certificates/completion",
98
+ data: cert_data, filename: "certificate.pdf")
99
+ ```
100
+
101
+ Batch-generating 10,000 invoices in a job? Typst's per-document compile time is milliseconds —
102
+ see [`docs/batch-generation.md`](https://github.com/TheSoloHacker47/typstify/blob/main/docs/batch-generation.md) for a Sidekiq/Solid Queue pattern
103
+ and the two settings that make output reproducible.
104
+
105
+ ## Starter templates
106
+
107
+ `rails g typstify:template <name>` for: **invoice** · **receipt** · **report** ·
108
+ **certificate**. Each comes with sample data and a preview task:
109
+
110
+ ```bash
111
+ rake typstify:preview[invoices/show]
112
+ ```
113
+
114
+ which compiles with the bundled `sample_data.json` into `tmp/previews/`, so you can iterate on
115
+ design without loading your app's initializers or touching the database. Shared branding (logo,
116
+ colours, footer) lives in `app/views/shared/branding.typ` — change it once, every document
117
+ updates.
118
+
119
+ ## Accessible PDFs (EAA / ADA)
120
+
121
+ HTML→PDF pipelines produce untagged PDFs that fail accessibility requirements. Typst supports
122
+ PDF/UA-1 output; enable it globally:
123
+
124
+ ```ruby
125
+ Typstify.configure { |c| c.pdf_standard = :ua_1 }
126
+ ```
127
+
128
+ Also available: `:a_1b`, `:a_2b`, `:a_3b`, `:a_4`, `:pdf_1_7`, `:pdf_2_0`. PDF/UA-1 requires a
129
+ document title, so set `#set document(title: "…")` in the template.
130
+
131
+ If you invoice EU customers, your compliance team will hug you.
132
+
133
+ ## Fonts & Docker (read this before deploying)
134
+
135
+ Typst uses system fonts plus any directories you configure. The reliable production setup is
136
+ vendoring fonts in your app:
137
+
138
+ ```ruby
139
+ c.font_paths = [Rails.root.join("app/assets/fonts")]
140
+ ```
141
+
142
+ A missing font family fails loudly in development and test (`strict_fonts`, on by default
143
+ there) instead of silently substituting a different face in production. Inter ships with the
144
+ gem, so the starter templates work with no configuration at all.
145
+
146
+ Once your fonts are vendored, turn off the system font scan. It costs about **50 ms per
147
+ render** — the starter invoice goes from 58 ms to 4 ms:
148
+
149
+ ```ruby
150
+ c.ignore_system_fonts = true
151
+ ```
152
+
153
+ Typst Universe packages can be vendored into a directory for network-free builds — with a
154
+ platform caveat that matters. Full guide: [`docs/fonts-and-docker.md`](https://github.com/TheSoloHacker47/typstify/blob/main/docs/fonts-and-docker.md).
155
+
156
+ ## Configuration
157
+
158
+ ```ruby
159
+ # config/initializers/typstify.rb — written by the install generator
160
+ Typstify.configure do |c|
161
+ c.template_root = Rails.root.join("app/views") # default
162
+ c.shared_dir = "shared" # partials namespace
163
+ c.font_paths = [Rails.root.join("app/assets/fonts")]
164
+ c.ignore_system_fonts = true # false by default; see below
165
+ c.package_cache = Rails.root.join("vendor/typst_packages") # vendored Universe packages
166
+ c.pdf_standard = :ua_1 # nil by default
167
+ c.strict_fonts = Rails.env.local? # the default
168
+ c.on_warning = ->(warnings, template) { Rails.logger.warn("#{template}: #{warnings}") }
169
+ end
170
+ ```
171
+
172
+ ### One honest caveat about `on_warning`
173
+
174
+ The `typst` binding this gem depends on discards the compiler's warnings when compilation
175
+ **succeeds** — it only formats them into the message when compilation fails. So `on_warning`
176
+ fires for warnings that accompany a compile error, and for the font check this gem performs
177
+ itself, but not yet for arbitrary success-path warnings. There is nothing to listen to until
178
+ that changes upstream, so the fix is in flight there:
179
+ [actsasflinn/typst-rb#10](https://github.com/actsasflinn/typst-rb/pull/10). Rather than ship an
180
+ option that quietly never fires, the missing-font case — the one that actually bites people —
181
+ is checked here, before the compiler runs.
182
+
183
+ ## Errors
184
+
185
+ | | when |
186
+ |---|---|
187
+ | `Typstify::MissingTemplate` | neither `.typ` nor `.typ.erb` exists; the message lists both paths tried |
188
+ | `Typstify::CompileError` | Typst rejected the document; carries its annotated diagnostic and the line, with your template's name substituted for the workspace path |
189
+ | `Typstify::FontMissingError` | a template names a font nothing can supply, in strict mode |
190
+ | `Typstify::PathError` | a template name resolved outside `template_root` |
191
+ | `ArgumentError` | `data:` contains something JSON cannot represent; the message names the key path, e.g. `data.line_items[0].amount` |
192
+
193
+ ## Migrating from wicked_pdf
194
+
195
+ There's no HTML→Typst converter — you rewrite each PDF view, and that's genuinely fine: most
196
+ teams have 2–5 PDF views, and each takes minutes, not hours, in Typst. The side-by-side
197
+ walkthrough (a real wicked_pdf invoice vs its Typst version, at a third of the line count):
198
+ [`docs/migrating-from-wicked_pdf.md`](https://github.com/TheSoloHacker47/typstify/blob/main/docs/migrating-from-wicked_pdf.md).
199
+
200
+ ## Compatibility
201
+
202
+ Ruby ≥ 3.1 · Rails 7.1 – 8.1 · Linux & macOS. PDF compilation is done by the excellent
203
+ [`typst` gem](https://github.com/actsasflinn/typst-rb) (Rust binding to the Typst compiler) —
204
+ this gem is the Rails layer on top, and ships no compiler of its own.
205
+
206
+ Note: Rails 8.1 requires Ruby 3.4 or newer in practice, because `actionview` uses syntax that
207
+ older Rubies cannot parse. The CI matrix pairs them accordingly.
208
+
209
+ ## Related work
210
+
211
+ [`typst-rails`](https://github.com/durable-oss/typst-rails) by David J. Berube covers similar
212
+ ground and appeared around the same time; it supports Rage and Sinatra alongside Rails. This
213
+ gem is narrower on purpose — Rails only — and spends that focus on the sandboxed workspace, the
214
+ data-mode security model, the starter template pack and PDF/UA output. Worth comparing both
215
+ against what you need.
216
+
217
+ ## Contributing
218
+
219
+ ```bash
220
+ git clone https://github.com/TheSoloHacker47/typstify && cd typstify
221
+ bin/setup && bundle exec rake
222
+ ```
223
+
224
+ Template pack contributions especially welcome — new document types need sample data, a spec,
225
+ and a golden. See [CONTRIBUTING.md](https://github.com/TheSoloHacker47/typstify/blob/main/CONTRIBUTING.md).
226
+
227
+ ## License
228
+
229
+ [MIT](https://github.com/TheSoloHacker47/typstify/blob/main/LICENSE). Inter is vendored under the [SIL Open Font License](https://github.com/TheSoloHacker47/typstify/blob/main/fonts/inter/OFL.txt).
230
+ Typst itself is Apache-2.0 by the Typst team — go star it.
231
+
232
+ ## More from me
233
+
234
+ - [scrubber_rb](https://github.com/TheSoloHacker47/scrubber-rb) — fast PII and secret redaction for Ruby, with a Rust core
235
+ - [bundler-overrule](https://github.com/TheSoloHacker47/bundler-overrule) — force, ban and swap gem versions in your Gemfile
data/SECURITY.md ADDED
@@ -0,0 +1,47 @@
1
+ # Security policy
2
+
3
+ ## Supported versions
4
+
5
+ The latest minor release receives security fixes.
6
+
7
+ ## Reporting a vulnerability
8
+
9
+ Report privately through GitHub's
10
+ [security advisory form](https://github.com/TheSoloHacker47/typstify/security/advisories/new),
11
+ or by email to thesolohacker47@gmail.com. Please do not open a public issue first.
12
+
13
+ Expect an acknowledgement within 72 hours, and a fix or a plan within 14 days for anything
14
+ confirmed.
15
+
16
+ ## What counts as a vulnerability here
17
+
18
+ This gem makes two claims. A credible way to break either is a vulnerability:
19
+
20
+ 1. **Data cannot become code.** Anything passed as `data:` reaches the template through
21
+ `data.json` and is read as a string. If you can make a value passed through `data:` execute
22
+ as Typst — call a function, read a file, alter the document's structure — that is a
23
+ vulnerability.
24
+ 2. **A template cannot read outside its workspace.** Each render compiles in a temporary
25
+ directory holding only the template, its `shared/` partials and `data.json`, with Typst
26
+ rooted there. If you can make a template read a file that was not copied in for it — through
27
+ a symlink, a path in `template_root`, a package import, or anything else — that is a
28
+ vulnerability.
29
+
30
+ Also in scope: a way to make `Typstify::Resolver` open a file outside `template_root`, and a
31
+ way to make `Typstify::Escaping.typ` emit a string that Typst still parses as markup.
32
+
33
+ ## What does not count
34
+
35
+ - **ERB mode without `typ()`.** `.typ.erb` interpolates Ruby strings into Typst source; an
36
+ unescaped value there is injection by construction, which is why `typ()` exists and why the
37
+ README says to use data mode instead. `spec/security/erb_mode_spec.rb` documents this
38
+ explicitly.
39
+ - **Templates authored by an attacker.** A `.typ` file in `app/views` is code you deployed. The
40
+ workspace boundary limits what it can reach, but the threat model is untrusted *data*, not
41
+ untrusted templates.
42
+ - **Vulnerabilities in the Typst compiler itself.** Report those to
43
+ [typst/typst](https://github.com/typst/typst); we will pick up the fix when the binding does.
44
+
45
+ ## Credit
46
+
47
+ Reporters are credited in the changelog and the advisory unless they ask not to be.
Binary file
Binary file
@@ -0,0 +1,92 @@
1
+ Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+ -----------------------------------------------------------
8
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
9
+ -----------------------------------------------------------
10
+
11
+ PREAMBLE
12
+ The goals of the Open Font License (OFL) are to stimulate worldwide
13
+ development of collaborative font projects, to support the font creation
14
+ efforts of academic and linguistic communities, and to provide a free and
15
+ open framework in which fonts may be shared and improved in partnership
16
+ with others.
17
+
18
+ The OFL allows the licensed fonts to be used, studied, modified and
19
+ redistributed freely as long as they are not sold by themselves. The
20
+ fonts, including any derivative works, can be bundled, embedded,
21
+ redistributed and/or sold with any software provided that any reserved
22
+ names are not used by derivative works. The fonts and derivatives,
23
+ however, cannot be released under any other type of license. The
24
+ requirement for fonts to remain under this license does not apply
25
+ to any document created using the fonts or their derivatives.
26
+
27
+ DEFINITIONS
28
+ "Font Software" refers to the set of files released by the Copyright
29
+ Holder(s) under this license and clearly marked as such. This may
30
+ include source files, build scripts and documentation.
31
+
32
+ "Reserved Font Name" refers to any names specified as such after the
33
+ copyright statement(s).
34
+
35
+ "Original Version" refers to the collection of Font Software components as
36
+ distributed by the Copyright Holder(s).
37
+
38
+ "Modified Version" refers to any derivative made by adding to, deleting,
39
+ or substituting -- in part or in whole -- any of the components of the
40
+ Original Version, by changing formats or by porting the Font Software to a
41
+ new environment.
42
+
43
+ "Author" refers to any designer, engineer, programmer, technical
44
+ writer or other person who contributed to the Font Software.
45
+
46
+ PERMISSION AND CONDITIONS
47
+ Permission is hereby granted, free of charge, to any person obtaining
48
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
49
+ redistribute, and sell modified and unmodified copies of the Font
50
+ Software, subject to the following conditions:
51
+
52
+ 1) Neither the Font Software nor any of its individual components,
53
+ in Original or Modified Versions, may be sold by itself.
54
+
55
+ 2) Original or Modified Versions of the Font Software may be bundled,
56
+ redistributed and/or sold with any software, provided that each copy
57
+ contains the above copyright notice and this license. These can be
58
+ included either as stand-alone text files, human-readable headers or
59
+ in the appropriate machine-readable metadata fields within text or
60
+ binary files as long as those fields can be easily viewed by the user.
61
+
62
+ 3) No Modified Version of the Font Software may use the Reserved Font
63
+ Name(s) unless explicit written permission is granted by the corresponding
64
+ Copyright Holder. This restriction only applies to the primary font name as
65
+ presented to the users.
66
+
67
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
68
+ Software shall not be used to promote, endorse or advertise any
69
+ Modified Version, except to acknowledge the contribution(s) of the
70
+ Copyright Holder(s) and the Author(s) or with their explicit written
71
+ permission.
72
+
73
+ 5) The Font Software, modified or unmodified, in part or in whole,
74
+ must be distributed entirely under this license, and must not be
75
+ distributed under any other license. The requirement for fonts to
76
+ remain under this license does not apply to any document created
77
+ using the Font Software.
78
+
79
+ TERMINATION
80
+ This license becomes null and void if any of the above conditions are
81
+ not met.
82
+
83
+ DISCLAIMER
84
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
85
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
86
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
87
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
88
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
89
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
90
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
91
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
92
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Typstify
6
+ module Generators
7
+ # rails g typstify:install
8
+ #
9
+ # Drops in the initializer and the shared branding partial every starter
10
+ # template imports.
11
+ class InstallGenerator < ::Rails::Generators::Base
12
+ source_root File.expand_path("templates", __dir__)
13
+
14
+ desc "Creates config/initializers/typstify.rb and app/views/shared/branding.typ"
15
+
16
+ def create_initializer
17
+ template "typstify.rb", "config/initializers/typstify.rb"
18
+ end
19
+
20
+ def create_branding
21
+ copy_file Typstify.template_pack_path.join("shared", "branding.typ").to_s,
22
+ "app/views/shared/branding.typ"
23
+ end
24
+
25
+ def report
26
+ say ""
27
+ say "Typstify installed.", :green
28
+ say ""
29
+ say " Next: rails g typstify:template invoice"
30
+ say " rake typstify:preview[invoices/show]"
31
+ say ""
32
+ say " Fonts: templates use Inter, which ships with the gem. To use your own,"
33
+ say " vendor the files and set c.font_paths in the initializer."
34
+ say " See https://github.com/TheSoloHacker47/typstify/blob/main/docs/fonts-and-docker.md"
35
+ say ""
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Typstify — PDF generation on the Typst engine.
4
+ # https://github.com/TheSoloHacker47/typstify
5
+
6
+ Typstify.configure do |c|
7
+ # Where .typ templates live. They are views like any other.
8
+ c.template_root = Rails.root.join("app/views")
9
+
10
+ # Subdirectory of template_root copied into every compile, so
11
+ # `#import "shared/branding.typ"` resolves from any template.
12
+ c.shared_dir = "shared"
13
+
14
+ # Extra font directories. The gem's bundled Inter is always on the path.
15
+ # Vendoring the fonts your templates name is the reliable production setup —
16
+ # see docs/fonts-and-docker.md.
17
+ # c.font_paths = [Rails.root.join("app/assets/fonts")]
18
+
19
+ # Skip the operating system's font directories. Once your fonts are vendored
20
+ # above, nothing on the system is being used and scanning for it costs about
21
+ # 50 ms per render: the starter invoice goes from 58 ms to 4 ms. Off by
22
+ # default because it changes which face a template naming "Helvetica" gets.
23
+ # c.ignore_system_fonts = true
24
+
25
+ # Vendored Typst Universe packages, for builds with no network.
26
+ # Effective on Linux (so: in your Docker image); see the same doc for why.
27
+ # c.package_cache = Rails.root.join("vendor/typst_packages")
28
+
29
+ # Accessible output. :ua_1 is PDF/UA-1, which the European Accessibility Act
30
+ # effectively requires of machine-generated customer documents.
31
+ # Also available: :a_1b, :a_2b, :a_3b, :a_4, :pdf_1_7, :pdf_2_0.
32
+ # c.pdf_standard = :ua_1
33
+
34
+ # Raise instead of warning when a template names a font nothing can supply.
35
+ # Defaults to true in development and test, false in production.
36
+ # c.strict_fonts = Rails.env.local?
37
+
38
+ # Where warnings go. See the README for what this currently does and does not
39
+ # catch — the compiler binding drops warnings on a successful compile.
40
+ # c.on_warning = lambda do |warnings, template|
41
+ # Rails.logger.warn("[typstify] #{template}: #{warnings.join("; ")}")
42
+ # end
43
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Typstify
6
+ module Generators
7
+ # rails g typstify:template invoice
8
+ #
9
+ # Copies a starter template and its sample data into app/views, so the
10
+ # preview task works immediately and you have something real to edit.
11
+ class TemplateGenerator < ::Rails::Generators::NamedBase
12
+ AVAILABLE = %w[invoice receipt report certificate].freeze
13
+
14
+ # The gem's own template pack is the source: this generator copies
15
+ # starter documents, it does not render ERB of its own.
16
+ source_root Typstify.template_pack_path.to_s
17
+
18
+ class_option :path, type: :string, default: nil,
19
+ desc: "Where to put it, e.g. billing/invoice (default: <plural>/show)"
20
+
21
+ desc "Copies a starter template (#{AVAILABLE.join(", ")}) into app/views"
22
+
23
+ def validate_name
24
+ return if AVAILABLE.include?(name)
25
+
26
+ raise ::Rails::Generators::Error,
27
+ "Unknown template #{name.inspect}. Available: #{AVAILABLE.join(", ")}"
28
+ end
29
+
30
+ def copy_template
31
+ copy_file "#{name}/#{name}.typ", "app/views/#{destination}.typ"
32
+ end
33
+
34
+ def copy_sample_data
35
+ copy_file "#{name}/sample_data.json",
36
+ "app/views/#{File.dirname(destination)}/sample_data.json"
37
+ end
38
+
39
+ def ensure_branding
40
+ return if File.exist?(File.join(destination_root, "app/views/shared/branding.typ"))
41
+
42
+ copy_file "shared/branding.typ", "app/views/shared/branding.typ"
43
+ end
44
+
45
+ def report
46
+ say ""
47
+ say "Preview it: rake typstify:preview[#{destination}]", :green
48
+ say ""
49
+ say "Render it from a controller:"
50
+ say ""
51
+ say " render pdf: #{destination.inspect},"
52
+ say " data: { ... },"
53
+ say " filename: #{"#{name}.pdf".inspect}"
54
+ say ""
55
+ end
56
+
57
+ private
58
+
59
+ def destination
60
+ @destination ||= begin
61
+ given = options[:path]
62
+ given.nil? || given.empty? ? "#{name}s/show" : given
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :typstify do
4
+ desc "Compile a template with its sample data: rake typstify:preview[invoices/show]"
5
+ task :preview, [:template] do |_task, args|
6
+ require "typstify"
7
+ require "json"
8
+ require "fileutils"
9
+
10
+ name = args[:template]
11
+ abort "Usage: rake typstify:preview[invoices/show]" if name.to_s.empty?
12
+
13
+ resolution = Typstify::Resolver.new.call(name)
14
+ sample = resolution.path.dirname.join("sample_data.json")
15
+ data =
16
+ if sample.file?
17
+ JSON.parse(File.read(sample, encoding: Encoding::UTF_8))
18
+ else
19
+ warn "No sample_data.json next to #{resolution.path}; rendering with empty data."
20
+ {}
21
+ end
22
+
23
+ output = Pathname.new("tmp/previews").join("#{name.tr("/", "-")}.pdf")
24
+ FileUtils.mkdir_p(output.dirname)
25
+ output.binwrite(Typstify.render(template: name, data: data))
26
+
27
+ puts "Wrote #{output} (#{output.size} bytes)"
28
+ system("open", output.to_s) if RUBY_PLATFORM.include?("darwin") && ENV["TYPSTIFY_OPEN"] != "0"
29
+ end
30
+
31
+ desc "List the templates Typstify can see"
32
+ task :templates do
33
+ require "typstify"
34
+ root = Typstify.config.template_root
35
+ found = Dir.glob(root.join("**", "*.typ{,.erb}"))
36
+ puts "template_root: #{root}"
37
+ if found.empty?
38
+ puts " (none)"
39
+ else
40
+ found.each { |path| puts " #{Pathname.new(path).relative_path_from(root)}" }
41
+ end
42
+ end
43
+ end