typst 0.0.5 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +38 -4
- data/README.typ +38 -4
- data/ext/typst/Cargo.toml +30 -15
- data/ext/typst/src/compiler-new.rs +771 -0
- data/ext/typst/src/compiler.rs +100 -39
- data/ext/typst/src/download.rs +11 -71
- data/ext/typst/src/lib.rs +218 -21
- data/ext/typst/src/query.rs +131 -0
- data/ext/typst/src/world.rs +130 -136
- data/lib/typst.rb +61 -2
- metadata +19 -9
- data/ext/typst/Cargo.lock +0 -2840
- data/ext/typst/src/lib.wip.rs +0 -203
@@ -0,0 +1,131 @@
|
|
1
|
+
use comemo::Track;
|
2
|
+
use ecow::{eco_format, EcoString};
|
3
|
+
use serde::Serialize;
|
4
|
+
use typst::diag::{bail, StrResult, Warned};
|
5
|
+
use typst::foundations::{Content, IntoValue, LocatableSelector, Scope};
|
6
|
+
use typst::layout::PagedDocument;
|
7
|
+
use typst::syntax::Span;
|
8
|
+
use typst::World;
|
9
|
+
use typst_eval::{eval_string, EvalMode};
|
10
|
+
|
11
|
+
use crate::world::SystemWorld;
|
12
|
+
|
13
|
+
/// Processes an input file to extract provided metadata
|
14
|
+
#[derive(Debug, Clone)]
|
15
|
+
pub struct QueryCommand {
|
16
|
+
/// Defines which elements to retrieve
|
17
|
+
pub selector: String,
|
18
|
+
|
19
|
+
/// Extracts just one field from all retrieved elements
|
20
|
+
pub field: Option<String>,
|
21
|
+
|
22
|
+
/// Expects and retrieves exactly one element
|
23
|
+
pub one: bool,
|
24
|
+
|
25
|
+
/// The format to serialize in
|
26
|
+
pub format: SerializationFormat,
|
27
|
+
}
|
28
|
+
|
29
|
+
// Output file format for query command
|
30
|
+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
31
|
+
pub enum SerializationFormat {
|
32
|
+
Json,
|
33
|
+
Yaml,
|
34
|
+
}
|
35
|
+
|
36
|
+
/// Execute a query command.
|
37
|
+
pub fn query(world: &mut SystemWorld, command: &QueryCommand) -> StrResult<String> {
|
38
|
+
// Reset everything and ensure that the main file is present.
|
39
|
+
world.reset();
|
40
|
+
world.source(world.main()).map_err(|err| err.to_string())?;
|
41
|
+
|
42
|
+
let Warned { output, warnings } = typst::compile(world);
|
43
|
+
|
44
|
+
match output {
|
45
|
+
// Retrieve and print query results.
|
46
|
+
Ok(document) => {
|
47
|
+
let data = retrieve(world, command, &document)?;
|
48
|
+
let serialized = format(data, command)?;
|
49
|
+
Ok(serialized)
|
50
|
+
}
|
51
|
+
// Print errors and warnings.
|
52
|
+
Err(errors) => {
|
53
|
+
let mut message = EcoString::from("failed to compile document");
|
54
|
+
for (i, error) in errors.into_iter().enumerate() {
|
55
|
+
message.push_str(if i == 0 { ": " } else { ", " });
|
56
|
+
message.push_str(&error.message);
|
57
|
+
}
|
58
|
+
for warning in warnings {
|
59
|
+
message.push_str(": ");
|
60
|
+
message.push_str(&warning.message);
|
61
|
+
}
|
62
|
+
Err(message)
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
/// Retrieve the matches for the selector.
|
68
|
+
fn retrieve(
|
69
|
+
world: &dyn World,
|
70
|
+
command: &QueryCommand,
|
71
|
+
document: &PagedDocument,
|
72
|
+
) -> StrResult<Vec<Content>> {
|
73
|
+
let selector = eval_string(
|
74
|
+
&typst::ROUTINES,
|
75
|
+
world.track(),
|
76
|
+
&command.selector,
|
77
|
+
Span::detached(),
|
78
|
+
EvalMode::Code,
|
79
|
+
Scope::default(),
|
80
|
+
)
|
81
|
+
.map_err(|errors| {
|
82
|
+
let mut message = EcoString::from("failed to evaluate selector");
|
83
|
+
for (i, error) in errors.into_iter().enumerate() {
|
84
|
+
message.push_str(if i == 0 { ": " } else { ", " });
|
85
|
+
message.push_str(&error.message);
|
86
|
+
}
|
87
|
+
message
|
88
|
+
})?
|
89
|
+
.cast::<LocatableSelector>()
|
90
|
+
.map_err(|e| e.message().clone())?;
|
91
|
+
|
92
|
+
Ok(document
|
93
|
+
.introspector
|
94
|
+
.query(&selector.0)
|
95
|
+
.into_iter()
|
96
|
+
.collect::<Vec<_>>())
|
97
|
+
}
|
98
|
+
|
99
|
+
/// Format the query result in the output format.
|
100
|
+
fn format(elements: Vec<Content>, command: &QueryCommand) -> StrResult<String> {
|
101
|
+
if command.one && elements.len() != 1 {
|
102
|
+
bail!("expected exactly one element, found {}", elements.len());
|
103
|
+
}
|
104
|
+
|
105
|
+
let mapped: Vec<_> = elements
|
106
|
+
.into_iter()
|
107
|
+
.filter_map(|c| match &command.field {
|
108
|
+
Some(field) => c.get_by_name(field).ok(),
|
109
|
+
_ => Some(c.into_value()),
|
110
|
+
})
|
111
|
+
.collect();
|
112
|
+
|
113
|
+
if command.one {
|
114
|
+
let Some(value) = mapped.first() else {
|
115
|
+
bail!("no such field found for element");
|
116
|
+
};
|
117
|
+
serialize(value, command.format)
|
118
|
+
} else {
|
119
|
+
serialize(&mapped, command.format)
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
/// Serialize data to the output format.
|
124
|
+
fn serialize(data: &impl Serialize, format: SerializationFormat) -> StrResult<String> {
|
125
|
+
match format {
|
126
|
+
SerializationFormat::Json => {
|
127
|
+
serde_json::to_string_pretty(data).map_err(|e| eco_format!("{e}"))
|
128
|
+
}
|
129
|
+
SerializationFormat::Yaml => serde_yaml::to_string(&data).map_err(|e| eco_format!("{e}")),
|
130
|
+
}
|
131
|
+
}
|
data/ext/typst/src/world.rs
CHANGED
@@ -1,24 +1,23 @@
|
|
1
|
-
use std::cell::{Cell, OnceCell, RefCell, RefMut};
|
2
|
-
use std::collections::HashMap;
|
3
1
|
use std::fs;
|
4
|
-
use std::hash::Hash;
|
5
2
|
use std::path::{Path, PathBuf};
|
6
|
-
|
7
|
-
use log::{debug};
|
3
|
+
use std::sync::{Mutex, OnceLock};
|
8
4
|
|
9
5
|
use chrono::{DateTime, Datelike, Local};
|
10
|
-
use
|
11
|
-
use filetime::FileTime;
|
12
|
-
use same_file::Handle;
|
13
|
-
use siphasher::sip128::{Hasher128, SipHasher13};
|
6
|
+
use ecow::eco_format;
|
14
7
|
use typst::diag::{FileError, FileResult, StrResult};
|
15
|
-
use typst::
|
16
|
-
use typst::font::{Font, FontBook, FontVariant};
|
8
|
+
use typst::foundations::{Bytes, Datetime, Dict};
|
17
9
|
use typst::syntax::{FileId, Source, VirtualPath};
|
18
|
-
use typst::
|
10
|
+
use typst::text::{Font, FontBook};
|
11
|
+
use typst::utils::LazyHash;
|
12
|
+
use typst::{Features, Library, LibraryBuilder, World};
|
13
|
+
use typst_kit::{
|
14
|
+
fonts::{FontSearcher, FontSlot},
|
15
|
+
package::PackageStorage,
|
16
|
+
};
|
17
|
+
|
18
|
+
use std::collections::HashMap;
|
19
19
|
|
20
|
-
use crate::
|
21
|
-
use crate::package::prepare_package;
|
20
|
+
use crate::download::SlientDownload;
|
22
21
|
|
23
22
|
/// A world that provides access to the operating system.
|
24
23
|
pub struct SystemWorld {
|
@@ -31,41 +30,39 @@ pub struct SystemWorld {
|
|
31
30
|
/// The input path.
|
32
31
|
main: FileId,
|
33
32
|
/// Typst's standard library.
|
34
|
-
library:
|
33
|
+
library: LazyHash<Library>,
|
35
34
|
/// Metadata about discovered fonts.
|
36
|
-
book:
|
35
|
+
book: LazyHash<FontBook>,
|
37
36
|
/// Locations of and storage for lazily loaded fonts.
|
38
37
|
fonts: Vec<FontSlot>,
|
39
|
-
/// Maps
|
40
|
-
|
41
|
-
///
|
42
|
-
|
43
|
-
/// Maps canonical path hashes to source files and buffers.
|
44
|
-
slots: RefCell<HashMap<PathHash, PathSlot>>,
|
38
|
+
/// Maps file ids to source files and buffers.
|
39
|
+
slots: Mutex<HashMap<FileId, FileSlot>>,
|
40
|
+
/// Holds information about where packages are stored.
|
41
|
+
package_storage: PackageStorage,
|
45
42
|
/// The current datetime if requested. This is stored here to ensure it is
|
46
43
|
/// always the same within one compilation. Reset between compilations.
|
47
|
-
now:
|
44
|
+
now: OnceLock<DateTime<Local>>,
|
48
45
|
}
|
49
46
|
|
50
47
|
impl World for SystemWorld {
|
51
|
-
fn library(&self) -> &
|
48
|
+
fn library(&self) -> &LazyHash<Library> {
|
52
49
|
&self.library
|
53
50
|
}
|
54
51
|
|
55
|
-
fn book(&self) -> &
|
52
|
+
fn book(&self) -> &LazyHash<FontBook> {
|
56
53
|
&self.book
|
57
54
|
}
|
58
55
|
|
59
|
-
fn main(&self) ->
|
60
|
-
self.
|
56
|
+
fn main(&self) -> FileId {
|
57
|
+
self.main
|
61
58
|
}
|
62
59
|
|
63
60
|
fn source(&self, id: FileId) -> FileResult<Source> {
|
64
|
-
self.slot(id
|
61
|
+
self.slot(id, |slot| slot.source(&self.root, &self.package_storage))
|
65
62
|
}
|
66
63
|
|
67
64
|
fn file(&self, id: FileId) -> FileResult<Bytes> {
|
68
|
-
self.slot(id
|
65
|
+
self.slot(id, |slot| slot.file(&self.root, &self.package_storage))
|
69
66
|
}
|
70
67
|
|
71
68
|
fn font(&self, index: usize) -> Option<Font> {
|
@@ -94,34 +91,12 @@ impl SystemWorld {
|
|
94
91
|
}
|
95
92
|
|
96
93
|
/// Access the canonical slot for the given file id.
|
97
|
-
fn slot(&self, id: FileId) ->
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
.or_insert_with(|| {
|
104
|
-
// Determine the root path relative to which the file path
|
105
|
-
// will be resolved.
|
106
|
-
let buf;
|
107
|
-
let mut root = &self.root;
|
108
|
-
if let Some(spec) = id.package() {
|
109
|
-
buf = prepare_package(spec)?;
|
110
|
-
root = &buf;
|
111
|
-
}
|
112
|
-
// Join the path to the root. If it tries to escape, deny
|
113
|
-
// access. Note: It can still escape via symlinks.
|
114
|
-
system_path = id.vpath().resolve(root).ok_or(FileError::AccessDenied)?;
|
115
|
-
|
116
|
-
PathHash::new(&system_path)
|
117
|
-
})
|
118
|
-
.clone()?;
|
119
|
-
|
120
|
-
Ok(RefMut::map(self.slots.borrow_mut(), |paths| {
|
121
|
-
paths
|
122
|
-
.entry(hash)
|
123
|
-
.or_insert_with(|| PathSlot::new(id, system_path))
|
124
|
-
}))
|
94
|
+
fn slot<F, T>(&self, id: FileId, f: F) -> T
|
95
|
+
where
|
96
|
+
F: FnOnce(&mut FileSlot) -> T,
|
97
|
+
{
|
98
|
+
let mut map = self.slots.lock().unwrap();
|
99
|
+
f(map.entry(id).or_insert_with(|| FileSlot::new(id)))
|
125
100
|
}
|
126
101
|
|
127
102
|
/// The id of the main source file.
|
@@ -141,8 +116,7 @@ impl SystemWorld {
|
|
141
116
|
|
142
117
|
/// Reset the compilation state in preparation of a new compilation.
|
143
118
|
pub fn reset(&mut self) {
|
144
|
-
self.
|
145
|
-
for slot in self.slots.borrow_mut().values_mut() {
|
119
|
+
for slot in self.slots.lock().unwrap().values_mut() {
|
146
120
|
slot.reset();
|
147
121
|
}
|
148
122
|
self.now.take();
|
@@ -165,7 +139,9 @@ pub struct SystemWorldBuilder {
|
|
165
139
|
root: PathBuf,
|
166
140
|
main: PathBuf,
|
167
141
|
font_paths: Vec<PathBuf>,
|
168
|
-
|
142
|
+
ignore_system_fonts: bool,
|
143
|
+
inputs: Dict,
|
144
|
+
features: Features,
|
169
145
|
}
|
170
146
|
|
171
147
|
impl SystemWorldBuilder {
|
@@ -174,7 +150,9 @@ impl SystemWorldBuilder {
|
|
174
150
|
root,
|
175
151
|
main,
|
176
152
|
font_paths: Vec::new(),
|
177
|
-
|
153
|
+
ignore_system_fonts: false,
|
154
|
+
inputs: Dict::default(),
|
155
|
+
features: Features::default(),
|
178
156
|
}
|
179
157
|
}
|
180
158
|
|
@@ -183,21 +161,25 @@ impl SystemWorldBuilder {
|
|
183
161
|
self
|
184
162
|
}
|
185
163
|
|
186
|
-
pub fn
|
187
|
-
self.
|
164
|
+
pub fn ignore_system_fonts(mut self, ignore: bool) -> Self {
|
165
|
+
self.ignore_system_fonts = ignore;
|
188
166
|
self
|
189
167
|
}
|
190
168
|
|
191
|
-
pub fn
|
192
|
-
|
193
|
-
|
169
|
+
pub fn inputs(mut self, inputs: Dict) -> Self {
|
170
|
+
self.inputs = inputs;
|
171
|
+
self
|
172
|
+
}
|
194
173
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
174
|
+
pub fn features(mut self, features: Features) -> Self {
|
175
|
+
self.features = features;
|
176
|
+
self
|
177
|
+
}
|
178
|
+
|
179
|
+
pub fn build(self) -> StrResult<SystemWorld> {
|
180
|
+
let fonts = FontSearcher::new()
|
181
|
+
.include_system_fonts(!self.ignore_system_fonts)
|
182
|
+
.search_with(&self.font_paths);
|
201
183
|
|
202
184
|
let input = self.main.canonicalize().map_err(|_| {
|
203
185
|
eco_format!("input file not found (searched at {})", self.main.display())
|
@@ -211,12 +193,12 @@ impl SystemWorldBuilder {
|
|
211
193
|
input,
|
212
194
|
root: self.root,
|
213
195
|
main: FileId::new(None, main_path),
|
214
|
-
library:
|
215
|
-
book:
|
216
|
-
fonts:
|
217
|
-
|
218
|
-
|
219
|
-
now:
|
196
|
+
library: LazyHash::new(LibraryBuilder::default().with_inputs(self.inputs).with_features(self.features).build()),
|
197
|
+
book: LazyHash::new(fonts.book),
|
198
|
+
fonts: fonts.fonts,
|
199
|
+
slots: Mutex::default(),
|
200
|
+
package_storage: PackageStorage::new(None, None, crate::download::downloader()),
|
201
|
+
now: OnceLock::new(),
|
220
202
|
};
|
221
203
|
Ok(world)
|
222
204
|
}
|
@@ -225,23 +207,20 @@ impl SystemWorldBuilder {
|
|
225
207
|
/// Holds canonical data for all paths pointing to the same entity.
|
226
208
|
///
|
227
209
|
/// Both fields can be populated if the file is both imported and read().
|
228
|
-
struct
|
210
|
+
struct FileSlot {
|
229
211
|
/// The slot's canonical file id.
|
230
212
|
id: FileId,
|
231
|
-
/// The slot's path on the system.
|
232
|
-
path: PathBuf,
|
233
213
|
/// The lazily loaded and incrementally updated source file.
|
234
214
|
source: SlotCell<Source>,
|
235
215
|
/// The lazily loaded raw byte buffer.
|
236
216
|
file: SlotCell<Bytes>,
|
237
217
|
}
|
238
218
|
|
239
|
-
impl
|
219
|
+
impl FileSlot {
|
240
220
|
/// Create a new path slot.
|
241
|
-
fn new(id: FileId
|
221
|
+
fn new(id: FileId) -> Self {
|
242
222
|
Self {
|
243
223
|
id,
|
244
|
-
path,
|
245
224
|
file: SlotCell::new(),
|
246
225
|
source: SlotCell::new(),
|
247
226
|
}
|
@@ -249,92 +228,107 @@ impl PathSlot {
|
|
249
228
|
|
250
229
|
/// Marks the file as not yet accessed in preparation of the next
|
251
230
|
/// compilation.
|
252
|
-
fn reset(&self) {
|
231
|
+
fn reset(&mut self) {
|
253
232
|
self.source.reset();
|
254
233
|
self.file.reset();
|
255
234
|
}
|
256
235
|
|
257
|
-
fn source(&self) -> FileResult<Source> {
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
236
|
+
fn source(&mut self, root: &Path, package_storage: &PackageStorage) -> FileResult<Source> {
|
237
|
+
let id = self.id;
|
238
|
+
self.source.get_or_init(
|
239
|
+
|| system_path(root, id, package_storage),
|
240
|
+
|data, prev| {
|
241
|
+
let text = decode_utf8(&data)?;
|
242
|
+
if let Some(mut prev) = prev {
|
243
|
+
prev.replace(text);
|
244
|
+
Ok(prev)
|
245
|
+
} else {
|
246
|
+
Ok(Source::new(self.id, text.into()))
|
247
|
+
}
|
248
|
+
},
|
249
|
+
)
|
250
|
+
}
|
251
|
+
|
252
|
+
fn file(&mut self, root: &Path, package_storage: &PackageStorage) -> FileResult<Bytes> {
|
253
|
+
let id = self.id;
|
254
|
+
self.file.get_or_init(
|
255
|
+
|| system_path(root, id, package_storage),
|
256
|
+
|data, _| Ok(Bytes::new(data)),
|
257
|
+
)
|
267
258
|
}
|
259
|
+
}
|
268
260
|
|
269
|
-
|
270
|
-
|
261
|
+
/// The path of the slot on the system.
|
262
|
+
fn system_path(root: &Path, id: FileId, package_storage: &PackageStorage) -> FileResult<PathBuf> {
|
263
|
+
// Determine the root path relative to which the file path
|
264
|
+
// will be resolved.
|
265
|
+
let buf;
|
266
|
+
let mut root = root;
|
267
|
+
if let Some(spec) = id.package() {
|
268
|
+
buf = package_storage.prepare_package(spec, &mut SlientDownload(&spec))?;
|
269
|
+
root = &buf;
|
271
270
|
}
|
271
|
+
|
272
|
+
// Join the path to the root. If it tries to escape, deny
|
273
|
+
// access. Note: It can still escape via symlinks.
|
274
|
+
id.vpath().resolve(root).ok_or(FileError::AccessDenied)
|
272
275
|
}
|
273
276
|
|
274
277
|
/// Lazily processes data for a file.
|
275
278
|
struct SlotCell<T> {
|
276
|
-
data
|
277
|
-
|
278
|
-
|
279
|
+
/// The processed data.
|
280
|
+
data: Option<FileResult<T>>,
|
281
|
+
/// A hash of the raw file contents / access error.
|
282
|
+
fingerprint: u128,
|
283
|
+
/// Whether the slot has been accessed in the current compilation.
|
284
|
+
accessed: bool,
|
279
285
|
}
|
280
286
|
|
281
287
|
impl<T: Clone> SlotCell<T> {
|
282
288
|
/// Creates a new, empty cell.
|
283
289
|
fn new() -> Self {
|
284
290
|
Self {
|
285
|
-
data:
|
286
|
-
|
287
|
-
accessed:
|
291
|
+
data: None,
|
292
|
+
fingerprint: 0,
|
293
|
+
accessed: false,
|
288
294
|
}
|
289
295
|
}
|
290
296
|
|
291
297
|
/// Marks the cell as not yet accessed in preparation of the next
|
292
298
|
/// compilation.
|
293
|
-
fn reset(&self) {
|
294
|
-
self.accessed
|
299
|
+
fn reset(&mut self) {
|
300
|
+
self.accessed = false;
|
295
301
|
}
|
296
302
|
|
297
303
|
/// Gets the contents of the cell or initialize them.
|
298
304
|
fn get_or_init(
|
299
|
-
&self,
|
300
|
-
path:
|
305
|
+
&mut self,
|
306
|
+
path: impl FnOnce() -> FileResult<PathBuf>,
|
301
307
|
f: impl FnOnce(Vec<u8>, Option<T>) -> FileResult<T>,
|
302
308
|
) -> FileResult<T> {
|
303
|
-
|
304
|
-
if
|
305
|
-
if
|
309
|
+
// If we accessed the file already in this compilation, retrieve it.
|
310
|
+
if std::mem::replace(&mut self.accessed, true) {
|
311
|
+
if let Some(data) = &self.data {
|
306
312
|
return data.clone();
|
307
313
|
}
|
308
314
|
}
|
309
315
|
|
310
|
-
|
311
|
-
|
312
|
-
let
|
313
|
-
let value = read(path).and_then(|data| f(data, prev));
|
314
|
-
*borrow = Some(value.clone());
|
315
|
-
value
|
316
|
-
}
|
316
|
+
// Read and hash the file.
|
317
|
+
let result = path().and_then(|p| read(&p));
|
318
|
+
let fingerprint = typst::utils::hash128(&result);
|
317
319
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
}
|
324
|
-
|
325
|
-
|
320
|
+
// If the file contents didn't change, yield the old processed data.
|
321
|
+
if std::mem::replace(&mut self.fingerprint, fingerprint) == fingerprint {
|
322
|
+
if let Some(data) = &self.data {
|
323
|
+
return data.clone();
|
324
|
+
}
|
325
|
+
}
|
326
|
+
|
327
|
+
let prev = self.data.take().and_then(Result::ok);
|
328
|
+
let value = result.and_then(|data| f(data, prev));
|
329
|
+
self.data = Some(value.clone());
|
326
330
|
|
327
|
-
|
328
|
-
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
329
|
-
struct PathHash(u128);
|
330
|
-
|
331
|
-
impl PathHash {
|
332
|
-
fn new(path: &Path) -> FileResult<Self> {
|
333
|
-
let f = |e| FileError::from_io(e, path);
|
334
|
-
let handle = Handle::from_path(path).map_err(f)?;
|
335
|
-
let mut state = SipHasher13::new();
|
336
|
-
handle.hash(&mut state);
|
337
|
-
Ok(Self(state.finish128().as_u128()))
|
331
|
+
value
|
338
332
|
}
|
339
333
|
}
|
340
334
|
|
data/lib/typst.rb
CHANGED
@@ -78,7 +78,7 @@ module Typst
|
|
78
78
|
|
79
79
|
def initialize(input, root: ".", font_paths: [])
|
80
80
|
super(input, root: root, font_paths: font_paths)
|
81
|
-
@bytes = Typst::_to_pdf(self.input, self.root, self.font_paths, File.dirname(__FILE__))
|
81
|
+
@bytes = Typst::_to_pdf(self.input, self.root, self.font_paths, File.dirname(__FILE__), false, {})[0]
|
82
82
|
end
|
83
83
|
|
84
84
|
def document
|
@@ -91,7 +91,33 @@ module Typst
|
|
91
91
|
|
92
92
|
def initialize(input, root: ".", font_paths: [])
|
93
93
|
super(input, root: root, font_paths: font_paths)
|
94
|
-
@pages = Typst::_to_svg(self.input, self.root, self.font_paths, File.dirname(__FILE__))
|
94
|
+
@pages = Typst::_to_svg(self.input, self.root, self.font_paths, File.dirname(__FILE__), false, {}).collect{ |page| page.pack("C*").to_s }
|
95
|
+
end
|
96
|
+
|
97
|
+
def write(output)
|
98
|
+
if pages.size > 1
|
99
|
+
pages.each_with_index do |page, i|
|
100
|
+
if output.include?("{{n}}")
|
101
|
+
file_name = output.gsub("{{n}}", (i+1).to_s)
|
102
|
+
else
|
103
|
+
file_name = File.basename(output, File.extname(output)) + "_" + i.to_s
|
104
|
+
file_name = file_name + File.extname(output)
|
105
|
+
end
|
106
|
+
File.open(file_name, "w"){ |f| f.write(page) }
|
107
|
+
end
|
108
|
+
elsif pages.size == 1
|
109
|
+
File.open(output, "w"){ |f| f.write(pages[0]) }
|
110
|
+
else
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class Png < Base
|
116
|
+
attr_accessor :pages
|
117
|
+
|
118
|
+
def initialize(input, root: ".", font_paths: [])
|
119
|
+
super(input, root: root, font_paths: font_paths)
|
120
|
+
@pages = Typst::_to_png(self.input, self.root, self.font_paths, File.dirname(__FILE__), false, {}).collect{ |page| page.pack("C*").to_s }
|
95
121
|
end
|
96
122
|
|
97
123
|
def write(output)
|
@@ -139,4 +165,37 @@ module Typst
|
|
139
165
|
end
|
140
166
|
alias_method :document, :markup
|
141
167
|
end
|
168
|
+
|
169
|
+
class HtmlExperimental < Base
|
170
|
+
attr_accessor :bytes
|
171
|
+
|
172
|
+
def initialize(input, root: ".", font_paths: [])
|
173
|
+
super(input, root: root, font_paths: font_paths)
|
174
|
+
@bytes = Typst::_to_html(self.input, self.root, self.font_paths, File.dirname(__FILE__), false, {})[0]
|
175
|
+
end
|
176
|
+
|
177
|
+
def document
|
178
|
+
bytes.pack("C*").to_s
|
179
|
+
end
|
180
|
+
alias_method :markup, :document
|
181
|
+
end
|
182
|
+
|
183
|
+
class Query < Base
|
184
|
+
attr_accessor :format
|
185
|
+
attr_accessor :result
|
186
|
+
|
187
|
+
def initialize(selector, input, field: nil, one: false, format: "json", root: ".", font_paths: [])
|
188
|
+
super(input, root: root, font_paths: font_paths)
|
189
|
+
self.format = format
|
190
|
+
self.result = Typst::_query(selector, field, one, format, self.input, self.root, self.font_paths, File.dirname(__FILE__), false, {})
|
191
|
+
end
|
192
|
+
|
193
|
+
def result(raw: false)
|
194
|
+
case raw || format
|
195
|
+
when "json" then JSON(@result)
|
196
|
+
when "yaml" then YAML::safe_load(@result)
|
197
|
+
else @result
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
142
201
|
end
|