sghtmltopdf 0.4.0-aarch64-linux → 0.5.1-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 +4 -4
- data/README.md +27 -2
- 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 +17 -17
- data/lib/sghtmltopdf/options.rb +37 -40
- data/lib/sghtmltopdf/railtie.rb +5 -5
- data/lib/sghtmltopdf/renderer.rb +22 -22
- data/lib/sghtmltopdf/server_client.rb +18 -19
- data/lib/sghtmltopdf/version.rb +1 -1
- data/lib/sghtmltopdf.rb +33 -34
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 35fd52de515952053bbd3f16b2b8bc8b06a1271afbfa70ef7968d8c71e3e0831
|
|
4
|
+
data.tar.gz: 5edee4a9f35c7997f572d74faf106046b16d8f92b0c47f8527a0d78c289e1b58
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b215282c4ba6e4a67c40f04bf5ee4ae7d1aa67528e4e044b10ba359c237fb93114acea7884775acf86aaa36ffe2b89ff103522858b6cc719918eace286906534
|
|
7
|
+
data.tar.gz: 3c578b24199edb331c9d8bc117f75fc7c779e77da2929596d3c35d72fa771217d4c1b57a4859086624b4424ac49422293560ebdb99a364ee11e695a8b015eb15
|
data/README.md
CHANGED
|
@@ -13,10 +13,10 @@ The engine runs inside your process through a native extension (magnus + rb-sys)
|
|
|
13
13
|
gem "sghtmltopdf"
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
Precompiled native gems are published for `x86_64-linux`, `aarch64-linux`, `x86_64-linux-musl`, `aarch64-linux-musl`, and `
|
|
16
|
+
Precompiled native gems are published for `x86_64-linux`, `aarch64-linux`, `x86_64-linux-musl`, `aarch64-linux-musl`, `arm64-darwin`, and `x86_64-darwin`.
|
|
17
17
|
There is no build step on those platforms.
|
|
18
18
|
|
|
19
|
-
Elsewhere (
|
|
19
|
+
Elsewhere (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
20
|
Point those environments at a separate `sghtmltopdf server` process instead; see [Delegating to a server](#delegating-to-a-server).
|
|
21
21
|
|
|
22
22
|
Requires Ruby >= 3.2.
|
|
@@ -38,6 +38,31 @@ Sghtmltopdf.render_to_file(html, "invoice.pdf", page_size: "A4")
|
|
|
38
38
|
Sghtmltopdf.render(html) { |bytes| io.write(bytes) }
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## Header and footer HTML
|
|
42
|
+
|
|
43
|
+
Pass markup directly without creating temporary files:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
Sghtmltopdf.render(html,
|
|
47
|
+
header_html_content: '<div>Invoice [title]</div>',
|
|
48
|
+
footer_html_content: '<div>Page [page] of [topage]</div>')
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
These options work with `render`, block output, `render_to_file`, global
|
|
52
|
+
configuration, and server delegation. The shared CLI options are
|
|
53
|
+
`--header-html-content` and `--footer-html-content`.
|
|
54
|
+
|
|
55
|
+
`header_html` and `footer_html` still accept file paths. Supplying both a path
|
|
56
|
+
and content for the same side raises `Sghtmltopdf::UsageError`, including when
|
|
57
|
+
one comes from global configuration. `nil` or `false` disables a configured
|
|
58
|
+
option; an empty string is an explicitly empty header or footer. Either HTML
|
|
59
|
+
form takes precedence over simple text options for that side.
|
|
60
|
+
|
|
61
|
+
Content uses the same placeholder expansion and margin clipping as file input.
|
|
62
|
+
Embedded `data:` images are supported; external resources remain blocked.
|
|
63
|
+
With server delegation, markup travels in URL query parameters, so URL length
|
|
64
|
+
limits may apply and request logs may contain the markup.
|
|
65
|
+
|
|
41
66
|
## Rails
|
|
42
67
|
|
|
43
68
|
Adding the gem is enough; the Railtie wires everything up, and nothing is loaded when Rails is absent.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Sghtmltopdf
|
|
4
|
-
#
|
|
4
|
+
# The global default options.
|
|
5
5
|
#
|
|
6
6
|
# Sghtmltopdf.configure do |c|
|
|
7
7
|
# c.page_size = "A4"
|
|
8
8
|
# c.gothic_font = "/path/to/NotoSansJP-Regular.ttf"
|
|
9
9
|
# end
|
|
10
10
|
#
|
|
11
|
-
#
|
|
12
|
-
# (
|
|
11
|
+
# A value set here can be overridden by an argument to `render`/`render_to_file`
|
|
12
|
+
# (merged in the order global, then call-time).
|
|
13
13
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
14
|
+
# Key names are not validated. The option definitions live in one place on the Rust side
|
|
15
|
+
# (`cli/options.rs`), so an unknown key makes clap raise a `UsageError` at render time.
|
|
16
16
|
class Configuration
|
|
17
17
|
def initialize(options = {})
|
|
18
18
|
@options = {}
|
|
19
|
-
#
|
|
20
|
-
# (@defaults)
|
|
21
|
-
#
|
|
19
|
+
# Values set explicitly (@options) are kept separately from the defaults injected by
|
|
20
|
+
# the Railtie and others (@defaults). Reads always prefer @options, so nothing depends
|
|
21
|
+
# on the order the initialisers run in.
|
|
22
22
|
@defaults = {}
|
|
23
23
|
options.each { |key, value| self[key] = value }
|
|
24
24
|
end
|
|
@@ -32,30 +32,30 @@ module Sghtmltopdf
|
|
|
32
32
|
@options[Options.canonical_key(key)] = value
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
# @param with_defaults [Boolean]
|
|
36
|
-
# HTTP
|
|
37
|
-
# (`base_url
|
|
38
|
-
#
|
|
35
|
+
# @param with_defaults [Boolean] whether to include the injected defaults.
|
|
36
|
+
# Set it to `false` when delegating to the HTTP server: the Rails-oriented defaults
|
|
37
|
+
# (`base_url` and `allow`) exist for local file resolution and are keys server mode
|
|
38
|
+
# cannot take from a request
|
|
39
39
|
def to_h(with_defaults: true)
|
|
40
40
|
with_defaults ? @defaults.merge(@options) : @options.dup
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
#
|
|
44
|
-
#
|
|
43
|
+
# Inject the defaults. Used by the Railtie to set the Rails-oriented defaults.
|
|
44
|
+
# They are weaker than explicitly set values (`[]=` wins regardless of order).
|
|
45
45
|
def apply_defaults(defaults)
|
|
46
46
|
defaults.each { |key, value| @defaults[Options.canonical_key(key)] = value }
|
|
47
47
|
self
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
# `c.page_size = "A4"
|
|
50
|
+
# Accepts both `c.page_size = "A4"` and `c.page_size`.
|
|
51
51
|
def method_missing(name, *args)
|
|
52
52
|
key = name.to_s
|
|
53
53
|
if key.end_with?("=")
|
|
54
|
-
raise ArgumentError, "#{name}
|
|
54
|
+
raise ArgumentError, "#{name} takes one argument" unless args.size == 1
|
|
55
55
|
|
|
56
56
|
self[key.chomp("=")] = args.first
|
|
57
57
|
else
|
|
58
|
-
raise ArgumentError, "#{name}
|
|
58
|
+
raise ArgumentError, "#{name} takes no arguments" unless args.empty?
|
|
59
59
|
|
|
60
60
|
self[key]
|
|
61
61
|
end
|
data/lib/sghtmltopdf/options.rb
CHANGED
|
@@ -3,44 +3,42 @@
|
|
|
3
3
|
require "uri"
|
|
4
4
|
|
|
5
5
|
module Sghtmltopdf
|
|
6
|
-
#
|
|
6
|
+
# Converts an options hash into:
|
|
7
7
|
#
|
|
8
|
-
# *
|
|
9
|
-
# * HTTP
|
|
8
|
+
# * the CLI argument list (argv) passed to the native extension ... [.to_argv]
|
|
9
|
+
# * the query string passed to HTTP server mode ... [.to_query]
|
|
10
10
|
module Options
|
|
11
|
-
# 入力は常に標準入力を表す`-`を置く(実際のバイト列はFFIで直接渡すため
|
|
12
|
-
# 読まれない)。出力先はRust側のSinkが決めるので、ここもダミーの`-`。
|
|
13
|
-
# `-`入力のときCLIは`--output`を必須にするため、省略はできない。
|
|
14
|
-
ARGV_PREFIX = ["sghtmltopdf", "-", "--output", "-"].freeze
|
|
15
11
|
|
|
16
|
-
# Ruby
|
|
17
|
-
#
|
|
12
|
+
# The keys interpreted on the Ruby side alone. They are not conversion options, so they
|
|
13
|
+
# appear in neither the argv nor the query.
|
|
18
14
|
TRANSPORT_KEYS = %i[server_url server_open_timeout server_read_timeout chunk_size].freeze
|
|
19
15
|
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
16
|
+
# Alias keys (values are the canonical names). The CLI accepts `--allow` as an
|
|
17
|
+
# alias of `--allow-path`, but the Ruby side must not carry both keys around.
|
|
18
|
+
# If the defaults use one key and the call uses the other, a hash merge does
|
|
19
|
+
# not override: both end up in argv (repeating the same flag means "union",
|
|
20
|
+
# not "replace").
|
|
25
21
|
ALIAS_KEYS = {allow: :allow_path}.freeze
|
|
26
22
|
|
|
27
23
|
module_function
|
|
28
24
|
|
|
29
|
-
#
|
|
25
|
+
# Map an alias key to its canonical name.
|
|
30
26
|
def canonical_key(key)
|
|
31
27
|
key = key.to_sym
|
|
32
28
|
ALIAS_KEYS.fetch(key, key)
|
|
33
29
|
end
|
|
34
30
|
|
|
35
|
-
#
|
|
31
|
+
# Normalize all keys of a hash.
|
|
36
32
|
def canonicalize(options)
|
|
37
33
|
options.to_h { |key, value| [canonical_key(key), value] }
|
|
38
34
|
end
|
|
39
35
|
|
|
40
|
-
# @param options [Hash] Ruby
|
|
41
|
-
# @return [Array<String>]
|
|
36
|
+
# @param options [Hash] the Ruby options hash
|
|
37
|
+
# @return [Array<String>] the conversion options passed to the core's `Converter`.
|
|
38
|
+
# The HTML and the destination travel over FFI, so there is no program name, input
|
|
39
|
+
# path or `--output` here.
|
|
42
40
|
def to_argv(options)
|
|
43
|
-
argv =
|
|
41
|
+
argv = []
|
|
44
42
|
each_pair(options) do |name, value|
|
|
45
43
|
argv.push("--#{name}")
|
|
46
44
|
argv.push(value) unless value.nil?
|
|
@@ -48,18 +46,18 @@ module Sghtmltopdf
|
|
|
48
46
|
argv
|
|
49
47
|
end
|
|
50
48
|
|
|
51
|
-
# @param options [Hash] Ruby
|
|
52
|
-
# @return [String] `POST /pdf
|
|
49
|
+
# @param options [Hash] the Ruby options hash
|
|
50
|
+
# @return [String] the query string for `POST /pdf` (with no leading `?`)
|
|
53
51
|
def to_query(options)
|
|
54
52
|
parts = []
|
|
55
53
|
each_pair(options) do |name, value|
|
|
56
|
-
#
|
|
54
|
+
# A valueless flag becomes just the key (the server treats no value as true).
|
|
57
55
|
parts << (value.nil? ? escape(name) : "#{escape(name)}=#{escape(value)}")
|
|
58
56
|
end
|
|
59
57
|
parts.join("&")
|
|
60
58
|
end
|
|
61
59
|
|
|
62
|
-
#
|
|
60
|
+
# Convert one key and value into an argv fragment.
|
|
63
61
|
#
|
|
64
62
|
# page_size: "A4" → ["--page-size", "A4"]
|
|
65
63
|
# grayscale: true → ["--grayscale"]
|
|
@@ -69,8 +67,8 @@ module Sghtmltopdf
|
|
|
69
67
|
pairs_for(key, value).flat_map { |name, arg| arg.nil? ? ["--#{name}"] : ["--#{name}", arg] }
|
|
70
68
|
end
|
|
71
69
|
|
|
72
|
-
#
|
|
73
|
-
#
|
|
70
|
+
# Turn one key and value into a list of "flag name and value" pairs. A pair whose value is
|
|
71
|
+
# `nil` is a flag taking no value (`--toc` and the like).
|
|
74
72
|
def pairs_for(key, value)
|
|
75
73
|
name = flag_name(key)
|
|
76
74
|
return font_pairs(value) if name == "font"
|
|
@@ -78,26 +76,25 @@ module Sghtmltopdf
|
|
|
78
76
|
case value
|
|
79
77
|
when nil, false then []
|
|
80
78
|
when true then [[name, nil]]
|
|
81
|
-
#
|
|
79
|
+
# An array means the same option repeated. The same rule applies to each element.
|
|
82
80
|
when Array then value.flat_map { |element| pairs_for(key, element) }
|
|
83
81
|
when Hash
|
|
84
|
-
# wicked_pdf
|
|
85
|
-
# CLI
|
|
86
|
-
#
|
|
87
|
-
#
|
|
82
|
+
# Nesting such as wicked_pdf's `margin: {top: 10}` is not accepted. There is no
|
|
83
|
+
# corresponding CLI flag, and flattening it mechanically would let even misspelled
|
|
84
|
+
# keys through silently. When migrating, use the correspondence table in the
|
|
85
|
+
# migration guide to rewrite them. A unitless number is read as mm, as in wicked_pdf (`cli/units.rs`).
|
|
88
86
|
example = value.keys.first
|
|
89
87
|
raise ArgumentError,
|
|
90
|
-
"#{key}
|
|
91
|
-
"
|
|
92
|
-
"#{":
|
|
88
|
+
"a Hash cannot be passed for #{key} (only :font takes a path and an index). " \
|
|
89
|
+
"Give nested options as flat keys" \
|
|
90
|
+
"#{": for example #{key}_#{example}: \"...\"" if example}"
|
|
93
91
|
else [[name, value.to_s]]
|
|
94
92
|
end
|
|
95
93
|
end
|
|
96
94
|
|
|
97
|
-
# `--font
|
|
98
|
-
# `ArgMatches#indices_of
|
|
99
|
-
#
|
|
100
|
-
# 必ず対応する`--font`の直後へ置く。
|
|
95
|
+
# `--font` and `--font-index` are paired by their order of appearance (the CLI ties each
|
|
96
|
+
# `--font-index` to "the last `--font` before it" via `ArgMatches#indices_of`).
|
|
97
|
+
# So a face index always goes immediately after its own `--font`.
|
|
101
98
|
#
|
|
102
99
|
# font: "a.ttf" → ["--font", "a.ttf"]
|
|
103
100
|
# font: {path: "a.ttc", index: 1} → ["--font", "a.ttc", "--font-index", "1"]
|
|
@@ -113,7 +110,7 @@ module Sghtmltopdf
|
|
|
113
110
|
when Array then value.flat_map { |element| font_pairs(element) }
|
|
114
111
|
when Hash
|
|
115
112
|
path = value[:path] || value["path"]
|
|
116
|
-
raise ArgumentError, "font
|
|
113
|
+
raise ArgumentError, "a font Hash needs a path: #{value.inspect}" if path.nil?
|
|
117
114
|
|
|
118
115
|
index = value[:index] || value["index"]
|
|
119
116
|
pairs = [["font", path.to_s]]
|
|
@@ -123,12 +120,12 @@ module Sghtmltopdf
|
|
|
123
120
|
end
|
|
124
121
|
end
|
|
125
122
|
|
|
126
|
-
# `:page_size`
|
|
123
|
+
# `:page_size` becomes `page-size`.
|
|
127
124
|
def flag_name(key)
|
|
128
125
|
key.to_s.tr("_", "-")
|
|
129
126
|
end
|
|
130
127
|
|
|
131
|
-
#
|
|
128
|
+
# Enumerate only the conversion options, as pairs, in the order they were given.
|
|
132
129
|
def each_pair(options, &block)
|
|
133
130
|
options.each do |key, value|
|
|
134
131
|
key = canonical_key(key)
|
data/lib/sghtmltopdf/railtie.rb
CHANGED
|
@@ -28,12 +28,12 @@ module Sghtmltopdf
|
|
|
28
28
|
defaults
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
#
|
|
32
|
-
# `config.assets.paths
|
|
33
|
-
# `propshaft.append_assets_path`)
|
|
31
|
+
# Read in `after_initialize`, not inside the initializer. The pipeline fills
|
|
32
|
+
# `config.assets.paths` in its own initializer (for Propshaft,
|
|
33
|
+
# `propshaft.append_assets_path`), and that one runs later.
|
|
34
34
|
#
|
|
35
|
-
# `config/initializers
|
|
36
|
-
#
|
|
35
|
+
# This runs after `config/initializers`, but `apply_defaults` is always
|
|
36
|
+
# weaker than explicitly set values, so it never overrides user settings.
|
|
37
37
|
initializer "sghtmltopdf.defaults" do |app|
|
|
38
38
|
app.config.after_initialize do
|
|
39
39
|
Sghtmltopdf.config.apply_defaults(Sghtmltopdf::Railtie.default_options(app))
|
data/lib/sghtmltopdf/renderer.rb
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Sghtmltopdf
|
|
4
|
-
# `render pdf: "invoice"
|
|
4
|
+
# Sorts the options of `render pdf: "invoice"` into three groups:
|
|
5
5
|
#
|
|
6
|
-
# * Rails
|
|
7
|
-
# *
|
|
8
|
-
# * PDF
|
|
6
|
+
# * those passed to Rails view rendering (`render_to_string`)
|
|
7
|
+
# * those passed to building the response (`send_data`)
|
|
8
|
+
# * those passed to PDF conversion (`Sghtmltopdf.render`)
|
|
9
9
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
10
|
+
#
|
|
11
|
+
# It is a pure Ruby class with no dependency on Rails, so it can be unit tested without Rails.
|
|
12
12
|
class Renderer
|
|
13
|
-
# `render_to_string
|
|
13
|
+
# The keys passed straight to `render_to_string`.
|
|
14
14
|
RAILS_RENDER_KEYS = %i[
|
|
15
15
|
action assigns body collection file formats handlers html inline layout
|
|
16
16
|
locals object partial plain prefixes template variants
|
|
17
17
|
].freeze
|
|
18
18
|
|
|
19
|
-
#
|
|
19
|
+
# The keys used to build the response (passed to `send_data`).
|
|
20
20
|
RESPONSE_KEYS = %i[disposition filename status].freeze
|
|
21
21
|
|
|
22
|
-
#
|
|
22
|
+
# The keys the renderer itself interprets (for debugging: return the HTML rather than a PDF).
|
|
23
23
|
RENDERER_KEYS = %i[show_as_html].freeze
|
|
24
24
|
|
|
25
25
|
PDF_CONTENT_TYPE = "application/pdf"
|
|
@@ -27,17 +27,17 @@ module Sghtmltopdf
|
|
|
27
27
|
|
|
28
28
|
attr_reader :name, :options
|
|
29
29
|
|
|
30
|
-
# @param name [String, Symbol, nil] `pdf
|
|
31
|
-
# @param options [Hash] `render
|
|
32
|
-
# @param default_name [String, nil] `name
|
|
33
|
-
# (
|
|
30
|
+
# @param name [String, Symbol, nil] the value given to `pdf:` (the basis of the file name)
|
|
31
|
+
# @param options [Hash] the other options given to `render`
|
|
32
|
+
# @param default_name [String, nil] the file name when `name` is empty
|
|
33
|
+
# (the controller's `action_name` is expected)
|
|
34
34
|
def initialize(name, options = {}, default_name: nil)
|
|
35
35
|
@name = blank?(name) ? (default_name || "document").to_s : name.to_s
|
|
36
36
|
@options = options.to_h { |key, value| [key.to_sym, value] }
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
# `ActionController::Renderers.add(:pdf)
|
|
40
|
-
# Railtie
|
|
39
|
+
# Register the renderer with `ActionController::Renderers.add(:pdf)`.
|
|
40
|
+
# Called from the Railtie's Action Controller load hook (`on_load`).
|
|
41
41
|
def self.register!
|
|
42
42
|
::ActionController::Renderers.add(:pdf) do |name, options|
|
|
43
43
|
renderer = ::Sghtmltopdf::Renderer.new(name, options, default_name: action_name)
|
|
@@ -46,18 +46,18 @@ module Sghtmltopdf
|
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
#
|
|
49
|
+
# The options used to render the view.
|
|
50
50
|
def render_options
|
|
51
51
|
options.select { |key, _| RAILS_RENDER_KEYS.include?(key) }
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
# PDF
|
|
54
|
+
# The options used for the PDF conversion.
|
|
55
55
|
def convert_options
|
|
56
56
|
known = RAILS_RENDER_KEYS + RESPONSE_KEYS + RENDERER_KEYS
|
|
57
57
|
options.reject { |key, _| known.include?(key) }
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
#
|
|
60
|
+
# Turn the rendered HTML into the response body.
|
|
61
61
|
def body_for(html)
|
|
62
62
|
show_as_html? ? html : Sghtmltopdf.render(html, **convert_options)
|
|
63
63
|
end
|
|
@@ -73,19 +73,19 @@ module Sghtmltopdf
|
|
|
73
73
|
show_as_html? ? HTML_CONTENT_TYPE : PDF_CONTENT_TYPE
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
-
# `filename: "x.pdf"`
|
|
76
|
+
# `filename: "x.pdf"` wins over `pdf: "x"`. The extension is not doubled up.
|
|
77
77
|
def filename
|
|
78
78
|
base = blank?(options[:filename]) ? name : options[:filename].to_s
|
|
79
79
|
base.downcase.end_with?(".pdf") ? base : "#{base}.pdf"
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
-
#
|
|
82
|
+
# The default is `inline` (opened in the browser), as in wicked_pdf.
|
|
83
83
|
def disposition
|
|
84
84
|
blank?(options[:disposition]) ? "inline" : options[:disposition].to_s
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
-
# wicked_pdf
|
|
88
|
-
#
|
|
87
|
+
# The equivalent of wicked_pdf's `show_as_html`. It returns the HTML rather than a PDF, so
|
|
88
|
+
# the layout can be inspected in the browser's developer tools.
|
|
89
89
|
def show_as_html?
|
|
90
90
|
value = options[:show_as_html]
|
|
91
91
|
!(value.nil? || value == false || value == "false")
|
|
@@ -6,7 +6,7 @@ require "uri"
|
|
|
6
6
|
module Sghtmltopdf
|
|
7
7
|
class ServerError < Error; end
|
|
8
8
|
|
|
9
|
-
# HTTP
|
|
9
|
+
# The client delegating conversion to HTTP server mode (`sghtmltopdf server`).
|
|
10
10
|
#
|
|
11
11
|
# Sghtmltopdf.configure { |c| c.server_url = "http://pdf.internal:8080" }
|
|
12
12
|
# pdf = Sghtmltopdf.render(html, page_size: "A4")
|
|
@@ -14,29 +14,28 @@ module Sghtmltopdf
|
|
|
14
14
|
DEFAULT_OPEN_TIMEOUT = 5
|
|
15
15
|
DEFAULT_READ_TIMEOUT = 120
|
|
16
16
|
|
|
17
|
-
#
|
|
17
|
+
# The rough size of each read. With `?stream=1` this is the unit handed to the block.
|
|
18
18
|
CHUNK_SIZE = 64 * 1024
|
|
19
19
|
|
|
20
20
|
attr_reader :uri, :open_timeout, :read_timeout
|
|
21
21
|
|
|
22
|
-
# @param url [String]
|
|
22
|
+
# @param url [String] the server's base URL (`http://host:port`)
|
|
23
23
|
def initialize(url, open_timeout: nil, read_timeout: nil)
|
|
24
24
|
@uri = parse(url)
|
|
25
25
|
@open_timeout = (open_timeout || DEFAULT_OPEN_TIMEOUT).to_f
|
|
26
26
|
@read_timeout = (read_timeout || DEFAULT_READ_TIMEOUT).to_f
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
# HTML
|
|
29
|
+
# Convert HTML to PDF.
|
|
30
30
|
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# PDF全体をStringで返す。
|
|
31
|
+
# Given a block it uses `?stream=1` (chunked transfer encoding) and hands over chunks as
|
|
32
|
+
# the server settles each page. With no block it returns the whole PDF as a String.
|
|
34
33
|
def render(html, options, &block)
|
|
35
34
|
request = build_request(html, options, stream: !block.nil?)
|
|
36
35
|
pdf = nil
|
|
37
36
|
start do |http|
|
|
38
|
-
# `request
|
|
39
|
-
#
|
|
37
|
+
# With a block, `request` returns the response object, so the result is received in an
|
|
38
|
+
# outer variable.
|
|
40
39
|
http.request(request) do |response|
|
|
41
40
|
ensure_success!(response)
|
|
42
41
|
if block
|
|
@@ -49,9 +48,9 @@ module Sghtmltopdf
|
|
|
49
48
|
pdf
|
|
50
49
|
end
|
|
51
50
|
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
51
|
+
# Write the conversion result to `path`. To leave no broken PDF on a failure part-way
|
|
52
|
+
# through, it writes to a temporary file and renames (matching the behaviour of the
|
|
53
|
+
# native extension's `FileSink`).
|
|
55
54
|
def render_to_file(html, options, path)
|
|
56
55
|
tmp = "#{path}.#{Process.pid}.tmp"
|
|
57
56
|
begin
|
|
@@ -60,7 +59,7 @@ module Sghtmltopdf
|
|
|
60
59
|
end
|
|
61
60
|
rescue SystemCallError => e
|
|
62
61
|
File.unlink(tmp) if File.exist?(tmp)
|
|
63
|
-
raise InputError, "#{path}
|
|
62
|
+
raise InputError, "failed to write to #{path}: #{e.message}"
|
|
64
63
|
rescue StandardError
|
|
65
64
|
File.unlink(tmp) if File.exist?(tmp)
|
|
66
65
|
raise
|
|
@@ -74,7 +73,7 @@ module Sghtmltopdf
|
|
|
74
73
|
def parse(url)
|
|
75
74
|
uri = URI.parse(url.to_s)
|
|
76
75
|
unless uri.is_a?(URI::HTTP) && uri.host
|
|
77
|
-
raise ArgumentError, "server_url
|
|
76
|
+
raise ArgumentError, "server_url must be an http(s) URL: #{url.inspect}"
|
|
78
77
|
end
|
|
79
78
|
|
|
80
79
|
uri
|
|
@@ -102,12 +101,12 @@ module Sghtmltopdf
|
|
|
102
101
|
&block
|
|
103
102
|
)
|
|
104
103
|
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
105
|
-
raise ServerError, "#{base}
|
|
104
|
+
raise ServerError, "the connection to #{base} timed out: #{e.class}"
|
|
106
105
|
rescue SocketError, SystemCallError, IOError, OpenSSL::SSL::SSLError => e
|
|
107
|
-
raise ServerError, "#{base}
|
|
106
|
+
raise ServerError, "the connection to #{base} failed: #{e.message}"
|
|
108
107
|
end
|
|
109
108
|
|
|
110
|
-
#
|
|
109
|
+
# An error response's body is a `text/plain` message (the same wording as the CLI).
|
|
111
110
|
def ensure_success!(response)
|
|
112
111
|
return if response.is_a?(Net::HTTPOK)
|
|
113
112
|
|
|
@@ -120,8 +119,8 @@ module Sghtmltopdf
|
|
|
120
119
|
when 400 then UsageError
|
|
121
120
|
when 413 then InputError
|
|
122
121
|
when 500 then RenderError
|
|
123
|
-
# 404
|
|
124
|
-
#
|
|
122
|
+
# A 404 or 405 means a wrong path or method, most likely because the other end is not
|
|
123
|
+
# an sghtmltopdf server. A 503 or 504 means the queue overflowed or the queue wait timed out.
|
|
125
124
|
else ServerError
|
|
126
125
|
end
|
|
127
126
|
end
|
data/lib/sghtmltopdf/version.rb
CHANGED
data/lib/sghtmltopdf.rb
CHANGED
|
@@ -5,9 +5,9 @@ require_relative "sghtmltopdf/options"
|
|
|
5
5
|
require_relative "sghtmltopdf/configuration"
|
|
6
6
|
require_relative "sghtmltopdf/renderer"
|
|
7
7
|
|
|
8
|
-
# precompiled gem
|
|
9
|
-
# (rake-compiler
|
|
10
|
-
# `lib/sghtmltopdf/sghtmltopdf.so
|
|
8
|
+
# A precompiled gem puts the `.so` in a directory per Ruby minor version
|
|
9
|
+
# (rake-compiler's cross-build convention). During development `rake compile` puts it in
|
|
10
|
+
# `lib/sghtmltopdf/sghtmltopdf.so`, so both are tried.
|
|
11
11
|
begin
|
|
12
12
|
RUBY_VERSION =~ /(\d+\.\d+)/
|
|
13
13
|
require "sghtmltopdf/#{Regexp.last_match(1)}/sghtmltopdf"
|
|
@@ -15,37 +15,36 @@ rescue LoadError
|
|
|
15
15
|
require "sghtmltopdf/sghtmltopdf"
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
# `Error`(
|
|
18
|
+
# It inherits `Error` (defined by the native extension), so it comes after the extension is loaded.
|
|
19
19
|
require_relative "sghtmltopdf/server_client"
|
|
20
20
|
|
|
21
21
|
module Sghtmltopdf
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
22
|
+
# The rough number of bytes handed over per call to a block-taking `render` (local conversion only).
|
|
23
|
+
# Calling the block on every settled page would mean reacquiring the GVL too often, so this
|
|
24
|
+
# much is accumulated first.
|
|
25
25
|
DEFAULT_CHUNK_SIZE = 64 * 1024
|
|
26
26
|
|
|
27
27
|
class << self
|
|
28
|
-
# HTML
|
|
28
|
+
# Convert HTML and return the PDF bytes (an ASCII-8BIT String).
|
|
29
29
|
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# を意識しない設計に対応する)。
|
|
30
|
+
# Given a block, it calls the block per chunk rather than assembling the whole PDF first
|
|
31
|
+
# (returning nil). It is the hook for streaming into Rack's `response.stream` or into an
|
|
32
|
+
# S3 multipart upload (matching the engine's design, which is unaware of the output sink).
|
|
34
33
|
#
|
|
35
34
|
# Sghtmltopdf.render(html) { |bytes| response.stream.write(bytes) }
|
|
36
35
|
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# chunked transfer encoding
|
|
36
|
+
# Both locally and when delegating to a server, it can be written out without waiting for
|
|
37
|
+
# the whole PDF to be assembled (locally page by page as they settle; from a server, the
|
|
38
|
+
# `?stream=1` chunked transfer encoding passed straight through).
|
|
40
39
|
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# (
|
|
40
|
+
# Only the PDF writing is incremental, though: HTML parsing and layout still happen for
|
|
41
|
+
# the whole document first. The first chunk arrives late in the conversion, and the peak
|
|
42
|
+
# memory is no different from the block-less case. To settle pages while the HTML is being
|
|
43
|
+
# read, use it together with `streaming: true`
|
|
44
|
+
# (which trades constraints for a large reduction in memory).
|
|
46
45
|
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
46
|
+
# The rough bytes per call can be changed with `chunk_size:` (64KiB by default; local
|
|
47
|
+
# conversion only; a smaller value means reacquiring the GVL more often).
|
|
49
48
|
def render(html, **options, &block)
|
|
50
49
|
options = Options.canonicalize(options)
|
|
51
50
|
client = server_client(options)
|
|
@@ -56,10 +55,10 @@ module Sghtmltopdf
|
|
|
56
55
|
nil
|
|
57
56
|
end
|
|
58
57
|
|
|
59
|
-
# HTML
|
|
58
|
+
# Convert HTML and write it to `path`.
|
|
60
59
|
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
60
|
+
# It writes to a temporary file and renames only on success, so a failure part-way through
|
|
61
|
+
# leaves no broken PDF at the destination (the same when delegating to a server).
|
|
63
62
|
def render_to_file(html, path, **options)
|
|
64
63
|
options = Options.canonicalize(options)
|
|
65
64
|
client = server_client(options)
|
|
@@ -69,7 +68,7 @@ module Sghtmltopdf
|
|
|
69
68
|
nil
|
|
70
69
|
end
|
|
71
70
|
|
|
72
|
-
#
|
|
71
|
+
# The global default options.
|
|
73
72
|
def configure
|
|
74
73
|
yield config
|
|
75
74
|
config
|
|
@@ -79,19 +78,19 @@ module Sghtmltopdf
|
|
|
79
78
|
@config ||= Configuration.new
|
|
80
79
|
end
|
|
81
80
|
|
|
82
|
-
#
|
|
81
|
+
# Mainly for tests. Resets the configuration to empty.
|
|
83
82
|
def reset_config!
|
|
84
83
|
@config = Configuration.new
|
|
85
84
|
end
|
|
86
85
|
|
|
87
86
|
private
|
|
88
87
|
|
|
89
|
-
#
|
|
88
|
+
# Merge in the order global settings, then call-time options, and turn that into argv.
|
|
90
89
|
def argv_for(options)
|
|
91
90
|
Options.to_argv(config.to_h.merge(options))
|
|
92
91
|
end
|
|
93
92
|
|
|
94
|
-
# `server_url
|
|
93
|
+
# With a `server_url` it delegates to the server. The timeout merges in the same order.
|
|
95
94
|
def server_client(options)
|
|
96
95
|
merged = config.to_h.merge(options)
|
|
97
96
|
url = merged[:server_url]
|
|
@@ -104,16 +103,16 @@ module Sghtmltopdf
|
|
|
104
103
|
)
|
|
105
104
|
end
|
|
106
105
|
|
|
107
|
-
#
|
|
106
|
+
# The rough bytes handed to the block per call (local conversion only).
|
|
108
107
|
def chunk_size(options)
|
|
109
108
|
value = config.to_h.merge(options)[:chunk_size]
|
|
110
109
|
value.nil? ? DEFAULT_CHUNK_SIZE : Integer(value)
|
|
111
110
|
end
|
|
112
111
|
|
|
113
|
-
#
|
|
114
|
-
# (Rails
|
|
115
|
-
#
|
|
116
|
-
#
|
|
112
|
+
# The options passed to the server. The injected defaults are removed
|
|
113
|
+
# (the Rails-oriented `base_url` and `allow` are defaults for local file resolution, and
|
|
114
|
+
# server mode cannot take them from a request, giving a 400.
|
|
115
|
+
# An explicitly set value is sent as-is and the server decides whether to accept it).
|
|
117
116
|
def server_options(options)
|
|
118
117
|
config.to_h(with_defaults: false).merge(options)
|
|
119
118
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sghtmltopdf
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.1
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- yo_waka
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: |
|
|
14
14
|
Ruby binding for sghtmltopdf, a successor to wkhtmltopdf: an HTML-to-PDF
|