#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unsafe_code)]
extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Fragment {
pub surface: Surface,
pub nodes: Vec<LayoutNode>,
pub source_map: SourceMap,
pub metadata: FragmentMetadata,
}
impl Fragment {
#[must_use]
pub fn node(&self, node: NodeId) -> Option<&LayoutNode> {
self.nodes.iter().find(|candidate| candidate.id == node)
}
pub fn source_entries_for_node(&self, node: NodeId) -> impl Iterator<Item = &SourceMapEntry> {
self.source_map.entries_for_node(node)
}
pub fn source_origins_for_node(&self, node: NodeId) -> impl Iterator<Item = SourceOrigin<'_>> {
self.source_entries_for_node(node)
.filter_map(|entry| self.source_origin_for_entry(entry))
}
#[must_use]
pub fn source_origin_for_entry(&self, entry: &SourceMapEntry) -> Option<SourceOrigin<'_>> {
let source = self.source_map.source(entry.range.source)?;
Some(SourceOrigin {
node: entry.node,
source,
span: entry.range.span,
role: entry.role,
})
}
#[must_use]
pub fn primary_source_for_node(&self, node: NodeId) -> Option<SourceRange> {
self.source_map.primary_range_for_node(node)
}
#[must_use]
pub fn glyph_source_range(&self, node: NodeId, glyph_index: usize) -> Option<SourceRange> {
let node = self.node(node)?;
let LayoutNodeKind::GlyphRun(run) = &node.kind else {
return None;
};
let span = run.glyphs.get(glyph_index)?.cluster?;
let source = node
.primary_source
.or_else(|| self.primary_source_for_node(node.id))?
.source;
Some(SourceRange { source, span })
}
#[must_use]
pub fn glyph_source_origin(
&self,
node: NodeId,
glyph_index: usize,
) -> Option<SourceOrigin<'_>> {
let range = self.glyph_source_range(node, glyph_index)?;
let source = self.source_map.source(range.source)?;
Some(SourceOrigin {
node,
source,
span: range.span,
role: SourceRole::Primary,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SourceOrigin<'a> {
pub node: NodeId,
pub source: &'a SourceFile,
pub span: ByteSpan,
pub role: SourceRole,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct FragmentMetadata {
pub engine_profile: String,
pub format_id: String,
pub fragment_kind: FragmentKind,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum FragmentKind {
#[default]
MathInline,
MathDisplay,
Text,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Surface {
pub width: Length,
pub height: Length,
pub baseline: Length,
}
#[derive(Clone, Debug, PartialEq)]
pub struct LayoutNode {
pub id: NodeId,
pub origin: Point,
pub bounds: Rect,
pub primary_source: Option<SourceRange>,
pub style: Style,
pub kind: LayoutNodeKind,
}
impl LayoutNode {
#[must_use]
pub fn baseline_y(&self) -> Option<Length> {
match &self.kind {
LayoutNodeKind::Box(layout_box) => Some(Length(
self.origin.y.0 + layout_box.metrics.baseline_offset().0,
)),
LayoutNodeKind::GlyphRun(_) => Some(self.origin.y),
LayoutNodeKind::List(_)
| LayoutNodeKind::Group { .. }
| LayoutNodeKind::Rule(_)
| LayoutNodeKind::Glue(_)
| LayoutNodeKind::Kern(_)
| LayoutNodeKind::Drawing(_) => None,
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum LayoutNodeKind {
Box(LayoutBox),
List(LayoutList),
Group {
children: Vec<NodeId>,
},
GlyphRun(GlyphRun),
Rule(Rule),
Glue(Glue),
Kern(Kern),
Drawing(Drawing),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LayoutBox {
pub kind: BoxKind,
pub metrics: BoxMetrics,
pub children: Vec<NodeId>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum BoxKind {
Horizontal,
Vertical,
Math,
Text,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct BoxMetrics {
pub width: Length,
pub height: Length,
pub depth: Length,
pub shift: Length,
}
impl BoxMetrics {
#[must_use]
pub const fn baseline_offset(&self) -> Length {
self.height
}
#[must_use]
pub const fn total_height(&self) -> Length {
Length(self.height.0 + self.depth.0)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LayoutList {
pub kind: ListKind,
pub children: Vec<NodeId>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ListKind {
Horizontal,
Vertical,
Math,
Paragraph,
Text,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GlyphRun {
pub font: FontRef,
pub direction: Direction,
pub script: Option<Tag>,
pub language: Option<String>,
pub glyphs: Vec<PositionedGlyph>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct PositionedGlyph {
pub glyph_id: GlyphId,
pub offset: Point,
pub advance: Point,
pub cluster: Option<ByteSpan>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FontRef {
pub id: FontId,
pub name: String,
pub size: Length,
pub features: Vec<FontFeature>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FontFeature {
pub tag: Tag,
pub value: u32,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Rule {
pub size: Size,
pub color: Color,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Glue {
pub amount: Length,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Kern {
pub amount: Length,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Drawing {
pub commands: Vec<DrawCommand>,
pub stroke: Option<Stroke>,
pub fill: Option<Color>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DrawCommand {
MoveTo(Point),
LineTo(Point),
CubicTo {
ctrl1: Point,
ctrl2: Point,
to: Point,
},
Close,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Stroke {
pub color: Color,
pub width: Length,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Style {
pub foreground: Color,
pub background: Option<Color>,
pub opacity: Alpha,
}
impl Default for Style {
fn default() -> Self {
Self {
foreground: Color::default(),
background: None,
opacity: Alpha::OPAQUE,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Alpha(pub u8);
impl Alpha {
pub const TRANSPARENT: Self = Self(0);
pub const OPAQUE: Self = Self(255);
}
impl Default for Alpha {
fn default() -> Self {
Self::OPAQUE
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SourceMap {
pub sources: Vec<SourceFile>,
pub entries: Vec<SourceMapEntry>,
}
impl SourceMap {
pub fn add_source(&mut self, name: impl Into<String>) -> SourceId {
let id = SourceId(self.sources.len() as u32);
self.sources.push(SourceFile {
id,
name: name.into(),
});
id
}
pub fn intern_source(&mut self, name: impl Into<String>) -> SourceId {
let name = name.into();
if let Some(source) = self.sources.iter().find(|source| source.name == name) {
return source.id;
}
self.add_source(name)
}
pub fn add_entry(&mut self, node: NodeId, range: SourceRange, role: SourceRole) {
self.entries.push(SourceMapEntry { node, range, role });
}
#[must_use]
pub fn source(&self, id: SourceId) -> Option<&SourceFile> {
self.sources.iter().find(|source| source.id == id)
}
pub fn entries_for_node(&self, node: NodeId) -> impl Iterator<Item = &SourceMapEntry> {
self.entries.iter().filter(move |entry| entry.node == node)
}
#[must_use]
pub fn primary_range_for_node(&self, node: NodeId) -> Option<SourceRange> {
self.entries_for_node(node)
.find(|entry| entry.role == SourceRole::Primary)
.map(|entry| entry.range)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SourceFile {
pub id: SourceId,
pub name: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SourceMapEntry {
pub node: NodeId,
pub range: SourceRange,
pub role: SourceRole,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SourceRange {
pub source: SourceId,
pub span: ByteSpan,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SourceRole {
Primary,
EnclosingConstruct,
MacroExpansion,
ResourceRequest,
Package,
FontDefinition,
Resource,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ByteSpan {
pub start: u32,
pub end: u32,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Point {
pub x: Length,
pub y: Length,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Size {
pub width: Length,
pub height: Length,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Rect {
pub origin: Point,
pub size: Size,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Length(pub i32);
impl Length {
pub const ZERO: Self = Self(0);
#[must_use]
pub const fn from_scaled_points(value: i32) -> Self {
Self(value)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct NodeId(pub u32);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct SourceId(pub u32);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct FontId(pub u32);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct GlyphId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum OutlineCommand {
MoveTo {
x: f32,
y: f32,
},
LineTo {
x: f32,
y: f32,
},
QuadTo {
cx: f32,
cy: f32,
x: f32,
y: f32,
},
CurveTo {
c1x: f32,
c1y: f32,
c2x: f32,
c2y: f32,
x: f32,
y: f32,
},
Close,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct GlyphOutline {
pub units_per_em: u16,
pub commands: Vec<OutlineCommand>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Tag(pub [u8; 4]);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum Direction {
#[default]
LeftToRight,
RightToLeft,
TopToBottom,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl Default for Color {
fn default() -> Self {
Self {
r: 0,
g: 0,
b: 0,
a: 255,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn source_map_tracks_multiple_sources_for_node() {
let mut source_map = SourceMap::default();
let input = source_map.add_source("input");
let package = source_map.add_source("amsmath.sty");
let input_again = source_map.intern_source("input");
source_map.add_entry(
NodeId(7),
SourceRange {
source: input,
span: ByteSpan { start: 1, end: 5 },
},
SourceRole::Primary,
);
source_map.add_entry(
NodeId(7),
SourceRange {
source: package,
span: ByteSpan { start: 10, end: 20 },
},
SourceRole::Package,
);
source_map.add_entry(
NodeId(7),
SourceRange {
source: input,
span: ByteSpan { start: 0, end: 16 },
},
SourceRole::ResourceRequest,
);
let entries = source_map.entries_for_node(NodeId(7)).count();
assert_eq!(input_again, input);
assert_eq!(source_map.sources.len(), 2);
assert_eq!(entries, 3);
assert_eq!(
source_map.source(input).expect("input source").name,
"input"
);
assert_eq!(
source_map.primary_range_for_node(NodeId(7)),
Some(SourceRange {
source: input,
span: ByteSpan { start: 1, end: 5 },
})
);
let fragment = Fragment {
source_map,
..Fragment::default()
};
let origins = fragment
.source_origins_for_node(NodeId(7))
.collect::<Vec<_>>();
assert_eq!(origins.len(), 3);
assert_eq!(origins[0].source.name, "input");
assert_eq!(origins[0].span, ByteSpan { start: 1, end: 5 });
assert_eq!(origins[1].source.name, "amsmath.sty");
assert_eq!(origins[1].role, SourceRole::Package);
assert_eq!(origins[2].role, SourceRole::ResourceRequest);
}
#[test]
fn fragment_resolves_glyph_clusters_to_original_source_ranges() {
let mut source_map = SourceMap::default();
let input = source_map.add_source("input");
let node = NodeId(4);
source_map.add_entry(
node,
SourceRange {
source: input,
span: ByteSpan { start: 0, end: 6 },
},
SourceRole::Primary,
);
let fragment = Fragment {
source_map,
nodes: alloc::vec![LayoutNode {
id: node,
origin: Point::default(),
bounds: Rect::default(),
primary_source: None,
style: Style::default(),
kind: LayoutNodeKind::GlyphRun(GlyphRun {
font: FontRef {
id: FontId(1),
name: "test".into(),
size: Length::ZERO,
features: alloc::vec![],
},
direction: Direction::LeftToRight,
script: None,
language: None,
glyphs: alloc::vec![PositionedGlyph {
glyph_id: GlyphId(9),
offset: Point::default(),
advance: Point::default(),
cluster: Some(ByteSpan { start: 2, end: 4 }),
}],
}),
}],
..Fragment::default()
};
assert_eq!(fragment.node(node).expect("node").id, node);
assert_eq!(
fragment.glyph_source_range(node, 0),
Some(SourceRange {
source: input,
span: ByteSpan { start: 2, end: 4 },
})
);
let origin = fragment
.glyph_source_origin(node, 0)
.expect("glyph source origin");
assert_eq!(origin.source.name, "input");
assert_eq!(origin.span, ByteSpan { start: 2, end: 4 });
assert_eq!(origin.role, SourceRole::Primary);
assert_eq!(fragment.glyph_source_range(node, 1), None);
}
#[test]
fn ir_represents_tex_boxes_and_lists_without_renderer_terms() {
let list = LayoutNode {
id: NodeId(1),
origin: Point::default(),
bounds: Rect::default(),
primary_source: None,
style: Style::default(),
kind: LayoutNodeKind::List(LayoutList {
kind: ListKind::Math,
children: alloc::vec![NodeId(2)],
}),
};
let boxed = LayoutNode {
id: NodeId(2),
origin: Point {
x: Length::ZERO,
y: Length::from_scaled_points(10_000),
},
bounds: Rect::default(),
primary_source: None,
style: Style::default(),
kind: LayoutNodeKind::Box(LayoutBox {
kind: BoxKind::Horizontal,
metrics: BoxMetrics {
width: Length::from_scaled_points(65_536),
height: Length::from_scaled_points(32_768),
depth: Length::from_scaled_points(16_384),
shift: Length::ZERO,
},
children: alloc::vec![],
}),
};
let glyphs = LayoutNode {
id: NodeId(3),
origin: Point {
x: Length::ZERO,
y: Length::from_scaled_points(50_000),
},
bounds: Rect::default(),
primary_source: None,
style: Style::default(),
kind: LayoutNodeKind::GlyphRun(GlyphRun {
font: FontRef {
id: FontId(1),
name: "test".into(),
size: Length::ZERO,
features: alloc::vec![],
},
direction: Direction::LeftToRight,
script: None,
language: None,
glyphs: alloc::vec![],
}),
};
let fragment = Fragment {
nodes: alloc::vec![list, boxed, glyphs],
..Fragment::default()
};
assert_eq!(fragment.nodes[0].baseline_y(), None);
assert_eq!(
fragment.nodes[1].baseline_y(),
Some(Length::from_scaled_points(42_768))
);
assert_eq!(
fragment.nodes[2].baseline_y(),
Some(Length::from_scaled_points(50_000))
);
match &fragment.nodes[0].kind {
LayoutNodeKind::List(layout_list) => {
assert_eq!(layout_list.kind, ListKind::Math);
assert_eq!(layout_list.children, alloc::vec![NodeId(2)]);
}
other => panic!("unexpected node: {other:?}"),
}
match &fragment.nodes[1].kind {
LayoutNodeKind::Box(layout_box) => {
assert_eq!(layout_box.kind, BoxKind::Horizontal);
assert_eq!(layout_box.metrics.depth, Length::from_scaled_points(16_384));
assert_eq!(
layout_box.metrics.baseline_offset(),
Length::from_scaled_points(32_768)
);
assert_eq!(
layout_box.metrics.total_height(),
Length::from_scaled_points(49_152)
);
}
other => panic!("unexpected node: {other:?}"),
}
}
}