ironpress 1.5.2 → 1.5.3
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 +40 -17
- data/ext/ironpress/Cargo.toml +17 -0
- data/ext/ironpress/extconf.rb +5 -1
- data/ext/ironpress/src/lib.rs +238 -0
- data/lib/ironpress/version.rb +5 -0
- data/lib/ironpress.rb +68 -2
- metadata +8 -6
- data/Cargo.toml +0 -13
- data/src/lib.rs +0 -387
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cfcd5dc3040d841312793e77a4119d49c14db383e9dcc7970182159c1989e206
|
|
4
|
+
data.tar.gz: e25587f9d4d4569b59cdff1da629d342bee5fc3b5e2c16832a0c5bea2809289c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1b608a05c7b569eb4282776226568d01a314f36f61d530a3db3c367622d83a11e16c1d3b915c653a9b9915bd13f99ec9fc6c0f51d88366e8b06a7bed07c6db2f
|
|
7
|
+
data.tar.gz: c9b9d71b037da3b8df4086e076b968075e34a10f03d56dbbc0263a6d9cdd473e3012b73320e548ea99599678c762385bececfae69d2969440cfff21425e5f99e
|
data/README.md
CHANGED
|
@@ -1,35 +1,58 @@
|
|
|
1
|
-
# Ironpress
|
|
1
|
+
# Ironpress for Ruby
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Generate PDF bytes from HTML or Markdown without launching a browser.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
```ruby
|
|
8
|
-
gem install ironpress
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Or in your Gemfile:
|
|
12
|
-
|
|
13
7
|
```ruby
|
|
14
8
|
gem "ironpress"
|
|
15
9
|
```
|
|
16
10
|
|
|
11
|
+
Ironpress supports Ruby 3.0 and later. Releases include a source gem and native
|
|
12
|
+
gems for Linux, macOS, and Windows.
|
|
13
|
+
|
|
17
14
|
## Usage
|
|
18
15
|
|
|
19
16
|
```ruby
|
|
20
17
|
require "ironpress"
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
pdf = Ironpress.html_to_pdf("<h1>Hello World</h1><p>Generated with Ironpress.</p>")
|
|
19
|
+
pdf = Ironpress.html_to_pdf("<h1>Hello</h1>")
|
|
24
20
|
File.binwrite("output.pdf", pdf)
|
|
21
|
+
```
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
Configuration methods return the converter, so policies can be composed in an
|
|
24
|
+
idiomatic chain:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
converter = Ironpress::HtmlConverter.new
|
|
28
|
+
.page_size("Letter")
|
|
29
|
+
.margin_sides(36, 54, 36, 54)
|
|
30
|
+
.header("Quarterly report")
|
|
31
|
+
.footer("Page {page} of {pages}")
|
|
32
|
+
.sanitize(true)
|
|
33
|
+
|
|
34
|
+
pdf = converter.convert("<h1>Results</h1>")
|
|
29
35
|
```
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
The converter supports page geometry, PDF and image quality, sanitization,
|
|
38
|
+
headers and footers, custom TTF fonts, and optional CJK or emoji packs. Native
|
|
39
|
+
Ruby also supports constrained local resources through `base_path` and
|
|
40
|
+
`resource_root`, plus direct file output.
|
|
41
|
+
|
|
42
|
+
See the [binding capability matrix](../README.md) for the contract shared with
|
|
43
|
+
Rust, Python, and WebAssembly.
|
|
44
|
+
|
|
45
|
+
## Font packs
|
|
46
|
+
|
|
47
|
+
Ironpress never downloads fallback fonts while rendering. Download the artifact
|
|
48
|
+
your application needs, verify it as part of deployment, then install its bytes:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
converter = Ironpress::HtmlConverter.new.add_font_pack(
|
|
52
|
+
"cjk-jp",
|
|
53
|
+
File.binread("ironpress-font-cjk-jp.ttf")
|
|
54
|
+
)
|
|
55
|
+
pdf = converter.convert("<p lang='ja'>日本語</p>")
|
|
56
|
+
```
|
|
32
57
|
|
|
33
|
-
-
|
|
34
|
-
- **~5MB** native extension, no runtime dependencies
|
|
35
|
-
- Instant startup, no browser process
|
|
58
|
+
Valid pack names are `cjk-jp`, `cjk-kr`, `cjk-sc`, `cjk-tc`, and `emoji`.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "ironpress-ruby"
|
|
3
|
+
version = "1.5.3"
|
|
4
|
+
edition = "2024"
|
|
5
|
+
publish = false
|
|
6
|
+
description = "Ruby bindings for the Ironpress HTML-to-PDF engine"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
repository = "https://github.com/gastongouron/ironpress"
|
|
9
|
+
|
|
10
|
+
[lib]
|
|
11
|
+
name = "ironpress_ruby"
|
|
12
|
+
crate-type = ["cdylib"]
|
|
13
|
+
|
|
14
|
+
[dependencies]
|
|
15
|
+
ironpress-core = { version = "=1.5.3", package = "ironpress" }
|
|
16
|
+
magnus = "0.7"
|
|
17
|
+
rb-sys = { version = "0.9", default-features = false, features = ["stable-api-compiled-fallback"] }
|
data/ext/ironpress/extconf.rb
CHANGED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use magnus::{Error, RString, Ruby, class, define_module, function, method, prelude::*};
|
|
4
|
+
|
|
5
|
+
fn html_to_pdf(ruby: &Ruby, html: String) -> Result<RString, Error> {
|
|
6
|
+
let pdf = ironpress_core::html_to_pdf(&html)
|
|
7
|
+
.map_err(|error| Error::new(ruby.exception_runtime_error(), error.to_string()))?;
|
|
8
|
+
Ok(ruby.str_from_slice(&pdf))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
fn markdown_to_pdf(ruby: &Ruby, markdown: String) -> Result<RString, Error> {
|
|
12
|
+
let pdf = ironpress_core::markdown_to_pdf(&markdown)
|
|
13
|
+
.map_err(|error| Error::new(ruby.exception_runtime_error(), error.to_string()))?;
|
|
14
|
+
Ok(ruby.str_from_slice(&pdf))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[magnus::wrap(class = "Ironpress::NativeConverter", free_immediately, size)]
|
|
18
|
+
struct NativeConverter {
|
|
19
|
+
converter: ironpress_core::HtmlConverter,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl NativeConverter {
|
|
23
|
+
fn new() -> Self {
|
|
24
|
+
Self {
|
|
25
|
+
converter: ironpress_core::HtmlConverter::new(),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fn configured(
|
|
30
|
+
&self,
|
|
31
|
+
configure: impl FnOnce(ironpress_core::HtmlConverter) -> ironpress_core::HtmlConverter,
|
|
32
|
+
) -> Self {
|
|
33
|
+
Self {
|
|
34
|
+
converter: configure(self.converter.clone()),
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fn page_size(ruby: &Ruby, this: &Self, name: String) -> Result<Self, Error> {
|
|
39
|
+
let size = match name.to_ascii_lowercase().as_str() {
|
|
40
|
+
"a4" => ironpress_core::PageSize::A4,
|
|
41
|
+
"letter" => ironpress_core::PageSize::LETTER,
|
|
42
|
+
"legal" => ironpress_core::PageSize::LEGAL,
|
|
43
|
+
_ => {
|
|
44
|
+
return Err(Error::new(
|
|
45
|
+
ruby.exception_arg_error(),
|
|
46
|
+
format!("unknown page size `{name}`"),
|
|
47
|
+
));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
Ok(this.configured(|converter| converter.page_size(size)))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn page_size_custom(&self, width: f64, height: f64) -> Self {
|
|
54
|
+
self.configured(|converter| {
|
|
55
|
+
converter.page_size(ironpress_core::PageSize::new(width as f32, height as f32))
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fn margin(&self, points: f64) -> Self {
|
|
60
|
+
self.configured(|converter| {
|
|
61
|
+
converter.margin(ironpress_core::Margin::uniform(points as f32))
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn margin_sides(&self, top: f64, right: f64, bottom: f64, left: f64) -> Self {
|
|
66
|
+
self.configured(|converter| {
|
|
67
|
+
converter.margin(ironpress_core::Margin::new(
|
|
68
|
+
top as f32,
|
|
69
|
+
right as f32,
|
|
70
|
+
bottom as f32,
|
|
71
|
+
left as f32,
|
|
72
|
+
))
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fn compress(&self, enabled: bool) -> Self {
|
|
77
|
+
self.configured(|converter| converter.compress(enabled))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn jpeg_quality(&self, quality: u8) -> Self {
|
|
81
|
+
self.configured(|converter| converter.jpeg_quality(quality))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn auto_resize_images(&self, enabled: bool) -> Self {
|
|
85
|
+
self.configured(|converter| converter.auto_resize_images(enabled))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fn image_dpi(&self, dpi: f64) -> Self {
|
|
89
|
+
self.configured(|converter| converter.image_dpi(dpi as f32))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fn filter_dpi(&self, dpi: f64) -> Self {
|
|
93
|
+
self.configured(|converter| converter.filter_dpi(dpi as f32))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fn mask_dpi(&self, dpi: f64) -> Self {
|
|
97
|
+
self.configured(|converter| converter.mask_dpi(dpi as f32))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fn background_raster_dpi(&self, dpi: f64) -> Self {
|
|
101
|
+
self.configured(|converter| converter.background_raster_dpi(dpi as f32))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fn occlusion_cull(&self, enabled: bool) -> Self {
|
|
105
|
+
self.configured(|converter| converter.occlusion_cull(enabled))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fn sanitize(&self, enabled: bool) -> Self {
|
|
109
|
+
self.configured(|converter| converter.sanitize(enabled))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn add_font(&self, name: String, data: RString) -> Self {
|
|
113
|
+
// SAFETY: `data` remains rooted for this call and is copied before returning.
|
|
114
|
+
let bytes = unsafe { data.as_slice() }.to_vec();
|
|
115
|
+
self.configured(|converter| converter.add_font(&name, bytes))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fn add_font_pack(ruby: &Ruby, this: &Self, kind: String, data: RString) -> Result<Self, Error> {
|
|
119
|
+
let kind = kind
|
|
120
|
+
.parse::<ironpress_core::FontPackKind>()
|
|
121
|
+
.map_err(|error| Error::new(ruby.exception_arg_error(), error.to_string()))?;
|
|
122
|
+
// SAFETY: `data` remains rooted for this call and is copied before returning.
|
|
123
|
+
let bytes = unsafe { data.as_slice() }.to_vec();
|
|
124
|
+
let pack = ironpress_core::FontPack::parse(kind, bytes)
|
|
125
|
+
.map_err(|error| Error::new(ruby.exception_arg_error(), error.to_string()))?;
|
|
126
|
+
Ok(this.configured(|converter| converter.add_font_pack(pack)))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fn base_path(&self, path: String) -> Self {
|
|
130
|
+
self.configured(|converter| converter.base_path(Path::new(&path)))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
fn resource_root(&self, path: String) -> Self {
|
|
134
|
+
self.configured(|converter| converter.resource_root(Path::new(&path)))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fn header(&self, text: String) -> Self {
|
|
138
|
+
self.configured(|converter| converter.header(text))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
fn footer(&self, text: String) -> Self {
|
|
142
|
+
self.configured(|converter| converter.footer(text))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
fn convert(ruby: &Ruby, this: &Self, html: String) -> Result<RString, Error> {
|
|
146
|
+
let pdf = this
|
|
147
|
+
.converter
|
|
148
|
+
.convert(&html)
|
|
149
|
+
.map_err(|error| Error::new(ruby.exception_runtime_error(), error.to_string()))?;
|
|
150
|
+
Ok(ruby.str_from_slice(&pdf))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
fn convert_markdown(ruby: &Ruby, this: &Self, markdown: String) -> Result<RString, Error> {
|
|
154
|
+
let pdf = this
|
|
155
|
+
.converter
|
|
156
|
+
.convert_markdown(&markdown)
|
|
157
|
+
.map_err(|error| Error::new(ruby.exception_runtime_error(), error.to_string()))?;
|
|
158
|
+
Ok(ruby.str_from_slice(&pdf))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
fn convert_to_file(ruby: &Ruby, this: &Self, html: String, path: String) -> Result<(), Error> {
|
|
162
|
+
let pdf = this
|
|
163
|
+
.converter
|
|
164
|
+
.convert(&html)
|
|
165
|
+
.map_err(|error| Error::new(ruby.exception_runtime_error(), error.to_string()))?;
|
|
166
|
+
std::fs::write(path, pdf)
|
|
167
|
+
.map_err(|error| Error::new(ruby.exception_runtime_error(), error.to_string()))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
fn convert_markdown_to_file(
|
|
171
|
+
ruby: &Ruby,
|
|
172
|
+
this: &Self,
|
|
173
|
+
markdown: String,
|
|
174
|
+
path: String,
|
|
175
|
+
) -> Result<(), Error> {
|
|
176
|
+
let pdf = this
|
|
177
|
+
.converter
|
|
178
|
+
.convert_markdown(&markdown)
|
|
179
|
+
.map_err(|error| Error::new(ruby.exception_runtime_error(), error.to_string()))?;
|
|
180
|
+
std::fs::write(path, pdf)
|
|
181
|
+
.map_err(|error| Error::new(ruby.exception_runtime_error(), error.to_string()))
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#[magnus::init]
|
|
186
|
+
fn init(_ruby: &Ruby) -> Result<(), Error> {
|
|
187
|
+
let module = define_module("Ironpress")?;
|
|
188
|
+
module.define_singleton_method("html_to_pdf", function!(html_to_pdf, 1))?;
|
|
189
|
+
module.define_singleton_method("markdown_to_pdf", function!(markdown_to_pdf, 1))?;
|
|
190
|
+
|
|
191
|
+
let class = module.define_class("NativeConverter", class::object())?;
|
|
192
|
+
class.define_singleton_method("new", function!(NativeConverter::new, 0))?;
|
|
193
|
+
class.define_method("page_size", method!(NativeConverter::page_size, 1))?;
|
|
194
|
+
class.define_method(
|
|
195
|
+
"page_size_custom",
|
|
196
|
+
method!(NativeConverter::page_size_custom, 2),
|
|
197
|
+
)?;
|
|
198
|
+
class.define_method("margin", method!(NativeConverter::margin, 1))?;
|
|
199
|
+
class.define_method("margin_sides", method!(NativeConverter::margin_sides, 4))?;
|
|
200
|
+
class.define_method("compress", method!(NativeConverter::compress, 1))?;
|
|
201
|
+
class.define_method("jpeg_quality", method!(NativeConverter::jpeg_quality, 1))?;
|
|
202
|
+
class.define_method(
|
|
203
|
+
"auto_resize_images",
|
|
204
|
+
method!(NativeConverter::auto_resize_images, 1),
|
|
205
|
+
)?;
|
|
206
|
+
class.define_method("image_dpi", method!(NativeConverter::image_dpi, 1))?;
|
|
207
|
+
class.define_method("filter_dpi", method!(NativeConverter::filter_dpi, 1))?;
|
|
208
|
+
class.define_method("mask_dpi", method!(NativeConverter::mask_dpi, 1))?;
|
|
209
|
+
class.define_method(
|
|
210
|
+
"background_raster_dpi",
|
|
211
|
+
method!(NativeConverter::background_raster_dpi, 1),
|
|
212
|
+
)?;
|
|
213
|
+
class.define_method(
|
|
214
|
+
"occlusion_cull",
|
|
215
|
+
method!(NativeConverter::occlusion_cull, 1),
|
|
216
|
+
)?;
|
|
217
|
+
class.define_method("sanitize", method!(NativeConverter::sanitize, 1))?;
|
|
218
|
+
class.define_method("add_font", method!(NativeConverter::add_font, 2))?;
|
|
219
|
+
class.define_method("add_font_pack", method!(NativeConverter::add_font_pack, 2))?;
|
|
220
|
+
class.define_method("base_path", method!(NativeConverter::base_path, 1))?;
|
|
221
|
+
class.define_method("resource_root", method!(NativeConverter::resource_root, 1))?;
|
|
222
|
+
class.define_method("header", method!(NativeConverter::header, 1))?;
|
|
223
|
+
class.define_method("footer", method!(NativeConverter::footer, 1))?;
|
|
224
|
+
class.define_method("convert", method!(NativeConverter::convert, 1))?;
|
|
225
|
+
class.define_method(
|
|
226
|
+
"convert_markdown",
|
|
227
|
+
method!(NativeConverter::convert_markdown, 1),
|
|
228
|
+
)?;
|
|
229
|
+
class.define_method(
|
|
230
|
+
"convert_to_file",
|
|
231
|
+
method!(NativeConverter::convert_to_file, 2),
|
|
232
|
+
)?;
|
|
233
|
+
class.define_method(
|
|
234
|
+
"convert_markdown_to_file",
|
|
235
|
+
method!(NativeConverter::convert_markdown_to_file, 2),
|
|
236
|
+
)?;
|
|
237
|
+
Ok(())
|
|
238
|
+
}
|
data/lib/ironpress.rb
CHANGED
|
@@ -1,7 +1,73 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require "rbconfig"
|
|
4
|
+
require_relative "ironpress/version"
|
|
5
|
+
|
|
6
|
+
ruby_api_version = RUBY_VERSION.split(".").first(2).join(".")
|
|
7
|
+
versioned_extension = File.join(__dir__, "ironpress", ruby_api_version, "ironpress_ruby")
|
|
8
|
+
source_extension = File.join(__dir__, "ironpress", "ironpress_ruby")
|
|
9
|
+
extension_suffix = RbConfig::CONFIG.fetch("DLEXT")
|
|
10
|
+
extension = if File.file?("#{versioned_extension}.#{extension_suffix}")
|
|
11
|
+
versioned_extension
|
|
12
|
+
else
|
|
13
|
+
source_extension
|
|
14
|
+
end
|
|
15
|
+
require extension
|
|
4
16
|
|
|
5
17
|
module Ironpress
|
|
6
|
-
|
|
18
|
+
# Mutable Ruby facade over immutable native converter values.
|
|
19
|
+
class HtmlConverter
|
|
20
|
+
CONFIGURATION_METHODS = %i[
|
|
21
|
+
page_size
|
|
22
|
+
page_size_custom
|
|
23
|
+
margin
|
|
24
|
+
margin_sides
|
|
25
|
+
compress
|
|
26
|
+
jpeg_quality
|
|
27
|
+
auto_resize_images
|
|
28
|
+
image_dpi
|
|
29
|
+
filter_dpi
|
|
30
|
+
mask_dpi
|
|
31
|
+
background_raster_dpi
|
|
32
|
+
occlusion_cull
|
|
33
|
+
sanitize
|
|
34
|
+
add_font
|
|
35
|
+
add_font_pack
|
|
36
|
+
base_path
|
|
37
|
+
resource_root
|
|
38
|
+
header
|
|
39
|
+
footer
|
|
40
|
+
].freeze
|
|
41
|
+
|
|
42
|
+
def initialize
|
|
43
|
+
@native = NativeConverter.new
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
CONFIGURATION_METHODS.each do |name|
|
|
47
|
+
define_method(name) do |*arguments|
|
|
48
|
+
@native = @native.public_send(name, *arguments)
|
|
49
|
+
self
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def convert(html)
|
|
54
|
+
@native.convert(html)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def convert_markdown(markdown)
|
|
58
|
+
@native.convert_markdown(markdown)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def convert_to_file(html, path)
|
|
62
|
+
@native.convert_to_file(html, path)
|
|
63
|
+
self
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def convert_markdown_to_file(markdown, path)
|
|
67
|
+
@native.convert_markdown_to_file(markdown, path)
|
|
68
|
+
self
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private_constant :NativeConverter
|
|
7
73
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ironpress
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.5.
|
|
4
|
+
version: 1.5.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Paul Gaston Gouron
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|
|
@@ -24,19 +24,21 @@ dependencies:
|
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0.9'
|
|
27
|
-
description:
|
|
28
|
-
|
|
27
|
+
description: |-
|
|
28
|
+
Convert HTML, CSS, and Markdown to PDF with no browser or system
|
|
29
|
+
dependencies. Native Rust extension for maximum performance.
|
|
29
30
|
email:
|
|
30
31
|
executables: []
|
|
31
32
|
extensions:
|
|
32
33
|
- ext/ironpress/extconf.rb
|
|
33
34
|
extra_rdoc_files: []
|
|
34
35
|
files:
|
|
35
|
-
- Cargo.toml
|
|
36
36
|
- README.md
|
|
37
|
+
- ext/ironpress/Cargo.toml
|
|
37
38
|
- ext/ironpress/extconf.rb
|
|
39
|
+
- ext/ironpress/src/lib.rs
|
|
38
40
|
- lib/ironpress.rb
|
|
39
|
-
-
|
|
41
|
+
- lib/ironpress/version.rb
|
|
40
42
|
homepage: https://github.com/gastongouron/ironpress
|
|
41
43
|
licenses:
|
|
42
44
|
- MIT
|
data/Cargo.toml
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
[package]
|
|
2
|
-
name = "ironpress-ruby"
|
|
3
|
-
version = "1.5.2"
|
|
4
|
-
edition = "2024"
|
|
5
|
-
publish = false
|
|
6
|
-
|
|
7
|
-
[lib]
|
|
8
|
-
name = "ironpress_ruby"
|
|
9
|
-
crate-type = ["cdylib"]
|
|
10
|
-
|
|
11
|
-
[dependencies]
|
|
12
|
-
ironpress-core = { path = "../..", package = "ironpress" }
|
|
13
|
-
magnus = "0.7"
|
data/src/lib.rs
DELETED
|
@@ -1,387 +0,0 @@
|
|
|
1
|
-
use std::cell::RefCell;
|
|
2
|
-
use std::path::Path;
|
|
3
|
-
|
|
4
|
-
use magnus::{
|
|
5
|
-
Error, IntoValue, RString, Ruby, Value,
|
|
6
|
-
class, define_module, function, method,
|
|
7
|
-
prelude::*,
|
|
8
|
-
typed_data,
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
fn html_to_pdf(ruby: &Ruby, html: String) -> Result<RString, Error> {
|
|
12
|
-
let pdf = ironpress_core::html_to_pdf(&html)
|
|
13
|
-
.map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
|
|
14
|
-
Ok(ruby.str_from_slice(&pdf))
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
fn markdown_to_pdf(ruby: &Ruby, markdown: String) -> Result<RString, Error> {
|
|
18
|
-
let pdf = ironpress_core::markdown_to_pdf(&markdown)
|
|
19
|
-
.map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
|
|
20
|
-
Ok(ruby.str_from_slice(&pdf))
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// Inner state for the builder -- stored inside a RefCell so magnus method
|
|
25
|
-
// receivers (&self) can mutate it.
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
struct ConverterInner {
|
|
29
|
-
page_size: ironpress_core::types::PageSize,
|
|
30
|
-
margin: ironpress_core::types::Margin,
|
|
31
|
-
compress: Option<bool>,
|
|
32
|
-
jpeg_quality: Option<u8>,
|
|
33
|
-
auto_resize_images: Option<bool>,
|
|
34
|
-
image_dpi: Option<f32>,
|
|
35
|
-
filter_dpi: Option<f32>,
|
|
36
|
-
mask_dpi: Option<f32>,
|
|
37
|
-
background_raster_dpi: Option<f32>,
|
|
38
|
-
occlusion_cull: Option<bool>,
|
|
39
|
-
sanitize: Option<bool>,
|
|
40
|
-
custom_fonts: Vec<(String, Vec<u8>)>,
|
|
41
|
-
base_path: Option<String>,
|
|
42
|
-
resource_root: Option<String>,
|
|
43
|
-
header: Option<String>,
|
|
44
|
-
footer: Option<String>,
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
impl Default for ConverterInner {
|
|
48
|
-
fn default() -> Self {
|
|
49
|
-
Self {
|
|
50
|
-
page_size: ironpress_core::types::PageSize::default(),
|
|
51
|
-
margin: ironpress_core::types::Margin::default(),
|
|
52
|
-
compress: None,
|
|
53
|
-
jpeg_quality: None,
|
|
54
|
-
auto_resize_images: None,
|
|
55
|
-
image_dpi: None,
|
|
56
|
-
filter_dpi: None,
|
|
57
|
-
mask_dpi: None,
|
|
58
|
-
background_raster_dpi: None,
|
|
59
|
-
occlusion_cull: None,
|
|
60
|
-
sanitize: None,
|
|
61
|
-
custom_fonts: Vec::new(),
|
|
62
|
-
base_path: None,
|
|
63
|
-
resource_root: None,
|
|
64
|
-
header: None,
|
|
65
|
-
footer: None,
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
impl ConverterInner {
|
|
71
|
-
/// Build an `ironpress_core::HtmlConverter` from the accumulated state.
|
|
72
|
-
fn build(&self) -> ironpress_core::HtmlConverter {
|
|
73
|
-
let mut c = ironpress_core::HtmlConverter::new()
|
|
74
|
-
.page_size(self.page_size)
|
|
75
|
-
.margin(self.margin);
|
|
76
|
-
|
|
77
|
-
if let Some(v) = self.compress {
|
|
78
|
-
c = c.compress(v);
|
|
79
|
-
}
|
|
80
|
-
if let Some(v) = self.jpeg_quality {
|
|
81
|
-
c = c.jpeg_quality(v);
|
|
82
|
-
}
|
|
83
|
-
if let Some(v) = self.auto_resize_images {
|
|
84
|
-
c = c.auto_resize_images(v);
|
|
85
|
-
}
|
|
86
|
-
if let Some(v) = self.image_dpi {
|
|
87
|
-
c = c.image_dpi(v);
|
|
88
|
-
}
|
|
89
|
-
if let Some(v) = self.filter_dpi {
|
|
90
|
-
c = c.filter_dpi(v);
|
|
91
|
-
}
|
|
92
|
-
if let Some(v) = self.mask_dpi {
|
|
93
|
-
c = c.mask_dpi(v);
|
|
94
|
-
}
|
|
95
|
-
if let Some(v) = self.background_raster_dpi {
|
|
96
|
-
c = c.background_raster_dpi(v);
|
|
97
|
-
}
|
|
98
|
-
if let Some(v) = self.occlusion_cull {
|
|
99
|
-
c = c.occlusion_cull(v);
|
|
100
|
-
}
|
|
101
|
-
if let Some(v) = self.sanitize {
|
|
102
|
-
c = c.sanitize(v);
|
|
103
|
-
}
|
|
104
|
-
for (name, data) in &self.custom_fonts {
|
|
105
|
-
c = c.add_font(name, data.clone());
|
|
106
|
-
}
|
|
107
|
-
if let Some(ref p) = self.base_path {
|
|
108
|
-
c = c.base_path(Path::new(p));
|
|
109
|
-
}
|
|
110
|
-
if let Some(ref p) = self.resource_root {
|
|
111
|
-
c = c.resource_root(Path::new(p));
|
|
112
|
-
}
|
|
113
|
-
if let Some(ref t) = self.header {
|
|
114
|
-
c = c.header(t.clone());
|
|
115
|
-
}
|
|
116
|
-
if let Some(ref t) = self.footer {
|
|
117
|
-
c = c.footer(t.clone());
|
|
118
|
-
}
|
|
119
|
-
c
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ---------------------------------------------------------------------------
|
|
124
|
-
// Ruby-visible class: Ironpress::HtmlConverter
|
|
125
|
-
//
|
|
126
|
-
// All setter methods mutate the receiver and return `self` so calls can be
|
|
127
|
-
// chained:
|
|
128
|
-
//
|
|
129
|
-
// converter = Ironpress::HtmlConverter.new
|
|
130
|
-
// .page_size("Letter")
|
|
131
|
-
// .margin(36.0)
|
|
132
|
-
// .compress(true)
|
|
133
|
-
// pdf = converter.convert("<h1>Hello</h1>")
|
|
134
|
-
// ---------------------------------------------------------------------------
|
|
135
|
-
|
|
136
|
-
#[magnus::wrap(class = "Ironpress::HtmlConverter", free_immediately, size)]
|
|
137
|
-
struct RbHtmlConverter {
|
|
138
|
-
inner: RefCell<ConverterInner>,
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Type alias for the typed Ruby object handle that can be both dereferenced
|
|
142
|
-
// to &RbHtmlConverter and converted back to a Ruby Value.
|
|
143
|
-
type RbSelf = typed_data::Obj<RbHtmlConverter>;
|
|
144
|
-
|
|
145
|
-
// --- helpers ---------------------------------------------------------------
|
|
146
|
-
|
|
147
|
-
fn parse_page_size(ruby: &Ruby, name: &str) -> Result<ironpress_core::types::PageSize, Error> {
|
|
148
|
-
match name.to_lowercase().as_str() {
|
|
149
|
-
"a4" => Ok(ironpress_core::types::PageSize::A4),
|
|
150
|
-
"letter" => Ok(ironpress_core::types::PageSize::LETTER),
|
|
151
|
-
"legal" => Ok(ironpress_core::types::PageSize::LEGAL),
|
|
152
|
-
_ => Err(Error::new(
|
|
153
|
-
ruby.exception_arg_error(),
|
|
154
|
-
format!("Unknown page size: {name}"),
|
|
155
|
-
)),
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// --- methods ---------------------------------------------------------------
|
|
160
|
-
|
|
161
|
-
impl RbHtmlConverter {
|
|
162
|
-
/// `Ironpress::HtmlConverter.new` -- create with defaults.
|
|
163
|
-
fn new() -> Self {
|
|
164
|
-
Self {
|
|
165
|
-
inner: RefCell::new(ConverterInner::default()),
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/// `page_size(name)` -- set page size by name ("A4", "Letter", "Legal").
|
|
170
|
-
fn page_size(ruby: &Ruby, rb_self: RbSelf, name: String) -> Result<Value, Error> {
|
|
171
|
-
let size = parse_page_size(ruby, &name)?;
|
|
172
|
-
rb_self.inner.borrow_mut().page_size = size;
|
|
173
|
-
Ok(rb_self.into_value_with(ruby))
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/// `margin(points)` -- set uniform margin on all sides.
|
|
177
|
-
fn margin(ruby: &Ruby, rb_self: RbSelf, points: f64) -> Value {
|
|
178
|
-
rb_self.inner.borrow_mut().margin =
|
|
179
|
-
ironpress_core::types::Margin::uniform(points as f32);
|
|
180
|
-
rb_self.into_value_with(ruby)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/// `margin_sides(top, right, bottom, left)` -- set per-side margins.
|
|
184
|
-
fn margin_sides(
|
|
185
|
-
ruby: &Ruby,
|
|
186
|
-
rb_self: RbSelf,
|
|
187
|
-
top: f64,
|
|
188
|
-
right: f64,
|
|
189
|
-
bottom: f64,
|
|
190
|
-
left: f64,
|
|
191
|
-
) -> Value {
|
|
192
|
-
rb_self.inner.borrow_mut().margin = ironpress_core::types::Margin::new(
|
|
193
|
-
top as f32,
|
|
194
|
-
right as f32,
|
|
195
|
-
bottom as f32,
|
|
196
|
-
left as f32,
|
|
197
|
-
);
|
|
198
|
-
rb_self.into_value_with(ruby)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/// `compress(enabled)` -- enable/disable FlateDecode compression.
|
|
202
|
-
fn compress(ruby: &Ruby, rb_self: RbSelf, enabled: bool) -> Value {
|
|
203
|
-
rb_self.inner.borrow_mut().compress = Some(enabled);
|
|
204
|
-
rb_self.into_value_with(ruby)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/// `jpeg_quality(quality)` -- JPEG quality 0-100.
|
|
208
|
-
fn jpeg_quality(ruby: &Ruby, rb_self: RbSelf, quality: u8) -> Value {
|
|
209
|
-
rb_self.inner.borrow_mut().jpeg_quality = Some(quality);
|
|
210
|
-
rb_self.into_value_with(ruby)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/// `auto_resize_images(enabled)` -- enable/disable automatic image downscaling.
|
|
214
|
-
fn auto_resize_images(ruby: &Ruby, rb_self: RbSelf, enabled: bool) -> Value {
|
|
215
|
-
rb_self.inner.borrow_mut().auto_resize_images = Some(enabled);
|
|
216
|
-
rb_self.into_value_with(ruby)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/// `image_dpi(dpi)` -- source image resolution (min 72, default 300).
|
|
220
|
-
fn image_dpi(ruby: &Ruby, rb_self: RbSelf, dpi: f64) -> Value {
|
|
221
|
-
rb_self.inner.borrow_mut().image_dpi = Some(dpi as f32);
|
|
222
|
-
rb_self.into_value_with(ruby)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/// `filter_dpi(dpi)` -- blur/filter bitmap DPI.
|
|
226
|
-
fn filter_dpi(ruby: &Ruby, rb_self: RbSelf, dpi: f64) -> Value {
|
|
227
|
-
rb_self.inner.borrow_mut().filter_dpi = Some(dpi as f32);
|
|
228
|
-
rb_self.into_value_with(ruby)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/// `mask_dpi(dpi)` -- CSS mask-image coverage DPI.
|
|
232
|
-
fn mask_dpi(ruby: &Ruby, rb_self: RbSelf, dpi: f64) -> Value {
|
|
233
|
-
rb_self.inner.borrow_mut().mask_dpi = Some(dpi as f32);
|
|
234
|
-
rb_self.into_value_with(ruby)
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/// `background_raster_dpi(dpi)` -- background bitmap DPI.
|
|
238
|
-
fn background_raster_dpi(ruby: &Ruby, rb_self: RbSelf, dpi: f64) -> Value {
|
|
239
|
-
rb_self.inner.borrow_mut().background_raster_dpi = Some(dpi as f32);
|
|
240
|
-
rb_self.into_value_with(ruby)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/// `occlusion_cull(enabled)` -- enable/disable occlusion culling.
|
|
244
|
-
fn occlusion_cull(ruby: &Ruby, rb_self: RbSelf, enabled: bool) -> Value {
|
|
245
|
-
rb_self.inner.borrow_mut().occlusion_cull = Some(enabled);
|
|
246
|
-
rb_self.into_value_with(ruby)
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/// `sanitize(enabled)` -- enable/disable HTML sanitization.
|
|
250
|
-
fn sanitize(ruby: &Ruby, rb_self: RbSelf, enabled: bool) -> Value {
|
|
251
|
-
rb_self.inner.borrow_mut().sanitize = Some(enabled);
|
|
252
|
-
rb_self.into_value_with(ruby)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/// `add_font(name, ttf_data)` -- register a custom TrueType font.
|
|
256
|
-
fn add_font(ruby: &Ruby, rb_self: RbSelf, name: String, ttf_data: RString) -> Value {
|
|
257
|
-
let data = unsafe { ttf_data.as_slice() }.to_vec();
|
|
258
|
-
rb_self.inner.borrow_mut().custom_fonts.push((name, data));
|
|
259
|
-
rb_self.into_value_with(ruby)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/// `base_path(path)` -- base directory for resolving relative paths.
|
|
263
|
-
fn base_path(ruby: &Ruby, rb_self: RbSelf, path: String) -> Value {
|
|
264
|
-
rb_self.inner.borrow_mut().base_path = Some(path);
|
|
265
|
-
rb_self.into_value_with(ruby)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/// `resource_root(path)` -- authorized resource boundary directory.
|
|
269
|
-
fn resource_root(ruby: &Ruby, rb_self: RbSelf, path: String) -> Value {
|
|
270
|
-
rb_self.inner.borrow_mut().resource_root = Some(path);
|
|
271
|
-
rb_self.into_value_with(ruby)
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/// `header(text)` -- header text rendered at the top of each page.
|
|
275
|
-
fn header(ruby: &Ruby, rb_self: RbSelf, text: String) -> Value {
|
|
276
|
-
rb_self.inner.borrow_mut().header = Some(text);
|
|
277
|
-
rb_self.into_value_with(ruby)
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/// `footer(text)` -- footer text rendered at the bottom of each page.
|
|
281
|
-
/// Use `{page}` for current page number and `{pages}` for total count.
|
|
282
|
-
fn footer(ruby: &Ruby, rb_self: RbSelf, text: String) -> Value {
|
|
283
|
-
rb_self.inner.borrow_mut().footer = Some(text);
|
|
284
|
-
rb_self.into_value_with(ruby)
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/// `convert(html)` -- convert HTML to PDF bytes.
|
|
288
|
-
fn convert(ruby: &Ruby, rb_self: RbSelf, html: String) -> Result<RString, Error> {
|
|
289
|
-
let converter = rb_self.inner.borrow().build();
|
|
290
|
-
let pdf = converter
|
|
291
|
-
.convert(&html)
|
|
292
|
-
.map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
|
|
293
|
-
Ok(ruby.str_from_slice(&pdf))
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/// `convert_markdown(markdown)` -- convert Markdown to PDF bytes.
|
|
297
|
-
fn convert_markdown(ruby: &Ruby, rb_self: RbSelf, markdown: String) -> Result<RString, Error> {
|
|
298
|
-
let converter = rb_self.inner.borrow().build();
|
|
299
|
-
let pdf = converter
|
|
300
|
-
.convert_markdown(&markdown)
|
|
301
|
-
.map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
|
|
302
|
-
Ok(ruby.str_from_slice(&pdf))
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/// `convert_to_file(html, path)` -- convert HTML and write PDF to a file.
|
|
306
|
-
fn convert_to_file(
|
|
307
|
-
ruby: &Ruby,
|
|
308
|
-
rb_self: RbSelf,
|
|
309
|
-
html: String,
|
|
310
|
-
path: String,
|
|
311
|
-
) -> Result<Value, Error> {
|
|
312
|
-
let converter = rb_self.inner.borrow().build();
|
|
313
|
-
let pdf = converter
|
|
314
|
-
.convert(&html)
|
|
315
|
-
.map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
|
|
316
|
-
std::fs::write(&path, &pdf)
|
|
317
|
-
.map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
|
|
318
|
-
Ok(rb_self.into_value_with(ruby))
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/// `convert_markdown_to_file(markdown, path)` -- convert Markdown and write PDF to a file.
|
|
322
|
-
fn convert_markdown_to_file(
|
|
323
|
-
ruby: &Ruby,
|
|
324
|
-
rb_self: RbSelf,
|
|
325
|
-
markdown: String,
|
|
326
|
-
path: String,
|
|
327
|
-
) -> Result<Value, Error> {
|
|
328
|
-
let converter = rb_self.inner.borrow().build();
|
|
329
|
-
let pdf = converter
|
|
330
|
-
.convert_markdown(&markdown)
|
|
331
|
-
.map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
|
|
332
|
-
std::fs::write(&path, &pdf)
|
|
333
|
-
.map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
|
|
334
|
-
Ok(rb_self.into_value_with(ruby))
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
#[magnus::init]
|
|
339
|
-
fn init(_ruby: &Ruby) -> Result<(), Error> {
|
|
340
|
-
let module = define_module("Ironpress")?;
|
|
341
|
-
|
|
342
|
-
// Module-level convenience functions (existing API).
|
|
343
|
-
module.define_singleton_method("html_to_pdf", function!(html_to_pdf, 1))?;
|
|
344
|
-
module.define_singleton_method("markdown_to_pdf", function!(markdown_to_pdf, 1))?;
|
|
345
|
-
|
|
346
|
-
// HtmlConverter class under the Ironpress module.
|
|
347
|
-
let klass = module.define_class("HtmlConverter", class::object())?;
|
|
348
|
-
klass.define_singleton_method("new", function!(RbHtmlConverter::new, 0))?;
|
|
349
|
-
klass.define_method("page_size", method!(RbHtmlConverter::page_size, 1))?;
|
|
350
|
-
klass.define_method("margin", method!(RbHtmlConverter::margin, 1))?;
|
|
351
|
-
klass.define_method("margin_sides", method!(RbHtmlConverter::margin_sides, 4))?;
|
|
352
|
-
klass.define_method("compress", method!(RbHtmlConverter::compress, 1))?;
|
|
353
|
-
klass.define_method("jpeg_quality", method!(RbHtmlConverter::jpeg_quality, 1))?;
|
|
354
|
-
klass.define_method(
|
|
355
|
-
"auto_resize_images",
|
|
356
|
-
method!(RbHtmlConverter::auto_resize_images, 1),
|
|
357
|
-
)?;
|
|
358
|
-
klass.define_method("image_dpi", method!(RbHtmlConverter::image_dpi, 1))?;
|
|
359
|
-
klass.define_method("filter_dpi", method!(RbHtmlConverter::filter_dpi, 1))?;
|
|
360
|
-
klass.define_method("mask_dpi", method!(RbHtmlConverter::mask_dpi, 1))?;
|
|
361
|
-
klass.define_method(
|
|
362
|
-
"background_raster_dpi",
|
|
363
|
-
method!(RbHtmlConverter::background_raster_dpi, 1),
|
|
364
|
-
)?;
|
|
365
|
-
klass.define_method("occlusion_cull", method!(RbHtmlConverter::occlusion_cull, 1))?;
|
|
366
|
-
klass.define_method("sanitize", method!(RbHtmlConverter::sanitize, 1))?;
|
|
367
|
-
klass.define_method("add_font", method!(RbHtmlConverter::add_font, 2))?;
|
|
368
|
-
klass.define_method("base_path", method!(RbHtmlConverter::base_path, 1))?;
|
|
369
|
-
klass.define_method("resource_root", method!(RbHtmlConverter::resource_root, 1))?;
|
|
370
|
-
klass.define_method("header", method!(RbHtmlConverter::header, 1))?;
|
|
371
|
-
klass.define_method("footer", method!(RbHtmlConverter::footer, 1))?;
|
|
372
|
-
klass.define_method("convert", method!(RbHtmlConverter::convert, 1))?;
|
|
373
|
-
klass.define_method(
|
|
374
|
-
"convert_markdown",
|
|
375
|
-
method!(RbHtmlConverter::convert_markdown, 1),
|
|
376
|
-
)?;
|
|
377
|
-
klass.define_method(
|
|
378
|
-
"convert_to_file",
|
|
379
|
-
method!(RbHtmlConverter::convert_to_file, 2),
|
|
380
|
-
)?;
|
|
381
|
-
klass.define_method(
|
|
382
|
-
"convert_markdown_to_file",
|
|
383
|
-
method!(RbHtmlConverter::convert_markdown_to_file, 2),
|
|
384
|
-
)?;
|
|
385
|
-
|
|
386
|
-
Ok(())
|
|
387
|
-
}
|