typst 0.0.4 → 0.13.0

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.
data/ext/typst/src/lib.rs CHANGED
@@ -1,22 +1,94 @@
1
1
  use std::path::PathBuf;
2
2
 
3
- use magnus::{define_module, function, exception, Error, IntoValue};
3
+ use magnus::{define_module, function, exception, Error }; //, IntoValue};
4
4
  use magnus::{prelude::*};
5
5
 
6
+ use std::collections::HashMap;
7
+ use query::{query as typst_query, QueryCommand, SerializationFormat};
8
+ use typst::foundations::{Dict, Value};
9
+ use typst_library::Feature;
6
10
  use world::SystemWorld;
7
11
 
8
12
  mod compiler;
9
13
  mod download;
10
- mod fonts;
11
- mod package;
14
+ mod query;
12
15
  mod world;
13
16
 
17
+ fn to_html(
18
+ input: PathBuf,
19
+ root: Option<PathBuf>,
20
+ font_paths: Vec<PathBuf>,
21
+ resource_path: PathBuf,
22
+ ignore_system_fonts: bool,
23
+ sys_inputs: HashMap<String, String>,
24
+ ) -> Result<Vec<Vec<u8>>, Error> {
25
+ let input = input.canonicalize()
26
+ .map_err(|err| magnus::Error::new(exception::arg_error(), err.to_string()))?;
27
+
28
+ let root = if let Some(root) = root {
29
+ root.canonicalize()
30
+ .map_err(|err| magnus::Error::new(exception::arg_error(), err.to_string()))?
31
+ } else if let Some(dir) = input.parent() {
32
+ dir.into()
33
+ } else {
34
+ PathBuf::new()
35
+ };
36
+
37
+ let resource_path = resource_path.canonicalize()
38
+ .map_err(|err| magnus::Error::new(exception::arg_error(), err.to_string()))?;
39
+
40
+ let mut default_fonts = Vec::new();
41
+ for entry in walkdir::WalkDir::new(resource_path.join("fonts")) {
42
+ let path = entry
43
+ .map_err(|err| magnus::Error::new(exception::arg_error(), err.to_string()))?
44
+ .into_path();
45
+ let Some(extension) = path.extension() else {
46
+ continue;
47
+ };
48
+ if extension == "ttf" || extension == "otf" {
49
+ default_fonts.push(path);
50
+ }
51
+ }
52
+
53
+ let mut features = Vec::new();
54
+ features.push(Feature::Html);
55
+
56
+ let feat = features.iter()
57
+ .map(|&feature|
58
+ match feature {
59
+ Feature::Html => typst::Feature::Html,
60
+ _ => typst::Feature::Html // TODO: fix this hack
61
+ }
62
+ )
63
+ .collect();
64
+
65
+ let mut world = SystemWorld::builder(root, input)
66
+ .inputs(Dict::from_iter(
67
+ sys_inputs
68
+ .into_iter()
69
+ .map(|(k, v)| (k.into(), Value::Str(v.into()))),
70
+ ))
71
+ .features(feat)
72
+ .font_paths(font_paths)
73
+ .ignore_system_fonts(ignore_system_fonts)
74
+ .build()
75
+ .map_err(|msg| magnus::Error::new(exception::arg_error(), msg.to_string()))?;
76
+
77
+ let bytes = world
78
+ .compile(Some("html"), None, &Vec::new())
79
+ .map_err(|msg| magnus::Error::new(exception::arg_error(), msg.to_string()))?;
80
+
81
+ Ok(bytes)
82
+ }
83
+
14
84
  fn to_svg(
15
85
  input: PathBuf,
16
86
  root: Option<PathBuf>,
17
87
  font_paths: Vec<PathBuf>,
18
88
  resource_path: PathBuf,
19
- ) -> Result<Vec<String>, Error> {
89
+ ignore_system_fonts: bool,
90
+ sys_inputs: HashMap<String, String>,
91
+ ) -> Result<Vec<Vec<u8>>, Error> {
20
92
  let input = input.canonicalize()
21
93
  .map_err(|err| magnus::Error::new(exception::arg_error(), err.to_string()))?;
22
94
 
@@ -46,24 +118,85 @@ fn to_svg(
46
118
  }
47
119
 
48
120
  let mut world = SystemWorld::builder(root, input)
121
+ .inputs(Dict::from_iter(
122
+ sys_inputs
123
+ .into_iter()
124
+ .map(|(k, v)| (k.into(), Value::Str(v.into()))),
125
+ ))
49
126
  .font_paths(font_paths)
50
- .font_files(default_fonts)
127
+ .ignore_system_fonts(ignore_system_fonts)
51
128
  .build()
52
129
  .map_err(|msg| magnus::Error::new(exception::arg_error(), msg.to_string()))?;
53
130
 
54
131
  let svg_bytes = world
55
- .compile_svg()
132
+ .compile(Some("svg"), None, &Vec::new())
56
133
  .map_err(|msg| magnus::Error::new(exception::arg_error(), msg.to_string()))?;
57
134
 
58
135
  Ok(svg_bytes)
59
136
  }
60
137
 
138
+ fn to_png(
139
+ input: PathBuf,
140
+ root: Option<PathBuf>,
141
+ font_paths: Vec<PathBuf>,
142
+ resource_path: PathBuf,
143
+ ignore_system_fonts: bool,
144
+ sys_inputs: HashMap<String, String>,
145
+ ) -> Result<Vec<Vec<u8>>, Error> {
146
+ let input = input.canonicalize()
147
+ .map_err(|err| magnus::Error::new(exception::arg_error(), err.to_string()))?;
148
+
149
+ let root = if let Some(root) = root {
150
+ root.canonicalize()
151
+ .map_err(|err| magnus::Error::new(exception::arg_error(), err.to_string()))?
152
+ } else if let Some(dir) = input.parent() {
153
+ dir.into()
154
+ } else {
155
+ PathBuf::new()
156
+ };
157
+
158
+ let resource_path = resource_path.canonicalize()
159
+ .map_err(|err| magnus::Error::new(exception::arg_error(), err.to_string()))?;
160
+
161
+ let mut default_fonts = Vec::new();
162
+ for entry in walkdir::WalkDir::new(resource_path.join("fonts")) {
163
+ let path = entry
164
+ .map_err(|err| magnus::Error::new(exception::arg_error(), err.to_string()))?
165
+ .into_path();
166
+ let Some(extension) = path.extension() else {
167
+ continue;
168
+ };
169
+ if extension == "ttf" || extension == "otf" {
170
+ default_fonts.push(path);
171
+ }
172
+ }
173
+
174
+ let mut world = SystemWorld::builder(root, input)
175
+ .inputs(Dict::from_iter(
176
+ sys_inputs
177
+ .into_iter()
178
+ .map(|(k, v)| (k.into(), Value::Str(v.into()))),
179
+ ))
180
+ .font_paths(font_paths)
181
+ .ignore_system_fonts(ignore_system_fonts)
182
+ .build()
183
+ .map_err(|msg| magnus::Error::new(exception::arg_error(), msg.to_string()))?;
184
+
185
+ let bytes = world
186
+ .compile(Some("png"), None, &Vec::new())
187
+ .map_err(|msg| magnus::Error::new(exception::arg_error(), msg.to_string()))?;
188
+
189
+ Ok(bytes)
190
+ }
191
+
61
192
  fn to_pdf(
62
193
  input: PathBuf,
63
194
  root: Option<PathBuf>,
64
195
  font_paths: Vec<PathBuf>,
65
196
  resource_path: PathBuf,
66
- ) -> Result<Vec<u8>, Error> {
197
+ ignore_system_fonts: bool,
198
+ sys_inputs: HashMap<String, String>,
199
+ ) -> Result<Vec<Vec<u8>>, Error> {
67
200
  let input = input.canonicalize()
68
201
  .map_err(|err| magnus::Error::new(exception::arg_error(), err.to_string()))?;
69
202
 
@@ -93,41 +226,105 @@ fn to_pdf(
93
226
  }
94
227
 
95
228
  let mut world = SystemWorld::builder(root, input)
229
+ .inputs(Dict::from_iter(
230
+ sys_inputs
231
+ .into_iter()
232
+ .map(|(k, v)| (k.into(), Value::Str(v.into()))),
233
+ ))
96
234
  .font_paths(font_paths)
97
- .font_files(default_fonts)
235
+ .ignore_system_fonts(ignore_system_fonts)
98
236
  .build()
99
237
  .map_err(|msg| magnus::Error::new(exception::arg_error(), msg.to_string()))?;
100
238
 
101
239
  let pdf_bytes = world
102
- .compile_pdf()
240
+ .compile(Some("pdf"), None, &Vec::new())
103
241
  .map_err(|msg| magnus::Error::new(exception::arg_error(), msg.to_string()))?;
104
242
 
105
243
  Ok(pdf_bytes)
106
244
  }
107
245
 
108
- fn write_pdf(
246
+ fn query(
247
+ selector: String,
248
+ field: Option<String>,
249
+ one: bool,
250
+ format: Option<String>,
109
251
  input: PathBuf,
110
- output: PathBuf,
111
252
  root: Option<PathBuf>,
112
253
  font_paths: Vec<PathBuf>,
113
254
  resource_path: PathBuf,
114
- ) -> Result<magnus::Value, Error> {
115
- let pdf_bytes = to_pdf(input, root, font_paths, resource_path)?;
255
+ ignore_system_fonts: bool,
256
+ sys_inputs: HashMap<String, String>,
257
+ ) -> Result<String, Error> {
258
+ let format = match format.unwrap().to_ascii_lowercase().as_str() {
259
+ "json" => SerializationFormat::Json,
260
+ "yaml" => SerializationFormat::Yaml,
261
+ _ => return Err(magnus::Error::new(exception::arg_error(), "unsupported serialization format"))?,
262
+ };
116
263
 
117
- std::fs::write(output, pdf_bytes)
118
- .map_err(|_| magnus::Error::new(exception::arg_error(), "error"))?;
264
+ let input = input.canonicalize()
265
+ .map_err(|err| magnus::Error::new(exception::arg_error(), err.to_string()))?;
119
266
 
120
- let value = true.into_value();
121
- Ok(value)
122
- }
267
+ let root = if let Some(root) = root {
268
+ root.canonicalize()
269
+ .map_err(|err| magnus::Error::new(exception::arg_error(), err.to_string()))?
270
+ } else if let Some(dir) = input.parent() {
271
+ dir.into()
272
+ } else {
273
+ PathBuf::new()
274
+ };
275
+
276
+ let resource_path = resource_path.canonicalize()
277
+ .map_err(|err| magnus::Error::new(exception::arg_error(), err.to_string()))?;
278
+
279
+ let mut default_fonts = Vec::new();
280
+ for entry in walkdir::WalkDir::new(resource_path.join("fonts")) {
281
+ let path = entry
282
+ .map_err(|err| magnus::Error::new(exception::arg_error(), err.to_string()))?
283
+ .into_path();
284
+ let Some(extension) = path.extension() else {
285
+ continue;
286
+ };
287
+ if extension == "ttf" || extension == "otf" {
288
+ default_fonts.push(path);
289
+ }
290
+ }
291
+
292
+ let mut world = SystemWorld::builder(root, input)
293
+ .inputs(Dict::from_iter(
294
+ sys_inputs
295
+ .into_iter()
296
+ .map(|(k, v)| (k.into(), Value::Str(v.into()))),
297
+ ))
298
+ .font_paths(font_paths)
299
+ .ignore_system_fonts(ignore_system_fonts)
300
+ .build()
301
+ .map_err(|msg| magnus::Error::new(exception::arg_error(), msg.to_string()))?;
302
+
303
+ let result = typst_query(
304
+ &mut world,
305
+ &QueryCommand {
306
+ selector: selector.into(),
307
+ field: field.map(Into::into),
308
+ one,
309
+ format,
310
+ },
311
+ );
312
+
313
+ match result {
314
+ Ok(data) => Ok(data),
315
+ Err(msg) => Err(magnus::Error::new(exception::arg_error(), msg.to_string())),
316
+ }
317
+ }
123
318
 
124
319
  #[magnus::init]
125
320
  fn init() -> Result<(), Error> {
126
321
  env_logger::init();
127
322
 
128
323
  let module = define_module("Typst")?;
129
- module.define_singleton_method("_to_pdf", function!(to_pdf, 4))?;
130
- module.define_singleton_method("_write_pdf", function!(write_pdf, 5))?;
131
- module.define_singleton_method("_to_svg", function!(to_svg, 4))?;
324
+ module.define_singleton_method("_to_pdf", function!(to_pdf, 6))?;
325
+ module.define_singleton_method("_to_svg", function!(to_svg, 6))?;
326
+ module.define_singleton_method("_to_png", function!(to_png, 6))?;
327
+ module.define_singleton_method("_to_html", function!(to_html, 6))?;
328
+ module.define_singleton_method("_query", function!(query, 10))?;
132
329
  Ok(())
133
330
  }
@@ -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
+ }