diff --git a/Cargo.lock b/Cargo.lock index a45d93c3..375ae9be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4476,6 +4476,7 @@ dependencies = [ "base64", "comemo", "log", + "siphasher", "tiny-skia", "typst", "typst-ts-core", diff --git a/core/src/hash.rs b/core/src/hash.rs index 26ad1730..b219e16c 100644 --- a/core/src/hash.rs +++ b/core/src/hash.rs @@ -5,6 +5,7 @@ use siphasher::sip128::{Hasher128, SipHasher13}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize as rDeser, Serialize as rSer}; +use typst::diag::StrResult; /// See /// The fingerprint conflicts should be very rare and should be handled by the @@ -35,26 +36,39 @@ impl Fingerprint { ((self.1 as u128) << 64) | self.0 as u128 } + pub fn try_from_str(s: &str) -> StrResult { + let bytes = base64::engine::general_purpose::STANDARD_NO_PAD + .decode(&s.as_bytes()[..11]) + .expect("invalid base64 string"); + let lo = u64::from_le_bytes(bytes.try_into().unwrap()); + let mut bytes = base64::engine::general_purpose::STANDARD_NO_PAD + .decode(&s.as_bytes()[11..]) + .expect("invalid base64 string"); + bytes.resize(8, 0); + let hi = u64::from_le_bytes(bytes.try_into().unwrap()); + Ok(Self::from_pair(lo, hi)) + } + /// Create a xml id from the given prefix and the fingerprint of this /// reference. Note that the entire html document shares namespace for /// ids. #[comemo::memoize] pub fn as_svg_id(self, prefix: &'static str) -> String { - let fingerprint_hi = + let fingerprint_lo = base64::engine::general_purpose::STANDARD_NO_PAD.encode(self.0.to_le_bytes()); if self.1 == 0 { - return [prefix, &fingerprint_hi].join(""); + return [prefix, &fingerprint_lo].join(""); } // possible the id in the lower 64 bits. - let fingerprint_lo = { + let fingerprint_hi = { let id = self.1.to_le_bytes(); // truncate zero let rev_zero = id.iter().rev().skip_while(|&&b| b == 0).count(); let id = &id[..rev_zero]; base64::engine::general_purpose::STANDARD_NO_PAD.encode(id) }; - [prefix, &fingerprint_hi, &fingerprint_lo].join("") + [prefix, &fingerprint_lo, &fingerprint_hi].join("") } } @@ -66,18 +80,21 @@ pub trait FingerprintHasher: std::hash::Hasher { } /// A fingerprint hasher that uses the [`SipHasher13`] algorithm. -struct FingerprintSipHasher { +#[derive(Default)] +pub struct FingerprintSipHasher { /// The underlying data passed to the hasher. data: Vec, } +pub type FingerprintSipHasherBase = SipHasher13; + impl std::hash::Hasher for FingerprintSipHasher { fn write(&mut self, bytes: &[u8]) { self.data.extend_from_slice(bytes); } fn finish(&self) -> u64 { - let mut inner = SipHasher13::new(); + let mut inner = FingerprintSipHasherBase::new(); self.data.hash(&mut inner); inner.finish() } @@ -86,7 +103,7 @@ impl std::hash::Hasher for FingerprintSipHasher { impl FingerprintHasher for FingerprintSipHasher { fn finish_fingerprint(&self) -> (Fingerprint, Vec) { let buffer = self.data.clone(); - let mut inner = SipHasher13::new(); + let mut inner = FingerprintSipHasherBase::new(); buffer.hash(&mut inner); let hash = inner.finish128(); (Fingerprint(hash.h1, hash.h2), buffer) @@ -157,3 +174,18 @@ pub fn item_hash128(item: &T) -> u128 { pub fn hash128(t: &T) -> u128 { typst::util::hash128(t) } + +#[test] +fn test_fingerprint() { + let t = Fingerprint::from_pair(0, 1); + assert_eq!(Fingerprint::try_from_str(&t.as_svg_id("")).unwrap(), t); + + let t = Fingerprint::from_pair(1, 1); + assert_eq!(Fingerprint::try_from_str(&t.as_svg_id("")).unwrap(), t); + + let t = Fingerprint::from_pair(1, 0); + assert_eq!(Fingerprint::try_from_str(&t.as_svg_id("")).unwrap(), t); + + let t = Fingerprint::from_pair(0, 0); + assert_eq!(Fingerprint::try_from_str(&t.as_svg_id("")).unwrap(), t); +} diff --git a/core/src/vector/bbox.rs b/core/src/vector/bbox.rs index 52e87b56..d2eef1fa 100644 --- a/core/src/vector/bbox.rs +++ b/core/src/vector/bbox.rs @@ -5,6 +5,7 @@ use comemo::Prehashed; use typst::font::Font; use super::ir::{FontIndice, FontRef, GlyphPackBuilder, GlyphRef}; +use super::vm::RenderState; use super::{ flat_ir::{self, Module}, flat_vm::{FlatGroupContext, FlatIncrGroupContext, FlatIncrRenderVm, FlatRenderVm}, @@ -205,8 +206,14 @@ impl TransformContext for BBoxBuilder { /// See [`GroupContext`]. impl> GroupContext for BBoxBuilder { - fn render_item_at(&mut self, ctx: &mut C, pos: ir::Point, item: &ir::SvgItem) { - let bbox = ctx.render_item(item); + fn render_item_at( + &mut self, + state: RenderState, + ctx: &mut C, + pos: ir::Point, + item: &ir::SvgItem, + ) { + let bbox = ctx.render_item(state, item); self.inner.push((pos, bbox)); } @@ -215,7 +222,7 @@ impl> GroupContext for BBoxBuilder self.render_glyph_ref_inner(pos, &glyph_ref, glyph) } - fn render_path(&mut self, _ctx: &mut C, path: &ir::PathItem) { + fn render_path(&mut self, _ctx: &mut C, path: &ir::PathItem, _abs_ref: &Fingerprint) { let path = PathRepr::from_item(path).unwrap(); self.inner.push(( ir::Point::default(), @@ -407,7 +414,7 @@ fn convert_path(path_data: &str) -> Option { #[cfg(test)] mod tests { - use tests::ir::PathItem; + use tests::ir::{PathItem, Size}; use crate::vector::path2d::SvgPath2DBuilder; @@ -438,6 +445,7 @@ mod tests { let d = d.0.into(); let path = PathItem { d, + size: None, styles: Default::default(), }; @@ -450,7 +458,10 @@ mod tests { let mut task = t.get(); let rect = get_rect_item(1., 2., 10., 20.); - let bbox = task.render_item(&rect); + let bbox = task.render_item( + RenderState::new_size(Size::new(Scalar(10.), Scalar(20.))), + &rect, + ); println!("{:?}", bbox.realize(Transform::identity())); } @@ -461,7 +472,10 @@ mod tests { let mut task = t.get(); let rect = get_rect_item(1., 2., 10., 20.); - let bbox = task.render_item(&rect); + let bbox = task.render_item( + RenderState::new_size(Size::new(Scalar(10.), Scalar(20.))), + &rect, + ); let ts = sk::Transform::from_translate(10., 20.); println!("{:?}", bbox.realize(ts.into())); diff --git a/core/src/vector/flat_ir/mod.rs b/core/src/vector/flat_ir/mod.rs index 7dcda903..8daa8c75 100644 --- a/core/src/vector/flat_ir/mod.rs +++ b/core/src/vector/flat_ir/mod.rs @@ -39,8 +39,8 @@ use crate::{ use super::{ geom::{Abs, Point, Size}, ir::{ - DefId, FontItem, FontRef, GlyphItem, GlyphRef, ImageGlyphItem, ImageItem, ImmutStr, - LinkItem, OutlineGlyphItem, PathItem, SpanId, TextShape, TransformItem, + DefId, FontItem, FontRef, GlyphItem, GlyphRef, GradientItem, ImageGlyphItem, ImageItem, + ImmutStr, LinkItem, OutlineGlyphItem, PathItem, SpanId, TextShape, TransformItem, }, }; @@ -67,7 +67,8 @@ pub enum FlatSvgItem { Path(PathItem), Text(FlatTextItem), Item(TransformedRef), - Group(GroupRef), + Group(GroupRef, Option), + Gradient(GradientItem), } /// Flatten text item. diff --git a/core/src/vector/flat_ir/module.rs b/core/src/vector/flat_ir/module.rs index a2778834..5f640fed 100644 --- a/core/src/vector/flat_ir/module.rs +++ b/core/src/vector/flat_ir/module.rs @@ -255,6 +255,7 @@ impl ModuleBuilderImpl { FlatSvgItem::Path(path) } + SvgItem::Gradient(g) => FlatSvgItem::Gradient(g), SvgItem::Link(link) => FlatSvgItem::Link(link), SvgItem::Text(text) => { let font = self.build_font(&text.font); @@ -288,7 +289,7 @@ impl ModuleBuilderImpl { FlatSvgItem::Item(TransformedRef(transform, item_id)) } - SvgItem::Group(group) => { + SvgItem::Group(group, size) => { let t = if self.should_attach_debug_info { Some(self.source_mapping_buffer.len()) } else { @@ -312,7 +313,8 @@ impl ModuleBuilderImpl { .push(SourceMappingNode::Group(sm_range.collect())); self.source_mapping_buffer.push(sm_id); } - FlatSvgItem::Group(GroupRef(items.into())) + + FlatSvgItem::Group(GroupRef(items.into()), size) } }; diff --git a/core/src/vector/flat_vm.rs b/core/src/vector/flat_vm.rs index 71b5735f..6af7425b 100644 --- a/core/src/vector/flat_vm.rs +++ b/core/src/vector/flat_vm.rs @@ -5,6 +5,7 @@ use crate::hash::Fingerprint; use super::flat_ir as ir; use super::ir::{FontIndice, GlyphRef}; +use super::vm::RenderState; use super::{ ir::{Point, Scalar}, vm::{GroupContext, TransformContext}, @@ -30,7 +31,13 @@ pub trait FlatGroupContext: Sized { fn with_frame(self, _ctx: &mut C, _group: &ir::GroupRef) -> Self { self } - fn with_text(self, _ctx: &mut C, _text: &ir::FlatTextItem) -> Self { + fn with_text( + self, + _ctx: &mut C, + _text: &ir::FlatTextItem, + _fill_key: &Fingerprint, + _state: RenderState, + ) -> Self { self } fn with_reuse(self, _ctx: &mut C, _v: &Fingerprint) -> Self { @@ -56,21 +63,28 @@ pub trait FlatRenderVm<'m>: Sized + FontIndice<'m> { self.start_flat_group(value) } - fn start_flat_text(&mut self, value: &Fingerprint, _text: &ir::FlatTextItem) -> Self::Group { + fn start_flat_text( + &mut self, + _state: RenderState, + value: &Fingerprint, + _text: &ir::FlatTextItem, + ) -> Self::Group { self.start_flat_group(value) } #[doc(hidden)] /// Default implemenetion to render an item into the a `` element. fn _render_flat_item(&mut self, abs_ref: &Fingerprint) -> Self::Resultant { + // todo state + let state = RenderState::default(); let item: &'m ir::FlatSvgItem = self.get_item(abs_ref).unwrap(); match &item { - ir::FlatSvgItem::Group(group) => self.render_group_ref(abs_ref, group), + ir::FlatSvgItem::Group(group, _) => self.render_group_ref(abs_ref, group), ir::FlatSvgItem::Item(transformed) => self.render_transformed_ref(abs_ref, transformed), - ir::FlatSvgItem::Text(text) => self.render_flat_text(abs_ref, text), + ir::FlatSvgItem::Text(text) => self.render_flat_text(state, abs_ref, text), ir::FlatSvgItem::Path(path) => { let mut g = self.start_flat_group(abs_ref); - g.render_path(self, path); + g.render_path(self, path, abs_ref); g.into() } ir::FlatSvgItem::Link(link) => { @@ -83,7 +97,7 @@ pub trait FlatRenderVm<'m>: Sized + FontIndice<'m> { g.render_image(self, image); g.into() } - ir::FlatSvgItem::None => { + ir::FlatSvgItem::Gradient(..) | ir::FlatSvgItem::None => { panic!("FlatRenderVm.RenderFrame.UnknownItem {:?}", item) } } @@ -125,10 +139,11 @@ pub trait FlatRenderVm<'m>: Sized + FontIndice<'m> { /// Render a text into the underlying context. fn render_flat_text( &mut self, + state: RenderState, abs_ref: &Fingerprint, text: &ir::FlatTextItem, ) -> Self::Resultant { - let group_ctx = self.start_flat_text(abs_ref, text); + let group_ctx = self.start_flat_text(state, abs_ref, text); let font = self.get_font(&text.font).unwrap(); @@ -184,13 +199,15 @@ where next_abs_ref: &Fingerprint, prev_abs_ref: &Fingerprint, ) -> Self::Resultant { + // todo state + let state = RenderState::default(); let next_item: &'m ir::FlatSvgItem = self.get_item(next_abs_ref).unwrap(); let prev_item = self.get_item(prev_abs_ref); let mut group_ctx = self.start_flat_group(next_abs_ref); match &next_item { - ir::FlatSvgItem::Group(group) => { + ir::FlatSvgItem::Group(group, _) => { let mut group_ctx = group_ctx .with_reuse(self, prev_abs_ref) .with_frame(self, group); @@ -205,11 +222,11 @@ where group_ctx } ir::FlatSvgItem::Text(text) => { - let group_ctx = group_ctx.with_text(self, text); + let group_ctx = group_ctx.with_text(self, text, next_abs_ref, state); self.render_diff_flat_text(group_ctx, text) } ir::FlatSvgItem::Path(path) => { - group_ctx.render_path(self, path); + group_ctx.render_path(self, path, next_abs_ref); group_ctx } ir::FlatSvgItem::Link(link) => { @@ -220,7 +237,7 @@ where group_ctx.render_image(self, image); group_ctx } - ir::FlatSvgItem::None => { + ir::FlatSvgItem::Gradient(..) | ir::FlatSvgItem::None => { panic!("FlatRenderVm.RenderFrame.UnknownItem {:?}", next_item) } } @@ -243,7 +260,7 @@ where prev_item_: Option<&ir::FlatSvgItem>, next: &ir::GroupRef, ) { - if let Some(ir::FlatSvgItem::Group(prev_group)) = prev_item_ { + if let Some(ir::FlatSvgItem::Group(prev_group, _)) = prev_item_ { let mut unused_prev: BTreeMap = prev_group.0.iter().map(|v| v.1).enumerate().collect(); let reusable: HashSet = diff --git a/core/src/vector/geom.rs b/core/src/vector/geom.rs index e19ebd1d..c2a1bf95 100644 --- a/core/src/vector/geom.rs +++ b/core/src/vector/geom.rs @@ -5,8 +5,8 @@ use std::{ }; use typst::geom::{ - Abs as TypstAbs, Axes as TypstAxes, Point as TypstPoint, Ratio as TypstRatio, - Scalar as TypstScalar, Transform as TypstTransform, + Abs as TypstAbs, Angle as TypstAngle, Axes as TypstAxes, Point as TypstPoint, + Ratio as TypstRatio, Scalar as TypstScalar, Transform as TypstTransform, }; #[cfg(feature = "rkyv")] @@ -19,6 +19,12 @@ use rkyv::{Archive, Deserialize as rDeser, Serialize as rSer}; #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] pub struct Scalar(pub f32); +impl Scalar { + fn is_zero(&self) -> bool { + self.0 == 0.0 + } +} + impl From for Scalar { fn from(float: f32) -> Self { Self(float) @@ -55,6 +61,12 @@ impl From for Scalar { } } +impl From for Scalar { + fn from(scalar: TypstAngle) -> Self { + Self(scalar.to_rad() as f32) + } +} + impl fmt::Debug for Scalar { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) @@ -128,6 +140,8 @@ pub type Size = Axes; pub type Point = Axes; /// Ratio within range [0, 1] pub type Ratio = Scalar; +/// Angle in radians +pub type Angle = Scalar; /// A container with a horizontal and vertical component. #[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Hash)] @@ -257,12 +271,65 @@ impl Transform { ts.into() } + #[inline] + pub fn post_concat(self, other: Self) -> Self { + other.pre_concat(self) + } + #[inline] pub fn pre_translate(self, tx: f32, ty: f32) -> Self { let ts: tiny_skia::Transform = self.into(); let ts = ts.pre_translate(tx, ty); ts.into() } + + /// Whether this is the identity transformation. + pub fn is_identity(self) -> bool { + self == Self::identity() + } + + /// Inverts the transformation. + /// + /// Returns `None` if the determinant of the matrix is zero. + pub fn invert(self) -> Option { + // Allow the trivial case to be inlined. + if self.is_identity() { + return Some(self); + } + + // Fast path for scale-translate-only transforms. + if self.kx.is_zero() && self.ky.is_zero() { + if self.sx.is_zero() || self.sy.is_zero() { + return Some(Self::from_translate(-self.tx, -self.ty)); + } + + let inv_x = 1.0 / self.sx.0; + let inv_y = 1.0 / self.sy.0; + return Some(Self { + sx: Scalar(inv_x), + ky: Scalar(0.), + kx: Scalar(0.), + sy: Scalar(inv_y), + tx: Scalar(-self.tx.0 * inv_x), + ty: Scalar(-self.ty.0 * inv_y), + }); + } + + let det = self.sx.0 * self.sy.0 - self.kx.0 * self.ky.0; + if det.abs() < 1e-12 { + return None; + } + + let inv_det = 1.0 / det; + Some(Self { + sx: Scalar(self.sy.0 * inv_det), + ky: Scalar(-self.ky.0 * inv_det), + kx: Scalar(-self.kx.0 * inv_det), + sy: Scalar(self.sx.0 * inv_det), + tx: Scalar((self.kx.0 * self.ty.0 - self.sy.0 * self.tx.0) * inv_det), + ty: Scalar((self.ky.0 * self.tx.0 - self.sx.0 * self.ty.0) * inv_det), + }) + } } impl From for Transform { diff --git a/core/src/vector/incr.rs b/core/src/vector/incr.rs index db9f8741..f1c1a0dd 100644 --- a/core/src/vector/incr.rs +++ b/core/src/vector/incr.rs @@ -70,6 +70,10 @@ impl IncrDocServer { } }) .collect::>(); + + for (_, ext) in lower_builder.extra_items.into_iter() { + builder.build(ext); + } let delta = builder.finalize_delta(); // max, min lifetime current, gc_items diff --git a/core/src/vector/ir.rs b/core/src/vector/ir.rs index 4abe4a34..35fa95e6 100644 --- a/core/src/vector/ir.rs +++ b/core/src/vector/ir.rs @@ -138,7 +138,9 @@ pub enum SvgItem { Path((PathItem, SpanId)), Text(TextItem), Transformed(TransformedItem), - Group(GroupItem), + Group(GroupItem, Option), + // todo: big size 64 + Gradient(GradientItem), } /// Data of an `` element. @@ -232,6 +234,112 @@ pub struct LinkItem { pub size: Size, } +/// Item representing an `` element. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[cfg_attr(feature = "rkyv", derive(Archive, rDeser, rSer))] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +pub struct ColorItem { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +impl ColorItem { + // todo: to_css + pub fn to_hex(self) -> String { + let Self { r, g, b, a } = self; + if a != 255 { + format!("#{:02x}{:02x}{:02x}{:02x}", r, g, b, a) + } else { + format!("#{:02x}{:02x}{:02x}", r, g, b) + } + } + + pub fn typst(&self) -> typst::geom::Color { + typst::geom::Color::from_u8(self.r, self.g, self.b, self.a) + } +} + +/// A color space for mixing. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "rkyv", derive(Archive, rDeser, rSer))] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +pub enum ColorSpace { + /// A perceptual color space. + Oklab, + + /// The standard RGB color space. + Srgb, + + /// The D65-gray color space. + D65Gray, + + /// The linear RGB color space. + LinearRgb, + + /// The HSL color space. + Hsl, + + /// The HSV color space. + Hsv, + + /// The CMYK color space. + Cmyk, +} + +impl From for ColorSpace { + fn from(value: typst::geom::ColorSpace) -> Self { + use typst::geom::ColorSpace::*; + match value { + Oklab => Self::Oklab, + Srgb => Self::Srgb, + D65Gray => Self::D65Gray, + LinearRgb => Self::LinearRgb, + Hsl => Self::Hsl, + Hsv => Self::Hsv, + Cmyk => Self::Cmyk, + } + } +} + +impl From for typst::geom::ColorSpace { + fn from(value: ColorSpace) -> Self { + use typst::geom::ColorSpace::*; + match value { + ColorSpace::Oklab => Oklab, + ColorSpace::Srgb => Srgb, + ColorSpace::D65Gray => D65Gray, + ColorSpace::LinearRgb => LinearRgb, + ColorSpace::Hsl => Hsl, + ColorSpace::Hsv => Hsv, + ColorSpace::Cmyk => Cmyk, + } + } +} + +/// Item representing an `` element. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[cfg_attr(feature = "rkyv", derive(Archive, rDeser, rSer))] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +pub struct GradientItem { + /// The path instruction. + pub stops: Vec<(ColorItem, Scalar)>, + /// Whether the gradient is relative to itself (its own bounding box). + /// Otherwise, the gradient is relative to the parent bounding box. + pub relative_to_self: Option, + /// Whether to anti-alias the gradient (used for sharp gradients). + pub anti_alias: bool, + /// A color space for mixing. + pub space: ColorSpace, + /// The gradient kind. + /// See [`GradientKind`] for more information. + pub kind: GradientKind, + /// Additional gradient styles. + /// See [`GradientStyle`] for more information. + pub styles: Vec, +} + /// Item representing an `` element. #[derive(Debug, Clone, Hash, PartialEq, Eq)] #[cfg_attr(feature = "rkyv", derive(Archive, rDeser, rSer))] @@ -239,11 +347,39 @@ pub struct LinkItem { pub struct PathItem { /// The path instruction. pub d: ImmutStr, + /// bbox of the path. + pub size: Option, /// The path style. /// See [`PathStyle`] for more information. pub styles: Vec, } +/// Kind of graidents for [`GradientItem`]. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[cfg_attr(feature = "rkyv", derive(Archive, rDeser, rSer))] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +pub enum GradientKind { + /// Angle of a linear gradient. + Linear(Scalar), + /// Radius of a radial gradient. + Radial(Scalar), + /// Angle of a conic gradient. + Conic(Scalar), +} + +/// Attributes that is applicable to the [`GradientItem`]. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[cfg_attr(feature = "rkyv", derive(Archive, rDeser, rSer))] +#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] +pub enum GradientStyle { + /// Center of a radial or conic gradient. + Center(Point), + /// Focal center of a radial gradient. + FocalCenter(Point), + /// Focal radius of a radial gradient. + FocalRadius(Scalar), +} + /// Attributes that is applicable to the [`PathItem`]. #[derive(Debug, Clone, Hash, PartialEq, Eq)] #[cfg_attr(feature = "rkyv", derive(Archive, rDeser, rSer))] diff --git a/core/src/vector/lowering.rs b/core/src/vector/lowering.rs index f254a019..fdd06a84 100644 --- a/core/src/vector/lowering.rs +++ b/core/src/vector/lowering.rs @@ -1,12 +1,19 @@ //! Lowering Typst Document into SvgItem. +use std::any::Any; +use std::hash::Hash; use std::io::Read; use std::sync::Arc; use once_cell::sync::OnceCell; -use typst::doc::{Destination, Document, Frame, FrameItem, GroupItem, Meta, Position, TextItem}; +use typst::doc::{ + Destination, Document, Frame, FrameItem, FrameKind, GroupItem, Meta, Position, TextItem, +}; use typst::font::Font; -use typst::geom::{Dir, FixedStroke, Geometry, LineCap, LineJoin, PathItem, Shape, Size}; +use typst::geom::{ + Abs as TypstAbs, Dir, FixedStroke, Geometry, Gradient, LineCap, LineJoin, Paint, PathItem, + Ratio as TypstRatio, Relative, Shape, Size, Smart, +}; use typst::image::Image; use ttf_parser::OutlineBuilder; @@ -23,19 +30,34 @@ use super::{ utils::{AbsExt, ToCssExt}, }; use crate::font::GlyphProvider; +use crate::hash::{Fingerprint, FingerprintHasher, FingerprintSipHasher}; +use crate::vector::flat_ir::FlatSvgItem; +use crate::vector::ir::{ColorItem, GradientItem, GradientKind}; +use crate::ImmutStr; use ttf_parser::GlyphId; static WARN_VIEW_BOX: OnceCell<()> = OnceCell::new(); +#[derive(Clone)] +struct ShapeInfo { + path: ir::PathItem, + fill_gradient: Option<(Fingerprint, GradientItem)>, + stroke_gradient: Option<(Fingerprint, GradientItem)>, +} + /// Lower a frame item into svg item. pub struct LowerBuilder { introspector: Introspector, + /// Extra items that used by the document but not directly rendered. + /// For example, gradients. + pub extra_items: Vec<(Fingerprint, ir::SvgItem)>, } impl LowerBuilder { pub fn new(output: &Document) -> Self { Self { introspector: Introspector::new(&output.pages), + extra_items: Vec::new(), } } @@ -51,9 +73,17 @@ impl LowerBuilder { for (pos, item) in frame.items() { let item = match item { FrameItem::Group(group) => self.lower_group(group), - FrameItem::Text(text) => Self::lower_text(text), + FrameItem::Text(text) => self.lower_text(text), FrameItem::Shape(shape, span_id) => { - SvgItem::Path((Self::lower_shape(shape), span_id_to_u64(span_id))) + let s = Self::lower_shape(shape); + if let Some((f, gradient)) = s.fill_gradient { + self.extra_items.push((f, ir::SvgItem::Gradient(gradient))); + } + if let Some((f, gradient)) = s.stroke_gradient { + self.extra_items.push((f, ir::SvgItem::Gradient(gradient))); + } + + SvgItem::Path((s.path, span_id_to_u64(span_id))) } FrameItem::Image(image, size, span_id) => { SvgItem::Image((lower_image(image, *size), span_id_to_u64(span_id))) @@ -96,7 +126,10 @@ impl LowerBuilder { std::cmp::Ordering::Equal }); - SvgItem::Group(ir::GroupItem(items)) + match frame.kind() { + FrameKind::Hard => SvgItem::Group(ir::GroupItem(items), Some(frame.size().into())), + FrameKind::Soft => SvgItem::Group(ir::GroupItem(items), None), + } } /// Lower a group frame with optional transform and clipping into svg item. @@ -135,7 +168,11 @@ impl LowerBuilder { let d = builder.0.into(); inner = SvgItem::Transformed(ir::TransformedItem( - TransformItem::Clip(Arc::new(ir::PathItem { d, styles: vec![] })), + TransformItem::Clip(Arc::new(ir::PathItem { + d, + size: None, + styles: vec![], + })), Box::new(inner), )); }; @@ -173,7 +210,7 @@ impl LowerBuilder { /// Lower a text into svg item. // #[comemo::memoize] - pub(super) fn lower_text(text: &TextItem) -> SvgItem { + pub(super) fn lower_text(&mut self, text: &TextItem) -> SvgItem { let mut glyphs = Vec::with_capacity(text.glyphs.len()); for glyph in &text.glyphs { let id = GlyphId(glyph.id); @@ -186,7 +223,12 @@ impl LowerBuilder { let glyph_chars: String = text.text.to_string(); - let fill = text.fill.clone().to_css().into(); + // let fill = text.fill.clone().to_css().into(); + let mut fill_gradient = None; + let fill = Self::lower_paint(text.fill.clone(), &mut fill_gradient); + if let Some((f, gradient)) = fill_gradient { + self.extra_items.push((f, ir::SvgItem::Gradient(gradient))); + } let span_id = text .glyphs @@ -220,7 +262,7 @@ impl LowerBuilder { /// Lower a geometrical shape into svg item. #[comemo::memoize] - pub(super) fn lower_shape(shape: &Shape) -> ir::PathItem { + fn lower_shape(shape: &Shape) -> ShapeInfo { let mut builder = SvgPath2DBuilder(String::new()); // to ensure that our shape focus on the original point @@ -268,10 +310,15 @@ impl LowerBuilder { let mut styles = Vec::new(); + let mut fill_gradient = None; if let Some(paint_fill) = &shape.fill { - styles.push(ir::PathStyle::Fill(paint_fill.clone().to_css().into())); + styles.push(ir::PathStyle::Fill(Self::lower_paint( + paint_fill.clone(), + &mut fill_gradient, + ))); } + let mut stroke_gradient = None; // todo: default miter_limit, thickness if let Some(FixedStroke { paint, @@ -302,10 +349,110 @@ impl LowerBuilder { LineJoin::Round => styles.push(ir::PathStyle::StrokeLineJoin("round".into())), } - styles.push(ir::PathStyle::Stroke(paint.clone().to_css().into())); + styles.push(ir::PathStyle::Stroke(Self::lower_paint( + paint.clone(), + &mut stroke_gradient, + ))); + } + + let mut shape_size = shape.geometry.bbox_size(); + // Edge cases for strokes. + if shape_size.x.to_pt() == 0.0 { + shape_size.x = TypstAbs::pt(1.0); } - ir::PathItem { d, styles } + if shape_size.y.to_pt() == 0.0 { + shape_size.y = TypstAbs::pt(1.0); + } + + ShapeInfo { + path: ir::PathItem { + d, + size: Some(shape_size.into()), + styles, + }, + fill_gradient, + stroke_gradient, + } + } + + #[inline] + pub(super) fn lower_paint( + g: Paint, + cell: &mut Option<(Fingerprint, GradientItem)>, + ) -> ImmutStr { + match g { + Paint::Solid(c) => c.to_css().into(), + Paint::Gradient(g) => { + let (g, fingerprint) = Self::lower_graident(g); + *cell = Some((fingerprint, g)); + format!("url(#{})", fingerprint.as_svg_id("g")).into() + } + } + } + + #[comemo::memoize] + pub(super) fn lower_graident(g: Gradient) -> (GradientItem, Fingerprint) { + let mut stops = Vec::with_capacity(g.stops_ref().len()); + for (c, step) in g.stops_ref() { + let [r, g, b, a] = c.to_vec4_u8(); + stops.push((ColorItem { r, g, b, a }, (*step).into())) + } + + let relative_to_self = match g.relative() { + Smart::Auto => None, + Smart::Custom(t) => Some(t == Relative::Self_), + }; + + let anti_alias = g.anti_alias(); + let space = g.space().into(); + + let mut styles = Vec::new(); + let kind = match g { + Gradient::Linear(l) => GradientKind::Linear(l.angle.into()), + Gradient::Radial(l) => { + if l.center.x != TypstRatio::new(0.5) || l.center.y != TypstRatio::new(0.5) { + styles.push(ir::GradientStyle::Center(l.center.into())); + } + + if l.focal_center.x != TypstRatio::new(0.5) + || l.focal_center.y != TypstRatio::new(0.5) + { + styles.push(ir::GradientStyle::FocalCenter(l.focal_center.into())); + } + + if l.focal_radius != TypstRatio::zero() { + styles.push(ir::GradientStyle::FocalRadius(l.focal_radius.into())); + } + + GradientKind::Radial(l.radius.into()) + } + Gradient::Conic(l) => { + if l.center.x != TypstRatio::new(0.5) || l.center.y != TypstRatio::new(0.5) { + styles.push(ir::GradientStyle::Center(l.center.into())); + } + + GradientKind::Conic(l.angle.into()) + } + }; + + let g = GradientItem { + stops, + relative_to_self, + anti_alias, + space, + kind, + styles, + }; + + // todo: don't leak the fingerprint primitive + let flat_item = &FlatSvgItem::Gradient(g.clone()); + let mut f = FingerprintSipHasher::default(); + flat_item.type_id().hash(&mut f); + flat_item.hash(&mut f); + let (f, _) = f.finish_fingerprint(); + + (g, f) } } diff --git a/core/src/vector/mod.rs b/core/src/vector/mod.rs index 882bac2d..ac2cc896 100644 --- a/core/src/vector/mod.rs +++ b/core/src/vector/mod.rs @@ -82,7 +82,7 @@ fn rkyv_assertions() { const _: () = assert!(core::mem::align_of::() == 4); const _: () = assert!(core::mem::size_of::() == 16); const _: () = assert!(core::mem::align_of::() == 4); - const _: () = assert!(core::mem::size_of::() == 16); + const _: () = assert!(core::mem::size_of::() == 28); const _: () = assert!(core::mem::align_of::() == 4); const _: () = assert!(core::mem::size_of::() == 36); const _: () = assert!(core::mem::align_of::() == 4); @@ -102,4 +102,6 @@ fn rkyv_assertions() { const _: () = assert!(core::mem::align_of::() == 4); const _: () = assert!(core::mem::size_of::() == 16); const _: () = assert!(core::mem::align_of::() == 4); + const _: () = assert!(core::mem::size_of::() == 4); + const _: () = assert!(core::mem::align_of::() == 1); } diff --git a/core/src/vector/vm.rs b/core/src/vector/vm.rs index 75d78946..61149427 100644 --- a/core/src/vector/vm.rs +++ b/core/src/vector/vm.rs @@ -1,4 +1,6 @@ -use super::ir::{self, Abs, Axes, Point, Ratio, Scalar, SvgItem}; +use crate::hash::{item_hash128, Fingerprint}; + +use super::ir::{self, Abs, Axes, Point, Ratio, Scalar, Size, SvgItem, Transform}; /// A build pattern for applying transforms to the group of items. /// See [`ir::Transform`]. @@ -30,13 +32,24 @@ pub trait TransformContext: Sized { /// A RAII trait for rendering SVG items into underlying context. pub trait GroupContext: Sized { /// attach shape of the text to the node using css rules. - fn with_text_shape(&mut self, _ctx: &mut C, _shape: &ir::TextShape) {} + fn with_text_shape( + &mut self, + _ctx: &mut C, + _shape: &ir::TextShape, + _fill_key: &Fingerprint, + _state: RenderState, + ) { + } + + fn begin_text(&mut self, _text: &ir::TextItem) {} + + fn end_text(&mut self, _state: RenderState, _width: Scalar, _text: &ir::TextItem) {} /// Render an item at point into underlying context. - fn render_item_at(&mut self, ctx: &mut C, pos: Point, item: &SvgItem); + fn render_item_at(&mut self, state: RenderState, ctx: &mut C, pos: Point, item: &SvgItem); /// Render an item into underlying context. - fn render_item(&mut self, ctx: &mut C, item: &SvgItem) { - self.render_item_at(ctx, Point::default(), item); + fn render_item(&mut self, state: RenderState, ctx: &mut C, item: &SvgItem) { + self.render_item_at(state, ctx, Point::default(), item); } /// Render a semantic text into underlying context. @@ -46,7 +59,7 @@ pub trait GroupContext: Sized { fn render_glyph(&mut self, _ctx: &mut C, _pos: Scalar, _item: &ir::GlyphItem) {} /// Render a geometrical shape into underlying context. - fn render_path(&mut self, _ctx: &mut C, _path: &ir::PathItem) {} + fn render_path(&mut self, _ctx: &mut C, _path: &ir::PathItem, _abs_ref: &Fingerprint) {} /// Render a semantic link into underlying context. fn render_link(&mut self, _ctx: &mut C, _link: &ir::LinkItem) {} @@ -57,6 +70,75 @@ pub trait GroupContext: Sized { fn attach_debug_info(&mut self, _ctx: &mut C, _span_id: u64) {} } +/// Contextual information for rendering. +#[derive(Clone, Copy)] +pub struct RenderState { + /// The transform of the current item. + pub transform: Transform, + /// The size of the first hard frame in the hierarchy. + pub size: Size, +} + +impl Default for RenderState { + fn default() -> Self { + Self { + transform: Transform::identity(), + size: Size::default(), + } + } +} + +impl RenderState { + pub fn new_size(size: Size) -> Self { + Self { + transform: Transform::identity(), + size, + } + } + + /// Pre translate the current item's transform. + pub fn pre_translate(self, pos: Point) -> Self { + self.pre_concat(Transform::from_translate(pos.x, pos.y)) + } + + /// Pre concat the current item's transform. + pub fn pre_concat(self, transform: Transform) -> Self { + Self { + transform: self.transform.pre_concat(transform), + ..self + } + } + + /// Sets the size of the first hard frame in the hierarchy. + pub fn with_size(self, size: Size) -> Self { + Self { size, ..self } + } + + /// Sets the current item's transform. + pub fn with_transform(self, transform: Transform) -> Self { + Self { transform, ..self } + } + + fn pre_apply(self, transform: &ir::TransformItem) -> RenderState { + match transform { + ir::TransformItem::Matrix(transform) => self.pre_concat(**transform), + ir::TransformItem::Translate(transform) => { + self.pre_concat(Transform::from_translate(transform.x, transform.y)) + } + ir::TransformItem::Scale(transform) => { + self.pre_concat(Transform::from_scale(transform.0, transform.1)) + } + ir::TransformItem::Rotate(_transform) => { + todo!() + } + ir::TransformItem::Skew(transform) => { + self.pre_concat(Transform::from_skew(transform.0, transform.1)) + } + ir::TransformItem::Clip(_transform) => self, + } + } +} + /// A trait for rendering SVG items into underlying context. pub trait RenderVm: Sized { type Resultant; @@ -71,19 +153,19 @@ pub trait RenderVm: Sized { } /// Start a new `` like object for text. - fn start_text(&mut self, _text: &ir::TextItem) -> Self::Group { + fn start_text(&mut self, _state: RenderState, _text: &ir::TextItem) -> Self::Group { self.start_group() } /// Render an item into underlying context. - fn render_item(&mut self, item: &SvgItem) -> Self::Resultant { + fn render_item(&mut self, state: RenderState, item: &SvgItem) -> Self::Resultant { match &item { - ir::SvgItem::Group(group) => self.render_group(group), - ir::SvgItem::Transformed(transformed) => self.render_transformed(transformed), - ir::SvgItem::Text(text) => self.render_text(text), + ir::SvgItem::Group(group, sz) => self.render_group(state, group, sz), + ir::SvgItem::Transformed(transformed) => self.render_transformed(state, transformed), + ir::SvgItem::Text(text) => self.render_text(state, text), ir::SvgItem::Path((path, ..)) => { let mut g = self.start_group(); - g.render_path(self, path); + g.render_path(self, path, &Fingerprint::from_u128(item_hash128(path))); g.into() } ir::SvgItem::Link(link) => { @@ -96,31 +178,47 @@ pub trait RenderVm: Sized { g.render_image(self, image); g.into() } + ir::SvgItem::Gradient(..) => { + panic!("RenderVm.RenderFrame.UnknownItem {:?}", item) + } } } /// Render a frame group into underlying context. - fn render_group(&mut self, group: &ir::GroupItem) -> Self::Resultant { + fn render_group( + &mut self, + mut state: RenderState, + group: &ir::GroupItem, + sz: &Option, + ) -> Self::Resultant { let mut group_ctx = self.start_frame(group); + if let Some(sz) = sz { + state = state.with_transform(Transform::identity()).with_size(*sz); + } + for (pos, item_ref) in group.0.iter() { - group_ctx.render_item_at(self, *pos, item_ref); + group_ctx.render_item_at(state.pre_translate(*pos), self, *pos, item_ref); } group_ctx.into() } /// Render a transformed frame into underlying context. - fn render_transformed(&mut self, transformed: &ir::TransformedItem) -> Self::Resultant { + fn render_transformed( + &mut self, + state: RenderState, + transformed: &ir::TransformedItem, + ) -> Self::Resultant { let mut ts = self.start_group().transform(self, &transformed.0); - ts.render_item(self, &transformed.1); + ts.render_item(state.pre_apply(&transformed.0), self, &transformed.1); ts.into() } /// Render a text into the underlying context. // todo: combine with flat item one - fn render_text(&mut self, text: &ir::TextItem) -> Self::Resultant { - let group_ctx = self.start_text(text); + fn render_text(&mut self, state: RenderState, text: &ir::TextItem) -> Self::Resultant { + let group_ctx = self.start_text(state, text); // upem is the unit per em defined in the font. // ppem is calcuated by the font size. @@ -131,6 +229,7 @@ pub trait RenderVm: Sized { let mut group_ctx = group_ctx.transform_scale(self, ppem, -ppem); + group_ctx.begin_text(text); let mut x = 0f32; for (offset, advance, glyph) in text.content.glyphs.iter() { let offset = x + offset.0; @@ -140,6 +239,7 @@ pub trait RenderVm: Sized { x += advance.0; } + group_ctx.end_text(state, Scalar(x), text); group_ctx.render_semantic_text(self, text, Scalar(x)); group_ctx.into() diff --git a/exporter/canvas/src/annotation.rs b/exporter/canvas/src/annotation.rs index ba9ea314..31adbd5f 100644 --- a/exporter/canvas/src/annotation.rs +++ b/exporter/canvas/src/annotation.rs @@ -40,7 +40,7 @@ impl<'m, 't> AnnotationListTask<'m, 't> { }), &t.1, ), - SvgItem::Group(group) => self.process_group(ts, group), + SvgItem::Group(group, _) => self.process_group(ts, group), SvgItem::Link(link) => self.process_link(ts, link), _ => {} } @@ -64,7 +64,7 @@ impl<'m, 't> AnnotationListTask<'m, 't> { }), &t.1, ), - FlatSvgItem::Group(group) => self.process_flat_group(ts, group), + FlatSvgItem::Group(group, _) => self.process_flat_group(ts, group), FlatSvgItem::Link(link) => self.process_link(ts, link), _ => {} } diff --git a/exporter/canvas/src/content.rs b/exporter/canvas/src/content.rs index ceede74a..3213fe40 100644 --- a/exporter/canvas/src/content.rs +++ b/exporter/canvas/src/content.rs @@ -11,7 +11,7 @@ use typst_ts_core::{ flat_ir::{self, FlatSvgItem, FlatTextItem, GroupRef, Module}, flat_vm::{FlatGroupContext, FlatRenderVm}, ir::{self, Abs, Axes, FontIndice, FontRef, Ratio, Scalar, SvgItem}, - vm::{GroupContext, RenderVm, TransformContext}, + vm::{GroupContext, RenderState, RenderVm, TransformContext}, }, TextContent, }; @@ -69,9 +69,15 @@ trait TranslateCtx { /// See [`GroupContext`]. impl> GroupContext for TextContentBuilder { - fn render_item_at(&mut self, ctx: &mut C, pos: ir::Point, item: &ir::SvgItem) { + fn render_item_at( + &mut self, + state: RenderState, + ctx: &mut C, + pos: ir::Point, + item: &ir::SvgItem, + ) { ctx.translate(pos.x, pos.y); - ctx.render_item(item); + ctx.render_item(state, item); ctx.translate(-pos.x, -pos.y); } } @@ -122,7 +128,7 @@ impl<'m, 't> TextContentTask<'m, 't> { }), &t.1, ), - SvgItem::Group(group) => self.process_group(ts, group), + SvgItem::Group(group, _) => self.process_group(ts, group), SvgItem::Text(text) => self.process_text(ts, text), _ => {} } @@ -142,7 +148,7 @@ impl<'m, 't> TextContentTask<'m, 't> { }), &t.1, ), - SvgItem::Group(group) => { + SvgItem::Group(group, _) => { self.process_group(ts, group); } SvgItem::Text(text) => { @@ -186,7 +192,7 @@ impl<'m, 't> TextContentTask<'m, 't> { }), &t.1, ), - FlatSvgItem::Group(group) => self.process_flat_group(ts, group), + FlatSvgItem::Group(group, _) => self.process_flat_group(ts, group), FlatSvgItem::Text(text) => self.process_flat_text(ts, text), _ => {} } @@ -207,7 +213,7 @@ impl<'m, 't> TextContentTask<'m, 't> { }), &t.1, ), - FlatSvgItem::Group(group) => { + FlatSvgItem::Group(group, _) => { self.process_flat_group(ts, group); } FlatSvgItem::Text(text) => { diff --git a/exporter/canvas/src/lib.rs b/exporter/canvas/src/lib.rs index e582764c..02895c07 100644 --- a/exporter/canvas/src/lib.rs +++ b/exporter/canvas/src/lib.rs @@ -18,7 +18,7 @@ use typst_ts_core::{ self, Abs, Axes, BuildGlyph, FontIndice, FontRef, GlyphItem, GlyphPackBuilder, GlyphRef, Image, ImageItem, ImmutStr, PathStyle, Ratio, Rect, Scalar, Size, SvgItem, }, - vm::{GroupContext, RenderVm, TransformContext}, + vm::{GroupContext, RenderState, RenderVm, TransformContext}, }, }; @@ -177,11 +177,20 @@ impl CanvasElem for CanvasPathElem { } if fill { + // todo: canvas gradient + if fill_color.starts_with("url") { + fill_color = "black".into() + } canvas.set_fill_style(&fill_color.as_ref().into()); canvas.fill_with_path_2d(&Path2d::new_with_path_string(&self.path_data.d).unwrap()); } if stroke && stroke_width.abs() > 1e-5 { + // todo: canvas gradient + if stroke_color.starts_with("url") { + stroke_color = "black".into() + } + canvas.set_stroke_style(&stroke_color.as_ref().into()); canvas.stroke_with_path(&Path2d::new_with_path_string(&self.path_data.d).unwrap()); } @@ -458,8 +467,14 @@ impl TransformContext for CanvasStack { impl<'m, C: BuildGlyph + RenderVm + GlyphIndice<'m>> GroupContext for CanvasStack { - fn render_item_at(&mut self, ctx: &mut C, pos: ir::Point, item: &ir::SvgItem) { - self.inner.push((pos, ctx.render_item(item))); + fn render_item_at( + &mut self, + state: RenderState, + ctx: &mut C, + pos: ir::Point, + item: &ir::SvgItem, + ) { + self.inner.push((pos, ctx.render_item(state, item))); } fn render_glyph(&mut self, ctx: &mut C, pos: Scalar, glyph: &ir::GlyphItem) { @@ -469,7 +484,7 @@ impl<'m, C: BuildGlyph + RenderVm + GlyphIndice<'m>> Gro } } - fn render_path(&mut self, _ctx: &mut C, path: &ir::PathItem) { + fn render_path(&mut self, _ctx: &mut C, path: &ir::PathItem, _abs_ref: &Fingerprint) { self.inner.push(( ir::Point::default(), Arc::new(Box::new(CanvasPathElem { @@ -560,6 +575,7 @@ impl<'m, 't, Feat: ExportFeature> FlatRenderVm<'m> for CanvasRenderTask<'m, 't, fn start_flat_text( &mut self, + _state: RenderState, value: &Fingerprint, text: &flat_ir::FlatTextItem, ) -> Self::Group { @@ -726,7 +742,7 @@ impl IncrCanvasDocClient { // prepare an empty page for the pages that are not rendered // todo: better solution? - let empty_page = self.mb.build(SvgItem::Group(Default::default())); + let empty_page = self.mb.build(SvgItem::Group(Default::default(), None)); kern.doc .module .items diff --git a/exporter/svg/Cargo.toml b/exporter/svg/Cargo.toml index ed2d8619..bcd1e9a2 100644 --- a/exporter/svg/Cargo.toml +++ b/exporter/svg/Cargo.toml @@ -17,7 +17,7 @@ base64.workspace = true typst-ts-core.workspace = true log.workspace = true - +siphasher.workspace = true [features] rkyv = ["typst-ts-core/rkyv"] diff --git a/exporter/svg/src/backend/mod.rs b/exporter/svg/src/backend/mod.rs index adecf9b9..f9e9a9c6 100644 --- a/exporter/svg/src/backend/mod.rs +++ b/exporter/svg/src/backend/mod.rs @@ -10,11 +10,12 @@ use typst_ts_core::{ flat_vm::{FlatGroupContext, FlatIncrGroupContext, FlatIncrRenderVm, FlatRenderVm}, ir::{ self, Abs, Axes, BuildGlyph, FontIndice, GlyphHashStablizer, GlyphRef, ImmutStr, - PathStyle, Ratio, Scalar, Size, + PathStyle, Ratio, Scalar, Size, Transform, }, - vm::{GroupContext, RenderVm, TransformContext}, + vm::{GroupContext, RenderState, RenderVm, TransformContext}, GlyphLowerBuilder, }, + TypstAbs, }; mod escape; @@ -30,6 +31,10 @@ pub trait BuildFillStyleClass { fn build_fill_style_class(&mut self, fill: ImmutStr) -> String; } +pub trait NotifyPaint { + fn notify_paint(&mut self, url_ref: ImmutStr) -> (u8, Fingerprint); +} + pub trait DynExportFeature { fn should_render_text_element(&self) -> bool; @@ -180,7 +185,7 @@ impl SvgGlyphBuilder { outline_glyph: &ir::OutlineGlyphItem, ) -> Option { let symbol_def = format!( - r#""#, + r#""#, glyph_id, outline_glyph.d ); Some(symbol_def) @@ -192,6 +197,7 @@ impl SvgGlyphBuilder { pub struct SvgTextBuilder { pub attributes: Vec<(&'static str, String)>, pub content: Vec, + pub text_fill: Option, } impl From for Arc { @@ -327,21 +333,117 @@ impl< + BuildGlyph + GlyphHashStablizer + BuildFillStyleClass + + NotifyPaint + DynExportFeature, > GroupContext for SvgTextBuilder { - fn with_text_shape(&mut self, ctx: &mut C, shape: &ir::TextShape) { + fn with_text_shape( + &mut self, + ctx: &mut C, + shape: &ir::TextShape, + fill_key: &Fingerprint, + state: RenderState, + ) { + let color = &shape.fill; // shorten black fill - let fill_id = ctx.build_fill_style_class(shape.fill.clone()); + let fill_id = if shape.fill.starts_with("url") { + // todo + let (kind, f) = ctx.notify_paint(color.clone()); + // abs_ref + let paint_id = fill_key.as_svg_id("tf"); + self.text_fill = Some(*fill_key); + + let tag = match kind { + b'l' => "linearGradient", + b'r' => "radialGradient", + b'p' => "pattern", + _ => unreachable!(), + }; + + let transform = match kind { + b'p' => "patternTransform", + _ => "gradientTransform", + }; + + // todo: bbox + let bbox = state.size; + let t = Transform::from_scale(bbox.x, bbox.y) + .post_concat(state.transform.invert().unwrap()) + .post_concat(Transform::from_scale(Scalar(204.8), Scalar(-204.8))) + .to_css(); + + self.content.push(SvgText::Plain(format!( + r##"<{} id="{}" {}="{}" href="#{}" xlink:href="#{}">"##, + tag, + paint_id, + transform, + t, + f.as_svg_id("g"), + f.as_svg_id("g"), + tag + ))); + + "".to_owned() + } else { + ctx.build_fill_style_class(color.clone()) + }; self.attributes .push(("class", format!("typst-text {}", fill_id))); } - fn render_item_at(&mut self, ctx: &mut C, pos: ir::Point, item: &ir::SvgItem) { + fn begin_text(&mut self, _text: &ir::TextItem) { + if let Some(fill) = &self.text_fill { + // clip path rect + let clip_id = fill.as_svg_id("tc"); + self.content.push(SvgText::Plain(format!( + r#""#, + clip_id + ))); + } + } + + fn end_text(&mut self, _state: RenderState, width: Scalar, text: &ir::TextItem) { + if let Some(fill) = &self.text_fill { + // upem is the unit per em defined in the font. + // ppem is calcuated by the font size. + // > ppem = text_size / upem + let upem = text.font.metrics().units_per_em as f32; + + // because the text is already scaled by the font size, + // we need to scale it back to the original size. + // todo: infinite multiplication + let descender = text + .font + .metrics() + .descender + .at(TypstAbs::raw(upem as f64)) + .to_pt() as f32; + let width = width.0 * upem / text.shape.size.0; + + self.content + .push(SvgText::Plain(r#""#.to_owned())); + + let fill_id = fill.as_svg_id("tf"); + let clip_id = fill.as_svg_id("tc"); + // clip path rect + self.content.push(SvgText::Plain(format!( + r##""##, + fill_id, width, upem, descender, clip_id + ))); + } + } + + fn render_item_at( + &mut self, + state: RenderState, + ctx: &mut C, + pos: ir::Point, + item: &ir::SvgItem, + ) { let translate_attr = format!("translate({:.3},{:.3})", pos.x.0, pos.y.0); - let sub_content = ctx.render_item(item); + let sub_content = ctx.render_item(state, item); self.content.push(SvgText::Content(Arc::new(SvgTextNode { attributes: vec![("transform", translate_attr)], @@ -375,8 +477,49 @@ impl< ))) } - fn render_path(&mut self, _ctx: &mut C, path: &ir::PathItem) { - self.content.push(render_path(path)) + fn render_path(&mut self, ctx: &mut C, path: &ir::PathItem, abs_ref: &Fingerprint) { + for s in &path.styles { + match s { + PathStyle::Fill(color) | PathStyle::Stroke(color) => { + let is_fill = matches!(s, PathStyle::Fill(..)); + if color.starts_with("url") { + // todo + let (kind, f) = ctx.notify_paint(color.clone()); + // abs_ref + let paint_id = abs_ref.as_svg_id(if is_fill { "pf" } else { "ps" }); + + let tag = match kind { + b'l' => "linearGradient", + b'r' => "radialGradient", + b'p' => "pattern", + _ => unreachable!(), + }; + + let transform = match kind { + b'p' => "patternTransform", + _ => "gradientTransform", + }; + + // todo: bbox + let bbox = path.size.unwrap(); + let t = Transform::from_scale(bbox.x, bbox.y).to_css(); + + self.content.push(SvgText::Plain(format!( + r##"<{} id="{}" {}="{}" href="#{}" xlink:href="#{}">"##, + tag, + paint_id, + transform, + t, + f.as_svg_id("g"), + f.as_svg_id("g"), + tag + ))); + } + } + _ => {} + } + } + self.content.push(render_path(path, abs_ref)) } fn render_image(&mut self, _ctx: &mut C, image_item: &ir::ImageItem) { @@ -416,6 +559,7 @@ impl< + FontIndice<'m> + GlyphHashStablizer + BuildFillStyleClass + + NotifyPaint + DynExportFeature, > FlatGroupContext for SvgTextBuilder { @@ -464,8 +608,14 @@ impl< self } - fn with_text(mut self, ctx: &mut C, text: &flat_ir::FlatTextItem) -> Self { - self.with_text_shape(ctx, &text.shape); + fn with_text( + mut self, + ctx: &mut C, + text: &flat_ir::FlatTextItem, + fill_key: &Fingerprint, + state: RenderState, + ) -> Self { + self.with_text_shape(ctx, &text.shape, fill_key, state); self } @@ -510,17 +660,30 @@ impl<'m, C: FlatIncrRenderVm<'m, Resultant = Arc, Group = SvgTextBu /// Render a [`ir::PathItem`] into svg text. #[comemo::memoize] -fn render_path(path: &ir::PathItem) -> SvgText { +fn render_path(path: &ir::PathItem, abs_ref: &Fingerprint) -> SvgText { let mut p = vec![r#" { - fill_color = color; + fill_color = if color.starts_with("url") { + ft = format!(r#"url(#{})"#, abs_ref.as_svg_id("pf")); + &ft + } else { + color + }; } PathStyle::Stroke(color) => { - p.push(format!(r#"stroke="{}" "#, color)); + // compress the stroke color + p.push(if color.starts_with("url") { + let ps = abs_ref.as_svg_id("ps"); + format!(r##"stroke="url(#{})" "##, &ps) + } else { + format!(r##"stroke="{}" "##, color) + }); } PathStyle::StrokeWidth(width) => { p.push(format!(r#"stroke-width="{}" "#, width.0)); diff --git a/exporter/svg/src/frontend/context.rs b/exporter/svg/src/frontend/context.rs index 69266e79..6487879d 100644 --- a/exporter/svg/src/frontend/context.rs +++ b/exporter/svg/src/frontend/context.rs @@ -1,7 +1,8 @@ -use std::{collections::HashMap, sync::Arc}; +use siphasher::sip128::Hasher128; +use std::{collections::HashMap, hash::Hash, sync::Arc}; use typst_ts_core::{ - hash::{item_hash128, Fingerprint, FingerprintBuilder}, + hash::{item_hash128, Fingerprint, FingerprintBuilder, FingerprintSipHasherBase}, vector::{ flat_ir::{FlatSvgItem, FlatTextItem, GroupRef, Module}, flat_vm::{FlatIncrRenderVm, FlatRenderVm}, @@ -10,12 +11,15 @@ use typst_ts_core::{ ImmutStr, PathItem, StyleNs, }, vm::GroupContext, - vm::RenderVm, + vm::{RenderState, RenderVm}, }, }; use crate::{ - backend::{BuildClipPath, BuildFillStyleClass, DynExportFeature, SvgTextBuilder, SvgTextNode}, + backend::{ + BuildClipPath, BuildFillStyleClass, DynExportFeature, NotifyPaint, SvgTextBuilder, + SvgTextNode, + }, ExportFeature, GlyphProvider, }; @@ -24,6 +28,8 @@ use crate::{ pub(crate) type StyleDefMap = HashMap<(StyleNs, ImmutStr), String>; /// Maps the clip path's data to the clip path id. pub(crate) type ClipPathMap = HashMap; +/// Maps the clip path's data to the clip path id. +pub(crate) type GradientMap = HashMap; /// The task context for rendering svg items /// The 'm lifetime is the lifetime of the module which stores the frame data. @@ -47,6 +53,8 @@ pub struct RenderContext<'m, 't, Feat: ExportFeature> { pub(crate) style_defs: &'t mut StyleDefMap, /// Stores the clip paths used in the document. pub(crate) clip_paths: &'t mut ClipPathMap, + /// Stores the clip paths used in the document. + pub(crate) gradients: &'t mut GradientMap, /// See [`ExportFeature`]. pub should_render_text_element: bool, @@ -131,6 +139,39 @@ impl<'m, 't, Feat: ExportFeature> BuildClipPath for RenderContext<'m, 't, Feat> } } +impl<'m, 't, Feat: ExportFeature> NotifyPaint for RenderContext<'m, 't, Feat> { + fn notify_paint(&mut self, url_ref: ImmutStr) -> (u8, Fingerprint) { + if let Some(f) = self.gradients.get(&url_ref) { + return *f; + } + + // url(#ghash) + if !url_ref.starts_with("url(#g") || !url_ref.ends_with(')') { + panic!("Invalid url reference: {}", url_ref); + } + + let id = url_ref.trim_start_matches("url(#g").trim_end_matches(')'); + let id = Fingerprint::try_from_str(id).unwrap(); + + let kind = match self.get_item(&id) { + Some(FlatSvgItem::Gradient(g)) => &g.kind, + _ => { + // #[cfg(debug_assertions)] + panic!("Invalid gradient reference: {}", id.as_svg_id("g")); + } + }; + + let kind = match kind { + ir::GradientKind::Linear(..) => b'l', + ir::GradientKind::Radial(..) => b'r', + ir::GradientKind::Conic(..) => b'p', + }; + + self.gradients.insert(url_ref, (kind, id)); + (kind, id) + } +} + /// Example of how to implement a RenderVm. impl<'m, 't, Feat: ExportFeature> RenderVm for RenderContext<'m, 't, Feat> { // type Resultant = String; @@ -140,6 +181,7 @@ impl<'m, 't, Feat: ExportFeature> RenderVm for RenderContext<'m, 't, Feat> { fn start_group(&mut self) -> Self::Group { Self::Group { attributes: vec![], + text_fill: None, content: Vec::with_capacity(1), } } @@ -150,9 +192,16 @@ impl<'m, 't, Feat: ExportFeature> RenderVm for RenderContext<'m, 't, Feat> { g } - fn start_text(&mut self, text: &ir::TextItem) -> Self::Group { + fn start_text(&mut self, state: RenderState, text: &ir::TextItem) -> Self::Group { let mut g = self.start_group(); - g.with_text_shape(self, &text.shape); + + let mut k = FingerprintSipHasherBase::new(); + text.font.hash(&mut k); + text.content.glyphs.hash(&mut k); + text.shape.hash(&mut k); + let k = k.finish128().as_u128(); + + g.with_text_shape(self, &text.shape, &Fingerprint::from_u128(k), state); g.attach_debug_info(self, text.content.span_id); g @@ -171,6 +220,7 @@ impl<'m, 't, Feat: ExportFeature> FlatRenderVm<'m> for RenderContext<'m, 't, Fea fn start_flat_group(&mut self, v: &Fingerprint) -> Self::Group { Self::Group { attributes: vec![("data-tid", v.as_svg_id("g"))], + text_fill: None, content: Vec::with_capacity(1), } } @@ -181,9 +231,14 @@ impl<'m, 't, Feat: ExportFeature> FlatRenderVm<'m> for RenderContext<'m, 't, Fea g } - fn start_flat_text(&mut self, value: &Fingerprint, text: &FlatTextItem) -> Self::Group { + fn start_flat_text( + &mut self, + state: RenderState, + value: &Fingerprint, + text: &FlatTextItem, + ) -> Self::Group { let mut g = self.start_flat_group(value); - g.with_text_shape(self, &text.shape); + g.with_text_shape(self, &text.shape, value, state); g } } diff --git a/exporter/svg/src/frontend/dynamic_layout.rs b/exporter/svg/src/frontend/dynamic_layout.rs index 15f37963..636a8e26 100644 --- a/exporter/svg/src/frontend/dynamic_layout.rs +++ b/exporter/svg/src/frontend/dynamic_layout.rs @@ -35,6 +35,10 @@ impl DynamicLayoutSvgExporter { }) .collect::>(); + for (_, ext) in t.extra_items.into_iter() { + self.builder.build(ext); + } + self.layouts .push((layout_width.into(), LayoutRegionNode::new_pages(pages))); // log::trace!("svg dynamic layout render time: {:?}", diff --git a/exporter/svg/src/frontend/flat.rs b/exporter/svg/src/frontend/flat.rs index be3975c3..1132cc19 100644 --- a/exporter/svg/src/frontend/flat.rs +++ b/exporter/svg/src/frontend/flat.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use typst::{diag::SourceResult, doc::Document}; use typst_ts_core::vector::{ flat_ir::{ - flatten_glyphs, FlatModule, ItemPack, LayoutRegion, LayoutRegionNode, LayoutRegionRepr, - Module, ModuleBuilder, ModuleMetadata, Page, SvgDocument, + flatten_glyphs, FlatModule, FlatSvgItem, ItemPack, LayoutRegion, LayoutRegionNode, + LayoutRegionRepr, Module, ModuleBuilder, ModuleMetadata, Page, SvgDocument, }, flat_vm::FlatRenderVm, LowerBuilder, @@ -82,7 +82,22 @@ impl SvgExporter { true, ); - generate_text(Self::render_svg_template(t, header, svg_body, glyphs)) + let gradients = std::mem::take(&mut t.gradients); + let gradients = gradients + .values() + .filter_map(|(_, id)| match module.get_item(id) { + Some(FlatSvgItem::Gradient(g)) => Some((id, g)), + _ => { + // #[cfg(debug_assertions)] + panic!("Invalid gradient reference: {}", id.as_svg_id("g")); + #[allow(unreachable_code)] + None + } + }); + + generate_text(Self::render_svg_template( + t, header, svg_body, glyphs, gradients, + )) } } @@ -101,6 +116,10 @@ pub fn export_module(output: &Document) -> SourceResult> { }); } + for (_, ext) in t.extra_items.into_iter() { + builder.build(ext); + } + let repr: Module = builder.finalize(); let glyphs = flatten_glyphs(repr.glyphs).into(); diff --git a/exporter/svg/src/frontend/incremental.rs b/exporter/svg/src/frontend/incremental.rs index 5fa9afa6..86235e7b 100644 --- a/exporter/svg/src/frontend/incremental.rs +++ b/exporter/svg/src/frontend/incremental.rs @@ -151,7 +151,7 @@ impl IncrSvgDocClient { // prepare an empty page for the pages that are not rendered // todo: better solution? - let empty_page = self.mb.build(SvgItem::Group(Default::default())); + let empty_page = self.mb.build(SvgItem::Group(Default::default(), None)); kern.module_mut() .items .extend(self.mb.items.iter().map(|(f, (_, v))| (*f, v.clone()))); diff --git a/exporter/svg/src/frontend/mod.rs b/exporter/svg/src/frontend/mod.rs index 2585dafd..6aeca93a 100644 --- a/exporter/svg/src/frontend/mod.rs +++ b/exporter/svg/src/frontend/mod.rs @@ -1,13 +1,19 @@ -use std::sync::Arc; +use std::{collections::HashSet, f32::consts::TAU, fmt::Write, sync::Arc}; -use typst::doc::Document; +use typst::{ + doc::Document, + geom::{Angle, Color, ColorSpace, Hsl, Hsv, Quadrant, WeightedColor}, +}; use typst_ts_core::{ font::GlyphProvider, - hash::FingerprintBuilder, + hash::{item_hash128, Fingerprint, FingerprintBuilder}, vector::{ - flat_ir::{self, Module}, - ir::{Axes, DefId, GlyphItem, GlyphPackBuilder, Size, SvgItem}, - vm::RenderVm, + flat_ir::{self, ModuleBuilder}, + ir::{ + Axes, DefId, GlyphItem, GlyphPackBuilder, GradientItem, GradientKind, GradientStyle, + Scalar, Size, SvgItem, + }, + vm::{RenderState, RenderVm}, LowerBuilder, }, }; @@ -29,6 +35,8 @@ use crate::{ }; pub use incremental::{IncrSvgDocClient, IncrSvgDocServer, IncrementalRenderContext}; +use self::context::GradientMap; + pub struct SvgExporter { pub _feat_phantom: std::marker::PhantomData, } @@ -104,12 +112,223 @@ impl SvgExporter { } } + /// Render the gradients for SVG + /// .. + /// ^^^^^^^^^^^ + /// See [`GradientMap`]. + fn gradients<'a>( + gradients: impl Iterator, + svg: &mut Vec, + ) { + let mut sub_gradients = HashSet::<(Fingerprint, SVGSubGradient)>::default(); + + // todo: aspect ratio + for (id, gradient) in gradients { + match &gradient.kind { + GradientKind::Linear(angle) => { + // todo: use native angle + let angle = typst::geom::Angle::rad(angle.0 as f64); + + // todo: correct aspect ratio + // let angle = Gradient::correct_aspect_ratio(linear.angle, *ratio); + let (sin, cos) = (angle.sin(), angle.cos()); + let length = sin.abs() + cos.abs(); + let (x1, y1, x2, y2) = match angle.quadrant() { + Quadrant::First => (0.0, 0.0, cos * length, sin * length), + Quadrant::Second => (1.0, 0.0, cos * length + 1.0, sin * length), + Quadrant::Third => (1.0, 1.0, cos * length + 1.0, sin * length + 1.0), + Quadrant::Fourth => (0.0, 1.0, cos * length, sin * length + 1.0), + }; + + svg.push(SvgText::Plain( + format!( + r##""##, + id.as_svg_id("g"), + x1, y1, x2, y2, + ) + )) + } + GradientKind::Radial(radius) => { + let mut center = &Axes::new(Scalar(0.5), Scalar(0.5)); + let mut focal_center = &Axes::new(Scalar(0.5), Scalar(0.5)); + let mut focal_radius = &Scalar(0.); + for s in &gradient.styles { + match s { + GradientStyle::Center(c) => { + center = c; + } + GradientStyle::FocalCenter(c) => { + focal_center = c; + } + GradientStyle::FocalRadius(r) => { + focal_radius = r; + } + } + } + + svg.push(SvgText::Plain( + format!( + r##""##, + id.as_svg_id("g"), + center.x.0, center.y.0, radius.0, focal_center.x.0, focal_center.y.0, focal_radius.0, + ) + )); + } + GradientKind::Conic(angle) => { + svg.push(SvgText::Plain( + format!( + r##""##, + id.as_svg_id("g"), + ) + )); + + // The rotation angle, negated to match rotation in PNG. + // todo: use native angle + // let angle = Gradient::correct_aspect_ratio(angle, *ratio); + // let angle = typst::geom::Angle::rad(angle.0 as f64); + let angle: f32 = -(angle.0).rem_euclid(TAU); + let mut center = &Axes::new(Scalar(0.5), Scalar(0.5)); + for s in &gradient.styles { + if let GradientStyle::Center(c) = s { + center = c; + } + } + + // We build an arg segment for each segment of a circle. + let dtheta = TAU / CONIC_SEGMENT as f32; + for i in 0..CONIC_SEGMENT { + let theta1 = dtheta * i as f32; + let theta2 = dtheta * (i + 1) as f32; + + // Create the path for the segment. + let mut builder = SvgPath2DBuilder::default(); + builder.move_to( + correct_pattern_pos(center.x.0), + correct_pattern_pos(center.y.0), + ); + builder.line_to( + correct_pattern_pos(-2.0 * (theta1 + angle).cos() + center.x.0), + correct_pattern_pos(2.0 * (theta1 + angle).sin() + center.y.0), + ); + builder.arc( + (2.0, 2.0), + 0.0, + 0, + 1, + ( + correct_pattern_pos(-2.0 * (theta2 + angle).cos() + center.x.0), + correct_pattern_pos(2.0 * (theta2 + angle).sin() + center.y.0), + ), + ); + builder.close(); + + let t1 = (i as f32) / CONIC_SEGMENT as f32; + let t2 = (i + 1) as f32 / CONIC_SEGMENT as f32; + let subgradient = SVGSubGradient { + center: *center, + t0: Angle::rad((theta1 + angle) as f64), + t1: Angle::rad((theta2 + angle) as f64), + c0: sample_color_stops(gradient, t1), + c1: sample_color_stops(gradient, t2), + }; + let f = Fingerprint::from_u128(item_hash128(&subgradient)); + sub_gradients.insert((f, subgradient)); + + svg.push(SvgText::Plain(format!( + r##""##, + builder.0, + f.as_svg_id("g"), + f.as_svg_id("g"), + ))); + } + + svg.push(SvgText::Plain("".to_owned())); + + // We skip the default stop generation code. + continue; + } + } + + for window in gradient.stops.windows(2) { + let (start_c, start_t) = &window[0]; + let (end_c, end_t) = &window[1]; + + svg.push(SvgText::Plain(format!( + r##""##, + RatioRepr(start_t.0), + start_c.clone().to_hex(), + ))); + + // Generate (256 / len) stops between the two stops. + // This is a workaround for a bug in many readers: + // They tend to just ignore the color space of the gradient. + // The goal is to have smooth gradients but not to balloon the file size + // too much if there are already a lot of stops as in most presets. + let len = if gradient.anti_alias { + (256 / gradient.stops.len() as u32).max(2) + } else { + 2 + }; + + for i in 1..(len - 1) { + let t0 = i as f32 / (len - 1) as f32; + let t = start_t.0 + (end_t.0 - start_t.0) * t0; + let c = sample_color_stops(gradient, t); + + svg.push(SvgText::Plain(format!( + r##""##, + RatioRepr(t), + c.to_hex(), + ))); + } + + svg.push(SvgText::Plain(format!( + r##""##, + RatioRepr(end_t.0), + end_c.clone().to_hex(), + ))); + } + + svg.push(SvgText::Plain(match gradient.kind { + GradientKind::Linear(..) => "".to_owned(), + GradientKind::Radial(..) => "".to_owned(), + GradientKind::Conic(..) => "".to_owned(), + })); + } + + for (id, gradient) in sub_gradients { + let x1 = 2.0 - gradient.t0.cos() as f32 + gradient.center.x.0; + let y1 = gradient.t0.sin() as f32 + gradient.center.y.0; + let x2 = 2.0 - gradient.t1.cos() as f32 + gradient.center.x.0; + let y2 = gradient.t1.sin() as f32 + gradient.center.y.0; + + svg.push(SvgText::Plain(format!( + r##""##, + id.as_svg_id("g"), + x1, y1, x2, y2, + ))); + + svg.push(SvgText::Plain(format!( + r##""##, + gradient.c0.to_hex(), + ))); + + svg.push(SvgText::Plain(format!( + r##""##, + gradient.c1.to_hex(), + ))); + + svg.push(SvgText::Plain("".to_owned())); + } + } + /// Template SVG. - fn render_svg_template( + fn render_svg_template<'a>( t: SvgTask, header: String, mut body: Vec, glyphs: impl IntoIterator, + gradients: impl Iterator, ) -> Vec { let mut svg = vec![ SvgText::Plain(header), @@ -128,6 +347,7 @@ impl SvgExporter { svg.push("".into()); svg.push(r#""#.into()); Self::clip_paths(t.clip_paths, &mut svg); + Self::gradients(gradients, &mut svg); svg.push("".into()); Self::style_defs(t.style_defs, &mut svg); @@ -163,17 +383,32 @@ impl SvgExporter { .iter() .map(|p| lower_builder.lower(p)) .collect::>(); + let mut module = ModuleBuilder::default(); + + for (_, ext) in lower_builder.extra_items.clone().into_iter() { + module.build(ext); + } + + let module = module.finalize(); // render SVG body let mut svg_body = vec![]; - t.render_pages_transient(output, pages, &mut svg_body); + t.render_pages_transient(module, output, pages, &mut svg_body); // render the glyphs collected from the pages let (_, glyphs) = std::mem::take(&mut t.glyph_defs).finalize(); let glyphs = t.render_glyphs(glyphs.iter().enumerate().map(|(x, (_, y))| (x, y)), false); + let gradients = lower_builder + .extra_items + .iter() + .filter_map(|(f, item)| match item { + SvgItem::Gradient(item) => Some((f, item)), + _ => None, + }); + // template SVG - Self::render_svg_template(t, header, svg_body, glyphs) + Self::render_svg_template(t, header, svg_body, glyphs, gradients) } /// Render SVG wrapped with HTML for [`Document`]. @@ -217,6 +452,8 @@ pub struct SvgTask { pub(crate) style_defs: StyleDefMap, /// Stores the clip paths used in the document. pub(crate) clip_paths: ClipPathMap, + /// Stores the gradients used in the document. + pub(crate) gradients: GradientMap, _feat_phantom: std::marker::PhantomData, } @@ -232,6 +469,7 @@ impl Default for SvgTask { glyph_defs: GlyphPackBuilder::default(), style_defs: StyleDefMap::default(), clip_paths: ClipPathMap::default(), + gradients: GradientMap::default(), _feat_phantom: std::marker::PhantomData, } @@ -272,6 +510,7 @@ impl SvgTask { glyph_defs: &mut self.glyph_defs, style_defs: &mut self.style_defs, clip_paths: &mut self.clip_paths, + gradients: &mut self.gradients, should_attach_debug_info: Feat::SHOULD_ATTACH_DEBUG_INFO, should_render_text_element: true, @@ -333,28 +572,20 @@ impl SvgTask { /// Render pages into the svg_body. pub fn render_pages_transient( &mut self, + module: flat_ir::Module, output: &Document, pages: Vec, svg_body: &mut Vec, ) { - #[cfg(feature = "flat-vector")] - let module = Module::default(); - let mut render_task = { - #[cfg(feature = "flat-vector")] - let render_task = self.get_render_context(&module); - - #[cfg(not(feature = "flat-vector"))] - let render_task = self.get_render_context(); - - render_task - }; + let mut render_task = self.get_render_context(&module); render_task.use_stable_glyph_id = false; // accumulate the height of pages let mut acc_height = 0u32; for (idx, page) in pages.iter().enumerate() { - let size = Self::page_size(output.pages[idx].size().into()); + let size_f32 = output.pages[idx].size().into(); + let size = Self::page_size(size_f32); let attributes = vec![ ("transform", format!("translate(0, {})", acc_height)), @@ -362,7 +593,7 @@ impl SvgTask { ("data-page-height", size.y.to_string()), ]; - let page_svg = render_task.render_item(page); + let page_svg = render_task.render_item(RenderState::new_size(size_f32), page); svg_body.push(SvgText::Content(Arc::new(SvgTextNode { attributes, @@ -372,3 +603,152 @@ impl SvgTask { } } } + +/// Maps a coordinate in a unit size square to a coordinate in the pattern. +fn correct_pattern_pos(x: f32) -> f32 { + (x + 0.5) / 2.0 +} + +#[derive(Default)] +struct SvgPath2DBuilder(pub String); + +/// See: https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths +impl SvgPath2DBuilder { + #[allow(dead_code)] + pub fn rect(&mut self, x: f32, y: f32, w: f32, h: f32) { + write!( + &mut self.0, + "M {} {} H {} V {} H {} Z", + x, + y, + x + w, + y + h, + x + ) + .unwrap(); + } +} + +impl SvgPath2DBuilder { + fn move_to(&mut self, x: f32, y: f32) { + write!(&mut self.0, "M {} {} ", x, y).unwrap(); + } + + fn line_to(&mut self, x: f32, y: f32) { + write!(&mut self.0, "L {} {} ", x, y).unwrap(); + } + + /// Creates an arc path. + fn arc( + &mut self, + radius: (f32, f32), + x_axis_rot: f32, + large_arc_flag: u32, + sweep_flag: u32, + pos: (f32, f32), + ) { + write!( + &mut self.0, + "A {rx} {ry} {x_axis_rot} {large_arc_flag} {sweep_flag} {x} {y} ", + rx = radius.0, + ry = radius.1, + x = pos.0, + y = pos.1, + ) + .unwrap(); + } + + fn close(&mut self) { + write!(&mut self.0, "Z ").unwrap(); + } +} + +/// The number of segments in a conic gradient. +/// This is a heuristic value that seems to work well. +/// Smaller values could be interesting for optimization. +const CONIC_SEGMENT: usize = 360; + +/// A subgradient for conic gradients. +#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy)] +struct SVGSubGradient { + /// The center point of the gradient. + center: Axes, + /// The start point of the subgradient. + t0: Angle, + /// The end point of the subgradient. + t1: Angle, + /// The color at the start point of the subgradient. + c0: Color, + /// The color at the end point of the subgradient. + c1: Color, +} + +/// Sample the stops at a given position. +// todo: use native approach +fn sample_color_stops(gradient: &GradientItem, t: f32) -> Color { + let t = t.clamp(0.0, 1.0); + let mut low = 0; + let mut high = gradient.stops.len(); + + let mixing_space = gradient.space.into(); + let stops = &gradient.stops; + + while low < high { + let mid = (low + high) / 2; + if stops[mid].1 .0 < t { + low = mid + 1; + } else { + high = mid; + } + } + + if low == 0 { + low = 1; + } + let (col_0, pos_0) = &stops[low - 1]; + let (col_1, pos_1) = &stops[low]; + let t = (t - pos_0.0) / (pos_1.0 - pos_0.0); + let col_0 = col_0.typst(); + let col_1 = col_1.typst(); + + let out = Color::mix_iter( + [ + WeightedColor::new(col_0, (1.0 - t) as f64), + WeightedColor::new(col_1, t as f64), + ], + mixing_space, + ) + .unwrap(); + + // Special case for handling multi-turn hue interpolation. + if mixing_space == ColorSpace::Hsl || mixing_space == ColorSpace::Hsv { + let hue_0 = col_0.to_space(mixing_space).to_vec4()[0]; + let hue_1 = col_1.to_space(mixing_space).to_vec4()[0]; + + // Check if we need to interpolate over the 360° boundary. + if (hue_0 - hue_1).abs() > 180.0 { + let hue_0 = if hue_0 < hue_1 { hue_0 + 360.0 } else { hue_0 }; + let hue_1 = if hue_1 < hue_0 { hue_1 + 360.0 } else { hue_1 }; + + let hue = hue_0 * (1.0 - t) + hue_1 * t; + + if mixing_space == ColorSpace::Hsl { + let [_, saturation, lightness, alpha] = out.to_hsl().to_vec4(); + return Color::Hsl(Hsl::new(hue, saturation, lightness, alpha)); + } else if mixing_space == ColorSpace::Hsv { + let [_, saturation, value, alpha] = out.to_hsv().to_vec4(); + return Color::Hsv(Hsv::new(hue, saturation, value, alpha)); + } + } + } + + out +} + +struct RatioRepr(f32); + +impl std::fmt::Display for RatioRepr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:.3}%", self.0 * 100.0) + } +} diff --git a/exporter/svg/src/frontend/typst.svg.css b/exporter/svg/src/frontend/typst.svg.css index 8f912f8b..ff41b3a0 100644 --- a/exporter/svg/src/frontend/typst.svg.css +++ b/exporter/svg/src/frontend/typst.svg.css @@ -30,10 +30,10 @@ svg { cursor: pointer; pointer-events: all; } -.outline_glyph path { +.outline_glyph path, path.outline_glyph { fill: var(--glyph_fill); } -.outline_glyph path { +.outline_glyph path, path.outline_glyph { transition: 0.2s fill; } .hover .typst-text { diff --git a/fuzzers/corpora/bugs/block-width-box_00.typ b/fuzzers/corpora/bugs/block-width-box_00.typ new file mode 100644 index 00000000..bc407621 --- /dev/null +++ b/fuzzers/corpora/bugs/block-width-box_00.typ @@ -0,0 +1,7 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#block(width: 100%, fill: red, box("a box")) + +#block(width: 100%, fill: red, [#box("a box") #box()]) diff --git a/fuzzers/corpora/bugs/equation-numbering-reference_00.typ b/fuzzers/corpora/bugs/equation-numbering-reference_00.typ new file mode 100644 index 00000000..a37de962 --- /dev/null +++ b/fuzzers/corpora/bugs/equation-numbering-reference_00.typ @@ -0,0 +1,13 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// #set page(height: 70pt) +// +// $ +// Delta = b^2 - 4 a c +// $ +// +// // Error: 14-24 cannot reference equation without numbering +// // Hint: 14-24 you can enable equation numbering with `#set math.equation(numbering: "1.")` +// Looks at the @quadratic formula. \ No newline at end of file diff --git a/fuzzers/corpora/bugs/grid-2_00.typ b/fuzzers/corpora/bugs/grid-2_00.typ index dceee975..acce7253 100644 --- a/fuzzers/corpora/bugs/grid-2_00.typ +++ b/fuzzers/corpora/bugs/grid-2_00.typ @@ -9,7 +9,7 @@ rect(width: 100%, fill: red), rect(width: 100%, fill: blue), rect(width: 100%, height: 80%, fill: green), - [hello \ darkness #parbreak my \ old \ friend \ I], + [hello \ darkness #parbreak() my \ old \ friend \ I], rect(width: 100%, height: 20%, fill: blue), polygon(fill: red, (0%, 0%), (100%, 0%), (100%, 20%)) ) diff --git a/fuzzers/corpora/bugs/layout-infinite-lengths_00.typ b/fuzzers/corpora/bugs/layout-infinite-lengths_00.typ new file mode 100644 index 00000000..cd0dae8b --- /dev/null +++ b/fuzzers/corpora/bugs/layout-infinite-lengths_00.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// #set page(width: auto, height: auto) +// +// // Error: cannot expand into infinite width +// #layout(size => grid(columns: (size.width, size.height))[a][b][c][d]) \ No newline at end of file diff --git a/fuzzers/corpora/bugs/layout-infinite-lengths_01.typ b/fuzzers/corpora/bugs/layout-infinite-lengths_01.typ new file mode 100644 index 00000000..778f9e9a --- /dev/null +++ b/fuzzers/corpora/bugs/layout-infinite-lengths_01.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// #set page(width: auto, height: auto) +// +// // Error: 17-66 cannot create grid with infinite height +// #layout(size => grid(rows: (size.width, size.height))[a][b][c][d]) \ No newline at end of file diff --git a/fuzzers/corpora/bugs/layout-infinite-lengths_02.typ b/fuzzers/corpora/bugs/layout-infinite-lengths_02.typ new file mode 100644 index 00000000..c57cc25e --- /dev/null +++ b/fuzzers/corpora/bugs/layout-infinite-lengths_02.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// #set page(width: auto, height: auto) +// +// // Error: 17-41 cannot create line with infinite length +// #layout(size => line(length: size.width)) \ No newline at end of file diff --git a/fuzzers/corpora/bugs/layout-infinite-lengths_03.typ b/fuzzers/corpora/bugs/layout-infinite-lengths_03.typ new file mode 100644 index 00000000..2774697f --- /dev/null +++ b/fuzzers/corpora/bugs/layout-infinite-lengths_03.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// #set page(width: auto, height: auto) +// +// // Error: 17-54 cannot create polygon with infinite size +// #layout(size => polygon((0pt,0pt), (0pt, size.width))) \ No newline at end of file diff --git a/fuzzers/corpora/bugs/mat-aug-color_00.typ b/fuzzers/corpora/bugs/mat-aug-color_00.typ new file mode 100644 index 00000000..cd7562af --- /dev/null +++ b/fuzzers/corpora/bugs/mat-aug-color_00.typ @@ -0,0 +1,12 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// https://github.com/typst/typst/issues/2268 +// The augment line should be of the same color as the text +#set text( + font: "New Computer Modern", + lang: "en", + fill: yellow, +) + +$mat(augment: #1, M, v) arrow.r.squiggly mat(augment: #1, R, b)$ diff --git a/fuzzers/corpora/bugs/math-eval_00.typ b/fuzzers/corpora/bugs/math-eval_00.typ new file mode 100644 index 00000000..16bff7f4 --- /dev/null +++ b/fuzzers/corpora/bugs/math-eval_00.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// Evaluating a math expr should renders the same as an equation + +#eval(mode: "math", "f(a) = cases(a + b\, space space x >= 3,a + b\, space space x = 5)") + +$f(a) = cases(a + b\, space space x >= 3,a + b\, space space x = 5)$ diff --git a/fuzzers/corpora/bugs/math-number-spacing_00.typ b/fuzzers/corpora/bugs/math-number-spacing_00.typ new file mode 100644 index 00000000..e7374c95 --- /dev/null +++ b/fuzzers/corpora/bugs/math-number-spacing_00.typ @@ -0,0 +1,10 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +$ +10degree \ +10 degree \ +10.1degree \ +10.1 degree +$ diff --git a/fuzzers/corpora/bugs/math-shift_00.typ b/fuzzers/corpora/bugs/math-shift_00.typ new file mode 100644 index 00000000..415ea7b7 --- /dev/null +++ b/fuzzers/corpora/bugs/math-shift_00.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// https://github.com/typst/typst/issues/2214 +// The math content should also be affected by the TextElem baseline. + +hello #text(baseline: -5pt)[123 #sym.WW\orld]\ +hello #text(baseline: -5pt)[$123 WW#text[or]$ld]\ diff --git a/fuzzers/corpora/bugs/math-text-break_00.typ b/fuzzers/corpora/bugs/math-text-break_00.typ new file mode 100644 index 00000000..49204dae --- /dev/null +++ b/fuzzers/corpora/bugs/math-text-break_00.typ @@ -0,0 +1,5 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +$ x := "a\nb\nc\nd\ne" $ diff --git a/fuzzers/corpora/bugs/new-cm-svg_00.typ b/fuzzers/corpora/bugs/new-cm-svg_00.typ new file mode 100644 index 00000000..43f0de6d --- /dev/null +++ b/fuzzers/corpora/bugs/new-cm-svg_00.typ @@ -0,0 +1,5 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +#set text(font: "New Computer Modern") +#image("/assets/files/diagram.svg") diff --git a/fuzzers/corpora/bugs/newline-mode_00.typ b/fuzzers/corpora/bugs/newline-mode_00.typ new file mode 100644 index 00000000..11cc7c15 --- /dev/null +++ b/fuzzers/corpora/bugs/newline-mode_00.typ @@ -0,0 +1,14 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#{ + "hello" + .clusters() + if false { + + } + else { + ("1", "2") + } +} diff --git a/fuzzers/corpora/bugs/newline-mode_01.typ b/fuzzers/corpora/bugs/newline-mode_01.typ new file mode 100644 index 00000000..7fc87c90 --- /dev/null +++ b/fuzzers/corpora/bugs/newline-mode_01.typ @@ -0,0 +1,13 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#"hello" + .codepoints() + +#if false { + +} +else { + ("1", "2") +} diff --git a/fuzzers/corpora/bugs/pagebreak-bibliography_00.typ b/fuzzers/corpora/bugs/pagebreak-bibliography_00.typ new file mode 100644 index 00000000..3b1ba0e2 --- /dev/null +++ b/fuzzers/corpora/bugs/pagebreak-bibliography_00.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#pagebreak(weak: true) +#bibliography("/assets/files/works.bib") diff --git a/fuzzers/corpora/bugs/pagebreak-numbering_00.typ b/fuzzers/corpora/bugs/pagebreak-numbering_00.typ new file mode 100644 index 00000000..a50c5726 --- /dev/null +++ b/fuzzers/corpora/bugs/pagebreak-numbering_00.typ @@ -0,0 +1,16 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// https://github.com/typst/typst/issues/2095 +// The empty page 2 should not have a page number + +#set page(numbering: none) +This and next page should not be numbered + +#pagebreak(weak: true, to: "odd") + +#set page(numbering: "1") +#counter(page).update(1) + +This page should + diff --git a/fuzzers/corpora/bugs/pagebreak-set-style_00.typ b/fuzzers/corpora/bugs/pagebreak-set-style_00.typ new file mode 100644 index 00000000..3f4d65f1 --- /dev/null +++ b/fuzzers/corpora/bugs/pagebreak-set-style_00.typ @@ -0,0 +1,15 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// https://github.com/typst/typst/issues/2162 +// The styles should not be applied to the pagebreak empty page, +// it should only be applied after that. + +#pagebreak(to: "even") // We should now skip to page 2 + +Some text on page 2 + +#pagebreak(to: "even") // We should now skip to page 4 + +#set page(fill: orange) // This sets the color of the page starting from page 4 +Some text on page 4 diff --git a/fuzzers/corpora/bugs/place-spacing_00.typ b/fuzzers/corpora/bugs/place-spacing_00.typ new file mode 100644 index 00000000..27592f1e --- /dev/null +++ b/fuzzers/corpora/bugs/place-spacing_00.typ @@ -0,0 +1,9 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#show figure: set block(spacing: 4em) + +Paragraph before float. +#figure(rect(), placement: bottom) +Paragraph after float. diff --git a/fuzzers/corpora/bugs/place-spacing_01.typ b/fuzzers/corpora/bugs/place-spacing_01.typ new file mode 100644 index 00000000..7bb3369b --- /dev/null +++ b/fuzzers/corpora/bugs/place-spacing_01.typ @@ -0,0 +1,9 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#show place: set block(spacing: 4em) + +Paragraph before place. +#place(rect()) +Paragraph after place. diff --git a/fuzzers/corpora/bugs/raw-color-overwrite_00.typ b/fuzzers/corpora/bugs/raw-color-overwrite_00.typ new file mode 100644 index 00000000..46eed62f --- /dev/null +++ b/fuzzers/corpora/bugs/raw-color-overwrite_00.typ @@ -0,0 +1,14 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + + +#show raw: set text(fill: blue) + +`Hello, World!` + +```rs +fn main() { + println!("Hello, World!"); +} +``` \ No newline at end of file diff --git a/fuzzers/corpora/layout/align_03.typ b/fuzzers/corpora/layout/align_03.typ index fbfbc96e..989765d7 100644 --- a/fuzzers/corpora/layout/align_03.typ +++ b/fuzzers/corpora/layout/align_03.typ @@ -3,6 +3,6 @@ #show: test-page // Ref: false -#test(type(center), "alignment") -#test(type(horizon), "alignment") -#test(type(center + horizon), "2d alignment") +#test(type(center), alignment) +#test(type(horizon), alignment) +#test(type(center + horizon), alignment) diff --git a/fuzzers/corpora/layout/align_06.typ b/fuzzers/corpora/layout/align_06.typ new file mode 100644 index 00000000..6434f769 --- /dev/null +++ b/fuzzers/corpora/layout/align_06.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// // Error: 8-30 cannot add a vertical and a 2D alignment +// #align(top + (bottom + right), [A]) \ No newline at end of file diff --git a/fuzzers/corpora/layout/cjk-latin-spacing_00.typ b/fuzzers/corpora/layout/cjk-latin-spacing_00.typ new file mode 100644 index 00000000..acc868ca --- /dev/null +++ b/fuzzers/corpora/layout/cjk-latin-spacing_00.typ @@ -0,0 +1,19 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// Test CJK-Latin spacing. + +#set page(width: 50pt + 10pt, margin: (x: 5pt)) +#set text(lang: "zh", font: "Noto Serif CJK SC", cjk-latin-spacing: auto) +#set par(justify: true) + +中文,中12文1中,文12中文 + +中文,中ab文a中,文ab中文 + +#set text(cjk-latin-spacing: none) + +中文,中12文1中,文12中文 + +中文,中ab文a中,文ab中文 + diff --git a/fuzzers/corpora/layout/cjk-punctuation-adjustment_00.typ b/fuzzers/corpora/layout/cjk-punctuation-adjustment_00.typ new file mode 100644 index 00000000..fb489e9e --- /dev/null +++ b/fuzzers/corpora/layout/cjk-punctuation-adjustment_00.typ @@ -0,0 +1,14 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +#set page(width: 15em) + +// In the following example, the space between 》! and ? should be squeezed. +// because zh-CN follows GB style +#set text(lang: "zh", region: "CN", font: "Noto Serif CJK SC") +原来,你也玩《原神》!? + +// However, in the following example, the space between 》! and ? should not be squeezed. +// because zh-TW does not follow GB style +#set text(lang: "zh", region: "TW", font: "Noto Serif CJK TC") +原來,你也玩《原神》! ? \ No newline at end of file diff --git a/fuzzers/corpora/layout/cjk-punctuation-adjustment_01.typ b/fuzzers/corpora/layout/cjk-punctuation-adjustment_01.typ new file mode 100644 index 00000000..ba2e426d --- /dev/null +++ b/fuzzers/corpora/layout/cjk-punctuation-adjustment_01.typ @@ -0,0 +1,9 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + + +#set text(lang: "zh", region: "CN", font: "Noto Serif CJK SC") +《书名〈章节〉》 // the space between 〉 and 》 should be squeezed + +〔茸毛〕:很细的毛 // the space between 〕 and : should be squeezed diff --git a/fuzzers/corpora/layout/cjk-punctuation-adjustment_02.typ b/fuzzers/corpora/layout/cjk-punctuation-adjustment_02.typ new file mode 100644 index 00000000..4296bdf3 --- /dev/null +++ b/fuzzers/corpora/layout/cjk-punctuation-adjustment_02.typ @@ -0,0 +1,23 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set page(width: 21em) +#set text(lang: "zh", region: "CN", font: "Noto Serif CJK SC") + +// These examples contain extensive use of Chinese punctuation marks, +// from 《Which parentheses should be used when applying parentheses?》. +// link: https://archive.md/2bb1N + + +(〔中〕医、〔中〕药、技)系列评审 + +(长三角[长江三角洲])(GB/T 16159—2012《汉语拼音正词法基本规则》) + +【爱因斯坦(Albert Einstein)】物理学家 + +〔(2009)民申字第1622号〕 + +“江南海北长相忆,浅水深山独掩扉。”([唐]刘长卿《会赦后酬主簿所问》) + +参看1378页〖象形文字〗。(《现代汉语词典》修订本) diff --git a/fuzzers/corpora/layout/clip_00.typ b/fuzzers/corpora/layout/clip_00.typ index 8b718471..e0200a73 100644 --- a/fuzzers/corpora/layout/clip_00.typ +++ b/fuzzers/corpora/layout/clip_00.typ @@ -8,5 +8,5 @@ world 1 Space -Hello #box(width: 1em, height: 1em, clip: true)[#rect(width: 3em, height: 3em, fill: red)] +Hello #box(width: 1em, height: 1em, clip: true)[#rect(width: 3em, height: 3em, fill: red)] world 2 diff --git a/fuzzers/corpora/layout/clip_01.typ b/fuzzers/corpora/layout/clip_01.typ index 23533688..c9691955 100644 --- a/fuzzers/corpora/layout/clip_01.typ +++ b/fuzzers/corpora/layout/clip_01.typ @@ -4,7 +4,7 @@ // Test cliping text #block(width: 5em, height: 2em, clip: false, stroke: 1pt + black)[ - But, soft! what light through + But, soft! what light through ] #v(2em) diff --git a/fuzzers/corpora/layout/clip_02.typ b/fuzzers/corpora/layout/clip_02.typ index 64ba9881..f65d4237 100644 --- a/fuzzers/corpora/layout/clip_02.typ +++ b/fuzzers/corpora/layout/clip_02.typ @@ -2,7 +2,7 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page -// Test cliping svg glyphs +// Test clipping svg glyphs Emoji: #box(height: 0.5em, stroke: 1pt + black)[🐪, 🌋, 🏞] Emoji: #box(height: 0.5em, clip: true, stroke: 1pt + black)[🐪, 🌋, 🏞] diff --git a/fuzzers/corpora/layout/clip_04.typ b/fuzzers/corpora/layout/clip_04.typ new file mode 100644 index 00000000..7d10dd5d --- /dev/null +++ b/fuzzers/corpora/layout/clip_04.typ @@ -0,0 +1,16 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test clipping with `radius`. + +#set page(height: 60pt) + +#box( + radius: 5pt, + stroke: 2pt + black, + width: 20pt, + height: 20pt, + clip: true, + image("/assets/files/rhino.png", width: 30pt) +) diff --git a/fuzzers/corpora/layout/enum-align_03.typ b/fuzzers/corpora/layout/enum-align_03.typ index eb77fbfb..551c8bad 100644 --- a/fuzzers/corpora/layout/enum-align_03.typ +++ b/fuzzers/corpora/layout/enum-align_03.typ @@ -1,11 +1,10 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page -// -// // Test valid number align values (horizontal) -// #set enum(number-align: start) -// #set enum(number-align: end) -// #set enum(number-align: left) -// #set enum(number-align: right) -// // Error: 25-28 alignment must be horizontal -// #set enum(number-align: top) \ No newline at end of file + +// Test valid number align values (horizontal) +// Ref: false +#set enum(number-align: start) +#set enum(number-align: end) +#set enum(number-align: left) +#set enum(number-align: right) diff --git a/fuzzers/corpora/layout/enum-align_04.typ b/fuzzers/corpora/layout/enum-align_04.typ new file mode 100644 index 00000000..d44bb6f2 --- /dev/null +++ b/fuzzers/corpora/layout/enum-align_04.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// // Error: 25-28 expected `start`, `left`, `center`, `right`, or `end`, found top +// #set enum(number-align: top) \ No newline at end of file diff --git a/fuzzers/corpora/layout/page-number-align_00.typ b/fuzzers/corpora/layout/page-number-align_00.typ new file mode 100644 index 00000000..a1cf4404 --- /dev/null +++ b/fuzzers/corpora/layout/page-number-align_00.typ @@ -0,0 +1,12 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set page( + height: 100pt, + margin: 30pt, + numbering: "(1)", + number-align: top + right, +) + +#block(width: 100%, height: 100%, fill: aqua.lighten(50%)) diff --git a/fuzzers/corpora/layout/page-number-align_01.typ b/fuzzers/corpora/layout/page-number-align_01.typ new file mode 100644 index 00000000..1f32cda9 --- /dev/null +++ b/fuzzers/corpora/layout/page-number-align_01.typ @@ -0,0 +1,12 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set page( + height: 100pt, + margin: 30pt, + numbering: "[1]", + number-align: bottom + left, +) + +#block(width: 100%, height: 100%, fill: aqua.lighten(50%)) diff --git a/fuzzers/corpora/layout/page-number-align_02.typ b/fuzzers/corpora/layout/page-number-align_02.typ new file mode 100644 index 00000000..014fcc25 --- /dev/null +++ b/fuzzers/corpora/layout/page-number-align_02.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// // Error: 25-39 page number cannot be `horizon`-aligned +// #set page(number-align: left + horizon) \ No newline at end of file diff --git a/fuzzers/corpora/layout/pagebreak-weak_00.typ b/fuzzers/corpora/layout/pagebreak-weak_00.typ new file mode 100644 index 00000000..2fa0121c --- /dev/null +++ b/fuzzers/corpora/layout/pagebreak-weak_00.typ @@ -0,0 +1,11 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// After place +// Should result in three pages. +First +#pagebreak(weak: true) +#place(right)[placed A] +#pagebreak(weak: true) +Third diff --git a/fuzzers/corpora/layout/pagebreak-weak_01.typ b/fuzzers/corpora/layout/pagebreak-weak_01.typ new file mode 100644 index 00000000..857c8351 --- /dev/null +++ b/fuzzers/corpora/layout/pagebreak-weak_01.typ @@ -0,0 +1,12 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// After only ignorables & invisibles +// Should result in two pages. +First +#pagebreak(weak: true) +#counter(page).update(1) +#metadata("Some") +#pagebreak(weak: true) +Second diff --git a/fuzzers/corpora/layout/pagebreak-weak_02.typ b/fuzzers/corpora/layout/pagebreak-weak_02.typ new file mode 100644 index 00000000..3fdc7e05 --- /dev/null +++ b/fuzzers/corpora/layout/pagebreak-weak_02.typ @@ -0,0 +1,12 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// After only ignorables, but regular break +// Should result in three pages. +First +#pagebreak() +#counter(page).update(1) +#metadata("Some") +#pagebreak() +Third diff --git a/fuzzers/corpora/layout/par-justify_05.typ b/fuzzers/corpora/layout/par-justify_05.typ new file mode 100644 index 00000000..97e3c545 --- /dev/null +++ b/fuzzers/corpora/layout/par-justify_05.typ @@ -0,0 +1,11 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test that runts are avoided when it's not too costly to do so. +#set page(width: 124pt) +#set par(justify: true) +#for i in range(0, 20) { + "a b c " +} +#"d" diff --git a/fuzzers/corpora/layout/par-justify_06.typ b/fuzzers/corpora/layout/par-justify_06.typ new file mode 100644 index 00000000..b6bb3eaf --- /dev/null +++ b/fuzzers/corpora/layout/par-justify_06.typ @@ -0,0 +1,12 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test that justification cannot lead to a leading space +#set par(justify: true) +#set text(size: 12pt) +#set page(width: 45mm, height: auto) + +lorem ipsum 1234, lorem ipsum dolor sit amet + +#" leading whitespace should still be displayed" diff --git a/fuzzers/corpora/layout/par-justify_07.typ b/fuzzers/corpora/layout/par-justify_07.typ new file mode 100644 index 00000000..bf97e870 --- /dev/null +++ b/fuzzers/corpora/layout/par-justify_07.typ @@ -0,0 +1,15 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test that justification doesn't break code blocks + +#set par(justify: true) + +```cpp +int main() { + printf("Hello world\n"); + return 0; +} +``` + diff --git a/fuzzers/corpora/layout/place-float-auto_03.typ b/fuzzers/corpora/layout/place-float-auto_03.typ new file mode 100644 index 00000000..ed4264c6 --- /dev/null +++ b/fuzzers/corpora/layout/place-float-auto_03.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// // Error: 2-36 floating placement must be `auto`, `top`, or `bottom` +// #place(horizon, float: true)[Hello] \ No newline at end of file diff --git a/fuzzers/corpora/layout/place-float-auto_04.typ b/fuzzers/corpora/layout/place-float-auto_04.typ new file mode 100644 index 00000000..7a4f1557 --- /dev/null +++ b/fuzzers/corpora/layout/place-float-auto_04.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// // Error: 2-27 floating placement must be `auto`, `top`, or `bottom` +// #place(float: true)[Hello] \ No newline at end of file diff --git a/fuzzers/corpora/layout/place-float-auto_05.typ b/fuzzers/corpora/layout/place-float-auto_05.typ new file mode 100644 index 00000000..2799e10f --- /dev/null +++ b/fuzzers/corpora/layout/place-float-auto_05.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// // Error: 2-34 floating placement must be `auto`, `top`, or `bottom` +// #place(right, float: true)[Hello] \ No newline at end of file diff --git a/fuzzers/corpora/layout/place-float-columns_00.typ b/fuzzers/corpora/layout/place-float-columns_00.typ new file mode 100644 index 00000000..1e4fc2f7 --- /dev/null +++ b/fuzzers/corpora/layout/place-float-columns_00.typ @@ -0,0 +1,20 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set page(height: 200pt, width: 300pt) +#show: columns.with(2) + += Introduction +#figure( + placement: bottom, + caption: [A glacier], + image("/assets/files/glacier.jpg", width: 50%), +) +#lorem(45) +#figure( + placement: top, + caption: [A rectangle], + rect[Hello!], +) +#lorem(20) diff --git a/fuzzers/corpora/layout/spacing_02.typ b/fuzzers/corpora/layout/spacing_02.typ index dc6ed1b8..f26fdd77 100644 --- a/fuzzers/corpora/layout/spacing_02.typ +++ b/fuzzers/corpora/layout/spacing_02.typ @@ -2,7 +2,11 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page -// Test RTL spacing. -#set text(dir: rtl) -A #h(10pt) B \ -A #h(1fr) B +// Test spacing collapsing with different font sizes. +#grid(columns: 2)[ + #text(size: 12pt, block(below: 1em)[A]) + #text(size: 8pt, block(above: 1em)[B]) +][ + #text(size: 12pt, block(below: 1em)[A]) + #text(size: 8pt, block(above: 1.25em)[B]) +] diff --git a/fuzzers/corpora/layout/spacing_03.typ b/fuzzers/corpora/layout/spacing_03.typ index 8721efe2..dc6ed1b8 100644 --- a/fuzzers/corpora/layout/spacing_03.typ +++ b/fuzzers/corpora/layout/spacing_03.typ @@ -1,7 +1,8 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page -// -// // Missing spacing. -// // Error: 11-13 missing argument: amount -// Totally #h() ignored \ No newline at end of file + +// Test RTL spacing. +#set text(dir: rtl) +A #h(10pt) B \ +A #h(1fr) B diff --git a/fuzzers/corpora/layout/spacing_04.typ b/fuzzers/corpora/layout/spacing_04.typ new file mode 100644 index 00000000..8721efe2 --- /dev/null +++ b/fuzzers/corpora/layout/spacing_04.typ @@ -0,0 +1,7 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// // Missing spacing. +// // Error: 11-13 missing argument: amount +// Totally #h() ignored \ No newline at end of file diff --git a/fuzzers/corpora/layout/table_03.typ b/fuzzers/corpora/layout/table_03.typ index 49601fa8..8cb339c0 100644 --- a/fuzzers/corpora/layout/table_03.typ +++ b/fuzzers/corpora/layout/table_03.typ @@ -2,5 +2,33 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page -// Ref: false -#table() +// Test inset. +#table( + columns: 3, + inset: 10pt, + [A], [B], [C] +) + +#table( + columns: 3, + inset: (y: 10pt), + [A], [B], [C] +) + +#table( + columns: 3, + inset: (left: 20pt, rest: 10pt), + [A], [B], [C] +) + +#table( + columns: 2, + inset: ( + left: 20pt, + right: 5pt, + top: 10pt, + bottom: 3pt, + ), + [A], + [B], +) diff --git a/fuzzers/corpora/layout/table_04.typ b/fuzzers/corpora/layout/table_04.typ index b79cdf10..49601fa8 100644 --- a/fuzzers/corpora/layout/table_04.typ +++ b/fuzzers/corpora/layout/table_04.typ @@ -1,6 +1,6 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page -// -// // Error: 14-19 expected color, none, array, or function, found string -// #table(fill: "hey") \ No newline at end of file + +// Ref: false +#table() diff --git a/fuzzers/corpora/layout/table_05.typ b/fuzzers/corpora/layout/table_05.typ new file mode 100644 index 00000000..a0b20599 --- /dev/null +++ b/fuzzers/corpora/layout/table_05.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// // Error: 14-19 expected color, gradient, none, array, or function, found string +// #table(fill: "hey") \ No newline at end of file diff --git a/fuzzers/corpora/math/attach-p2_03.typ b/fuzzers/corpora/math/attach-p2_03.typ index f2c65ea6..88d9e607 100644 --- a/fuzzers/corpora/math/attach-p2_03.typ +++ b/fuzzers/corpora/math/attach-p2_03.typ @@ -8,5 +8,5 @@ $ x_1 p_1 frak(p)_1 2_1 dot_1 lg_1 !_1 \\_1 ]_1 "ip"_1 op("iq")_1 \ x^1 b^1 frak(b)^1 2^1 dot^1 lg^1 !^1 \\^1 ]^1 "ib"^1 op("id")^1 \ x_1 y_1 "_"_1 x^1 l^1 "`"^1 attach(I,tl:1,bl:1,tr:1,br:1) - scripts(sum)_1^1 integral_1^1 |1/2|_1^1 \ + scripts(sum)_1^1 integral_1^1 abs(1/2)_1^1 \ x^1_1, "("b y")"^1_1 != (b y)^1_1, "[∫]"_1 [integral]_1 $ diff --git a/fuzzers/corpora/math/block-alignment_00.typ b/fuzzers/corpora/math/block-alignment_00.typ new file mode 100644 index 00000000..ce665e28 --- /dev/null +++ b/fuzzers/corpora/math/block-alignment_00.typ @@ -0,0 +1,17 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test unnumbered +#let eq(alignment) = { + show math.equation: set align(alignment) + $ a + b = c $ +} + +#eq(center) +#eq(left) +#eq(right) + +#set text(dir: rtl) +#eq(start) +#eq(end) diff --git a/fuzzers/corpora/math/block-alignment_01.typ b/fuzzers/corpora/math/block-alignment_01.typ new file mode 100644 index 00000000..82c17a69 --- /dev/null +++ b/fuzzers/corpora/math/block-alignment_01.typ @@ -0,0 +1,19 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test numbered +#let eq(alignment) = { + show math.equation: set align(alignment) + $ a + b = c $ +} + +#set math.equation(numbering: "(1)") + +#eq(center) +#eq(left) +#eq(right) + +#set text(dir: rtl) +#eq(start) +#eq(end) diff --git a/fuzzers/corpora/math/cancel_04.typ b/fuzzers/corpora/math/cancel_04.typ index 34d690a1..152dd24a 100644 --- a/fuzzers/corpora/math/cancel_04.typ +++ b/fuzzers/corpora/math/cancel_04.typ @@ -4,5 +4,5 @@ // Resized and styled #set page(width: 200pt, height: auto) -$a + cancel(x, length: #200%) - cancel(x, length: #50%, stroke: #{red + 1.1pt})$ -$ b + cancel(x, length: #150%) - cancel(a + b + c, length: #50%, stroke: #{blue + 1.2pt}) $ +$a + cancel(x, length: #200%) - cancel(x, length: #50%, stroke: #(red + 1.1pt))$ +$ b + cancel(x, length: #150%) - cancel(a + b + c, length: #50%, stroke: #(blue + 1.2pt)) $ diff --git a/fuzzers/corpora/math/cancel_05.typ b/fuzzers/corpora/math/cancel_05.typ index d5122d26..a94b1716 100644 --- a/fuzzers/corpora/math/cancel_05.typ +++ b/fuzzers/corpora/math/cancel_05.typ @@ -2,6 +2,5 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page -// Rotated -$x + cancel(y, rotation: #90deg) - cancel(z, rotation: #135deg)$ -$ e + cancel((j + e)/(f + e)) - cancel((j + e)/(f + e), rotation: #30deg) $ +// Specifying cancel line angle with an absolute angle +$cancel(x, angle: #0deg) + cancel(x, angle: #45deg) + cancel(x, angle: #90deg) + cancel(x, angle: #135deg)$ diff --git a/fuzzers/corpora/math/cancel_06.typ b/fuzzers/corpora/math/cancel_06.typ new file mode 100644 index 00000000..c2658fe3 --- /dev/null +++ b/fuzzers/corpora/math/cancel_06.typ @@ -0,0 +1,7 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Specifying cancel line angle with a function +$x + cancel(y, angle: #{angle => angle + 90deg}) - cancel(z, angle: #(angle => angle + 135deg))$ +$ e + cancel((j + e)/(f + e)) - cancel((j + e)/(f + e), angle: #(angle => angle + 30deg)) $ diff --git a/fuzzers/corpora/math/content_04.typ b/fuzzers/corpora/math/content_04.typ index 15eb79e5..5971066a 100644 --- a/fuzzers/corpora/math/content_04.typ +++ b/fuzzers/corpora/math/content_04.typ @@ -4,9 +4,9 @@ // Test boxes without a baseline act as if the baseline is at the base #{ - box(stroke: 0.2pt, $a #box(stroke: 0.2pt, $a$)$) - h(12pt) - box(stroke: 0.2pt, $a #box(stroke: 0.2pt, $g$)$) - h(12pt) - box(stroke: 0.2pt, $g #box(stroke: 0.2pt, $g$)$) + box(stroke: 0.2pt, $a #box(stroke: 0.2pt, $a$)$) + h(12pt) + box(stroke: 0.2pt, $a #box(stroke: 0.2pt, $g$)$) + h(12pt) + box(stroke: 0.2pt, $g #box(stroke: 0.2pt, $g$)$) } diff --git a/fuzzers/corpora/math/delimited_00.typ b/fuzzers/corpora/math/delimited_00.typ index 7b903f41..81bd7e8f 100644 --- a/fuzzers/corpora/math/delimited_00.typ +++ b/fuzzers/corpora/math/delimited_00.typ @@ -3,5 +3,5 @@ #show: test-page // Test automatic matching. -$ (a) + {b/2} + |a|/2 + (b) $ -$f(x/2) < zeta(c^2 + |a + b/2|)$ +$ (a) + {b/2} + abs(a)/2 + (b) $ +$f(x/2) < zeta(c^2 + abs(a + b/2))$ diff --git a/fuzzers/corpora/math/delimited_03.typ b/fuzzers/corpora/math/delimited_03.typ index a15e5a86..b84a5141 100644 --- a/fuzzers/corpora/math/delimited_03.typ +++ b/fuzzers/corpora/math/delimited_03.typ @@ -4,4 +4,4 @@ // Test fence confusion. $ |x + |y| + z/a| \ - |x + lr(|y|) + z/a| $ + lr(|x + |y| + z/a|) $ diff --git a/fuzzers/corpora/math/frac_04.typ b/fuzzers/corpora/math/frac_04.typ index f7635166..5ca116a0 100644 --- a/fuzzers/corpora/math/frac_04.typ +++ b/fuzzers/corpora/math/frac_04.typ @@ -1,6 +1,6 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page -// -// // Error: 8-13 missing argument: lower -// $ binom(x^2) $ \ No newline at end of file + +// Test multinomial coefficients. +$ binom(n, k_1, k_2, k_3) $ diff --git a/fuzzers/corpora/math/frac_05.typ b/fuzzers/corpora/math/frac_05.typ index 4b6f9b45..f7635166 100644 --- a/fuzzers/corpora/math/frac_05.typ +++ b/fuzzers/corpora/math/frac_05.typ @@ -1,7 +1,6 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page - -// Test dif. -$ (dif y)/(dif x), dif/x, x/dif, dif/dif \ - frac(dif y, dif x), frac(dif, x), frac(x, dif), frac(dif, dif) $ +// +// // Error: 8-13 missing argument: lower +// $ binom(x^2) $ \ No newline at end of file diff --git a/fuzzers/corpora/math/frac_06.typ b/fuzzers/corpora/math/frac_06.typ index 75c4ed68..4b6f9b45 100644 --- a/fuzzers/corpora/math/frac_06.typ +++ b/fuzzers/corpora/math/frac_06.typ @@ -2,5 +2,6 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page -// Test associativity. -$ 1/2/3 = (1/2)/3 = 1/(2/3) $ +// Test dif. +$ (dif y)/(dif x), dif/x, x/dif, dif/dif \ + frac(dif y, dif x), frac(dif, x), frac(x, dif), frac(dif, dif) $ diff --git a/fuzzers/corpora/math/frac_07.typ b/fuzzers/corpora/math/frac_07.typ index 5dafa22e..75c4ed68 100644 --- a/fuzzers/corpora/math/frac_07.typ +++ b/fuzzers/corpora/math/frac_07.typ @@ -2,10 +2,5 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page -// Test precedence. -$ a_1/b_2, 1/f(x), zeta(x)/2, "foo"[|x|]/2 \ - 1.2/3.7, 2.3^3.4 \ - 🏳️‍🌈[x]/2, f [x]/2, phi [x]/2, 🏳️‍🌈 [x]/2 \ - +[x]/2, 1(x)/2, 2[x]/2 \ - (a)b/2, b(a)[b]/2 \ - n!/2, 5!/2, n !/2, 1/n!, 1/5! $ +// Test associativity. +$ 1/2/3 = (1/2)/3 = 1/(2/3) $ diff --git a/fuzzers/corpora/math/frac_08.typ b/fuzzers/corpora/math/frac_08.typ new file mode 100644 index 00000000..5dafa22e --- /dev/null +++ b/fuzzers/corpora/math/frac_08.typ @@ -0,0 +1,11 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test precedence. +$ a_1/b_2, 1/f(x), zeta(x)/2, "foo"[|x|]/2 \ + 1.2/3.7, 2.3^3.4 \ + 🏳️‍🌈[x]/2, f [x]/2, phi [x]/2, 🏳️‍🌈 [x]/2 \ + +[x]/2, 1(x)/2, 2[x]/2 \ + (a)b/2, b(a)[b]/2 \ + n!/2, 5!/2, n !/2, 1/n!, 1/5! $ diff --git a/fuzzers/corpora/math/matrix-gaps_00.typ b/fuzzers/corpora/math/matrix-gaps_00.typ new file mode 100644 index 00000000..06f92b5f --- /dev/null +++ b/fuzzers/corpora/math/matrix-gaps_00.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set math.mat(row-gap: 1em, column-gap: 2em) +$ mat(1, 2; 3, 4) $ diff --git a/fuzzers/corpora/math/matrix-gaps_01.typ b/fuzzers/corpora/math/matrix-gaps_01.typ new file mode 100644 index 00000000..e1025012 --- /dev/null +++ b/fuzzers/corpora/math/matrix-gaps_01.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set math.mat(gap: 1em) +$ mat(1, 2; 3, 4) $ diff --git a/fuzzers/corpora/math/matrix-gaps_02.typ b/fuzzers/corpora/math/matrix-gaps_02.typ new file mode 100644 index 00000000..a7df9176 --- /dev/null +++ b/fuzzers/corpora/math/matrix-gaps_02.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set math.cases(gap: 1em) +$ x = cases(1, 2) $ diff --git a/fuzzers/corpora/math/matrix-gaps_03.typ b/fuzzers/corpora/math/matrix-gaps_03.typ new file mode 100644 index 00000000..e291c82f --- /dev/null +++ b/fuzzers/corpora/math/matrix-gaps_03.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set math.vec(gap: 1em) +$ vec(1, 2) $ diff --git a/fuzzers/corpora/math/matrix_07.typ b/fuzzers/corpora/math/matrix_07.typ new file mode 100644 index 00000000..ea6a3564 --- /dev/null +++ b/fuzzers/corpora/math/matrix_07.typ @@ -0,0 +1,19 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + + +// Test matrix line drawing (augmentation). +#grid( + columns: 2, + gutter: 10pt, + + $ mat(10, 2, 3, 4; 5, 6, 7, 8; augment: #3) $, + $ mat(10, 2, 3, 4; 5, 6, 7, 8; augment: #(-1)) $, + $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(hline: 2)) $, + $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(hline: -1)) $, + $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(hline: 1, vline: 1)) $, + $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(hline: -2, vline: -2)) $, + $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(vline: 2, stroke: 1pt + blue)) $, + $ mat(100, 2, 3; 4, 5, 6; 7, 8, 9; augment: #(vline: -1, stroke: 1pt + blue)) $, +) diff --git a/fuzzers/corpora/math/matrix_08.typ b/fuzzers/corpora/math/matrix_08.typ new file mode 100644 index 00000000..e0a282b6 --- /dev/null +++ b/fuzzers/corpora/math/matrix_08.typ @@ -0,0 +1,13 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + + +// Test using matrix line drawing with a set rule. +#set math.mat(augment: (hline: 2, vline: 1, stroke: 2pt + green)) +$ mat(1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 1) $ + +#set math.mat(augment: 2) +$ mat(1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 1) $ + +#set math.mat(augment: none) diff --git a/fuzzers/corpora/math/matrix_09.typ b/fuzzers/corpora/math/matrix_09.typ new file mode 100644 index 00000000..e5d26330 --- /dev/null +++ b/fuzzers/corpora/math/matrix_09.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// // Error: 3-37 cannot draw a vertical line after column 3 of a matrix with 3 columns +// $ mat(1, 0, 0; 0, 1, 1; augment: #3) $, \ No newline at end of file diff --git a/fuzzers/corpora/math/opticalsize_07.typ b/fuzzers/corpora/math/opticalsize_07.typ new file mode 100644 index 00000000..26ae23ab --- /dev/null +++ b/fuzzers/corpora/math/opticalsize_07.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test attaching primes only +$a' = a^', a_', a_'''^''^'$ \ No newline at end of file diff --git a/fuzzers/corpora/math/prime_00.typ b/fuzzers/corpora/math/prime_00.typ new file mode 100644 index 00000000..e3f00d36 --- /dev/null +++ b/fuzzers/corpora/math/prime_00.typ @@ -0,0 +1,12 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// Test prime symbols after code mode. +#let g = $f$ +#let gg = $f$ + +$ + #(g)' #g' #g ' \ + #g''''''''''''''''' \ + gg' +$ diff --git a/fuzzers/corpora/math/spacing_00.typ b/fuzzers/corpora/math/spacing_00.typ index 829151c4..bc29edcc 100644 --- a/fuzzers/corpora/math/spacing_00.typ +++ b/fuzzers/corpora/math/spacing_00.typ @@ -5,7 +5,7 @@ // Test spacing cases. $ä, +, c, (, )$ \ $=), (+), {times}$ -$⟧<⟦, |-|, [=$ \ +$⟧<⟦, abs(-), [=$ \ $a=b, a==b$ \ $-a, +a$ \ $a not b$ \ diff --git a/fuzzers/corpora/math/spacing_04.typ b/fuzzers/corpora/math/spacing_04.typ index 915a61e8..98c0938c 100644 --- a/fuzzers/corpora/math/spacing_04.typ +++ b/fuzzers/corpora/math/spacing_04.typ @@ -8,4 +8,4 @@ $a equiv b + c - d => e log 5 op("ln") 6$ \ $a cancel(equiv) b overline(+) c arrow(-) d hat(=>) e cancel(log) 5 dot(op("ln")) 6$ \ $a overbrace(equiv) b underline(+) c grave(-) d underbracket(=>) e circle(log) 5 caron(op("ln")) 6$ \ \ -$a attach(equiv, tl: a, tr: b) b attach(limits(+), t: a, b: b) c tilde(-) d breve(=>) e attach(limits(log), t: a, b: b) 5 attach(op("ln"), tr: a, bl: b) 6$ +$a attach(equiv, tl: a, tr: b) b attach(limits(+), t: a, b: b) c tilde(-) d breve(=>) e attach(limits(log), t: a, b: b) 5 attach(op("ln"), tr: a, bl: b) 6$ \ No newline at end of file diff --git a/fuzzers/corpora/math/spacing_05.typ b/fuzzers/corpora/math/spacing_05.typ new file mode 100644 index 00000000..cb863154 --- /dev/null +++ b/fuzzers/corpora/math/spacing_05.typ @@ -0,0 +1,10 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test weak spacing +$integral f(x) dif x$, +// Not weak +$integral f(x) thin dif x$, +// Both are weak, collide +$integral f(x) #h(0.166em, weak: true)dif x$ diff --git a/fuzzers/corpora/meta/bibliography_01.typ b/fuzzers/corpora/meta/bibliography_01.typ index 78304041..5a8482f6 100644 --- a/fuzzers/corpora/meta/bibliography_01.typ +++ b/fuzzers/corpora/meta/bibliography_01.typ @@ -4,5 +4,5 @@ #set page(width: 200pt) = Details -See also #cite("arrgh", "distress", [p. 22]), @arrgh[p. 4], and @distress[p. 5]. +See also #cite("arrgh", "distress", supplement: [p. 22]), @arrgh[p. 4], and @distress[p. 5]. #bibliography("/assets/files/works.bib") diff --git a/fuzzers/corpora/meta/document_01.typ b/fuzzers/corpora/meta/document_01.typ index 4b2933c3..6ecdcd96 100644 --- a/fuzzers/corpora/meta/document_01.typ +++ b/fuzzers/corpora/meta/document_01.typ @@ -4,4 +4,4 @@ // This, too. // Ref: false -#set document(author: ("A", "B")) +#set document(author: ("A", "B"), date: datetime.today()) diff --git a/fuzzers/corpora/meta/document_02.typ b/fuzzers/corpora/meta/document_02.typ index c6cbaad7..96e00c31 100644 --- a/fuzzers/corpora/meta/document_02.typ +++ b/fuzzers/corpora/meta/document_02.typ @@ -2,7 +2,5 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page // -// // This, too. -// // Error: 23-29 expected string, found integer -// #set document(author: (123,)) -// What's up? \ No newline at end of file +// // Error: 21-28 expected datetime or none, found string +// #set document(date: "today") \ No newline at end of file diff --git a/fuzzers/corpora/meta/document_03.typ b/fuzzers/corpora/meta/document_03.typ index 08b4871f..c6cbaad7 100644 --- a/fuzzers/corpora/meta/document_03.typ +++ b/fuzzers/corpora/meta/document_03.typ @@ -2,7 +2,7 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page // -// Hello -// -// // Error: 2-30 document set rules must appear before any content -// #set document(title: "Hello") \ No newline at end of file +// // This, too. +// // Error: 23-29 expected string, found integer +// #set document(author: (123,)) +// What's up? \ No newline at end of file diff --git a/fuzzers/corpora/meta/document_04.typ b/fuzzers/corpora/meta/document_04.typ index 653418c5..08b4871f 100644 --- a/fuzzers/corpora/meta/document_04.typ +++ b/fuzzers/corpora/meta/document_04.typ @@ -2,5 +2,7 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page // -// // Error: 10-12 can only be used in set rules -// #document() \ No newline at end of file +// Hello +// +// // Error: 2-30 document set rules must appear before any content +// #set document(title: "Hello") \ No newline at end of file diff --git a/fuzzers/corpora/meta/document_05.typ b/fuzzers/corpora/meta/document_05.typ index 615bddc5..653418c5 100644 --- a/fuzzers/corpora/meta/document_05.typ +++ b/fuzzers/corpora/meta/document_05.typ @@ -2,7 +2,5 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page // -// #box[ -// // Error: 4-32 document set rules are not allowed inside of containers -// #set document(title: "Hello") -// ] \ No newline at end of file +// // Error: 10-12 can only be used in set rules +// #document() \ No newline at end of file diff --git a/fuzzers/corpora/meta/document_06.typ b/fuzzers/corpora/meta/document_06.typ index bb723336..615bddc5 100644 --- a/fuzzers/corpora/meta/document_06.typ +++ b/fuzzers/corpora/meta/document_06.typ @@ -3,6 +3,6 @@ #show: test-page // // #box[ -// // Error: 4-18 page configuration is not allowed inside of containers -// #set page("a4") +// // Error: 4-32 document set rules are not allowed inside of containers +// #set document(title: "Hello") // ] \ No newline at end of file diff --git a/fuzzers/corpora/meta/document_07.typ b/fuzzers/corpora/meta/document_07.typ index cfc296f0..bb723336 100644 --- a/fuzzers/corpora/meta/document_07.typ +++ b/fuzzers/corpora/meta/document_07.typ @@ -3,6 +3,6 @@ #show: test-page // // #box[ -// // Error: 4-15 pagebreaks are not allowed inside of containers -// #pagebreak() +// // Error: 4-18 page configuration is not allowed inside of containers +// #set page("a4") // ] \ No newline at end of file diff --git a/fuzzers/corpora/meta/document_08.typ b/fuzzers/corpora/meta/document_08.typ new file mode 100644 index 00000000..cfc296f0 --- /dev/null +++ b/fuzzers/corpora/meta/document_08.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// #box[ +// // Error: 4-15 pagebreaks are not allowed inside of containers +// #pagebreak() +// ] \ No newline at end of file diff --git a/fuzzers/corpora/meta/figure-caption_00.typ b/fuzzers/corpora/meta/figure-caption_00.typ new file mode 100644 index 00000000..156921ab --- /dev/null +++ b/fuzzers/corpora/meta/figure-caption_00.typ @@ -0,0 +1,11 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test figure.caption element +#show figure.caption: emph + +#figure( + [Not italicized], + caption: [Italicized], +) diff --git a/fuzzers/corpora/meta/figure-caption_01.typ b/fuzzers/corpora/meta/figure-caption_01.typ new file mode 100644 index 00000000..6248ac99 --- /dev/null +++ b/fuzzers/corpora/meta/figure-caption_01.typ @@ -0,0 +1,16 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test figure.caption element for specific figure kinds +#show figure.caption.where(kind: table): underline + +#figure( + [Not a table], + caption: [Not underlined], +) + +#figure( + table[A table], + caption: [Underlined], +) diff --git a/fuzzers/corpora/meta/figure-caption_02.typ b/fuzzers/corpora/meta/figure-caption_02.typ new file mode 100644 index 00000000..88887f7b --- /dev/null +++ b/fuzzers/corpora/meta/figure-caption_02.typ @@ -0,0 +1,34 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test creating custom figure and custom caption + +#let gap = 0.7em +#show figure.where(kind: "custom"): it => rect(inset: gap, { + align(center, it.body) + v(gap, weak: true) + line(length: 100%) + v(gap, weak: true) + align(center, it.caption) +}) + +#figure( + [A figure], + kind: "custom", + caption: [Hi], + supplement: [A], +) + +#show figure.caption: it => emph[ + #it.body + (#it.supplement + #it.counter.display(it.numbering)) +] + +#figure( + [Another figure], + kind: "custom", + caption: [Hi], + supplement: [B], +) diff --git a/fuzzers/corpora/meta/figure_02.typ b/fuzzers/corpora/meta/figure_02.typ index 1cbcfefc..e8c40e11 100644 --- a/fuzzers/corpora/meta/figure_02.typ +++ b/fuzzers/corpora/meta/figure_02.typ @@ -7,7 +7,7 @@ #show figure.where(kind: "theorem"): it => { let name = none if not it.caption == none { - name = [ #emph(it.caption)] + name = [ #emph(it.caption.body)] } else { name = [] } diff --git a/fuzzers/corpora/meta/figure_04.typ b/fuzzers/corpora/meta/figure_04.typ new file mode 100644 index 00000000..461ab672 --- /dev/null +++ b/fuzzers/corpora/meta/figure_04.typ @@ -0,0 +1,11 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test custom separator for figure caption +#set figure.caption(separator: [ --- ]) + +#figure( + table(columns: 2)[a][b], + caption: [The table with custom separator.], +) diff --git a/fuzzers/corpora/meta/figure_05.typ b/fuzzers/corpora/meta/figure_05.typ new file mode 100644 index 00000000..613dc919 --- /dev/null +++ b/fuzzers/corpora/meta/figure_05.typ @@ -0,0 +1,24 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test localized default separator +#set text(lang: "fr", region: "CH") + +#figure( + circle(), + caption: [Un cercle.], +) +#set text(lang: "es") + +#figure( + polygon.regular(size: 1cm, vertices: 3), + caption: [Un triángulo.], +) + +#set text(lang: "fr", region: "CA") + +#figure( + square(), + caption: [Un carré.], +) diff --git a/fuzzers/corpora/meta/numbering_03.typ b/fuzzers/corpora/meta/numbering_03.typ index 30714d1f..17a84b67 100644 --- a/fuzzers/corpora/meta/numbering_03.typ +++ b/fuzzers/corpora/meta/numbering_03.typ @@ -3,7 +3,7 @@ #show: test-page #set text(lang: "zh", font: ("Linux Libertine", "Noto Serif CJK SC")) -#for i in range(9,21, step: 2){ +#for i in range(9, 21, step: 2){ numbering("一", i) [ and ] numbering("壹", i) diff --git a/fuzzers/corpora/meta/outline-entry_02.typ b/fuzzers/corpora/meta/outline-entry_02.typ index 5c8c3c7e..57d284a9 100644 --- a/fuzzers/corpora/meta/outline-entry_02.typ +++ b/fuzzers/corpora/meta/outline-entry_02.typ @@ -4,5 +4,5 @@ // // // Error: 2-23 cannot outline cite // #outline(target: cite) -// #cite("arrgh", "distress", [p. 22]) +// #cite("arrgh", "distress", supplement: [p. 22]) // #bibliography("/assets/files/works.bib") \ No newline at end of file diff --git a/fuzzers/corpora/meta/page-label_00.typ b/fuzzers/corpora/meta/page-label_00.typ new file mode 100644 index 00000000..a6d1b5e5 --- /dev/null +++ b/fuzzers/corpora/meta/page-label_00.typ @@ -0,0 +1,50 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +#set page(margin: (bottom: 20pt, rest: 10pt)) +#let filler = lorem(20) + +// (i) - (ii). No style opt. because of suffix. +#set page(numbering: "(i)") +#filler +#pagebreak() +#filler + +// 3 - 4. Style opt. Page Label should use /D style. +#set page(numbering: "1") +#filler +#pagebreak() +#filler + +// I - IV. Style opt. Page Label should use /R style and start at 1 again. +#set page(numbering: "I / I") +#counter(page).update(1) +#filler +#pagebreak() +#filler +#pagebreak() +#filler +#pagebreak() +#filler + +// Pre: ほ, Pre: ろ, Pre: は, Pre: に. No style opt. Uses prefix field entirely. +// Counter update without numbering change. +#set page(numbering: "Pre: い") +#filler +#pagebreak() +#filler +#counter(page).update(2) +#filler +#pagebreak() +#filler +#pagebreak() +#filler + +// aa & ba. Style opt only for values <= 26. Page Label uses lower alphabet style. +// Repeats letter each 26 pages or uses numbering directly as prefix. +#set page(numbering: "a") +#counter(page).update(27) +#filler +#pagebreak() +#counter(page).update(53) +#filler diff --git a/fuzzers/corpora/meta/query-figure_00.typ b/fuzzers/corpora/meta/query-figure_00.typ index 864a3b94..0aa04cd7 100644 --- a/fuzzers/corpora/meta/query-figure_00.typ +++ b/fuzzers/corpora/meta/query-figure_00.typ @@ -18,7 +18,7 @@ Figure #numbering(it.numbering, ..counter(figure).at(it.location())): - #it.caption + #it.caption.body #box(width: 1fr, repeat[.]) #counter(page).at(it.location()).first() \ ] diff --git a/fuzzers/corpora/meta/state_03.typ b/fuzzers/corpora/meta/state_03.typ index 79396e71..3f178b10 100644 --- a/fuzzers/corpora/meta/state_03.typ +++ b/fuzzers/corpora/meta/state_03.typ @@ -3,10 +3,8 @@ #show: test-page // Make sure that a warning is produced if the layout fails to converge. -// Warning: -3:1-6:1 layout did not converge within 5 attempts -// Hint: -3:1-6:1 check if any states or queries are updating themselves -#let s = state("x", 1) -#locate(loc => { - s.update(s.final(loc) + 1) -}) +// Warning: layout did not converge within 5 attempts +// Hint: check if any states or queries are updating themselves +#let s = state("s", 1) +#locate(loc => s.update(s.final(loc) + 1)) #s.display() diff --git a/fuzzers/corpora/text/deco_01.typ b/fuzzers/corpora/text/deco_01.typ index d027a6c8..6a01d7d4 100644 --- a/fuzzers/corpora/text/deco_01.typ +++ b/fuzzers/corpora/text/deco_01.typ @@ -3,8 +3,8 @@ #show: test-page #let redact = strike.with(stroke: 10pt, extent: 0.05em) -#let highlight = strike.with(stroke: 10pt + rgb("abcdef88"), extent: 0.05em) +#let highlight-custom = strike.with(stroke: 10pt + rgb("abcdef88"), extent: 0.05em) // Abuse thickness and transparency for redacting and highlighting stuff. Sometimes, we work #redact[in secret]. -There might be #highlight[redacted] things. +There might be #highlight-custom[redacted] things. diff --git a/fuzzers/corpora/text/deco_03.typ b/fuzzers/corpora/text/deco_03.typ new file mode 100644 index 00000000..1cb36fb2 --- /dev/null +++ b/fuzzers/corpora/text/deco_03.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test highlight. +This is the built-in #highlight[highlight with default color]. +We can also specify a customized value +#highlight(fill: green.lighten(80%))[to highlight]. diff --git a/fuzzers/corpora/text/deco_04.typ b/fuzzers/corpora/text/deco_04.typ new file mode 100644 index 00000000..077c2373 --- /dev/null +++ b/fuzzers/corpora/text/deco_04.typ @@ -0,0 +1,9 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test default highlight bounds. +#highlight[ace], +#highlight[base], +#highlight[super], +#highlight[phone #sym.integral] diff --git a/fuzzers/corpora/text/deco_05.typ b/fuzzers/corpora/text/deco_05.typ new file mode 100644 index 00000000..62188702 --- /dev/null +++ b/fuzzers/corpora/text/deco_05.typ @@ -0,0 +1,10 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test a tighter highlight. +#set highlight(top-edge: "x-height", bottom-edge: "baseline") +#highlight[ace], +#highlight[base], +#highlight[super], +#highlight[phone #sym.integral] diff --git a/fuzzers/corpora/text/deco_06.typ b/fuzzers/corpora/text/deco_06.typ new file mode 100644 index 00000000..3447b238 --- /dev/null +++ b/fuzzers/corpora/text/deco_06.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test a bounds highlight. +#set highlight(top-edge: "bounds", bottom-edge: "bounds") +#highlight[abc] +#highlight[abc #sym.integral] diff --git a/fuzzers/corpora/text/deco_07.typ b/fuzzers/corpora/text/deco_07.typ new file mode 100644 index 00000000..8f5564c3 --- /dev/null +++ b/fuzzers/corpora/text/deco_07.typ @@ -0,0 +1,7 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test underline background +#set underline(background: true, stroke: (thickness: 0.5em, paint: red, cap: "round")) +#underline[This is in the background] diff --git a/fuzzers/corpora/text/deco_08.typ b/fuzzers/corpora/text/deco_08.typ new file mode 100644 index 00000000..75214bf5 --- /dev/null +++ b/fuzzers/corpora/text/deco_08.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test overline background +#set overline(background: true, stroke: (thickness: 0.5em, paint: red, cap: "round")) +#overline[This is in the background] + diff --git a/fuzzers/corpora/text/deco_09.typ b/fuzzers/corpora/text/deco_09.typ new file mode 100644 index 00000000..57871fdd --- /dev/null +++ b/fuzzers/corpora/text/deco_09.typ @@ -0,0 +1,7 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test strike background +#set strike(background: true, stroke: 5pt + red) +#strike[This is in the background] diff --git a/fuzzers/corpora/text/numbers_00.typ b/fuzzers/corpora/text/numbers_00.typ new file mode 100644 index 00000000..6c5dd767 --- /dev/null +++ b/fuzzers/corpora/text/numbers_00.typ @@ -0,0 +1,33 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test numbers in text mode. +12 \ +12.0 \ +3.14 \ +1234567890 \ +0123456789 \ +0 \ +0.0 \ ++0 \ ++0.0 \ +-0 \ +-0.0 \ +-1 \ +-3.14 \ +-9876543210 \ +-0987654321 \ +٣٫١٤ \ +-٣٫١٤ \ +-¾ \ +#text(fractions: true)[-3/2] \ +2022 - 2023 \ +2022 -- 2023 \ +2022--2023 \ +2022-2023 \ +٢٠٢٢ - ٢٠٢٣ \ +٢٠٢٢ -- ٢٠٢٣ \ +٢٠٢٢--٢٠٢٣ \ +٢٠٢٢-٢٠٢٣ \ +-500 -- -400 diff --git a/fuzzers/corpora/text/numbers_01.typ b/fuzzers/corpora/text/numbers_01.typ new file mode 100644 index 00000000..882e98e3 --- /dev/null +++ b/fuzzers/corpora/text/numbers_01.typ @@ -0,0 +1,14 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test integers. +#12 \ +#1234567890 \ +#0123456789 \ +#0 \ +#(-0) \ +#(-1) \ +#(-9876543210) \ +#(-0987654321) \ +#(4 - 8) diff --git a/fuzzers/corpora/text/numbers_02.typ b/fuzzers/corpora/text/numbers_02.typ new file mode 100644 index 00000000..6a4579e6 --- /dev/null +++ b/fuzzers/corpora/text/numbers_02.typ @@ -0,0 +1,16 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test floats. +#12.0 \ +#3.14 \ +#1234567890.0 \ +#0123456789.0 \ +#0.0 \ +#(-0.0) \ +#(-1.0) \ +#(-9876543210.0) \ +#(-0987654321.0) \ +#(-3.14) \ +#(4.0 - 8.0) diff --git a/fuzzers/corpora/text/numbers_03.typ b/fuzzers/corpora/text/numbers_03.typ new file mode 100644 index 00000000..4db8c6a5 --- /dev/null +++ b/fuzzers/corpora/text/numbers_03.typ @@ -0,0 +1,14 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test the `str` function with integers. +#str(12) \ +#str(1234567890) \ +#str(0123456789) \ +#str(0) \ +#str(-0) \ +#str(-1) \ +#str(-9876543210) \ +#str(-0987654321) \ +#str(4 - 8) diff --git a/fuzzers/corpora/text/numbers_04.typ b/fuzzers/corpora/text/numbers_04.typ new file mode 100644 index 00000000..290ec230 --- /dev/null +++ b/fuzzers/corpora/text/numbers_04.typ @@ -0,0 +1,16 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test the `str` function with floats. +#str(12.0) \ +#str(3.14) \ +#str(1234567890.0) \ +#str(0123456789.0) \ +#str(0.0) \ +#str(-0.0) \ +#str(-1.0) \ +#str(-9876543210.0) \ +#str(-0987654321.0) \ +#str(-3.14) \ +#str(4.0 - 8.0) diff --git a/fuzzers/corpora/text/numbers_05.typ b/fuzzers/corpora/text/numbers_05.typ new file mode 100644 index 00000000..14301384 --- /dev/null +++ b/fuzzers/corpora/text/numbers_05.typ @@ -0,0 +1,14 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test the `repr` function with integers. +#repr(12) \ +#repr(1234567890) \ +#repr(0123456789) \ +#repr(0) \ +#repr(-0) \ +#repr(-1) \ +#repr(-9876543210) \ +#repr(-0987654321) \ +#repr(4 - 8) diff --git a/fuzzers/corpora/text/numbers_06.typ b/fuzzers/corpora/text/numbers_06.typ new file mode 100644 index 00000000..b5251639 --- /dev/null +++ b/fuzzers/corpora/text/numbers_06.typ @@ -0,0 +1,16 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test the `repr` function with floats. +#repr(12.0) \ +#repr(3.14) \ +#repr(1234567890.0) \ +#repr(0123456789.0) \ +#repr(0.0) \ +#repr(-0.0) \ +#repr(-1.0) \ +#repr(-9876543210.0) \ +#repr(-0987654321.0) \ +#repr(-3.14) \ +#repr(4.0 - 8.0) diff --git a/fuzzers/corpora/text/quote_00.typ b/fuzzers/corpora/text/quote_00.typ new file mode 100644 index 00000000..c9e7891b --- /dev/null +++ b/fuzzers/corpora/text/quote_00.typ @@ -0,0 +1,9 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Text direction affects author positioning +And I quote: #quote(attribution: [René Descartes])[cogito, ergo sum]. + +#set text(lang: "ar") +#quote(attribution: [عالم])[مرحبًا] diff --git a/fuzzers/corpora/text/quote_01.typ b/fuzzers/corpora/text/quote_01.typ new file mode 100644 index 00000000..2e1a1f7e --- /dev/null +++ b/fuzzers/corpora/text/quote_01.typ @@ -0,0 +1,10 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Text direction affects block alignment +#set quote(block: true) +#quote(attribution: [René Descartes])[cogito, ergo sum] + +#set text(lang: "ar") +#quote(attribution: [عالم])[مرحبًا] diff --git a/fuzzers/corpora/text/quote_02.typ b/fuzzers/corpora/text/quote_02.typ new file mode 100644 index 00000000..b0d4e552 --- /dev/null +++ b/fuzzers/corpora/text/quote_02.typ @@ -0,0 +1,10 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Spacing with other blocks +#set quote(block: true) + +#lorem(10) +#quote(lorem(10)) +#lorem(10) diff --git a/fuzzers/corpora/text/quote_03.typ b/fuzzers/corpora/text/quote_03.typ new file mode 100644 index 00000000..58bd114e --- /dev/null +++ b/fuzzers/corpora/text/quote_03.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Inline citation +#bibliography("/assets/files/works.bib") + +#quote(attribution: )[In a hole in the ground there lived a hobbit.] diff --git a/fuzzers/corpora/text/quote_04.typ b/fuzzers/corpora/text/quote_04.typ new file mode 100644 index 00000000..86504f54 --- /dev/null +++ b/fuzzers/corpora/text/quote_04.typ @@ -0,0 +1,9 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Citation-format: label or numeric +#set quote(block: true) +#bibliography("/assets/files/works.bib", style: "ieee") + +#quote(attribution: )[In a hole in the ground there lived a hobbit.] diff --git a/fuzzers/corpora/text/quote_05.typ b/fuzzers/corpora/text/quote_05.typ new file mode 100644 index 00000000..670aab9d --- /dev/null +++ b/fuzzers/corpora/text/quote_05.typ @@ -0,0 +1,9 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Citation-format: note +#set quote(block: true) +#bibliography("/assets/files/works.bib", style: "chicago-notes") + +#quote(attribution: )[In a hole in the ground there lived a hobbit.] diff --git a/fuzzers/corpora/text/quote_06.typ b/fuzzers/corpora/text/quote_06.typ new file mode 100644 index 00000000..65ff3d70 --- /dev/null +++ b/fuzzers/corpora/text/quote_06.typ @@ -0,0 +1,9 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Citation-format: author-date or author +#set quote(block: true) +#bibliography("/assets/files/works.bib", style: "apa") + +#quote(attribution: )[In a hole in the ground there lived a hobbit.] diff --git a/fuzzers/corpora/text/quotes_06.typ b/fuzzers/corpora/text/quotes_06.typ new file mode 100644 index 00000000..4a49992e --- /dev/null +++ b/fuzzers/corpora/text/quotes_06.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test nested double and single quotes. +"'test statement'" \ +"'test' statement" \ +"statement 'test'" diff --git a/fuzzers/corpora/text/raw-align_02.typ b/fuzzers/corpora/text/raw-align_02.typ index 6b81cfa7..737ef400 100644 --- a/fuzzers/corpora/text/raw-align_02.typ +++ b/fuzzers/corpora/text/raw-align_02.typ @@ -2,5 +2,5 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page // -// // Error: 17-20 alignment must be horizontal +// // Error: 17-20 expected `start`, `left`, `center`, `right`, or `end`, found top // #set raw(align: top) \ No newline at end of file diff --git a/fuzzers/corpora/text/raw-line_00.typ b/fuzzers/corpora/text/raw-line_00.typ new file mode 100644 index 00000000..7d0cf928 --- /dev/null +++ b/fuzzers/corpora/text/raw-line_00.typ @@ -0,0 +1,26 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set page(width: 200pt) + +```rs +fn main() { + println!("Hello, world!"); +} +``` + +#show raw.line: it => { + box(stack( + dir: ltr, + box(width: 15pt)[#it.number], + it.body, + )) + linebreak() +} + +```rs +fn main() { + println!("Hello, world!"); +} +``` diff --git a/fuzzers/corpora/text/raw-line_01.typ b/fuzzers/corpora/text/raw-line_01.typ new file mode 100644 index 00000000..d458d267 --- /dev/null +++ b/fuzzers/corpora/text/raw-line_01.typ @@ -0,0 +1,33 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set page(width: 200pt) +#show raw: it => stack(dir: ttb, ..it.lines) +#show raw.line: it => { + box( + width: 100%, + height: 1.75em, + inset: 0.25em, + fill: if calc.rem(it.number, 2) == 0 { + luma(90%) + } else { + white + }, + align(horizon, stack( + dir: ltr, + box(width: 15pt)[#it.number], + it.body, + )) + ) +} + +```typ +#show raw.line: block.with( + fill: luma(60%) +); + +Hello, world! + += A heading for good measure +``` diff --git a/fuzzers/corpora/text/raw-line_02.typ b/fuzzers/corpora/text/raw-line_02.typ new file mode 100644 index 00000000..e82f50e6 --- /dev/null +++ b/fuzzers/corpora/text/raw-line_02.typ @@ -0,0 +1,19 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set page(width: 200pt) +#show raw.line: set text(fill: red) + +```py +import numpy as np + +def f(x): + return x**2 + +x = np.linspace(0, 10, 100) +y = f(x) + +print(x) +print(y) +``` diff --git a/fuzzers/corpora/text/raw-line_03.typ b/fuzzers/corpora/text/raw-line_03.typ new file mode 100644 index 00000000..7c9bc49b --- /dev/null +++ b/fuzzers/corpora/text/raw-line_03.typ @@ -0,0 +1,38 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Ref: false + +// Test line extraction works. + +#show raw: code => { + for i in code.lines { + test(i.count, 10) + } + + test(code.lines.at(0).text, "import numpy as np") + test(code.lines.at(1).text, "") + test(code.lines.at(2).text, "def f(x):") + test(code.lines.at(3).text, " return x**2") + test(code.lines.at(4).text, "") + test(code.lines.at(5).text, "x = np.linspace(0, 10, 100)") + test(code.lines.at(6).text, "y = f(x)") + test(code.lines.at(7).text, "") + test(code.lines.at(8).text, "print(x)") + test(code.lines.at(9).text, "print(y)") + test(code.lines.at(10, default: none), none) +} + +```py +import numpy as np + +def f(x): + return x**2 + +x = np.linspace(0, 10, 100) +y = f(x) + +print(x) +print(y) +``` diff --git a/fuzzers/corpora/text/raw-tabs_00.typ b/fuzzers/corpora/text/raw-tabs_00.typ new file mode 100644 index 00000000..f26e07d2 --- /dev/null +++ b/fuzzers/corpora/text/raw-tabs_00.typ @@ -0,0 +1,12 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set raw(tab-size: 8) + +```tsv +Year Month Day +2000 2 3 +2001 2 1 +2002 3 10 +``` diff --git a/fuzzers/corpora/text/smartquotes_00.typ b/fuzzers/corpora/text/smartquotes_00.typ new file mode 100644 index 00000000..99c9f456 --- /dev/null +++ b/fuzzers/corpora/text/smartquotes_00.typ @@ -0,0 +1,10 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Use language quotes for missing keys, allow partial reset +#set smartquote(quotes: "«»") +"Double and 'Single' Quotes" + +#set smartquote(quotes: (double: auto, single: "«»")) +"Double and 'Single' Quotes" diff --git a/fuzzers/corpora/text/smartquotes_01.typ b/fuzzers/corpora/text/smartquotes_01.typ new file mode 100644 index 00000000..8b95c42f --- /dev/null +++ b/fuzzers/corpora/text/smartquotes_01.typ @@ -0,0 +1,10 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Allow 2 graphemes +#set smartquote(quotes: "a\u{0301}a\u{0301}") +"Double and 'Single' Quotes" + +#set smartquote(quotes: (single: "a\u{0301}a\u{0301}")) +"Double and 'Single' Quotes" diff --git a/fuzzers/corpora/text/smartquotes_02.typ b/fuzzers/corpora/text/smartquotes_02.typ new file mode 100644 index 00000000..0ad846ac --- /dev/null +++ b/fuzzers/corpora/text/smartquotes_02.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// // Error: 25-28 expected 2 characters, found 1 character +// #set smartquote(quotes: "'") \ No newline at end of file diff --git a/fuzzers/corpora/text/smartquotes_03.typ b/fuzzers/corpora/text/smartquotes_03.typ new file mode 100644 index 00000000..bc52ae7e --- /dev/null +++ b/fuzzers/corpora/text/smartquotes_03.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// // Error: 25-35 expected 2 quotes, found 4 quotes +// #set smartquote(quotes: ("'",) * 4) \ No newline at end of file diff --git a/fuzzers/corpora/text/smartquotes_04.typ b/fuzzers/corpora/text/smartquotes_04.typ new file mode 100644 index 00000000..45a2bbed --- /dev/null +++ b/fuzzers/corpora/text/smartquotes_04.typ @@ -0,0 +1,6 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// // Error: 25-45 expected 2 quotes, found 4 quotes +// #set smartquote(quotes: (single: ("'",) * 4)) \ No newline at end of file diff --git a/fuzzers/corpora/visualize/gradient-conic_00.typ b/fuzzers/corpora/visualize/gradient-conic_00.typ new file mode 100644 index 00000000..b9507466 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-conic_00.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#square( + size: 50pt, + fill: gradient.conic(..color.map.rainbow, space: color.hsv), +) diff --git a/fuzzers/corpora/visualize/gradient-conic_01.typ b/fuzzers/corpora/visualize/gradient-conic_01.typ new file mode 100644 index 00000000..fc650859 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-conic_01.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#square( + size: 50pt, + fill: gradient.conic(..color.map.rainbow, space: color.hsv, center: (10%, 10%)), +) diff --git a/fuzzers/corpora/visualize/gradient-conic_02.typ b/fuzzers/corpora/visualize/gradient-conic_02.typ new file mode 100644 index 00000000..9d86c075 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-conic_02.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#square( + size: 50pt, + fill: gradient.conic(..color.map.rainbow, space: color.hsv, center: (90%, 90%)), +) diff --git a/fuzzers/corpora/visualize/gradient-conic_03.typ b/fuzzers/corpora/visualize/gradient-conic_03.typ new file mode 100644 index 00000000..ebd90190 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-conic_03.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#square( + size: 50pt, + fill: gradient.conic(..color.map.rainbow, space: color.hsv, angle: 90deg), +) diff --git a/fuzzers/corpora/visualize/gradient-dir_00.typ b/fuzzers/corpora/visualize/gradient-dir_00.typ new file mode 100644 index 00000000..aafa0742 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-dir_00.typ @@ -0,0 +1,14 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set page(width: 900pt) +#for i in range(0, 360, step: 15){ + box( + height: 100pt, + width: 100pt, + fill: gradient.linear(angle: i * 1deg, (red, 0%), (blue, 100%)), + align(center + horizon)[Angle: #i degrees], + ) + h(30pt) +} diff --git a/fuzzers/corpora/visualize/gradient-presets_00.typ b/fuzzers/corpora/visualize/gradient-presets_00.typ new file mode 100644 index 00000000..183de084 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-presets_00.typ @@ -0,0 +1,34 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set page(width: 200pt, height: auto, margin: 0pt) +#set text(fill: white, size: 18pt) +#set text(top-edge: "bounds", bottom-edge: "bounds") + +#let presets = ( + ("turbo", color.map.turbo), + ("cividis", color.map.cividis), + ("rainbow", color.map.rainbow), + ("spectral", color.map.spectral), + ("viridis", color.map.viridis), + ("inferno", color.map.inferno), + ("magma", color.map.magma), + ("plasma", color.map.plasma), + ("rocket", color.map.rocket), + ("mako", color.map.mako), + ("vlag", color.map.vlag), + ("icefire", color.map.icefire), + ("flare", color.map.flare), + ("crest", color.map.crest), +) + +#stack( + spacing: 3pt, + ..presets.map(((name, preset)) => block( + width: 100%, + height: 20pt, + fill: gradient.linear(..preset), + align(center + horizon, smallcaps(name)), + )) +) diff --git a/fuzzers/corpora/visualize/gradient-radial_00.typ b/fuzzers/corpora/visualize/gradient-radial_00.typ new file mode 100644 index 00000000..a9eb2eac --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-radial_00.typ @@ -0,0 +1,9 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + + +#square( + size: 100pt, + fill: gradient.radial(..color.map.rainbow, space: color.hsl), +) \ No newline at end of file diff --git a/fuzzers/corpora/visualize/gradient-radial_01.typ b/fuzzers/corpora/visualize/gradient-radial_01.typ new file mode 100644 index 00000000..955f9eb9 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-radial_01.typ @@ -0,0 +1,24 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + + +#grid( + columns: 2, + square( + size: 50pt, + fill: gradient.radial(..color.map.rainbow, space: color.hsl, center: (0%, 0%)), + ), + square( + size: 50pt, + fill: gradient.radial(..color.map.rainbow, space: color.hsl, center: (0%, 100%)), + ), + square( + size: 50pt, + fill: gradient.radial(..color.map.rainbow, space: color.hsl, center: (100%, 0%)), + ), + square( + size: 50pt, + fill: gradient.radial(..color.map.rainbow, space: color.hsl, center: (100%, 100%)), + ), +) diff --git a/fuzzers/corpora/visualize/gradient-radial_02.typ b/fuzzers/corpora/visualize/gradient-radial_02.typ new file mode 100644 index 00000000..9ac02195 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-radial_02.typ @@ -0,0 +1,13 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + + +#square( + size: 50pt, + fill: gradient.radial(..color.map.rainbow, space: color.hsl, radius: 10%), +) +#square( + size: 50pt, + fill: gradient.radial(..color.map.rainbow, space: color.hsl, radius: 72%), +) diff --git a/fuzzers/corpora/visualize/gradient-radial_03.typ b/fuzzers/corpora/visualize/gradient-radial_03.typ new file mode 100644 index 00000000..acaca3b4 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-radial_03.typ @@ -0,0 +1,12 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#circle( + radius: 25pt, + fill: gradient.radial(white, rgb("#8fbc8f"), focal-center: (35%, 35%), focal-radius: 5%), +) +#circle( + radius: 25pt, + fill: gradient.radial(white, rgb("#8fbc8f"), focal-center: (75%, 35%), focal-radius: 5%), +) diff --git a/fuzzers/corpora/visualize/gradient-relative-conic_00.typ b/fuzzers/corpora/visualize/gradient-relative-conic_00.typ new file mode 100644 index 00000000..4d09de53 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-relative-conic_00.typ @@ -0,0 +1,16 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// The image should look as if there is a single gradient that is being used for +// both the page and the rectangles. +#let grad = gradient.conic(red, blue, green, purple, relative: "parent"); +#let my-rect = rect(width: 50%, height: 50%, fill: grad) +#set page( + height: 200pt, + width: 200pt, + fill: grad, + background: place(top + left, my-rect), +) +#place(top + right, my-rect) +#place(bottom + center, rotate(45deg, my-rect)) diff --git a/fuzzers/corpora/visualize/gradient-relative-conic_01.typ b/fuzzers/corpora/visualize/gradient-relative-conic_01.typ new file mode 100644 index 00000000..7a4a7240 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-relative-conic_01.typ @@ -0,0 +1,16 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// The image should look as if there are multiple gradients, one for each +// rectangle. +#let grad = gradient.conic(red, blue, green, purple, relative: "self"); +#let my-rect = rect(width: 50%, height: 50%, fill: grad) +#set page( + height: 200pt, + width: 200pt, + fill: grad, + background: place(top + left, my-rect), +) +#place(top + right, my-rect) +#place(bottom + center, rotate(45deg, my-rect)) diff --git a/fuzzers/corpora/visualize/gradient-relative-linear_00.typ b/fuzzers/corpora/visualize/gradient-relative-linear_00.typ new file mode 100644 index 00000000..2aef6eb4 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-relative-linear_00.typ @@ -0,0 +1,16 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// The image should look as if there is a single gradient that is being used for +// both the page and the rectangles. +#let grad = gradient.linear(red, blue, green, purple, relative: "parent"); +#let my-rect = rect(width: 50%, height: 50%, fill: grad) +#set page( + height: 200pt, + width: 200pt, + fill: grad, + background: place(top + left, my-rect), +) +#place(top + right, my-rect) +#place(bottom + center, rotate(45deg, my-rect)) diff --git a/fuzzers/corpora/visualize/gradient-relative-linear_01.typ b/fuzzers/corpora/visualize/gradient-relative-linear_01.typ new file mode 100644 index 00000000..a6eb06b2 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-relative-linear_01.typ @@ -0,0 +1,16 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// The image should look as if there are multiple gradients, one for each +// rectangle. +#let grad = gradient.linear(red, blue, green, purple, relative: "self"); +#let my-rect = rect(width: 50%, height: 50%, fill: grad) +#set page( + height: 200pt, + width: 200pt, + fill: grad, + background: place(top + left, my-rect), +) +#place(top + right, my-rect) +#place(bottom + center, rotate(45deg, my-rect)) diff --git a/fuzzers/corpora/visualize/gradient-relative-radial_00.typ b/fuzzers/corpora/visualize/gradient-relative-radial_00.typ new file mode 100644 index 00000000..4f7b0f6d --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-relative-radial_00.typ @@ -0,0 +1,16 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// The image should look as if there is a single gradient that is being used for +// both the page and the rectangles. +#let grad = gradient.radial(red, blue, green, purple, relative: "parent"); +#let my-rect = rect(width: 50%, height: 50%, fill: grad) +#set page( + height: 200pt, + width: 200pt, + fill: grad, + background: place(top + left, my-rect), +) +#place(top + right, my-rect) +#place(bottom + center, rotate(45deg, my-rect)) diff --git a/fuzzers/corpora/visualize/gradient-relative-radial_01.typ b/fuzzers/corpora/visualize/gradient-relative-radial_01.typ new file mode 100644 index 00000000..dce99026 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-relative-radial_01.typ @@ -0,0 +1,16 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// The image should look as if there are multiple gradients, one for each +// rectangle. +#let grad = gradient.radial(red, blue, green, purple, relative: "self"); +#let my-rect = rect(width: 50%, height: 50%, fill: grad) +#set page( + height: 200pt, + width: 200pt, + fill: grad, + background: place(top + left, my-rect), +) +#place(top + right, my-rect) +#place(bottom + center, rotate(45deg, my-rect)) diff --git a/fuzzers/corpora/visualize/gradient-repeat_00.typ b/fuzzers/corpora/visualize/gradient-repeat_00.typ new file mode 100644 index 00000000..430f42b4 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-repeat_00.typ @@ -0,0 +1,9 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#rect( + height: 40pt, + width: 100%, + fill: gradient.linear(..color.map.inferno).repeat(2, mirror: true) +) diff --git a/fuzzers/corpora/visualize/gradient-repeat_01.typ b/fuzzers/corpora/visualize/gradient-repeat_01.typ new file mode 100644 index 00000000..f0348dd8 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-repeat_01.typ @@ -0,0 +1,9 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#rect( + height: 40pt, + width: 100%, + fill: gradient.linear(..color.map.rainbow).repeat(2, mirror: true), +) diff --git a/fuzzers/corpora/visualize/gradient-repeat_02.typ b/fuzzers/corpora/visualize/gradient-repeat_02.typ new file mode 100644 index 00000000..a9dcf8f7 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-repeat_02.typ @@ -0,0 +1,9 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#rect( + height: 40pt, + width: 100%, + fill: gradient.linear(..color.map.rainbow).repeat(5, mirror: true) +) diff --git a/fuzzers/corpora/visualize/gradient-repeat_03.typ b/fuzzers/corpora/visualize/gradient-repeat_03.typ new file mode 100644 index 00000000..e24a7949 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-repeat_03.typ @@ -0,0 +1,9 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#rect( + height: 40pt, + width: 100%, + fill: gradient.linear(..color.map.rainbow).sharp(10).repeat(5, mirror: false) +) diff --git a/fuzzers/corpora/visualize/gradient-repeat_04.typ b/fuzzers/corpora/visualize/gradient-repeat_04.typ new file mode 100644 index 00000000..e0fbb9fd --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-repeat_04.typ @@ -0,0 +1,9 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#rect( + height: 40pt, + width: 100%, + fill: gradient.linear(..color.map.rainbow).sharp(10).repeat(5, mirror: true) +) diff --git a/fuzzers/corpora/visualize/gradient-sharp_00.typ b/fuzzers/corpora/visualize/gradient-sharp_00.typ new file mode 100644 index 00000000..ec314015 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-sharp_00.typ @@ -0,0 +1,16 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#square( + size: 100pt, + fill: gradient.linear(..color.map.rainbow, space: color.hsl).sharp(10), +) +#square( + size: 100pt, + fill: gradient.radial(..color.map.rainbow, space: color.hsl).sharp(10), +) +#square( + size: 100pt, + fill: gradient.conic(..color.map.rainbow, space: color.hsl).sharp(10), +) diff --git a/fuzzers/corpora/visualize/gradient-sharp_01.typ b/fuzzers/corpora/visualize/gradient-sharp_01.typ new file mode 100644 index 00000000..a0acaae4 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-sharp_01.typ @@ -0,0 +1,16 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#square( + size: 100pt, + fill: gradient.linear(..color.map.rainbow, space: color.hsl).sharp(10, smoothness: 40%), +) +#square( + size: 100pt, + fill: gradient.radial(..color.map.rainbow, space: color.hsl).sharp(10, smoothness: 40%), +) +#square( + size: 100pt, + fill: gradient.conic(..color.map.rainbow, space: color.hsl).sharp(10, smoothness: 40%), +) diff --git a/fuzzers/corpora/visualize/gradient-stroke_00.typ b/fuzzers/corpora/visualize/gradient-stroke_00.typ new file mode 100644 index 00000000..b655cfb5 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-stroke_00.typ @@ -0,0 +1,5 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#align(center + top, square(size: 50pt, fill: black, stroke: 5pt + gradient.linear(red, blue))) diff --git a/fuzzers/corpora/visualize/gradient-stroke_01.typ b/fuzzers/corpora/visualize/gradient-stroke_01.typ new file mode 100644 index 00000000..a0d0b7b3 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-stroke_01.typ @@ -0,0 +1,12 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#align( + center + bottom, + square( + size: 50pt, + fill: gradient.radial(red, blue, radius: 70.7%, focal-center: (10%, 10%)), + stroke: 10pt + gradient.radial(red, blue, radius: 70.7%, focal-center: (10%, 10%)) + ) +) diff --git a/fuzzers/corpora/visualize/gradient-stroke_02.typ b/fuzzers/corpora/visualize/gradient-stroke_02.typ new file mode 100644 index 00000000..1fd3b8b5 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-stroke_02.typ @@ -0,0 +1,12 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#align( + center + bottom, + square( + size: 50pt, + fill: black, + stroke: 10pt + gradient.conic(red, blue) + ) +) diff --git a/fuzzers/corpora/visualize/gradient-stroke_03.typ b/fuzzers/corpora/visualize/gradient-stroke_03.typ new file mode 100644 index 00000000..8b9a5c1d --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-stroke_03.typ @@ -0,0 +1,9 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test gradient on lines +#set page(width: 100pt, height: 100pt) +#line(length: 100%, stroke: 1pt + gradient.linear(red, blue)) +#line(length: 100%, angle: 10deg, stroke: 1pt + gradient.linear(red, blue)) +#line(length: 100%, angle: 10deg, stroke: 1pt + gradient.linear(red, blue, relative: "parent")) diff --git a/fuzzers/corpora/visualize/gradient-text-decorations_00.typ b/fuzzers/corpora/visualize/gradient-text-decorations_00.typ new file mode 100644 index 00000000..161c5dd3 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-text-decorations_00.typ @@ -0,0 +1,10 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + + +#set text(fill: gradient.linear(red, blue)) + +Hello #underline[World]! \ +Hello #overline[World]! \ +Hello #strike[World]! \ diff --git a/fuzzers/corpora/visualize/gradient-text-other_00.typ b/fuzzers/corpora/visualize/gradient-text-other_00.typ new file mode 100644 index 00000000..2351b998 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-text-other_00.typ @@ -0,0 +1,9 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set page(width: 200pt, height: auto, margin: 10pt) +#set par(justify: true) +#set text(fill: gradient.radial(red, blue)) +#lorem(30) + diff --git a/fuzzers/corpora/visualize/gradient-text-other_01.typ b/fuzzers/corpora/visualize/gradient-text-other_01.typ new file mode 100644 index 00000000..f8d0290f --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-text-other_01.typ @@ -0,0 +1,8 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set page(width: 200pt, height: auto, margin: 10pt) +#set par(justify: true) +#set text(fill: gradient.conic(red, blue, angle: 45deg)) +#lorem(30) diff --git a/fuzzers/corpora/visualize/gradient-text_00.typ b/fuzzers/corpora/visualize/gradient-text_00.typ new file mode 100644 index 00000000..12a314e1 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-text_00.typ @@ -0,0 +1,10 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page +// +// // Ref: false +// // Make sure they don't work when `relative: "self"`. +// +// // Hint: 17-61 make sure to set `relative: auto` on your text fill +// // Error: 17-61 gradients on text must be relative to the parent +// #set text(fill: gradient.linear(red, blue, relative: "self")) \ No newline at end of file diff --git a/fuzzers/corpora/visualize/gradient-text_01.typ b/fuzzers/corpora/visualize/gradient-text_01.typ new file mode 100644 index 00000000..504417c4 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-text_01.typ @@ -0,0 +1,12 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test that gradient fills on text work for globally defined gradients. + +#set page(width: 200pt, height: auto, margin: 10pt, background: { + rect(width: 100%, height: 30pt, fill: gradient.linear(red, blue)) +}) +#set par(justify: true) +#set text(fill: gradient.linear(red, blue)) +#lorem(30) diff --git a/fuzzers/corpora/visualize/gradient-text_02.typ b/fuzzers/corpora/visualize/gradient-text_02.typ new file mode 100644 index 00000000..af3ddc0b --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-text_02.typ @@ -0,0 +1,12 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Sanity check that the direction works on text. + +#set page(width: 200pt, height: auto, margin: 10pt, background: { + rect(height: 100%, width: 30pt, fill: gradient.linear(dir: btt, red, blue)) +}) +#set par(justify: true) +#set text(fill: gradient.linear(dir: btt, red, blue)) +#lorem(30) diff --git a/fuzzers/corpora/visualize/gradient-text_03.typ b/fuzzers/corpora/visualize/gradient-text_03.typ new file mode 100644 index 00000000..daa0a6fa --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-text_03.typ @@ -0,0 +1,10 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test that gradient fills on text work for locally defined gradients. + +#set page(width: auto, height: auto, margin: 10pt) +#show box: set text(fill: gradient.linear(..color.map.rainbow)) + +Hello, #box[World]! diff --git a/fuzzers/corpora/visualize/gradient-text_04.typ b/fuzzers/corpora/visualize/gradient-text_04.typ new file mode 100644 index 00000000..15520265 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-text_04.typ @@ -0,0 +1,10 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +// Test that gradients fills on text work with transforms. + +#set page(width: auto, height: auto, margin: 10pt) +#show box: set text(fill: gradient.linear(..color.map.rainbow)) + +#rotate(45deg, box[World]) diff --git a/fuzzers/corpora/visualize/gradient-transform_00.typ b/fuzzers/corpora/visualize/gradient-transform_00.typ new file mode 100644 index 00000000..7917ad65 --- /dev/null +++ b/fuzzers/corpora/visualize/gradient-transform_00.typ @@ -0,0 +1,13 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#let grad = gradient.linear(red, blue, green, purple, relative: "parent"); +#let my-rect = rect(width: 50pt, height: 50pt, fill: grad) +#set page( + height: 200pt, + width: 200pt, +) +#place(top + right, scale(x: 200%, y: 130%, my-rect)) +#place(bottom + center, rotate(45deg, my-rect)) +#place(horizon + center, scale(x: 200%, y: 130%, rotate(45deg, my-rect))) diff --git a/fuzzers/corpora/visualize/image_08.typ b/fuzzers/corpora/visualize/image_08.typ index 6566dc5d..4fb1b7b8 100644 --- a/fuzzers/corpora/visualize/image_08.typ +++ b/fuzzers/corpora/visualize/image_08.typ @@ -2,5 +2,5 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page // -// // Error: 2-25 failed to parse svg: found closing tag 'g' instead of 'style' in line 4 +// // Error: 2-25 failed to parse SVG (found closing tag 'g' instead of 'style' in line 4) // #image("/assets/files/bad.svg") \ No newline at end of file diff --git a/fuzzers/corpora/visualize/image_10.typ b/fuzzers/corpora/visualize/image_10.typ index 4a477bef..76e9a6b1 100644 --- a/fuzzers/corpora/visualize/image_10.typ +++ b/fuzzers/corpora/visualize/image_10.typ @@ -2,5 +2,5 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page // -// // Error: 2-168 failed to parse svg: missing root node +// // Error: 2-168 failed to parse SVG (missing root node) // #image.decode(``.text, format: "svg") \ No newline at end of file diff --git a/fuzzers/corpora/visualize/image_13.typ b/fuzzers/corpora/visualize/image_13.typ index a7c81d3e..de56fa55 100644 --- a/fuzzers/corpora/visualize/image_13.typ +++ b/fuzzers/corpora/visualize/image_13.typ @@ -2,5 +2,5 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page // -// // Error: 2-83 failed to decode image +// // Error: 2-83 failed to decode image (Format error decoding Png: Invalid PNG signature.) // #image.decode(read("/assets/files/tiger.jpg", encoding: none), format: "png", width: 80%) \ No newline at end of file diff --git a/fuzzers/corpora/visualize/polygon_00.typ b/fuzzers/corpora/visualize/polygon_00.typ index a274ea80..ce0887d2 100644 --- a/fuzzers/corpora/visualize/polygon_00.typ +++ b/fuzzers/corpora/visualize/polygon_00.typ @@ -9,6 +9,7 @@ #polygon() #polygon((0em, 0pt)) #polygon((0pt, 0pt), (10pt, 0pt)) +#polygon.regular(size: 0pt, vertices: 9) #polygon((5pt, 0pt), (0pt, 10pt), (10pt, 10pt)) #polygon( @@ -27,3 +28,6 @@ // Self-intersections #polygon((0pt, 10pt), (30pt, 20pt), (0pt, 30pt), (20pt, 0pt), (20pt, 35pt)) + +// Regular polygon; should have equal side lengths +#for k in range(3, 9) {polygon.regular(size: 30pt, vertices: k,)} diff --git a/fuzzers/corpora/visualize/shape-fill-stroke_02.typ b/fuzzers/corpora/visualize/shape-fill-stroke_02.typ index 99d43218..b6f3a734 100644 --- a/fuzzers/corpora/visualize/shape-fill-stroke_02.typ +++ b/fuzzers/corpora/visualize/shape-fill-stroke_02.typ @@ -5,8 +5,51 @@ // Test stroke composition. #set square(stroke: 4pt) #set text(font: "Roboto") -#square( - stroke: (left: red, top: yellow, right: green, bottom: blue), - radius: 100%, align(center+horizon)[*G*], - inset: 8pt +#stack( + dir: ltr, + square( + stroke: (left: red, top: yellow, right: green, bottom: blue), + radius: 50%, align(center+horizon)[*G*], + inset: 8pt + ), + h(0.5cm), + square( + stroke: (left: red, top: yellow + 8pt, right: green, bottom: blue + 2pt), + radius: 50%, align(center+horizon)[*G*], + inset: 8pt + ), + h(0.5cm), + square( + stroke: (left: red, top: yellow, right: green, bottom: blue), + radius: 100%, align(center+horizon)[*G*], + inset: 8pt + ), +) + +// Join between different solid strokes +#set square(size: 20pt, stroke: 2pt) +#set square(stroke: (left: green + 4pt, top: black + 2pt, right: blue, bottom: black + 2pt)) +#stack( + dir: ltr, + square(), + h(0.2cm), + square(radius: (top-left: 0pt, rest: 1pt)), + h(0.2cm), + square(radius: (top-left: 0pt, rest: 8pt)), + h(0.2cm), + square(radius: (top-left: 0pt, rest: 100pt)), +) + + +// Join between solid and dotted strokes +#set square(stroke: (left: green + 4pt, top: black + 2pt, right: (paint: blue, dash: "dotted"), bottom: (paint: black, dash: "dotted"))) +#stack( + dir: ltr, + square(), + h(0.2cm), + square(radius: (top-left: 0pt, rest: 1pt)), + h(0.2cm), + square(radius: (top-left: 0pt, rest: 8pt)), + h(0.2cm), + square(radius: (top-left: 0pt, rest: 100pt)), ) diff --git a/fuzzers/corpora/visualize/shape-rect_01.typ b/fuzzers/corpora/visualize/shape-rect_01.typ index eb205a7a..8eb6c5d3 100644 --- a/fuzzers/corpora/visualize/shape-rect_01.typ +++ b/fuzzers/corpora/visualize/shape-rect_01.typ @@ -29,7 +29,7 @@ #stack( dir: ltr, spacing: 1fr, - rect(width: 2cm, radius: 60%), + rect(width: 2cm, radius: 30%), rect(width: 1cm, radius: (left: 10pt, right: 5pt)), rect(width: 1.25cm, radius: ( top-left: 2pt, diff --git a/fuzzers/corpora/visualize/shape-rounded_00.typ b/fuzzers/corpora/visualize/shape-rounded_00.typ index dfb7573b..1f78a8fe 100644 --- a/fuzzers/corpora/visualize/shape-rounded_00.typ +++ b/fuzzers/corpora/visualize/shape-rounded_00.typ @@ -2,6 +2,53 @@ #import "/contrib/templates/std-tests/preset.typ": * #show: test-page -// Ensure that radius is clamped. -#rect(radius: -20pt) -#square(radius: 30pt) +#set square(size: 20pt, stroke: 4pt) + +// no radius for non-rounded corners +#stack( + dir: ltr, + square(), + h(10pt), + square(radius: 0pt), + h(10pt), + square(radius: -10pt), +) + +#stack( + dir: ltr, + square(), + h(10pt), + square(radius: 0%), + h(10pt), + square(radius: -10%), +) + + +// small values for small radius +#stack( + dir: ltr, + square(radius: 1pt), + h(10pt), + square(radius: 5%), + h(10pt), + square(radius: 2pt), +) + +// large values for large radius or circle +#stack( + dir: ltr, + square(radius: 8pt), + h(10pt), + square(radius: 10pt), + h(10pt), + square(radius: 12pt), +) + +#stack( + dir: ltr, + square(radius: 45%), + h(10pt), + square(radius: 50%), + h(10pt), + square(radius: 55%), +) diff --git a/fuzzers/corpora/visualize/svg-text_01.typ b/fuzzers/corpora/visualize/svg-text_01.typ new file mode 100644 index 00000000..41ec17e2 --- /dev/null +++ b/fuzzers/corpora/visualize/svg-text_01.typ @@ -0,0 +1,11 @@ + +#import "/contrib/templates/std-tests/preset.typ": * +#show: test-page + +#set page(width: 250pt) +#show image: set text(font: ("Roboto", "Noto Serif CJK SC")) + +#figure( + image("/assets/files/chinese.svg"), + caption: [Bilingual text] +) diff --git a/tests/common/src/std_artifact.rs b/tests/common/src/std_artifact.rs index cbfb45fc..341734dd 100644 --- a/tests/common/src/std_artifact.rs +++ b/tests/common/src/std_artifact.rs @@ -5,9 +5,11 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("bugs", "args-sink_00"), ("bugs", "args-underscore_00"), ("bugs", "bidi-tofus_00"), + ("bugs", "block-width-box_00"), ("bugs", "cite-locate_00"), ("bugs", "clamp-panic_00"), ("bugs", "columns-1_00"), + ("bugs", "equation-numbering-reference_00"), ("bugs", "flow-1_00"), ("bugs", "flow-2_00"), ("bugs", "flow-3_00"), @@ -21,13 +23,31 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("bugs", "grid-3_00"), ("bugs", "hide-meta_00"), ("bugs", "hide-meta_01"), + ("bugs", "layout-infinite-lengths_00"), + ("bugs", "layout-infinite-lengths_01"), + ("bugs", "layout-infinite-lengths_02"), + ("bugs", "layout-infinite-lengths_03"), ("bugs", "line-align_00"), + ("bugs", "mat-aug-color_00"), + ("bugs", "math-eval_00"), + ("bugs", "math-number-spacing_00"), ("bugs", "math-realize_00"), ("bugs", "math-realize_01"), ("bugs", "math-realize_02"), + ("bugs", "math-shift_00"), + ("bugs", "math-text-break_00"), + ("bugs", "new-cm-svg_00"), + ("bugs", "newline-mode_00"), + ("bugs", "newline-mode_01"), + ("bugs", "pagebreak-bibliography_00"), + ("bugs", "pagebreak-numbering_00"), + ("bugs", "pagebreak-set-style_00"), ("bugs", "parameter-pattern_00"), ("bugs", "place-base_00"), ("bugs", "place-pagebreak_00"), + ("bugs", "place-spacing_00"), + ("bugs", "place-spacing_01"), + ("bugs", "raw-color-overwrite_00"), ("bugs", "smartquotes-in-outline_00"), ("bugs", "smartquotes-on-newline_00"), ("bugs", "square-base_00"), @@ -39,13 +59,19 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("layout", "align_03"), ("layout", "align_04"), ("layout", "align_05"), + ("layout", "align_06"), ("layout", "block-sizing_00"), ("layout", "block-sizing_01"), ("layout", "block-spacing_00"), + ("layout", "cjk-latin-spacing_00"), + ("layout", "cjk-punctuation-adjustment_00"), + ("layout", "cjk-punctuation-adjustment_01"), + ("layout", "cjk-punctuation-adjustment_02"), ("layout", "clip_00"), ("layout", "clip_01"), ("layout", "clip_02"), ("layout", "clip_03"), + ("layout", "clip_04"), ("layout", "columns_00"), ("layout", "columns_01"), ("layout", "columns_02"), @@ -67,6 +93,7 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("layout", "enum-align_01"), ("layout", "enum-align_02"), ("layout", "enum-align_03"), + ("layout", "enum-align_04"), ("layout", "enum-numbering_00"), ("layout", "enum-numbering_01"), ("layout", "enum-numbering_02"), @@ -133,6 +160,9 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("layout", "page-margin_00"), ("layout", "page-margin_01"), ("layout", "page-marginals_00"), + ("layout", "page-number-align_00"), + ("layout", "page-number-align_01"), + ("layout", "page-number-align_02"), ("layout", "page-style_00"), ("layout", "page-style_01"), ("layout", "page-style_02"), @@ -145,6 +175,9 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("layout", "page_05"), ("layout", "pagebreak-parity_00"), ("layout", "pagebreak-parity_01"), + ("layout", "pagebreak-weak_00"), + ("layout", "pagebreak-weak_01"), + ("layout", "pagebreak-weak_02"), ("layout", "pagebreak_00"), ("layout", "pagebreak_01"), ("layout", "pagebreak_02"), @@ -173,6 +206,9 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("layout", "par-justify_02"), ("layout", "par-justify_03"), ("layout", "par-justify_04"), + ("layout", "par-justify_05"), + ("layout", "par-justify_06"), + ("layout", "par-justify_07"), ("layout", "par-knuth_00"), ("layout", "par-simple_00"), ("layout", "par_00"), @@ -183,6 +219,10 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("layout", "place-float-auto_00"), ("layout", "place-float-auto_01"), ("layout", "place-float-auto_02"), + ("layout", "place-float-auto_03"), + ("layout", "place-float-auto_04"), + ("layout", "place-float-auto_05"), + ("layout", "place-float-columns_00"), ("layout", "place-float-figure_00"), ("layout", "place-nested_00"), ("layout", "place-nested_01"), @@ -200,6 +240,7 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("layout", "spacing_01"), ("layout", "spacing_02"), ("layout", "spacing_03"), + ("layout", "spacing_04"), ("layout", "stack-1_00"), ("layout", "stack-1_01"), ("layout", "stack-1_02"), @@ -211,6 +252,7 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("layout", "table_02"), ("layout", "table_03"), ("layout", "table_04"), + ("layout", "table_05"), ("layout", "terms_00"), ("layout", "terms_01"), ("layout", "terms_02"), @@ -253,12 +295,15 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("math", "attach-p3_04"), ("math", "attach-p3_05"), ("math", "attach-p3_06"), + ("math", "block-alignment_00"), + ("math", "block-alignment_01"), ("math", "cancel_00"), ("math", "cancel_01"), ("math", "cancel_02"), ("math", "cancel_03"), ("math", "cancel_04"), ("math", "cancel_05"), + ("math", "cancel_06"), ("math", "cases_00"), ("math", "class_00"), ("math", "class_01"), @@ -288,6 +333,7 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("math", "frac_05"), ("math", "frac_06"), ("math", "frac_07"), + ("math", "frac_08"), ("math", "matrix-alignment_00"), ("math", "matrix-alignment_01"), ("math", "matrix-alignment_02"), @@ -295,6 +341,10 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("math", "matrix-alignment_04"), ("math", "matrix-alignment_05"), ("math", "matrix-alignment_06"), + ("math", "matrix-gaps_00"), + ("math", "matrix-gaps_01"), + ("math", "matrix-gaps_02"), + ("math", "matrix-gaps_03"), ("math", "matrix_00"), ("math", "matrix_01"), ("math", "matrix_02"), @@ -302,6 +352,9 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("math", "matrix_04"), ("math", "matrix_05"), ("math", "matrix_06"), + ("math", "matrix_07"), + ("math", "matrix_08"), + ("math", "matrix_09"), ("math", "multiline_00"), ("math", "multiline_01"), ("math", "multiline_02"), @@ -324,6 +377,8 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("math", "opticalsize_04"), ("math", "opticalsize_05"), ("math", "opticalsize_06"), + ("math", "opticalsize_07"), + ("math", "prime_00"), ("math", "root_00"), ("math", "root_01"), ("math", "root_02"), @@ -335,6 +390,7 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("math", "spacing_02"), ("math", "spacing_03"), ("math", "spacing_04"), + ("math", "spacing_05"), ("math", "style_00"), ("math", "style_01"), ("math", "style_02"), @@ -374,10 +430,16 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("meta", "document_05"), ("meta", "document_06"), ("meta", "document_07"), + ("meta", "document_08"), + ("meta", "figure-caption_00"), + ("meta", "figure-caption_01"), + ("meta", "figure-caption_02"), ("meta", "figure_00"), ("meta", "figure_01"), ("meta", "figure_02"), ("meta", "figure_03"), + ("meta", "figure_04"), + ("meta", "figure_05"), ("meta", "footnote-break_00"), ("meta", "footnote-columns_00"), ("meta", "footnote-container_00"), @@ -426,6 +488,7 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("meta", "outline-indent_01"), ("meta", "outline-indent_02"), ("meta", "outline_00"), + ("meta", "page-label_00"), ("meta", "query-before-after_00"), ("meta", "query-before-after_01"), ("meta", "query-figure_00"), @@ -447,6 +510,13 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("text", "deco_00"), ("text", "deco_01"), ("text", "deco_02"), + ("text", "deco_03"), + ("text", "deco_04"), + ("text", "deco_05"), + ("text", "deco_06"), + ("text", "deco_07"), + ("text", "deco_08"), + ("text", "deco_09"), ("text", "edge_00"), ("text", "edge_01"), ("text", "edge_02"), @@ -518,17 +588,37 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("text", "lorem_02"), ("text", "microtype_00"), ("text", "microtype_01"), + ("text", "numbers_00"), + ("text", "numbers_01"), + ("text", "numbers_02"), + ("text", "numbers_03"), + ("text", "numbers_04"), + ("text", "numbers_05"), + ("text", "numbers_06"), + ("text", "quote_00"), + ("text", "quote_01"), + ("text", "quote_02"), + ("text", "quote_03"), + ("text", "quote_04"), + ("text", "quote_05"), + ("text", "quote_06"), ("text", "quotes_00"), ("text", "quotes_01"), ("text", "quotes_02"), ("text", "quotes_03"), ("text", "quotes_04"), ("text", "quotes_05"), + ("text", "quotes_06"), ("text", "raw-align_00"), ("text", "raw-align_01"), ("text", "raw-align_02"), ("text", "raw-code_00"), + ("text", "raw-line_00"), + ("text", "raw-line_01"), + ("text", "raw-line_02"), + ("text", "raw-line_03"), ("text", "raw-syntaxes_00"), + ("text", "raw-tabs_00"), ("text", "raw-theme_00"), ("text", "raw_00"), ("text", "raw_01"), @@ -549,6 +639,11 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("text", "shift_00"), ("text", "shift_01"), ("text", "shift_02"), + ("text", "smartquotes_00"), + ("text", "smartquotes_01"), + ("text", "smartquotes_02"), + ("text", "smartquotes_03"), + ("text", "smartquotes_04"), ("text", "space_00"), ("text", "space_01"), ("text", "space_02"), @@ -564,6 +659,42 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("text", "tracking-spacing_03"), ("text", "tracking-spacing_04"), ("text", "tracking-spacing_05"), + ("visualize", "gradient-conic_00"), + ("visualize", "gradient-conic_01"), + ("visualize", "gradient-conic_02"), + ("visualize", "gradient-conic_03"), + ("visualize", "gradient-dir_00"), + ("visualize", "gradient-presets_00"), + ("visualize", "gradient-radial_00"), + ("visualize", "gradient-radial_01"), + ("visualize", "gradient-radial_02"), + ("visualize", "gradient-radial_03"), + ("visualize", "gradient-relative-conic_00"), + ("visualize", "gradient-relative-conic_01"), + ("visualize", "gradient-relative-linear_00"), + ("visualize", "gradient-relative-linear_01"), + ("visualize", "gradient-relative-radial_00"), + ("visualize", "gradient-relative-radial_01"), + ("visualize", "gradient-repeat_00"), + ("visualize", "gradient-repeat_01"), + ("visualize", "gradient-repeat_02"), + ("visualize", "gradient-repeat_03"), + ("visualize", "gradient-repeat_04"), + ("visualize", "gradient-sharp_00"), + ("visualize", "gradient-sharp_01"), + ("visualize", "gradient-stroke_00"), + ("visualize", "gradient-stroke_01"), + ("visualize", "gradient-stroke_02"), + ("visualize", "gradient-stroke_03"), + ("visualize", "gradient-text-decorations_00"), + ("visualize", "gradient-text-other_00"), + ("visualize", "gradient-text-other_01"), + ("visualize", "gradient-text_00"), + ("visualize", "gradient-text_01"), + ("visualize", "gradient-text_02"), + ("visualize", "gradient-text_03"), + ("visualize", "gradient-text_04"), + ("visualize", "gradient-transform_00"), ("visualize", "image_00"), ("visualize", "image_01"), ("visualize", "image_02"), @@ -625,5 +756,6 @@ pub const STD_TEST_FILES: &[(&str, &str)] = &[ ("visualize", "stroke_05"), ("visualize", "stroke_06"), ("visualize", "stroke_07"), - ("visualize", "svg-text_00") + ("visualize", "svg-text_00"), + ("visualize", "svg-text_01") ]; diff --git a/tools/rkyv-assertions/src/main.rs b/tools/rkyv-assertions/src/main.rs index af048b9c..4f2201d0 100644 --- a/tools/rkyv-assertions/src/main.rs +++ b/tools/rkyv-assertions/src/main.rs @@ -55,5 +55,6 @@ fn main() { ArchivedPage, ArchivedLayoutRegion, ArchivedBuildInfo, + ArchivedColorItem, ); }