typst 0.0.4 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 comemo::Prehashed;
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::eval::{eco_format, Bytes, Datetime, Library};
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::World;
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::fonts::{FontSearcher, FontSlot};
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: Prehashed<Library>,
33
+ library: LazyHash<Library>,
35
34
  /// Metadata about discovered fonts.
36
- book: Prehashed<FontBook>,
35
+ book: LazyHash<FontBook>,
37
36
  /// Locations of and storage for lazily loaded fonts.
38
37
  fonts: Vec<FontSlot>,
39
- /// Maps package-path combinations to canonical hashes. All package-path
40
- /// combinations that point to the same file are mapped to the same hash. To
41
- /// be used in conjunction with `paths`.
42
- hashes: RefCell<HashMap<FileId, FileResult<PathHash>>>,
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: OnceCell<DateTime<Local>>,
44
+ now: OnceLock<DateTime<Local>>,
48
45
  }
49
46
 
50
47
  impl World for SystemWorld {
51
- fn library(&self) -> &Prehashed<Library> {
48
+ fn library(&self) -> &LazyHash<Library> {
52
49
  &self.library
53
50
  }
54
51
 
55
- fn book(&self) -> &Prehashed<FontBook> {
52
+ fn book(&self) -> &LazyHash<FontBook> {
56
53
  &self.book
57
54
  }
58
55
 
59
- fn main(&self) -> Source {
60
- self.source(self.main).unwrap()
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)?.source()
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)?.file()
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) -> FileResult<RefMut<PathSlot>> {
98
- let mut system_path = PathBuf::new();
99
- let hash = self
100
- .hashes
101
- .borrow_mut()
102
- .entry(id)
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.hashes.borrow_mut().clear();
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
- font_files: Vec<PathBuf>,
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
- font_files: Vec::new(),
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 font_files(mut self, font_files: Vec<PathBuf>) -> Self {
187
- self.font_files = font_files;
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 build(self) -> StrResult<SystemWorld> {
192
- let mut searcher = FontSearcher::new();
193
- searcher.search(&self.font_paths, &self.font_files);
169
+ pub fn inputs(mut self, inputs: Dict) -> Self {
170
+ self.inputs = inputs;
171
+ self
172
+ }
194
173
 
195
- for (name, infos) in searcher.book.families() {
196
- for info in infos {
197
- let FontVariant { style, weight, stretch } = info.variant;
198
- debug!(target: "typst-rb", "font {name:?} variants - Style: {style:?}, Weight: {weight:?}, Stretch: {stretch:?}");
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: Prehashed::new(typst_library::build()),
215
- book: Prehashed::new(searcher.book),
216
- fonts: searcher.fonts,
217
- hashes: RefCell::default(),
218
- slots: RefCell::default(),
219
- now: OnceCell::new(),
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 PathSlot {
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 PathSlot {
219
+ impl FileSlot {
240
220
  /// Create a new path slot.
241
- fn new(id: FileId, path: PathBuf) -> Self {
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
- self.source.get_or_init(&self.path, |data, prev| {
259
- let text = decode_utf8(&data)?;
260
- if let Some(mut prev) = prev {
261
- prev.replace(text);
262
- Ok(prev)
263
- } else {
264
- Ok(Source::new(self.id, text.into()))
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
- fn file(&self) -> FileResult<Bytes> {
270
- self.file.get_or_init(&self.path, |data, _| Ok(data.into()))
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: RefCell<Option<FileResult<T>>>,
277
- refreshed: Cell<FileTime>,
278
- accessed: Cell<bool>,
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: RefCell::new(None),
286
- refreshed: Cell::new(FileTime::zero()),
287
- accessed: Cell::new(false),
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.set(false);
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: &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
- let mut borrow = self.data.borrow_mut();
304
- if let Some(data) = &*borrow {
305
- if self.accessed.replace(true) || self.current(path) {
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
- self.accessed.set(true);
311
- self.refreshed.set(FileTime::now());
312
- let prev = borrow.take().and_then(Result::ok);
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
- /// Whether the cell contents are still up to date with the file system.
319
- fn current(&self, path: &Path) -> bool {
320
- fs::metadata(path).map_or(false, |meta| {
321
- let modified = FileTime::from_last_modification_time(&meta);
322
- modified < self.refreshed.get()
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
- /// A hash that is the same for all paths pointing to the same entity.
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
@@ -17,7 +17,7 @@ module Typst
17
17
  end
18
18
 
19
19
  def write(output)
20
- File.open(output, "w"){ |f| f.write(document) }
20
+ File.open(output, "wb"){ |f| f.write(document) }
21
21
  end
22
22
 
23
23
  def self.from_s(main_source, dependencies: {}, fonts: {})
@@ -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
142
- 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
201
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typst
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Flinn
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-11-29 00:00:00.000000000 Z
10
+ date: 2025-02-23 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rb_sys
@@ -24,7 +23,6 @@ dependencies:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
25
  version: 0.9.83
27
- description:
28
26
  email: flinn@actsasflinn.com
29
27
  executables: []
30
28
  extensions:
@@ -42,8 +40,8 @@ files:
42
40
  - ext/typst/src/download.rs
43
41
  - ext/typst/src/fonts.rs
44
42
  - ext/typst/src/lib.rs
45
- - ext/typst/src/lib.wip.rs
46
43
  - ext/typst/src/package.rs
44
+ - ext/typst/src/query.rs
47
45
  - ext/typst/src/world.rs
48
46
  - lib/fonts/DejaVuSansMono-Bold.ttf
49
47
  - lib/fonts/DejaVuSansMono-BoldOblique.ttf
@@ -64,7 +62,6 @@ homepage: https://github.com/actsasflinn/typst-rb
64
62
  licenses:
65
63
  - Apache-2.0
66
64
  metadata: {}
67
- post_install_message:
68
65
  rdoc_options: []
69
66
  require_paths:
70
67
  - lib
@@ -79,8 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
76
  - !ruby/object:Gem::Version
80
77
  version: '0'
81
78
  requirements: []
82
- rubygems_version: 3.4.22
83
- signing_key:
79
+ rubygems_version: 3.6.3
84
80
  specification_version: 4
85
81
  summary: Ruby binding to typst, a new markup-based typesetting system that is powerful
86
82
  and easy to learn.