typst 0.15.1.8 → 0.15.1.9.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +4 -4
- data/README.md +54 -1
- data/README.typ +57 -2
- data/Rakefile +7 -3
- data/ext/typst/Cargo.toml +1 -1
- data/ext/typst/extconf.rb +11 -0
- data/ext/typst/src/lib.rs +145 -36
- data/ext/typst/src/nogvl.rs +49 -0
- data/lib/base.rb +2 -1
- data/lib/query.rb +2 -2
- data/lib/typst.rb +9 -0
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3c7cc49e13eced386fc44cfb914ebf07dc5892490730f3fd4fbed7448e974f74
|
|
4
|
+
data.tar.gz: 1f7e43e5d752bf8817f22ad3a3da673f1370f697d93effe05d69fe15128960e9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c44932e11673f6881589eb84e1e7a736e1116adf8c4f9200d4e14da7624eeb24df01320dac16faea7f5ac3e0d8567ea44c400422c94c57e250e7c705d289643c
|
|
7
|
+
data.tar.gz: 8b5689a033596181456b73a321f7df85b3ecd66b67865a33b6030b9560d0474b596f330564b7c335bf0f15bb9e469129fea424d01f1ac97ef1f13f595a7511c8
|
data/Cargo.lock
CHANGED
|
@@ -2060,18 +2060,18 @@ dependencies = [
|
|
|
2060
2060
|
|
|
2061
2061
|
[[package]]
|
|
2062
2062
|
name = "rb-sys"
|
|
2063
|
-
version = "0.9.
|
|
2063
|
+
version = "0.9.130"
|
|
2064
2064
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2065
|
-
checksum = "
|
|
2065
|
+
checksum = "02faf625bb10ba893e3ae620f19c9fb1b5f8fcae0fe4eb86bb3f2230fad75edb"
|
|
2066
2066
|
dependencies = [
|
|
2067
2067
|
"rb-sys-build",
|
|
2068
2068
|
]
|
|
2069
2069
|
|
|
2070
2070
|
[[package]]
|
|
2071
2071
|
name = "rb-sys-build"
|
|
2072
|
-
version = "0.9.
|
|
2072
|
+
version = "0.9.130"
|
|
2073
2073
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2074
|
-
checksum = "
|
|
2074
|
+
checksum = "05be6f9c86fe5482808162826f6c047d093b3670582296c770de1d94f8066694"
|
|
2075
2075
|
dependencies = [
|
|
2076
2076
|
"bindgen",
|
|
2077
2077
|
"lazy_static",
|
data/README.md
CHANGED
|
@@ -263,6 +263,58 @@ max_age = 10
|
|
|
263
263
|
Typst::clear_cache(max_age)
|
|
264
264
|
```
|
|
265
265
|
|
|
266
|
+
### Experimental
|
|
267
|
+
|
|
268
|
+
#### Threads and concurrency
|
|
269
|
+
|
|
270
|
+
A new experimental `concurrent` flag is added to work around Ruby's global VM lock and speed things up for batch workloads.
|
|
271
|
+
|
|
272
|
+
Set the option globally
|
|
273
|
+
```ruby
|
|
274
|
+
Typst.concurrent = true
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Or set the option per call
|
|
278
|
+
```ruby
|
|
279
|
+
Typst(body: invoice, concurrent: true)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Compiling releases Ruby's global VM lock, so several threads can compile at the
|
|
283
|
+
same time and genuinely use several cores.
|
|
284
|
+
|
|
285
|
+
```ruby
|
|
286
|
+
docs = invoices.map { |invoice| Thread.new { Typst(body: invoice, concurrent: true).compile(:pdf) } }.map(&:value)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Eight threads compiling the same document, measured on a 24-core machine:
|
|
290
|
+
|
|
291
|
+
| threads | documents/second | cores busy |
|
|
292
|
+
|--------:|-----------------:|-----------:|
|
|
293
|
+
| 1 | 9.9 | 1.00 |
|
|
294
|
+
| 8 | 36.0 | 7.65 |
|
|
295
|
+
|
|
296
|
+
That is throughput across documents, not speed within one. typst parallelizes
|
|
297
|
+
page runs, so a document with a single page layout is one run and stays on one
|
|
298
|
+
core no matter how many threads it is given.
|
|
299
|
+
|
|
300
|
+
Threads share the compilation cache and the font discovery. Compiling different
|
|
301
|
+
sources, with different `sys_inputs`, font paths and roots, at the same time is
|
|
302
|
+
safe.
|
|
303
|
+
|
|
304
|
+
Three things to know:
|
|
305
|
+
|
|
306
|
+
* A compile cannot be interrupted. `Thread#kill` and `Ctrl-C` take effect once
|
|
307
|
+
it returns.
|
|
308
|
+
* Package storage is not safe to populate concurrently. Threads that need the
|
|
309
|
+
same not-yet-cached package each download their own copy and untar it
|
|
310
|
+
straight into the same directory, over each other; on Windows that fails
|
|
311
|
+
outright rather than clobbering. Compile once to warm the cache before
|
|
312
|
+
fanning out. Reading a package already on disk from many threads is safe.
|
|
313
|
+
* `clear_cache` takes the cache's write lock, so calling it in a loop while other
|
|
314
|
+
threads compile will starve them. Measured with four threads compiling and one
|
|
315
|
+
evicting as fast as it could, the compiles took 400 times longer. Call it
|
|
316
|
+
between batches of documents, not between documents.
|
|
317
|
+
|
|
266
318
|
## Contributors & Acknowledgements
|
|
267
319
|
typst-rb is based on [typst-py](https://github.com/messense/typst-py) by [messense](https://github.com/messense)\
|
|
268
320
|
clear_cache was contributed by [NRicciVestmark](https://github.com/NRicciVestmark)\
|
|
@@ -270,7 +322,8 @@ CI improvements were contributed by [am1006](https://github.com/am1006)\
|
|
|
270
322
|
Defect resolutions by [adam12](https://github.com/adam12) and [walterdavis](https://github.com/walterdavis)\
|
|
271
323
|
Design suggestions by [alec-c4](https://github.com/alec-c4)\
|
|
272
324
|
Compiler warnings were contributed by [TheSoloHacker47](https://github.com/TheSoloHacker47) \
|
|
273
|
-
Font optimization patches were contributed by [dmke](https://github.com/dmke)
|
|
325
|
+
Font optimization patches were contributed by [dmke](https://github.com/dmke) \
|
|
326
|
+
Concurrency patches were contributed by [dmke](https://github.com/dmke)
|
|
274
327
|
|
|
275
328
|
## License
|
|
276
329
|
|
data/README.typ
CHANGED
|
@@ -270,14 +270,69 @@ max_age = 10
|
|
|
270
270
|
Typst::clear_cache(max_age)
|
|
271
271
|
```
|
|
272
272
|
|
|
273
|
+
=== Experimental
|
|
274
|
+
|
|
275
|
+
==== Threads and concurrency
|
|
276
|
+
|
|
277
|
+
A new experimental `concurrent` flag is added to work around Ruby's global VM lock and speed things up for batch workloads.
|
|
278
|
+
|
|
279
|
+
Set the option globally
|
|
280
|
+
```ruby
|
|
281
|
+
Typst.concurrent = true
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Or set the option per call
|
|
285
|
+
```ruby
|
|
286
|
+
Typst(body: invoice, concurrent: true)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Compiling releases Ruby's global VM lock, so several threads can compile at the
|
|
290
|
+
same time and genuinely use several cores.
|
|
291
|
+
|
|
292
|
+
```ruby
|
|
293
|
+
docs = invoices.map { |invoice| Thread.new { Typst(body: invoice, concurrent: true).compile(:pdf) } }.map(&:value)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Eight threads compiling the same document, measured on a 24-core machine:
|
|
297
|
+
|
|
298
|
+
#table(
|
|
299
|
+
columns: 3,
|
|
300
|
+
align: (right, right, right),
|
|
301
|
+
table.header([threads], [documents/second], [cores busy]),
|
|
302
|
+
[1], [9.9], [1.00],
|
|
303
|
+
[8], [36.0], [7.65],
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
That is throughput across documents, not speed within one. typst parallelizes
|
|
307
|
+
page runs, so a document with a single page layout is one run and stays on one
|
|
308
|
+
core no matter how many threads it is given.
|
|
309
|
+
|
|
310
|
+
Threads share the compilation cache and the font discovery. Compiling different
|
|
311
|
+
sources, with different `sys_inputs`, font paths and roots, at the same time is
|
|
312
|
+
safe.
|
|
313
|
+
|
|
314
|
+
Three things to know:
|
|
315
|
+
|
|
316
|
+
- A compile cannot be interrupted. `Thread#kill` and `Ctrl-C` take effect once
|
|
317
|
+
it returns.
|
|
318
|
+
- Package storage is not safe to populate concurrently. Threads that need the
|
|
319
|
+
same not-yet-cached package each download their own copy and untar it
|
|
320
|
+
straight into the same directory, over each other; on Windows that fails
|
|
321
|
+
outright rather than clobbering. Compile once to warm the cache before
|
|
322
|
+
fanning out. Reading a package already on disk from many threads is safe.
|
|
323
|
+
- `clear_cache` takes the cache's write lock, so calling it in a loop while other
|
|
324
|
+
threads compile will starve them. Measured with four threads compiling and one
|
|
325
|
+
evicting as fast as it could, the compiles took 400 times longer. Call it
|
|
326
|
+
between batches of documents, not between documents.
|
|
327
|
+
|
|
273
328
|
== Contributors & Acknowledgements
|
|
274
329
|
typst-rb is based on #link("https://github.com/messense/typst-py")[typst-py] by #link("https://github.com/messense")[messense]\
|
|
275
330
|
clear_cache was contributed by #link("https://github.com/NRicciVestmark")[NRicciVestmark]\
|
|
276
331
|
CI improvements were contributed by #link("https://github.com/am1006")[am1006]\
|
|
277
332
|
Defect resolutions by #link("https://github.com/adam12")[adam12] and #link("https://github.com/walterdavis")[walterdavis]\
|
|
278
333
|
Design suggestions by #link("https://github.com/alec-c4")[alec-c4]\
|
|
279
|
-
Compiler warnings were contributed by #link("https://github.com/TheSoloHacker47")[TheSoloHacker47]
|
|
280
|
-
|
|
334
|
+
Compiler warnings were contributed by #link("https://github.com/TheSoloHacker47")[TheSoloHacker47]
|
|
335
|
+
Concurrency patches were contributed by #link("https://github.com/dmke")[dmke]
|
|
281
336
|
|
|
282
337
|
== License
|
|
283
338
|
|
data/Rakefile
CHANGED
|
@@ -30,12 +30,16 @@ end
|
|
|
30
30
|
|
|
31
31
|
Rake::TestTask.new do |t|
|
|
32
32
|
t.libs << "test"
|
|
33
|
-
|
|
33
|
+
# Absolute, because test/typst_test.rb chdirs into its own directory as it
|
|
34
|
+
# loads and the loader resolves what follows it against the working directory.
|
|
35
|
+
t.test_files = FileList['test/*_test.rb'].map { |f| File.expand_path(f) }
|
|
34
36
|
t.verbose = true
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
task 'benchmark' do |t|
|
|
38
|
-
|
|
40
|
+
Dir.glob("benchmarks/*.rb").each do |b|
|
|
41
|
+
sh "bundle exec ruby #{b}"
|
|
42
|
+
end
|
|
39
43
|
end
|
|
40
44
|
|
|
41
45
|
task 'gem:native' do |t|
|
|
@@ -49,4 +53,4 @@ task 'gem:native:push' do |t|
|
|
|
49
53
|
CROSS_PLATFORMS.each do |platform|
|
|
50
54
|
sh "gem push pkg/typst-#{spec.version}-#{platform}.gem"
|
|
51
55
|
end
|
|
52
|
-
end
|
|
56
|
+
end
|
data/ext/typst/Cargo.toml
CHANGED
|
@@ -52,4 +52,4 @@ walkdir = "2.4.0"
|
|
|
52
52
|
|
|
53
53
|
# enable rb-sys feature to test against Ruby head. This is only needed if you
|
|
54
54
|
# want to work with the unreleased, in-development, next version of Ruby
|
|
55
|
-
rb-sys = { version = "0.9.
|
|
55
|
+
rb-sys = { version = "0.9.130", default-features = false, features = ["stable-api-compiled-fallback"] }
|
data/ext/typst/extconf.rb
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
require "mkmf"
|
|
2
2
|
require "rb_sys/mkmf"
|
|
3
3
|
|
|
4
|
+
module RbSys
|
|
5
|
+
class CargoBuilder < Gem::Ext::Builder
|
|
6
|
+
alias_method :old_platform_specific_rustc_args, :platform_specific_rustc_args
|
|
7
|
+
def new_platform_specific_rustc_args(dest_dir, flags = [])
|
|
8
|
+
flags += ["-l", "kernel32"] if mingw_target? # add kernel32 before libruby to avoid the sleep bug
|
|
9
|
+
old_platform_specific_rustc_args(dest_dir, flags)
|
|
10
|
+
end
|
|
11
|
+
alias_method :platform_specific_rustc_args, :new_platform_specific_rustc_args
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
4
15
|
create_rust_makefile("typst/typst")
|
data/ext/typst/src/lib.rs
CHANGED
|
@@ -7,15 +7,16 @@ use query::{query as typst_query, QueryCommand, SerializationFormat};
|
|
|
7
7
|
use typst::foundations::{Dict, Value};
|
|
8
8
|
use typst_library::Feature;
|
|
9
9
|
use typst_pdf::PdfStandard;
|
|
10
|
+
use nogvl::without_gvl;
|
|
10
11
|
use world::SystemWorld;
|
|
11
12
|
|
|
12
13
|
mod compiler;
|
|
13
14
|
mod download;
|
|
15
|
+
mod nogvl;
|
|
14
16
|
mod query;
|
|
15
17
|
mod world;
|
|
16
18
|
|
|
17
19
|
fn to_html(
|
|
18
|
-
ruby: &Ruby,
|
|
19
20
|
input: PathBuf,
|
|
20
21
|
root: Option<PathBuf>,
|
|
21
22
|
font_paths: Vec<PathBuf>,
|
|
@@ -24,13 +25,13 @@ fn to_html(
|
|
|
24
25
|
render_bleed: bool,
|
|
25
26
|
pretty: bool,
|
|
26
27
|
sys_inputs: HashMap<String, String>,
|
|
27
|
-
) -> Result<(Vec<Vec<u8>>, Vec<String>),
|
|
28
|
+
) -> Result<(Vec<Vec<u8>>, Vec<String>), String> {
|
|
28
29
|
let input = input.canonicalize()
|
|
29
|
-
.map_err(|err|
|
|
30
|
+
.map_err(|err| err.to_string())?;
|
|
30
31
|
|
|
31
32
|
let root = if let Some(root) = root {
|
|
32
33
|
root.canonicalize()
|
|
33
|
-
|
|
34
|
+
.map_err(|err| err.to_string())?
|
|
34
35
|
} else if let Some(dir) = input.parent() {
|
|
35
36
|
dir.into()
|
|
36
37
|
} else {
|
|
@@ -60,32 +61,53 @@ fn to_html(
|
|
|
60
61
|
.ignore_system_fonts(ignore_system_fonts)
|
|
61
62
|
.ignore_embedded_fonts(ignore_embedded_fonts)
|
|
62
63
|
.build()
|
|
63
|
-
.map_err(|msg|
|
|
64
|
+
.map_err(|msg| msg.to_string())?;
|
|
64
65
|
|
|
65
66
|
let compiled = world
|
|
66
67
|
.compile(Some("html"), None, &Vec::new(), pretty, render_bleed)
|
|
67
|
-
.map_err(|msg|
|
|
68
|
+
.map_err(|msg| msg.to_string())?;
|
|
68
69
|
|
|
69
70
|
Ok(compiled)
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
fn
|
|
73
|
+
fn route_to_html(
|
|
73
74
|
ruby: &Ruby,
|
|
74
75
|
input: PathBuf,
|
|
75
76
|
root: Option<PathBuf>,
|
|
76
77
|
font_paths: Vec<PathBuf>,
|
|
77
78
|
ignore_system_fonts: bool,
|
|
78
79
|
ignore_embedded_fonts: bool,
|
|
80
|
+
concurrent: bool,
|
|
79
81
|
render_bleed: bool,
|
|
80
82
|
pretty: bool,
|
|
81
83
|
sys_inputs: HashMap<String, String>,
|
|
82
84
|
) -> Result<(Vec<Vec<u8>>, Vec<String>), Error> {
|
|
85
|
+
if concurrent {
|
|
86
|
+
without_gvl(move || -> Result<(Vec<Vec<u8>>, Vec<String>), String> {
|
|
87
|
+
to_html(input, root, font_paths, ignore_system_fonts, ignore_embedded_fonts, render_bleed, pretty, sys_inputs)
|
|
88
|
+
})
|
|
89
|
+
} else {
|
|
90
|
+
to_html(input, root, font_paths, ignore_system_fonts, ignore_embedded_fonts, render_bleed, pretty, sys_inputs)
|
|
91
|
+
}
|
|
92
|
+
.map_err(|msg| magnus::Error::new(ruby.exception_arg_error(), msg))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fn to_svg(
|
|
96
|
+
input: PathBuf,
|
|
97
|
+
root: Option<PathBuf>,
|
|
98
|
+
font_paths: Vec<PathBuf>,
|
|
99
|
+
ignore_system_fonts: bool,
|
|
100
|
+
ignore_embedded_fonts: bool,
|
|
101
|
+
render_bleed: bool,
|
|
102
|
+
pretty: bool,
|
|
103
|
+
sys_inputs: HashMap<String, String>,
|
|
104
|
+
) -> Result<(Vec<Vec<u8>>, Vec<String>), String> {
|
|
83
105
|
let input = input.canonicalize()
|
|
84
|
-
.map_err(|
|
|
106
|
+
.map_err(|msg| msg.to_string())?;
|
|
85
107
|
|
|
86
108
|
let root = if let Some(root) = root {
|
|
87
109
|
root.canonicalize()
|
|
88
|
-
.map_err(|
|
|
110
|
+
.map_err(|msg| msg.to_string())?
|
|
89
111
|
} else if let Some(dir) = input.parent() {
|
|
90
112
|
dir.into()
|
|
91
113
|
} else {
|
|
@@ -102,32 +124,53 @@ fn to_svg(
|
|
|
102
124
|
.ignore_system_fonts(ignore_system_fonts)
|
|
103
125
|
.ignore_embedded_fonts(ignore_embedded_fonts)
|
|
104
126
|
.build()
|
|
105
|
-
.map_err(|msg|
|
|
127
|
+
.map_err(|msg| msg.to_string())?;
|
|
106
128
|
|
|
107
129
|
let compiled = world
|
|
108
130
|
.compile(Some("svg"), None, &Vec::new(), pretty, render_bleed)
|
|
109
|
-
.map_err(|msg|
|
|
131
|
+
.map_err(|msg| msg.to_string())?;
|
|
110
132
|
|
|
111
133
|
Ok(compiled)
|
|
112
134
|
}
|
|
113
135
|
|
|
114
|
-
fn
|
|
136
|
+
fn route_to_svg(
|
|
115
137
|
ruby: &Ruby,
|
|
116
138
|
input: PathBuf,
|
|
117
139
|
root: Option<PathBuf>,
|
|
118
140
|
font_paths: Vec<PathBuf>,
|
|
119
141
|
ignore_system_fonts: bool,
|
|
120
142
|
ignore_embedded_fonts: bool,
|
|
143
|
+
concurrent: bool,
|
|
121
144
|
render_bleed: bool,
|
|
145
|
+
pretty: bool,
|
|
122
146
|
sys_inputs: HashMap<String, String>,
|
|
123
|
-
ppi: Option<f32>,
|
|
124
147
|
) -> Result<(Vec<Vec<u8>>, Vec<String>), Error> {
|
|
148
|
+
if concurrent {
|
|
149
|
+
without_gvl(move || -> Result<(Vec<Vec<u8>>, Vec<String>), String> {
|
|
150
|
+
to_svg(input, root, font_paths, ignore_system_fonts, ignore_embedded_fonts, render_bleed, pretty, sys_inputs)
|
|
151
|
+
})
|
|
152
|
+
} else {
|
|
153
|
+
to_svg(input, root, font_paths, ignore_system_fonts, ignore_embedded_fonts, render_bleed, pretty, sys_inputs)
|
|
154
|
+
}
|
|
155
|
+
.map_err(|msg| magnus::Error::new(ruby.exception_arg_error(), msg))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fn to_png(
|
|
159
|
+
input: PathBuf,
|
|
160
|
+
root: Option<PathBuf>,
|
|
161
|
+
font_paths: Vec<PathBuf>,
|
|
162
|
+
ignore_system_fonts: bool,
|
|
163
|
+
ignore_embedded_fonts: bool,
|
|
164
|
+
render_bleed: bool,
|
|
165
|
+
sys_inputs: HashMap<String, String>,
|
|
166
|
+
ppi: Option<f32>,
|
|
167
|
+
) -> Result<(Vec<Vec<u8>>, Vec<String>), String> {
|
|
125
168
|
let input = input.canonicalize()
|
|
126
|
-
.map_err(|
|
|
169
|
+
.map_err(|msg| msg.to_string())?;
|
|
127
170
|
|
|
128
171
|
let root = if let Some(root) = root {
|
|
129
172
|
root.canonicalize()
|
|
130
|
-
.map_err(|
|
|
173
|
+
.map_err(|msg| msg.to_string())?
|
|
131
174
|
} else if let Some(dir) = input.parent() {
|
|
132
175
|
dir.into()
|
|
133
176
|
} else {
|
|
@@ -144,17 +187,38 @@ fn to_png(
|
|
|
144
187
|
.ignore_system_fonts(ignore_system_fonts)
|
|
145
188
|
.ignore_embedded_fonts(ignore_embedded_fonts)
|
|
146
189
|
.build()
|
|
147
|
-
.map_err(|msg|
|
|
190
|
+
.map_err(|msg| msg.to_string())?;
|
|
148
191
|
|
|
149
192
|
let compiled = world
|
|
150
193
|
.compile(Some("png"), ppi, &Vec::new(), false, render_bleed)
|
|
151
|
-
.map_err(|msg|
|
|
194
|
+
.map_err(|msg| msg.to_string())?;
|
|
152
195
|
|
|
153
196
|
Ok(compiled)
|
|
154
197
|
}
|
|
155
198
|
|
|
156
|
-
fn
|
|
199
|
+
fn route_to_png(
|
|
157
200
|
ruby: &Ruby,
|
|
201
|
+
input: PathBuf,
|
|
202
|
+
root: Option<PathBuf>,
|
|
203
|
+
font_paths: Vec<PathBuf>,
|
|
204
|
+
ignore_system_fonts: bool,
|
|
205
|
+
ignore_embedded_fonts: bool,
|
|
206
|
+
concurrent: bool,
|
|
207
|
+
render_bleed: bool,
|
|
208
|
+
sys_inputs: HashMap<String, String>,
|
|
209
|
+
ppi: Option<f32>,
|
|
210
|
+
) -> Result<(Vec<Vec<u8>>, Vec<String>), Error> {
|
|
211
|
+
if concurrent {
|
|
212
|
+
without_gvl(move || -> Result<(Vec<Vec<u8>>, Vec<String>), String> {
|
|
213
|
+
to_png(input, root, font_paths, ignore_system_fonts, ignore_embedded_fonts, render_bleed, sys_inputs, ppi)
|
|
214
|
+
})
|
|
215
|
+
} else {
|
|
216
|
+
to_png(input, root, font_paths, ignore_system_fonts, ignore_embedded_fonts, render_bleed, sys_inputs, ppi)
|
|
217
|
+
}
|
|
218
|
+
.map_err(|msg| magnus::Error::new(ruby.exception_arg_error(), msg))
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fn to_pdf(
|
|
158
222
|
input: PathBuf,
|
|
159
223
|
root: Option<PathBuf>,
|
|
160
224
|
font_paths: Vec<PathBuf>,
|
|
@@ -163,13 +227,13 @@ fn to_pdf(
|
|
|
163
227
|
pretty: bool,
|
|
164
228
|
sys_inputs: HashMap<String, String>,
|
|
165
229
|
pdf_standards: Vec<String>,
|
|
166
|
-
) -> Result<(Vec<Vec<u8>>, Vec<String>),
|
|
230
|
+
) -> Result<(Vec<Vec<u8>>, Vec<String>), String> {
|
|
167
231
|
let input = input.canonicalize()
|
|
168
|
-
.map_err(|
|
|
232
|
+
.map_err(|msg| msg.to_string())?;
|
|
169
233
|
|
|
170
234
|
let root = if let Some(root) = root {
|
|
171
235
|
root.canonicalize()
|
|
172
|
-
.map_err(|
|
|
236
|
+
.map_err(|msg| msg.to_string())?
|
|
173
237
|
} else if let Some(dir) = input.parent() {
|
|
174
238
|
dir.into()
|
|
175
239
|
} else {
|
|
@@ -186,7 +250,7 @@ fn to_pdf(
|
|
|
186
250
|
.ignore_system_fonts(ignore_system_fonts)
|
|
187
251
|
.ignore_embedded_fonts(ignore_embedded_fonts)
|
|
188
252
|
.build()
|
|
189
|
-
.map_err(|msg|
|
|
253
|
+
.map_err(|msg| msg.to_string())?;
|
|
190
254
|
|
|
191
255
|
let pdf_standards_lookup: HashMap::<&str, PdfStandard> = HashMap::from([
|
|
192
256
|
("1.4", PdfStandard::V_1_4),
|
|
@@ -213,19 +277,40 @@ fn to_pdf(
|
|
|
213
277
|
let result = pdf_standards_lookup.get(pdf_standard.as_str());
|
|
214
278
|
match result {
|
|
215
279
|
Some(value) => pdf_standards_vec.push(*value),
|
|
216
|
-
_ => return Err(
|
|
280
|
+
_ => return Err("Unknown PdfStandard".to_string()),
|
|
217
281
|
}
|
|
218
282
|
}
|
|
219
283
|
|
|
220
284
|
let compiled = world
|
|
221
285
|
.compile(Some("pdf"), None, &pdf_standards_vec, pretty, true)
|
|
222
|
-
.map_err(|msg|
|
|
286
|
+
.map_err(|msg| msg.to_string())?;
|
|
223
287
|
|
|
224
288
|
Ok(compiled)
|
|
225
289
|
}
|
|
226
290
|
|
|
227
|
-
fn
|
|
291
|
+
fn route_to_pdf(
|
|
228
292
|
ruby: &Ruby,
|
|
293
|
+
input: PathBuf,
|
|
294
|
+
root: Option<PathBuf>,
|
|
295
|
+
font_paths: Vec<PathBuf>,
|
|
296
|
+
ignore_system_fonts: bool,
|
|
297
|
+
ignore_embedded_fonts: bool,
|
|
298
|
+
concurrent: bool,
|
|
299
|
+
pretty: bool,
|
|
300
|
+
sys_inputs: HashMap<String, String>,
|
|
301
|
+
pdf_standards: Vec<String>,
|
|
302
|
+
) -> Result<(Vec<Vec<u8>>, Vec<String>), Error> {
|
|
303
|
+
if concurrent {
|
|
304
|
+
without_gvl(move || -> Result<(Vec<Vec<u8>>, Vec<String>), String> {
|
|
305
|
+
to_pdf(input, root, font_paths, ignore_system_fonts, ignore_embedded_fonts, pretty, sys_inputs, pdf_standards)
|
|
306
|
+
})
|
|
307
|
+
} else {
|
|
308
|
+
to_pdf(input, root, font_paths, ignore_system_fonts, ignore_embedded_fonts, pretty, sys_inputs, pdf_standards)
|
|
309
|
+
}
|
|
310
|
+
.map_err(|msg| magnus::Error::new(ruby.exception_arg_error(), msg))
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
fn query(
|
|
229
314
|
selector: String,
|
|
230
315
|
field: Option<String>,
|
|
231
316
|
one: bool,
|
|
@@ -236,19 +321,19 @@ fn query(
|
|
|
236
321
|
ignore_system_fonts: bool,
|
|
237
322
|
ignore_embedded_fonts: bool,
|
|
238
323
|
sys_inputs: HashMap<String, String>,
|
|
239
|
-
) -> Result<String,
|
|
324
|
+
) -> Result<String, String> {
|
|
240
325
|
let format = match format.unwrap().to_ascii_lowercase().as_str() {
|
|
241
326
|
"json" => SerializationFormat::Json,
|
|
242
327
|
"yaml" => SerializationFormat::Yaml,
|
|
243
|
-
_ => return Err(
|
|
328
|
+
_ => return Err("unsupported serialization format".to_string()),
|
|
244
329
|
};
|
|
245
330
|
|
|
246
331
|
let input = input.canonicalize()
|
|
247
|
-
.map_err(|
|
|
332
|
+
.map_err(|msg| msg.to_string())?;
|
|
248
333
|
|
|
249
334
|
let root = if let Some(root) = root {
|
|
250
335
|
root.canonicalize()
|
|
251
|
-
.map_err(|
|
|
336
|
+
.map_err(|msg| msg.to_string())?
|
|
252
337
|
} else if let Some(dir) = input.parent() {
|
|
253
338
|
dir.into()
|
|
254
339
|
} else {
|
|
@@ -265,7 +350,7 @@ fn query(
|
|
|
265
350
|
.ignore_system_fonts(ignore_system_fonts)
|
|
266
351
|
.ignore_embedded_fonts(ignore_embedded_fonts)
|
|
267
352
|
.build()
|
|
268
|
-
.map_err(|msg|
|
|
353
|
+
.map_err(|msg| msg.to_string())?;
|
|
269
354
|
|
|
270
355
|
let result = typst_query(
|
|
271
356
|
&mut world,
|
|
@@ -279,8 +364,32 @@ fn query(
|
|
|
279
364
|
|
|
280
365
|
match result {
|
|
281
366
|
Ok(data) => Ok(data),
|
|
282
|
-
Err(msg) => Err(
|
|
367
|
+
Err(msg) => return Err(msg.to_string()),
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
fn route_query(
|
|
372
|
+
ruby: &Ruby,
|
|
373
|
+
selector: String,
|
|
374
|
+
field: Option<String>,
|
|
375
|
+
one: bool,
|
|
376
|
+
format: Option<String>,
|
|
377
|
+
input: PathBuf,
|
|
378
|
+
root: Option<PathBuf>,
|
|
379
|
+
font_paths: Vec<PathBuf>,
|
|
380
|
+
ignore_system_fonts: bool,
|
|
381
|
+
ignore_embedded_fonts: bool,
|
|
382
|
+
concurrent: bool,
|
|
383
|
+
sys_inputs: HashMap<String, String>,
|
|
384
|
+
) -> Result<String, Error> {
|
|
385
|
+
if concurrent {
|
|
386
|
+
without_gvl(move || -> Result<String, String> {
|
|
387
|
+
query(selector, field, one, format, input, root, font_paths, ignore_system_fonts, ignore_embedded_fonts, sys_inputs)
|
|
388
|
+
})
|
|
389
|
+
} else {
|
|
390
|
+
query(selector, field, one, format, input, root, font_paths, ignore_system_fonts, ignore_embedded_fonts, sys_inputs)
|
|
283
391
|
}
|
|
392
|
+
.map_err(|msg| magnus::Error::new(ruby.exception_arg_error(), msg))
|
|
284
393
|
}
|
|
285
394
|
|
|
286
395
|
fn clear_cache(_ruby: &Ruby, max_age: usize) {
|
|
@@ -296,11 +405,11 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
296
405
|
env_logger::init();
|
|
297
406
|
|
|
298
407
|
let module = ruby.define_module("Typst")?;
|
|
299
|
-
module.define_singleton_method("_to_pdf", function!(
|
|
300
|
-
module.define_singleton_method("_to_svg", function!(
|
|
301
|
-
module.define_singleton_method("_to_png", function!(
|
|
302
|
-
module.define_singleton_method("_to_html", function!(
|
|
303
|
-
module.define_singleton_method("_query", function!(
|
|
408
|
+
module.define_singleton_method("_to_pdf", function!(route_to_pdf, 9))?;
|
|
409
|
+
module.define_singleton_method("_to_svg", function!(route_to_svg, 9))?;
|
|
410
|
+
module.define_singleton_method("_to_png", function!(route_to_png, 9))?;
|
|
411
|
+
module.define_singleton_method("_to_html", function!(route_to_html, 9))?;
|
|
412
|
+
module.define_singleton_method("_query", function!(route_query, 11))?;
|
|
304
413
|
module.define_singleton_method("_clear_cache", function!(clear_cache, 1))?;
|
|
305
414
|
module.define_singleton_method("_clear_font_cache", function!(clear_font_cache, 0))?;
|
|
306
415
|
Ok(())
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
use std::ffi::c_void;
|
|
2
|
+
use std::panic::{catch_unwind, AssertUnwindSafe};
|
|
3
|
+
|
|
4
|
+
/// Runs `f` with the GVL released, so other Ruby threads keep running while
|
|
5
|
+
/// Typst compiles.
|
|
6
|
+
///
|
|
7
|
+
/// `f` runs on the calling OS thread and must not touch the Ruby API — no
|
|
8
|
+
/// `Ruby` handle, no `Value`, no allocation of Ruby objects. Convert to and
|
|
9
|
+
/// from Ruby types outside of this call.
|
|
10
|
+
///
|
|
11
|
+
/// No unblocking function is registered, so the call cannot be interrupted;
|
|
12
|
+
/// a compile always runs to completion. That matches the previous behaviour,
|
|
13
|
+
/// where holding the GVL made the compile uninterruptible anyway.
|
|
14
|
+
pub fn without_gvl<F, T>(f: F) -> T
|
|
15
|
+
where
|
|
16
|
+
F: FnOnce() -> T,
|
|
17
|
+
{
|
|
18
|
+
struct Payload<F, T> {
|
|
19
|
+
f: Option<F>,
|
|
20
|
+
result: Option<std::thread::Result<T>>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// A Rust panic must not unwind across the C frame that Ruby pushes here,
|
|
24
|
+
// so it is caught and resumed once we hold the GVL again.
|
|
25
|
+
unsafe extern "C" fn trampoline<F, T>(data: *mut c_void) -> *mut c_void
|
|
26
|
+
where
|
|
27
|
+
F: FnOnce() -> T,
|
|
28
|
+
{
|
|
29
|
+
let payload = &mut *data.cast::<Payload<F, T>>();
|
|
30
|
+
let f = payload.f.take().expect("closure called twice");
|
|
31
|
+
payload.result = Some(catch_unwind(AssertUnwindSafe(f)));
|
|
32
|
+
std::ptr::null_mut()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let mut payload = Payload { f: Some(f), result: None };
|
|
36
|
+
unsafe {
|
|
37
|
+
rb_sys::rb_thread_call_without_gvl(
|
|
38
|
+
Some(trampoline::<F, T>),
|
|
39
|
+
(&raw mut payload).cast::<c_void>(),
|
|
40
|
+
None,
|
|
41
|
+
std::ptr::null_mut(),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
match payload.result.take().expect("closure was not called") {
|
|
46
|
+
Ok(value) => value,
|
|
47
|
+
Err(panic) => std::panic::resume_unwind(panic),
|
|
48
|
+
}
|
|
49
|
+
}
|
data/lib/base.rb
CHANGED
|
@@ -38,12 +38,13 @@ module Typst
|
|
|
38
38
|
options[:ignore_embedded_fonts] ||= false
|
|
39
39
|
options[:pretty] ||= false
|
|
40
40
|
options[:render_bleed] ||= false
|
|
41
|
+
options[:concurrent] ||= Typst.concurrent
|
|
41
42
|
|
|
42
43
|
self.options = options
|
|
43
44
|
end
|
|
44
45
|
|
|
45
46
|
def typst_options
|
|
46
|
-
[:file, :root, :font_paths, :ignore_system_fonts, :ignore_embedded_fonts, :render_bleed]
|
|
47
|
+
[:file, :root, :font_paths, :ignore_system_fonts, :ignore_embedded_fonts, :concurrent, :render_bleed]
|
|
47
48
|
end
|
|
48
49
|
|
|
49
50
|
def typst_args(opts)
|
data/lib/query.rb
CHANGED
|
@@ -2,9 +2,9 @@ module Typst
|
|
|
2
2
|
class Query < Base
|
|
3
3
|
attr_accessor :format
|
|
4
4
|
|
|
5
|
-
def initialize(selector, input, field: nil, one: false, format: "json", root: ".", font_paths: [], ignore_system_fonts: false, ignore_embedded_fonts: false, sys_inputs: {})
|
|
5
|
+
def initialize(selector, input, field: nil, one: false, format: "json", root: ".", font_paths: [], ignore_system_fonts: false, ignore_embedded_fonts: false, concurrent: false, sys_inputs: {})
|
|
6
6
|
self.format = format
|
|
7
|
-
@result = Typst::_query(selector, field, one, format, input, root, font_paths, ignore_system_fonts, ignore_embedded_fonts, sys_inputs)
|
|
7
|
+
@result = Typst::_query(selector, field, one, format, input, root, font_paths, ignore_system_fonts, ignore_embedded_fonts, concurrent, sys_inputs)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def result(raw: false)
|
data/lib/typst.rb
CHANGED
|
@@ -4,6 +4,7 @@ end
|
|
|
4
4
|
|
|
5
5
|
module Typst
|
|
6
6
|
@@formats = {}
|
|
7
|
+
@@concurrent = false
|
|
7
8
|
|
|
8
9
|
def self.register_format(**format)
|
|
9
10
|
@@formats.merge!(format)
|
|
@@ -13,6 +14,14 @@ module Typst
|
|
|
13
14
|
@@formats
|
|
14
15
|
end
|
|
15
16
|
|
|
17
|
+
def self.concurrent
|
|
18
|
+
@@concurrent
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.concurrent=(value)
|
|
22
|
+
@@concurrent = value
|
|
23
|
+
end
|
|
24
|
+
|
|
16
25
|
def self.clear_cache(max_age = 0)
|
|
17
26
|
Typst::_clear_cache(max_age)
|
|
18
27
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: typst
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.15.1.
|
|
4
|
+
version: 0.15.1.9.pre
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Flinn
|
|
@@ -18,7 +18,7 @@ dependencies:
|
|
|
18
18
|
version: '0.9'
|
|
19
19
|
- - ">="
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
|
-
version: 0.9.
|
|
21
|
+
version: 0.9.130
|
|
22
22
|
type: :runtime
|
|
23
23
|
prerelease: false
|
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -28,7 +28,7 @@ dependencies:
|
|
|
28
28
|
version: '0.9'
|
|
29
29
|
- - ">="
|
|
30
30
|
- !ruby/object:Gem::Version
|
|
31
|
-
version: 0.9.
|
|
31
|
+
version: 0.9.130
|
|
32
32
|
- !ruby/object:Gem::Dependency
|
|
33
33
|
name: rubyzip
|
|
34
34
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -88,6 +88,7 @@ files:
|
|
|
88
88
|
- ext/typst/src/compiler.rs
|
|
89
89
|
- ext/typst/src/download.rs
|
|
90
90
|
- ext/typst/src/lib.rs
|
|
91
|
+
- ext/typst/src/nogvl.rs
|
|
91
92
|
- ext/typst/src/package.rs
|
|
92
93
|
- ext/typst/src/query.rs
|
|
93
94
|
- ext/typst/src/world.rs
|