sghtmltopdf 0.1.0-aarch64-linux
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/LICENSE +21 -0
- data/README.md +109 -0
- data/lib/sghtmltopdf/3.0/sghtmltopdf.so +0 -0
- data/lib/sghtmltopdf/3.1/sghtmltopdf.so +0 -0
- data/lib/sghtmltopdf/3.2/sghtmltopdf.so +0 -0
- data/lib/sghtmltopdf/3.3/sghtmltopdf.so +0 -0
- data/lib/sghtmltopdf/3.4/sghtmltopdf.so +0 -0
- data/lib/sghtmltopdf/4.0/sghtmltopdf.so +0 -0
- data/lib/sghtmltopdf/configuration.rb +68 -0
- data/lib/sghtmltopdf/options.rb +126 -0
- data/lib/sghtmltopdf/railtie.rb +31 -0
- data/lib/sghtmltopdf/renderer.rb +100 -0
- data/lib/sghtmltopdf/server_client.rb +139 -0
- data/lib/sghtmltopdf/version.rb +5 -0
- data/lib/sghtmltopdf/view_helpers.rb +85 -0
- data/lib/sghtmltopdf.rb +121 -0
- metadata +69 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 76c5e09cd07d909ef74f7b5c2c56ea163f16dd407e2d309238ac0932309e9528
|
|
4
|
+
data.tar.gz: 56498f8925b1e32b11d919b77620886cf7c39287d55d3fdeb6547e84e9bf35a6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: cc27feea209ad21fc3af19e4d3e0aac98039bf004ef6a21f42f18b8888af56a9390a491ebfc1a8e45482b545a9f3e7c258b5216ee162c9f7df4248276c6fd6d3
|
|
7
|
+
data.tar.gz: cc1033fec212e7a14c8a6f66da6f0d6cb2d257a1fd27d13d60b1bb6f59039ba73e211ce2db6b913a633dc9472f1f58e2a9c624785aacd5e5178323ee1a771154
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yo_waka
|
|
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,109 @@
|
|
|
1
|
+
# sghtmltopdf
|
|
2
|
+
|
|
3
|
+
Ruby binding for [sghtmltopdf](https://github.com/waka/sghtmltopdf), an HTML-to-PDF renderer written in Rust that does not depend on Chromium, WebKit, or Gecko.
|
|
4
|
+
|
|
5
|
+
The engine runs inside your process through a native extension (magnus + rb-sys) — no subprocess, no temporary files — and releases the GVL while rendering, so other Puma threads keep running.
|
|
6
|
+
|
|
7
|
+
[Documentation](https://waka.github.io/sghtmltopdf/en/usage/ruby_rails.html) · [Repository](https://github.com/waka/sghtmltopdf) · [CHANGELOG](https://github.com/waka/sghtmltopdf/blob/main/CHANGELOG.md)
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# Gemfile
|
|
13
|
+
gem "sghtmltopdf"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Precompiled native gems are published for `x86_64-linux`, `aarch64-linux`, `x86_64-linux-musl`, `aarch64-linux-musl`, and `arm64-darwin`.
|
|
17
|
+
There is no build step on those platforms.
|
|
18
|
+
|
|
19
|
+
Elsewhere (Intel Mac, Windows) the gem cannot run in-process — the source gem does not carry the Rust core and will refuse to build with an explanatory message.
|
|
20
|
+
Point those environments at a separate `sghtmltopdf server` process instead; see [Delegating to a server](#delegating-to-a-server).
|
|
21
|
+
|
|
22
|
+
Requires Ruby >= 3.2.
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
pdf = Sghtmltopdf.render("<h1>Invoice</h1>", page_size: "A4", margin_top: "20mm")
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Option names are the CLI long options without `--` and with `-` replaced by `_`, so `--page-size A4` becomes `page_size: "A4"`.
|
|
31
|
+
The [option reference](https://waka.github.io/sghtmltopdf/en/usage/cli/reference.html) lists all of them.
|
|
32
|
+
|
|
33
|
+
Write straight to a file (written to a temporary file and renamed on success, so a failure never leaves a broken PDF behind), or take the bytes in chunks:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
Sghtmltopdf.render_to_file(html, "invoice.pdf", page_size: "A4")
|
|
37
|
+
|
|
38
|
+
Sghtmltopdf.render(html) { |bytes| io.write(bytes) }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Rails
|
|
42
|
+
|
|
43
|
+
Adding the gem is enough; the Railtie wires everything up, and nothing is loaded when Rails is absent.
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
# config/initializers/sghtmltopdf.rb
|
|
47
|
+
Sghtmltopdf.configure do |c|
|
|
48
|
+
c.page_size = "A4"
|
|
49
|
+
c.gothic_font = Rails.root.join("vendor/fonts/NotoSansJP-Regular.ttf")
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
A `:pdf` renderer is registered, in the spirit of [wicked_pdf](https://github.com/mileszs/wicked_pdf) — the same keys, so an existing controller often needs no change at all:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
class InvoicesController < ApplicationController
|
|
57
|
+
def show
|
|
58
|
+
render pdf: "invoice", # filename; ".pdf" is appended
|
|
59
|
+
template: "invoices/show",
|
|
60
|
+
layout: "pdf",
|
|
61
|
+
page_size: "A4", margin_top: "20mm"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
View-rendering keys (`template`, `layout`, `locals`, …) go to `render_to_string`, response keys (`filename`, `disposition`, `status`) go to `send_data`, `show_as_html: true` returns the HTML instead of a PDF, and everything else is passed to the converter.
|
|
67
|
+
Converter keys are flat CLI flag names, so wicked_pdf's nested `margin: {top: 10}` becomes `margin_top: "10mm"` (with the unit spelled out); the [migration guide](https://waka.github.io/sghtmltopdf/en/migration/wicked-pdf.html) maps every key one by one.
|
|
68
|
+
|
|
69
|
+
### Assets
|
|
70
|
+
|
|
71
|
+
PDF rendering does not go through the HTTP server, so `/assets/…` URLs are resolved as local files: the Railtie defaults `base_url` to `Rails.root/public` and restricts local reads to `Rails.root` via `allow`.
|
|
72
|
+
That is enough for a precompiled production app; in development, these helpers inline the asset instead:
|
|
73
|
+
|
|
74
|
+
```erb
|
|
75
|
+
<%= sghtmltopdf_stylesheet_link_tag "pdf" %>
|
|
76
|
+
<%= sghtmltopdf_image_tag "logo.png" %>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Streaming the response
|
|
80
|
+
|
|
81
|
+
To send pages as soon as their layout is final, pass a block and use `ActionController::Live` — this also makes `Rack::Timeout` and `Thread#kill` effective at chunk boundaries:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
class InvoicesController < ApplicationController
|
|
85
|
+
include ActionController::Live
|
|
86
|
+
|
|
87
|
+
def show
|
|
88
|
+
response.headers["Content-Type"] = "application/pdf"
|
|
89
|
+
html = render_to_string(template: "invoices/show", layout: "pdf")
|
|
90
|
+
Sghtmltopdf.render(html) { |bytes| response.stream.write(bytes) }
|
|
91
|
+
ensure
|
|
92
|
+
response.stream.close
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Delegating to a server
|
|
98
|
+
|
|
99
|
+
If the gem cannot run where your app runs, or you would rather not spend the app's CPU on rendering, set `server_url` and the same calls are delegated over HTTP to a separate `sghtmltopdf server` process.
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
Sghtmltopdf.configure { |c| c.server_url = "http://pdf:8080" }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The [official Docker image](https://waka.github.io/sghtmltopdf/en/getting-started/docker.html) runs that server and bundles Japanese fonts.
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT License ([LICENSE](LICENSE)).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sghtmltopdf
|
|
4
|
+
# グローバルな既定オプション。
|
|
5
|
+
#
|
|
6
|
+
# Sghtmltopdf.configure do |c|
|
|
7
|
+
# c.page_size = "A4"
|
|
8
|
+
# c.gothic_font = "/path/to/NotoSansJP-Regular.ttf"
|
|
9
|
+
# end
|
|
10
|
+
#
|
|
11
|
+
# ここで設定した値は`render`/`render_to_file`の引数で上書きできる
|
|
12
|
+
# (マージ順はグローバル → 呼び出し時)。
|
|
13
|
+
#
|
|
14
|
+
# キー名の妥当性は検査しない。オプション定義はRust側(`cli/options.rs`)の
|
|
15
|
+
# 1箇所に集約する方針のため、未知のキーはレンダリング時にclapが`UsageError`をraiseする。
|
|
16
|
+
class Configuration
|
|
17
|
+
def initialize(options = {})
|
|
18
|
+
@options = {}
|
|
19
|
+
# 明示的に設定した値(@options)と、Railtieなどが流し込んだ既定値
|
|
20
|
+
# (@defaults)は分けて持つ。読み出しは常に@optionsが勝つので、
|
|
21
|
+
# イニシャライザの実行順に依存しない。
|
|
22
|
+
@defaults = {}
|
|
23
|
+
options.each { |key, value| self[key] = value }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def [](key)
|
|
27
|
+
key = key.to_sym
|
|
28
|
+
@options.key?(key) ? @options[key] : @defaults[key]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def []=(key, value)
|
|
32
|
+
@options[key.to_sym] = value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @param with_defaults [Boolean] 流し込まれた既定値を含めるか。
|
|
36
|
+
# HTTPサーバへ委譲するときは`false`にする。Rails向けの既定値
|
|
37
|
+
# (`base_url`・`allow`)はローカルのファイル解決のためのもので、
|
|
38
|
+
# サーバモードではリクエストから指定できないキーだから
|
|
39
|
+
def to_h(with_defaults: true)
|
|
40
|
+
with_defaults ? @defaults.merge(@options) : @options.dup
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# 既定値を流し込む。Railtieが Rails向けの既定値を入れるのに使う。
|
|
44
|
+
# 明示的に設定された値より弱い(順序に関係なく`[]=`が勝つ)。
|
|
45
|
+
def apply_defaults(defaults)
|
|
46
|
+
defaults.each { |key, value| @defaults[key.to_sym] = value }
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# `c.page_size = "A4"`と`c.page_size`を受ける。
|
|
51
|
+
def method_missing(name, *args)
|
|
52
|
+
key = name.to_s
|
|
53
|
+
if key.end_with?("=")
|
|
54
|
+
raise ArgumentError, "#{name}は引数1つを取ります" unless args.size == 1
|
|
55
|
+
|
|
56
|
+
self[key.chomp("=")] = args.first
|
|
57
|
+
else
|
|
58
|
+
raise ArgumentError, "#{name}は引数を取りません" unless args.empty?
|
|
59
|
+
|
|
60
|
+
self[key]
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def respond_to_missing?(_name, _include_private = false)
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module Sghtmltopdf
|
|
6
|
+
# オプションハッシュを変換する。
|
|
7
|
+
#
|
|
8
|
+
# * ネイティブ拡張へ渡すCLIの引数列(argv) … [.to_argv]
|
|
9
|
+
# * HTTPサーバモードへ渡すクエリ文字列 … [.to_query]
|
|
10
|
+
module Options
|
|
11
|
+
# 入力は常に標準入力を表す`-`を置く(実際のバイト列はFFIで直接渡すため
|
|
12
|
+
# 読まれない)。出力先はRust側のSinkが決めるので、ここもダミーの`-`。
|
|
13
|
+
# `-`入力のときCLIは`--output`を必須にするため、省略はできない。
|
|
14
|
+
ARGV_PREFIX = ["sghtmltopdf", "-", "--output", "-"].freeze
|
|
15
|
+
|
|
16
|
+
# Ruby側だけで解釈するキー。変換オプションではないので、argvにも
|
|
17
|
+
# クエリにも出さない。
|
|
18
|
+
TRANSPORT_KEYS = %i[server_url server_open_timeout server_read_timeout chunk_size].freeze
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# @param options [Hash] Rubyのオプションハッシュ
|
|
23
|
+
# @return [Array<String>] clapへ渡す引数列
|
|
24
|
+
def to_argv(options)
|
|
25
|
+
argv = ARGV_PREFIX.dup
|
|
26
|
+
each_pair(options) do |name, value|
|
|
27
|
+
argv.push("--#{name}")
|
|
28
|
+
argv.push(value) unless value.nil?
|
|
29
|
+
end
|
|
30
|
+
argv
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @param options [Hash] Rubyのオプションハッシュ
|
|
34
|
+
# @return [String] `POST /pdf`のクエリ文字列(先頭に`?`は付けない)
|
|
35
|
+
def to_query(options)
|
|
36
|
+
parts = []
|
|
37
|
+
each_pair(options) do |name, value|
|
|
38
|
+
# 値なしのフラグはキーだけを置く(サーバは値なし=真として扱う)。
|
|
39
|
+
parts << (value.nil? ? escape(name) : "#{escape(name)}=#{escape(value)}")
|
|
40
|
+
end
|
|
41
|
+
parts.join("&")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# 1つのキーと値をargvの断片へ変換する。
|
|
45
|
+
#
|
|
46
|
+
# page_size: "A4" → ["--page-size", "A4"]
|
|
47
|
+
# grayscale: true → ["--grayscale"]
|
|
48
|
+
# grayscale: false → []
|
|
49
|
+
# allow: ["/a", "/b"] → ["--allow", "/a", "--allow", "/b"]
|
|
50
|
+
def args_for(key, value)
|
|
51
|
+
pairs_for(key, value).flat_map { |name, arg| arg.nil? ? ["--#{name}"] : ["--#{name}", arg] }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# 1つのキーと値を「フラグ名と値」のペアの列にする。値が`nil`のペアは
|
|
55
|
+
# 値を取らないフラグ(`--toc`など)。
|
|
56
|
+
def pairs_for(key, value)
|
|
57
|
+
name = flag_name(key)
|
|
58
|
+
return font_pairs(value) if name == "font"
|
|
59
|
+
|
|
60
|
+
case value
|
|
61
|
+
when nil, false then []
|
|
62
|
+
when true then [[name, nil]]
|
|
63
|
+
# 配列は同じオプションの繰り返し。要素ごとに同じ規則を適用する。
|
|
64
|
+
when Array then value.flat_map { |element| pairs_for(key, element) }
|
|
65
|
+
when Hash
|
|
66
|
+
# wicked_pdfの`margin: {top: 10}`のような入れ子は受けない。数値の
|
|
67
|
+
# 単位の解釈が違う(wicked_pdfはmm・こちらはpx)ため、機械的に
|
|
68
|
+
# 平坦化すると黙って別の余白になる。移行時は
|
|
69
|
+
# 移行ガイドの対応表を見て書き換えてもらう。
|
|
70
|
+
example = value.keys.first
|
|
71
|
+
raise ArgumentError,
|
|
72
|
+
"#{key}にHashは渡せません(pathとindexを取るのは:fontだけです)。" \
|
|
73
|
+
"入れ子のオプションは平坦なキーで指定してください" \
|
|
74
|
+
"#{": 例 #{key}_#{example}: \"…\"" if example}"
|
|
75
|
+
else [[name, value.to_s]]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# `--font`と`--font-index`は出現順で対応付けられる(CLIは
|
|
80
|
+
# `ArgMatches#indices_of`で「`--font-index`より手前にある最後の`--font`」
|
|
81
|
+
# へ結び付ける)。そのため、フェイス番号は
|
|
82
|
+
# 必ず対応する`--font`の直後へ置く。
|
|
83
|
+
#
|
|
84
|
+
# font: "a.ttf" → ["--font", "a.ttf"]
|
|
85
|
+
# font: {path: "a.ttc", index: 1} → ["--font", "a.ttc", "--font-index", "1"]
|
|
86
|
+
# font: ["a.ttf", {path: "b.ttc", index: 2}]
|
|
87
|
+
# → ["--font", "a.ttf", "--font", "b.ttc", "--font-index", "2"]
|
|
88
|
+
def font_args(value)
|
|
89
|
+
font_pairs(value).flat_map { |name, arg| ["--#{name}", arg] }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def font_pairs(value)
|
|
93
|
+
case value
|
|
94
|
+
when nil, false then []
|
|
95
|
+
when Array then value.flat_map { |element| font_pairs(element) }
|
|
96
|
+
when Hash
|
|
97
|
+
path = value[:path] || value["path"]
|
|
98
|
+
raise ArgumentError, "fontのHashにはpathが必要です: #{value.inspect}" if path.nil?
|
|
99
|
+
|
|
100
|
+
index = value[:index] || value["index"]
|
|
101
|
+
pairs = [["font", path.to_s]]
|
|
102
|
+
pairs << ["font-index", index.to_s] unless index.nil?
|
|
103
|
+
pairs
|
|
104
|
+
else [["font", value.to_s]]
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# `:page_size` → `page-size`。
|
|
109
|
+
def flag_name(key)
|
|
110
|
+
key.to_s.tr("_", "-")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# 変換オプションだけを、渡された順にペアとして列挙する。
|
|
114
|
+
def each_pair(options, &block)
|
|
115
|
+
options.each do |key, value|
|
|
116
|
+
next if TRANSPORT_KEYS.include?(key.to_sym)
|
|
117
|
+
|
|
118
|
+
pairs_for(key, value).each(&block)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def escape(value)
|
|
123
|
+
URI.encode_www_form_component(value.to_s)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/railtie"
|
|
4
|
+
require_relative "renderer"
|
|
5
|
+
require_relative "view_helpers"
|
|
6
|
+
|
|
7
|
+
module Sghtmltopdf
|
|
8
|
+
class Railtie < ::Rails::Railtie
|
|
9
|
+
def self.default_options(root)
|
|
10
|
+
root = root.to_s
|
|
11
|
+
defaults = {allow: [root]}
|
|
12
|
+
public_dir = File.join(root, "public")
|
|
13
|
+
defaults[:base_url] = public_dir if File.directory?(public_dir)
|
|
14
|
+
defaults
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
initializer "sghtmltopdf.defaults" do |app|
|
|
18
|
+
Sghtmltopdf.config.apply_defaults(Sghtmltopdf::Railtie.default_options(app.root))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
initializer "sghtmltopdf.renderer" do
|
|
22
|
+
ActiveSupport.on_load(:action_controller) do
|
|
23
|
+
Sghtmltopdf::Renderer.register!
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
ActiveSupport.on_load(:action_view) do
|
|
27
|
+
include Sghtmltopdf::ViewHelpers
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sghtmltopdf
|
|
4
|
+
# `render pdf: "invoice"`のオプションを、
|
|
5
|
+
#
|
|
6
|
+
# * Railsのビュー描画(`render_to_string`)へ渡すもの
|
|
7
|
+
# * レスポンスの組み立て(`send_data`)へ渡すもの
|
|
8
|
+
# * PDF変換(`Sghtmltopdf.render`)へ渡すもの
|
|
9
|
+
#
|
|
10
|
+
# の3つに振り分ける。
|
|
11
|
+
# Railsに依存しないpure Rubyのクラスなので、Rails無しでも単体テストできる。
|
|
12
|
+
class Renderer
|
|
13
|
+
# `render_to_string`へそのまま渡すキー。
|
|
14
|
+
RAILS_RENDER_KEYS = %i[
|
|
15
|
+
action assigns body collection file formats handlers html inline layout
|
|
16
|
+
locals object partial plain prefixes template variants
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
# レスポンスの組み立てに使うキー(`send_data`へ渡す)。
|
|
20
|
+
RESPONSE_KEYS = %i[disposition filename status].freeze
|
|
21
|
+
|
|
22
|
+
# レンダラ自身が解釈するキー(PDFにせずHTMLのまま返すデバッグ用)。
|
|
23
|
+
RENDERER_KEYS = %i[show_as_html].freeze
|
|
24
|
+
|
|
25
|
+
PDF_CONTENT_TYPE = "application/pdf"
|
|
26
|
+
HTML_CONTENT_TYPE = "text/html"
|
|
27
|
+
|
|
28
|
+
attr_reader :name, :options
|
|
29
|
+
|
|
30
|
+
# @param name [String, Symbol, nil] `pdf:`に渡された値(ファイル名の素)
|
|
31
|
+
# @param options [Hash] `render`に渡されたその他のオプション
|
|
32
|
+
# @param default_name [String, nil] `name`が空のときのファイル名
|
|
33
|
+
# (コントローラの`action_name`を想定)
|
|
34
|
+
def initialize(name, options = {}, default_name: nil)
|
|
35
|
+
@name = blank?(name) ? (default_name || "document").to_s : name.to_s
|
|
36
|
+
@options = options.to_h { |key, value| [key.to_sym, value] }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# `ActionController::Renderers.add(:pdf)`でレンダラを登録する。
|
|
40
|
+
# RailtieのAction Controller読み込みフック(`on_load`)から呼ぶ。
|
|
41
|
+
def self.register!
|
|
42
|
+
::ActionController::Renderers.add(:pdf) do |name, options|
|
|
43
|
+
renderer = ::Sghtmltopdf::Renderer.new(name, options, default_name: action_name)
|
|
44
|
+
html = render_to_string(**renderer.render_options)
|
|
45
|
+
send_data(renderer.body_for(html), **renderer.send_data_options)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# ビューの描画に使うオプション。
|
|
50
|
+
def render_options
|
|
51
|
+
options.select { |key, _| RAILS_RENDER_KEYS.include?(key) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# PDF変換に使うオプション。
|
|
55
|
+
def convert_options
|
|
56
|
+
known = RAILS_RENDER_KEYS + RESPONSE_KEYS + RENDERER_KEYS
|
|
57
|
+
options.reject { |key, _| known.include?(key) }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# 描画したHTMLをレスポンスの本文へ変換する。
|
|
61
|
+
def body_for(html)
|
|
62
|
+
show_as_html? ? html : Sghtmltopdf.render(html, **convert_options)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def send_data_options
|
|
66
|
+
opts = {type: content_type, disposition: disposition}
|
|
67
|
+
opts[:filename] = filename unless show_as_html?
|
|
68
|
+
opts[:status] = options[:status] if options.key?(:status)
|
|
69
|
+
opts
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def content_type
|
|
73
|
+
show_as_html? ? HTML_CONTENT_TYPE : PDF_CONTENT_TYPE
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# `filename: "x.pdf"` > `pdf: "x"` の順。拡張子は二重に付けない。
|
|
77
|
+
def filename
|
|
78
|
+
base = blank?(options[:filename]) ? name : options[:filename].to_s
|
|
79
|
+
base.downcase.end_with?(".pdf") ? base : "#{base}.pdf"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# wicked_pdfと同じく既定は`inline`(ブラウザ内で開く)。
|
|
83
|
+
def disposition
|
|
84
|
+
blank?(options[:disposition]) ? "inline" : options[:disposition].to_s
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# wicked_pdfの`show_as_html`相当。PDFにせずHTMLをそのまま返すので、
|
|
88
|
+
# ブラウザの開発者ツールでレイアウトを確認できる。
|
|
89
|
+
def show_as_html?
|
|
90
|
+
value = options[:show_as_html]
|
|
91
|
+
!(value.nil? || value == false || value == "false")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def blank?(value)
|
|
97
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module Sghtmltopdf
|
|
7
|
+
class ServerError < Error; end
|
|
8
|
+
|
|
9
|
+
# HTTPサーバモード(`sghtmltopdf server`)へ変換を委譲するクライアント。
|
|
10
|
+
#
|
|
11
|
+
# Sghtmltopdf.configure { |c| c.server_url = "http://pdf.internal:8080" }
|
|
12
|
+
# pdf = Sghtmltopdf.render(html, page_size: "A4")
|
|
13
|
+
class ServerClient
|
|
14
|
+
DEFAULT_OPEN_TIMEOUT = 5
|
|
15
|
+
DEFAULT_READ_TIMEOUT = 120
|
|
16
|
+
|
|
17
|
+
# 一度に読むチャンクの目安。`?stream=1`のときはこの単位でブロックへ渡る。
|
|
18
|
+
CHUNK_SIZE = 64 * 1024
|
|
19
|
+
|
|
20
|
+
attr_reader :uri, :open_timeout, :read_timeout
|
|
21
|
+
|
|
22
|
+
# @param url [String] サーバのベースURL(`http://host:port`)
|
|
23
|
+
def initialize(url, open_timeout: nil, read_timeout: nil)
|
|
24
|
+
@uri = parse(url)
|
|
25
|
+
@open_timeout = (open_timeout || DEFAULT_OPEN_TIMEOUT).to_f
|
|
26
|
+
@read_timeout = (read_timeout || DEFAULT_READ_TIMEOUT).to_f
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# HTMLをPDFへ変換する。
|
|
30
|
+
#
|
|
31
|
+
# ブロックを渡すと`?stream=1`(chunked transfer encoding)を使い、
|
|
32
|
+
# サーバがページを確定したそばからチャンクを渡す。ブロックが無ければ
|
|
33
|
+
# PDF全体をStringで返す。
|
|
34
|
+
def render(html, options, &block)
|
|
35
|
+
request = build_request(html, options, stream: !block.nil?)
|
|
36
|
+
pdf = nil
|
|
37
|
+
start do |http|
|
|
38
|
+
# `request`はブロック付きだとレスポンスオブジェクトを返すので、
|
|
39
|
+
# 結果は外の変数で受ける。
|
|
40
|
+
http.request(request) do |response|
|
|
41
|
+
ensure_success!(response)
|
|
42
|
+
if block
|
|
43
|
+
response.read_body { |chunk| block.call(chunk.b) }
|
|
44
|
+
else
|
|
45
|
+
pdf = read_all(response)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
pdf
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# 変換結果を`path`へ書き出す。途中で失敗しても壊れたPDFを残さないよう、
|
|
53
|
+
# 一時ファイルへ書いてからrenameする(ネイティブ拡張の`FileSink`と同じ
|
|
54
|
+
# 挙動に揃えている)。
|
|
55
|
+
def render_to_file(html, options, path)
|
|
56
|
+
tmp = "#{path}.#{Process.pid}.tmp"
|
|
57
|
+
begin
|
|
58
|
+
File.open(tmp, "wb") do |file|
|
|
59
|
+
render(html, options) { |chunk| file.write(chunk) }
|
|
60
|
+
end
|
|
61
|
+
rescue SystemCallError => e
|
|
62
|
+
File.unlink(tmp) if File.exist?(tmp)
|
|
63
|
+
raise InputError, "#{path}への書き出しに失敗しました: #{e.message}"
|
|
64
|
+
rescue StandardError
|
|
65
|
+
File.unlink(tmp) if File.exist?(tmp)
|
|
66
|
+
raise
|
|
67
|
+
end
|
|
68
|
+
File.rename(tmp, path)
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def parse(url)
|
|
75
|
+
uri = URI.parse(url.to_s)
|
|
76
|
+
unless uri.is_a?(URI::HTTP) && uri.host
|
|
77
|
+
raise ArgumentError, "server_urlにはhttp(s)のURLを指定してください: #{url.inspect}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
uri
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def build_request(html, options, stream:)
|
|
84
|
+
query = Options.to_query(options)
|
|
85
|
+
query = stream ? [query, "stream=1"].reject(&:empty?).join("&") : query
|
|
86
|
+
target = uri.dup
|
|
87
|
+
target.path = "/pdf"
|
|
88
|
+
target.query = query.empty? ? nil : query
|
|
89
|
+
|
|
90
|
+
request = Net::HTTP::Post.new(target)
|
|
91
|
+
request["Content-Type"] = "text/html; charset=utf-8"
|
|
92
|
+
request.body = html.to_s.b
|
|
93
|
+
request
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def start(&block)
|
|
97
|
+
Net::HTTP.start(
|
|
98
|
+
uri.host, uri.port,
|
|
99
|
+
use_ssl: uri.scheme == "https",
|
|
100
|
+
open_timeout: open_timeout,
|
|
101
|
+
read_timeout: read_timeout,
|
|
102
|
+
&block
|
|
103
|
+
)
|
|
104
|
+
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
105
|
+
raise ServerError, "#{base}への接続がタイムアウトしました: #{e.class}"
|
|
106
|
+
rescue SocketError, SystemCallError, IOError, OpenSSL::SSL::SSLError => e
|
|
107
|
+
raise ServerError, "#{base}への接続に失敗しました: #{e.message}"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# エラー応答の本文は`text/plain`の日本語メッセージ(CLIと同じ文言)。
|
|
111
|
+
def ensure_success!(response)
|
|
112
|
+
return if response.is_a?(Net::HTTPOK)
|
|
113
|
+
|
|
114
|
+
message = read_all(response).force_encoding(Encoding::UTF_8).strip
|
|
115
|
+
raise error_class(response), "#{base}: #{message}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def error_class(response)
|
|
119
|
+
case response.code.to_i
|
|
120
|
+
when 400 then UsageError
|
|
121
|
+
when 413 then InputError
|
|
122
|
+
when 500 then RenderError
|
|
123
|
+
# 404/405はパスやメソッドの間違い=相手がsghtmltopdfのサーバでない
|
|
124
|
+
# 可能性が高い。503/504はキュー溢れ・キュー待ちのタイムアウト。
|
|
125
|
+
else ServerError
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def read_all(response)
|
|
130
|
+
buffer = +""
|
|
131
|
+
response.read_body { |chunk| buffer << chunk }
|
|
132
|
+
buffer.b
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def base
|
|
136
|
+
"#{uri.scheme}://#{uri.host}:#{uri.port}"
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sghtmltopdf
|
|
4
|
+
# Action View用のヘルパ。
|
|
5
|
+
#
|
|
6
|
+
# PDFのレンダリングはHTTPサーバを介さないため、`/assets/…`のようなURLは
|
|
7
|
+
# ローカルファイルとして解決される(`--base-url`の既定は
|
|
8
|
+
# `Rails.root/public`。[Railtie.default_options])。precompile済みの
|
|
9
|
+
# 本番環境ではこれで素の`stylesheet_link_tag`もそのまま動くが、開発環境の
|
|
10
|
+
# ようにアセットがまだ`public/`へ書き出されていない場合は解決できない。
|
|
11
|
+
#
|
|
12
|
+
# そこで、
|
|
13
|
+
#
|
|
14
|
+
# <%= sghtmltopdf_stylesheet_link_tag "pdf" %>
|
|
15
|
+
#
|
|
16
|
+
# のようにCSSの中身を`<style>`へ展開するヘルパを用意する(wicked_pdfの
|
|
17
|
+
# `wicked_pdf_stylesheet_link_tag`に相当)。
|
|
18
|
+
module ViewHelpers
|
|
19
|
+
# アセットのローカルファイルパスを返す。見つからなければ`nil`。
|
|
20
|
+
#
|
|
21
|
+
# 1. `public/`配下(precompile済み。本番環境)
|
|
22
|
+
# 2. アセットパイプラインのロードパス(開発環境。Propshaft/Sprockets)
|
|
23
|
+
#
|
|
24
|
+
# の順に探す。パイプラインの参照はどちらのgemにも依存しないよう
|
|
25
|
+
# `respond_to?`で分岐している(best effort)。
|
|
26
|
+
def sghtmltopdf_asset_path(source)
|
|
27
|
+
path = source.to_s
|
|
28
|
+
return path if path.start_with?("/") && File.file?(path)
|
|
29
|
+
|
|
30
|
+
from_public_dir(path) || from_asset_pipeline(path)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# CSSの中身を`<style>`へ展開する。複数指定でき、見つからないものは
|
|
34
|
+
# 黙って飛ばす(PDF生成そのものは止めない)。
|
|
35
|
+
def sghtmltopdf_stylesheet_link_tag(*sources)
|
|
36
|
+
css = sources.flatten.filter_map do |source|
|
|
37
|
+
path = sghtmltopdf_asset_path(with_extension(source, ".css"))
|
|
38
|
+
File.read(path) if path
|
|
39
|
+
end
|
|
40
|
+
return "".html_safe if css.empty?
|
|
41
|
+
|
|
42
|
+
content_tag(:style, css.join("\n").html_safe, type: "text/css")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# `image_tag`のsrcをローカルファイルパスへ差し替える。
|
|
46
|
+
def sghtmltopdf_image_tag(source, options = {})
|
|
47
|
+
image_tag(sghtmltopdf_asset_path(source) || source, options)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
# `asset_path`が返すURL(asset_hostが付くこともある)からパス部分だけを
|
|
53
|
+
# 取り出し、`public/`配下の実ファイルへ対応付ける。
|
|
54
|
+
def from_public_dir(source)
|
|
55
|
+
url = respond_to?(:asset_path) ? asset_path(source) : source
|
|
56
|
+
relative = url.to_s.sub(%r{\Ahttps?://[^/]+}, "").split(/[?#]/).first.to_s
|
|
57
|
+
return nil if relative.empty?
|
|
58
|
+
|
|
59
|
+
candidate = File.join(::Rails.public_path.to_s, relative)
|
|
60
|
+
File.file?(candidate) ? candidate : nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def from_asset_pipeline(source)
|
|
64
|
+
assets = ::Rails.application.try(:assets)
|
|
65
|
+
return nil if assets.nil?
|
|
66
|
+
|
|
67
|
+
# Propshaft
|
|
68
|
+
if assets.respond_to?(:load_path)
|
|
69
|
+
found = assets.load_path.find(source)
|
|
70
|
+
return found.path.to_s if found.respond_to?(:path)
|
|
71
|
+
end
|
|
72
|
+
# Sprockets
|
|
73
|
+
if assets.respond_to?(:[])
|
|
74
|
+
found = assets[source]
|
|
75
|
+
return found.filename.to_s if found.respond_to?(:filename)
|
|
76
|
+
end
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def with_extension(source, extension)
|
|
81
|
+
name = source.to_s
|
|
82
|
+
name.end_with?(extension) ? name : "#{name}#{extension}"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
data/lib/sghtmltopdf.rb
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "sghtmltopdf/version"
|
|
4
|
+
require_relative "sghtmltopdf/options"
|
|
5
|
+
require_relative "sghtmltopdf/configuration"
|
|
6
|
+
require_relative "sghtmltopdf/renderer"
|
|
7
|
+
|
|
8
|
+
# precompiled gemはRubyのマイナーバージョンごとのディレクトリへ`.so`を置く
|
|
9
|
+
# (rake-compilerのクロスビルドの慣習)。開発中の`rake compile`は
|
|
10
|
+
# `lib/sghtmltopdf/sghtmltopdf.so`に置くので、両方を試す。
|
|
11
|
+
begin
|
|
12
|
+
RUBY_VERSION =~ /(\d+\.\d+)/
|
|
13
|
+
require "sghtmltopdf/#{Regexp.last_match(1)}/sghtmltopdf"
|
|
14
|
+
rescue LoadError
|
|
15
|
+
require "sghtmltopdf/sghtmltopdf"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# `Error`(ネイティブ拡張が定義)を継承するため、拡張の読み込みより後に書く。
|
|
19
|
+
require_relative "sghtmltopdf/server_client"
|
|
20
|
+
|
|
21
|
+
module Sghtmltopdf
|
|
22
|
+
# ブロック付き`render`で1回に渡すバイト数の目安(ローカル変換のみ)。
|
|
23
|
+
# ページ確定ごとにブロックを呼ぶとGVLの取り直しが増えるため、ここまで
|
|
24
|
+
# 溜めてから渡す。
|
|
25
|
+
DEFAULT_CHUNK_SIZE = 64 * 1024
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
# HTMLを変換してPDFのバイト列(ASCII-8BITのString)を返す。
|
|
29
|
+
#
|
|
30
|
+
# ブロックを渡すと、PDF全体を組み立ててから返す代わりにチャンクごとに
|
|
31
|
+
# ブロックを呼ぶ(返り値はnil)。Rackの`response.stream`へ流したり、S3の
|
|
32
|
+
# マルチパートアップロードへ繋いだりするための口(エンジン側は出力先(sink)
|
|
33
|
+
# を意識しない設計に対応する)。
|
|
34
|
+
#
|
|
35
|
+
# Sghtmltopdf.render(html) { |bytes| response.stream.write(bytes) }
|
|
36
|
+
#
|
|
37
|
+
# ローカル・サーバ委譲のどちらでも、PDF全体が組み上がるのを待たずに
|
|
38
|
+
# 書き出せる(ローカルは確定したページから順に、サーバは`?stream=1`の
|
|
39
|
+
# chunked transfer encodingをそのまま渡す)。
|
|
40
|
+
#
|
|
41
|
+
# ただし逐次になるのはPDFの書き出しだけで、HTMLのパースとレイアウトは
|
|
42
|
+
# 文書全体に対して先に行う。最初のチャンクが届くのは変換の終盤で、
|
|
43
|
+
# ピークメモリもブロック無しの場合と変わらない。HTMLを読みながら
|
|
44
|
+
# ページを確定させたい場合は`streaming: true`と併せて使う
|
|
45
|
+
# (制約と引き換えにメモリが大きく減る)。
|
|
46
|
+
#
|
|
47
|
+
# 1回に渡すバイト数の目安は`chunk_size:`で変えられる(既定64KiB。
|
|
48
|
+
# ローカル変換のみ。小さくするとGVLの取り直しが増える)。
|
|
49
|
+
def render(html, **options, &block)
|
|
50
|
+
client = server_client(options)
|
|
51
|
+
return client.render(html.to_s, server_options(options), &block) if client
|
|
52
|
+
return Native.render(html.to_s, argv_for(options)) if block.nil?
|
|
53
|
+
|
|
54
|
+
Native.render_each(html.to_s, argv_for(options), block, chunk_size(options))
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# HTMLを変換して`path`へ書き出す。
|
|
59
|
+
#
|
|
60
|
+
# 一時ファイルへ書いて成功時だけrenameするため、途中で失敗しても
|
|
61
|
+
# 壊れたPDFが出力先に残らない(サーバへ委譲する場合も同じ)。
|
|
62
|
+
def render_to_file(html, path, **options)
|
|
63
|
+
client = server_client(options)
|
|
64
|
+
return client.render_to_file(html.to_s, server_options(options), path.to_s) if client
|
|
65
|
+
|
|
66
|
+
Native.render_to_file(html.to_s, argv_for(options), path.to_s)
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# グローバルな既定オプション。
|
|
71
|
+
def configure
|
|
72
|
+
yield config
|
|
73
|
+
config
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def config
|
|
77
|
+
@config ||= Configuration.new
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# 主にテスト用。設定を空に戻す。
|
|
81
|
+
def reset_config!
|
|
82
|
+
@config = Configuration.new
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
# グローバル設定 → 呼び出し時オプションの順にマージしてargvにする。
|
|
88
|
+
def argv_for(options)
|
|
89
|
+
Options.to_argv(config.to_h.merge(options))
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# `server_url`があればサーバへ委譲する。タイムアウトも同じ順でマージする。
|
|
93
|
+
def server_client(options)
|
|
94
|
+
merged = config.to_h.merge(options)
|
|
95
|
+
url = merged[:server_url]
|
|
96
|
+
return nil if url.nil? || url.to_s.empty?
|
|
97
|
+
|
|
98
|
+
ServerClient.new(
|
|
99
|
+
url,
|
|
100
|
+
open_timeout: merged[:server_open_timeout],
|
|
101
|
+
read_timeout: merged[:server_read_timeout]
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# ブロックへ1回に渡すバイト数の目安(ローカル変換のみ)。
|
|
106
|
+
def chunk_size(options)
|
|
107
|
+
value = config.to_h.merge(options)[:chunk_size]
|
|
108
|
+
value.nil? ? DEFAULT_CHUNK_SIZE : Integer(value)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# サーバへ渡すオプション。流し込まれた既定値は外す
|
|
112
|
+
# (Rails向けの`base_url`・`allow`はローカルのファイル解決のための
|
|
113
|
+
# 既定値で、サーバモードではリクエストから指定できず400になる。
|
|
114
|
+
# 明示的に設定した値はそのまま送り、可否はサーバに判断させる)。
|
|
115
|
+
def server_options(options)
|
|
116
|
+
config.to_h(with_defaults: false).merge(options)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
require_relative "sghtmltopdf/railtie" if defined?(::Rails::Railtie)
|
metadata
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sghtmltopdf
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: aarch64-linux
|
|
6
|
+
authors:
|
|
7
|
+
- yo_waka
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-08-08 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: |
|
|
14
|
+
wkhtmltopdfの後継として、Chromium/WebKit/Geckoに依存しないRust製の
|
|
15
|
+
HTML→PDFレンダリングエンジンをRubyから使うためのバインディング。
|
|
16
|
+
Railsからはwicked_pdf互換のレンダラ(`render pdf: "invoice"`)で使える。
|
|
17
|
+
email:
|
|
18
|
+
- y.wakahara@gmail.com
|
|
19
|
+
executables: []
|
|
20
|
+
extensions: []
|
|
21
|
+
extra_rdoc_files: []
|
|
22
|
+
files:
|
|
23
|
+
- LICENSE
|
|
24
|
+
- README.md
|
|
25
|
+
- lib/sghtmltopdf.rb
|
|
26
|
+
- lib/sghtmltopdf/3.0/sghtmltopdf.so
|
|
27
|
+
- lib/sghtmltopdf/3.1/sghtmltopdf.so
|
|
28
|
+
- lib/sghtmltopdf/3.2/sghtmltopdf.so
|
|
29
|
+
- lib/sghtmltopdf/3.3/sghtmltopdf.so
|
|
30
|
+
- lib/sghtmltopdf/3.4/sghtmltopdf.so
|
|
31
|
+
- lib/sghtmltopdf/4.0/sghtmltopdf.so
|
|
32
|
+
- lib/sghtmltopdf/configuration.rb
|
|
33
|
+
- lib/sghtmltopdf/options.rb
|
|
34
|
+
- lib/sghtmltopdf/railtie.rb
|
|
35
|
+
- lib/sghtmltopdf/renderer.rb
|
|
36
|
+
- lib/sghtmltopdf/server_client.rb
|
|
37
|
+
- lib/sghtmltopdf/version.rb
|
|
38
|
+
- lib/sghtmltopdf/view_helpers.rb
|
|
39
|
+
homepage: https://github.com/waka/sghtmltopdf
|
|
40
|
+
licenses:
|
|
41
|
+
- MIT
|
|
42
|
+
metadata:
|
|
43
|
+
source_code_uri: https://github.com/waka/sghtmltopdf
|
|
44
|
+
changelog_uri: https://github.com/waka/sghtmltopdf/blob/main/CHANGELOG.md
|
|
45
|
+
bug_tracker_uri: https://github.com/waka/sghtmltopdf/issues
|
|
46
|
+
rubygems_mfa_required: 'true'
|
|
47
|
+
post_install_message:
|
|
48
|
+
rdoc_options: []
|
|
49
|
+
require_paths:
|
|
50
|
+
- lib
|
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - ">="
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: '3.0'
|
|
56
|
+
- - "<"
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: 4.1.dev
|
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: '0'
|
|
64
|
+
requirements: []
|
|
65
|
+
rubygems_version: 3.5.23
|
|
66
|
+
signing_key:
|
|
67
|
+
specification_version: 4
|
|
68
|
+
summary: Chromium/WebKit/Geckoに依存しないHTML→PDFレンダラー
|
|
69
|
+
test_files: []
|