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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ccb7358464b7e2b54de35755d1aaf2a278b59dddee9716101cdf3715eebf51cc
4
- data.tar.gz: 4c653cda8ba36f061a2d2253f59b4877ee39e2f9f7157cc84e05ab4ec978ad1f
3
+ metadata.gz: d1f9d3e5d0ef7f52bf2b132763152947790daa86174b4062a7d9a6b84d1997f3
4
+ data.tar.gz: 7dce2ae788b4118bb6567288b2087a4391deda2a1afffae79c287c1743ce818b
5
5
  SHA512:
6
- metadata.gz: 1f861dc645c65f74dee61b47a000395e10da29746bb27941079bdb0e34a69828fd424b2033f67692679f3500f6386ee0c2c5ada72bb048fdae593a167f35051a
7
- data.tar.gz: 616b41944d6a37a7110cf807dab0d2feaf3aeda1861d5c254276d6c59ace03e530c29c03a07b10cff4131ed6f04e4dcd2f284abb6cfcf1c6ff48d87a8f3c6170
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.7.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.7.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.
@@ -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.7.0"
5
+ version = "0.8.0"
6
6
  edition = "2021"
7
7
  publish = false
8
8
  description = "Ruby binding for sghtmltopdf (see bindings/ruby)"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sghtmltopdf
4
- VERSION = "0.7.0"
4
+ VERSION = "0.8.0"
5
5
  end
@@ -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 = source.to_s
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
- path = sghtmltopdf_asset_path(with_extension(source, ".css"))
52
- inline_stylesheet(path) if path
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
- # `path` read as CSS, with every `url()` pointed at a file the engine can
97
- # read and every `@import` spliced in.
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(path, chain = [])
114
- css = File.read(path)
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 file a CSS reference names, or `nil` when it names none.
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
- return found.filename.to_s if found.respond_to?(:filename)
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sghtmltopdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - yo_waka