page_print 0.1.9 → 0.1.11
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 +121 -200
- data/docs/configuration.md +204 -0
- data/docs/production.md +148 -0
- data/ext/page_print/page_print.c +213 -105
- data/lib/page_print/rails_resource_fetcher.rb +4 -1
- data/lib/page_print/version.rb +1 -1
- metadata +3 -1
data/docs/production.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Production guide
|
|
2
|
+
|
|
3
|
+
PagePrint is pre-1.0 software. Treat adoption as an application-specific engineering decision: validate the native package, representative documents, throughput, memory use, fonts, and failure behavior on the same platform used in production.
|
|
4
|
+
|
|
5
|
+
## Production readiness checklist
|
|
6
|
+
|
|
7
|
+
Before deploying PagePrint:
|
|
8
|
+
|
|
9
|
+
- [ ] Confirm the deployment platform has a native gem or can build PlutoBook from source.
|
|
10
|
+
- [ ] Render representative short, long, image-heavy, and multilingual documents in CI.
|
|
11
|
+
- [ ] Visually compare pagination, fonts, images, headers, and footers against approved fixtures.
|
|
12
|
+
- [ ] Verify every stylesheet, image, and font is available through the Rails integration or an allowlisted resource fetcher.
|
|
13
|
+
- [ ] Confirm templates do not depend on JavaScript, CSS Grid, or other unsupported browser features.
|
|
14
|
+
- [ ] Load-test with production-sized documents and the same worker/thread configuration used in deployment.
|
|
15
|
+
- [ ] Set application-level job timeouts and monitor render latency, failures, and output size.
|
|
16
|
+
- [ ] Prefer `render_to_file` for large PDFs, and clean up temporary output in both success and failure paths.
|
|
17
|
+
- [ ] Pin the gem version and test upgrades before rollout; renderer upgrades can alter layout and pagination.
|
|
18
|
+
|
|
19
|
+
## Platform support
|
|
20
|
+
|
|
21
|
+
PagePrint requires Ruby 3.2 or newer.
|
|
22
|
+
|
|
23
|
+
| Platform | Installation |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `x86_64-linux` | Native gem with vendored PlutoBook libraries; the Ruby extension is compiled against the local Ruby |
|
|
26
|
+
| `arm64-darwin` | Native gem with vendored PlutoBook libraries; the Ruby extension is compiled against the local Ruby |
|
|
27
|
+
| Other platforms | Source build using an installed PlutoBook |
|
|
28
|
+
|
|
29
|
+
The published native gems disable PlutoBook's optional curl, TurboJPEG, and WebP features to keep the bundled dependency set smaller. PagePrint uses its own Ruby resource fetcher boundary and does not expose PlutoBook's optional network fetcher.
|
|
30
|
+
|
|
31
|
+
Your production image still needs a compiler and Ruby development headers because the small PagePrint extension is compiled during installation.
|
|
32
|
+
|
|
33
|
+
### Installing on other platforms
|
|
34
|
+
|
|
35
|
+
Install PlutoBook development headers and libraries before installing the gem. On macOS with Homebrew:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
brew install plutobook pkg-config
|
|
39
|
+
bundle install
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If `pkg-config` cannot find PlutoBook, provide explicit paths:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
gem install page_print -- \
|
|
46
|
+
--with-plutobook-include=/path/to/include \
|
|
47
|
+
--with-plutobook-lib=/path/to/lib
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Use equivalent Bundler build configuration in repeatable deployments:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
bundle config set --local build.page_print \
|
|
54
|
+
"--with-plutobook-include=/path/to/include --with-plutobook-lib=/path/to/lib"
|
|
55
|
+
bundle install
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Run a render smoke test while building the deployment artifact, rather than only checking that the gem can be required:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
bundle exec ruby -e '
|
|
62
|
+
require "page_print"
|
|
63
|
+
pdf = PagePrint.render("<h1>Deployment check</h1>")
|
|
64
|
+
abort "invalid PDF" unless pdf.start_with?("%PDF")
|
|
65
|
+
'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Security
|
|
69
|
+
|
|
70
|
+
PagePrint's resource policy is deny-by-default: an unresolved URL is not fetched over the network. Preserve that property in custom fetchers.
|
|
71
|
+
|
|
72
|
+
- Do not build a fetcher that blindly downloads arbitrary URLs. Doing so can introduce server-side request forgery (SSRF), access to cloud metadata endpoints, and unbounded downloads.
|
|
73
|
+
- Prefer a fixed asset map, an application-owned URI scheme, or strict allowlists for scheme, host, path, content type, and size.
|
|
74
|
+
- Do not treat PagePrint as a sandbox for hostile HTML. It parses complex input in native code and should run with the minimum filesystem and process permissions needed by the application.
|
|
75
|
+
- Never place secrets in HTML, asset URLs, metadata, or error logs.
|
|
76
|
+
- Validate and limit user-controlled content before inserting it into a trusted template.
|
|
77
|
+
|
|
78
|
+
PagePrint's disabled network fallback improves predictability and reduces accidental outbound access, but it is not a complete security boundary.
|
|
79
|
+
|
|
80
|
+
## Template compatibility
|
|
81
|
+
|
|
82
|
+
PagePrint is intended for static, server-rendered documents. It does not execute JavaScript. PlutoBook supports much of CSS 3 and selected CSS 4 functionality, but important gaps include CSS Grid and some advanced visual features.
|
|
83
|
+
|
|
84
|
+
Before migrating from PDFKit, `wkhtmltopdf`, or a browser renderer:
|
|
85
|
+
|
|
86
|
+
1. Inventory JavaScript, layout modules, fonts, SVG, and remote assets used by the templates.
|
|
87
|
+
2. Check the [PlutoBook feature matrix](https://github.com/plutoprint/plutobook/blob/main/FEATURES.md).
|
|
88
|
+
3. Render a fixture set with both engines.
|
|
89
|
+
4. Compare page breaks and visual output, not only whether a PDF was produced.
|
|
90
|
+
|
|
91
|
+
Use explicit `@page`, print styles, and embedded or controlled fonts. Font substitution is a common cause of different line wrapping and pagination across development and production.
|
|
92
|
+
|
|
93
|
+
## Assets and networking
|
|
94
|
+
|
|
95
|
+
For deterministic output, package document assets with the application and return them from a resource fetcher. Avoid making rendering depend on the availability or latency of another service.
|
|
96
|
+
|
|
97
|
+
Rails applications using Propshaft get a default fetcher for `/assets/...`. Outside Rails, configure a fetcher once at process startup:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
PagePrint.configure do |config|
|
|
101
|
+
config.resource_fetcher = ProductionPdfAssets.new
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
See the [resource fetcher reference](configuration.md#resource-fetchers) for the callable contract.
|
|
106
|
+
|
|
107
|
+
## Capacity and concurrency
|
|
108
|
+
|
|
109
|
+
HTML parsing, layout, and PDF writing are native CPU- and memory-intensive work. The extension releases Ruby's Global VM Lock during PlutoBook loading and writing, but Ruby callbacks such as a custom resource fetcher run with the lock.
|
|
110
|
+
|
|
111
|
+
Do not size workers from the repository benchmark alone. Measure:
|
|
112
|
+
|
|
113
|
+
- latency percentiles, not only averages;
|
|
114
|
+
- peak resident memory with concurrent renders;
|
|
115
|
+
- the largest realistic HTML, image, and PDF sizes;
|
|
116
|
+
- application behavior when a render raises or a job times out;
|
|
117
|
+
- both `render` and `render_to_file` if you expect large outputs.
|
|
118
|
+
|
|
119
|
+
Use bounded job concurrency. For background processing, let the job system retry only failures known to be transient; invalid templates and unsupported resources will fail repeatedly.
|
|
120
|
+
|
|
121
|
+
## Failure handling
|
|
122
|
+
|
|
123
|
+
PagePrint validates option types and raises Ruby exceptions for invalid input, resource fetcher failures, HTML loading failures, and PDF write failures. Treat a successful method return—and, where appropriate, a `%PDF` signature and non-zero output size—as the completion signal.
|
|
124
|
+
|
|
125
|
+
When writing temporary files:
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
require "tempfile"
|
|
129
|
+
|
|
130
|
+
Tempfile.create(["invoice", ".pdf"]) do |file|
|
|
131
|
+
PagePrint.render_to_file(html, file.path)
|
|
132
|
+
deliver(file.path)
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Do not expose native error text directly to end users. Record enough context to identify the template and render options, without logging document contents that may contain personal or confidential data.
|
|
137
|
+
|
|
138
|
+
## Upgrade strategy
|
|
139
|
+
|
|
140
|
+
Pin PagePrint in the application lockfile. Before upgrading PagePrint or PlutoBook:
|
|
141
|
+
|
|
142
|
+
1. Build on every production platform.
|
|
143
|
+
2. Run the complete PDF fixture suite.
|
|
144
|
+
3. Compare visual output and page count.
|
|
145
|
+
4. Repeat a focused load test.
|
|
146
|
+
5. Roll out gradually while watching latency and render failures.
|
|
147
|
+
|
|
148
|
+
[Back to the README](../README.md)
|
data/ext/page_print/page_print.c
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
#include "ruby/encoding.h"
|
|
3
3
|
#include "ruby/thread.h"
|
|
4
4
|
#include <limits.h>
|
|
5
|
+
#include <math.h>
|
|
5
6
|
#include <stdint.h>
|
|
7
|
+
#include <string.h>
|
|
6
8
|
#include <plutobook/plutobook.h>
|
|
7
9
|
|
|
8
10
|
#if defined(__GNUC__) || defined(__clang__)
|
|
@@ -55,6 +57,7 @@ typedef struct {
|
|
|
55
57
|
|
|
56
58
|
typedef struct {
|
|
57
59
|
VALUE object;
|
|
60
|
+
VALUE error;
|
|
58
61
|
int state;
|
|
59
62
|
} pageprint_resource_fetcher_t;
|
|
60
63
|
|
|
@@ -66,10 +69,12 @@ typedef struct {
|
|
|
66
69
|
|
|
67
70
|
typedef struct {
|
|
68
71
|
VALUE output;
|
|
72
|
+
VALUE error;
|
|
69
73
|
int state;
|
|
70
74
|
} pageprint_pdf_string_output_t;
|
|
71
75
|
|
|
72
76
|
typedef struct {
|
|
77
|
+
pageprint_pdf_string_output_t *owner;
|
|
73
78
|
VALUE output;
|
|
74
79
|
const char *data;
|
|
75
80
|
unsigned int length;
|
|
@@ -102,10 +107,49 @@ typedef struct {
|
|
|
102
107
|
VALUE metadata;
|
|
103
108
|
} pageprint_apply_metadata_args_t;
|
|
104
109
|
|
|
110
|
+
typedef struct {
|
|
111
|
+
plutobook_t *book;
|
|
112
|
+
VALUE html;
|
|
113
|
+
VALUE base_url;
|
|
114
|
+
VALUE resource_fetcher;
|
|
115
|
+
VALUE metadata;
|
|
116
|
+
pageprint_resource_fetcher_t resource_fetcher_state;
|
|
117
|
+
} pageprint_book_context_t;
|
|
118
|
+
|
|
119
|
+
typedef struct {
|
|
120
|
+
pageprint_book_context_t book_context;
|
|
121
|
+
VALUE html;
|
|
122
|
+
VALUE path;
|
|
123
|
+
VALUE options;
|
|
124
|
+
} pageprint_render_to_file_context_t;
|
|
125
|
+
|
|
126
|
+
typedef struct {
|
|
127
|
+
pageprint_book_context_t book_context;
|
|
128
|
+
VALUE html;
|
|
129
|
+
VALUE options;
|
|
130
|
+
pageprint_pdf_string_output_t output;
|
|
131
|
+
} pageprint_render_context_t;
|
|
132
|
+
|
|
105
133
|
static VALUE pageprint_append_pdf_string(VALUE value);
|
|
106
134
|
static plutobook_stream_status_t pageprint_write_pdf_string(void *closure, const char *data, unsigned int length);
|
|
107
135
|
static plutobook_resource_data_t *pageprint_fetch_resource(void *closure, const char *url);
|
|
108
136
|
|
|
137
|
+
static void pageprint_reject_embedded_nul(VALUE value, const char *name)
|
|
138
|
+
{
|
|
139
|
+
if (memchr(RSTRING_PTR(value), '\0', (size_t)RSTRING_LEN(value))) {
|
|
140
|
+
rb_raise(rb_eArgError, "%s must not contain NUL bytes", name);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static VALUE pageprint_frozen_string_copy(VALUE value)
|
|
145
|
+
{
|
|
146
|
+
VALUE copy = rb_str_dup(value);
|
|
147
|
+
|
|
148
|
+
rb_obj_freeze(copy);
|
|
149
|
+
|
|
150
|
+
return copy;
|
|
151
|
+
}
|
|
152
|
+
|
|
109
153
|
static void PAGEPRINT_NORETURN pageprint_raise_plutobook_error(VALUE error_class, const char *message)
|
|
110
154
|
{
|
|
111
155
|
const char *error_message = plutobook_get_error_message();
|
|
@@ -128,6 +172,12 @@ static void PAGEPRINT_NORETURN pageprint_raise_plutobook_error_with_path(VALUE e
|
|
|
128
172
|
rb_raise(error_class, "%s %s", message, path);
|
|
129
173
|
}
|
|
130
174
|
|
|
175
|
+
static void PAGEPRINT_NORETURN pageprint_jump_callback_error(int state, VALUE error)
|
|
176
|
+
{
|
|
177
|
+
rb_set_errinfo(error);
|
|
178
|
+
rb_jump_tag(state);
|
|
179
|
+
}
|
|
180
|
+
|
|
131
181
|
static double pageprint_unit_factor_from_value(VALUE value, const char *name)
|
|
132
182
|
{
|
|
133
183
|
ID value_id;
|
|
@@ -175,6 +225,14 @@ static plutobook_page_size_t pageprint_page_size_from_value(VALUE value)
|
|
|
175
225
|
width_number = NUM2DBL(width);
|
|
176
226
|
height_number = NUM2DBL(height);
|
|
177
227
|
|
|
228
|
+
if (!isfinite(width_number)) {
|
|
229
|
+
rb_raise(rb_eArgError, "page_size width must be finite");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!isfinite(height_number)) {
|
|
233
|
+
rb_raise(rb_eArgError, "page_size height must be finite");
|
|
234
|
+
}
|
|
235
|
+
|
|
178
236
|
if (width_number <= 0) {
|
|
179
237
|
rb_raise(rb_eArgError, "page_size width must be greater than 0");
|
|
180
238
|
}
|
|
@@ -247,6 +305,10 @@ static plutobook_page_margins_t pageprint_margins_from_value(VALUE value)
|
|
|
247
305
|
bottom_number = NUM2DBL(bottom);
|
|
248
306
|
left_number = NUM2DBL(left);
|
|
249
307
|
|
|
308
|
+
if (!isfinite(top_number) || !isfinite(right_number) || !isfinite(bottom_number) || !isfinite(left_number)) {
|
|
309
|
+
rb_raise(rb_eArgError, "margins values must be finite");
|
|
310
|
+
}
|
|
311
|
+
|
|
250
312
|
if (top_number < 0 || right_number < 0 || bottom_number < 0 || left_number < 0) {
|
|
251
313
|
rb_raise(rb_eArgError, "margins values must be greater than or equal to 0");
|
|
252
314
|
}
|
|
@@ -410,11 +472,17 @@ static void *pageprint_write_pdf_without_gvl(void *ptr)
|
|
|
410
472
|
static void *pageprint_append_pdf_string_with_gvl(void *ptr)
|
|
411
473
|
{
|
|
412
474
|
pageprint_pdf_string_append_t *append = ptr;
|
|
475
|
+
pageprint_pdf_string_output_t *output = append->owner;
|
|
413
476
|
int state = 0;
|
|
414
477
|
|
|
415
478
|
rb_protect(pageprint_append_pdf_string, (VALUE)append, &state);
|
|
416
479
|
|
|
417
|
-
|
|
480
|
+
if (state && !output->state) {
|
|
481
|
+
output->state = state;
|
|
482
|
+
output->error = rb_errinfo();
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return NULL;
|
|
418
486
|
}
|
|
419
487
|
|
|
420
488
|
static void *pageprint_write_pdf_stream_without_gvl(void *ptr)
|
|
@@ -492,24 +560,31 @@ static void *pageprint_call_resource_fetcher_with_gvl(void *ptr)
|
|
|
492
560
|
|
|
493
561
|
rb_protect(pageprint_call_resource_fetcher, (VALUE)args, &state);
|
|
494
562
|
|
|
495
|
-
|
|
563
|
+
if (state && !args->fetcher->state) {
|
|
564
|
+
args->fetcher->state = state;
|
|
565
|
+
args->fetcher->error = rb_errinfo();
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return NULL;
|
|
496
569
|
}
|
|
497
570
|
|
|
498
571
|
static plutobook_resource_data_t *pageprint_fetch_resource(void *closure, const char *url)
|
|
499
572
|
{
|
|
500
573
|
pageprint_resource_fetcher_t *fetcher = closure;
|
|
501
574
|
pageprint_resource_fetch_args_t args;
|
|
502
|
-
|
|
575
|
+
|
|
576
|
+
if (fetcher->state) {
|
|
577
|
+
return NULL;
|
|
578
|
+
}
|
|
503
579
|
|
|
504
580
|
if (!NIL_P(fetcher->object)) {
|
|
505
581
|
args.fetcher = fetcher;
|
|
506
582
|
args.url = url;
|
|
507
583
|
args.resource = NULL;
|
|
508
584
|
|
|
509
|
-
|
|
585
|
+
rb_thread_call_with_gvl(pageprint_call_resource_fetcher_with_gvl, &args);
|
|
510
586
|
|
|
511
|
-
if (state) {
|
|
512
|
-
fetcher->state = state;
|
|
587
|
+
if (fetcher->state) {
|
|
513
588
|
plutobook_set_error_message("failed to fetch URL '%s'", url);
|
|
514
589
|
return NULL;
|
|
515
590
|
}
|
|
@@ -608,15 +683,11 @@ static pageprint_options_t pageprint_options_from_value(VALUE options)
|
|
|
608
683
|
return result;
|
|
609
684
|
}
|
|
610
685
|
|
|
611
|
-
static
|
|
686
|
+
static void pageprint_create_book_from_html(VALUE html, VALUE options, pageprint_book_context_t *context)
|
|
612
687
|
{
|
|
613
688
|
pageprint_options_t pageprint_options;
|
|
614
|
-
|
|
615
|
-
const char *html_str;
|
|
616
689
|
const char *base_url_str;
|
|
617
690
|
long html_len;
|
|
618
|
-
|
|
619
|
-
plutobook_t *book;
|
|
620
691
|
pageprint_load_html_args_t load_args;
|
|
621
692
|
|
|
622
693
|
if (!RB_TYPE_P(html, T_STRING)) {
|
|
@@ -631,45 +702,49 @@ static plutobook_t *pageprint_create_book_from_html(VALUE html, VALUE options, p
|
|
|
631
702
|
rb_raise(rb_eArgError, "html is too large");
|
|
632
703
|
}
|
|
633
704
|
|
|
705
|
+
pageprint_reject_embedded_nul(html, "html");
|
|
634
706
|
pageprint_options = pageprint_options_from_value(options);
|
|
707
|
+
context->html = pageprint_frozen_string_copy(html);
|
|
708
|
+
context->base_url = Qnil;
|
|
709
|
+
context->resource_fetcher = pageprint_options.resource_fetcher;
|
|
710
|
+
context->metadata = pageprint_options.metadata;
|
|
711
|
+
|
|
712
|
+
if (!NIL_P(pageprint_options.base_url)) {
|
|
713
|
+
pageprint_reject_embedded_nul(pageprint_options.base_url, "base_url");
|
|
714
|
+
context->base_url = pageprint_frozen_string_copy(pageprint_options.base_url);
|
|
715
|
+
}
|
|
635
716
|
|
|
636
|
-
|
|
637
|
-
html_len = RSTRING_LEN(html);
|
|
717
|
+
html_len = RSTRING_LEN(context->html);
|
|
638
718
|
|
|
639
719
|
/* 1. Create */
|
|
640
|
-
book = plutobook_create(pageprint_options.page_size, pageprint_options.margins, pageprint_options.media);
|
|
720
|
+
context->book = plutobook_create(pageprint_options.page_size, pageprint_options.margins, pageprint_options.media);
|
|
641
721
|
|
|
642
|
-
if (!book) {
|
|
722
|
+
if (!context->book) {
|
|
643
723
|
rb_raise(rb_eRuntimeError, "failed to create plutobook");
|
|
644
724
|
}
|
|
645
725
|
|
|
646
|
-
|
|
647
|
-
|
|
726
|
+
context->resource_fetcher_state.object = context->resource_fetcher;
|
|
727
|
+
context->resource_fetcher_state.error = Qnil;
|
|
728
|
+
context->resource_fetcher_state.state = 0;
|
|
648
729
|
|
|
649
|
-
plutobook_set_custom_resource_fetcher(book, pageprint_fetch_resource,
|
|
730
|
+
plutobook_set_custom_resource_fetcher(context->book, pageprint_fetch_resource, &context->resource_fetcher_state);
|
|
650
731
|
|
|
651
732
|
/* 2. Load HTML */
|
|
652
733
|
plutobook_clear_error_message();
|
|
653
734
|
|
|
654
735
|
base_url_str = "";
|
|
655
|
-
if (!NIL_P(
|
|
656
|
-
base_url_str =
|
|
736
|
+
if (!NIL_P(context->base_url)) {
|
|
737
|
+
base_url_str = RSTRING_PTR(context->base_url);
|
|
657
738
|
}
|
|
658
739
|
|
|
659
|
-
load_args.book = book;
|
|
660
|
-
load_args.html =
|
|
740
|
+
load_args.book = context->book;
|
|
741
|
+
load_args.html = RSTRING_PTR(context->html);
|
|
661
742
|
load_args.length = (int)html_len;
|
|
662
743
|
load_args.user_style = "";
|
|
663
744
|
load_args.user_script = "";
|
|
664
745
|
load_args.base_url = base_url_str;
|
|
665
746
|
load_args.ok = 0;
|
|
666
747
|
|
|
667
|
-
RB_GC_GUARD(html);
|
|
668
|
-
RB_GC_GUARD(options);
|
|
669
|
-
RB_GC_GUARD(pageprint_options.base_url);
|
|
670
|
-
RB_GC_GUARD(pageprint_options.resource_fetcher);
|
|
671
|
-
RB_GC_GUARD(pageprint_options.metadata);
|
|
672
|
-
|
|
673
748
|
rb_thread_call_without_gvl(
|
|
674
749
|
pageprint_load_html_without_gvl,
|
|
675
750
|
&load_args,
|
|
@@ -677,37 +752,41 @@ static plutobook_t *pageprint_create_book_from_html(VALUE html, VALUE options, p
|
|
|
677
752
|
NULL
|
|
678
753
|
);
|
|
679
754
|
|
|
680
|
-
|
|
681
|
-
|
|
755
|
+
RB_GC_GUARD(context->html);
|
|
756
|
+
RB_GC_GUARD(context->base_url);
|
|
682
757
|
|
|
683
|
-
|
|
684
|
-
|
|
758
|
+
if (!load_args.ok) {
|
|
759
|
+
if (context->resource_fetcher_state.state) {
|
|
760
|
+
pageprint_jump_callback_error(
|
|
761
|
+
context->resource_fetcher_state.state,
|
|
762
|
+
context->resource_fetcher_state.error
|
|
763
|
+
);
|
|
685
764
|
}
|
|
686
765
|
|
|
687
766
|
pageprint_raise_plutobook_error(rb_eRuntimeError, "failed to load HTML into plutobook");
|
|
688
767
|
}
|
|
689
768
|
|
|
690
|
-
if (
|
|
691
|
-
|
|
692
|
-
|
|
769
|
+
if (context->resource_fetcher_state.state) {
|
|
770
|
+
pageprint_jump_callback_error(
|
|
771
|
+
context->resource_fetcher_state.state,
|
|
772
|
+
context->resource_fetcher_state.error
|
|
773
|
+
);
|
|
693
774
|
}
|
|
694
775
|
|
|
695
776
|
{
|
|
696
777
|
pageprint_apply_metadata_args_t metadata_args;
|
|
697
778
|
int metadata_state = 0;
|
|
698
779
|
|
|
699
|
-
metadata_args.book = book;
|
|
700
|
-
metadata_args.metadata =
|
|
780
|
+
metadata_args.book = context->book;
|
|
781
|
+
metadata_args.metadata = context->metadata;
|
|
701
782
|
|
|
702
783
|
rb_protect(pageprint_apply_metadata_with_gvl, (VALUE)&metadata_args, &metadata_state);
|
|
784
|
+
RB_GC_GUARD(context->metadata);
|
|
703
785
|
|
|
704
786
|
if (metadata_state) {
|
|
705
|
-
plutobook_destroy(book);
|
|
706
787
|
rb_jump_tag(metadata_state);
|
|
707
788
|
}
|
|
708
789
|
}
|
|
709
|
-
|
|
710
|
-
return book;
|
|
711
790
|
}
|
|
712
791
|
|
|
713
792
|
static VALUE pageprint_append_pdf_string(VALUE value)
|
|
@@ -723,62 +802,54 @@ static plutobook_stream_status_t pageprint_write_pdf_string(void *closure, const
|
|
|
723
802
|
{
|
|
724
803
|
pageprint_pdf_string_output_t *output = closure;
|
|
725
804
|
pageprint_pdf_string_append_t append;
|
|
726
|
-
int state;
|
|
727
805
|
|
|
806
|
+
if (output->state) {
|
|
807
|
+
return PLUTOBOOK_STREAM_STATUS_WRITE_ERROR;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
append.owner = output;
|
|
728
811
|
append.output = output->output;
|
|
729
812
|
append.data = data;
|
|
730
813
|
append.length = length;
|
|
731
814
|
|
|
732
|
-
|
|
815
|
+
rb_thread_call_with_gvl(pageprint_append_pdf_string_with_gvl, &append);
|
|
733
816
|
|
|
734
|
-
if (state) {
|
|
735
|
-
output->state = state;
|
|
817
|
+
if (output->state) {
|
|
736
818
|
return PLUTOBOOK_STREAM_STATUS_WRITE_ERROR;
|
|
737
819
|
}
|
|
738
820
|
|
|
739
821
|
return PLUTOBOOK_STREAM_STATUS_SUCCESS;
|
|
740
822
|
}
|
|
741
823
|
|
|
742
|
-
static VALUE
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
const char *path_str;
|
|
748
|
-
|
|
749
|
-
plutobook_t *book;
|
|
750
|
-
pageprint_resource_fetcher_t resource_fetcher;
|
|
751
|
-
pageprint_write_pdf_args_t write_args;
|
|
752
|
-
|
|
753
|
-
rb_check_arity(argc, 2, 3);
|
|
824
|
+
static VALUE pageprint_destroy_book(VALUE value)
|
|
825
|
+
{
|
|
826
|
+
pageprint_book_context_t *context = (pageprint_book_context_t *)value;
|
|
827
|
+
plutobook_t *book = context->book;
|
|
754
828
|
|
|
755
|
-
|
|
756
|
-
path = argv[1];
|
|
757
|
-
options = argc == 3 ? argv[2] : Qnil;
|
|
829
|
+
context->book = NULL;
|
|
758
830
|
|
|
759
|
-
if (
|
|
760
|
-
|
|
831
|
+
if (book) {
|
|
832
|
+
plutobook_destroy(book);
|
|
761
833
|
}
|
|
762
834
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
}
|
|
835
|
+
return Qnil;
|
|
836
|
+
}
|
|
766
837
|
|
|
767
|
-
|
|
838
|
+
static VALUE pageprint_render_to_file_body(VALUE value)
|
|
839
|
+
{
|
|
840
|
+
pageprint_render_to_file_context_t *context = (pageprint_render_to_file_context_t *)value;
|
|
841
|
+
const char *path_str;
|
|
842
|
+
pageprint_write_pdf_args_t write_args;
|
|
768
843
|
|
|
769
|
-
|
|
844
|
+
pageprint_create_book_from_html(context->html, context->options, &context->book_context);
|
|
845
|
+
path_str = RSTRING_PTR(context->path);
|
|
770
846
|
|
|
771
847
|
plutobook_clear_error_message();
|
|
772
848
|
|
|
773
|
-
write_args.book = book;
|
|
849
|
+
write_args.book = context->book_context.book;
|
|
774
850
|
write_args.path = path_str;
|
|
775
851
|
write_args.ok = 0;
|
|
776
852
|
|
|
777
|
-
RB_GC_GUARD(html);
|
|
778
|
-
RB_GC_GUARD(options);
|
|
779
|
-
RB_GC_GUARD(resource_fetcher.object);
|
|
780
|
-
RB_GC_GUARD(path);
|
|
781
|
-
|
|
782
853
|
rb_thread_call_without_gvl(
|
|
783
854
|
pageprint_write_pdf_without_gvl,
|
|
784
855
|
&write_args,
|
|
@@ -786,10 +857,17 @@ static VALUE pageprint_render_to_file(int argc, VALUE *argv, VALUE self) {
|
|
|
786
857
|
NULL
|
|
787
858
|
);
|
|
788
859
|
|
|
789
|
-
|
|
860
|
+
RB_GC_GUARD(context->path);
|
|
861
|
+
RB_GC_GUARD(context->book_context.resource_fetcher);
|
|
862
|
+
RB_GC_GUARD(context->book_context.resource_fetcher_state.error);
|
|
863
|
+
RB_GC_GUARD(context->html);
|
|
864
|
+
RB_GC_GUARD(context->options);
|
|
790
865
|
|
|
791
|
-
if (
|
|
792
|
-
|
|
866
|
+
if (context->book_context.resource_fetcher_state.state) {
|
|
867
|
+
pageprint_jump_callback_error(
|
|
868
|
+
context->book_context.resource_fetcher_state.state,
|
|
869
|
+
context->book_context.resource_fetcher_state.error
|
|
870
|
+
);
|
|
793
871
|
}
|
|
794
872
|
|
|
795
873
|
if (!write_args.ok) {
|
|
@@ -799,36 +877,42 @@ static VALUE pageprint_render_to_file(int argc, VALUE *argv, VALUE self) {
|
|
|
799
877
|
return Qtrue;
|
|
800
878
|
}
|
|
801
879
|
|
|
802
|
-
static VALUE
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
pageprint_pdf_string_output_t output;
|
|
880
|
+
static VALUE pageprint_render_to_file(int argc, VALUE *argv, VALUE self)
|
|
881
|
+
{
|
|
882
|
+
pageprint_render_to_file_context_t context = { 0 };
|
|
806
883
|
|
|
807
|
-
|
|
808
|
-
pageprint_resource_fetcher_t resource_fetcher;
|
|
809
|
-
pageprint_write_pdf_stream_args_t write_args;
|
|
884
|
+
rb_check_arity(argc, 2, 3);
|
|
810
885
|
|
|
811
|
-
|
|
886
|
+
context.html = argv[0];
|
|
887
|
+
context.options = argc == 3 ? argv[2] : Qnil;
|
|
888
|
+
|
|
889
|
+
if (!RB_TYPE_P(argv[1], T_STRING)) {
|
|
890
|
+
rb_raise(rb_eTypeError, "path must be a String");
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
if (RSTRING_LEN(argv[1]) == 0) {
|
|
894
|
+
rb_raise(rb_eArgError, "path must not be empty");
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
pageprint_reject_embedded_nul(argv[1], "path");
|
|
898
|
+
context.path = pageprint_frozen_string_copy(argv[1]);
|
|
812
899
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
900
|
+
return rb_ensure(pageprint_render_to_file_body, (VALUE)&context, pageprint_destroy_book, (VALUE)&context.book_context);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
static VALUE pageprint_render_body(VALUE value)
|
|
904
|
+
{
|
|
905
|
+
pageprint_render_context_t *context = (pageprint_render_context_t *)value;
|
|
906
|
+
pageprint_write_pdf_stream_args_t write_args;
|
|
818
907
|
|
|
819
|
-
|
|
908
|
+
pageprint_create_book_from_html(context->html, context->options, &context->book_context);
|
|
820
909
|
|
|
821
910
|
plutobook_clear_error_message();
|
|
822
911
|
|
|
823
|
-
write_args.book = book;
|
|
824
|
-
write_args.output = &output;
|
|
912
|
+
write_args.book = context->book_context.book;
|
|
913
|
+
write_args.output = &context->output;
|
|
825
914
|
write_args.ok = 0;
|
|
826
915
|
|
|
827
|
-
RB_GC_GUARD(html);
|
|
828
|
-
RB_GC_GUARD(options);
|
|
829
|
-
RB_GC_GUARD(resource_fetcher.object);
|
|
830
|
-
RB_GC_GUARD(output.output);
|
|
831
|
-
|
|
832
916
|
rb_thread_call_without_gvl(
|
|
833
917
|
pageprint_write_pdf_stream_without_gvl,
|
|
834
918
|
&write_args,
|
|
@@ -836,21 +920,45 @@ static VALUE pageprint_render(int argc, VALUE *argv, VALUE self) {
|
|
|
836
920
|
NULL
|
|
837
921
|
);
|
|
838
922
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
923
|
+
RB_GC_GUARD(context->book_context.resource_fetcher);
|
|
924
|
+
RB_GC_GUARD(context->book_context.resource_fetcher_state.error);
|
|
925
|
+
RB_GC_GUARD(context->output.output);
|
|
926
|
+
RB_GC_GUARD(context->output.error);
|
|
927
|
+
RB_GC_GUARD(context->html);
|
|
928
|
+
RB_GC_GUARD(context->options);
|
|
929
|
+
|
|
930
|
+
if (context->book_context.resource_fetcher_state.state) {
|
|
931
|
+
pageprint_jump_callback_error(
|
|
932
|
+
context->book_context.resource_fetcher_state.state,
|
|
933
|
+
context->book_context.resource_fetcher_state.error
|
|
934
|
+
);
|
|
843
935
|
}
|
|
844
936
|
|
|
845
|
-
if (output.state) {
|
|
846
|
-
|
|
937
|
+
if (context->output.state) {
|
|
938
|
+
pageprint_jump_callback_error(context->output.state, context->output.error);
|
|
847
939
|
}
|
|
848
940
|
|
|
849
941
|
if (!write_args.ok) {
|
|
850
942
|
pageprint_raise_plutobook_error(rb_eRuntimeError, "failed to write PDF to string");
|
|
851
943
|
}
|
|
852
944
|
|
|
853
|
-
return output.output;
|
|
945
|
+
return context->output.output;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
static VALUE pageprint_render(int argc, VALUE *argv, VALUE self)
|
|
949
|
+
{
|
|
950
|
+
pageprint_render_context_t context = { 0 };
|
|
951
|
+
|
|
952
|
+
rb_check_arity(argc, 1, 2);
|
|
953
|
+
|
|
954
|
+
context.html = argv[0];
|
|
955
|
+
context.options = argc == 2 ? argv[1] : Qnil;
|
|
956
|
+
context.output.output = rb_str_new(NULL, 0);
|
|
957
|
+
context.output.error = Qnil;
|
|
958
|
+
context.output.state = 0;
|
|
959
|
+
rb_enc_associate_index(context.output.output, rb_ascii8bit_encindex());
|
|
960
|
+
|
|
961
|
+
return rb_ensure(pageprint_render_body, (VALUE)&context, pageprint_destroy_book, (VALUE)&context.book_context);
|
|
854
962
|
}
|
|
855
963
|
|
|
856
964
|
void Init_page_print(void) {
|
|
@@ -888,6 +996,6 @@ void Init_page_print(void) {
|
|
|
888
996
|
id_mm = rb_intern_const("mm");
|
|
889
997
|
id_px = rb_intern_const("px");
|
|
890
998
|
|
|
891
|
-
rb_define_singleton_method(mPagePrint, "render_to_file",
|
|
892
|
-
rb_define_singleton_method(mPagePrint, "render",
|
|
999
|
+
rb_define_singleton_method(mPagePrint, "render_to_file", pageprint_render_to_file, -1);
|
|
1000
|
+
rb_define_singleton_method(mPagePrint, "render", pageprint_render, -1);
|
|
893
1001
|
}
|