1use core::fmt;
2
3use alloc::collections::BTreeMap;
4use alloc::format;
5use alloc::string::String;
6use alloc::vec::Vec;
7use mathtex_font::{FontData, FontQuery, FontSystem, MathKernCorner, ShapeFeature, ShapeRequest};
8use mathtex_ir::{
9 BoxKind, BoxMetrics, ByteSpan, Direction, FontId, FontRef, Fragment, FragmentMetadata, Glue,
10 GlyphId, GlyphRun, Kern, LayoutBox, LayoutNode, LayoutNodeKind, Length, NodeId, Point,
11 PositionedGlyph, Rect, Rule, Size, SourceRange, SourceRole, Style, Surface,
12};
13
14use crate::platform::{HostBoxRequest, HostBoxStyle, LinebreakRequest, Platform};
15use crate::resource::{ResourceKind, ResourceProvider, ResourceRequest as EngineResourceRequest};
16
17pub type GeneratedNodeHandle = mathtex_portable_engine_generated::PortableNodeHandle;
19
20#[derive(Clone, Copy)]
22struct ParentBox {
23 vertical: bool,
24 width: i32,
25 height: i32,
26 depth: i32,
27}
28
29pub type GeneratedNodeKind = mathtex_portable_engine_generated::PortableNodeKind;
31
32pub type GeneratedNodeSnapshot = mathtex_portable_engine_generated::PortableNodeSnapshot;
34
35pub type GeneratedSourceSpan = mathtex_portable_engine_generated::PortableSourceSpan;
37
38pub type GeneratedNodeSourceSpan = mathtex_portable_engine_generated::PortableNodeSourceSpan;
40
41pub type GeneratedResourceRequestRecord =
43 mathtex_portable_engine_generated::PortableResourceRequestRecord;
44
45pub type GeneratedFontHandle = mathtex_portable_engine_generated::PortableFontHandle;
47
48pub type GeneratedFontMetrics = mathtex_portable_engine_generated::PortableFontMetrics;
50
51pub type GeneratedNativeGlyph = mathtex_portable_engine_generated::PortableNativeGlyph;
53
54pub type GeneratedNativeTextMetrics = mathtex_portable_engine_generated::PortableNativeTextMetrics;
56
57pub type GeneratedNativeGlyphMetrics =
59 mathtex_portable_engine_generated::PortableNativeGlyphMetrics;
60
61pub type GeneratedMathVariant = mathtex_portable_engine_generated::PortableMathVariant;
63
64pub type GeneratedMathAssemblyPart =
66 mathtex_portable_engine_generated::PortableMathAssemblyPart;
67
68pub type GeneratedMathKernCorner = mathtex_portable_engine_generated::PortableMathKernCorner;
70
71pub trait GeneratedFontPlatform: mathtex_portable_engine_generated::FontPlatform {}
73
74macro_rules! trace_loop {
76 ($tag:literal) => {{
77 let _ = $tag;
78 }};
79}
80
81impl<T> GeneratedFontPlatform for T where T: mathtex_portable_engine_generated::FontPlatform {}
82
83pub type EmptyGeneratedFontPlatform = mathtex_portable_engine_generated::EmptyFontPlatform;
85
86pub trait GeneratedPlatform: mathtex_portable_engine_generated::PortablePlatform {}
88
89impl<T> GeneratedPlatform for T where T: mathtex_portable_engine_generated::PortablePlatform {}
90
91pub type EmptyGeneratedPlatform = mathtex_portable_engine_generated::EmptyPlatform;
93
94pub type GeneratedClock = mathtex_portable_engine_generated::PortableClock;
96
97pub type GeneratedLinebreakRequest<'a> =
99 mathtex_portable_engine_generated::PortableLinebreakRequest<'a>;
100
101pub type GeneratedHostBoxRequest = mathtex_portable_engine_generated::PortableHostBoxRequest;
103
104pub type GeneratedHostBoxStyle = mathtex_portable_engine_generated::PortableHostBoxStyle;
106
107pub type GeneratedHostBox = mathtex_portable_engine_generated::PortableHostBox;
109
110#[derive(Clone, Copy, Debug)]
112pub struct GeneratedPlatformAdapter<P> {
113 platform: P,
114}
115
116impl<P> GeneratedPlatformAdapter<P> {
117 #[must_use]
119 pub fn new(platform: P) -> Self {
120 Self { platform }
121 }
122
123 #[must_use]
125 pub fn platform(&self) -> &P {
126 &self.platform
127 }
128
129 #[must_use]
131 pub fn into_platform(self) -> P {
132 self.platform
133 }
134}
135
136impl<P> mathtex_portable_engine_generated::PortablePlatform for GeneratedPlatformAdapter<P>
137where
138 P: Platform,
139{
140 fn clock(&mut self) -> GeneratedClock {
141 let clock = self.platform.clock();
142 GeneratedClock {
143 seconds: clock.seconds,
144 micros: clock.micros,
145 }
146 }
147
148 fn linebreak_start(&mut self, request: GeneratedLinebreakRequest<'_>) {
149 self.platform.linebreak_start(LinebreakRequest {
150 font: request.font,
151 locale: request.locale,
152 text: request.text,
153 });
154 }
155
156 fn linebreak_next(&mut self) -> Option<i32> {
157 self.platform.linebreak_next()
158 }
159
160 fn host_box(&mut self, request: GeneratedHostBoxRequest) -> Option<GeneratedHostBox> {
161 let style = match request.style {
162 GeneratedHostBoxStyle::Text => HostBoxStyle::Text,
163 GeneratedHostBoxStyle::Script => HostBoxStyle::Script,
164 GeneratedHostBoxStyle::ScriptScript => HostBoxStyle::ScriptScript,
165 };
166 let response = self.platform.host_box(HostBoxRequest {
167 token: request.token,
168 style,
169 font_size: request.font_size,
170 })?;
171 Some(GeneratedHostBox {
172 width: response.width,
173 height: response.height,
174 depth: response.depth,
175 runs: response
176 .runs
177 .into_iter()
178 .map(|run| mathtex_portable_engine_generated::PortableHostBoxRun {
179 font_name: run.font_name,
180 font_size: run.font_size,
181 glyphs: run
182 .glyphs
183 .into_iter()
184 .map(|glyph| mathtex_portable_engine_generated::PortableHostBoxGlyph {
185 glyph: glyph.glyph,
186 x: glyph.x,
187 y: glyph.y,
188 advance: glyph.advance,
189 })
190 .collect(),
191 })
192 .collect(),
193 rules: response
194 .rules
195 .into_iter()
196 .map(|rule| mathtex_portable_engine_generated::PortableHostBoxRule {
197 x: rule.x,
198 y: rule.y,
199 width: rule.width,
200 height: rule.height,
201 })
202 .collect(),
203 })
204 }
205}
206
207#[derive(Clone, Debug)]
209pub struct GeneratedFontSystemAdapter<F> {
210 fonts: F,
211 next_handle: GeneratedFontHandle,
212 loaded_fonts: BTreeMap<GeneratedFontHandle, LoadedGeneratedFont>,
213}
214
215#[derive(Clone, Debug, PartialEq, Eq)]
216struct LoadedGeneratedFont {
217 data: FontData,
218 size: Length,
219 spec: String,
221 script: Option<[u8; 4]>,
223 features: Vec<ShapeFeature>,
225}
226
227impl<F> GeneratedFontSystemAdapter<F> {
228 #[must_use]
230 pub fn new(fonts: F) -> Self {
231 Self {
232 fonts,
233 next_handle: 1,
234 loaded_fonts: BTreeMap::new(),
235 }
236 }
237
238 #[must_use]
240 pub fn font_system(&self) -> &F {
241 &self.fonts
242 }
243
244 #[must_use]
246 pub fn into_font_system(self) -> F {
247 self.fonts
248 }
249
250 #[must_use]
252 pub fn loaded_font(&self, handle: GeneratedFontHandle) -> Option<&FontData> {
253 self.loaded_fonts.get(&handle).map(|font| &font.data)
254 }
255}
256
257impl<F> mathtex_portable_engine_generated::FontPlatform for GeneratedFontSystemAdapter<F>
258where
259 F: FontSystem,
260{
261 fn resolve_font_handle(&mut self, name: &[i32], size: i32) -> Option<GeneratedFontHandle> {
262 trace_loop!("resolve_font_handle");
263 let spec = unicode_scalars_to_string(name);
264 let (script, features) = parse_spec_shaping(&spec);
265 let font = self
266 .fonts
267 .load_font(&FontQuery {
268 family: spec.clone(),
269 size: Length::from_scaled_points(size),
270 math: true,
271 })
272 .ok()?;
273 let next_handle = self.next_handle.checked_add(1)?;
274 let handle = self.next_handle;
275 self.next_handle = next_handle;
276 self.loaded_fonts.insert(
277 handle,
278 LoadedGeneratedFont {
279 data: font,
280 size: Length::from_scaled_points(size),
281 spec,
282 script,
283 features,
284 },
285 );
286 Some(handle)
287 }
288
289 fn release_font_handle(&mut self, font: GeneratedFontHandle, _type_flag: i32) {
290 self.loaded_fonts.remove(&font);
291 }
292
293 fn font_table(&self) -> Vec<(GeneratedFontHandle, String, i32)> {
294 self.loaded_fonts
295 .iter()
296 .map(|(&handle, font)| (handle, font.spec.clone(), font.size.0))
297 .collect()
298 }
299
300 fn restore_font_table(&mut self, table: &[(GeneratedFontHandle, String, i32)]) -> bool {
301 let mut ok = true;
302 for (handle, spec, size) in table {
303 let size = Length::from_scaled_points(*size);
304 match self.fonts.load_font(&FontQuery {
305 family: spec.clone(),
306 size,
307 math: true,
308 }) {
309 Ok(data) => {
310 let (script, features) = parse_spec_shaping(spec);
311 self.loaded_fonts.insert(
312 *handle,
313 LoadedGeneratedFont {
314 data,
315 size,
316 spec: spec.clone(),
317 script,
318 features,
319 },
320 );
321 self.next_handle = self.next_handle.max(handle.saturating_add(1));
322 }
323 Err(_) => ok = false,
324 }
325 }
326 ok
327 }
328
329 fn font_metrics(&mut self, font: GeneratedFontHandle) -> GeneratedFontMetrics {
330 self.loaded_fonts
331 .get(&font)
332 .and_then(|font| font.data.metrics(font.size).ok())
333 .map(generated_font_metrics)
334 .unwrap_or_default()
335 }
336
337 fn opentype_font_metrics(&mut self, font: GeneratedFontHandle) -> GeneratedFontMetrics {
338 self.font_metrics(font)
339 }
340
341 fn is_opentype_math_font(&mut self, font: GeneratedFontHandle) -> bool {
342 trace_loop!("is_opentype_math_font");
343 self.loaded_fonts
344 .get(&font)
345 .and_then(|font| font.data.has_opentype_math().ok())
346 .unwrap_or(false)
347 }
348
349 fn using_opentype(&mut self, font: GeneratedFontHandle) -> bool {
350 self.loaded_fonts.contains_key(&font)
352 }
353
354 fn math_symbol_parameter(&mut self, font: GeneratedFontHandle, parameter: i32) -> i32 {
355 self.loaded_fonts
356 .get(&font)
357 .and_then(|font| font.data.math_symbol_parameter(parameter, font.size).ok())
358 .unwrap_or(0)
359 }
360
361 fn math_extension_parameter(&mut self, font: GeneratedFontHandle, parameter: i32) -> i32 {
362 self.loaded_fonts
363 .get(&font)
364 .and_then(|font| {
365 font.data
366 .math_extension_parameter(parameter, font.size)
367 .ok()
368 })
369 .unwrap_or(0)
370 }
371
372 fn opentype_math_constant(&mut self, font: GeneratedFontHandle, constant: i32) -> i32 {
373 trace_loop!("opentype_math_constant");
374 self.loaded_fonts
375 .get(&font)
376 .and_then(|font| font.data.opentype_math_constant(constant, font.size).ok())
377 .unwrap_or(0)
378 }
379
380 fn opentype_math_accent_position(&mut self, font: GeneratedFontHandle, glyph: i32) -> i32 {
381 let Ok(glyph) = u32::try_from(glyph) else {
382 return 0;
383 };
384 self.loaded_fonts
385 .get(&font)
386 .and_then(|font| {
387 font.data
388 .opentype_math_accent_position(GlyphId(glyph), font.size)
389 .ok()
390 })
391 .unwrap_or(0)
392 }
393
394 fn math_glyph_italic_correction(&mut self, font: GeneratedFontHandle, glyph: i32) -> i32 {
395 trace_loop!("math_glyph_italic_correction");
396 let Ok(glyph) = u32::try_from(glyph) else {
397 return 0;
398 };
399 self.loaded_fonts
400 .get(&font)
401 .and_then(|font| {
402 font.data
403 .math_italic_correction(GlyphId(glyph), font.size)
404 .ok()
405 })
406 .unwrap_or(0)
407 }
408
409 fn math_glyph_variant(
410 &mut self,
411 font: GeneratedFontHandle,
412 glyph: i32,
413 index: u16,
414 horizontal: bool,
415 ) -> Option<GeneratedMathVariant> {
416 trace_loop!("math_glyph_variant");
417 let glyph = u32::try_from(glyph).ok()?;
418 let loaded = self.loaded_fonts.get(&font)?;
419 let (variant_glyph, advance) = loaded
420 .data
421 .math_variant(GlyphId(glyph), index, horizontal, loaded.size)
422 .ok()??;
423 Some(GeneratedMathVariant {
424 glyph: variant_glyph.min(i32::MAX as u32) as i32,
425 advance,
426 })
427 }
428
429 fn math_glyph_assembly(
430 &mut self,
431 font: GeneratedFontHandle,
432 glyph: i32,
433 horizontal: bool,
434 ) -> Vec<GeneratedMathAssemblyPart> {
435 trace_loop!("math_glyph_assembly");
436 let Ok(glyph) = u32::try_from(glyph) else {
437 return Vec::new();
438 };
439 let Some(loaded) = self.loaded_fonts.get(&font) else {
440 return Vec::new();
441 };
442 let Ok(parts) = loaded
443 .data
444 .math_assembly(GlyphId(glyph), horizontal, loaded.size)
445 else {
446 return Vec::new();
447 };
448 parts
449 .into_iter()
450 .map(|part| GeneratedMathAssemblyPart {
451 glyph: part.glyph.min(i32::MAX as u32) as i32,
452 start_connector: part.start_connector,
453 end_connector: part.end_connector,
454 full_advance: part.full_advance,
455 extender: part.extender,
456 })
457 .collect()
458 }
459
460 fn math_min_connector_overlap(&mut self, font: GeneratedFontHandle) -> i32 {
461 self.loaded_fonts
462 .get(&font)
463 .and_then(|font| font.data.math_min_connector_overlap(font.size).ok())
464 .unwrap_or(0)
465 }
466
467 fn math_kern_at(
468 &mut self,
469 font: GeneratedFontHandle,
470 glyph: i32,
471 corner: GeneratedMathKernCorner,
472 correction_height: i32,
473 ) -> i32 {
474 trace_loop!("math_kern_at");
475 let Ok(glyph) = u32::try_from(glyph) else {
476 return 0;
477 };
478 let corner = match corner {
479 GeneratedMathKernCorner::TopRight => MathKernCorner::TopRight,
480 GeneratedMathKernCorner::TopLeft => MathKernCorner::TopLeft,
481 GeneratedMathKernCorner::BottomRight => MathKernCorner::BottomRight,
482 GeneratedMathKernCorner::BottomLeft => MathKernCorner::BottomLeft,
483 };
484 self.loaded_fonts
485 .get(&font)
486 .and_then(|font| font.data.math_kern_at(GlyphId(glyph), corner, correction_height).ok())
487 .unwrap_or(0)
488 }
489
490 fn math_points_to_units(&mut self, font: GeneratedFontHandle, points: f32) -> f32 {
491 self.loaded_fonts
492 .get(&font)
493 .and_then(|font| font.data.points_to_units(points, font.size).ok())
494 .unwrap_or(0.0)
495 }
496
497 fn math_units_to_scaled(&mut self, font: GeneratedFontHandle, units: i32) -> i32 {
498 self.loaded_fonts
499 .get(&font)
500 .and_then(|font| font.data.units_to_scaled(units, font.size).ok())
501 .unwrap_or(0)
502 }
503
504 fn math_point_size(&mut self, font: GeneratedFontHandle) -> f32 {
505 self.loaded_fonts
506 .get(&font)
507 .map(|font| (f64::from(font.size.0) / 65536.0) as f32)
508 .unwrap_or(0.0)
509 }
510
511 fn map_char_to_glyph(&mut self, font: GeneratedFontHandle, codepoint: i32) -> i32 {
512 trace_loop!("map_char_to_glyph");
513 let Some(codepoint) = u32::try_from(codepoint).ok().and_then(char::from_u32) else {
514 return 0;
515 };
516 self.loaded_fonts
517 .get(&font)
518 .and_then(|font| font.data.glyph_index(codepoint).ok().flatten())
519 .map(|glyph| glyph.0.min(i32::MAX as u32) as i32)
520 .unwrap_or(0)
521 }
522
523 fn map_glyph_to_index(&mut self, font: GeneratedFontHandle, name: &str) -> i32 {
524 self.loaded_fonts
525 .get(&font)
526 .and_then(|font| font.data.glyph_index_by_name(name).ok().flatten())
527 .map(|glyph| glyph.0.min(i32::MAX as u32) as i32)
528 .unwrap_or(0)
529 }
530
531 fn ot_font_get(
532 &mut self,
533 font: GeneratedFontHandle,
534 what: i32,
535 param1: i32,
536 param2: i32,
537 param3: i32,
538 ) -> i32 {
539 let Some(loaded) = self.loaded_fonts.get(&font) else {
540 return 0;
541 };
542 let data = &loaded.data;
543 let result = match what {
545 1 => data.ot_glyph_count(), 16 => data.ot_script_count(), 17 => data.ot_language_count(param1 as u32), 18 => data.ot_feature_count(param1 as u32, param2 as u32), 19 => data.ot_script_tag(param1 as u32), 20 => data.ot_language_tag(param1 as u32, param2 as u32), 21 => data.ot_feature_tag(param1 as u32, param2 as u32, param3 as u32), _ => return 0,
553 };
554 result.map_or(0, |value| value.min(i32::MAX as u32) as i32)
555 }
556
557 fn font_spec(&self, font: GeneratedFontHandle) -> Option<String> {
558 self.loaded_fonts.get(&font).map(|loaded| loaded.spec.clone())
559 }
560
561 fn shape_native_text(
562 &mut self,
563 font: GeneratedFontHandle,
564 text: &[u16],
565 _use_glyph_metrics: bool,
566 ) -> GeneratedNativeTextMetrics {
567 let Some(loaded) = self.loaded_fonts.get(&font) else {
568 return GeneratedNativeTextMetrics::default();
569 };
570 let text = utf16_to_string(text);
571 let metrics = loaded.data.metrics(loaded.size).ok();
572 let Ok(shaped) = self.fonts.shape_text(&ShapeRequest {
573 font: loaded.data.id,
574 text: text.as_str(),
575 direction: Direction::LeftToRight,
576 source: Some(ByteSpan {
577 start: 0,
578 end: text.len().min(u32::MAX as usize) as u32,
579 }),
580 script: loaded.script,
581 features: loaded.features.clone(),
582 }) else {
583 return GeneratedNativeTextMetrics {
584 height: metrics.map_or(0, |metrics| metrics.ascent),
585 depth: metrics.map_or(0, |metrics| -metrics.descent),
586 ..GeneratedNativeTextMetrics::default()
587 };
588 };
589
590 let mut cursor = 0i32;
591 let glyphs = shaped
592 .glyphs
593 .iter()
594 .map(|glyph| {
595 let native = GeneratedNativeGlyph {
596 glyph_id: glyph_id_u16(glyph.glyph_id),
597 x: cursor.saturating_add(glyph.offset.x.0),
598 y: glyph.offset.y.0,
599 advance: glyph.advance.x.0,
600 cluster_start: glyph.cluster.map_or(0, |span| span.start),
601 cluster_end: glyph.cluster.map_or(0, |span| span.end),
602 src_start: 0,
604 src_end: 0,
605 };
606 cursor = cursor.saturating_add(glyph.advance.x.0);
607 native
608 })
609 .collect();
610
611 GeneratedNativeTextMetrics {
612 width: cursor,
613 height: metrics.map_or(0, |metrics| metrics.ascent),
614 depth: metrics.map_or(0, |metrics| -metrics.descent),
615 glyphs,
616 }
617 }
618
619 fn measure_native_glyph(
620 &mut self,
621 font: GeneratedFontHandle,
622 glyph: u16,
623 _use_glyph_metrics: bool,
624 ) -> GeneratedNativeGlyphMetrics {
625 self.loaded_fonts
626 .get(&font)
627 .and_then(|font| {
628 font.data
629 .glyph_metrics(GlyphId(u32::from(glyph)), font.size)
630 .ok()
631 })
632 .map(|metrics| GeneratedNativeGlyphMetrics {
633 width: metrics.width,
634 height: metrics.height,
635 depth: metrics.depth,
636 })
637 .unwrap_or_default()
638 }
639}
640
641fn generated_font_metrics(metrics: mathtex_font::FontMetrics) -> GeneratedFontMetrics {
642 GeneratedFontMetrics {
643 ascent: metrics.ascent,
644 descent: metrics.descent,
645 xheight: metrics.xheight,
646 capheight: metrics.capheight,
647 slant: metrics.slant,
648 }
649}
650
651fn parse_spec_shaping(spec: &str) -> (Option<[u8; 4]>, Vec<ShapeFeature>) {
653 let Some((_, feature_str)) = spec.split_once(':') else {
654 return (None, Vec::new());
655 };
656 let mut script = None;
657 let mut features = Vec::new();
658 for part in feature_str.split(';') {
659 let part = part.trim();
660 if part.is_empty() {
661 continue;
662 }
663 if let Some(value) = part.strip_prefix("script=") {
664 script = Some(ot_tag_bytes(value));
665 } else if part.strip_prefix("language=").is_some() {
666 } else if let Some(rest) = part.strip_prefix('+') {
668 let (tag, param) = rest.split_once('=').map_or((rest, 0i64), |(tag, value)| {
670 (tag, value.trim().parse::<i64>().unwrap_or(0))
671 });
672 let value = if param >= 0 { param + 1 } else { param };
673 features.push(ShapeFeature {
674 tag: ot_tag_bytes(tag),
675 value: value as u32,
676 });
677 } else if let Some(rest) = part.strip_prefix('-') {
678 features.push(ShapeFeature {
679 tag: ot_tag_bytes(rest),
680 value: 0,
681 });
682 } else if let Some((tag, value)) = part.split_once('=') {
683 features.push(ShapeFeature {
684 tag: ot_tag_bytes(tag),
685 value: value.trim().parse().unwrap_or(1),
686 });
687 }
688 }
689 (script, features)
690}
691
692fn ot_tag_bytes(tag: &str) -> [u8; 4] {
694 let bytes = tag.trim().as_bytes();
695 [
696 bytes.first().copied().unwrap_or(b' '),
697 bytes.get(1).copied().unwrap_or(b' '),
698 bytes.get(2).copied().unwrap_or(b' '),
699 bytes.get(3).copied().unwrap_or(b' '),
700 ]
701}
702
703fn unicode_scalars_to_string(name: &[i32]) -> String {
704 name.iter()
705 .map(|codepoint| {
706 u32::try_from(*codepoint)
707 .ok()
708 .and_then(char::from_u32)
709 .unwrap_or(char::REPLACEMENT_CHARACTER)
710 })
711 .collect()
712}
713
714fn utf16_to_string(text: &[u16]) -> String {
715 char::decode_utf16(text.iter().copied())
716 .map(|codepoint| codepoint.unwrap_or(char::REPLACEMENT_CHARACTER))
717 .collect()
718}
719
720fn glyph_id_u16(glyph: GlyphId) -> u16 {
721 u16::try_from(glyph.0).unwrap_or(0)
722}
723
724#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
726pub struct GeneratedLayoutCapture {
727 pub page_builds: usize,
729 pub shipouts: usize,
731 pub special_outputs: usize,
733 pub picture_loads: usize,
735 pub source_specials: usize,
737 pub write_whatsit_diagnostics: usize,
739 pub pdf_extensions: usize,
741 pub transcript_bytes: usize,
743 pub page_top_prunes: usize,
745 pub last_shipout_box: Option<GeneratedNodeHandle>,
747 pub captured_fragment_root: Option<GeneratedNodeHandle>,
749 pub last_abort_status: Option<i32>,
751}
752
753impl GeneratedLayoutCapture {
754 #[must_use]
756 pub fn from_engine(engine: &mathtex_portable_engine_generated::PortableTexEngine<'_>) -> Self {
757 Self {
758 page_builds: engine.stripped_page_build_count(),
759 shipouts: engine.stripped_shipout_count(),
760 special_outputs: engine.stripped_special_output_count(),
761 picture_loads: engine.stripped_picture_load_count(),
762 source_specials: engine.stripped_source_special_count(),
763 write_whatsit_diagnostics: engine.stripped_write_whatsit_diagnostic_count(),
764 pdf_extensions: engine.stripped_pdf_extension_count(),
765 transcript_bytes: engine.transcript_bytes().len(),
766 page_top_prunes: engine.stripped_page_top_prune_count(),
767 last_shipout_box: engine.last_stripped_shipout_box(),
768 captured_fragment_root: engine.captured_fragment_root(),
769 last_abort_status: engine.last_abort_status(),
770 }
771 }
772
773 #[must_use]
775 pub fn last_shipout_snapshot(
776 &self,
777 engine: &mathtex_portable_engine_generated::PortableTexEngine<'_>,
778 ) -> Option<GeneratedNodeSnapshot> {
779 engine.snapshot_node(self.last_shipout_box?)
780 }
781
782 #[must_use]
784 pub fn to_fragment(
785 &self,
786 engine: &mathtex_portable_engine_generated::PortableTexEngine<'_>,
787 metadata: FragmentMetadata,
788 ) -> Option<Fragment> {
789 let root = self.captured_fragment_root.or(self.last_shipout_box)?;
790 let mut builder = GeneratedIrBuilder::new(engine, metadata);
791 let root_node = builder.emit_node(root, Point::default(), None)?;
792 builder.finish_with_root(root_node)
793 }
794
795 #[must_use]
797 pub fn to_fragment_with_source(
798 &self,
799 engine: &mathtex_portable_engine_generated::PortableTexEngine<'_>,
800 metadata: FragmentMetadata,
801 _source_name: impl Into<String>,
802 _source_span: ByteSpan,
803 ) -> Option<Fragment> {
804 let root = self.captured_fragment_root.or(self.last_shipout_box)?;
805 let mut builder = GeneratedIrBuilder::new(engine, metadata);
806 let root_node = builder.emit_node(root, Point::default(), None)?;
807 builder.finish_with_root(root_node)
808 }
809
810 #[must_use]
812 pub fn to_fragment_with_recorded_source(
813 &self,
814 engine: &mathtex_portable_engine_generated::PortableTexEngine<'_>,
815 metadata: FragmentMetadata,
816 ) -> Option<Fragment> {
817 let root = self.captured_fragment_root.or(self.last_shipout_box)?;
818 let mut builder = GeneratedIrBuilder::new(engine, metadata);
819 let root_node = builder.emit_node(root, Point::default(), None)?;
820 builder.finish_with_root(root_node)
821 }
822}
823
824#[must_use]
826pub fn generated_node_to_fragment(
827 engine: &mathtex_portable_engine_generated::PortableTexEngine<'_>,
828 root: GeneratedNodeHandle,
829 metadata: FragmentMetadata,
830) -> Option<Fragment> {
831 let mut builder = GeneratedIrBuilder::new(engine, metadata);
832 let root_node = builder.emit_node(root, Point::default(), None)?;
833 builder.finish_with_root(root_node)
834}
835
836const HOST_FONT_ID_BASE: u32 = 0x8000_0000;
838
839struct GeneratedIrBuilder<'a, 'resources> {
840 engine: &'a mathtex_portable_engine_generated::PortableTexEngine<'resources>,
841 fragment: Fragment,
842 next_node: u32,
843 visits_remaining: usize,
844 host_font_ids: BTreeMap<String, u32>,
846}
847
848impl<'a, 'resources> GeneratedIrBuilder<'a, 'resources> {
849 fn new(
850 engine: &'a mathtex_portable_engine_generated::PortableTexEngine<'resources>,
851 metadata: FragmentMetadata,
852 ) -> Self {
853 Self {
854 engine,
855 fragment: Fragment {
856 metadata,
857 ..Fragment::default()
858 },
859 next_node: 0,
860 visits_remaining: 16_384,
861 host_font_ids: BTreeMap::new(),
862 }
863 }
864
865 fn host_font_id(&mut self, name: &str) -> FontId {
866 let next = HOST_FONT_ID_BASE.saturating_add(self.host_font_ids.len() as u32);
867 FontId(*self.host_font_ids.entry(name.into()).or_insert(next))
868 }
869
870 fn finish_with_root(mut self, root: NodeId) -> Option<Fragment> {
871 let root_node = self.fragment.node(root)?;
872 if let LayoutNodeKind::Box(layout_box) = &root_node.kind {
873 self.fragment.surface = Surface {
874 width: layout_box.metrics.width,
875 height: layout_box.metrics.total_height(),
876 baseline: layout_box.metrics.height,
877 };
878 }
879 Some(self.fragment)
880 }
881
882 fn emit_node(
883 &mut self,
884 handle: GeneratedNodeHandle,
885 origin: Point,
886 parent: Option<ParentBox>,
887 ) -> Option<NodeId> {
888 self.consume_visit()?;
889 let snapshot = self.engine.snapshot_node(handle)?;
890 match snapshot.kind {
891 GeneratedNodeKind::HorizontalBox
892 | GeneratedNodeKind::VerticalBox
893 | GeneratedNodeKind::UnsetBox => self.emit_box(snapshot, origin),
894 GeneratedNodeKind::Rule => {
895 const NULL_FLAG: i32 = -1_073_741_824; let mut width = snapshot.width;
898 let mut height = snapshot.height;
899 let mut depth = snapshot.depth;
900 if let Some(p) = parent {
901 if p.vertical {
902 if width == NULL_FLAG {
903 width = p.width;
904 }
905 } else {
906 if height == NULL_FLAG {
907 height = p.height;
908 }
909 if depth == NULL_FLAG {
910 depth = p.depth;
911 }
912 }
913 }
914 Some(self.emit(
915 &snapshot,
916 origin,
917 LayoutNodeKind::Rule(Rule {
918 size: Size {
919 width: Length::from_scaled_points(width),
920 height: Length::from_scaled_points(height + depth),
921 },
922 color: Default::default(),
923 }),
924 ))
925 }
926 GeneratedNodeKind::Glue => Some(self.emit(
927 &snapshot,
928 origin,
929 LayoutNodeKind::Glue(Glue {
930 amount: Length::from_scaled_points(snapshot.width),
931 }),
932 )),
933 GeneratedNodeKind::Kern => Some(self.emit(
934 &snapshot,
935 origin,
936 LayoutNodeKind::Kern(Kern {
937 amount: Length::from_scaled_points(snapshot.width),
938 }),
939 )),
940 GeneratedNodeKind::Character => {
941 let cluster: Option<ByteSpan> = None;
942 Some(self.emit(
943 &snapshot,
944 origin,
945 LayoutNodeKind::GlyphRun(GlyphRun {
946 font: FontRef {
947 id: FontId(snapshot.font as u32),
948 name: self
949 .engine
950 .native_font_spec(snapshot.font)
951 .or_else(|| self.engine.font_name(snapshot.font))
952 .unwrap_or_else(|| format!("generated-font-{}", snapshot.font)),
953 size: Length::from_scaled_points(self.engine.font_at_size(snapshot.font)),
954 features: Vec::new(),
955 },
956 direction: Direction::LeftToRight,
957 script: None,
958 language: None,
959 glyphs: Vec::from([PositionedGlyph {
960 glyph_id: GlyphId(snapshot.character as u32),
961 offset: Point::default(),
962 advance: Point {
963 x: Length::from_scaled_points(snapshot.width),
964 y: Length::ZERO,
965 },
966 cluster,
967 }]),
968 }),
969 ))
970 }
971 GeneratedNodeKind::NativeWord | GeneratedNodeKind::NativeGlyph => {
972 Some(self.emit_native_glyph_run(snapshot, origin))
973 }
974 GeneratedNodeKind::HostBoxRef => self.emit_host_box(snapshot, origin),
975 GeneratedNodeKind::OutputWhatsit => None,
976 _ => None,
977 }
978 }
979
980 fn emit_host_box(&mut self, snapshot: GeneratedNodeSnapshot, origin: Point) -> Option<NodeId> {
982 let record = self
983 .engine
984 .host_box_record(usize::try_from(snapshot.character).ok()?)?
985 .clone();
986 let mut children = Vec::new();
987 for run in &record.runs {
988 let glyphs = run
989 .glyphs
990 .iter()
991 .map(|glyph| PositionedGlyph {
992 glyph_id: GlyphId(u32::from(glyph.glyph)),
993 offset: Point {
994 x: Length::from_scaled_points(glyph.x),
995 y: Length::from_scaled_points(glyph.y),
996 },
997 advance: Point {
998 x: Length::from_scaled_points(glyph.advance),
999 y: Length::ZERO,
1000 },
1001 cluster: None,
1002 })
1003 .collect();
1004 let font_id = self.host_font_id(&run.font_name);
1006 let child = self.push_host_box_child(
1007 Point::default(),
1008 Size {
1009 width: Length::from_scaled_points(record.width),
1010 height: Length::from_scaled_points(record.height + record.depth),
1011 },
1012 LayoutNodeKind::GlyphRun(GlyphRun {
1013 font: FontRef {
1014 id: font_id,
1015 name: run.font_name.clone(),
1016 size: Length::from_scaled_points(run.font_size),
1017 features: Vec::new(),
1018 },
1019 direction: Direction::LeftToRight,
1020 script: None,
1021 language: None,
1022 glyphs,
1023 }),
1024 );
1025 children.push(child);
1026 }
1027 for rule in &record.rules {
1028 let child = self.push_host_box_child(
1029 Point {
1030 x: Length::from_scaled_points(rule.x),
1031 y: Length::from_scaled_points(rule.y),
1032 },
1033 Size {
1034 width: Length::from_scaled_points(rule.width),
1035 height: Length::from_scaled_points(rule.height),
1036 },
1037 LayoutNodeKind::Rule(Rule {
1038 size: Size {
1039 width: Length::from_scaled_points(rule.width),
1040 height: Length::from_scaled_points(rule.height),
1041 },
1042 color: Default::default(),
1043 }),
1044 );
1045 children.push(child);
1046 }
1047 Some(self.emit(
1048 &snapshot,
1049 origin,
1050 LayoutNodeKind::Box(LayoutBox {
1051 kind: BoxKind::Horizontal,
1052 metrics: BoxMetrics {
1053 width: Length::from_scaled_points(record.width),
1054 height: Length::from_scaled_points(record.height),
1055 depth: Length::from_scaled_points(record.depth),
1056 shift: Length::ZERO,
1057 },
1058 children,
1059 }),
1060 ))
1061 }
1062
1063 fn push_host_box_child(&mut self, origin: Point, size: Size, kind: LayoutNodeKind) -> NodeId {
1065 let id = NodeId(self.next_node);
1066 self.next_node += 1;
1067 self.fragment.nodes.push(LayoutNode {
1068 id,
1069 origin,
1070 bounds: Rect {
1071 origin: Point::default(),
1072 size,
1073 },
1074 primary_source: None,
1075 style: Style::default(),
1076 kind,
1077 });
1078 id
1079 }
1080
1081 fn emit_native_glyph_run(&mut self, snapshot: GeneratedNodeSnapshot, origin: Point) -> NodeId {
1082 let cluster: Option<ByteSpan> = None;
1083 let glyphs = if snapshot.native_glyphs.is_empty() {
1084 Vec::from([PositionedGlyph {
1085 glyph_id: GlyphId(snapshot.character as u32),
1086 offset: Point::default(),
1087 advance: Point {
1088 x: Length::from_scaled_points(snapshot.width),
1089 y: Length::ZERO,
1090 },
1091 cluster,
1092 }])
1093 } else {
1094 snapshot
1095 .native_glyphs
1096 .iter()
1097 .map(|glyph| PositionedGlyph {
1098 glyph_id: GlyphId(u32::from(glyph.glyph_id)),
1099 offset: Point {
1100 x: Length::from_scaled_points(glyph.x),
1101 y: Length::from_scaled_points(glyph.y),
1102 },
1103 advance: Point {
1104 x: Length::from_scaled_points(glyph.advance),
1105 y: Length::ZERO,
1106 },
1107 cluster: if glyph.src_end > glyph.src_start {
1109 Some(ByteSpan {
1110 start: glyph.src_start,
1111 end: glyph.src_end,
1112 })
1113 } else {
1114 None
1115 },
1116 })
1117 .collect()
1118 };
1119
1120 self.emit(
1121 &snapshot,
1122 origin,
1123 LayoutNodeKind::GlyphRun(GlyphRun {
1124 font: FontRef {
1125 id: FontId(snapshot.font as u32),
1126 name: self
1127 .engine
1128 .native_font_spec(snapshot.font)
1129 .or_else(|| self.engine.font_name(snapshot.font))
1130 .unwrap_or_else(|| format!("generated-font-{}", snapshot.font)),
1131 size: Length::from_scaled_points(self.engine.font_at_size(snapshot.font)),
1132 features: Vec::new(),
1133 },
1134 direction: Direction::LeftToRight,
1135 script: None,
1136 language: None,
1137 glyphs,
1138 }),
1139 )
1140 }
1141
1142 fn emit_box(&mut self, snapshot: GeneratedNodeSnapshot, origin: Point) -> Option<NodeId> {
1143 let children = self.emit_children(
1144 snapshot.list,
1145 snapshot.kind,
1146 snapshot.width,
1147 snapshot.height,
1148 snapshot.depth,
1149 snapshot.glue_set,
1150 snapshot.glue_sign,
1151 snapshot.glue_order,
1152 );
1153 let kind = match snapshot.kind {
1154 GeneratedNodeKind::VerticalBox => BoxKind::Vertical,
1155 GeneratedNodeKind::HorizontalBox => BoxKind::Horizontal,
1156 GeneratedNodeKind::UnsetBox => BoxKind::Math,
1157 _ => BoxKind::Math,
1158 };
1159 Some(self.emit_with_primary_source(
1160 &snapshot,
1161 origin,
1162 LayoutNodeKind::Box(LayoutBox {
1163 kind,
1164 metrics: BoxMetrics {
1165 width: Length::from_scaled_points(snapshot.width),
1166 height: Length::from_scaled_points(snapshot.height),
1167 depth: Length::from_scaled_points(snapshot.depth),
1168 shift: Length::from_scaled_points(snapshot.shift),
1169 },
1170 children,
1171 }),
1172 None,
1173 ))
1174 }
1175
1176 fn emit_children(
1177 &mut self,
1178 first: Option<GeneratedNodeHandle>,
1179 parent_kind: GeneratedNodeKind,
1180 parent_width: i32,
1181 parent_height: i32,
1182 parent_depth: i32,
1183 glue_set: f64,
1184 glue_sign: i32,
1185 glue_order: i32,
1186 ) -> Vec<NodeId> {
1187 let mut children = Vec::new();
1189 let vertical = matches!(parent_kind, GeneratedNodeKind::VerticalBox);
1190 let mut cursor = first;
1191 let mut cur_h = 0i32;
1192 let mut cur_v = if vertical { -parent_height } else { 0 };
1193 while let Some(handle) = cursor {
1194 let Some(snapshot) = self.engine.snapshot_node(handle) else {
1195 break;
1196 };
1197 let true_box = matches!(
1198 snapshot.kind,
1199 GeneratedNodeKind::HorizontalBox
1200 | GeneratedNodeKind::VerticalBox
1201 | GeneratedNodeKind::UnsetBox
1202 );
1203 let has_extent = true_box
1204 || matches!(
1205 snapshot.kind,
1206 GeneratedNodeKind::Rule
1207 | GeneratedNodeKind::Character
1208 | GeneratedNodeKind::NativeWord
1209 | GeneratedNodeKind::NativeGlyph
1210 | GeneratedNodeKind::Ligature
1211 );
1212 let is_spacing = matches!(
1213 snapshot.kind,
1214 GeneratedNodeKind::Kern | GeneratedNodeKind::Glue
1215 );
1216 let advance = if matches!(snapshot.kind, GeneratedNodeKind::Glue) {
1218 let natural = snapshot.width;
1219 if glue_sign == 1 && snapshot.glue_stretch_order == glue_order {
1220 natural + (glue_set * f64::from(snapshot.glue_stretch)).round() as i32
1221 } else if glue_sign == 2 && snapshot.glue_shrink_order == glue_order {
1222 natural - (glue_set * f64::from(snapshot.glue_shrink)).round() as i32
1223 } else {
1224 natural
1225 }
1226 } else {
1227 snapshot.width
1228 };
1229 let origin = if vertical {
1230 if is_spacing {
1231 let origin = Point {
1232 x: Length::ZERO,
1233 y: Length::from_scaled_points(cur_v),
1234 };
1235 cur_v += advance;
1236 origin
1237 } else if has_extent {
1238 cur_v += snapshot.height;
1239 let origin = Point {
1240 x: Length::from_scaled_points(if true_box { snapshot.shift } else { 0 }),
1241 y: Length::from_scaled_points(cur_v),
1242 };
1243 cur_v += snapshot.depth;
1244 origin
1245 } else {
1246 Point {
1248 x: Length::ZERO,
1249 y: Length::from_scaled_points(cur_v),
1250 }
1251 }
1252 } else {
1253 let origin = Point {
1254 x: Length::from_scaled_points(cur_h),
1255 y: Length::from_scaled_points(if true_box { snapshot.shift } else { 0 }),
1256 };
1257 cur_h += advance;
1258 origin
1259 };
1260 let parent = ParentBox {
1261 vertical,
1262 width: parent_width,
1263 height: parent_height,
1264 depth: parent_depth,
1265 };
1266 if let Some(child) = self.emit_node(handle, origin, Some(parent)) {
1267 children.push(child);
1268 }
1269 cursor = snapshot.link;
1270 }
1271 children
1272 }
1273
1274 fn emit(
1275 &mut self,
1276 snapshot: &GeneratedNodeSnapshot,
1277 origin: Point,
1278 kind: LayoutNodeKind,
1279 ) -> NodeId {
1280 self.emit_with_primary_source(snapshot, origin, kind, None)
1281 }
1282
1283 fn emit_with_primary_source(
1284 &mut self,
1285 snapshot: &GeneratedNodeSnapshot,
1286 origin: Point,
1287 kind: LayoutNodeKind,
1288 _preferred_primary_source: Option<SourceRange>,
1289 ) -> NodeId {
1290 let id = NodeId(self.next_node);
1291 self.next_node += 1;
1292 let primary_source = snapshot.source.as_ref().map(|span| {
1294 let source = self.fragment.source_map.intern_source(span.name.clone());
1295 SourceRange {
1296 source,
1297 span: ByteSpan {
1298 start: span.start,
1299 end: span.end,
1300 },
1301 }
1302 });
1303 if let Some(range) = primary_source {
1304 self.fragment
1305 .source_map
1306 .add_entry(id, range, SourceRole::Primary);
1307 for span in self.engine.node_enclosing_spans(snapshot.handle) {
1309 let source = self.fragment.source_map.intern_source(span.name.clone());
1310 let enclosing = SourceRange {
1311 source,
1312 span: ByteSpan {
1313 start: span.start,
1314 end: span.end,
1315 },
1316 };
1317 self.fragment
1318 .source_map
1319 .add_entry(id, enclosing, SourceRole::EnclosingConstruct);
1320 }
1321 }
1322 self.fragment.nodes.push(LayoutNode {
1323 id,
1324 origin,
1325 bounds: Rect {
1326 origin: Point::default(),
1327 size: Size {
1328 width: Length::from_scaled_points(snapshot.width),
1329 height: Length::from_scaled_points(snapshot.height + snapshot.depth),
1330 },
1331 },
1332 primary_source,
1333 style: Style::default(),
1334 kind,
1335 });
1336 id
1337 }
1338
1339 fn consume_visit(&mut self) -> Option<()> {
1340 if self.visits_remaining == 0 {
1341 return None;
1342 }
1343 self.visits_remaining -= 1;
1344 Some(())
1345 }
1346}
1347
1348#[derive(Clone)]
1350pub struct GeneratedFormatCache {
1351 image: mathtex_portable_engine_generated::PortableFormatImage,
1352}
1353
1354impl GeneratedFormatCache {
1355 #[must_use]
1357 pub fn empty() -> Self {
1358 Self {
1359 image: mathtex_portable_engine_generated::PortableFormatImage::empty(),
1360 }
1361 }
1362
1363 #[must_use]
1365 pub fn initialized(profile: mathtex_portable_engine_generated::EngineProfile) -> Self {
1366 let mut engine = mathtex_portable_engine_generated::PortableTexEngine::from_format(
1367 profile,
1368 &mathtex_portable_engine_generated::PortableFormatImage::empty(),
1369 mathtex_portable_engine_generated::EmptyResourceProvider,
1370 );
1371 assert!(
1372 engine.initialize_format_state(),
1373 "generated engine base initialization failed"
1374 );
1375 Self::from_engine_owned(engine)
1376 }
1377
1378 #[must_use]
1380 pub fn from_engine(engine: &mathtex_portable_engine_generated::PortableTexEngine<'_>) -> Self {
1381 Self {
1382 image: engine.snapshot_format(),
1383 }
1384 }
1385
1386 #[must_use]
1388 pub fn from_engine_owned(
1389 engine: mathtex_portable_engine_generated::PortableTexEngine<'_>,
1390 ) -> Self {
1391 Self {
1392 image: engine.into_format(),
1393 }
1394 }
1395
1396 #[must_use]
1398 pub fn image(&self) -> &mathtex_portable_engine_generated::PortableFormatImage {
1399 &self.image
1400 }
1401
1402 #[must_use]
1404 pub fn to_bytes(&self) -> Vec<u8> {
1405 self.image.to_bytes()
1406 }
1407
1408 #[must_use]
1410 pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
1411 Some(Self {
1412 image: mathtex_portable_engine_generated::PortableFormatImage::from_bytes(bytes)?,
1413 })
1414 }
1415
1416 #[must_use]
1418 pub fn instantiate<'resources, R>(
1419 &self,
1420 profile: mathtex_portable_engine_generated::EngineProfile,
1421 resources: R,
1422 ) -> mathtex_portable_engine_generated::PortableTexEngine<'resources>
1423 where
1424 R: mathtex_portable_engine_generated::ResourceProvider + 'resources,
1425 {
1426 mathtex_portable_engine_generated::PortableTexEngine::from_format(
1427 profile,
1428 &self.image,
1429 resources,
1430 )
1431 }
1432}
1433
1434impl Default for GeneratedFormatCache {
1435 fn default() -> Self {
1436 Self::empty()
1437 }
1438}
1439
1440const PACKAGED_FORMAT_MAGIC: &[u8; 6] = b"MTXPKG";
1442const PACKAGED_FORMAT_VERSION: u8 = 2;
1443const PACKAGED_COMPRESSION_DEFLATE: u8 = 1;
1444const PACKAGED_DEFLATE_LEVEL: u8 = 7;
1446
1447fn encode_packaged_payload(
1448 format_bytes: &[u8],
1449 font_table: &[(mathtex_portable_engine_generated::PortableFontHandle, String, i32)],
1450) -> Vec<u8> {
1451 let mut out = Vec::with_capacity(format_bytes.len() + 64);
1452 out.extend_from_slice(&(format_bytes.len() as u64).to_le_bytes());
1453 out.extend_from_slice(format_bytes);
1454 out.extend_from_slice(&(font_table.len() as u32).to_le_bytes());
1455 for (handle, spec, size) in font_table {
1456 out.extend_from_slice(&(*handle as u64).to_le_bytes());
1457 out.extend_from_slice(&size.to_le_bytes());
1458 let spec = spec.as_bytes();
1459 out.extend_from_slice(&(spec.len() as u32).to_le_bytes());
1460 out.extend_from_slice(spec);
1461 }
1462 out
1463}
1464
1465#[must_use]
1467pub fn pack_packaged_format(
1468 format_bytes: &[u8],
1469 font_table: &[(mathtex_portable_engine_generated::PortableFontHandle, String, i32)],
1470) -> Vec<u8> {
1471 let payload = encode_packaged_payload(format_bytes, font_table);
1472 let compressed = miniz_oxide::deflate::compress_to_vec(&payload, PACKAGED_DEFLATE_LEVEL);
1473 let mut out = Vec::with_capacity(compressed.len() + 16);
1474 out.extend_from_slice(PACKAGED_FORMAT_MAGIC);
1475 out.push(PACKAGED_FORMAT_VERSION);
1476 out.push(PACKAGED_COMPRESSION_DEFLATE);
1477 out.extend_from_slice(&(payload.len() as u64).to_le_bytes());
1478 out.extend_from_slice(&compressed);
1479 out
1480}
1481
1482#[must_use]
1484pub fn unpack_packaged_format(
1485 bytes: &[u8],
1486) -> Option<(
1487 Vec<u8>,
1488 Vec<(mathtex_portable_engine_generated::PortableFontHandle, String, i32)>,
1489)> {
1490 fn take<'a>(b: &'a [u8], cursor: &mut usize, n: usize) -> Option<&'a [u8]> {
1491 let slice = b.get(*cursor..*cursor + n)?;
1492 *cursor += n;
1493 Some(slice)
1494 }
1495 let mut cursor = 0usize;
1496 if take(bytes, &mut cursor, 6)? != PACKAGED_FORMAT_MAGIC {
1497 return None;
1498 }
1499 if *bytes.get(cursor)? != PACKAGED_FORMAT_VERSION {
1500 return None;
1501 }
1502 cursor += 1;
1503 let compression = *bytes.get(cursor)?;
1504 cursor += 1;
1505 let payload_len = u64::from_le_bytes(take(bytes, &mut cursor, 8)?.try_into().ok()?) as usize;
1506 let compressed = bytes.get(cursor..)?;
1507 let payload = match compression {
1508 PACKAGED_COMPRESSION_DEFLATE => {
1509 miniz_oxide::inflate::decompress_to_vec(compressed).ok()?
1510 }
1511 0 => compressed.to_vec(),
1512 _ => return None,
1513 };
1514 if payload.len() != payload_len {
1515 return None;
1516 }
1517
1518 let mut cursor = 0usize;
1519 let format_len = u64::from_le_bytes(take(&payload, &mut cursor, 8)?.try_into().ok()?) as usize;
1520 let format_bytes = take(&payload, &mut cursor, format_len)?.to_vec();
1521 let font_count = u32::from_le_bytes(take(&payload, &mut cursor, 4)?.try_into().ok()?) as usize;
1522 let mut font_table = Vec::with_capacity(font_count);
1523 for _ in 0..font_count {
1524 let handle = u64::from_le_bytes(take(&payload, &mut cursor, 8)?.try_into().ok()?) as usize;
1525 let size = i32::from_le_bytes(take(&payload, &mut cursor, 4)?.try_into().ok()?);
1526 let spec_len = u32::from_le_bytes(take(&payload, &mut cursor, 4)?.try_into().ok()?) as usize;
1527 let spec = String::from_utf8(take(&payload, &mut cursor, spec_len)?.to_vec()).ok()?;
1528 font_table.push((handle, spec, size));
1529 }
1530 Some((format_bytes, font_table))
1531}
1532
1533impl fmt::Debug for GeneratedFormatCache {
1534 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1535 formatter
1536 .debug_struct("GeneratedFormatCache")
1537 .finish_non_exhaustive()
1538 }
1539}
1540
1541#[derive(Clone, Debug)]
1543pub struct GeneratedResourceProvider<R> {
1544 inner: R,
1545}
1546
1547impl<R> GeneratedResourceProvider<R> {
1548 #[must_use]
1550 pub fn new(inner: R) -> Self {
1551 Self { inner }
1552 }
1553
1554 #[must_use]
1556 pub fn inner(&self) -> &R {
1557 &self.inner
1558 }
1559
1560 #[must_use]
1562 pub fn into_inner(self) -> R {
1563 self.inner
1564 }
1565}
1566
1567impl<R> mathtex_portable_engine_generated::ResourceProvider for GeneratedResourceProvider<R>
1568where
1569 R: ResourceProvider,
1570{
1571 fn read(
1572 &mut self,
1573 generated_request: mathtex_portable_engine_generated::ResourceRequest<'_>,
1574 ) -> Option<Vec<u8>> {
1575 let name = normalized_generated_resource_name(generated_request.name);
1576 let kind = generated_kind_to_engine_kind(generated_request.kind)?;
1577 let mut request = if kind == ResourceKind::Asset {
1578 generated_request.package.map_or_else(
1579 || EngineResourceRequest::new(name, kind),
1580 |package| EngineResourceRequest::asset(package, name),
1581 )
1582 } else {
1583 EngineResourceRequest::new(name, kind)
1584 };
1585 if let Some(source) = request_source_from_generated(generated_request.source.as_ref()) {
1586 request = request.with_source(source.name, source.span);
1587 }
1588 let resource = read_generated_resource_request(&self.inner, &request).or_else(|| {
1589 if kind != ResourceKind::Asset {
1590 return None;
1591 }
1592 let mut fallback = EngineResourceRequest::new(name, ResourceKind::TexInput);
1593 if let Some(source) = request_source_from_generated(generated_request.source.as_ref()) {
1594 fallback = fallback.with_source(source.name, source.span);
1595 }
1596 read_generated_resource_request(&self.inner, &fallback)
1597 })?;
1598 Some(resource.bytes)
1599 }
1600}
1601
1602fn read_generated_resource_request<R>(
1603 provider: &R,
1604 request: &EngineResourceRequest,
1605) -> Option<crate::resource::Resource>
1606where
1607 R: ResourceProvider,
1608{
1609 if let Ok(resource) = provider.read_request(request) {
1610 return Some(resource);
1611 }
1612
1613 for name in generated_resource_candidate_names(request.name.as_str(), request.kind) {
1614 let mut candidate = request.clone();
1615 candidate.name = name;
1616 if let Ok(resource) = provider.read_request(&candidate) {
1617 return Some(resource);
1618 }
1619 }
1620
1621 None
1622}
1623
1624fn generated_resource_candidate_names(name: &str, kind: ResourceKind) -> Vec<String> {
1625 if resource_name_has_extension(name) {
1626 return Vec::new();
1627 }
1628
1629 let suffixes: &[&str] = match kind {
1630 ResourceKind::TexInput => &[".tex", ".ltx"],
1631 ResourceKind::Package => &[".sty"],
1632 ResourceKind::Class => &[".cls"],
1633 ResourceKind::FontDefinition => &[".fd"],
1634 ResourceKind::Font => &[".tfm", ".otf", ".ttf"],
1635 ResourceKind::Encoding => &[".enc"],
1636 ResourceKind::Map => &[".map"],
1637 ResourceKind::PackageSupport
1638 | ResourceKind::Config
1639 | ResourceKind::FormatImage
1640 | ResourceKind::Asset => &[],
1641 };
1642
1643 suffixes
1644 .iter()
1645 .map(|suffix| {
1646 let mut candidate = String::with_capacity(name.len() + suffix.len());
1647 candidate.push_str(name);
1648 candidate.push_str(suffix);
1649 candidate
1650 })
1651 .collect()
1652}
1653
1654fn resource_name_has_extension(name: &str) -> bool {
1655 let last_separator = name.rfind(['/', '\\']).map_or(0, |index| index + 1);
1656 name[last_separator..].contains('.')
1657}
1658
1659fn normalized_generated_resource_name(mut name: &str) -> &str {
1660 while let Some(stripped) = name.strip_prefix("./") {
1661 name = stripped;
1662 }
1663 name
1664}
1665
1666fn request_source_from_generated(
1667 source: Option<&mathtex_portable_engine_generated::PortableSourceSpan>,
1668) -> Option<crate::resource::ResourceRequestSource> {
1669 let source = source?;
1670 Some(crate::resource::ResourceRequestSource {
1671 name: source.name.clone(),
1672 span: ByteSpan {
1673 start: source.start,
1674 end: source.end,
1675 },
1676 })
1677}
1678
1679fn generated_kind_to_engine_kind(
1680 kind: mathtex_portable_engine_generated::ResourceKind,
1681) -> Option<ResourceKind> {
1682 match kind {
1683 mathtex_portable_engine_generated::ResourceKind::TexInput => Some(ResourceKind::TexInput),
1684 mathtex_portable_engine_generated::ResourceKind::Package => Some(ResourceKind::Package),
1685 mathtex_portable_engine_generated::ResourceKind::Class => Some(ResourceKind::Class),
1686 mathtex_portable_engine_generated::ResourceKind::FontDefinition => {
1687 Some(ResourceKind::FontDefinition)
1688 }
1689 mathtex_portable_engine_generated::ResourceKind::PackageSupport => {
1690 Some(ResourceKind::PackageSupport)
1691 }
1692 mathtex_portable_engine_generated::ResourceKind::Font => Some(ResourceKind::Font),
1693 mathtex_portable_engine_generated::ResourceKind::Encoding => Some(ResourceKind::Encoding),
1694 mathtex_portable_engine_generated::ResourceKind::Map => Some(ResourceKind::Map),
1695 mathtex_portable_engine_generated::ResourceKind::Config => Some(ResourceKind::Config),
1696 mathtex_portable_engine_generated::ResourceKind::FormatImage => {
1697 Some(ResourceKind::FormatImage)
1698 }
1699 mathtex_portable_engine_generated::ResourceKind::Asset => Some(ResourceKind::Asset),
1700 mathtex_portable_engine_generated::ResourceKind::Other(_) => Some(ResourceKind::TexInput),
1701 }
1702}
1703
1704#[cfg(test)]
1705mod tests {
1706 use super::*;
1707 use crate::resource::InMemoryResourceProvider;
1708
1709 #[test]
1710 fn generated_provider_adapter_reads_package_bytes() {
1711 let mut resources = InMemoryResourceProvider::new().with_resource(
1712 "amsmath.sty",
1713 ResourceKind::Package,
1714 br"\ProvidesPackage{amsmath}",
1715 );
1716 resources.insert_request(
1717 EngineResourceRequest::asset("mhchem", "arrows.dat"),
1718 b"asset",
1719 );
1720 let mut generated = GeneratedResourceProvider::new(resources);
1721
1722 let bytes = mathtex_portable_engine_generated::ResourceProvider::read(
1723 &mut generated,
1724 mathtex_portable_engine_generated::ResourceRequest {
1725 name: "amsmath.sty",
1726 kind: mathtex_portable_engine_generated::ResourceKind::Package,
1727 package: None,
1728 format: 26,
1729 mode: "rb",
1730 source: None,
1731 },
1732 )
1733 .expect("generated provider should read package");
1734
1735 assert_eq!(bytes, br"\ProvidesPackage{amsmath}");
1736
1737 let bytes = mathtex_portable_engine_generated::ResourceProvider::read(
1738 &mut generated,
1739 mathtex_portable_engine_generated::ResourceRequest {
1740 name: "arrows.dat",
1741 kind: mathtex_portable_engine_generated::ResourceKind::Asset,
1742 package: Some("mhchem"),
1743 format: 26,
1744 mode: "rb",
1745 source: None,
1746 },
1747 )
1748 .expect("generated provider should read package asset");
1749
1750 assert_eq!(bytes, b"asset");
1751 }
1752
1753 #[test]
1754 fn generated_engine_can_be_constructed_from_cached_format_state() {
1755 let resources = GeneratedResourceProvider::new(InMemoryResourceProvider::new());
1756 let format = GeneratedFormatCache::empty();
1757 let engine = format.instantiate(
1758 mathtex_portable_engine_generated::EngineProfile::xetex(),
1759 resources,
1760 );
1761
1762 assert_eq!(
1763 engine.profile(),
1764 mathtex_portable_engine_generated::EngineProfile::xetex()
1765 );
1766 assert_eq!(engine.resource_request_count(), 0);
1767 assert_eq!(
1768 GeneratedLayoutCapture::from_engine(&engine),
1769 GeneratedLayoutCapture::default()
1770 );
1771 assert_eq!(
1772 GeneratedLayoutCapture::from_engine(&engine).last_shipout_snapshot(&engine),
1773 None
1774 );
1775 assert_eq!(
1776 GeneratedLayoutCapture::from_engine(&engine).to_fragment(
1777 &engine,
1778 FragmentMetadata {
1779 engine_profile: "xetex".into(),
1780 format_id: "empty".into(),
1781 fragment_kind: Default::default(),
1782 },
1783 ),
1784 None
1785 );
1786 assert_eq!(
1787 GeneratedLayoutCapture::from_engine(&engine).to_fragment_with_source(
1788 &engine,
1789 FragmentMetadata {
1790 engine_profile: "xetex".into(),
1791 format_id: "empty".into(),
1792 fragment_kind: Default::default(),
1793 },
1794 "input.tex",
1795 ByteSpan { start: 0, end: 3 },
1796 ),
1797 None
1798 );
1799 }
1800
1801 #[test]
1802 fn generated_format_cache_can_snapshot_initialized_engine_state() {
1803 let cached = GeneratedFormatCache::initialized(
1804 mathtex_portable_engine_generated::EngineProfile::xetex(),
1805 );
1806 let second = cached.instantiate(
1807 mathtex_portable_engine_generated::EngineProfile::xetex(),
1808 GeneratedResourceProvider::new(InMemoryResourceProvider::new()),
1809 );
1810
1811 assert_eq!(
1812 second.profile(),
1813 mathtex_portable_engine_generated::EngineProfile::xetex()
1814 );
1815 assert_eq!(second.resource_request_count(), 0);
1816 assert_eq!(
1817 GeneratedLayoutCapture::from_engine(&second),
1818 GeneratedLayoutCapture::default()
1819 );
1820 }
1821
1822 #[test]
1823 fn generated_engine_runs_main_control_for_primary_end_input() {
1824 let format = GeneratedFormatCache::initialized(
1825 mathtex_portable_engine_generated::EngineProfile::tex(),
1826 );
1827 let mut engine = format.instantiate(
1828 mathtex_portable_engine_generated::EngineProfile::tex(),
1829 GeneratedResourceProvider::new(InMemoryResourceProvider::new()),
1830 );
1831
1832 assert!(engine.begin_primary_input("input.tex", br"\end".to_vec()));
1833 engine.run_format_initialization();
1834 assert_eq!(engine.last_abort_status(), None);
1835 }
1836
1837 #[test]
1838 fn generated_engine_loads_input_through_resource_provider_during_main_control() {
1839 let resources = InMemoryResourceProvider::new().with_resource(
1840 "child.tex",
1841 ResourceKind::TexInput,
1842 br"\relax",
1843 );
1844 let format = GeneratedFormatCache::initialized(
1845 mathtex_portable_engine_generated::EngineProfile::tex(),
1846 );
1847 let mut engine = format.instantiate(
1848 mathtex_portable_engine_generated::EngineProfile::tex(),
1849 GeneratedResourceProvider::new(resources),
1850 );
1851
1852 assert!(engine.begin_primary_input("input.tex", br"\input child.tex \end".to_vec()));
1853 engine.run_main_control();
1854
1855 assert_eq!(engine.last_abort_status(), None);
1856 assert_eq!(engine.resource_request_count(), 1);
1857 let request = &engine.resource_request_records()[0];
1858 assert_eq!(request.name, "child.tex");
1859 assert_eq!(
1860 request.kind,
1861 mathtex_portable_engine_generated::ResourceKind::TexInput
1862 );
1863 assert_eq!(request.byte_len, Some(6));
1864 assert_eq!(request.source, None);
1866 }
1867
1868 #[test]
1869 fn generated_engine_captures_fragment_root_to_ir_without_shipout() {
1870 let format = GeneratedFormatCache::initialized(
1871 mathtex_portable_engine_generated::EngineProfile::tex(),
1872 );
1873 let mut engine = format.instantiate(
1874 mathtex_portable_engine_generated::EngineProfile::tex(),
1875 GeneratedResourceProvider::new(InMemoryResourceProvider::new()),
1876 );
1877
1878 engine.begin_fragment_capture();
1879 assert!(engine.begin_primary_input(
1880 "input.tex",
1881 br"\catcode`{=1 \catcode`}=2 \hbox{\vrule width 1pt height 2pt depth 0pt}\end".to_vec()
1882 ));
1883 engine.run_main_control();
1884 engine.end_fragment_capture();
1885
1886 let capture = GeneratedLayoutCapture::from_engine(&engine);
1887 assert_eq!(capture.last_abort_status, None);
1888 assert!(!String::from_utf8_lossy(engine.transcript_bytes()).contains('!'));
1889 assert_eq!(capture.shipouts, 0);
1890 let register = engine
1891 .captured_fragment_root()
1892 .expect("fragment root should be captured");
1893 let snapshot = engine
1894 .snapshot_node(register)
1895 .expect("captured root should snapshot");
1896 assert_eq!(
1897 snapshot.kind,
1898 mathtex_portable_engine_generated::PortableNodeKind::HorizontalBox
1899 );
1900
1901 let fragment = generated_node_to_fragment(
1902 &engine,
1903 register,
1904 FragmentMetadata {
1905 engine_profile: "tex".into(),
1906 format_id: "generated".into(),
1907 fragment_kind: Default::default(),
1908 },
1909 )
1910 .expect("captured root should convert to IR");
1911
1912 let root = fragment
1913 .nodes
1914 .iter()
1915 .find_map(|node| match &node.kind {
1916 LayoutNodeKind::Box(layout_box) => Some(layout_box),
1917 _ => None,
1918 })
1919 .expect("fragment should contain a root box");
1920 assert_eq!(root.kind, BoxKind::Horizontal);
1921 assert_eq!(root.children.len(), 1);
1922 assert!(fragment
1923 .nodes
1924 .iter()
1925 .any(|node| matches!(node.kind, LayoutNodeKind::Rule(_))));
1926 }
1927
1928 #[test]
1929 fn generated_engine_loads_tfm_and_emits_glyph_ir_without_shipout() {
1930 let resources = InMemoryResourceProvider::new().with_resource(
1931 "cmr10",
1932 ResourceKind::Font,
1933 include_bytes!("../../../vendor/texlive-source/texk/web2c/tests/cmr10.tfm").to_vec(),
1934 );
1935 let format = GeneratedFormatCache::initialized(
1936 mathtex_portable_engine_generated::EngineProfile::tex(),
1937 );
1938 let mut engine = format.instantiate(
1939 mathtex_portable_engine_generated::EngineProfile::tex(),
1940 GeneratedResourceProvider::new(resources),
1941 );
1942
1943 engine.begin_fragment_capture();
1944 assert!(engine.begin_primary_input(
1945 "input.tex",
1946 br"\catcode`{=1 \catcode`}=2 \font\tenrm=cmr10 \hbox{\tenrm A}\end".to_vec()
1947 ));
1948 engine.run_main_control();
1949 engine.end_fragment_capture();
1950
1951 let capture = GeneratedLayoutCapture::from_engine(&engine);
1952 assert_eq!(capture.last_abort_status, None);
1953 assert!(!String::from_utf8_lossy(engine.transcript_bytes()).contains('!'));
1954 assert_eq!(capture.shipouts, 0);
1955 assert_eq!(engine.resource_request_count(), 1);
1956 let request = &engine.resource_request_records()[0];
1957 assert_eq!(request.name, "cmr10");
1958 assert_eq!(
1959 request.kind,
1960 mathtex_portable_engine_generated::ResourceKind::Font
1961 );
1962 assert!(request.byte_len.is_some());
1963
1964 let fragment = generated_node_to_fragment(
1965 &engine,
1966 engine
1967 .captured_fragment_root()
1968 .expect("fragment root should be captured"),
1969 FragmentMetadata {
1970 engine_profile: "tex".into(),
1971 format_id: "generated".into(),
1972 fragment_kind: Default::default(),
1973 },
1974 )
1975 .expect("font-backed captured root should convert to IR");
1976
1977 let glyph_run = fragment
1978 .nodes
1979 .iter()
1980 .find_map(|node| match &node.kind {
1981 LayoutNodeKind::GlyphRun(run) => Some(run),
1982 _ => None,
1983 })
1984 .expect("font-backed hbox should emit a glyph run");
1985 assert_eq!(glyph_run.glyphs.len(), 1);
1986 assert_eq!(glyph_run.glyphs[0].glyph_id, GlyphId(u32::from(b'A')));
1987 assert!(glyph_run.glyphs[0].advance.x.0 > 0);
1988 }
1989}