mirror of
https://github.com/typst/typst
synced 2025-06-28 00:03:17 +08:00
Automatic frame merging
This commit is contained in:
parent
f27f7a05ab
commit
255d4c620f
148
src/frame.rs
148
src/frame.rs
@ -1,6 +1,6 @@
|
|||||||
//! Finished layouts.
|
//! Finished layouts.
|
||||||
|
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter, Write};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::font::FaceId;
|
use crate::font::FaceId;
|
||||||
@ -8,6 +8,7 @@ use crate::geom::{
|
|||||||
Align, Em, Length, Numeric, Paint, Path, Point, Size, Spec, Stroke, Transform,
|
Align, Em, Length, Numeric, Paint, Path, Point, Size, Spec, Stroke, Transform,
|
||||||
};
|
};
|
||||||
use crate::image::ImageId;
|
use crate::image::ImageId;
|
||||||
|
use crate::util::{EcoString, MaybeShared};
|
||||||
|
|
||||||
/// A finished layout with elements at fixed positions.
|
/// A finished layout with elements at fixed positions.
|
||||||
#[derive(Default, Clone, Eq, PartialEq)]
|
#[derive(Default, Clone, Eq, PartialEq)]
|
||||||
@ -57,20 +58,15 @@ impl Frame {
|
|||||||
self.elements.insert(layer, (pos, element));
|
self.elements.insert(layer, (pos, element));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a group element.
|
/// Add a frame.
|
||||||
pub fn push_frame(&mut self, pos: Point, frame: Arc<Self>) {
|
///
|
||||||
self.elements.push((pos, Element::Group(Group::new(frame))));
|
/// Automatically decides whether to inline the frame or to include it as a
|
||||||
}
|
/// group based on the number of elements in the frame.
|
||||||
|
pub fn push_frame(&mut self, pos: Point, frame: impl FrameRepr) {
|
||||||
/// Add all elements of another frame, placing them relative to the given
|
if self.elements.is_empty() || frame.as_ref().elements.len() <= 5 {
|
||||||
/// position.
|
frame.inline(self, pos);
|
||||||
pub fn merge_frame(&mut self, pos: Point, subframe: Self) {
|
|
||||||
if pos == Point::zero() && self.elements.is_empty() {
|
|
||||||
self.elements = subframe.elements;
|
|
||||||
} else {
|
} else {
|
||||||
for (subpos, child) in subframe.elements {
|
self.elements.push((pos, Element::Group(Group::new(frame.share()))));
|
||||||
self.elements.push((pos + subpos, child));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,30 +118,86 @@ impl Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Link the whole frame to a resource.
|
/// Link the whole frame to a resource.
|
||||||
pub fn link(&mut self, url: impl Into<String>) {
|
pub fn link(&mut self, url: EcoString) {
|
||||||
self.push(Point::zero(), Element::Link(url.into(), self.size));
|
self.push(Point::zero(), Element::Link(url, self.size));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Frame {
|
impl Debug for Frame {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.debug_struct("Frame")
|
f.debug_list()
|
||||||
.field("size", &self.size)
|
.entries(self.elements.iter().map(|(_, element)| element))
|
||||||
.field("baseline", &self.baseline)
|
|
||||||
.field(
|
|
||||||
"children",
|
|
||||||
&crate::util::debug(|f| {
|
|
||||||
f.debug_map()
|
|
||||||
.entries(self.elements.iter().map(|(k, v)| (k, v)))
|
|
||||||
.finish()
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<Frame> for Frame {
|
||||||
|
fn as_ref(&self) -> &Frame {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A representational form of a frame (owned, shared or maybe shared).
|
||||||
|
pub trait FrameRepr: AsRef<Frame> {
|
||||||
|
/// Transform into a shared representation.
|
||||||
|
fn share(self) -> Arc<Frame>;
|
||||||
|
|
||||||
|
/// Inline `self` into the sink frame.
|
||||||
|
fn inline(self, sink: &mut Frame, offset: Point);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameRepr for Frame {
|
||||||
|
fn share(self) -> Arc<Frame> {
|
||||||
|
Arc::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inline(self, sink: &mut Frame, offset: Point) {
|
||||||
|
if offset.is_zero() {
|
||||||
|
if sink.elements.is_empty() {
|
||||||
|
sink.elements = self.elements;
|
||||||
|
} else {
|
||||||
|
sink.elements.extend(self.elements);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sink.elements
|
||||||
|
.extend(self.elements.into_iter().map(|(p, e)| (p + offset, e)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameRepr for Arc<Frame> {
|
||||||
|
fn share(self) -> Arc<Frame> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inline(self, sink: &mut Frame, offset: Point) {
|
||||||
|
match Arc::try_unwrap(self) {
|
||||||
|
Ok(frame) => frame.inline(sink, offset),
|
||||||
|
Err(rc) => sink
|
||||||
|
.elements
|
||||||
|
.extend(rc.elements.iter().cloned().map(|(p, e)| (p + offset, e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameRepr for MaybeShared<Frame> {
|
||||||
|
fn share(self) -> Arc<Frame> {
|
||||||
|
match self {
|
||||||
|
Self::Owned(owned) => owned.share(),
|
||||||
|
Self::Shared(shared) => shared.share(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inline(self, sink: &mut Frame, offset: Point) {
|
||||||
|
match self {
|
||||||
|
Self::Owned(owned) => owned.inline(sink, offset),
|
||||||
|
Self::Shared(shared) => shared.inline(sink, offset),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The building block frames are composed of.
|
/// The building block frames are composed of.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub enum Element {
|
pub enum Element {
|
||||||
/// A group of elements.
|
/// A group of elements.
|
||||||
Group(Group),
|
Group(Group),
|
||||||
@ -156,11 +208,23 @@ pub enum Element {
|
|||||||
/// An image and its size.
|
/// An image and its size.
|
||||||
Image(ImageId, Size),
|
Image(ImageId, Size),
|
||||||
/// A link to an external resource and its trigger region.
|
/// A link to an external resource and its trigger region.
|
||||||
Link(String, Size),
|
Link(EcoString, Size),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Element {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Group(group) => group.fmt(f),
|
||||||
|
Self::Text(text) => write!(f, "{text:?}"),
|
||||||
|
Self::Shape(shape) => write!(f, "{shape:?}"),
|
||||||
|
Self::Image(image, _) => write!(f, "{image:?}"),
|
||||||
|
Self::Link(url, _) => write!(f, "Link({url:?})"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A group of elements with optional clipping.
|
/// A group of elements with optional clipping.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub struct Group {
|
pub struct Group {
|
||||||
/// The group's frame.
|
/// The group's frame.
|
||||||
pub frame: Arc<Frame>,
|
pub frame: Arc<Frame>,
|
||||||
@ -181,8 +245,15 @@ impl Group {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for Group {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.write_str("Group ")?;
|
||||||
|
self.frame.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A run of shaped text.
|
/// A run of shaped text.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub struct Text {
|
pub struct Text {
|
||||||
/// The font face the glyphs are contained in.
|
/// The font face the glyphs are contained in.
|
||||||
pub face_id: FaceId,
|
pub face_id: FaceId,
|
||||||
@ -201,6 +272,19 @@ impl Text {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for Text {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
// This is only a rough approxmiation of the source text.
|
||||||
|
f.write_str("Text(\"")?;
|
||||||
|
for glyph in &self.glyphs {
|
||||||
|
for c in glyph.c.escape_debug() {
|
||||||
|
f.write_char(c)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.write_str("\")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A glyph in a run of shaped text.
|
/// A glyph in a run of shaped text.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub struct Glyph {
|
pub struct Glyph {
|
||||||
@ -210,6 +294,8 @@ pub struct Glyph {
|
|||||||
pub x_advance: Em,
|
pub x_advance: Em,
|
||||||
/// The horizontal offset of the glyph.
|
/// The horizontal offset of the glyph.
|
||||||
pub x_offset: Em,
|
pub x_offset: Em,
|
||||||
|
/// The first character of the glyph's cluster.
|
||||||
|
pub c: char,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A geometric shape with optional fill and stroke.
|
/// A geometric shape with optional fill and stroke.
|
||||||
|
@ -83,7 +83,7 @@ impl Layout for ImageNode {
|
|||||||
|
|
||||||
// Apply link if it exists.
|
// Apply link if it exists.
|
||||||
if let Some(url) = styles.get(TextNode::LINK) {
|
if let Some(url) = styles.get(TextNode::LINK) {
|
||||||
frame.link(url);
|
frame.link(url.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(vec![Arc::new(frame)])
|
Ok(vec![Arc::new(frame)])
|
||||||
|
@ -132,7 +132,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
|||||||
|
|
||||||
// Apply link if it exists.
|
// Apply link if it exists.
|
||||||
if let Some(url) = styles.get(TextNode::LINK) {
|
if let Some(url) = styles.get(TextNode::LINK) {
|
||||||
frame.link(url);
|
frame.link(url.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
|
@ -551,7 +551,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let height = frame.size.y;
|
let height = frame.size.y;
|
||||||
output.merge_frame(pos, frame);
|
output.push_frame(pos, frame);
|
||||||
pos.y += height;
|
pos.y += height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ use super::{shape, Lang, Quoter, Quotes, RepeatNode, ShapedText, TextNode};
|
|||||||
use crate::font::FontStore;
|
use crate::font::FontStore;
|
||||||
use crate::library::layout::Spacing;
|
use crate::library::layout::Spacing;
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::util::{ArcExt, EcoString};
|
use crate::util::{EcoString, MaybeShared};
|
||||||
|
|
||||||
/// Arrange text, spacing and inline-level nodes into a paragraph.
|
/// Arrange text, spacing and inline-level nodes into a paragraph.
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
@ -283,7 +283,7 @@ enum Item<'a> {
|
|||||||
/// Fractional spacing between other items.
|
/// Fractional spacing between other items.
|
||||||
Fractional(Fraction),
|
Fractional(Fraction),
|
||||||
/// A layouted child node.
|
/// A layouted child node.
|
||||||
Frame(Frame),
|
Frame(Arc<Frame>),
|
||||||
/// A repeating node.
|
/// A repeating node.
|
||||||
Repeat(&'a RepeatNode, StyleChain<'a>),
|
Repeat(&'a RepeatNode, StyleChain<'a>),
|
||||||
}
|
}
|
||||||
@ -522,7 +522,7 @@ fn prepare<'a>(
|
|||||||
let size = Size::new(regions.first.x, regions.base.y);
|
let size = Size::new(regions.first.x, regions.base.y);
|
||||||
let pod = Regions::one(size, regions.base, Spec::splat(false));
|
let pod = Regions::one(size, regions.base, Spec::splat(false));
|
||||||
let frame = node.layout(ctx, &pod, styles)?.remove(0);
|
let frame = node.layout(ctx, &pod, styles)?.remove(0);
|
||||||
items.push(Item::Frame(Arc::take(frame)));
|
items.push(Item::Frame(frame));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1041,7 +1041,7 @@ fn stack(
|
|||||||
|
|
||||||
let pos = Point::with_y(output.size.y);
|
let pos = Point::with_y(output.size.y);
|
||||||
output.size.y += height;
|
output.size.y += height;
|
||||||
output.merge_frame(pos, frame);
|
output.push_frame(pos, frame);
|
||||||
|
|
||||||
regions.first.y -= height + p.leading;
|
regions.first.y -= height + p.leading;
|
||||||
first = false;
|
first = false;
|
||||||
@ -1111,7 +1111,7 @@ fn commit(
|
|||||||
// Build the frames and determine the height and baseline.
|
// Build the frames and determine the height and baseline.
|
||||||
let mut frames = vec![];
|
let mut frames = vec![];
|
||||||
for item in reordered {
|
for item in reordered {
|
||||||
let mut push = |offset: &mut Length, frame: Frame| {
|
let mut push = |offset: &mut Length, frame: MaybeShared<Frame>| {
|
||||||
let width = frame.size.x;
|
let width = frame.size.x;
|
||||||
top.set_max(frame.baseline());
|
top.set_max(frame.baseline());
|
||||||
bottom.set_max(frame.size.y - frame.baseline());
|
bottom.set_max(frame.size.y - frame.baseline());
|
||||||
@ -1127,10 +1127,11 @@ fn commit(
|
|||||||
offset += v.share(fr, remaining);
|
offset += v.share(fr, remaining);
|
||||||
}
|
}
|
||||||
Item::Text(shaped) => {
|
Item::Text(shaped) => {
|
||||||
push(&mut offset, shaped.build(&mut ctx.fonts, justification));
|
let frame = shaped.build(&mut ctx.fonts, justification);
|
||||||
|
push(&mut offset, MaybeShared::Owned(frame));
|
||||||
}
|
}
|
||||||
Item::Frame(frame) => {
|
Item::Frame(frame) => {
|
||||||
push(&mut offset, frame.clone());
|
push(&mut offset, MaybeShared::Shared(frame.clone()));
|
||||||
}
|
}
|
||||||
Item::Repeat(node, styles) => {
|
Item::Repeat(node, styles) => {
|
||||||
let before = offset;
|
let before = offset;
|
||||||
@ -1146,7 +1147,7 @@ fn commit(
|
|||||||
}
|
}
|
||||||
if frame.size.x > Length::zero() {
|
if frame.size.x > Length::zero() {
|
||||||
for _ in 0 .. (count as usize).min(1000) {
|
for _ in 0 .. (count as usize).min(1000) {
|
||||||
push(&mut offset, frame.as_ref().clone());
|
push(&mut offset, MaybeShared::Shared(frame.clone()));
|
||||||
offset += apart;
|
offset += apart;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1168,7 +1169,7 @@ fn commit(
|
|||||||
for (offset, frame) in frames {
|
for (offset, frame) in frames {
|
||||||
let x = offset + p.align.position(remaining);
|
let x = offset + p.align.position(remaining);
|
||||||
let y = top - frame.baseline();
|
let y = top - frame.baseline();
|
||||||
output.merge_frame(Point::new(x, y), frame);
|
output.push_frame(Point::new(x, y), frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(output)
|
Ok(output)
|
||||||
|
@ -100,6 +100,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
Em::zero()
|
Em::zero()
|
||||||
},
|
},
|
||||||
x_offset: glyph.x_offset,
|
x_offset: glyph.x_offset,
|
||||||
|
c: glyph.c,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -118,7 +119,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
|
|
||||||
// Apply link if it exists.
|
// Apply link if it exists.
|
||||||
if let Some(url) = self.styles.get(TextNode::LINK) {
|
if let Some(url) = self.styles.get(TextNode::LINK) {
|
||||||
frame.link(url);
|
frame.link(url.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
frame
|
frame
|
||||||
|
@ -11,7 +11,7 @@ pub use prehashed::Prehashed;
|
|||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::ops::Range;
|
use std::ops::{Deref, Range};
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -101,6 +101,31 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Either owned or shared.
|
||||||
|
pub enum MaybeShared<T> {
|
||||||
|
/// Owned data.
|
||||||
|
Owned(T),
|
||||||
|
/// Shared data.
|
||||||
|
Shared(Arc<T>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsRef<T> for MaybeShared<T> {
|
||||||
|
fn as_ref(&self) -> &T {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for MaybeShared<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
match self {
|
||||||
|
Self::Owned(owned) => owned,
|
||||||
|
Self::Shared(shared) => shared,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Additional methods for slices.
|
/// Additional methods for slices.
|
||||||
pub trait SliceExt<T> {
|
pub trait SliceExt<T> {
|
||||||
/// Split a slice into consecutive runs with the same key and yield for
|
/// Split a slice into consecutive runs with the same key and yield for
|
||||||
|
@ -101,7 +101,7 @@ fn main() {
|
|||||||
&png_path,
|
&png_path,
|
||||||
&ref_path,
|
&ref_path,
|
||||||
pdf_path.as_deref(),
|
pdf_path.as_deref(),
|
||||||
args.syntax,
|
&args.print,
|
||||||
) as usize;
|
) as usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,19 +114,27 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parsed command line arguments.
|
||||||
struct Args {
|
struct Args {
|
||||||
filter: Vec<String>,
|
filter: Vec<String>,
|
||||||
exact: bool,
|
exact: bool,
|
||||||
syntax: bool,
|
|
||||||
pdf: bool,
|
pdf: bool,
|
||||||
|
print: PrintConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Which things to print out for debugging.
|
||||||
|
#[derive(Default, Copy, Clone, Eq, PartialEq)]
|
||||||
|
struct PrintConfig {
|
||||||
|
syntax: bool,
|
||||||
|
frames: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Args {
|
impl Args {
|
||||||
fn new(args: impl Iterator<Item = String>) -> Self {
|
fn new(args: impl Iterator<Item = String>) -> Self {
|
||||||
let mut filter = Vec::new();
|
let mut filter = Vec::new();
|
||||||
let mut exact = false;
|
let mut exact = false;
|
||||||
let mut syntax = false;
|
|
||||||
let mut pdf = false;
|
let mut pdf = false;
|
||||||
|
let mut print = PrintConfig::default();
|
||||||
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
match arg.as_str() {
|
match arg.as_str() {
|
||||||
@ -136,14 +144,16 @@ impl Args {
|
|||||||
"--exact" => exact = true,
|
"--exact" => exact = true,
|
||||||
// Generate PDFs.
|
// Generate PDFs.
|
||||||
"--pdf" => pdf = true,
|
"--pdf" => pdf = true,
|
||||||
// Debug print the layout trees.
|
// Debug print the syntax trees.
|
||||||
"--syntax" => syntax = true,
|
"--syntax" => print.syntax = true,
|
||||||
|
// Debug print the frames.
|
||||||
|
"--frames" => print.frames = true,
|
||||||
// Everything else is a file filter.
|
// Everything else is a file filter.
|
||||||
_ => filter.push(arg),
|
_ => filter.push(arg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self { filter, pdf, syntax, exact }
|
Self { filter, exact, pdf, print }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches(&self, path: &Path) -> bool {
|
fn matches(&self, path: &Path) -> bool {
|
||||||
@ -163,7 +173,7 @@ fn test(
|
|||||||
png_path: &Path,
|
png_path: &Path,
|
||||||
ref_path: &Path,
|
ref_path: &Path,
|
||||||
pdf_path: Option<&Path>,
|
pdf_path: Option<&Path>,
|
||||||
syntax: bool,
|
print: &PrintConfig,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let name = src_path.strip_prefix(TYP_DIR).unwrap_or(src_path);
|
let name = src_path.strip_prefix(TYP_DIR).unwrap_or(src_path);
|
||||||
println!("Testing {}", name.display());
|
println!("Testing {}", name.display());
|
||||||
@ -199,7 +209,7 @@ fn test(
|
|||||||
i,
|
i,
|
||||||
compare_ref,
|
compare_ref,
|
||||||
line,
|
line,
|
||||||
syntax,
|
print,
|
||||||
&mut rng,
|
&mut rng,
|
||||||
);
|
);
|
||||||
ok &= part_ok;
|
ok &= part_ok;
|
||||||
@ -217,12 +227,25 @@ fn test(
|
|||||||
fs::write(pdf_path, pdf_data).unwrap();
|
fs::write(pdf_path, pdf_data).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if print.frames {
|
||||||
|
for frame in &frames {
|
||||||
|
println!("Frame: {:#?}", frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let canvas = render(ctx, &frames);
|
let canvas = render(ctx, &frames);
|
||||||
fs::create_dir_all(&png_path.parent().unwrap()).unwrap();
|
fs::create_dir_all(&png_path.parent().unwrap()).unwrap();
|
||||||
canvas.save_png(png_path).unwrap();
|
canvas.save_png(png_path).unwrap();
|
||||||
|
|
||||||
if let Ok(ref_pixmap) = sk::Pixmap::load_png(ref_path) {
|
if let Ok(ref_pixmap) = sk::Pixmap::load_png(ref_path) {
|
||||||
if canvas != ref_pixmap {
|
if canvas.width() != ref_pixmap.width()
|
||||||
|
|| canvas.height() != ref_pixmap.height()
|
||||||
|
|| canvas
|
||||||
|
.data()
|
||||||
|
.iter()
|
||||||
|
.zip(ref_pixmap.data())
|
||||||
|
.any(|(&a, &b)| a.abs_diff(b) > 2)
|
||||||
|
{
|
||||||
println!(" Does not match reference image. ❌");
|
println!(" Does not match reference image. ❌");
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
@ -233,7 +256,7 @@ fn test(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
if !syntax {
|
if *print == PrintConfig::default() {
|
||||||
print!("\x1b[1A");
|
print!("\x1b[1A");
|
||||||
}
|
}
|
||||||
println!("Testing {} ✔", name.display());
|
println!("Testing {} ✔", name.display());
|
||||||
@ -249,14 +272,14 @@ fn test_part(
|
|||||||
i: usize,
|
i: usize,
|
||||||
compare_ref: bool,
|
compare_ref: bool,
|
||||||
line: usize,
|
line: usize,
|
||||||
syntax: bool,
|
print: &PrintConfig,
|
||||||
rng: &mut LinearShift,
|
rng: &mut LinearShift,
|
||||||
) -> (bool, bool, Vec<Arc<Frame>>) {
|
) -> (bool, bool, Vec<Arc<Frame>>) {
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
|
|
||||||
let id = ctx.sources.provide(src_path, src);
|
let id = ctx.sources.provide(src_path, src);
|
||||||
let source = ctx.sources.get(id);
|
let source = ctx.sources.get(id);
|
||||||
if syntax {
|
if print.syntax {
|
||||||
println!("Syntax Tree: {:#?}", source.root())
|
println!("Syntax Tree: {:#?}", source.root())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user