sghtmltopdf 0.7.0 → 0.8.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 +4 -4
- data/Cargo.lock +2 -2
- data/README.md +1 -0
- data/ext/sghtmltopdf/Cargo.toml +1 -1
- data/lib/sghtmltopdf/version.rb +1 -1
- data/lib/sghtmltopdf/view_helpers.rb +49 -23
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d1f9d3e5d0ef7f52bf2b132763152947790daa86174b4062a7d9a6b84d1997f3
|
|
4
|
+
data.tar.gz: 7dce2ae788b4118bb6567288b2087a4391deda2a1afffae79c287c1743ce818b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d87d0e85c84e21a7f0f0df3a0564f56db2019ea32fcd164ade94066f2484e12f29e6d4a827d694cf490c1e56bb54df564da024da743b2e915eca7735d3b2c1d7
|
|
7
|
+
data.tar.gz: a22c3f77c40762e0da9170c174b2c233dcf5a1041fc952ca3476b5c3941657c89674604ffcd28ca4a512b6ef8f9a2377fe83909e0e759c4535084a2820bf01b1
|
data/Cargo.lock
CHANGED
|
@@ -1050,7 +1050,7 @@ dependencies = [
|
|
|
1050
1050
|
|
|
1051
1051
|
[[package]]
|
|
1052
1052
|
name = "sghtmltopdf"
|
|
1053
|
-
version = "0.
|
|
1053
|
+
version = "0.8.0"
|
|
1054
1054
|
dependencies = [
|
|
1055
1055
|
"base64",
|
|
1056
1056
|
"clap",
|
|
@@ -1077,7 +1077,7 @@ dependencies = [
|
|
|
1077
1077
|
|
|
1078
1078
|
[[package]]
|
|
1079
1079
|
name = "sghtmltopdf-ruby"
|
|
1080
|
-
version = "0.
|
|
1080
|
+
version = "0.8.0"
|
|
1081
1081
|
dependencies = [
|
|
1082
1082
|
"magnus",
|
|
1083
1083
|
"rb-sys",
|
data/README.md
CHANGED
|
@@ -113,6 +113,7 @@ That is enough for a precompiled production app; in development the digested `/a
|
|
|
113
113
|
```
|
|
114
114
|
|
|
115
115
|
`sghtmltopdf_stylesheet_link_tag` does not copy the CSS verbatim: it points every `url()` at a file the engine can read and splices in every `@import`.
|
|
116
|
+
With Sprockets, it uses the compiled CSS, so SCSS imports, variables and nesting are resolved by the application's Sass processor before embedding.
|
|
116
117
|
The asset pipeline rewrites `url()` through `asset_path` while precompiling, which turns a `@font-face` source into a digested `/assets/…` path, or into an absolute URL once `asset_host` is set — neither can be fetched while rendering, and a `@font-face` that fails to load falls back to the engine default rather than to the next `font-family`.
|
|
117
118
|
|
|
118
119
|
A file `allow_path` does not cover is embedded as a `data:` URI instead, so that it cannot silently vanish from the PDF; pass `inline: true` to embed unconditionally.
|
data/ext/sghtmltopdf/Cargo.toml
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# The package name differs from the library name on purpose: the core crate is published as
|
|
3
3
|
# `sghtmltopdf`, and two packages with the same name cannot be written to one lockfile.
|
|
4
4
|
name = "sghtmltopdf-ruby"
|
|
5
|
-
version = "0.
|
|
5
|
+
version = "0.8.0"
|
|
6
6
|
edition = "2021"
|
|
7
7
|
publish = false
|
|
8
8
|
description = "Ruby binding for sghtmltopdf (see bindings/ruby)"
|
data/lib/sghtmltopdf/version.rb
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Sghtmltopdf
|
|
4
|
+
# Sprockets' filename still points at the source (e.g. invoice.scss),
|
|
5
|
+
# while its to_s holds the compiled CSS. Keep both so references can be
|
|
6
|
+
# resolved without reading the uncompiled source again.
|
|
7
|
+
# Keep this outside ViewHelpers to avoid shadowing application constants in views.
|
|
8
|
+
ResolvedAsset = Struct.new(:path, :compiled_asset) do
|
|
9
|
+
def read
|
|
10
|
+
compiled_asset ? compiled_asset.to_s : File.read(path)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
private_constant :ResolvedAsset
|
|
14
|
+
|
|
4
15
|
# Action View helpers.
|
|
5
16
|
#
|
|
6
17
|
# Rendering a PDF never goes through an HTTP server, so a URL such as
|
|
@@ -30,14 +41,7 @@ module Sghtmltopdf
|
|
|
30
41
|
# in that order. The pipeline lookup goes through `respond_to?` so that
|
|
31
42
|
# neither gem becomes a dependency (best effort).
|
|
32
43
|
def sghtmltopdf_asset_path(source)
|
|
33
|
-
path
|
|
34
|
-
# A source that is already a URL (or a `data:` URI) is not an asset of
|
|
35
|
-
# this application. Without this, `from_public_dir` would strip the host
|
|
36
|
-
# off `https://example.com/logo.png` and hand back `public/logo.png`.
|
|
37
|
-
return nil if path.match?(%r{\A(?:[a-z][a-z0-9+.\-]*:|//)}i)
|
|
38
|
-
return path if path.start_with?("/") && File.file?(path)
|
|
39
|
-
|
|
40
|
-
from_public_dir(path) || from_asset_pipeline(path)
|
|
44
|
+
resolve_sghtmltopdf_asset(source)&.path
|
|
41
45
|
end
|
|
42
46
|
|
|
43
47
|
# Expands the CSS itself into a `<style>`. Takes several sources; any that
|
|
@@ -48,8 +52,8 @@ module Sghtmltopdf
|
|
|
48
52
|
# [#inline_stylesheet].
|
|
49
53
|
def sghtmltopdf_stylesheet_link_tag(*sources)
|
|
50
54
|
css = sources.flatten.filter_map do |source|
|
|
51
|
-
|
|
52
|
-
inline_stylesheet(
|
|
55
|
+
asset = resolve_sghtmltopdf_asset(with_extension(source, ".css"))
|
|
56
|
+
inline_stylesheet(asset) if asset
|
|
53
57
|
end
|
|
54
58
|
return "".html_safe if css.empty?
|
|
55
59
|
|
|
@@ -93,8 +97,19 @@ module Sghtmltopdf
|
|
|
93
97
|
|
|
94
98
|
private
|
|
95
99
|
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
def resolve_sghtmltopdf_asset(source)
|
|
101
|
+
path = source.to_s
|
|
102
|
+
# A source that is already a URL (or a `data:` URI) is not an asset of
|
|
103
|
+
# this application. Without this, `from_public_dir` would strip the host
|
|
104
|
+
# off `https://example.com/logo.png` and hand back `public/logo.png`.
|
|
105
|
+
return nil if path.match?(%r{\A(?:[a-z][a-z0-9+.\-]*:|//)}i)
|
|
106
|
+
return ResolvedAsset.new(path) if path.start_with?("/") && File.file?(path)
|
|
107
|
+
|
|
108
|
+
from_public_dir(path) || from_asset_pipeline(path)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# The asset's CSS, with every `url()` pointed at a file the engine can read
|
|
112
|
+
# and every remaining CSS `@import` spliced in.
|
|
98
113
|
#
|
|
99
114
|
# Both have to happen here because the engine keeps one base for the whole
|
|
100
115
|
# document: every CSS source is concatenated before it is parsed, so a
|
|
@@ -110,8 +125,9 @@ module Sghtmltopdf
|
|
|
110
125
|
# fetched.
|
|
111
126
|
#
|
|
112
127
|
# `chain` holds the files already being expanded, innermost last.
|
|
113
|
-
def inline_stylesheet(
|
|
114
|
-
css =
|
|
128
|
+
def inline_stylesheet(asset, chain = [])
|
|
129
|
+
css = asset.read
|
|
130
|
+
path = asset.path
|
|
115
131
|
dir = File.dirname(path)
|
|
116
132
|
chain += [real_path(path)].compact
|
|
117
133
|
comments = comment_ranges(css)
|
|
@@ -141,7 +157,7 @@ module Sghtmltopdf
|
|
|
141
157
|
target = css_url_target(unquote(href), dir)
|
|
142
158
|
return nil if target.nil?
|
|
143
159
|
|
|
144
|
-
real = real_path(target)
|
|
160
|
+
real = real_path(target.path)
|
|
145
161
|
return nil if real.nil? || chain.include?(real)
|
|
146
162
|
|
|
147
163
|
inline_stylesheet(target, chain)
|
|
@@ -163,11 +179,11 @@ module Sghtmltopdf
|
|
|
163
179
|
css.gsub(CSS_URL) do
|
|
164
180
|
match = Regexp.last_match
|
|
165
181
|
target = css_url_target(unquote(match[:href]), dir)
|
|
166
|
-
target ? css_url(engine_readable_src(target) || data_uri(target)) : match[0]
|
|
182
|
+
target ? css_url(engine_readable_src(target.path) || data_uri(target.path)) : match[0]
|
|
167
183
|
end
|
|
168
184
|
end
|
|
169
185
|
|
|
170
|
-
# The local
|
|
186
|
+
# The local asset a CSS reference names, or `nil` when it names none.
|
|
171
187
|
def css_url_target(ref, dir)
|
|
172
188
|
path = ref.split(/[?#]/, 2).first.to_s
|
|
173
189
|
return nil if path.empty? || path.match?(/\Adata:/i)
|
|
@@ -194,7 +210,7 @@ module Sghtmltopdf
|
|
|
194
210
|
root = ::Rails.application.config.try(:relative_url_root).to_s
|
|
195
211
|
path = path.delete_prefix(root) unless root.empty?
|
|
196
212
|
candidate = File.join(::Rails.public_path.to_s, path)
|
|
197
|
-
return candidate if File.file?(candidate)
|
|
213
|
+
return ResolvedAsset.new(candidate) if File.file?(candidate)
|
|
198
214
|
|
|
199
215
|
# Nothing is precompiled in development, so the load path is asked as
|
|
200
216
|
# well. The mount point is not part of a logical path either.
|
|
@@ -212,7 +228,7 @@ module Sghtmltopdf
|
|
|
212
228
|
# same way but is looked up across every root.
|
|
213
229
|
def from_stylesheet_dir(path, dir)
|
|
214
230
|
candidate = File.expand_path(path, dir)
|
|
215
|
-
return candidate if File.file?(candidate)
|
|
231
|
+
return ResolvedAsset.new(candidate) if File.file?(candidate)
|
|
216
232
|
|
|
217
233
|
from_asset_pipeline(path.delete_prefix("./"))
|
|
218
234
|
end
|
|
@@ -236,8 +252,10 @@ module Sghtmltopdf
|
|
|
236
252
|
|
|
237
253
|
# `@import` up to its terminating `;`, in both the `url()` and the bare
|
|
238
254
|
# string form. Media conditions are matched so they are consumed, not kept.
|
|
255
|
+
# CSS compressors can omit whitespace before a quoted URL. Keep the token
|
|
256
|
+
# boundary so an unrelated at-rule such as `@importurl` is not expanded.
|
|
239
257
|
IMPORT_STATEMENT = /
|
|
240
|
-
@import \s
|
|
258
|
+
@import \b \s*
|
|
241
259
|
(?: url\( \s* (?<href> "[^"]*" | '[^']*' | [^)"'\s]* ) \s* \)
|
|
242
260
|
| (?<href> "[^"]*" | '[^']*' ) )
|
|
243
261
|
[^;]* ;
|
|
@@ -258,7 +276,7 @@ module Sghtmltopdf
|
|
|
258
276
|
return nil if relative.empty?
|
|
259
277
|
|
|
260
278
|
candidate = File.join(::Rails.public_path.to_s, relative)
|
|
261
|
-
File.file?(candidate) ? candidate : nil
|
|
279
|
+
File.file?(candidate) ? ResolvedAsset.new(candidate) : nil
|
|
262
280
|
end
|
|
263
281
|
|
|
264
282
|
# `asset_path` for `source`, falling back to `source` itself.
|
|
@@ -282,12 +300,20 @@ module Sghtmltopdf
|
|
|
282
300
|
# Propshaft
|
|
283
301
|
if assets.respond_to?(:load_path)
|
|
284
302
|
found = assets.load_path.find(source)
|
|
285
|
-
return found.path.to_s if found.respond_to?(:path)
|
|
303
|
+
return ResolvedAsset.new(found.path.to_s) if found.respond_to?(:path)
|
|
286
304
|
end
|
|
287
305
|
# Sprockets
|
|
288
306
|
if assets.respond_to?(:[])
|
|
289
307
|
found = assets[source]
|
|
290
|
-
|
|
308
|
+
# Compiled CSS can contain digested URLs even in development, where
|
|
309
|
+
# no such file exists under public/. Sprockets looks up logical names;
|
|
310
|
+
# verify the digest before using the corresponding source file.
|
|
311
|
+
if found.nil?
|
|
312
|
+
logical = source.sub(/-[0-9a-f]+(?=\.[^.]+\z)/i, "")
|
|
313
|
+
candidate = assets[logical] unless logical == source
|
|
314
|
+
found = candidate if candidate.respond_to?(:digest_path) && candidate.digest_path == source
|
|
315
|
+
end
|
|
316
|
+
return ResolvedAsset.new(found.filename.to_s, found) if found.respond_to?(:filename)
|
|
291
317
|
end
|
|
292
318
|
nil
|
|
293
319
|
end
|