use crate::node::Span;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct FaceId(pub usize);
#[derive(Clone, Debug, PartialEq)]
pub struct PlacedGlyph {
pub face: FaceId,
pub gid: u16,
pub ch: char,
pub x: f64,
pub y: f64,
pub size: f64,
pub span: Span,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PlacedRule {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
pub span: Span,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PathSeg {
Line {
to: (f64, f64),
},
Quad {
ctrl: (f64, f64),
to: (f64, f64),
},
}
#[derive(Clone, Debug, PartialEq)]
pub struct PathContour {
pub start: (f64, f64),
pub segments: Vec<PathSeg>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PlacedPath {
pub contours: Vec<PathContour>,
pub span: Span,
}
#[derive(Clone, Debug, PartialEq, Default)]
pub struct Layout {
pub glyphs: Vec<PlacedGlyph>,
pub rules: Vec<PlacedRule>,
pub paths: Vec<PlacedPath>,
pub width: f64,
pub height: f64,
pub depth: f64,
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub(crate) struct GlueSpec {
pub(crate) natural: f64,
pub(crate) stretch: f64,
pub(crate) shrink: f64,
}
#[allow(dead_code)]
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Positioned<T> {
pub(crate) dx: f64,
pub(crate) dy: f64,
pub(crate) node: T,
}
#[allow(dead_code)]
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum MNode {
Glyph {
face: FaceId,
gid: u16,
ch: char,
size: f64,
span: Span,
},
Rule { width: f64, height: f64, span: Span },
Path {
contours: Vec<PathContour>,
span: Span,
},
Kern(f64),
Glue(GlueSpec),
Box(MBox),
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum BoxKind {
Horizontal,
Vertical,
}
#[allow(dead_code)]
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct MBox {
pub(crate) kind: BoxKind,
pub(crate) width: f64,
pub(crate) height: f64,
pub(crate) depth: f64,
pub(crate) children: Vec<Positioned<MNode>>,
}
#[allow(dead_code)]
impl MBox {
pub(crate) fn empty(kind: BoxKind) -> Self {
Self {
kind,
width: 0.0,
height: 0.0,
depth: 0.0,
children: Vec::new(),
}
}
pub(crate) fn flatten_into(&self, x: f64, y: f64, out: &mut Layout) {
for child in &self.children {
let cx = x + child.dx;
let cy = y + child.dy;
match &child.node {
MNode::Glyph {
face,
gid,
ch,
size,
span,
} => out.glyphs.push(PlacedGlyph {
face: *face,
gid: *gid,
ch: *ch,
x: cx,
y: cy,
size: *size,
span: *span,
}),
MNode::Rule {
width,
height,
span,
} => out.rules.push(PlacedRule {
x: cx,
y: cy,
width: *width,
height: *height,
span: *span,
}),
MNode::Path { contours, span } => {
let moved = contours
.iter()
.map(|c| PathContour {
start: (c.start.0 + cx, c.start.1 + cy),
segments: c
.segments
.iter()
.map(|s| match s {
PathSeg::Line { to } => PathSeg::Line {
to: (to.0 + cx, to.1 + cy),
},
PathSeg::Quad { ctrl, to } => PathSeg::Quad {
ctrl: (ctrl.0 + cx, ctrl.1 + cy),
to: (to.0 + cx, to.1 + cy),
},
})
.collect(),
})
.collect();
out.paths.push(PlacedPath {
contours: moved,
span: *span,
});
}
MNode::Kern(_) | MNode::Glue(_) => {}
MNode::Box(inner) => inner.flatten_into(cx, cy, out),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn flatten_covers_the_whole_node_model() {
let mut vbox = MBox::empty(BoxKind::Vertical);
vbox.width = 1.0;
vbox.height = 1.0;
vbox.children = vec![
Positioned {
dx: 0.1,
dy: 0.4,
node: MNode::Rule {
width: 0.8,
height: 0.04,
span: Span::new(0, 4),
},
},
Positioned {
dx: 0.0,
dy: 0.0,
node: MNode::Kern(0.25),
},
Positioned {
dx: 0.0,
dy: 0.0,
node: MNode::Glue(GlueSpec {
natural: 3.0 / 18.0,
stretch: 1.5 / 18.0,
shrink: 1.0 / 18.0,
}),
},
Positioned {
dx: 0.2,
dy: -0.3,
node: MNode::Path {
contours: vec![PathContour {
start: (0.0, 0.0),
segments: vec![
PathSeg::Line { to: (0.5, 0.0) },
PathSeg::Quad {
ctrl: (0.5, 0.5),
to: (0.0, 0.5),
},
],
}],
span: Span::new(4, 9),
},
},
];
let mut layout = Layout::default();
MBox {
kind: BoxKind::Horizontal,
width: 2.0,
height: 1.0,
depth: 0.0,
children: vec![Positioned {
dx: 1.0,
dy: 0.5,
node: MNode::Box(vbox),
}],
}
.flatten_into(0.0, 0.0, &mut layout);
assert!(layout.glyphs.is_empty());
assert_eq!(layout.rules.len(), 1);
assert_eq!(layout.rules[0].x, 1.1);
assert_eq!(layout.rules[0].y, 0.9);
assert_eq!(layout.paths.len(), 1);
assert_eq!(layout.paths[0].contours[0].start, (1.2, 0.2));
match layout.paths[0].contours[0].segments[1] {
PathSeg::Quad { ctrl, to } => {
assert_eq!(ctrl, (1.7, 0.7));
assert_eq!(to, (1.2, 0.7));
}
PathSeg::Line { .. } => unreachable!("second segment is the quad"),
}
}
#[test]
fn flatten_accumulates_offsets() {
let inner = MBox {
kind: BoxKind::Horizontal,
width: 1.0,
height: 0.7,
depth: 0.0,
children: vec![Positioned {
dx: 0.25,
dy: 0.0,
node: MNode::Glyph {
face: FaceId(0),
gid: 7,
ch: 'x',
size: 1.0,
span: Span::new(0, 1),
},
}],
};
let outer = MBox {
kind: BoxKind::Horizontal,
width: 2.0,
height: 0.7,
depth: 0.0,
children: vec![Positioned {
dx: 1.0,
dy: 0.5,
node: MNode::Box(inner),
}],
};
let mut layout = Layout::default();
outer.flatten_into(0.0, 0.0, &mut layout);
assert_eq!(layout.glyphs.len(), 1);
assert_eq!(layout.glyphs[0].x, 1.25);
assert_eq!(layout.glyphs[0].y, 0.5);
}
}