weasy_pdf 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: 7ce26a8ece90c530b6b5066c8b472213772024754487d1c499eaa176ea998303
4
+ data.tar.gz: f67004ea643989f581f5247016e4f324629fa6a250f02cc2cd2075ee119d74cf
5
+ SHA512:
6
+ metadata.gz: da3107e0b8a6d0b35f2217f4c96bf2ec412a8a6616de8b0d1c93fac805e92d6fbab6f902ba14e09f4745d2c5acae9bfa25c9c057f630464e04e9d5fbc2c2f080
7
+ data.tar.gz: 274b8f9f21837733e59926205339b19a8196595d07a1708d4f510c68564d0801d202e846b15a8a9b6a96f4adda8a29a6caa783f07b83024772d01f6795b0af90
data/CHANGELOG.md ADDED
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-04-18
11
+
12
+ ### Added
13
+
14
+ - Initial release — drop-in replacement for Wicked PDF using WeasyPrint as backend
15
+ - `WeasyPDF.new` shortcut for direct instantiation; `WeasyPDF::Renderer` for explicit usage
16
+ - `WeasyPDF::Renderer` generates PDFs from HTML strings, files, and URLs
17
+ - Native vite_ruby support: CDN in production, inlined CSS in development, disk-read in CI
18
+ - Automatic Rails integration via Railtie (no ApplicationController changes needed)
19
+ - Rack middleware `WeasyPDF::Middleware` for transparent `.pdf` URL conversion
20
+ - `[page]`/`[topage]` token mapping to CSS `counter(page)`/`counter(pages)`
21
+ - Configuration via `WeasyPDF.configure` with `attr_accessor` + hash-style `[]`/`[]=`
22
+ - All wkhtmltopdf-only options accepted silently for zero-friction migration
23
+ - `weasy_pdf_stylesheet_link_tag`, `weasy_pdf_image_tag`, `weasy_pdf_asset_path`,
24
+ `weasy_pdf_asset_base64`, `weasy_pdf_url_base64` view helpers
25
+ - `header:`/`footer:` with `:left`/`:center`/`:right` text via CSS `@page` margin boxes
26
+ - `header: { html: ... }` raises `WeasyPDF::Error` immediately with a migration hint
27
+
28
+ ### Development tooling
29
+
30
+ - StandardRB for linting (zero-config)
31
+ - SimpleCov with branch coverage (`bundle exec rake test` reports it)
32
+ - RBS signatures for `WeasyPDF`, `Renderer`, `Configuration` (sig/)
33
+ - CI matrix: Ruby 3.2/3.3/3.4 × Rails 7.1/7.2/8.0/8.1
34
+ - `bin/console` for interactive gem development
35
+
36
+ ### Not included (intentional divergence from Wicked PDF)
37
+
38
+ - JavaScript helpers (`wicked_pdf_javascript_*`) — WeasyPrint does not execute JS
39
+ - Webpacker/Shakapacker pack helpers — no `*_pack_tag` equivalents
40
+ - HTML template headers/footers — not possible via WeasyPrint CLI; raises with a clear error
41
+ - `ostruct` runtime dependency — uses `attr_accessor` instead
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Diego Enjamio
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,281 @@
1
+ # weasy_pdf
2
+
3
+ Generate PDFs from Rails views using [WeasyPrint](https://weasyprint.org/) — modern CSS, no JavaScript, no Chromium.
4
+
5
+ ```ruby
6
+ gem 'weasy_pdf'
7
+ ```
8
+
9
+ > Migrating from `wicked_pdf`? Jump to [Migration](#migration-from-wicked-pdf) — typically a one-line Gemfile change plus a `sed` over your views.
10
+
11
+ ## Requirements
12
+
13
+ - Ruby >= 3.2
14
+ - Rails >= 7.1
15
+ - WeasyPrint installed on the system:
16
+
17
+ ```bash
18
+ apt install weasyprint # Debian/Ubuntu
19
+ brew install weasyprint # macOS
20
+ pip install weasyprint # latest version
21
+ ```
22
+
23
+ ## Quick start
24
+
25
+ ```ruby
26
+ class InvoicesController < ApplicationController
27
+ def show
28
+ @invoice = Invoice.find(params[:id])
29
+ render pdf: "invoice_#{@invoice.number}", template: 'invoices/show'
30
+ end
31
+ end
32
+ ```
33
+
34
+ No initializer, no `ApplicationController` changes — the Railtie wires everything automatically.
35
+
36
+ ## Configuration
37
+
38
+ Optional — sensible defaults are used if you skip this:
39
+
40
+ ```ruby
41
+ # config/initializers/weasy_pdf.rb
42
+ WeasyPDF.configure do |config|
43
+ config.default_options = {
44
+ page_size: 'A4',
45
+ margin_top: '15mm',
46
+ media_type: 'print',
47
+ }
48
+
49
+ # base_url prepends to relative URLs in the rendered HTML when no asset
50
+ # can be resolved from disk. Useful in development.
51
+ config.base_url = Rails.env.development? ? 'http://localhost:3000' : nil
52
+
53
+ config.timeout = 60
54
+ end
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ ### Controllers
60
+
61
+ ```ruby
62
+ render pdf: 'invoice',
63
+ template: 'invoices/show',
64
+ layout: 'pdf',
65
+ page_size: 'A4',
66
+ margin: { top: 15, bottom: 15 },
67
+ header: { right: '[page] of [topage]', font_size: 9 },
68
+ show_as_html: params.key?('debug')
69
+ ```
70
+
71
+ ### Jobs / Mailers
72
+
73
+ ```ruby
74
+ pdf = render_to_string pdf: 'invoice', template: 'invoices/show', layout: 'pdf'
75
+ ```
76
+
77
+ ### Direct instantiation
78
+
79
+ ```ruby
80
+ WeasyPDF.new.pdf_from_string('<h1>Hello</h1>')
81
+ WeasyPDF.new.pdf_from_html_file('/path/to/file.html')
82
+ WeasyPDF.new.pdf_from_url('https://example.com')
83
+ ```
84
+
85
+ ### PDF layout
86
+
87
+ ```erb
88
+ <!DOCTYPE html>
89
+ <html>
90
+ <head>
91
+ <%= weasy_pdf_stylesheet_link_tag 'pdf' %>
92
+ </head>
93
+ <body>
94
+ <%= weasy_pdf_image_tag 'logo.png', width: 150 %>
95
+ <%= yield %>
96
+ </body>
97
+ </html>
98
+ ```
99
+
100
+ ## Asset helpers
101
+
102
+ ```erb
103
+ <%= weasy_pdf_stylesheet_link_tag 'pdf' %>
104
+ <%= weasy_pdf_image_tag 'logo.png', width: 150 %>
105
+ <%= weasy_pdf_asset_path 'logo.png' %>
106
+ <%= weasy_pdf_asset_base64 'logo.png' %>
107
+ <%= weasy_pdf_url_base64 'https://example.com/img.png' %>
108
+ ```
109
+
110
+ ## Asset support
111
+
112
+ **Vite-first** (auto-detected when `vite_ruby` is present) with fallback to precompiled assets from `public/`.
113
+
114
+ Each asset is resolved in this order:
115
+
116
+ 1. **Vite manifest** (if `vite_ruby` loaded):
117
+
118
+ | Environment | Behavior |
119
+ |---|---|
120
+ | Production CDN | `<link href="https://cdn.../pdf-abc.css">` — WeasyPrint downloads it |
121
+ | Dev + Vite server running | Inlines CSS via HTTP from localhost:5173 |
122
+ | No Vite server (CI/test) | Reads from `public/vite/assets/` |
123
+
124
+ 2. **Disk fallback** — searches `public/vite/assets/`, `public/assets/`, `public/`.
125
+ 3. **`base_url` fallback** — prepends `base_url` to the asset path.
126
+ 4. **Returns input unchanged** — logs a warning so misconfigurations are visible.
127
+
128
+ ## Headers and footers
129
+
130
+ CSS `@page` margin boxes — text and counters only:
131
+
132
+ ```ruby
133
+ render pdf: 'report',
134
+ header: { left: 'Acme', center: 'Confidential', right: '[page] of [topage]' },
135
+ footer: { center: '[page]', font_size: 8 }
136
+ ```
137
+
138
+ | Token | CSS output |
139
+ |---|---|
140
+ | `[page]` | `counter(page)` |
141
+ | `[topage]` | `counter(pages)` |
142
+ | `[section]`, `[title]`, `[date]`, `[time]`, `[webpage]` | Removed — no CSS equivalent |
143
+
144
+ For complex headers (logos, styled HTML), use [WeasyPrint running elements](https://weasyprint.readthedocs.io/en/stable/tutorial.html#headers-and-footers):
145
+
146
+ ```css
147
+ .pdf-header { position: running(header); }
148
+ @page { @top-center { content: element(header); } }
149
+ ```
150
+
151
+ ```erb
152
+ <div class="pdf-header">
153
+ <img src="<%= weasy_pdf_asset_path('logo.png') %>" height="30">
154
+ <span>Invoice #<%= @invoice.number %></span>
155
+ </div>
156
+ ```
157
+
158
+ ## Page sizes and orientation
159
+
160
+ `page_size:` accepts any [CSS Paged Media](https://www.w3.org/TR/css-page-3/#page-size-prop) keyword:
161
+
162
+ | Family | Sizes |
163
+ |---|---|
164
+ | ISO A | `A0` … `A10` (default: **A4**) |
165
+ | ISO B | `B0` … `B10` |
166
+ | ISO C | `C0` … `C10` |
167
+ | US | `Letter`, `Legal`, `Ledger` |
168
+ | JIS | `JIS-B0` … `JIS-B5` |
169
+
170
+ Custom dimensions (numbers default to mm; strings pass through):
171
+
172
+ ```ruby
173
+ render pdf: 'receipt', page_width: 80, page_height: 200 # 80mm × 200mm
174
+ render pdf: 'badge', page_width: '4in', page_height: '6in' # any CSS unit
175
+ ```
176
+
177
+ Orientation:
178
+
179
+ ```ruby
180
+ render pdf: 'report', page_size: 'A4', orientation: 'Landscape'
181
+ ```
182
+
183
+ `page_width` / `page_height` take precedence over `page_size` / `orientation`.
184
+
185
+ ## Rack Middleware
186
+
187
+ ```ruby
188
+ # config/application.rb
189
+ config.middleware.use WeasyPDF::Middleware
190
+ config.middleware.use WeasyPDF::Middleware, { page_size: 'A4' }, only: [/^\/invoices/]
191
+ ```
192
+
193
+ Requests to `/invoices/1.pdf` are transparently rendered as PDF.
194
+
195
+ ## Limitations
196
+
197
+ | | |
198
+ |---|---|
199
+ | JavaScript execution | Not supported — WeasyPrint is JS-free. Render server-side first. |
200
+ | HTML template headers/footers | Not supported — raises `WeasyPDF::Error` pointing to running elements |
201
+ | Webpacker / Shakapacker `*_pack_tag` helpers | Not supported — no equivalents |
202
+ | `pdf_from_url` | Basic HTTP only — no auth, no JS rendering |
203
+ | `outline`, `dpi`, `proxy`, `cookie`, `extra` | Accepted silently (migration compat), not passed to WeasyPrint |
204
+
205
+ ## Migration from Wicked PDF
206
+
207
+ > Not migrating from `wicked_pdf`? You can stop reading here.
208
+
209
+ ### Why migrate
210
+
211
+ | | wicked_pdf | weasy_pdf |
212
+ |---|---|---|
213
+ | Backend | wkhtmltopdf (archived 2023) | WeasyPrint (active) |
214
+ | CSS engine | WebKit 2013 — no flexbox/grid | Modern CSS |
215
+ | RAM per worker | ~75–200MB + known leaks | ~35–80MB, clean exit |
216
+ | Vite | Manual workarounds | Native (auto-detected) |
217
+ | Runtime deps | `activesupport`, `ostruct` | `activesupport` |
218
+
219
+ ### Steps
220
+
221
+ **1. Gemfile**
222
+
223
+ ```diff
224
+ - gem 'wicked_pdf'
225
+ - gem 'wkhtmltopdf-binary'
226
+ + gem 'weasy_pdf'
227
+ ```
228
+
229
+ **2. Initializer**
230
+
231
+ ```bash
232
+ mv config/initializers/wicked_pdf.rb config/initializers/weasy_pdf.rb
233
+ sed -i 's/WickedPdf/WeasyPDF/g' config/initializers/weasy_pdf.rb
234
+ ```
235
+
236
+ **3. Bulk-rename helpers in views**
237
+
238
+ ```bash
239
+ find app/views -name '*.erb' | xargs sed -i 's/wicked_pdf_/weasy_pdf_/g'
240
+ ```
241
+
242
+ **4. Delete dead helper calls**
243
+
244
+ After step 3 your views may contain `weasy_pdf_javascript_*` or `weasy_pdf_*_pack_tag` calls
245
+ that **don't exist** in weasy_pdf and would raise `NoMethodError` at render time:
246
+
247
+ ```bash
248
+ grep -rE 'weasy_pdf_(javascript_|.*_pack_)' app/views
249
+ ```
250
+
251
+ WeasyPrint doesn't execute JavaScript and Webpacker isn't supported — removing them is correct.
252
+
253
+ **5. HTML template headers**
254
+
255
+ `header: { html: { template: ... } }` now raises `WeasyPDF::Error` with a migration hint.
256
+ Replace with [running elements](#headers-and-footers).
257
+
258
+ **6. Controllers — no changes**
259
+
260
+ `render pdf:`, `render_to_string pdf:`, all PDF options work identically.
261
+
262
+ **7. Dockerfile**
263
+
264
+ ```dockerfile
265
+ RUN apt-get update && apt-get install -y --no-install-recommends \
266
+ weasyprint fonts-dejavu-core fonts-liberation \
267
+ && rm -rf /var/lib/apt/lists/*
268
+ ```
269
+
270
+ ### Performance differences
271
+
272
+ Neither backend is universally faster — pick by workload:
273
+
274
+ - **Long-running Sidekiq workers** → weasy_pdf (no RAM leaks)
275
+ - **High-concurrency web requests** → weasy_pdf (lower per-process RAM)
276
+ - **Documents using flexbox / grid / modern CSS** → weasy_pdf (wkhtmltopdf can't render them)
277
+ - **One-shot CLI on simple HTML** → roughly comparable, document complexity dominates
278
+
279
+ ## License
280
+
281
+ MIT
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WeasyPDF
4
+ # Pure value transformation: binary + resolved options → shell command Array.
5
+ # No global state — callers resolve configuration (base_url, etc.) before
6
+ # passing options in, so this class is testable without WeasyPDF.configuration.
7
+ class CommandBuilder
8
+ def initialize(binary, options = {})
9
+ @binary = binary
10
+ @options = options
11
+ end
12
+
13
+ def build(input_path, output_path)
14
+ cmd = [@binary]
15
+ cmd += ["--encoding", @options[:encoding]] if @options[:encoding]
16
+ cmd += ["--zoom", @options[:zoom].to_s] if @options[:zoom] && @options[:zoom] != 1
17
+ cmd += ["--media-type", @options[:media_type]] if @options[:media_type]
18
+ cmd += ["--base-url", @options[:base_url].to_s] if @options[:base_url]
19
+ Array(@options[:stylesheets]).each { |s| cmd += ["--stylesheet", s] }
20
+ cmd + [input_path, output_path]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WeasyPDF
4
+ class Configuration
5
+ DEFAULTS = {
6
+ page_size: "A4",
7
+ orientation: "Portrait",
8
+ margin_top: "10mm",
9
+ margin_bottom: "10mm",
10
+ margin_left: "10mm",
11
+ margin_right: "10mm",
12
+ encoding: "utf-8",
13
+ zoom: 1,
14
+ media_type: "print" # WeasyPrint defaults to 'screen'; 'print' activates @media print rules
15
+ }.freeze
16
+
17
+ BINARY_CANDIDATES = %w[
18
+ /usr/local/bin/weasyprint
19
+ /usr/bin/weasyprint
20
+ /opt/weasyprint/bin/weasyprint
21
+ /opt/homebrew/bin/weasyprint
22
+ ].freeze
23
+
24
+ attr_accessor :exe_path, :default_options, :base_url, :timeout, :temp_path
25
+
26
+ def initialize
27
+ @exe_path = find_binary
28
+ @default_options = DEFAULTS.dup
29
+ @base_url = nil
30
+ @timeout = 60
31
+ @temp_path = default_temp_path
32
+ end
33
+
34
+ def [](key)
35
+ public_send(key)
36
+ end
37
+
38
+ def []=(key, value)
39
+ public_send(:"#{key}=", value)
40
+ end
41
+
42
+ private
43
+
44
+ def find_binary
45
+ BINARY_CANDIDATES.each { |path| return path if File.executable?(path) }
46
+ ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).each do |dir|
47
+ full = File.join(dir, "weasyprint")
48
+ return full if File.executable?(full)
49
+ end
50
+ "weasyprint"
51
+ end
52
+
53
+ def default_temp_path
54
+ if defined?(Rails)
55
+ Rails.root.join("tmp", "weasy_pdf")
56
+ else
57
+ require "pathname"
58
+ Pathname.new("/tmp/weasy_pdf")
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WeasyPDF
4
+ # Rack middleware that converts HTML responses to PDF when the URL ends in .pdf.
5
+ #
6
+ # Usage in config/application.rb:
7
+ # config.middleware.use WeasyPDF::Middleware
8
+ # config.middleware.use WeasyPDF::Middleware, { page_size: 'A4' }, { only: [/^\/invoices/] }
9
+ class Middleware
10
+ def initialize(app, options = {}, conditions = {})
11
+ @app = app
12
+ @options = options
13
+ @conditions = conditions
14
+ end
15
+
16
+ def call(env)
17
+ request = Rack::Request.new(env)
18
+ render_as_pdf?(request) ? serve_pdf(env, request) : @app.call(env)
19
+ end
20
+
21
+ private
22
+
23
+ def render_as_pdf?(request)
24
+ request.path.end_with?(".pdf") && path_allowed?(request.path)
25
+ end
26
+
27
+ def path_allowed?(path)
28
+ if @conditions[:only]
29
+ Array(@conditions[:only]).any? { |pattern| path.match?(pattern) }
30
+ elsif @conditions[:except]
31
+ Array(@conditions[:except]).none? { |pattern| path.match?(pattern) }
32
+ else
33
+ true
34
+ end
35
+ end
36
+
37
+ def serve_pdf(env, request)
38
+ html_env = env.merge(
39
+ "PATH_INFO" => request.path.sub(/\.pdf\z/, ""),
40
+ "HTTP_ACCEPT" => "text/html",
41
+ "QUERY_STRING" => request.query_string
42
+ )
43
+
44
+ status, headers, body = @app.call(html_env)
45
+ return [status, headers, body] unless status == 200
46
+
47
+ html = body.respond_to?(:body) ? body.body : body.join
48
+
49
+ begin
50
+ pdf = WeasyPDF::Renderer.new(@options).pdf_from_string(html)
51
+ [
52
+ 200,
53
+ {
54
+ "Content-Type" => "application/pdf",
55
+ "Content-Disposition" => "inline; filename=\"#{pdf_filename(request.path)}\"",
56
+ "Content-Length" => pdf.bytesize.to_s
57
+ },
58
+ [pdf]
59
+ ]
60
+ rescue WeasyPDF::Error => e
61
+ [500, {"Content-Type" => "text/plain"}, ["WeasyPDF error: #{e.message}"]]
62
+ end
63
+ end
64
+
65
+ def pdf_filename(path)
66
+ "#{File.basename(path, ".pdf")}.pdf"
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WeasyPDF
4
+ module PdfHelper
5
+ def self.prepended(base)
6
+ # Guard prevents after_action from being registered twice when PdfHelper
7
+ # is prepended to both ActionController::Base and a concrete subclass.
8
+ return unless base == ActionController::Base
9
+
10
+ base.class_eval do
11
+ after_action :clean_temp_files
12
+ end
13
+ end
14
+
15
+ def render(*args)
16
+ if args.first.is_a?(Hash) && args.first.key?(:pdf)
17
+ render_with_weasy_pdf(args.first)
18
+ else
19
+ super
20
+ end
21
+ end
22
+
23
+ def render_to_string(*args)
24
+ if args.first.is_a?(Hash) && args.first.key?(:pdf)
25
+ render_to_string_with_weasy_pdf(args.first)
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ RAILS_RENDER_KEYS = %i[
32
+ template layout file inline locals formats handlers assigns
33
+ ].freeze
34
+
35
+ def render_with_weasy_pdf(options)
36
+ options = options.dup
37
+
38
+ filename = options.delete(:pdf) || "document"
39
+ show_as_html = options.delete(:show_as_html)
40
+ disposition = options.delete(:disposition) || "inline"
41
+
42
+ return render(options.slice(*RAILS_RENDER_KEYS).merge(content_type: "text/html")) if show_as_html
43
+
44
+ pdf = make_pdf(options)
45
+
46
+ send_data pdf,
47
+ filename: "#{filename}.pdf",
48
+ type: "application/pdf",
49
+ disposition: disposition
50
+ end
51
+
52
+ def render_to_string_with_weasy_pdf(options)
53
+ make_pdf(options.except(:pdf, :disposition, :show_as_html))
54
+ end
55
+
56
+ private
57
+
58
+ def make_pdf(options)
59
+ validate_header_footer!(options)
60
+
61
+ render_opts = options.slice(*RAILS_RENDER_KEYS).merge(formats: [:html])
62
+ html = render_to_string(**render_opts)
63
+ pdf_options = options.except(*RAILS_RENDER_KEYS)
64
+
65
+ WeasyPDF::Renderer.new(pdf_options).pdf_from_string(html)
66
+ end
67
+
68
+ # Wicked PDF supported rendering an ERB template or a URL as the header/footer,
69
+ # compositing it as a separate PDF page. WeasyPrint only supports CSS @page margin
70
+ # boxes, which accept text and CSS counters — not arbitrary HTML.
71
+ # Raise early so the caller knows to use :left/:center/:right strings instead.
72
+ def validate_header_footer!(options)
73
+ %i[header footer].each do |hf|
74
+ next unless options[hf].is_a?(Hash) && options[hf][:html].is_a?(Hash)
75
+
76
+ raise WeasyPDF::Error,
77
+ "#{hf}: :html templates and URLs are not supported. " \
78
+ "WeasyPrint uses CSS @page margin boxes, not separate HTML pages. " \
79
+ "Use :left, :center, or :right with plain text and [page]/[topage] counters. " \
80
+ "For complex layouts, see WeasyPrint running elements: " \
81
+ "https://weasyprint.readthedocs.io/en/stable/tutorial.html#headers-and-footers"
82
+ end
83
+ options
84
+ end
85
+
86
+ def clean_temp_files
87
+ # No-op — kept for API compatibility with Wicked PDF.
88
+ # Renderer cleans its own tempfiles via ensure blocks.
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WeasyPDF
4
+ class Railtie < Rails::Railtie
5
+ initializer "weasy_pdf.pdf_helper" do
6
+ ActiveSupport.on_load(:action_controller_base) do
7
+ prepend WeasyPDF::PdfHelper
8
+ end
9
+ end
10
+
11
+ initializer "weasy_pdf.view_helpers" do
12
+ ActiveSupport.on_load(:action_view) do
13
+ include WeasyPDF::ViewHelpers::Assets
14
+ include WeasyPDF::ViewHelpers::ViteAssets if defined?(ViteRuby)
15
+ end
16
+ end
17
+
18
+ initializer "weasy_pdf.mime_type" do
19
+ Mime::Type.register("application/pdf", :pdf) \
20
+ unless Mime::Type.lookup_by_extension(:pdf)
21
+ end
22
+ end
23
+ end