page_print 0.1.1
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 +247 -0
- data/ext/page_print/extconf.rb +48 -0
- data/ext/page_print/page_print.c +851 -0
- data/lib/page_print/rails_resource_fetcher.rb +138 -0
- data/lib/page_print/railtie.rb +15 -0
- data/lib/page_print/version.rb +3 -0
- data/lib/page_print.rb +28 -0
- metadata +53 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: '01186af68ea2b96b7895e67551850dc75c0a9da9a627878cca7d0a79104ffb5d'
|
|
4
|
+
data.tar.gz: 53611de8ac2727292a019cbe694fc9a39467f8d664e07082c8c52eddacea6521
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f6fb50c8b137d8ef169c2f3d8889a0a08134af90a57f12f8eb41c1d193affcfa51a890af14db135542ee10ebabbeb0648229aa3003578e7fdff805fa4044549e
|
|
7
|
+
data.tar.gz: 1979ba3ddf1d1cc8cba01ef515c347fb04b06289cc3a36ddf65c60422e4741132f3d338db1efae88c5a21e67f96c67471172c475b0af1eb78634d79d79a142b2
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dino Maric
|
|
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,247 @@
|
|
|
1
|
+
# PagePrint
|
|
2
|
+
|
|
3
|
+
`page_print` is a Ruby gem with a native C extension that renders HTML strings to PDF files using the `plutobook` library.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Ruby 3.0+
|
|
8
|
+
- Native gems are published for `x86_64-linux` and `arm64-darwin`.
|
|
9
|
+
- Source builds on unsupported platforms require PlutoBook development headers and library files.
|
|
10
|
+
|
|
11
|
+
### Source Build Requirements
|
|
12
|
+
|
|
13
|
+
On macOS with Homebrew:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
brew install plutobook pkg-config
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
If `pkg-config` cannot find `plutobook`, install the gem with explicit include and library paths:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
gem install page_print -- --with-plutobook-include=/path/to/include --with-plutobook-lib=/path/to/lib
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Add PagePrint to your Gemfile:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
gem "page_print", "~> 0.1.1"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or install from RubyGems directly:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
gem install page_print
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The native gems bundle PlutoBook and required non-system shared libraries. Optional PlutoBook features for curl, TurboJPEG, and WebP are disabled in native gems to keep the bundled dependency set smaller.
|
|
40
|
+
|
|
41
|
+
## Supported Platforms
|
|
42
|
+
|
|
43
|
+
| Platform | Install Type |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `x86_64-linux` | Native gem |
|
|
46
|
+
| `arm64-darwin` | Native gem |
|
|
47
|
+
| Other platforms | Source build |
|
|
48
|
+
|
|
49
|
+
## Local Build
|
|
50
|
+
|
|
51
|
+
Build and install locally:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
gem build page_print.gemspec
|
|
55
|
+
gem install ./page_print-*.gem
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Development
|
|
59
|
+
|
|
60
|
+
1. Install development dependencies:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
bundle install
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
2. Compile the native extension into `lib/page_print`:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
bundle exec rake compile
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
3. Open an interactive Ruby session against the local checkout:
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
bundle exec irb -Ilib
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Then load the gem from the repo and try it:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
require "page_print"
|
|
82
|
+
require "tmpdir"
|
|
83
|
+
|
|
84
|
+
output_path = File.join(Dir.tmpdir, "page_print-output.pdf")
|
|
85
|
+
|
|
86
|
+
PagePrint.html_to_pdf("<html><body><h1>Hello</h1></body></html>", output_path, page_size: :letter, margins: :narrow, media: :screen)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
You can also do a quick one-shot smoke test from the shell:
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
bundle exec ruby -Ilib -e 'require "tmpdir"; require "page_print"; output_path = File.join(Dir.tmpdir, "page_print-output.pdf"); p PagePrint.html_to_pdf("<html><body><h1>Hello</h1></body></html>", output_path, page_size: :letter, margins: :narrow, media: :screen)'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Or use the development console, which compiles the native extension first and then starts IRB with `PagePrint` loaded:
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
bin/console
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Running Tests
|
|
102
|
+
|
|
103
|
+
Run the test suite only:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
bundle exec rake test
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Or run the default rake task, which compiles the extension and then runs tests:
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
bundle exec rake
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Building Native Gems
|
|
116
|
+
|
|
117
|
+
Build an `x86_64-linux` platform gem with a vendored PlutoBook library:
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
bundle exec rake package:linux
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Build an Apple Silicon macOS platform gem with a vendored PlutoBook library:
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
bundle exec rake package:darwin_arm64
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
These tasks check out PlutoBook `v0.17.0`, build it into `lib/page_print/vendor/<platform>`, compile the PagePrint native extension against that vendored build, and write a platform gem to `pkg/`.
|
|
130
|
+
|
|
131
|
+
Native gems bundle PlutoBook and its non-system shared library dependencies. Optional PlutoBook features for curl, TurboJPEG, and WebP are disabled to keep the bundled dependency set smaller.
|
|
132
|
+
|
|
133
|
+
The packaging tasks expect PlutoBook's build dependencies to be installed on the build machine, including Meson, Ninja, pkg-config, Cairo, FreeType, HarfBuzz, Fontconfig, Expat, and ICU.
|
|
134
|
+
|
|
135
|
+
## Rails Usage
|
|
136
|
+
|
|
137
|
+
PagePrint is currently optimized for Rails applications using Propshaft. In Rails, PagePrint installs a default Propshaft-backed resource fetcher and uses the current request URL as the default `base_url`.
|
|
138
|
+
|
|
139
|
+
Render a PDF from a controller:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
class PrintsController < ApplicationController
|
|
143
|
+
def pdf
|
|
144
|
+
html = render_to_string(template: "prints/pdf", formats: [:html], layout: "pdf")
|
|
145
|
+
pdf = PagePrint.html_to_pdf_string(
|
|
146
|
+
html,
|
|
147
|
+
page_size: :a4,
|
|
148
|
+
margins: :normal,
|
|
149
|
+
media: :print,
|
|
150
|
+
metadata: { title: "Print PDF", author: "PagePrint" }
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
send_data pdf, filename: "print.pdf", type: "application/pdf", disposition: "inline"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Use normal Rails asset helpers in the PDF template or layout:
|
|
159
|
+
|
|
160
|
+
```erb
|
|
161
|
+
<%= stylesheet_link_tag "pdf" %>
|
|
162
|
+
<%= image_tag "logo.png" %>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
During controller actions, PagePrint defaults `base_url` to `request.base_url`. The default Rails resource fetcher resolves `/assets/...` through Propshaft or `public/assets`, avoiding HTTP requests back to the Rails app.
|
|
166
|
+
|
|
167
|
+
You can still override `base_url` explicitly:
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
pdf = PagePrint.html_to_pdf_string(html, base_url: "https://example.com")
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Override the fetcher only when needed:
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
# config/initializers/page_print.rb
|
|
177
|
+
PagePrint.configure do |config|
|
|
178
|
+
config.resource_fetcher = MyResourceFetcher.new
|
|
179
|
+
end
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Ruby Usage
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
require "page_print"
|
|
186
|
+
require "tmpdir"
|
|
187
|
+
|
|
188
|
+
html = <<~HTML
|
|
189
|
+
<html>
|
|
190
|
+
<body>
|
|
191
|
+
<h1>Hello</h1>
|
|
192
|
+
<p>This PDF was generated by PagePrint.</p>
|
|
193
|
+
</body>
|
|
194
|
+
</html>
|
|
195
|
+
HTML
|
|
196
|
+
|
|
197
|
+
output_path = File.join(Dir.tmpdir, "page_print-output.pdf")
|
|
198
|
+
|
|
199
|
+
PagePrint.html_to_pdf(html, output_path, base_url: "https://example.com")
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
To get the generated PDF as a binary string instead of writing directly to a file:
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
pdf = PagePrint.html_to_pdf_string(html, base_url: "https://example.com")
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Use custom page dimensions and margins when a preset is not enough:
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
pdf = PagePrint.html_to_pdf_string(
|
|
212
|
+
html,
|
|
213
|
+
page_size: { width: 100, height: 150, unit: :mm },
|
|
214
|
+
margins: { top: 5, right: 6, bottom: 7, left: 8, unit: :mm }
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
You can configure default options once, for example to provide a resource fetcher used by all renders:
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
PagePrint.configure do |config|
|
|
222
|
+
config.resource_fetcher = lambda do |url|
|
|
223
|
+
next unless url == "asset:pdf.css"
|
|
224
|
+
|
|
225
|
+
{ content: "body { font-family: sans-serif; }", mime_type: "text/css" }
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Supported keyword options:
|
|
231
|
+
|
|
232
|
+
- `base_url:` string used to resolve relative URLs in the HTML
|
|
233
|
+
- `page_size:` one of `:a3`, `:a4`, `:a5`, `:b4`, `:b5`, `:letter`, `:legal`, `:ledger`, or `{ width:, height:, unit: }`
|
|
234
|
+
- `margins:` one of `:none`, `:normal`, `:narrow`, `:moderate`, `:wide`, or `{ top:, right:, bottom:, left:, unit: }`
|
|
235
|
+
- `media:` one of `:print`, `:screen`
|
|
236
|
+
- `resource_fetcher:` callable that receives a URL and returns `nil` or `{ content:, mime_type:, text_encoding: nil }`
|
|
237
|
+
- `metadata:` hash with `:title`, `:author`, `:subject`, `:keywords`, `:creation_date`, or `:modification_date`
|
|
238
|
+
|
|
239
|
+
`metadata[:creation_date]` and `metadata[:modification_date]` should be ISO-8601 strings, for example `2026-05-10T12:00:00Z`.
|
|
240
|
+
|
|
241
|
+
Custom dimensions and margins require `unit:`. Supported units are `:pt`, `:pc`, `:in`, `:cm`, `:mm`, and `:px`.
|
|
242
|
+
|
|
243
|
+
## Notes
|
|
244
|
+
|
|
245
|
+
- The extension supports writing to a file path with `html_to_pdf` or returning PDF bytes with `html_to_pdf_string`.
|
|
246
|
+
- JavaScript execution is intentionally unsupported.
|
|
247
|
+
- Native gems disable PlutoBook's optional curl, TurboJPEG, and WebP features.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'mkmf'
|
|
2
|
+
|
|
3
|
+
def page_print_native_platform
|
|
4
|
+
platform = Gem::Platform.local
|
|
5
|
+
|
|
6
|
+
if platform.os == 'linux' && platform.cpu == 'x86_64'
|
|
7
|
+
'x86_64-linux'
|
|
8
|
+
else
|
|
9
|
+
platform.to_s
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
project_root = File.expand_path('../..', __dir__)
|
|
14
|
+
vendor_platform = ENV.fetch('PAGE_PRINT_VENDOR_PLATFORM', page_print_native_platform)
|
|
15
|
+
vendor_dir = File.join(project_root, 'lib', 'page_print', 'vendor', vendor_platform)
|
|
16
|
+
vendor_include_dir = File.join(vendor_dir, 'include')
|
|
17
|
+
vendor_lib_dir = File.join(vendor_dir, 'lib')
|
|
18
|
+
|
|
19
|
+
if File.directory?(vendor_include_dir) && File.directory?(vendor_lib_dir)
|
|
20
|
+
dir_config('plutobook', vendor_include_dir, vendor_lib_dir)
|
|
21
|
+
|
|
22
|
+
if vendor_platform.end_with?('-linux')
|
|
23
|
+
$LDFLAGS = "-Wl,-rpath,\\$$ORIGIN/vendor/#{vendor_platform}/lib #{$LDFLAGS}"
|
|
24
|
+
elsif vendor_platform.end_with?('-darwin')
|
|
25
|
+
$LDFLAGS = "-Wl,-rpath,@loader_path/vendor/#{vendor_platform}/lib #{$LDFLAGS}"
|
|
26
|
+
end
|
|
27
|
+
else
|
|
28
|
+
pkg_config('plutobook')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
dir_config('plutobook')
|
|
32
|
+
|
|
33
|
+
['/opt/homebrew/opt/plutobook', '/usr/local/opt/plutobook'].each do |prefix|
|
|
34
|
+
next unless File.directory?(prefix)
|
|
35
|
+
|
|
36
|
+
dir_config('plutobook', File.join(prefix, 'include'), File.join(prefix, 'lib'))
|
|
37
|
+
break
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
unless have_header('plutobook/plutobook.h')
|
|
41
|
+
abort 'plutobook header not found. Install plutobook or pass --with-plutobook-include=/path/to/include'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
unless have_library('plutobook', 'plutobook_create')
|
|
45
|
+
abort 'plutobook library not found. Install plutobook or pass --with-plutobook-lib=/path/to/lib'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
create_makefile('page_print/page_print')
|
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
#include "ruby.h"
|
|
2
|
+
#include "ruby/encoding.h"
|
|
3
|
+
#include "ruby/thread.h"
|
|
4
|
+
#include <limits.h>
|
|
5
|
+
#include <stdint.h>
|
|
6
|
+
#include <plutobook/plutobook.h>
|
|
7
|
+
|
|
8
|
+
#if defined(__GNUC__) || defined(__clang__)
|
|
9
|
+
#define PAGEPRINT_NORETURN __attribute__((noreturn))
|
|
10
|
+
#else
|
|
11
|
+
#define PAGEPRINT_NORETURN
|
|
12
|
+
#endif
|
|
13
|
+
|
|
14
|
+
static VALUE mPagePrint;
|
|
15
|
+
static ID id_base_url;
|
|
16
|
+
static ID id_page_size;
|
|
17
|
+
static ID id_margins;
|
|
18
|
+
static ID id_media;
|
|
19
|
+
static ID id_resource_fetcher;
|
|
20
|
+
static ID id_metadata;
|
|
21
|
+
static ID id_width;
|
|
22
|
+
static ID id_height;
|
|
23
|
+
static ID id_unit;
|
|
24
|
+
static ID id_top;
|
|
25
|
+
static ID id_right;
|
|
26
|
+
static ID id_bottom;
|
|
27
|
+
static ID id_left;
|
|
28
|
+
static ID id_resource_fetcher_ivar;
|
|
29
|
+
static ID id_base_url_method;
|
|
30
|
+
static ID id_call;
|
|
31
|
+
static ID id_content;
|
|
32
|
+
static ID id_mime_type;
|
|
33
|
+
static ID id_text_encoding;
|
|
34
|
+
static ID id_title;
|
|
35
|
+
static ID id_author;
|
|
36
|
+
static ID id_subject;
|
|
37
|
+
static ID id_keywords;
|
|
38
|
+
static ID id_creation_date;
|
|
39
|
+
static ID id_modification_date;
|
|
40
|
+
static ID id_pt;
|
|
41
|
+
static ID id_pc;
|
|
42
|
+
static ID id_in;
|
|
43
|
+
static ID id_cm;
|
|
44
|
+
static ID id_mm;
|
|
45
|
+
static ID id_px;
|
|
46
|
+
|
|
47
|
+
typedef struct {
|
|
48
|
+
plutobook_page_size_t page_size;
|
|
49
|
+
plutobook_page_margins_t margins;
|
|
50
|
+
plutobook_media_type_t media;
|
|
51
|
+
const char *base_url;
|
|
52
|
+
VALUE resource_fetcher;
|
|
53
|
+
VALUE metadata;
|
|
54
|
+
} pageprint_options_t;
|
|
55
|
+
|
|
56
|
+
typedef struct {
|
|
57
|
+
VALUE object;
|
|
58
|
+
int state;
|
|
59
|
+
} pageprint_resource_fetcher_t;
|
|
60
|
+
|
|
61
|
+
typedef struct {
|
|
62
|
+
pageprint_resource_fetcher_t *fetcher;
|
|
63
|
+
const char *url;
|
|
64
|
+
plutobook_resource_data_t *resource;
|
|
65
|
+
} pageprint_resource_fetch_args_t;
|
|
66
|
+
|
|
67
|
+
typedef struct {
|
|
68
|
+
VALUE output;
|
|
69
|
+
int state;
|
|
70
|
+
} pageprint_pdf_string_output_t;
|
|
71
|
+
|
|
72
|
+
typedef struct {
|
|
73
|
+
VALUE output;
|
|
74
|
+
const char *data;
|
|
75
|
+
unsigned int length;
|
|
76
|
+
} pageprint_pdf_string_append_t;
|
|
77
|
+
|
|
78
|
+
typedef struct {
|
|
79
|
+
plutobook_t *book;
|
|
80
|
+
const char *html;
|
|
81
|
+
int length;
|
|
82
|
+
const char *user_style;
|
|
83
|
+
const char *user_script;
|
|
84
|
+
const char *base_url;
|
|
85
|
+
int ok;
|
|
86
|
+
} pageprint_load_html_args_t;
|
|
87
|
+
|
|
88
|
+
typedef struct {
|
|
89
|
+
plutobook_t *book;
|
|
90
|
+
const char *path;
|
|
91
|
+
int ok;
|
|
92
|
+
} pageprint_write_pdf_args_t;
|
|
93
|
+
|
|
94
|
+
typedef struct {
|
|
95
|
+
plutobook_t *book;
|
|
96
|
+
pageprint_pdf_string_output_t *output;
|
|
97
|
+
int ok;
|
|
98
|
+
} pageprint_write_pdf_stream_args_t;
|
|
99
|
+
|
|
100
|
+
static VALUE pageprint_append_pdf_string(VALUE value);
|
|
101
|
+
static plutobook_stream_status_t pageprint_write_pdf_string(void *closure, const char *data, unsigned int length);
|
|
102
|
+
static plutobook_resource_data_t *pageprint_fetch_resource(void *closure, const char *url);
|
|
103
|
+
|
|
104
|
+
static void PAGEPRINT_NORETURN pageprint_raise_plutobook_error(VALUE error_class, const char *message)
|
|
105
|
+
{
|
|
106
|
+
const char *error_message = plutobook_get_error_message();
|
|
107
|
+
|
|
108
|
+
if (error_message && error_message[0] != '\0') {
|
|
109
|
+
rb_raise(error_class, "%s: %s", message, error_message);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
rb_raise(error_class, "%s", message);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
static void PAGEPRINT_NORETURN pageprint_raise_plutobook_error_with_path(VALUE error_class, const char *message, const char *path)
|
|
116
|
+
{
|
|
117
|
+
const char *error_message = plutobook_get_error_message();
|
|
118
|
+
|
|
119
|
+
if (error_message && error_message[0] != '\0') {
|
|
120
|
+
rb_raise(error_class, "%s %s: %s", message, path, error_message);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
rb_raise(error_class, "%s %s", message, path);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
static double pageprint_unit_factor_from_value(VALUE value, const char *name)
|
|
127
|
+
{
|
|
128
|
+
ID value_id;
|
|
129
|
+
|
|
130
|
+
if (!RB_SYMBOL_P(value)) {
|
|
131
|
+
rb_raise(rb_eTypeError, "%s must be a Symbol", name);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
value_id = SYM2ID(value);
|
|
135
|
+
|
|
136
|
+
if (value_id == id_pt) return PLUTOBOOK_UNITS_PT;
|
|
137
|
+
if (value_id == id_pc) return PLUTOBOOK_UNITS_PC;
|
|
138
|
+
if (value_id == id_in) return PLUTOBOOK_UNITS_IN;
|
|
139
|
+
if (value_id == id_cm) return PLUTOBOOK_UNITS_CM;
|
|
140
|
+
if (value_id == id_mm) return PLUTOBOOK_UNITS_MM;
|
|
141
|
+
if (value_id == id_px) return PLUTOBOOK_UNITS_PX;
|
|
142
|
+
|
|
143
|
+
rb_raise(rb_eArgError, "%s must be one of: :pt, :pc, :in, :cm, :mm, :px", name);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static plutobook_page_size_t pageprint_page_size_from_value(VALUE value)
|
|
147
|
+
{
|
|
148
|
+
ID value_id;
|
|
149
|
+
|
|
150
|
+
if (RB_TYPE_P(value, T_HASH)) {
|
|
151
|
+
VALUE width = rb_hash_aref(value, ID2SYM(id_width));
|
|
152
|
+
VALUE height = rb_hash_aref(value, ID2SYM(id_height));
|
|
153
|
+
VALUE unit = rb_hash_aref(value, ID2SYM(id_unit));
|
|
154
|
+
double width_number;
|
|
155
|
+
double height_number;
|
|
156
|
+
double factor;
|
|
157
|
+
|
|
158
|
+
if (NIL_P(width)) {
|
|
159
|
+
rb_raise(rb_eArgError, "page_size requires :width");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (NIL_P(height)) {
|
|
163
|
+
rb_raise(rb_eArgError, "page_size requires :height");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (NIL_P(unit)) {
|
|
167
|
+
rb_raise(rb_eArgError, "page_size requires :unit");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
width_number = NUM2DBL(width);
|
|
171
|
+
height_number = NUM2DBL(height);
|
|
172
|
+
|
|
173
|
+
if (width_number <= 0) {
|
|
174
|
+
rb_raise(rb_eArgError, "page_size width must be greater than 0");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (height_number <= 0) {
|
|
178
|
+
rb_raise(rb_eArgError, "page_size height must be greater than 0");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
factor = pageprint_unit_factor_from_value(unit, "page_size unit");
|
|
182
|
+
|
|
183
|
+
return PLUTOBOOK_MAKE_PAGE_SIZE((float)(width_number * factor), (float)(height_number * factor));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!RB_SYMBOL_P(value)) {
|
|
187
|
+
rb_raise(rb_eTypeError, "page_size must be a Symbol or Hash");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
value_id = SYM2ID(value);
|
|
191
|
+
|
|
192
|
+
if (value_id == rb_intern("a3")) return PLUTOBOOK_PAGE_SIZE_A3;
|
|
193
|
+
if (value_id == rb_intern("a4")) return PLUTOBOOK_PAGE_SIZE_A4;
|
|
194
|
+
if (value_id == rb_intern("a5")) return PLUTOBOOK_PAGE_SIZE_A5;
|
|
195
|
+
if (value_id == rb_intern("b4")) return PLUTOBOOK_PAGE_SIZE_B4;
|
|
196
|
+
if (value_id == rb_intern("b5")) return PLUTOBOOK_PAGE_SIZE_B5;
|
|
197
|
+
if (value_id == rb_intern("letter")) return PLUTOBOOK_PAGE_SIZE_LETTER;
|
|
198
|
+
if (value_id == rb_intern("legal")) return PLUTOBOOK_PAGE_SIZE_LEGAL;
|
|
199
|
+
if (value_id == rb_intern("ledger")) return PLUTOBOOK_PAGE_SIZE_LEDGER;
|
|
200
|
+
|
|
201
|
+
rb_raise(rb_eArgError, "page_size must be one of: :a3, :a4, :a5, :b4, :b5, :letter, :legal, :ledger");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
static plutobook_page_margins_t pageprint_margins_from_value(VALUE value)
|
|
205
|
+
{
|
|
206
|
+
ID value_id;
|
|
207
|
+
|
|
208
|
+
if (RB_TYPE_P(value, T_HASH)) {
|
|
209
|
+
VALUE top = rb_hash_aref(value, ID2SYM(id_top));
|
|
210
|
+
VALUE right = rb_hash_aref(value, ID2SYM(id_right));
|
|
211
|
+
VALUE bottom = rb_hash_aref(value, ID2SYM(id_bottom));
|
|
212
|
+
VALUE left = rb_hash_aref(value, ID2SYM(id_left));
|
|
213
|
+
VALUE unit = rb_hash_aref(value, ID2SYM(id_unit));
|
|
214
|
+
double top_number;
|
|
215
|
+
double right_number;
|
|
216
|
+
double bottom_number;
|
|
217
|
+
double left_number;
|
|
218
|
+
double factor;
|
|
219
|
+
|
|
220
|
+
if (NIL_P(top)) {
|
|
221
|
+
rb_raise(rb_eArgError, "margins requires :top");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (NIL_P(right)) {
|
|
225
|
+
rb_raise(rb_eArgError, "margins requires :right");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (NIL_P(bottom)) {
|
|
229
|
+
rb_raise(rb_eArgError, "margins requires :bottom");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (NIL_P(left)) {
|
|
233
|
+
rb_raise(rb_eArgError, "margins requires :left");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (NIL_P(unit)) {
|
|
237
|
+
rb_raise(rb_eArgError, "margins requires :unit");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
top_number = NUM2DBL(top);
|
|
241
|
+
right_number = NUM2DBL(right);
|
|
242
|
+
bottom_number = NUM2DBL(bottom);
|
|
243
|
+
left_number = NUM2DBL(left);
|
|
244
|
+
|
|
245
|
+
if (top_number < 0 || right_number < 0 || bottom_number < 0 || left_number < 0) {
|
|
246
|
+
rb_raise(rb_eArgError, "margins values must be greater than or equal to 0");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
factor = pageprint_unit_factor_from_value(unit, "margins unit");
|
|
250
|
+
|
|
251
|
+
return PLUTOBOOK_MAKE_PAGE_MARGINS(
|
|
252
|
+
(float)(top_number * factor),
|
|
253
|
+
(float)(right_number * factor),
|
|
254
|
+
(float)(bottom_number * factor),
|
|
255
|
+
(float)(left_number * factor)
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!RB_SYMBOL_P(value)) {
|
|
260
|
+
rb_raise(rb_eTypeError, "margins must be a Symbol or Hash");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
value_id = SYM2ID(value);
|
|
264
|
+
|
|
265
|
+
if (value_id == rb_intern("none")) return PLUTOBOOK_PAGE_MARGINS_NONE;
|
|
266
|
+
if (value_id == rb_intern("normal")) return PLUTOBOOK_PAGE_MARGINS_NORMAL;
|
|
267
|
+
if (value_id == rb_intern("narrow")) return PLUTOBOOK_PAGE_MARGINS_NARROW;
|
|
268
|
+
if (value_id == rb_intern("moderate")) return PLUTOBOOK_PAGE_MARGINS_MODERATE;
|
|
269
|
+
if (value_id == rb_intern("wide")) return PLUTOBOOK_PAGE_MARGINS_WIDE;
|
|
270
|
+
|
|
271
|
+
rb_raise(rb_eArgError, "margins must be one of: :none, :normal, :narrow, :moderate, :wide");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
static plutobook_media_type_t pageprint_media_from_value(VALUE value)
|
|
275
|
+
{
|
|
276
|
+
ID value_id;
|
|
277
|
+
|
|
278
|
+
if (!RB_SYMBOL_P(value)) {
|
|
279
|
+
rb_raise(rb_eTypeError, "media must be a Symbol");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
value_id = SYM2ID(value);
|
|
283
|
+
|
|
284
|
+
if (value_id == rb_intern("print")) return PLUTOBOOK_MEDIA_TYPE_PRINT;
|
|
285
|
+
if (value_id == rb_intern("screen")) return PLUTOBOOK_MEDIA_TYPE_SCREEN;
|
|
286
|
+
|
|
287
|
+
rb_raise(rb_eArgError, "media must be one of: :print, :screen");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
static int pageprint_metadata_id_known(ID key_id)
|
|
291
|
+
{
|
|
292
|
+
return key_id == id_title ||
|
|
293
|
+
key_id == id_author ||
|
|
294
|
+
key_id == id_subject ||
|
|
295
|
+
key_id == id_keywords ||
|
|
296
|
+
key_id == id_creation_date ||
|
|
297
|
+
key_id == id_modification_date;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
static void pageprint_validate_metadata_key(VALUE key)
|
|
301
|
+
{
|
|
302
|
+
ID key_id;
|
|
303
|
+
|
|
304
|
+
if (!RB_SYMBOL_P(key)) {
|
|
305
|
+
rb_raise(rb_eTypeError, "metadata keys must be Symbols");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
key_id = SYM2ID(key);
|
|
309
|
+
|
|
310
|
+
if (!pageprint_metadata_id_known(key_id)) {
|
|
311
|
+
rb_raise(rb_eArgError, "metadata contains unknown key: :%s", rb_id2name(key_id));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
static void pageprint_validate_metadata(VALUE metadata)
|
|
316
|
+
{
|
|
317
|
+
VALUE keys;
|
|
318
|
+
long i;
|
|
319
|
+
|
|
320
|
+
if (NIL_P(metadata)) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!RB_TYPE_P(metadata, T_HASH)) {
|
|
325
|
+
rb_raise(rb_eTypeError, "metadata must be a Hash or nil");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
keys = rb_funcall(metadata, rb_intern("keys"), 0);
|
|
329
|
+
|
|
330
|
+
for (i = 0; i < RARRAY_LEN(keys); i++) {
|
|
331
|
+
VALUE key = rb_ary_entry(keys, i);
|
|
332
|
+
VALUE value;
|
|
333
|
+
|
|
334
|
+
pageprint_validate_metadata_key(key);
|
|
335
|
+
|
|
336
|
+
value = rb_hash_aref(metadata, key);
|
|
337
|
+
|
|
338
|
+
if (!NIL_P(value) && !RB_TYPE_P(value, T_STRING)) {
|
|
339
|
+
rb_raise(rb_eTypeError, "metadata values must be Strings or nil");
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
static void pageprint_set_metadata_value(plutobook_t *book, VALUE metadata, ID key_id, plutobook_pdf_metadata_t metadata_key)
|
|
345
|
+
{
|
|
346
|
+
VALUE value;
|
|
347
|
+
|
|
348
|
+
value = rb_hash_aref(metadata, ID2SYM(key_id));
|
|
349
|
+
|
|
350
|
+
if (NIL_P(value)) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
plutobook_set_metadata(book, metadata_key, StringValueCStr(value));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
static void pageprint_apply_metadata(plutobook_t *book, VALUE metadata)
|
|
358
|
+
{
|
|
359
|
+
if (NIL_P(metadata)) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
pageprint_set_metadata_value(book, metadata, id_title, PLUTOBOOK_PDF_METADATA_TITLE);
|
|
364
|
+
pageprint_set_metadata_value(book, metadata, id_author, PLUTOBOOK_PDF_METADATA_AUTHOR);
|
|
365
|
+
pageprint_set_metadata_value(book, metadata, id_subject, PLUTOBOOK_PDF_METADATA_SUBJECT);
|
|
366
|
+
pageprint_set_metadata_value(book, metadata, id_keywords, PLUTOBOOK_PDF_METADATA_KEYWORDS);
|
|
367
|
+
pageprint_set_metadata_value(book, metadata, id_creation_date, PLUTOBOOK_PDF_METADATA_CREATION_DATE);
|
|
368
|
+
pageprint_set_metadata_value(book, metadata, id_modification_date, PLUTOBOOK_PDF_METADATA_MODIFICATION_DATE);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
static void *pageprint_load_html_without_gvl(void *ptr)
|
|
372
|
+
{
|
|
373
|
+
pageprint_load_html_args_t *args = ptr;
|
|
374
|
+
|
|
375
|
+
args->ok = plutobook_load_html(
|
|
376
|
+
args->book,
|
|
377
|
+
args->html,
|
|
378
|
+
args->length,
|
|
379
|
+
args->user_style,
|
|
380
|
+
args->user_script,
|
|
381
|
+
args->base_url
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
return NULL;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
static void *pageprint_write_pdf_without_gvl(void *ptr)
|
|
388
|
+
{
|
|
389
|
+
pageprint_write_pdf_args_t *args = ptr;
|
|
390
|
+
|
|
391
|
+
args->ok = plutobook_write_to_pdf(args->book, args->path);
|
|
392
|
+
|
|
393
|
+
return NULL;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
static void *pageprint_append_pdf_string_with_gvl(void *ptr)
|
|
397
|
+
{
|
|
398
|
+
pageprint_pdf_string_append_t *append = ptr;
|
|
399
|
+
int state = 0;
|
|
400
|
+
|
|
401
|
+
rb_protect(pageprint_append_pdf_string, (VALUE)append, &state);
|
|
402
|
+
|
|
403
|
+
return (void *)(intptr_t)state;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
static void *pageprint_write_pdf_stream_without_gvl(void *ptr)
|
|
407
|
+
{
|
|
408
|
+
pageprint_write_pdf_stream_args_t *args = ptr;
|
|
409
|
+
|
|
410
|
+
args->ok = plutobook_write_to_pdf_stream(args->book, pageprint_write_pdf_string, args->output);
|
|
411
|
+
|
|
412
|
+
return NULL;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
static VALUE pageprint_call_resource_fetcher(VALUE value)
|
|
416
|
+
{
|
|
417
|
+
pageprint_resource_fetch_args_t *args = (pageprint_resource_fetch_args_t *)value;
|
|
418
|
+
VALUE url;
|
|
419
|
+
VALUE result;
|
|
420
|
+
VALUE content;
|
|
421
|
+
VALUE mime_type;
|
|
422
|
+
VALUE text_encoding;
|
|
423
|
+
const char *text_encoding_str = "";
|
|
424
|
+
|
|
425
|
+
url = rb_str_new_cstr(args->url);
|
|
426
|
+
result = rb_funcall(args->fetcher->object, id_call, 1, url);
|
|
427
|
+
|
|
428
|
+
if (NIL_P(result)) {
|
|
429
|
+
return Qnil;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (!RB_TYPE_P(result, T_HASH)) {
|
|
433
|
+
rb_raise(rb_eTypeError, "resource_fetcher must return a Hash or nil");
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
content = rb_hash_aref(result, ID2SYM(id_content));
|
|
437
|
+
mime_type = rb_hash_aref(result, ID2SYM(id_mime_type));
|
|
438
|
+
text_encoding = rb_hash_aref(result, ID2SYM(id_text_encoding));
|
|
439
|
+
|
|
440
|
+
if (!RB_TYPE_P(content, T_STRING)) {
|
|
441
|
+
rb_raise(rb_eTypeError, "resource_fetcher result content must be a String");
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (!RB_TYPE_P(mime_type, T_STRING)) {
|
|
445
|
+
rb_raise(rb_eTypeError, "resource_fetcher result mime_type must be a String");
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (!NIL_P(text_encoding)) {
|
|
449
|
+
if (!RB_TYPE_P(text_encoding, T_STRING)) {
|
|
450
|
+
rb_raise(rb_eTypeError, "resource_fetcher result text_encoding must be a String or nil");
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
text_encoding_str = StringValueCStr(text_encoding);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (RSTRING_LEN(content) > UINT_MAX) {
|
|
457
|
+
rb_raise(rb_eArgError, "resource_fetcher result content is too large");
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
args->resource = plutobook_resource_data_create(
|
|
461
|
+
RSTRING_PTR(content),
|
|
462
|
+
(unsigned int)RSTRING_LEN(content),
|
|
463
|
+
StringValueCStr(mime_type),
|
|
464
|
+
text_encoding_str
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
if (!args->resource) {
|
|
468
|
+
pageprint_raise_plutobook_error(rb_eRuntimeError, "failed to create resource data");
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return Qnil;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
static void *pageprint_call_resource_fetcher_with_gvl(void *ptr)
|
|
475
|
+
{
|
|
476
|
+
pageprint_resource_fetch_args_t *args = ptr;
|
|
477
|
+
int state = 0;
|
|
478
|
+
|
|
479
|
+
rb_protect(pageprint_call_resource_fetcher, (VALUE)args, &state);
|
|
480
|
+
|
|
481
|
+
return (void *)(intptr_t)state;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
static plutobook_resource_data_t *pageprint_fetch_resource(void *closure, const char *url)
|
|
485
|
+
{
|
|
486
|
+
pageprint_resource_fetcher_t *fetcher = closure;
|
|
487
|
+
pageprint_resource_fetch_args_t args;
|
|
488
|
+
int state;
|
|
489
|
+
|
|
490
|
+
args.fetcher = fetcher;
|
|
491
|
+
args.url = url;
|
|
492
|
+
args.resource = NULL;
|
|
493
|
+
|
|
494
|
+
state = (int)(intptr_t)rb_thread_call_with_gvl(pageprint_call_resource_fetcher_with_gvl, &args);
|
|
495
|
+
|
|
496
|
+
if (state) {
|
|
497
|
+
fetcher->state = state;
|
|
498
|
+
plutobook_set_error_message("failed to fetch URL '%s'", url);
|
|
499
|
+
return NULL;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (args.resource) {
|
|
503
|
+
return args.resource;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return plutobook_fetch_url(url);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
static pageprint_options_t pageprint_options_from_value(VALUE options)
|
|
510
|
+
{
|
|
511
|
+
pageprint_options_t result;
|
|
512
|
+
VALUE base_url_value;
|
|
513
|
+
VALUE page_size_value;
|
|
514
|
+
VALUE margins_value;
|
|
515
|
+
VALUE media_value;
|
|
516
|
+
VALUE resource_fetcher_value;
|
|
517
|
+
VALUE metadata_value;
|
|
518
|
+
VALUE keyword_values[6];
|
|
519
|
+
ID keyword_ids[6];
|
|
520
|
+
|
|
521
|
+
result.page_size = PLUTOBOOK_PAGE_SIZE_A4;
|
|
522
|
+
result.margins = PLUTOBOOK_PAGE_MARGINS_NORMAL;
|
|
523
|
+
result.media = PLUTOBOOK_MEDIA_TYPE_PRINT;
|
|
524
|
+
result.base_url = "";
|
|
525
|
+
result.resource_fetcher = Qnil;
|
|
526
|
+
result.metadata = Qnil;
|
|
527
|
+
|
|
528
|
+
if (NIL_P(options)) {
|
|
529
|
+
options = rb_hash_new();
|
|
530
|
+
} else if (!RB_TYPE_P(options, T_HASH)) {
|
|
531
|
+
rb_raise(rb_eTypeError, "options must be a Hash");
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
keyword_ids[0] = id_base_url;
|
|
535
|
+
keyword_ids[1] = id_page_size;
|
|
536
|
+
keyword_ids[2] = id_margins;
|
|
537
|
+
keyword_ids[3] = id_media;
|
|
538
|
+
keyword_ids[4] = id_resource_fetcher;
|
|
539
|
+
keyword_ids[5] = id_metadata;
|
|
540
|
+
|
|
541
|
+
rb_get_kwargs(options, keyword_ids, 0, 6, keyword_values);
|
|
542
|
+
|
|
543
|
+
base_url_value = keyword_values[0];
|
|
544
|
+
page_size_value = keyword_values[1];
|
|
545
|
+
margins_value = keyword_values[2];
|
|
546
|
+
media_value = keyword_values[3];
|
|
547
|
+
resource_fetcher_value = keyword_values[4];
|
|
548
|
+
metadata_value = keyword_values[5];
|
|
549
|
+
|
|
550
|
+
if (base_url_value == Qundef) {
|
|
551
|
+
base_url_value = rb_funcall(mPagePrint, id_base_url_method, 0);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (!NIL_P(base_url_value)) {
|
|
555
|
+
if (!RB_TYPE_P(base_url_value, T_STRING)) {
|
|
556
|
+
rb_raise(rb_eTypeError, "base_url must be a String or nil");
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
result.base_url = StringValueCStr(base_url_value);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (page_size_value != Qundef) {
|
|
563
|
+
result.page_size = pageprint_page_size_from_value(page_size_value);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (margins_value != Qundef) {
|
|
567
|
+
result.margins = pageprint_margins_from_value(margins_value);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (media_value != Qundef) {
|
|
571
|
+
result.media = pageprint_media_from_value(media_value);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (resource_fetcher_value == Qundef) {
|
|
575
|
+
resource_fetcher_value = rb_ivar_get(mPagePrint, id_resource_fetcher_ivar);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (!NIL_P(resource_fetcher_value) && resource_fetcher_value != Qfalse) {
|
|
579
|
+
if (!rb_respond_to(resource_fetcher_value, id_call)) {
|
|
580
|
+
rb_raise(rb_eTypeError, "resource_fetcher must respond to call or be nil/false");
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
result.resource_fetcher = resource_fetcher_value;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (metadata_value != Qundef) {
|
|
587
|
+
pageprint_validate_metadata(metadata_value);
|
|
588
|
+
result.metadata = metadata_value;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return result;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
static plutobook_t *pageprint_create_book_from_html(VALUE html, VALUE options, pageprint_resource_fetcher_t *resource_fetcher)
|
|
595
|
+
{
|
|
596
|
+
pageprint_options_t pageprint_options;
|
|
597
|
+
|
|
598
|
+
const char *html_str;
|
|
599
|
+
long html_len;
|
|
600
|
+
|
|
601
|
+
plutobook_t *book;
|
|
602
|
+
pageprint_load_html_args_t load_args;
|
|
603
|
+
|
|
604
|
+
if (!RB_TYPE_P(html, T_STRING)) {
|
|
605
|
+
rb_raise(rb_eTypeError, "html must be a String");
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (RSTRING_LEN(html) == 0) {
|
|
609
|
+
rb_raise(rb_eArgError, "html must not be empty");
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (RSTRING_LEN(html) > INT_MAX) {
|
|
613
|
+
rb_raise(rb_eArgError, "html is too large");
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
pageprint_options = pageprint_options_from_value(options);
|
|
617
|
+
|
|
618
|
+
html_str = RSTRING_PTR(html);
|
|
619
|
+
html_len = RSTRING_LEN(html);
|
|
620
|
+
|
|
621
|
+
/* 1. Create */
|
|
622
|
+
book = plutobook_create(pageprint_options.page_size, pageprint_options.margins, pageprint_options.media);
|
|
623
|
+
|
|
624
|
+
if (!book) {
|
|
625
|
+
rb_raise(rb_eRuntimeError, "failed to create plutobook");
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
resource_fetcher->object = pageprint_options.resource_fetcher;
|
|
629
|
+
resource_fetcher->state = 0;
|
|
630
|
+
|
|
631
|
+
if (!NIL_P(resource_fetcher->object)) {
|
|
632
|
+
plutobook_set_custom_resource_fetcher(book, pageprint_fetch_resource, resource_fetcher);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/* 2. Load HTML */
|
|
636
|
+
plutobook_clear_error_message();
|
|
637
|
+
|
|
638
|
+
load_args.book = book;
|
|
639
|
+
load_args.html = html_str;
|
|
640
|
+
load_args.length = (int)html_len;
|
|
641
|
+
load_args.user_style = "";
|
|
642
|
+
load_args.user_script = "";
|
|
643
|
+
load_args.base_url = pageprint_options.base_url;
|
|
644
|
+
load_args.ok = 0;
|
|
645
|
+
|
|
646
|
+
rb_thread_call_without_gvl(
|
|
647
|
+
pageprint_load_html_without_gvl,
|
|
648
|
+
&load_args,
|
|
649
|
+
RUBY_UBF_IO,
|
|
650
|
+
NULL
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
if (!load_args.ok) {
|
|
654
|
+
plutobook_destroy(book);
|
|
655
|
+
|
|
656
|
+
if (resource_fetcher->state) {
|
|
657
|
+
rb_jump_tag(resource_fetcher->state);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
pageprint_raise_plutobook_error(rb_eRuntimeError, "failed to load HTML into plutobook");
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (resource_fetcher->state) {
|
|
664
|
+
plutobook_destroy(book);
|
|
665
|
+
rb_jump_tag(resource_fetcher->state);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
pageprint_apply_metadata(book, pageprint_options.metadata);
|
|
669
|
+
|
|
670
|
+
return book;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
static VALUE pageprint_append_pdf_string(VALUE value)
|
|
674
|
+
{
|
|
675
|
+
pageprint_pdf_string_append_t *append = (pageprint_pdf_string_append_t *)value;
|
|
676
|
+
|
|
677
|
+
rb_str_cat(append->output, append->data, append->length);
|
|
678
|
+
|
|
679
|
+
return Qnil;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
static plutobook_stream_status_t pageprint_write_pdf_string(void *closure, const char *data, unsigned int length)
|
|
683
|
+
{
|
|
684
|
+
pageprint_pdf_string_output_t *output = closure;
|
|
685
|
+
pageprint_pdf_string_append_t append;
|
|
686
|
+
int state;
|
|
687
|
+
|
|
688
|
+
append.output = output->output;
|
|
689
|
+
append.data = data;
|
|
690
|
+
append.length = length;
|
|
691
|
+
|
|
692
|
+
state = (int)(intptr_t)rb_thread_call_with_gvl(pageprint_append_pdf_string_with_gvl, &append);
|
|
693
|
+
|
|
694
|
+
if (state) {
|
|
695
|
+
output->state = state;
|
|
696
|
+
return PLUTOBOOK_STREAM_STATUS_WRITE_ERROR;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
return PLUTOBOOK_STREAM_STATUS_SUCCESS;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
static VALUE pageprint_html_to_pdf(int argc, VALUE *argv, VALUE self) {
|
|
703
|
+
VALUE html;
|
|
704
|
+
VALUE path;
|
|
705
|
+
VALUE options;
|
|
706
|
+
|
|
707
|
+
const char *path_str;
|
|
708
|
+
|
|
709
|
+
plutobook_t *book;
|
|
710
|
+
pageprint_resource_fetcher_t resource_fetcher;
|
|
711
|
+
pageprint_write_pdf_args_t write_args;
|
|
712
|
+
|
|
713
|
+
rb_check_arity(argc, 2, 3);
|
|
714
|
+
|
|
715
|
+
html = argv[0];
|
|
716
|
+
path = argv[1];
|
|
717
|
+
options = argc == 3 ? argv[2] : Qnil;
|
|
718
|
+
|
|
719
|
+
if (!RB_TYPE_P(path, T_STRING)) {
|
|
720
|
+
rb_raise(rb_eTypeError, "path must be a String");
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (RSTRING_LEN(path) == 0) {
|
|
724
|
+
rb_raise(rb_eArgError, "path must not be empty");
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
path_str = StringValueCStr(path);
|
|
728
|
+
|
|
729
|
+
book = pageprint_create_book_from_html(html, options, &resource_fetcher);
|
|
730
|
+
|
|
731
|
+
plutobook_clear_error_message();
|
|
732
|
+
|
|
733
|
+
write_args.book = book;
|
|
734
|
+
write_args.path = path_str;
|
|
735
|
+
write_args.ok = 0;
|
|
736
|
+
|
|
737
|
+
rb_thread_call_without_gvl(
|
|
738
|
+
pageprint_write_pdf_without_gvl,
|
|
739
|
+
&write_args,
|
|
740
|
+
RUBY_UBF_IO,
|
|
741
|
+
NULL
|
|
742
|
+
);
|
|
743
|
+
|
|
744
|
+
plutobook_destroy(book);
|
|
745
|
+
|
|
746
|
+
if (resource_fetcher.state) {
|
|
747
|
+
rb_jump_tag(resource_fetcher.state);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (!write_args.ok) {
|
|
751
|
+
pageprint_raise_plutobook_error_with_path(rb_eRuntimeError, "failed to write PDF to", path_str);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
RB_GC_GUARD(resource_fetcher.object);
|
|
755
|
+
RB_GC_GUARD(options);
|
|
756
|
+
RB_GC_GUARD(path);
|
|
757
|
+
|
|
758
|
+
return Qtrue;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
static VALUE pageprint_html_to_pdf_string(int argc, VALUE *argv, VALUE self) {
|
|
762
|
+
VALUE html;
|
|
763
|
+
VALUE options;
|
|
764
|
+
pageprint_pdf_string_output_t output;
|
|
765
|
+
|
|
766
|
+
plutobook_t *book;
|
|
767
|
+
pageprint_resource_fetcher_t resource_fetcher;
|
|
768
|
+
pageprint_write_pdf_stream_args_t write_args;
|
|
769
|
+
|
|
770
|
+
rb_check_arity(argc, 1, 2);
|
|
771
|
+
|
|
772
|
+
html = argv[0];
|
|
773
|
+
options = argc == 2 ? argv[1] : Qnil;
|
|
774
|
+
output.output = rb_str_new(NULL, 0);
|
|
775
|
+
output.state = 0;
|
|
776
|
+
rb_enc_associate_index(output.output, rb_ascii8bit_encindex());
|
|
777
|
+
|
|
778
|
+
book = pageprint_create_book_from_html(html, options, &resource_fetcher);
|
|
779
|
+
|
|
780
|
+
plutobook_clear_error_message();
|
|
781
|
+
|
|
782
|
+
write_args.book = book;
|
|
783
|
+
write_args.output = &output;
|
|
784
|
+
write_args.ok = 0;
|
|
785
|
+
|
|
786
|
+
rb_thread_call_without_gvl(
|
|
787
|
+
pageprint_write_pdf_stream_without_gvl,
|
|
788
|
+
&write_args,
|
|
789
|
+
RUBY_UBF_IO,
|
|
790
|
+
NULL
|
|
791
|
+
);
|
|
792
|
+
|
|
793
|
+
plutobook_destroy(book);
|
|
794
|
+
|
|
795
|
+
if (resource_fetcher.state) {
|
|
796
|
+
rb_jump_tag(resource_fetcher.state);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if (output.state) {
|
|
800
|
+
rb_jump_tag(output.state);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (!write_args.ok) {
|
|
804
|
+
pageprint_raise_plutobook_error(rb_eRuntimeError, "failed to write PDF to string");
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
RB_GC_GUARD(resource_fetcher.object);
|
|
808
|
+
RB_GC_GUARD(options);
|
|
809
|
+
RB_GC_GUARD(output.output);
|
|
810
|
+
|
|
811
|
+
return output.output;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
void Init_page_print(void) {
|
|
815
|
+
mPagePrint = rb_define_module("PagePrint");
|
|
816
|
+
|
|
817
|
+
id_base_url = rb_intern_const("base_url");
|
|
818
|
+
id_page_size = rb_intern_const("page_size");
|
|
819
|
+
id_margins = rb_intern_const("margins");
|
|
820
|
+
id_media = rb_intern_const("media");
|
|
821
|
+
id_resource_fetcher = rb_intern_const("resource_fetcher");
|
|
822
|
+
id_metadata = rb_intern_const("metadata");
|
|
823
|
+
id_width = rb_intern_const("width");
|
|
824
|
+
id_height = rb_intern_const("height");
|
|
825
|
+
id_unit = rb_intern_const("unit");
|
|
826
|
+
id_top = rb_intern_const("top");
|
|
827
|
+
id_right = rb_intern_const("right");
|
|
828
|
+
id_bottom = rb_intern_const("bottom");
|
|
829
|
+
id_left = rb_intern_const("left");
|
|
830
|
+
id_resource_fetcher_ivar = rb_intern_const("@resource_fetcher");
|
|
831
|
+
id_base_url_method = rb_intern_const("base_url");
|
|
832
|
+
id_call = rb_intern_const("call");
|
|
833
|
+
id_content = rb_intern_const("content");
|
|
834
|
+
id_mime_type = rb_intern_const("mime_type");
|
|
835
|
+
id_text_encoding = rb_intern_const("text_encoding");
|
|
836
|
+
id_title = rb_intern_const("title");
|
|
837
|
+
id_author = rb_intern_const("author");
|
|
838
|
+
id_subject = rb_intern_const("subject");
|
|
839
|
+
id_keywords = rb_intern_const("keywords");
|
|
840
|
+
id_creation_date = rb_intern_const("creation_date");
|
|
841
|
+
id_modification_date = rb_intern_const("modification_date");
|
|
842
|
+
id_pt = rb_intern_const("pt");
|
|
843
|
+
id_pc = rb_intern_const("pc");
|
|
844
|
+
id_in = rb_intern_const("in");
|
|
845
|
+
id_cm = rb_intern_const("cm");
|
|
846
|
+
id_mm = rb_intern_const("mm");
|
|
847
|
+
id_px = rb_intern_const("px");
|
|
848
|
+
|
|
849
|
+
rb_define_singleton_method(mPagePrint, "html_to_pdf", RUBY_METHOD_FUNC(pageprint_html_to_pdf), -1);
|
|
850
|
+
rb_define_singleton_method(mPagePrint, "html_to_pdf_string", RUBY_METHOD_FUNC(pageprint_html_to_pdf_string), -1);
|
|
851
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
require 'pathname'
|
|
2
|
+
require 'uri'
|
|
3
|
+
|
|
4
|
+
module PagePrint
|
|
5
|
+
class RailsResourceFetcher
|
|
6
|
+
DEFAULT_ASSET_PREFIX = '/assets'
|
|
7
|
+
|
|
8
|
+
def initialize(rails: nil, asset_prefix: DEFAULT_ASSET_PREFIX)
|
|
9
|
+
@rails = rails || rails_constant
|
|
10
|
+
@asset_prefix = normalize_asset_prefix(asset_prefix)
|
|
11
|
+
|
|
12
|
+
raise ArgumentError, 'Rails is not available' unless @rails
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call(url)
|
|
16
|
+
path = path_from_url(url)
|
|
17
|
+
return unless path&.start_with?("#{@asset_prefix}/")
|
|
18
|
+
|
|
19
|
+
read_public_asset(path) || read_resolved_asset(path)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
attr_reader :rails, :asset_prefix
|
|
25
|
+
|
|
26
|
+
def rails_constant
|
|
27
|
+
Object.const_get(:Rails) if Object.const_defined?(:Rails)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def normalize_asset_prefix(value)
|
|
31
|
+
prefix = value.to_s
|
|
32
|
+
prefix = "/#{prefix}" unless prefix.start_with?('/')
|
|
33
|
+
prefix.delete_suffix('/')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def path_from_url(url)
|
|
37
|
+
parsed = URI.parse(url.to_s)
|
|
38
|
+
parsed.path.empty? ? url.to_s : parsed.path
|
|
39
|
+
rescue URI::InvalidURIError
|
|
40
|
+
url.to_s
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def read_public_asset(path)
|
|
44
|
+
public_path = rails_public_path
|
|
45
|
+
return unless public_path
|
|
46
|
+
|
|
47
|
+
relative_path = path.delete_prefix('/')
|
|
48
|
+
file_path = public_path.join(relative_path).cleanpath
|
|
49
|
+
return unless inside_path?(file_path, public_path)
|
|
50
|
+
return unless file_path.file?
|
|
51
|
+
|
|
52
|
+
resource(File.binread(file_path), file_path.extname)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def read_resolved_asset(path)
|
|
56
|
+
asset_path = path.delete_prefix("#{asset_prefix}/")
|
|
57
|
+
content = read_from_propshaft(asset_path)
|
|
58
|
+
return resource(content, File.extname(asset_path)) if content
|
|
59
|
+
|
|
60
|
+
logical_path = propshaft_logical_path(asset_path)
|
|
61
|
+
return unless logical_path
|
|
62
|
+
|
|
63
|
+
content = read_from_propshaft(logical_path)
|
|
64
|
+
return unless content
|
|
65
|
+
|
|
66
|
+
resource(content, File.extname(logical_path))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def read_from_propshaft(path)
|
|
70
|
+
resolver = rails_application&.assets&.resolver
|
|
71
|
+
return unless resolver&.respond_to?(:read)
|
|
72
|
+
|
|
73
|
+
resolver.read(path)
|
|
74
|
+
rescue StandardError
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def propshaft_logical_path(asset_path)
|
|
79
|
+
resolver = rails_application&.assets&.resolver
|
|
80
|
+
return unless resolver&.respond_to?(:load_path)
|
|
81
|
+
return unless resolver.load_path.respond_to?(:find)
|
|
82
|
+
|
|
83
|
+
asset = resolver.load_path.find(asset_path)
|
|
84
|
+
asset&.logical_path&.to_s
|
|
85
|
+
rescue StandardError
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def rails_application
|
|
90
|
+
rails.application if rails.respond_to?(:application)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def rails_public_path
|
|
94
|
+
path = rails.public_path if rails.respond_to?(:public_path)
|
|
95
|
+
path ||= rails_application.public_path if rails_application&.respond_to?(:public_path)
|
|
96
|
+
return unless path
|
|
97
|
+
|
|
98
|
+
Pathname.new(path.to_s).cleanpath
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def inside_path?(path, root)
|
|
102
|
+
relative_path = path.relative_path_from(root).to_s
|
|
103
|
+
relative_path != '..' && !relative_path.start_with?('../')
|
|
104
|
+
rescue ArgumentError
|
|
105
|
+
false
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def resource(content, extension)
|
|
109
|
+
{ content: content, mime_type: mime_type_for(extension) }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def mime_type_for(extension)
|
|
113
|
+
if defined?(Rack::Mime)
|
|
114
|
+
Rack::Mime.mime_type(extension, 'application/octet-stream')
|
|
115
|
+
else
|
|
116
|
+
fallback_mime_type_for(extension)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def fallback_mime_type_for(extension)
|
|
121
|
+
case extension.to_s.downcase
|
|
122
|
+
when '.css' then 'text/css'
|
|
123
|
+
when '.gif' then 'image/gif'
|
|
124
|
+
when '.html', '.htm' then 'text/html'
|
|
125
|
+
when '.jpeg', '.jpg' then 'image/jpeg'
|
|
126
|
+
when '.js', '.mjs' then 'text/javascript'
|
|
127
|
+
when '.otf' then 'font/otf'
|
|
128
|
+
when '.png' then 'image/png'
|
|
129
|
+
when '.svg' then 'image/svg+xml'
|
|
130
|
+
when '.ttf' then 'font/ttf'
|
|
131
|
+
when '.webp' then 'image/webp'
|
|
132
|
+
when '.woff' then 'font/woff'
|
|
133
|
+
when '.woff2' then 'font/woff2'
|
|
134
|
+
else 'application/octet-stream'
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module PagePrint
|
|
2
|
+
class Railtie < ::Rails::Railtie
|
|
3
|
+
initializer 'page_print.configure_resource_fetcher' do
|
|
4
|
+
PagePrint.resource_fetcher ||= PagePrint::RailsResourceFetcher.new
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
initializer 'page_print.set_base_url' do
|
|
8
|
+
ActiveSupport.on_load(:action_controller_base) do
|
|
9
|
+
around_action do |_controller, action|
|
|
10
|
+
PagePrint.with_base_url(request.base_url) { action.call }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/page_print.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'page_print/version'
|
|
2
|
+
require_relative 'page_print/page_print'
|
|
3
|
+
require 'page_print/rails_resource_fetcher'
|
|
4
|
+
|
|
5
|
+
module PagePrint
|
|
6
|
+
class << self
|
|
7
|
+
attr_accessor :resource_fetcher
|
|
8
|
+
attr_writer :base_url
|
|
9
|
+
|
|
10
|
+
def base_url
|
|
11
|
+
Thread.current[:page_print_base_url] || @base_url
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def configure
|
|
15
|
+
yield self
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def with_base_url(base_url)
|
|
19
|
+
previous_base_url = Thread.current[:page_print_base_url]
|
|
20
|
+
Thread.current[:page_print_base_url] = base_url
|
|
21
|
+
yield
|
|
22
|
+
ensure
|
|
23
|
+
Thread.current[:page_print_base_url] = previous_base_url
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
require 'page_print/railtie' if defined?(Rails::Railtie)
|
metadata
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: page_print
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Dino Maric
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: A Ruby gem with a native extension that renders HTML strings to PDF files
|
|
13
|
+
via the plutobook C library.
|
|
14
|
+
email:
|
|
15
|
+
- dino.onex@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions:
|
|
18
|
+
- ext/page_print/extconf.rb
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- LICENSE
|
|
22
|
+
- README.md
|
|
23
|
+
- ext/page_print/extconf.rb
|
|
24
|
+
- ext/page_print/page_print.c
|
|
25
|
+
- lib/page_print.rb
|
|
26
|
+
- lib/page_print/rails_resource_fetcher.rb
|
|
27
|
+
- lib/page_print/railtie.rb
|
|
28
|
+
- lib/page_print/version.rb
|
|
29
|
+
homepage: https://github.com/WizardComputer/page_print
|
|
30
|
+
licenses:
|
|
31
|
+
- MIT
|
|
32
|
+
metadata:
|
|
33
|
+
homepage_uri: https://github.com/WizardComputer/page_print
|
|
34
|
+
source_code_uri: https://github.com/WizardComputer/page_print
|
|
35
|
+
changelog_uri: https://github.com/WizardComputer/page_print/releases
|
|
36
|
+
rdoc_options: []
|
|
37
|
+
require_paths:
|
|
38
|
+
- lib
|
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '3.0'
|
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0'
|
|
49
|
+
requirements: []
|
|
50
|
+
rubygems_version: 3.6.9
|
|
51
|
+
specification_version: 4
|
|
52
|
+
summary: Render HTML to PDF from Ruby using plutobook
|
|
53
|
+
test_files: []
|