typst 0.15.1.6 → 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/Cargo.toml +4 -1
- data/README.md +55 -1
- data/README.typ +56 -0
- data/Rakefile +10 -2
- data/ext/typst/Cargo.toml +1 -1
- data/ext/typst/extconf.rb +11 -0
- data/ext/typst/src/lib.rs +150 -36
- data/ext/typst/src/nogvl.rs +49 -0
- data/ext/typst/src/world.rs +76 -5
- data/lib/base.rb +2 -1
- data/lib/query.rb +2 -2
- data/lib/typst.rb +23 -6
- metadata +5 -4
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/Cargo.toml
CHANGED
data/README.md
CHANGED
|
@@ -263,13 +263,67 @@ 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)\
|
|
269
321
|
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
|
-
Compiler warnings were contributed by [TheSoloHacker47](https://github.com/TheSoloHacker47)
|
|
324
|
+
Compiler warnings were contributed by [TheSoloHacker47](https://github.com/TheSoloHacker47) \
|
|
325
|
+
Font optimization patches were contributed by [dmke](https://github.com/dmke) \
|
|
326
|
+
Concurrency patches were contributed by [dmke](https://github.com/dmke)
|
|
273
327
|
|
|
274
328
|
## License
|
|
275
329
|
|
data/README.typ
CHANGED
|
@@ -270,6 +270,61 @@ 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]\
|
|
@@ -277,6 +332,7 @@ 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
334
|
Compiler warnings were contributed by #link("https://github.com/TheSoloHacker47")[TheSoloHacker47]
|
|
335
|
+
Concurrency patches were contributed by #link("https://github.com/dmke")[dmke]
|
|
280
336
|
|
|
281
337
|
== License
|
|
282
338
|
|
data/Rakefile
CHANGED
|
@@ -30,10 +30,18 @@ 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
|
|
|
39
|
+
task 'benchmark' do |t|
|
|
40
|
+
Dir.glob("benchmarks/*.rb").each do |b|
|
|
41
|
+
sh "bundle exec ruby #{b}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
37
45
|
task 'gem:native' do |t|
|
|
38
46
|
CROSS_PLATFORMS.each do |platform|
|
|
39
47
|
sh "bundle exec rb-sys-dock --platform #{platform} --build"
|
|
@@ -45,4 +53,4 @@ task 'gem:native:push' do |t|
|
|
|
45
53
|
CROSS_PLATFORMS.each do |platform|
|
|
46
54
|
sh "gem push pkg/typst-#{spec.version}-#{platform}.gem"
|
|
47
55
|
end
|
|
48
|
-
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,24 +364,53 @@ 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) {
|
|
287
396
|
comemo::evict(max_age);
|
|
288
397
|
}
|
|
289
398
|
|
|
399
|
+
fn clear_font_cache(_ruby: &Ruby) {
|
|
400
|
+
world::clear_font_cache();
|
|
401
|
+
}
|
|
402
|
+
|
|
290
403
|
#[magnus::init]
|
|
291
404
|
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
292
405
|
env_logger::init();
|
|
293
406
|
|
|
294
407
|
let module = ruby.define_module("Typst")?;
|
|
295
|
-
module.define_singleton_method("_to_pdf", function!(
|
|
296
|
-
module.define_singleton_method("_to_svg", function!(
|
|
297
|
-
module.define_singleton_method("_to_png", function!(
|
|
298
|
-
module.define_singleton_method("_to_html", function!(
|
|
299
|
-
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))?;
|
|
300
413
|
module.define_singleton_method("_clear_cache", function!(clear_cache, 1))?;
|
|
414
|
+
module.define_singleton_method("_clear_font_cache", function!(clear_font_cache, 0))?;
|
|
301
415
|
Ok(())
|
|
302
416
|
}
|
|
@@ -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/ext/typst/src/world.rs
CHANGED
|
@@ -7,10 +7,10 @@ use chrono::{DateTime, Datelike, FixedOffset, Local};
|
|
|
7
7
|
use typst::diag::{FileError, FileResult, StrResult};
|
|
8
8
|
use typst::foundations::{Bytes, Datetime, Dict, Duration};
|
|
9
9
|
use typst::syntax::{FileId, Lines, Source, VirtualPath, VirtualRoot, RootedPath};
|
|
10
|
-
use typst::text::{Font, FontBook};
|
|
10
|
+
use typst::text::{Font, FontBook, FontInfo};
|
|
11
11
|
use typst::utils::LazyHash;
|
|
12
12
|
use typst::{Features, Library, LibraryExt, World};
|
|
13
|
-
use typst_kit::fonts::{self, FontStore};
|
|
13
|
+
use typst_kit::fonts::{self, FontPath, FontStore};
|
|
14
14
|
use typst_kit::packages::{FsPackages, SystemPackages, UniversePackages};
|
|
15
15
|
|
|
16
16
|
/// A world that provides access to the operating system.
|
|
@@ -195,7 +195,7 @@ impl SystemWorldBuilder {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
pub fn build(self) -> StrResult<SystemWorld> {
|
|
198
|
-
let fonts =
|
|
198
|
+
let fonts = font_store(self.ignore_system_fonts, self.ignore_embedded_fonts, self.font_paths);
|
|
199
199
|
|
|
200
200
|
let package_storage = system_packages(self.package_path, self.package_cache_path);
|
|
201
201
|
|
|
@@ -217,13 +217,84 @@ impl SystemWorldBuilder {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
/// Font discovery, which is far too slow to repeat on every compile.
|
|
221
|
+
///
|
|
222
|
+
/// A full system font scan takes about 95 ms, which for a small document
|
|
223
|
+
/// dwarfs the compilation itself. `FontStore` cannot be cloned, so the
|
|
224
|
+
/// discovered fonts are kept alongside the assembled stores.
|
|
225
|
+
struct FontCache {
|
|
226
|
+
/// The locations found by walking the system font directories.
|
|
227
|
+
system: Option<Arc<Vec<(PathBuf, u32, FontInfo)>>>,
|
|
228
|
+
/// The fonts shipped with typst, already parsed.
|
|
229
|
+
embedded: Option<Arc<Vec<(Font, FontInfo)>>>,
|
|
230
|
+
/// A store per combination of the two ignore flags, for compiles that
|
|
231
|
+
/// bring no font paths of their own. Sharing one keeps the fonts a
|
|
232
|
+
/// document uses loaded between compiles and hashes the book only once.
|
|
233
|
+
stores: [Option<Arc<FontStore>>; 4],
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const EMPTY_FONT_CACHE: FontCache =
|
|
237
|
+
FontCache { system: None, embedded: None, stores: [const { None }; 4] };
|
|
238
|
+
|
|
239
|
+
static FONT_CACHE: Mutex<FontCache> = Mutex::new(EMPTY_FONT_CACHE);
|
|
240
|
+
|
|
241
|
+
/// Forgets everything discovered so far, so that the next compile picks up
|
|
242
|
+
/// fonts installed or removed since.
|
|
243
|
+
pub fn clear_font_cache() {
|
|
244
|
+
*FONT_CACHE.lock().unwrap() = EMPTY_FONT_CACHE;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// Reads a cache field, filling it on a miss.
|
|
248
|
+
///
|
|
249
|
+
/// The lock is not held while building, so two threads missing at once both
|
|
250
|
+
/// build and one result is dropped. That wastes a scan but keeps a 95 ms
|
|
251
|
+
/// build off the lock.
|
|
252
|
+
fn cached<T>(
|
|
253
|
+
select: impl Fn(&mut FontCache) -> &mut Option<Arc<T>>,
|
|
254
|
+
build: impl FnOnce() -> T,
|
|
255
|
+
) -> Arc<T> {
|
|
256
|
+
let hit = select(&mut FONT_CACHE.lock().unwrap()).clone();
|
|
257
|
+
if let Some(value) = hit {
|
|
258
|
+
return value;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let value = Arc::new(build());
|
|
262
|
+
*select(&mut FONT_CACHE.lock().unwrap()) = Some(value.clone());
|
|
263
|
+
value
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/// Obtains a font store for the given configuration.
|
|
267
|
+
///
|
|
268
|
+
/// Extra font paths are scanned on every compile. They are usually a
|
|
269
|
+
/// temporary directory written by `Typst.build_world_from_s`, so their
|
|
270
|
+
/// contents can change between compiles and caching on them would grow
|
|
271
|
+
/// without bound.
|
|
272
|
+
fn font_store(ignore_system_fonts: bool, ignore_embedded_fonts: bool, font_paths: Vec<PathBuf>) -> Arc<FontStore> {
|
|
273
|
+
if !font_paths.is_empty() {
|
|
274
|
+
return Arc::new(build_font_store(ignore_system_fonts, ignore_embedded_fonts, font_paths));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let index = usize::from(ignore_system_fonts) << 1 | usize::from(ignore_embedded_fonts);
|
|
278
|
+
cached(
|
|
279
|
+
move |cache| &mut cache.stores[index],
|
|
280
|
+
|| build_font_store(ignore_system_fonts, ignore_embedded_fonts, Vec::new()),
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
|
|
220
284
|
fn build_font_store(ignore_system_fonts: bool, ignore_embedded_fonts: bool, font_paths: Vec<PathBuf>) -> FontStore {
|
|
221
285
|
let mut fonts = FontStore::new();
|
|
222
286
|
if !ignore_system_fonts {
|
|
223
|
-
|
|
287
|
+
let system = cached(
|
|
288
|
+
|cache| &mut cache.system,
|
|
289
|
+
|| fonts::system().map(|(path, info)| (path.path, path.index, info)).collect(),
|
|
290
|
+
);
|
|
291
|
+
fonts.extend(system.iter().map(|(path, index, info)| {
|
|
292
|
+
(FontPath { path: path.clone(), index: *index }, info.clone())
|
|
293
|
+
}));
|
|
224
294
|
}
|
|
225
295
|
if !ignore_embedded_fonts {
|
|
226
|
-
|
|
296
|
+
let embedded = cached(|cache| &mut cache.embedded, || fonts::embedded().collect());
|
|
297
|
+
fonts.extend(embedded.iter().cloned());
|
|
227
298
|
}
|
|
228
299
|
for path in font_paths {
|
|
229
300
|
fonts.extend(fonts::scan(&path));
|
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,10 +14,24 @@ 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
|
|
19
28
|
|
|
29
|
+
# Discards the discovered system and embedded fonts, so that the next
|
|
30
|
+
# compile picks up fonts installed or removed since the first one.
|
|
31
|
+
def self.clear_font_cache
|
|
32
|
+
Typst::_clear_font_cache
|
|
33
|
+
end
|
|
34
|
+
|
|
20
35
|
def self.build_world_from_s(main_source, **options, &blk)
|
|
21
36
|
dependencies = options[:dependencies] ||= {}
|
|
22
37
|
fonts = options[:fonts] ||= {}
|
|
@@ -30,16 +45,18 @@ module Typst
|
|
|
30
45
|
File.binwrite(tmp_dep_file, dep_source)
|
|
31
46
|
end
|
|
32
47
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
48
|
+
unless fonts.empty?
|
|
49
|
+
relative_font_path = Pathname.new(tmp_dir).join("fonts")
|
|
50
|
+
relative_font_path.mkpath
|
|
51
|
+
fonts.each do |font_name, font_bytes|
|
|
52
|
+
tmp_font_file = relative_font_path.join(font_name)
|
|
53
|
+
File.binwrite(tmp_font_file, font_bytes)
|
|
54
|
+
end
|
|
55
|
+
options[:font_paths] = (options[:font_paths] || []) + [relative_font_path]
|
|
38
56
|
end
|
|
39
57
|
|
|
40
58
|
options[:file] = tmp_main_file
|
|
41
59
|
options[:root] = tmp_dir
|
|
42
|
-
options[:font_paths] = [relative_font_path]
|
|
43
60
|
|
|
44
61
|
blk.call(options)
|
|
45
62
|
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
|
|
@@ -117,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
117
118
|
- !ruby/object:Gem::Version
|
|
118
119
|
version: '0'
|
|
119
120
|
requirements: []
|
|
120
|
-
rubygems_version: 4.0.
|
|
121
|
+
rubygems_version: 4.0.20
|
|
121
122
|
specification_version: 4
|
|
122
123
|
summary: Ruby binding to typst, a new markup-based typesetting system that is powerful
|
|
123
124
|
and easy to learn.
|