Rename pattern to tiling (#5590)

This commit is contained in:
Laurenz 2024-12-17 10:25:27 +01:00 committed by GitHub
parent ed67220e4b
commit 1346385255
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 426 additions and 424 deletions

View File

@ -719,11 +719,7 @@ fn segment(
false false
} }
let solid = stroke let solid = stroke.dash.as_ref().map(|dash| dash.array.is_empty()).unwrap_or(true);
.dash
.as_ref()
.map(|pattern| pattern.array.is_empty())
.unwrap_or(true);
let use_fill = solid && fill_corners(start, end, corners); let use_fill = solid && fill_corners(start, end, corners);
let shape = if use_fill { let shape = if use_fill {

View File

@ -144,8 +144,8 @@ pub fn add(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
| (Length(thickness), Gradient(gradient)) => { | (Length(thickness), Gradient(gradient)) => {
Stroke::from_pair(gradient, thickness).into_value() Stroke::from_pair(gradient, thickness).into_value()
} }
(Pattern(pattern), Length(thickness)) | (Length(thickness), Pattern(pattern)) => { (Tiling(tiling), Length(thickness)) | (Length(thickness), Tiling(tiling)) => {
Stroke::from_pair(pattern, thickness).into_value() Stroke::from_pair(tiling, thickness).into_value()
} }
(Duration(a), Duration(b)) => Duration(a + b), (Duration(a), Duration(b)) => Duration(a + b),

View File

@ -20,7 +20,7 @@ use crate::foundations::{
}; };
use crate::layout::{Abs, Angle, Em, Fr, Length, Ratio, Rel}; use crate::layout::{Abs, Angle, Em, Fr, Length, Ratio, Rel};
use crate::text::{RawContent, RawElem, TextElem}; use crate::text::{RawContent, RawElem, TextElem};
use crate::visualize::{Color, Gradient, Pattern}; use crate::visualize::{Color, Gradient, Tiling};
/// A computational value. /// A computational value.
#[derive(Default, Clone)] #[derive(Default, Clone)]
@ -50,8 +50,8 @@ pub enum Value {
Color(Color), Color(Color),
/// A gradient value: `gradient.linear(...)`. /// A gradient value: `gradient.linear(...)`.
Gradient(Gradient), Gradient(Gradient),
/// A pattern fill: `pattern(...)`. /// A tiling fill: `tiling(...)`.
Pattern(Pattern), Tiling(Tiling),
/// A symbol: `arrow.l`. /// A symbol: `arrow.l`.
Symbol(Symbol), Symbol(Symbol),
/// A version. /// A version.
@ -130,7 +130,7 @@ impl Value {
Self::Fraction(_) => Type::of::<Fr>(), Self::Fraction(_) => Type::of::<Fr>(),
Self::Color(_) => Type::of::<Color>(), Self::Color(_) => Type::of::<Color>(),
Self::Gradient(_) => Type::of::<Gradient>(), Self::Gradient(_) => Type::of::<Gradient>(),
Self::Pattern(_) => Type::of::<Pattern>(), Self::Tiling(_) => Type::of::<Tiling>(),
Self::Symbol(_) => Type::of::<Symbol>(), Self::Symbol(_) => Type::of::<Symbol>(),
Self::Version(_) => Type::of::<Version>(), Self::Version(_) => Type::of::<Version>(),
Self::Str(_) => Type::of::<Str>(), Self::Str(_) => Type::of::<Str>(),
@ -244,7 +244,7 @@ impl Debug for Value {
Self::Fraction(v) => Debug::fmt(v, f), Self::Fraction(v) => Debug::fmt(v, f),
Self::Color(v) => Debug::fmt(v, f), Self::Color(v) => Debug::fmt(v, f),
Self::Gradient(v) => Debug::fmt(v, f), Self::Gradient(v) => Debug::fmt(v, f),
Self::Pattern(v) => Debug::fmt(v, f), Self::Tiling(v) => Debug::fmt(v, f),
Self::Symbol(v) => Debug::fmt(v, f), Self::Symbol(v) => Debug::fmt(v, f),
Self::Version(v) => Debug::fmt(v, f), Self::Version(v) => Debug::fmt(v, f),
Self::Str(v) => Debug::fmt(v, f), Self::Str(v) => Debug::fmt(v, f),
@ -282,7 +282,7 @@ impl Repr for Value {
Self::Fraction(v) => v.repr(), Self::Fraction(v) => v.repr(),
Self::Color(v) => v.repr(), Self::Color(v) => v.repr(),
Self::Gradient(v) => v.repr(), Self::Gradient(v) => v.repr(),
Self::Pattern(v) => v.repr(), Self::Tiling(v) => v.repr(),
Self::Symbol(v) => v.repr(), Self::Symbol(v) => v.repr(),
Self::Version(v) => v.repr(), Self::Version(v) => v.repr(),
Self::Str(v) => v.repr(), Self::Str(v) => v.repr(),
@ -333,7 +333,7 @@ impl Hash for Value {
Self::Fraction(v) => v.hash(state), Self::Fraction(v) => v.hash(state),
Self::Color(v) => v.hash(state), Self::Color(v) => v.hash(state),
Self::Gradient(v) => v.hash(state), Self::Gradient(v) => v.hash(state),
Self::Pattern(v) => v.hash(state), Self::Tiling(v) => v.hash(state),
Self::Symbol(v) => v.hash(state), Self::Symbol(v) => v.hash(state),
Self::Version(v) => v.hash(state), Self::Version(v) => v.hash(state),
Self::Str(v) => v.hash(state), Self::Str(v) => v.hash(state),
@ -640,7 +640,7 @@ primitive! { Rel<Length>: "relative length",
primitive! { Fr: "fraction", Fraction } primitive! { Fr: "fraction", Fraction }
primitive! { Color: "color", Color } primitive! { Color: "color", Color }
primitive! { Gradient: "gradient", Gradient } primitive! { Gradient: "gradient", Gradient }
primitive! { Pattern: "pattern", Pattern } primitive! { Tiling: "tiling", Tiling }
primitive! { Symbol: "symbol", Symbol } primitive! { Symbol: "symbol", Symbol }
primitive! { Version: "version", Version } primitive! { Version: "version", Version }
primitive! { primitive! {

View File

@ -161,7 +161,7 @@ impl<'a> Locator<'a> {
/// ///
/// Should typically only be created at the document level, though there /// Should typically only be created at the document level, though there
/// are a few places where we use it as well that just don't support /// are a few places where we use it as well that just don't support
/// introspection (e.g. drawable patterns). /// introspection (e.g. tilings).
pub fn root() -> Self { pub fn root() -> Self {
Self { local: 0, outer: None } Self { local: 0, outer: None }
} }

View File

@ -249,7 +249,7 @@ pub struct TextElem {
if paint.v.relative() == Smart::Custom(RelativeTo::Self_) { if paint.v.relative() == Smart::Custom(RelativeTo::Self_) {
bail!( bail!(
paint.span, paint.span,
"gradients and patterns on text must be relative to the parent"; "gradients and tilings on text must be relative to the parent";
hint: "make sure to set `relative: auto` on your text fill" hint: "make sure to set `relative: auto` on your text fill"
); );
} }

View File

@ -6,10 +6,10 @@ mod image;
mod line; mod line;
mod paint; mod paint;
mod path; mod path;
mod pattern;
mod polygon; mod polygon;
mod shape; mod shape;
mod stroke; mod stroke;
mod tiling;
pub use self::color::*; pub use self::color::*;
pub use self::gradient::*; pub use self::gradient::*;
@ -17,12 +17,12 @@ pub use self::image::*;
pub use self::line::*; pub use self::line::*;
pub use self::paint::*; pub use self::paint::*;
pub use self::path::*; pub use self::path::*;
pub use self::pattern::*;
pub use self::polygon::*; pub use self::polygon::*;
pub use self::shape::*; pub use self::shape::*;
pub use self::stroke::*; pub use self::stroke::*;
pub use self::tiling::*;
use crate::foundations::{category, Category, Scope}; use crate::foundations::{category, Category, Scope, Type};
/// Drawing and data visualization. /// Drawing and data visualization.
/// ///
@ -37,7 +37,7 @@ pub(super) fn define(global: &mut Scope) {
global.category(VISUALIZE); global.category(VISUALIZE);
global.define_type::<Color>(); global.define_type::<Color>();
global.define_type::<Gradient>(); global.define_type::<Gradient>();
global.define_type::<Pattern>(); global.define_type::<Tiling>();
global.define_type::<Stroke>(); global.define_type::<Stroke>();
global.define_elem::<ImageElem>(); global.define_elem::<ImageElem>();
global.define_elem::<LineElem>(); global.define_elem::<LineElem>();
@ -47,4 +47,7 @@ pub(super) fn define(global: &mut Scope) {
global.define_elem::<CircleElem>(); global.define_elem::<CircleElem>();
global.define_elem::<PolygonElem>(); global.define_elem::<PolygonElem>();
global.define_elem::<PathElem>(); global.define_elem::<PathElem>();
// Compatibility.
global.define("pattern", Type::of::<Tiling>());
} }

View File

@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter};
use ecow::EcoString; use ecow::EcoString;
use crate::foundations::{cast, Repr, Smart}; use crate::foundations::{cast, Repr, Smart};
use crate::visualize::{Color, Gradient, Pattern, RelativeTo}; use crate::visualize::{Color, Gradient, RelativeTo, Tiling};
/// How a fill or stroke should be painted. /// How a fill or stroke should be painted.
#[derive(Clone, Eq, PartialEq, Hash)] #[derive(Clone, Eq, PartialEq, Hash)]
@ -12,8 +12,8 @@ pub enum Paint {
Solid(Color), Solid(Color),
/// A gradient. /// A gradient.
Gradient(Gradient), Gradient(Gradient),
/// A pattern. /// A tiling.
Pattern(Pattern), Tiling(Tiling),
} }
impl Paint { impl Paint {
@ -21,7 +21,7 @@ impl Paint {
pub fn unwrap_solid(&self) -> Color { pub fn unwrap_solid(&self) -> Color {
match self { match self {
Self::Solid(color) => *color, Self::Solid(color) => *color,
Self::Gradient(_) | Self::Pattern(_) => panic!("expected solid color"), Self::Gradient(_) | Self::Tiling(_) => panic!("expected solid color"),
} }
} }
@ -30,7 +30,7 @@ impl Paint {
match self { match self {
Self::Solid(_) => Smart::Auto, Self::Solid(_) => Smart::Auto,
Self::Gradient(gradient) => gradient.relative(), Self::Gradient(gradient) => gradient.relative(),
Self::Pattern(pattern) => pattern.relative(), Self::Tiling(tiling) => tiling.relative(),
} }
} }
@ -44,8 +44,8 @@ impl Paint {
Self::Gradient(gradient) => { Self::Gradient(gradient) => {
Self::Gradient(gradient.clone().with_relative(RelativeTo::Parent)) Self::Gradient(gradient.clone().with_relative(RelativeTo::Parent))
} }
Self::Pattern(pattern) => { Self::Tiling(tiling) => {
Self::Pattern(pattern.clone().with_relative(RelativeTo::Parent)) Self::Tiling(tiling.clone().with_relative(RelativeTo::Parent))
} }
} }
} }
@ -56,14 +56,14 @@ impl Debug for Paint {
match self { match self {
Self::Solid(v) => v.fmt(f), Self::Solid(v) => v.fmt(f),
Self::Gradient(v) => v.fmt(f), Self::Gradient(v) => v.fmt(f),
Self::Pattern(v) => v.fmt(f), Self::Tiling(v) => v.fmt(f),
} }
} }
} }
impl From<Pattern> for Paint { impl From<Tiling> for Paint {
fn from(pattern: Pattern) -> Self { fn from(tiling: Tiling) -> Self {
Self::Pattern(pattern) Self::Tiling(tiling)
} }
} }
@ -72,7 +72,7 @@ impl Repr for Paint {
match self { match self {
Self::Solid(color) => color.repr(), Self::Solid(color) => color.repr(),
Self::Gradient(gradient) => gradient.repr(), Self::Gradient(gradient) => gradient.repr(),
Self::Pattern(pattern) => pattern.repr(), Self::Tiling(tiling) => tiling.repr(),
} }
} }
} }
@ -94,9 +94,9 @@ cast! {
self => match self { self => match self {
Self::Solid(color) => color.into_value(), Self::Solid(color) => color.into_value(),
Self::Gradient(gradient) => gradient.into_value(), Self::Gradient(gradient) => gradient.into_value(),
Self::Pattern(pattern) => pattern.into_value(), Self::Tiling(tiling) => tiling.into_value(),
}, },
color: Color => Self::Solid(color), color: Color => Self::Solid(color),
gradient: Gradient => Self::Gradient(gradient), gradient: Gradient => Self::Gradient(gradient),
pattern: Pattern => Self::Pattern(pattern), tiling: Tiling => Self::Tiling(tiling),
} }

View File

@ -7,7 +7,7 @@ use crate::foundations::{
Resolve, Smart, StyleChain, Value, Resolve, Smart, StyleChain, Value,
}; };
use crate::layout::{Abs, Length}; use crate::layout::{Abs, Length};
use crate::visualize::{Color, Gradient, Paint, Pattern}; use crate::visualize::{Color, Gradient, Paint, Tiling};
/// Defines how to draw a line. /// Defines how to draw a line.
/// ///
@ -213,9 +213,9 @@ impl<T: Numeric> Stroke<T> {
thickness: self.thickness.map(&f), thickness: self.thickness.map(&f),
cap: self.cap, cap: self.cap,
join: self.join, join: self.join,
dash: self.dash.map(|pattern| { dash: self.dash.map(|dash| {
pattern.map(|pattern| DashPattern { dash.map(|dash| DashPattern {
array: pattern array: dash
.array .array
.into_iter() .into_iter()
.map(|l| match l { .map(|l| match l {
@ -223,7 +223,7 @@ impl<T: Numeric> Stroke<T> {
DashLength::LineWidth => DashLength::LineWidth, DashLength::LineWidth => DashLength::LineWidth,
}) })
.collect(), .collect(),
phase: f(pattern.phase), phase: f(dash.phase),
}) })
}), }),
miter_limit: self.miter_limit, miter_limit: self.miter_limit,
@ -237,14 +237,10 @@ impl Stroke<Abs> {
let thickness = self.thickness.unwrap_or(default.thickness); let thickness = self.thickness.unwrap_or(default.thickness);
let dash = self let dash = self
.dash .dash
.map(|pattern| { .map(|dash| {
pattern.map(|pattern| DashPattern { dash.map(|dash| DashPattern {
array: pattern array: dash.array.into_iter().map(|l| l.finish(thickness)).collect(),
.array phase: dash.phase,
.into_iter()
.map(|l| l.finish(thickness))
.collect(),
phase: pattern.phase,
}) })
}) })
.unwrap_or(default.dash); .unwrap_or(default.dash);
@ -372,8 +368,8 @@ cast! {
paint: Smart::Custom(gradient.into()), paint: Smart::Custom(gradient.into()),
..Default::default() ..Default::default()
}, },
pattern: Pattern => Self { tiling: Tiling => Self {
paint: Smart::Custom(pattern.into()), paint: Smart::Custom(tiling.into()),
..Default::default() ..Default::default()
}, },
mut dict: Dict => { mut dict: Dict => {

View File

@ -13,18 +13,18 @@ use crate::layout::{Abs, Axes, Frame, Length, Region, Size};
use crate::visualize::RelativeTo; use crate::visualize::RelativeTo;
use crate::World; use crate::World;
/// A repeating pattern fill. /// A repeating tiling fill.
/// ///
/// Typst supports the most common pattern type of tiled patterns, where a /// Typst supports the most common type of tilings, where a pattern is repeated
/// pattern is repeated in a grid-like fashion, covering the entire area of an /// in a grid-like fashion, covering the entire area of an element that is
/// element that is filled or stroked. The pattern is defined by a tile size and /// filled or stroked. The pattern is defined by a tile size and a body defining
/// a body defining the content of each cell. You can also add horizontal or /// the content of each cell. You can also add horizontal or vertical spacing
/// vertical spacing between the cells of the pattern. /// between the cells of the tiling.
/// ///
/// # Examples /// # Examples
/// ///
/// ```example /// ```example
/// #let pat = pattern(size: (30pt, 30pt))[ /// #let pat = tiling(size: (30pt, 30pt))[
/// #place(line(start: (0%, 0%), end: (100%, 100%))) /// #place(line(start: (0%, 0%), end: (100%, 100%)))
/// #place(line(start: (0%, 100%), end: (100%, 0%))) /// #place(line(start: (0%, 100%), end: (100%, 0%)))
/// ] /// ]
@ -32,14 +32,14 @@ use crate::World;
/// #rect(fill: pat, width: 100%, height: 60pt, stroke: 1pt) /// #rect(fill: pat, width: 100%, height: 60pt, stroke: 1pt)
/// ``` /// ```
/// ///
/// Patterns are also supported on text, but only when setting the /// Tilings are also supported on text, but only when setting the
/// [relativeness]($pattern.relative) to either `{auto}` (the default value) or /// [relativeness]($tiling.relative) to either `{auto}` (the default value) or
/// `{"parent"}`. To create word-by-word or glyph-by-glyph patterns, you can /// `{"parent"}`. To create word-by-word or glyph-by-glyph tilings, you can
/// wrap the words or characters of your text in [boxes]($box) manually or /// wrap the words or characters of your text in [boxes]($box) manually or
/// through a [show rule]($styling/#show-rules). /// through a [show rule]($styling/#show-rules).
/// ///
/// ```example /// ```example
/// #let pat = pattern( /// #let pat = tiling(
/// size: (30pt, 30pt), /// size: (30pt, 30pt),
/// relative: "parent", /// relative: "parent",
/// square( /// square(
@ -54,13 +54,13 @@ use crate::World;
/// ``` /// ```
/// ///
/// You can also space the elements further or closer apart using the /// You can also space the elements further or closer apart using the
/// [`spacing`]($pattern.spacing) feature of the pattern. If the spacing /// [`spacing`]($tiling.spacing) feature of the tiling. If the spacing
/// is lower than the size of the pattern, the pattern will overlap. /// is lower than the size of the tiling, the tiling will overlap.
/// If it is higher, the pattern will have gaps of the same color as the /// If it is higher, the tiling will have gaps of the same color as the
/// background of the pattern. /// background of the tiling.
/// ///
/// ```example /// ```example
/// #let pat = pattern( /// #let pat = tiling(
/// size: (30pt, 30pt), /// size: (30pt, 30pt),
/// spacing: (10pt, 10pt), /// spacing: (10pt, 10pt),
/// relative: "parent", /// relative: "parent",
@ -79,11 +79,11 @@ use crate::World;
/// ``` /// ```
/// ///
/// # Relativeness /// # Relativeness
/// The location of the starting point of the pattern is dependent on the /// The location of the starting point of the tiling is dependent on the
/// dimensions of a container. This container can either be the shape that it is /// dimensions of a container. This container can either be the shape that it is
/// being painted on, or the closest surrounding container. This is controlled /// being painted on, or the closest surrounding container. This is controlled
/// by the `relative` argument of a pattern constructor. By default, patterns /// by the `relative` argument of a tiling constructor. By default, tilings
/// are relative to the shape they are being painted on, unless the pattern is /// are relative to the shape they are being painted on, unless the tiling is
/// applied on text, in which case they are relative to the closest ancestor /// applied on text, in which case they are relative to the closest ancestor
/// container. /// container.
/// ///
@ -94,29 +94,33 @@ use crate::World;
/// contains the shape. This includes the boxes and blocks that are implicitly /// contains the shape. This includes the boxes and blocks that are implicitly
/// created by show rules and elements. For example, a [`rotate`] will not /// created by show rules and elements. For example, a [`rotate`] will not
/// affect the parent of a gradient, but a [`grid`] will. /// affect the parent of a gradient, but a [`grid`] will.
#[ty(scope, cast)] ///
/// # Compatibility
/// This type used to be called `pattern`. The name remains as an alias, but is
/// deprecated since Typst 0.13.
#[ty(scope, cast, keywords = ["pattern"])]
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Pattern(Arc<Repr>); pub struct Tiling(Arc<Repr>);
/// Internal representation of [`Pattern`]. /// Internal representation of [`Tiling`].
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
struct Repr { struct Repr {
/// The pattern's rendered content. /// The tiling's rendered content.
frame: LazyHash<Frame>, frame: LazyHash<Frame>,
/// The pattern's tile size. /// The tiling's tile size.
size: Size, size: Size,
/// The pattern's tile spacing. /// The tiling's tile spacing.
spacing: Size, spacing: Size,
/// The pattern's relative transform. /// The tiling's relative transform.
relative: Smart<RelativeTo>, relative: Smart<RelativeTo>,
} }
#[scope] #[scope]
impl Pattern { impl Tiling {
/// Construct a new pattern. /// Construct a new tiling.
/// ///
/// ```example /// ```example
/// #let pat = pattern( /// #let pat = tiling(
/// size: (20pt, 20pt), /// size: (20pt, 20pt),
/// relative: "parent", /// relative: "parent",
/// place( /// place(
@ -136,15 +140,15 @@ impl Pattern {
engine: &mut Engine, engine: &mut Engine,
/// The callsite span. /// The callsite span.
span: Span, span: Span,
/// The bounding box of each cell of the pattern. /// The bounding box of each cell of the tiling.
#[named] #[named]
#[default(Spanned::new(Smart::Auto, Span::detached()))] #[default(Spanned::new(Smart::Auto, Span::detached()))]
size: Spanned<Smart<Axes<Length>>>, size: Spanned<Smart<Axes<Length>>>,
/// The spacing between cells of the pattern. /// The spacing between cells of the tiling.
#[named] #[named]
#[default(Spanned::new(Axes::splat(Length::zero()), Span::detached()))] #[default(Spanned::new(Axes::splat(Length::zero()), Span::detached()))]
spacing: Spanned<Axes<Length>>, spacing: Spanned<Axes<Length>>,
/// The [relative placement](#relativeness) of the pattern. /// The [relative placement](#relativeness) of the tiling.
/// ///
/// For an element placed at the root/top level of the document, the /// For an element placed at the root/top level of the document, the
/// parent is the page itself. For other elements, the parent is the /// parent is the page itself. For other elements, the parent is the
@ -153,14 +157,14 @@ impl Pattern {
#[named] #[named]
#[default(Smart::Auto)] #[default(Smart::Auto)]
relative: Smart<RelativeTo>, relative: Smart<RelativeTo>,
/// The content of each cell of the pattern. /// The content of each cell of the tiling.
body: Content, body: Content,
) -> SourceResult<Pattern> { ) -> SourceResult<Tiling> {
let size_span = size.span; let size_span = size.span;
if let Smart::Custom(size) = size.v { if let Smart::Custom(size) = size.v {
// Ensure that sizes are absolute. // Ensure that sizes are absolute.
if !size.x.em.is_zero() || !size.y.em.is_zero() { if !size.x.em.is_zero() || !size.y.em.is_zero() {
bail!(size_span, "pattern tile size must be absolute"); bail!(size_span, "tile size must be absolute");
} }
// Ensure that sizes are non-zero and finite. // Ensure that sizes are non-zero and finite.
@ -169,25 +173,25 @@ impl Pattern {
|| !size.x.is_finite() || !size.x.is_finite()
|| !size.y.is_finite() || !size.y.is_finite()
{ {
bail!(size_span, "pattern tile size must be non-zero and non-infinite"); bail!(size_span, "tile size must be non-zero and non-infinite");
} }
} }
// Ensure that spacing is absolute. // Ensure that spacing is absolute.
if !spacing.v.x.em.is_zero() || !spacing.v.y.em.is_zero() { if !spacing.v.x.em.is_zero() || !spacing.v.y.em.is_zero() {
bail!(spacing.span, "pattern tile spacing must be absolute"); bail!(spacing.span, "tile spacing must be absolute");
} }
// Ensure that spacing is finite. // Ensure that spacing is finite.
if !spacing.v.x.is_finite() || !spacing.v.y.is_finite() { if !spacing.v.x.is_finite() || !spacing.v.y.is_finite() {
bail!(spacing.span, "pattern tile spacing must be finite"); bail!(spacing.span, "tile spacing must be finite");
} }
// The size of the frame // The size of the frame
let size = size.v.map(|l| l.map(|a| a.abs)); let size = size.v.map(|l| l.map(|a| a.abs));
let region = size.unwrap_or_else(|| Axes::splat(Abs::inf())); let region = size.unwrap_or_else(|| Axes::splat(Abs::inf()));
// Layout the pattern. // Layout the tiling.
let world = engine.world; let world = engine.world;
let library = world.library(); let library = world.library();
let locator = Locator::root(); let locator = Locator::root();
@ -204,7 +208,7 @@ impl Pattern {
// Check that the frame is non-zero. // Check that the frame is non-zero.
if frame.width().is_zero() || frame.height().is_zero() { if frame.width().is_zero() || frame.height().is_zero() {
bail!( bail!(
span, "pattern tile size must be non-zero"; span, "tile size must be non-zero";
hint: "try setting the size manually" hint: "try setting the size manually"
); );
} }
@ -218,8 +222,8 @@ impl Pattern {
} }
} }
impl Pattern { impl Tiling {
/// Set the relative placement of the pattern. /// Set the relative placement of the tiling.
pub fn with_relative(mut self, relative: RelativeTo) -> Self { pub fn with_relative(mut self, relative: RelativeTo) -> Self {
if let Some(this) = Arc::get_mut(&mut self.0) { if let Some(this) = Arc::get_mut(&mut self.0) {
this.relative = Smart::Custom(relative); this.relative = Smart::Custom(relative);
@ -233,27 +237,27 @@ impl Pattern {
self self
} }
/// Return the frame of the pattern. /// Return the frame of the tiling.
pub fn frame(&self) -> &Frame { pub fn frame(&self) -> &Frame {
&self.0.frame &self.0.frame
} }
/// Return the size of the pattern in absolute units. /// Return the size of the tiling in absolute units.
pub fn size(&self) -> Size { pub fn size(&self) -> Size {
self.0.size self.0.size
} }
/// Return the spacing of the pattern in absolute units. /// Return the spacing of the tiling in absolute units.
pub fn spacing(&self) -> Size { pub fn spacing(&self) -> Size {
self.0.spacing self.0.spacing
} }
/// Returns the relative placement of the pattern. /// Returns the relative placement of the tiling.
pub fn relative(&self) -> Smart<RelativeTo> { pub fn relative(&self) -> Smart<RelativeTo> {
self.0.relative self.0.relative
} }
/// Returns the relative placement of the pattern. /// Returns the relative placement of the tiling.
pub fn unwrap_relative(&self, on_text: bool) -> RelativeTo { pub fn unwrap_relative(&self, on_text: bool) -> RelativeTo {
self.0.relative.unwrap_or_else(|| { self.0.relative.unwrap_or_else(|| {
if on_text { if on_text {
@ -265,10 +269,10 @@ impl Pattern {
} }
} }
impl repr::Repr for Pattern { impl repr::Repr for Tiling {
fn repr(&self) -> EcoString { fn repr(&self) -> EcoString {
let mut out = let mut out =
eco_format!("pattern(({}, {})", self.0.size.x.repr(), self.0.size.y.repr()); eco_format!("tiling(({}, {})", self.0.size.x.repr(), self.0.size.y.repr());
if self.0.spacing.is_zero() { if self.0.spacing.is_zero() {
out.push_str(", spacing: ("); out.push_str(", spacing: (");

View File

@ -222,7 +222,7 @@ impl PaintEncode for Paint {
match self { match self {
Self::Solid(c) => c.set_as_fill(ctx, on_text, transforms), Self::Solid(c) => c.set_as_fill(ctx, on_text, transforms),
Self::Gradient(gradient) => gradient.set_as_fill(ctx, on_text, transforms), Self::Gradient(gradient) => gradient.set_as_fill(ctx, on_text, transforms),
Self::Pattern(pattern) => pattern.set_as_fill(ctx, on_text, transforms), Self::Tiling(tiling) => tiling.set_as_fill(ctx, on_text, transforms),
} }
} }
@ -235,7 +235,7 @@ impl PaintEncode for Paint {
match self { match self {
Self::Solid(c) => c.set_as_stroke(ctx, on_text, transforms), Self::Solid(c) => c.set_as_stroke(ctx, on_text, transforms),
Self::Gradient(gradient) => gradient.set_as_stroke(ctx, on_text, transforms), Self::Gradient(gradient) => gradient.set_as_stroke(ctx, on_text, transforms),
Self::Pattern(pattern) => pattern.set_as_stroke(ctx, on_text, transforms), Self::Tiling(tiling) => tiling.set_as_stroke(ctx, on_text, transforms),
} }
} }
} }

View File

@ -1,6 +1,6 @@
//! Generic writer for PDF content. //! Generic writer for PDF content.
//! //!
//! It is used to write page contents, color glyph instructions, and patterns. //! It is used to write page contents, color glyph instructions, and tilings.
//! //!
//! See also [`pdf_writer::Content`]. //! See also [`pdf_writer::Content`].
@ -96,7 +96,7 @@ pub struct Encoded {
/// objects only through resources. /// objects only through resources.
/// ///
/// Content streams can be used for page contents, but also to describe color /// Content streams can be used for page contents, but also to describe color
/// glyphs and patterns. /// glyphs and tilings.
pub struct Builder<'a, R = ()> { pub struct Builder<'a, R = ()> {
/// Settings for PDF export. /// Settings for PDF export.
pub(crate) options: &'a PdfOptions<'a>, pub(crate) options: &'a PdfOptions<'a>,
@ -187,7 +187,7 @@ impl State {
} }
} }
/// Subset of the state used to calculate the transform of gradients and patterns. /// Subset of the state used to calculate the transform of gradients and tilings.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(super) struct Transforms { pub(super) struct Transforms {
/// The transform of the current item. /// The transform of the current item.
@ -229,7 +229,7 @@ impl Builder<'_, ()> {
let get_opacity = |paint: &Paint| { let get_opacity = |paint: &Paint| {
let color = match paint { let color = match paint {
Paint::Solid(color) => *color, Paint::Solid(color) => *color,
Paint::Gradient(_) | Paint::Pattern(_) => return 255, Paint::Gradient(_) | Paint::Tiling(_) => return 255,
}; };
color.alpha().map_or(255, |v| (v * 255.0).round() as u8) color.alpha().map_or(255, |v| (v * 255.0).round() as u8)
@ -330,10 +330,10 @@ impl Builder<'_, ()> {
self.content.set_line_join(to_pdf_line_join(*join)); self.content.set_line_join(to_pdf_line_join(*join));
} }
if self.state.stroke.as_ref().map(|s| &s.dash) != Some(dash) { if self.state.stroke.as_ref().map(|s| &s.dash) != Some(dash) {
if let Some(pattern) = dash { if let Some(dash) = dash {
self.content.set_dash_pattern( self.content.set_dash_pattern(
pattern.array.iter().map(|l| l.to_f32()), dash.array.iter().map(|l| l.to_f32()),
pattern.phase.to_f32(), dash.phase.to_f32(),
); );
} else { } else {
self.content.set_dash_pattern([], 0.0); self.content.set_dash_pattern([], 0.0);

View File

@ -11,8 +11,8 @@ mod image;
mod named_destination; mod named_destination;
mod outline; mod outline;
mod page; mod page;
mod pattern;
mod resources; mod resources;
mod tiling;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
@ -39,10 +39,10 @@ use crate::gradient::{write_gradients, PdfGradient};
use crate::image::write_images; use crate::image::write_images;
use crate::named_destination::{write_named_destinations, NamedDestinations}; use crate::named_destination::{write_named_destinations, NamedDestinations};
use crate::page::{alloc_page_refs, traverse_pages, write_page_tree, EncodedPage}; use crate::page::{alloc_page_refs, traverse_pages, write_page_tree, EncodedPage};
use crate::pattern::{write_patterns, PdfPattern};
use crate::resources::{ use crate::resources::{
alloc_resources_refs, write_resource_dictionaries, Resources, ResourcesRefs, alloc_resources_refs, write_resource_dictionaries, Resources, ResourcesRefs,
}; };
use crate::tiling::{write_tilings, PdfTiling};
/// Export a document into a PDF file. /// Export a document into a PDF file.
/// ///
@ -65,7 +65,7 @@ pub fn pdf(document: &PagedDocument, options: &PdfOptions) -> SourceResult<Vec<u
color_fonts: builder.run(write_color_fonts)?, color_fonts: builder.run(write_color_fonts)?,
images: builder.run(write_images)?, images: builder.run(write_images)?,
gradients: builder.run(write_gradients)?, gradients: builder.run(write_gradients)?,
patterns: builder.run(write_patterns)?, tilings: builder.run(write_tilings)?,
ext_gs: builder.run(write_graphic_states)?, ext_gs: builder.run(write_graphic_states)?,
}) })
})? })?
@ -267,8 +267,8 @@ struct References {
images: HashMap<Image, Ref>, images: HashMap<Image, Ref>,
/// The IDs of written gradients. /// The IDs of written gradients.
gradients: HashMap<PdfGradient, Ref>, gradients: HashMap<PdfGradient, Ref>,
/// The IDs of written patterns. /// The IDs of written tilings.
patterns: HashMap<PdfPattern, Ref>, tilings: HashMap<PdfTiling, Ref>,
/// The IDs of written external graphics states. /// The IDs of written external graphics states.
ext_gs: HashMap<ExtGState, Ref>, ext_gs: HashMap<ExtGState, Ref>,
} }

View File

@ -23,7 +23,7 @@ use crate::color_font::ColorFontMap;
use crate::extg::ExtGState; use crate::extg::ExtGState;
use crate::gradient::PdfGradient; use crate::gradient::PdfGradient;
use crate::image::EncodedImage; use crate::image::EncodedImage;
use crate::pattern::PatternRemapper; use crate::tiling::TilingRemapper;
use crate::{PdfChunk, Renumber, WithEverything, WithResources}; use crate::{PdfChunk, Renumber, WithEverything, WithResources};
/// All the resources that have been collected when traversing the document. /// All the resources that have been collected when traversing the document.
@ -31,16 +31,16 @@ use crate::{PdfChunk, Renumber, WithEverything, WithResources};
/// This does not allocate references to resources, only track what was used /// This does not allocate references to resources, only track what was used
/// and deduplicate what can be deduplicated. /// and deduplicate what can be deduplicated.
/// ///
/// You may notice that this structure is a tree: [`PatternRemapper`] and /// You may notice that this structure is a tree: [`TilingRemapper`] and
/// [`ColorFontMap`] (that are present in the fields of [`Resources`]), /// [`ColorFontMap`] (that are present in the fields of [`Resources`]),
/// themselves contain [`Resources`] (that will be called "sub-resources" from /// themselves contain [`Resources`] (that will be called "sub-resources" from
/// now on). Because color glyphs and patterns are defined using content /// now on). Because color glyphs and tilings are defined using content
/// streams, just like pages, they can refer to resources too, which are tracked /// streams, just like pages, they can refer to resources too, which are tracked
/// by the respective sub-resources. /// by the respective sub-resources.
/// ///
/// Each instance of this structure will become a `/Resources` dictionary in /// Each instance of this structure will become a `/Resources` dictionary in
/// the final PDF. It is not possible to use a single shared dictionary for all /// the final PDF. It is not possible to use a single shared dictionary for all
/// pages, patterns and color fonts, because if a resource is listed in its own /// pages, tilings and color fonts, because if a resource is listed in its own
/// `/Resources` dictionary, some PDF readers will fail to open the document. /// `/Resources` dictionary, some PDF readers will fail to open the document.
/// ///
/// Because we need to lazily initialize sub-resources (we don't know how deep /// Because we need to lazily initialize sub-resources (we don't know how deep
@ -66,8 +66,8 @@ pub struct Resources<R = Ref> {
pub deferred_images: HashMap<usize, (Deferred<StrResult<EncodedImage>>, Span)>, pub deferred_images: HashMap<usize, (Deferred<StrResult<EncodedImage>>, Span)>,
/// Deduplicates gradients used across the document. /// Deduplicates gradients used across the document.
pub gradients: Remapper<PdfGradient>, pub gradients: Remapper<PdfGradient>,
/// Deduplicates patterns used across the document. /// Deduplicates tilings used across the document.
pub patterns: Option<Box<PatternRemapper<R>>>, pub tilings: Option<Box<TilingRemapper<R>>>,
/// Deduplicates external graphics states used across the document. /// Deduplicates external graphics states used across the document.
pub ext_gs: Remapper<ExtGState>, pub ext_gs: Remapper<ExtGState>,
/// Deduplicates color glyphs. /// Deduplicates color glyphs.
@ -107,8 +107,8 @@ impl<R: Renumber> Renumber for Resources<R> {
color_fonts.resources.renumber(offset); color_fonts.resources.renumber(offset);
} }
if let Some(patterns) = &mut self.patterns { if let Some(tilings) = &mut self.tilings {
patterns.resources.renumber(offset); tilings.resources.renumber(offset);
} }
} }
} }
@ -122,7 +122,7 @@ impl Default for Resources<()> {
images: Remapper::new("Im"), images: Remapper::new("Im"),
deferred_images: HashMap::new(), deferred_images: HashMap::new(),
gradients: Remapper::new("Gr"), gradients: Remapper::new("Gr"),
patterns: None, tilings: None,
ext_gs: Remapper::new("Gs"), ext_gs: Remapper::new("Gs"),
color_fonts: None, color_fonts: None,
languages: BTreeMap::new(), languages: BTreeMap::new(),
@ -144,9 +144,9 @@ impl Resources<()> {
images: self.images, images: self.images,
deferred_images: self.deferred_images, deferred_images: self.deferred_images,
gradients: self.gradients, gradients: self.gradients,
patterns: self tilings: self
.patterns .tilings
.zip(refs.patterns.as_ref()) .zip(refs.tilings.as_ref())
.map(|(p, r)| Box::new(p.with_refs(r))), .map(|(p, r)| Box::new(p.with_refs(r))),
ext_gs: self.ext_gs, ext_gs: self.ext_gs,
color_fonts: self color_fonts: self
@ -172,8 +172,8 @@ impl<R> Resources<R> {
if let Some(color_fonts) = &self.color_fonts { if let Some(color_fonts) = &self.color_fonts {
color_fonts.resources.traverse(process)?; color_fonts.resources.traverse(process)?;
} }
if let Some(patterns) = &self.patterns { if let Some(tilings) = &self.tilings {
patterns.resources.traverse(process)?; tilings.resources.traverse(process)?;
} }
Ok(()) Ok(())
} }
@ -186,7 +186,7 @@ impl<R> Resources<R> {
pub struct ResourcesRefs { pub struct ResourcesRefs {
pub reference: Ref, pub reference: Ref,
pub color_fonts: Option<Box<ResourcesRefs>>, pub color_fonts: Option<Box<ResourcesRefs>>,
pub patterns: Option<Box<ResourcesRefs>>, pub tilings: Option<Box<ResourcesRefs>>,
} }
impl Renumber for ResourcesRefs { impl Renumber for ResourcesRefs {
@ -195,8 +195,8 @@ impl Renumber for ResourcesRefs {
if let Some(color_fonts) = &mut self.color_fonts { if let Some(color_fonts) = &mut self.color_fonts {
color_fonts.renumber(offset); color_fonts.renumber(offset);
} }
if let Some(patterns) = &mut self.patterns { if let Some(tilings) = &mut self.tilings {
patterns.renumber(offset); tilings.renumber(offset);
} }
} }
} }
@ -214,8 +214,8 @@ pub fn alloc_resources_refs(
.color_fonts .color_fonts
.as_ref() .as_ref()
.map(|c| Box::new(refs_for(&c.resources, chunk))), .map(|c| Box::new(refs_for(&c.resources, chunk))),
patterns: resources tilings: resources
.patterns .tilings
.as_ref() .as_ref()
.map(|p| Box::new(refs_for(&p.resources, chunk))), .map(|p| Box::new(refs_for(&p.resources, chunk))),
} }
@ -231,7 +231,7 @@ pub fn alloc_resources_refs(
/// to the root node of the page tree because using the resource inheritance /// to the root node of the page tree because using the resource inheritance
/// feature breaks PDF merging with Apple Preview. /// feature breaks PDF merging with Apple Preview.
/// ///
/// Also write resource dictionaries for Type3 fonts and patterns. /// Also write resource dictionaries for Type3 fonts and PDF patterns.
pub fn write_resource_dictionaries(ctx: &WithEverything) -> SourceResult<(PdfChunk, ())> { pub fn write_resource_dictionaries(ctx: &WithEverything) -> SourceResult<(PdfChunk, ())> {
let mut chunk = PdfChunk::new(); let mut chunk = PdfChunk::new();
let mut used_color_spaces = ColorSpaces::default(); let mut used_color_spaces = ColorSpaces::default();
@ -266,8 +266,8 @@ pub fn write_resource_dictionaries(ctx: &WithEverything) -> SourceResult<(PdfChu
resources resources
.gradients .gradients
.write(&ctx.references.gradients, &mut patterns_dict); .write(&ctx.references.gradients, &mut patterns_dict);
if let Some(p) = &resources.patterns { if let Some(p) = &resources.tilings {
p.remapper.write(&ctx.references.patterns, &mut patterns_dict); p.remapper.write(&ctx.references.tilings, &mut patterns_dict);
} }
patterns_dict.finish(); patterns_dict.finish();

View File

@ -5,7 +5,7 @@ use pdf_writer::types::{ColorSpaceOperand, PaintType, TilingType};
use pdf_writer::{Filter, Name, Rect, Ref}; use pdf_writer::{Filter, Name, Rect, Ref};
use typst_library::diag::SourceResult; use typst_library::diag::SourceResult;
use typst_library::layout::{Abs, Ratio, Transform}; use typst_library::layout::{Abs, Ratio, Transform};
use typst_library::visualize::{Pattern, RelativeTo}; use typst_library::visualize::{RelativeTo, Tiling};
use typst_utils::Numeric; use typst_utils::Numeric;
use crate::color::PaintEncode; use crate::color::PaintEncode;
@ -14,18 +14,18 @@ use crate::{content, transform_to_array, PdfChunk, Resources, WithGlobalRefs};
/// Writes the actual patterns (tiling patterns) to the PDF. /// Writes the actual patterns (tiling patterns) to the PDF.
/// This is performed once after writing all pages. /// This is performed once after writing all pages.
pub fn write_patterns( pub fn write_tilings(
context: &WithGlobalRefs, context: &WithGlobalRefs,
) -> SourceResult<(PdfChunk, HashMap<PdfPattern, Ref>)> { ) -> SourceResult<(PdfChunk, HashMap<PdfTiling, Ref>)> {
let mut chunk = PdfChunk::new(); let mut chunk = PdfChunk::new();
let mut out = HashMap::new(); let mut out = HashMap::new();
context.resources.traverse(&mut |resources| { context.resources.traverse(&mut |resources| {
let Some(patterns) = &resources.patterns else { let Some(patterns) = &resources.tilings else {
return Ok(()); return Ok(());
}; };
for pdf_pattern in patterns.remapper.items() { for pdf_pattern in patterns.remapper.items() {
let PdfPattern { transform, pattern, content, .. } = pdf_pattern; let PdfTiling { transform, pattern, content, .. } = pdf_pattern;
if out.contains_key(pdf_pattern) { if out.contains_key(pdf_pattern) {
continue; continue;
} }
@ -69,11 +69,11 @@ pub fn write_patterns(
/// A pattern and its transform. /// A pattern and its transform.
#[derive(Clone, PartialEq, Eq, Hash, Debug)] #[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct PdfPattern { pub struct PdfTiling {
/// The transform to apply to the pattern. /// The transform to apply to the pattern.
pub transform: Transform, pub transform: Transform,
/// The pattern to paint. /// The pattern to paint.
pub pattern: Pattern, pub pattern: Tiling,
/// The rendered pattern. /// The rendered pattern.
pub content: Vec<u8>, pub content: Vec<u8>,
} }
@ -81,14 +81,14 @@ pub struct PdfPattern {
/// Registers a pattern with the PDF. /// Registers a pattern with the PDF.
fn register_pattern( fn register_pattern(
ctx: &mut content::Builder, ctx: &mut content::Builder,
pattern: &Pattern, pattern: &Tiling,
on_text: bool, on_text: bool,
mut transforms: content::Transforms, mut transforms: content::Transforms,
) -> SourceResult<usize> { ) -> SourceResult<usize> {
let patterns = ctx let patterns = ctx
.resources .resources
.patterns .tilings
.get_or_insert_with(|| Box::new(PatternRemapper::new())); .get_or_insert_with(|| Box::new(TilingRemapper::new()));
// Edge cases for strokes. // Edge cases for strokes.
if transforms.size.x.is_zero() { if transforms.size.x.is_zero() {
@ -113,7 +113,7 @@ fn register_pattern(
None, None,
)?; )?;
let pdf_pattern = PdfPattern { let pdf_pattern = PdfTiling {
transform, transform,
pattern: pattern.clone(), pattern: pattern.clone(),
content: content.content.wait().clone(), content: content.content.wait().clone(),
@ -122,7 +122,7 @@ fn register_pattern(
Ok(patterns.remapper.insert(pdf_pattern)) Ok(patterns.remapper.insert(pdf_pattern))
} }
impl PaintEncode for Pattern { impl PaintEncode for Tiling {
fn set_as_fill( fn set_as_fill(
&self, &self,
ctx: &mut content::Builder, ctx: &mut content::Builder,
@ -159,14 +159,14 @@ impl PaintEncode for Pattern {
} }
/// De-duplicate patterns and the resources they require to be drawn. /// De-duplicate patterns and the resources they require to be drawn.
pub struct PatternRemapper<R> { pub struct TilingRemapper<R> {
/// Pattern de-duplicator. /// Pattern de-duplicator.
pub remapper: Remapper<PdfPattern>, pub remapper: Remapper<PdfTiling>,
/// PDF resources that are used by these patterns. /// PDF resources that are used by these patterns.
pub resources: Resources<R>, pub resources: Resources<R>,
} }
impl PatternRemapper<()> { impl TilingRemapper<()> {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
remapper: Remapper::new("P"), remapper: Remapper::new("P"),
@ -175,8 +175,8 @@ impl PatternRemapper<()> {
} }
/// Allocate a reference to the resource dictionary of these patterns. /// Allocate a reference to the resource dictionary of these patterns.
pub fn with_refs(self, refs: &ResourcesRefs) -> PatternRemapper<Ref> { pub fn with_refs(self, refs: &ResourcesRefs) -> TilingRemapper<Ref> {
PatternRemapper { TilingRemapper {
remapper: self.remapper, remapper: self.remapper,
resources: self.resources.with_refs(refs), resources: self.resources.with_refs(refs),
} }

View File

@ -2,7 +2,7 @@ use std::sync::Arc;
use tiny_skia as sk; use tiny_skia as sk;
use typst_library::layout::{Axes, Point, Ratio, Size}; use typst_library::layout::{Axes, Point, Ratio, Size};
use typst_library::visualize::{Color, Gradient, Paint, Pattern, RelativeTo}; use typst_library::visualize::{Color, Gradient, Paint, RelativeTo, Tiling};
use crate::{AbsExt, State}; use crate::{AbsExt, State};
@ -72,26 +72,26 @@ impl PaintSampler for GradientSampler<'_> {
} }
} }
/// State used when sampling patterns for text. /// State used when sampling tilings for text.
/// ///
/// It caches the inverse transform to the parent, so that we can /// It caches the inverse transform to the parent, so that we can
/// reuse it instead of recomputing it for each pixel. /// reuse it instead of recomputing it for each pixel.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct PatternSampler<'a> { pub struct TilingSampler<'a> {
size: Size, size: Size,
transform_to_parent: sk::Transform, transform_to_parent: sk::Transform,
pixmap: &'a sk::Pixmap, pixmap: &'a sk::Pixmap,
pixel_per_pt: f32, pixel_per_pt: f32,
} }
impl<'a> PatternSampler<'a> { impl<'a> TilingSampler<'a> {
pub fn new( pub fn new(
pattern: &'a Pattern, tilings: &'a Tiling,
pixmap: &'a sk::Pixmap, pixmap: &'a sk::Pixmap,
state: &State, state: &State,
on_text: bool, on_text: bool,
) -> Self { ) -> Self {
let relative = pattern.unwrap_relative(on_text); let relative = tilings.unwrap_relative(on_text);
let fill_transform = match relative { let fill_transform = match relative {
RelativeTo::Self_ => sk::Transform::identity(), RelativeTo::Self_ => sk::Transform::identity(),
RelativeTo::Parent => state.container_transform.invert().unwrap(), RelativeTo::Parent => state.container_transform.invert().unwrap(),
@ -99,17 +99,17 @@ impl<'a> PatternSampler<'a> {
Self { Self {
pixmap, pixmap,
size: (pattern.size() + pattern.spacing()) * state.pixel_per_pt as f64, size: (tilings.size() + tilings.spacing()) * state.pixel_per_pt as f64,
transform_to_parent: fill_transform, transform_to_parent: fill_transform,
pixel_per_pt: state.pixel_per_pt, pixel_per_pt: state.pixel_per_pt,
} }
} }
} }
impl PaintSampler for PatternSampler<'_> { impl PaintSampler for TilingSampler<'_> {
/// Samples a single point in a glyph. /// Samples a single point in a glyph.
fn sample(self, (x, y): (u32, u32)) -> sk::PremultipliedColorU8 { fn sample(self, (x, y): (u32, u32)) -> sk::PremultipliedColorU8 {
// Compute the point in the pattern's coordinate space. // Compute the point in the tilings's coordinate space.
let mut point = sk::Point { x: x as f32, y: y as f32 }; let mut point = sk::Point { x: x as f32, y: y as f32 };
self.transform_to_parent.map_point(&mut point); self.transform_to_parent.map_point(&mut point);
@ -118,7 +118,7 @@ impl PaintSampler for PatternSampler<'_> {
let y = let y =
(point.y * self.pixel_per_pt).rem_euclid(self.size.y.to_f32()).floor() as u32; (point.y * self.pixel_per_pt).rem_euclid(self.size.y.to_f32()).floor() as u32;
// Sample the pattern // Sample the tilings
self.pixmap.pixel(x, y).unwrap() self.pixmap.pixel(x, y).unwrap()
} }
} }
@ -218,8 +218,8 @@ pub fn to_sk_paint<'a>(
sk_paint.anti_alias = gradient.anti_alias(); sk_paint.anti_alias = gradient.anti_alias();
} }
Paint::Pattern(pattern) => { Paint::Tiling(tilings) => {
let relative = pattern.unwrap_relative(on_text); let relative = tilings.unwrap_relative(on_text);
let fill_transform = match relative { let fill_transform = match relative {
RelativeTo::Self_ => fill_transform.unwrap_or_default(), RelativeTo::Self_ => fill_transform.unwrap_or_default(),
@ -228,7 +228,7 @@ pub fn to_sk_paint<'a>(
.post_concat(state.transform.invert().unwrap()), .post_concat(state.transform.invert().unwrap()),
}; };
let canvas = render_pattern_frame(&state, pattern); let canvas = render_tiling_frame(&state, tilings);
*pixmap = Some(Arc::new(canvas)); *pixmap = Some(Arc::new(canvas));
let offset = match relative { let offset = match relative {
@ -265,17 +265,17 @@ pub fn to_sk_color_u8(color: Color) -> sk::ColorU8 {
sk::ColorU8::from_rgba(r, g, b, a) sk::ColorU8::from_rgba(r, g, b, a)
} }
pub fn render_pattern_frame(state: &State, pattern: &Pattern) -> sk::Pixmap { pub fn render_tiling_frame(state: &State, tilings: &Tiling) -> sk::Pixmap {
let size = pattern.size() + pattern.spacing(); let size = tilings.size() + tilings.spacing();
let mut canvas = sk::Pixmap::new( let mut canvas = sk::Pixmap::new(
(size.x.to_f32() * state.pixel_per_pt).round() as u32, (size.x.to_f32() * state.pixel_per_pt).round() as u32,
(size.y.to_f32() * state.pixel_per_pt).round() as u32, (size.y.to_f32() * state.pixel_per_pt).round() as u32,
) )
.unwrap(); .unwrap();
// Render the pattern into a new canvas. // Render the tilings into a new canvas.
let ts = sk::Transform::from_scale(state.pixel_per_pt, state.pixel_per_pt); let ts = sk::Transform::from_scale(state.pixel_per_pt, state.pixel_per_pt);
let temp_state = State::new(pattern.size(), ts, state.pixel_per_pt); let temp_state = State::new(tilings.size(), ts, state.pixel_per_pt);
crate::render_frame(&mut canvas, temp_state, pattern.frame()); crate::render_frame(&mut canvas, temp_state, tilings.frame());
canvas canvas
} }

View File

@ -168,11 +168,11 @@ pub fn to_sk_line_join(join: LineJoin) -> sk::LineJoin {
} }
} }
pub fn to_sk_dash_pattern(pattern: &DashPattern<Abs, Abs>) -> Option<sk::StrokeDash> { pub fn to_sk_dash_pattern(dash: &DashPattern<Abs, Abs>) -> Option<sk::StrokeDash> {
// tiny-skia only allows dash patterns with an even number of elements, // tiny-skia only allows dash patterns with an even number of elements,
// while pdf allows any number. // while pdf allows any number.
let pattern_len = pattern.array.len(); let pattern_len = dash.array.len();
let len = if pattern_len % 2 == 1 { 2 * pattern_len } else { pattern_len }; let len = if pattern_len % 2 == 1 { 2 * pattern_len } else { pattern_len };
let dash_array = pattern.array.iter().map(|l| l.to_f32()).cycle().take(len).collect(); let dash_array = dash.array.iter().map(|l| l.to_f32()).cycle().take(len).collect();
sk::StrokeDash::new(dash_array, pattern.phase.to_f32()) sk::StrokeDash::new(dash_array, dash.phase.to_f32())
} }

View File

@ -8,7 +8,7 @@ use typst_library::text::color::{glyph_frame, should_outline};
use typst_library::text::{Font, TextItem}; use typst_library::text::{Font, TextItem};
use typst_library::visualize::{FixedStroke, Paint}; use typst_library::visualize::{FixedStroke, Paint};
use crate::paint::{self, GradientSampler, PaintSampler, PatternSampler}; use crate::paint::{self, GradientSampler, PaintSampler, TilingSampler};
use crate::{shape, AbsExt, State}; use crate::{shape, AbsExt, State};
/// Render a text run into the canvas. /// Render a text run into the canvas.
@ -145,9 +145,9 @@ fn render_outline_glyph(
paint::to_sk_color_u8(*color).premultiply(), paint::to_sk_color_u8(*color).premultiply(),
)?; )?;
} }
Paint::Pattern(pattern) => { Paint::Tiling(tiling) => {
let pixmap = paint::render_pattern_frame(&state, pattern); let pixmap = paint::render_tiling_frame(&state, tiling);
let sampler = PatternSampler::new(pattern, &pixmap, &state, true); let sampler = TilingSampler::new(tiling, &pixmap, &state, true);
write_bitmap(canvas, &bitmap, &state, sampler)?; write_bitmap(canvas, &bitmap, &state, sampler)?;
} }
} }

View File

@ -14,11 +14,11 @@ use typst_library::layout::{
Abs, Frame, FrameItem, FrameKind, GroupItem, Page, PagedDocument, Point, Ratio, Size, Abs, Frame, FrameItem, FrameKind, GroupItem, Page, PagedDocument, Point, Ratio, Size,
Transform, Transform,
}; };
use typst_library::visualize::{Geometry, Gradient, Pattern}; use typst_library::visualize::{Geometry, Gradient, Tiling};
use typst_utils::hash128; use typst_utils::hash128;
use xmlwriter::XmlWriter; use xmlwriter::XmlWriter;
use crate::paint::{GradientRef, PatternRef, SVGSubGradient}; use crate::paint::{GradientRef, SVGSubGradient, TilingRef};
use crate::text::RenderedGlyph; use crate::text::RenderedGlyph;
/// Export a frame into a SVG file. /// Export a frame into a SVG file.
@ -92,12 +92,12 @@ struct SVGRenderer {
/// different transforms. Therefore this allows us to reuse the same gradient /// different transforms. Therefore this allows us to reuse the same gradient
/// multiple times. /// multiple times.
gradient_refs: Deduplicator<GradientRef>, gradient_refs: Deduplicator<GradientRef>,
/// Deduplicated patterns with transform matrices. They use a reference /// Deduplicated tilings with transform matrices. They use a reference
/// (`href`) to a "source" pattern instead of being defined inline. /// (`href`) to a "source" tiling instead of being defined inline.
/// This saves a lot of space since patterns are often reused but with /// This saves a lot of space since tilings are often reused but with
/// different transforms. Therefore this allows us to reuse the same gradient /// different transforms. Therefore this allows us to reuse the same gradient
/// multiple times. /// multiple times.
pattern_refs: Deduplicator<PatternRef>, tiling_refs: Deduplicator<TilingRef>,
/// These are the actual gradients being written in the SVG file. /// These are the actual gradients being written in the SVG file.
/// These gradients are deduplicated because they do not contain the transform /// These gradients are deduplicated because they do not contain the transform
/// matrix, allowing them to be reused across multiple invocations. /// matrix, allowing them to be reused across multiple invocations.
@ -105,12 +105,12 @@ struct SVGRenderer {
/// The `Ratio` is the aspect ratio of the gradient, this is used to correct /// The `Ratio` is the aspect ratio of the gradient, this is used to correct
/// the angle of the gradient. /// the angle of the gradient.
gradients: Deduplicator<(Gradient, Ratio)>, gradients: Deduplicator<(Gradient, Ratio)>,
/// These are the actual patterns being written in the SVG file. /// These are the actual tilings being written in the SVG file.
/// These patterns are deduplicated because they do not contain the transform /// These tilings are deduplicated because they do not contain the transform
/// matrix, allowing them to be reused across multiple invocations. /// matrix, allowing them to be reused across multiple invocations.
/// ///
/// The `String` is the rendered pattern frame. /// The `String` is the rendered tiling frame.
patterns: Deduplicator<Pattern>, tilings: Deduplicator<Tiling>,
/// These are the gradients that compose a conic gradient. /// These are the gradients that compose a conic gradient.
conic_subgradients: Deduplicator<SVGSubGradient>, conic_subgradients: Deduplicator<SVGSubGradient>,
} }
@ -163,8 +163,8 @@ impl SVGRenderer {
gradient_refs: Deduplicator::new('g'), gradient_refs: Deduplicator::new('g'),
gradients: Deduplicator::new('f'), gradients: Deduplicator::new('f'),
conic_subgradients: Deduplicator::new('s'), conic_subgradients: Deduplicator::new('s'),
pattern_refs: Deduplicator::new('p'), tiling_refs: Deduplicator::new('p'),
patterns: Deduplicator::new('t'), tilings: Deduplicator::new('t'),
} }
} }
@ -272,8 +272,8 @@ impl SVGRenderer {
self.write_gradients(); self.write_gradients();
self.write_gradient_refs(); self.write_gradient_refs();
self.write_subgradients(); self.write_subgradients();
self.write_patterns(); self.write_tilings();
self.write_pattern_refs(); self.write_tiling_refs();
self.xml.end_document() self.xml.end_document()
} }

View File

@ -4,7 +4,7 @@ use ecow::{eco_format, EcoString};
use ttf_parser::OutlineBuilder; use ttf_parser::OutlineBuilder;
use typst_library::foundations::Repr; use typst_library::foundations::Repr;
use typst_library::layout::{Angle, Axes, Frame, Quadrant, Ratio, Size, Transform}; use typst_library::layout::{Angle, Axes, Frame, Quadrant, Ratio, Size, Transform};
use typst_library::visualize::{Color, FillRule, Gradient, Paint, Pattern, RatioOrAngle}; use typst_library::visualize::{Color, FillRule, Gradient, Paint, RatioOrAngle, Tiling};
use typst_utils::hash128; use typst_utils::hash128;
use xmlwriter::XmlWriter; use xmlwriter::XmlWriter;
@ -17,7 +17,7 @@ const CONIC_SEGMENT: usize = 360;
impl SVGRenderer { impl SVGRenderer {
/// Render a frame to a string. /// Render a frame to a string.
pub(super) fn render_pattern_frame( pub(super) fn render_tiling_frame(
&mut self, &mut self,
state: State, state: State,
ts: Transform, ts: Transform,
@ -44,8 +44,8 @@ impl SVGRenderer {
let id = self.push_gradient(gradient, size, ts); let id = self.push_gradient(gradient, size, ts);
self.xml.write_attribute_fmt("fill", format_args!("url(#{id})")); self.xml.write_attribute_fmt("fill", format_args!("url(#{id})"));
} }
Paint::Pattern(pattern) => { Paint::Tiling(tiling) => {
let id = self.push_pattern(pattern, size, ts); let id = self.push_tiling(tiling, size, ts);
self.xml.write_attribute_fmt("fill", format_args!("url(#{id})")); self.xml.write_attribute_fmt("fill", format_args!("url(#{id})"));
} }
} }
@ -86,32 +86,31 @@ impl SVGRenderer {
}) })
} }
pub(super) fn push_pattern( pub(super) fn push_tiling(
&mut self, &mut self,
pattern: &Pattern, tiling: &Tiling,
size: Size, size: Size,
ts: Transform, ts: Transform,
) -> Id { ) -> Id {
let pattern_size = pattern.size() + pattern.spacing(); let tiling_size = tiling.size() + tiling.spacing();
// Unfortunately due to a limitation of `xmlwriter`, we need to // Unfortunately due to a limitation of `xmlwriter`, we need to
// render the frame twice: once to allocate all of the resources // render the frame twice: once to allocate all of the resources
// that it needs and once to actually render it. // that it needs and once to actually render it.
self.render_pattern_frame( self.render_tiling_frame(
State::new(pattern_size, Transform::identity()), State::new(tiling_size, Transform::identity()),
Transform::identity(), Transform::identity(),
pattern.frame(), tiling.frame(),
); );
let pattern_id = self.patterns.insert_with(hash128(pattern), || pattern.clone()); let tiling_id = self.tilings.insert_with(hash128(tiling), || tiling.clone());
self.pattern_refs self.tiling_refs.insert_with(hash128(&(tiling_id, ts)), || TilingRef {
.insert_with(hash128(&(pattern_id, ts)), || PatternRef { id: tiling_id,
id: pattern_id, transform: ts,
transform: ts, ratio: Axes::new(
ratio: Axes::new( Ratio::new(tiling_size.x.to_pt() / size.x.to_pt()),
Ratio::new(pattern_size.x.to_pt() / size.x.to_pt()), Ratio::new(tiling_size.y.to_pt() / size.y.to_pt()),
Ratio::new(pattern_size.y.to_pt() / size.y.to_pt()), ),
), })
})
} }
/// Write the raw gradients (without transform) to the SVG file. /// Write the raw gradients (without transform) to the SVG file.
@ -188,12 +187,12 @@ impl SVGRenderer {
// Create the path for the segment. // Create the path for the segment.
let mut builder = SvgPathBuilder::default(); let mut builder = SvgPathBuilder::default();
builder.move_to( builder.move_to(
correct_pattern_pos(center.0), correct_tiling_pos(center.0),
correct_pattern_pos(center.1), correct_tiling_pos(center.1),
); );
builder.line_to( builder.line_to(
correct_pattern_pos(-2.0 * (theta1 + angle).cos() + center.0), correct_tiling_pos(-2.0 * (theta1 + angle).cos() + center.0),
correct_pattern_pos(2.0 * (theta1 + angle).sin() + center.1), correct_tiling_pos(2.0 * (theta1 + angle).sin() + center.1),
); );
builder.arc( builder.arc(
(2.0, 2.0), (2.0, 2.0),
@ -201,10 +200,10 @@ impl SVGRenderer {
0, 0,
1, 1,
( (
correct_pattern_pos( correct_tiling_pos(
-2.0 * (theta2 + angle).cos() + center.0, -2.0 * (theta2 + angle).cos() + center.0,
), ),
correct_pattern_pos( correct_tiling_pos(
2.0 * (theta2 + angle).sin() + center.1, 2.0 * (theta2 + angle).sin() + center.1,
), ),
), ),
@ -370,19 +369,19 @@ impl SVGRenderer {
self.xml.end_element(); self.xml.end_element();
} }
/// Write the raw gradients (without transform) to the SVG file. /// Write the raw tilings (without transform) to the SVG file.
pub(super) fn write_patterns(&mut self) { pub(super) fn write_tilings(&mut self) {
if self.patterns.is_empty() { if self.tilings.is_empty() {
return; return;
} }
self.xml.start_element("defs"); self.xml.start_element("defs");
self.xml.write_attribute("id", "patterns"); self.xml.write_attribute("id", "tilings");
for (id, pattern) in for (id, tiling) in
self.patterns.iter().map(|(i, p)| (i, p.clone())).collect::<Vec<_>>() self.tilings.iter().map(|(i, p)| (i, p.clone())).collect::<Vec<_>>()
{ {
let size = pattern.size() + pattern.spacing(); let size = tiling.size() + tiling.spacing();
self.xml.start_element("pattern"); self.xml.start_element("pattern");
self.xml.write_attribute("id", &id); self.xml.write_attribute("id", &id);
self.xml.write_attribute("width", &size.x.to_pt()); self.xml.write_attribute("width", &size.x.to_pt());
@ -396,7 +395,7 @@ impl SVGRenderer {
// Render the frame. // Render the frame.
let state = State::new(size, Transform::identity()); let state = State::new(size, Transform::identity());
let ts = Transform::identity(); let ts = Transform::identity();
self.render_frame(state, ts, pattern.frame()); self.render_frame(state, ts, tiling.frame());
self.xml.end_element(); self.xml.end_element();
} }
@ -404,28 +403,28 @@ impl SVGRenderer {
self.xml.end_element() self.xml.end_element()
} }
/// Writes the references to the deduplicated patterns for each usage site. /// Writes the references to the deduplicated tilings for each usage site.
pub(super) fn write_pattern_refs(&mut self) { pub(super) fn write_tiling_refs(&mut self) {
if self.pattern_refs.is_empty() { if self.tiling_refs.is_empty() {
return; return;
} }
self.xml.start_element("defs"); self.xml.start_element("defs");
self.xml.write_attribute("id", "pattern-refs"); self.xml.write_attribute("id", "tilings-refs");
for (id, pattern_ref) in self.pattern_refs.iter() { for (id, tiling_ref) in self.tiling_refs.iter() {
self.xml.start_element("pattern"); self.xml.start_element("pattern");
self.xml self.xml
.write_attribute("patternTransform", &SvgMatrix(pattern_ref.transform)); .write_attribute("patternTransform", &SvgMatrix(tiling_ref.transform));
self.xml.write_attribute("id", &id); self.xml.write_attribute("id", &id);
// Writing the href attribute to the "reference" pattern. // Writing the href attribute to the "reference" pattern.
self.xml self.xml
.write_attribute_fmt("href", format_args!("#{}", pattern_ref.id)); .write_attribute_fmt("href", format_args!("#{}", tiling_ref.id));
// Also writing the xlink:href attribute for compatibility. // Also writing the xlink:href attribute for compatibility.
self.xml self.xml
.write_attribute_fmt("xlink:href", format_args!("#{}", pattern_ref.id)); .write_attribute_fmt("xlink:href", format_args!("#{}", tiling_ref.id));
self.xml.end_element(); self.xml.end_element();
} }
@ -433,15 +432,15 @@ impl SVGRenderer {
} }
} }
/// A reference to a deduplicated pattern, with a transform matrix. /// A reference to a deduplicated tiling, with a transform matrix.
/// ///
/// Allows patterns to be reused across multiple invocations, /// Allows tilings to be reused across multiple invocations, simply by changing
/// simply by changing the transform matrix. /// the transform matrix.
#[derive(Hash)] #[derive(Hash)]
pub struct PatternRef { pub struct TilingRef {
/// The ID of the deduplicated gradient /// The ID of the deduplicated gradient
id: Id, id: Id,
/// The transform matrix to apply to the pattern. /// The transform matrix to apply to the tiling.
transform: Transform, transform: Transform,
/// The ratio of the size of the cell to the size of the filled area. /// The ratio of the size of the cell to the size of the filled area.
ratio: Axes<Ratio>, ratio: Axes<Ratio>,
@ -587,7 +586,7 @@ impl ColorEncode for Color {
} }
} }
/// Maps a coordinate in a unit size square to a coordinate in the pattern. /// Maps a coordinate in a unit size square to a coordinate in the tiling.
pub fn correct_pattern_pos(x: f32) -> f32 { pub fn correct_tiling_pos(x: f32) -> f32 {
(x + 0.5) / 2.0 (x + 0.5) / 2.0
} }

View File

@ -67,8 +67,8 @@ impl SVGRenderer {
) )
.post_concat(state.transform.invert().unwrap()), .post_concat(state.transform.invert().unwrap()),
} }
} else if let Paint::Pattern(pattern) = paint { } else if let Paint::Tiling(tiling) = paint {
match pattern.unwrap_relative(false) { match tiling.unwrap_relative(false) {
RelativeTo::Self_ => Transform::identity(), RelativeTo::Self_ => Transform::identity(),
RelativeTo::Parent => state.transform.invert().unwrap(), RelativeTo::Parent => state.transform.invert().unwrap(),
} }
@ -112,8 +112,8 @@ impl SVGRenderer {
let id = self.push_gradient(gradient, size, fill_transform); let id = self.push_gradient(gradient, size, fill_transform);
self.xml.write_attribute_fmt("stroke", format_args!("url(#{id})")); self.xml.write_attribute_fmt("stroke", format_args!("url(#{id})"));
} }
Paint::Pattern(pattern) => { Paint::Tiling(tiling) => {
let id = self.push_pattern(pattern, size, fill_transform); let id = self.push_tiling(tiling, size, fill_transform);
self.xml.write_attribute_fmt("stroke", format_args!("url(#{id})")); self.xml.write_attribute_fmt("stroke", format_args!("url(#{id})"));
} }
} }
@ -137,11 +137,11 @@ impl SVGRenderer {
); );
self.xml self.xml
.write_attribute("stroke-miterlimit", &stroke.miter_limit.get()); .write_attribute("stroke-miterlimit", &stroke.miter_limit.get());
if let Some(pattern) = &stroke.dash { if let Some(dash) = &stroke.dash {
self.xml.write_attribute("stroke-dashoffset", &pattern.phase.to_pt()); self.xml.write_attribute("stroke-dashoffset", &dash.phase.to_pt());
self.xml.write_attribute( self.xml.write_attribute(
"stroke-dasharray", "stroke-dasharray",
&pattern &dash
.array .array
.iter() .iter()
.map(|dash| dash.to_pt().to_string()) .map(|dash| dash.to_pt().to_string())

View File

@ -165,7 +165,7 @@ impl SVGRenderer {
) )
.post_concat(state.transform.invert().unwrap()), .post_concat(state.transform.invert().unwrap()),
}, },
Paint::Pattern(pattern) => match pattern.unwrap_relative(true) { Paint::Tiling(tiling) => match tiling.unwrap_relative(true) {
RelativeTo::Self_ => Transform::identity(), RelativeTo::Self_ => Transform::identity(),
RelativeTo::Parent => state.transform.invert().unwrap(), RelativeTo::Parent => state.transform.invert().unwrap(),
}, },

View File

@ -163,7 +163,7 @@ pub enum Expr<'a> {
Parenthesized(Parenthesized<'a>), Parenthesized(Parenthesized<'a>),
/// An array: `(1, "hi", 12cm)`. /// An array: `(1, "hi", 12cm)`.
Array(Array<'a>), Array(Array<'a>),
/// A dictionary: `(thickness: 3pt, pattern: dashed)`. /// A dictionary: `(thickness: 3pt, dash: "solid")`.
Dict(Dict<'a>), Dict(Dict<'a>),
/// A unary operation: `-x`. /// A unary operation: `-x`.
Unary(Unary<'a>), Unary(Unary<'a>),
@ -1195,7 +1195,7 @@ impl<'a> AstNode<'a> for ArrayItem<'a> {
} }
node! { node! {
/// A dictionary: `(thickness: 3pt, pattern: dashed)`. /// A dictionary: `(thickness: 3pt, dash: "solid")`.
Dict Dict
} }

View File

@ -224,7 +224,7 @@ pub enum SyntaxKind {
Parenthesized, Parenthesized,
/// An array: `(1, "hi", 12cm)`. /// An array: `(1, "hi", 12cm)`.
Array, Array,
/// A dictionary: `(thickness: 3pt, pattern: dashed)`. /// A dictionary: `(thickness: 3pt, dash: "solid")`.
Dict, Dict,
/// A named pair: `thickness: 3pt`. /// A named pair: `thickness: 3pt`.
Named, Named,

View File

@ -1077,7 +1077,7 @@ fn expr_with_paren(p: &mut Parser, atomic: bool) {
/// Parses either /// Parses either
/// - a parenthesized expression: `(1 + 2)`, or /// - a parenthesized expression: `(1 + 2)`, or
/// - an array: `(1, "hi", 12cm)`, or /// - an array: `(1, "hi", 12cm)`, or
/// - a dictionary: `(thickness: 3pt, pattern: dashed)`. /// - a dictionary: `(thickness: 3pt, dash: "solid")`.
fn parenthesized_or_array_or_dict(p: &mut Parser) -> SyntaxKind { fn parenthesized_or_array_or_dict(p: &mut Parser) -> SyntaxKind {
let mut state = GroupState { let mut state = GroupState {
count: 0, count: 0,

View File

@ -31,7 +31,7 @@ description: Changes in Typst 0.10.0
- More LaTeX commands (e.g. for quotes) are now respected in `.bib` files - More LaTeX commands (e.g. for quotes) are now respected in `.bib` files
## Visualization ## Visualization
- Added support for [patterns]($pattern) as fills and strokes - Added support for [patterns]($tiling) as fills and strokes
- The `alpha` parameter of the [`components`]($color.components) function on - The `alpha` parameter of the [`components`]($color.components) function on
colors is now a named parameter **(Breaking change)** colors is now a named parameter **(Breaking change)**
- Added support for the [Oklch]($color.oklch) color space - Added support for the [Oklch]($color.oklch) color space

View File

@ -37,7 +37,7 @@ description: Changes in Typst 0.11.1
## Export ## Export
- Fixed [smart quotes]($smartquote) in PDF outline - Fixed [smart quotes]($smartquote) in PDF outline
- Fixed [patterns]($pattern) with spacing in PDF - Fixed [patterns]($tiling) with spacing in PDF
- Fixed wrong PDF page labels when [page numbering]($page.numbering) was - Fixed wrong PDF page labels when [page numbering]($page.numbering) was
disabled after being previously enabled disabled after being previously enabled

View File

@ -226,7 +226,7 @@ applications, while academic applications tend to use strokes instead.
To add zebra stripes to a table, we use the `table` function's `fill` argument. To add zebra stripes to a table, we use the `table` function's `fill` argument.
It can take three kinds of arguments: It can take three kinds of arguments:
- A single color (this can also be a gradient or a pattern) to fill all cells - A single color (this can also be a gradient or a tiling) to fill all cells
with. Because we want some cells to have another color, this is not useful if with. Because we want some cells to have another color, this is not useful if
we want to build zebra tables. we want to build zebra tables.
- An array with colors which Typst cycles through for each column. We can use an - An array with colors which Typst cycles through for each column. We can use an
@ -828,7 +828,7 @@ line appears because there is no `top` line that could suppress it.
### How to achieve a double line? { #double-stroke } ### How to achieve a double line? { #double-stroke }
Typst does not yet have a native way to draw double strokes, but there are Typst does not yet have a native way to draw double strokes, but there are
multiple ways to emulate them, for example with [patterns]($pattern). We will multiple ways to emulate them, for example with [tilings]($tiling). We will
show a different workaround in this section: Table gutters. show a different workaround in this section: Table gutters.
Tables can space their cells apart using the `gutter` argument. When a gutter is Tables can space their cells apart using the `gutter` argument. When a gutter is

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 125 B

After

Width:  |  Height:  |  Size: 125 B

View File

Before

Width:  |  Height:  |  Size: 128 B

After

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

View File

Before

Width:  |  Height:  |  Size: 347 B

After

Width:  |  Height:  |  Size: 347 B

View File

Before

Width:  |  Height:  |  Size: 474 B

After

Width:  |  Height:  |  Size: 474 B

View File

Before

Width:  |  Height:  |  Size: 83 B

After

Width:  |  Height:  |  Size: 83 B

View File

Before

Width:  |  Height:  |  Size: 202 B

After

Width:  |  Height:  |  Size: 202 B

View File

Before

Width:  |  Height:  |  Size: 241 B

After

Width:  |  Height:  |  Size: 241 B

View File

Before

Width:  |  Height:  |  Size: 215 B

After

Width:  |  Height:  |  Size: 215 B

View File

Before

Width:  |  Height:  |  Size: 757 B

After

Width:  |  Height:  |  Size: 757 B

View File

Before

Width:  |  Height:  |  Size: 383 B

After

Width:  |  Height:  |  Size: 383 B

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -1,5 +1,5 @@
--- grid-stroke-pattern --- --- grid-stroke-tiling ---
#let double-line = pattern(size: (1.5pt, 1.5pt), { #let double-line = tiling(size: (1.5pt, 1.5pt), {
place(line(stroke: .6pt, start: (0%, 50%), end: (100%, 50%))) place(line(stroke: .6pt, start: (0%, 50%), end: (100%, 50%)))
}) })

View File

@ -17,7 +17,7 @@
#table(columns: 3, stroke: none, fill: green, [A], [B], [C]) #table(columns: 3, stroke: none, fill: green, [A], [B], [C])
--- table-fill-bad --- --- table-fill-bad ---
// Error: 14-19 expected color, gradient, pattern, none, array, or function, found string // Error: 14-19 expected color, gradient, tiling, none, array, or function, found string
#table(fill: "hey") #table(fill: "hey")
--- table-align-array --- --- table-align-array ---

View File

@ -84,7 +84,7 @@ I
--- issue-5499-text-fill-in-clip-block --- --- issue-5499-text-fill-in-clip-block ---
#let pat = pattern( #let t = tiling(
size: (30pt, 30pt), size: (30pt, 30pt),
relative: "parent", relative: "parent",
square( square(
@ -101,7 +101,7 @@ I
[ ] [ ]
text(fill: gradient.linear(..color.map.rainbow), "Hello") text(fill: gradient.linear(..color.map.rainbow), "Hello")
[ ] [ ]
text(fill: pat, "Hello") text(fill: t, "Hello")
}) })
#block(clip: true, height: 2em, { #block(clip: true, height: 2em, {
text(fill: blue, "Hello") text(fill: blue, "Hello")
@ -110,5 +110,5 @@ I
[ ] [ ]
text(fill: gradient.linear(..color.map.rainbow), "Hello") text(fill: gradient.linear(..color.map.rainbow), "Hello")
[ ] [ ]
text(fill: pat, "Hello") text(fill: t, "Hello")
}) })

View File

@ -390,7 +390,7 @@
--- gradient-text-bad-relative --- --- gradient-text-bad-relative ---
// Make sure they don't work when `relative: "self"`. // Make sure they don't work when `relative: "self"`.
// Hint: 17-61 make sure to set `relative: auto` on your text fill // Hint: 17-61 make sure to set `relative: auto` on your text fill
// Error: 17-61 gradients and patterns on text must be relative to the parent // Error: 17-61 gradients and tilings on text must be relative to the parent
#set text(fill: gradient.linear(red, blue, relative: "self")) #set text(fill: gradient.linear(red, blue, relative: "self"))
--- gradient-text-global --- --- gradient-text-global ---

View File

@ -1,159 +0,0 @@
// Test patterns.
--- pattern-line ---
// Tests that simple patterns work.
#set page(width: auto, height: auto, margin: 0pt)
#let pat = pattern(size: (10pt, 10pt), line(stroke: 4pt, start: (0%, 0%), end: (100%, 100%)))
#rect(width: 50pt, height: 50pt, fill: pat)
--- pattern-lines ---
#set page(width: auto, height: auto, margin: 0pt)
#let pat = pattern(size: (10pt, 10pt), {
place(line(stroke: 4pt, start: (0%, 0%), end: (100%, 100%)))
place(line(stroke: 4pt, start: (100%,0%), end: (200%, 100%)))
place(line(stroke: 4pt, start: (0%,100%), end: (100%, 200%)))
place(line(stroke: 4pt, start: (-100%,0%), end: (0%, 100%)))
place(line(stroke: 4pt, start: (0%,-100%), end: (100%, 0%)))
})
#rect(width: 50pt, height: 50pt, fill: pat)
--- pattern-relative-self ---
// Test with relative set to `"self"`
#let pat(..args) = pattern(size: (30pt, 30pt), ..args)[
#set line(stroke: green)
#place(top + left, line(start: (0%, 0%), end: (100%, 100%), stroke: 1pt))
#place(top + left, line(start: (0%, 100%), end: (100%, 0%), stroke: 1pt))
]
#set page(fill: pat(), width: 100pt, height: 100pt)
#rect(
width: 100%,
height: 100%,
fill: pat(relative: "self"),
stroke: 1pt + green,
)
--- pattern-relative-parent ---
// Test with relative set to `"parent"`
#let pat(fill, ..args) = pattern(size: (30pt, 30pt), ..args)[
#rect(width: 100%, height: 100%, fill: fill, stroke: none)
#place(top + left, line(start: (0%, 0%), end: (100%, 100%), stroke: 1pt))
#place(top + left, line(start: (0%, 100%), end: (100%, 0%), stroke: 1pt))
]
#set page(fill: pat(white), width: 100pt, height: 100pt)
#rect(fill: pat(none, relative: "parent"), width: 100%, height: 100%, stroke: 1pt)
--- pattern-small ---
// Tests small patterns for pixel accuracy.
#box(
width: 8pt,
height: 1pt,
fill: pattern(size: (1pt, 1pt), square(size: 1pt, fill: black))
)
#v(-1em)
#box(
width: 8pt,
height: 1pt,
fill: pattern(size: (2pt, 1pt), square(size: 1pt, fill: black))
)
--- pattern-zero-sized ---
// Error: 15-52 pattern tile size must be non-zero
// Hint: 15-52 try setting the size manually
#line(stroke: pattern(path((0pt, 0pt), (1em, 0pt))))
--- pattern-spacing-negative ---
// Test with spacing set to `(-10pt, -10pt)`
#let pat(..args) = pattern(size: (30pt, 30pt), ..args)[
#square(width: 100%, height: 100%, stroke: 1pt, fill: blue)
]
#set page(width: 100pt, height: 100pt)
#rect(fill: pat(spacing: (-10pt, -10pt)), width: 100%, height: 100%, stroke: 1pt)
--- pattern-spacing-zero ---
// Test with spacing set to `(0pt, 0pt)`
#let pat(..args) = pattern(size: (30pt, 30pt), ..args)[
#square(width: 100%, height: 100%, stroke: 1pt, fill: blue)
]
#set page(width: 100pt, height: 100pt)
#rect(fill: pat(spacing: (0pt, 0pt)), width: 100%, height: 100%, stroke: 1pt)
--- pattern-spacing-positive ---
// Test with spacing set to `(10pt, 10pt)`
#let pat(..args) = pattern(size: (30pt, 30pt), ..args)[
#square(width: 100%, height: 100%, stroke: 1pt, fill: blue)
]
#set page(width: 100pt, height: 100pt)
#rect(fill: pat(spacing: (10pt, 10pt,)), width: 100%, height: 100%, stroke: 1pt)
--- pattern-stroke ---
// Test pattern on strokes
#align(
center + top,
square(
size: 50pt,
fill: pattern(
size: (5pt, 5pt),
align(horizon + center, circle(fill: blue, radius: 2.5pt))
),
stroke: 7.5pt + pattern(
size: (5pt, 5pt),
align(horizon + center, circle(fill: red, radius: 2.5pt))
)
)
)
--- pattern-stroke-relative-parent ---
// Test pattern on strokes with relative set to `"parent"`
// The pattern on the circle should align with the pattern on the square.
#align(
center + top,
block(
width: 50pt,
height: 50pt,
fill: pattern(size: (5pt, 5pt), circle(radius: 2.5pt, fill: blue)),
align(center + horizon, circle(
radius: 15pt,
stroke: 7.5pt + pattern(
size: (5pt, 5pt), circle(radius: 2.5pt, fill: red), relative: "parent"
),
))
)
)
--- pattern-text ---
// Test a pattern on some text
// You shouldn't be able to see the text, if you can then
// that means that the transform matrices are not being
// applied to the text correctly.
#let pat = pattern(
size: (30pt, 30pt),
relative: "parent",
square(size: 30pt, fill: gradient.conic(..color.map.rainbow))
);
#set page(
width: 140pt,
height: 140pt,
fill: pat
)
#rotate(45deg, scale(x: 50%, y: 70%, rect(
width: 100%,
height: 100%,
stroke: 1pt,
)[
#lorem(10)
#set text(fill: pat)
#lorem(10)
]))

View File

@ -55,7 +55,7 @@
#rect(width: 20pt, height: 20pt, stroke: (thickness: 5pt, join: "round")) #rect(width: 20pt, height: 20pt, stroke: (thickness: 5pt, join: "round"))
--- red-stroke-bad-type --- --- red-stroke-bad-type ---
// Error: 15-21 expected length, color, gradient, pattern, dictionary, stroke, none, or auto, found array // Error: 15-21 expected length, color, gradient, tiling, dictionary, stroke, none, or auto, found array
#rect(stroke: (1, 2)) #rect(stroke: (1, 2))
--- rect-fill-stroke --- --- rect-fill-stroke ---

View File

@ -0,0 +1,163 @@
// Test tilings.
--- tiling-line ---
// Tests that simple tilings work.
#set page(width: auto, height: auto, margin: 0pt)
#let t = tiling(size: (10pt, 10pt), line(stroke: 4pt, start: (0%, 0%), end: (100%, 100%)))
#rect(width: 50pt, height: 50pt, fill: t)
--- tiling-lines ---
#set page(width: auto, height: auto, margin: 0pt)
#let t = tiling(size: (10pt, 10pt), {
place(line(stroke: 4pt, start: (0%, 0%), end: (100%, 100%)))
place(line(stroke: 4pt, start: (100%,0%), end: (200%, 100%)))
place(line(stroke: 4pt, start: (0%,100%), end: (100%, 200%)))
place(line(stroke: 4pt, start: (-100%,0%), end: (0%, 100%)))
place(line(stroke: 4pt, start: (0%,-100%), end: (100%, 0%)))
})
#rect(width: 50pt, height: 50pt, fill: t)
--- tiling-relative-self ---
// Test with relative set to `"self"`
#let t(..args) = tiling(size: (30pt, 30pt), ..args)[
#set line(stroke: green)
#place(top + left, line(start: (0%, 0%), end: (100%, 100%), stroke: 1pt))
#place(top + left, line(start: (0%, 100%), end: (100%, 0%), stroke: 1pt))
]
#set page(fill: t(), width: 100pt, height: 100pt)
#rect(
width: 100%,
height: 100%,
fill: t(relative: "self"),
stroke: 1pt + green,
)
--- tiling-relative-parent ---
// Test with relative set to `"parent"`
#let t(fill, ..args) = tiling(size: (30pt, 30pt), ..args)[
#rect(width: 100%, height: 100%, fill: fill, stroke: none)
#place(top + left, line(start: (0%, 0%), end: (100%, 100%), stroke: 1pt))
#place(top + left, line(start: (0%, 100%), end: (100%, 0%), stroke: 1pt))
]
#set page(fill: t(white), width: 100pt, height: 100pt)
#rect(fill: t(none, relative: "parent"), width: 100%, height: 100%, stroke: 1pt)
--- tiling-small ---
// Tests small tilings for pixel accuracy.
#box(
width: 8pt,
height: 1pt,
fill: tiling(size: (1pt, 1pt), square(size: 1pt, fill: black))
)
#v(-1em)
#box(
width: 8pt,
height: 1pt,
fill: tiling(size: (2pt, 1pt), square(size: 1pt, fill: black))
)
--- tiling-zero-sized ---
// Error: 15-51 tile size must be non-zero
// Hint: 15-51 try setting the size manually
#line(stroke: tiling(path((0pt, 0pt), (1em, 0pt))))
--- tiling-spacing-negative ---
// Test with spacing set to `(-10pt, -10pt)`
#let t(..args) = tiling(size: (30pt, 30pt), ..args)[
#square(width: 100%, height: 100%, stroke: 1pt, fill: blue)
]
#set page(width: 100pt, height: 100pt)
#rect(fill: t(spacing: (-10pt, -10pt)), width: 100%, height: 100%, stroke: 1pt)
--- tiling-spacing-zero ---
// Test with spacing set to `(0pt, 0pt)`
#let t(..args) = tiling(size: (30pt, 30pt), ..args)[
#square(width: 100%, height: 100%, stroke: 1pt, fill: blue)
]
#set page(width: 100pt, height: 100pt)
#rect(fill: t(spacing: (0pt, 0pt)), width: 100%, height: 100%, stroke: 1pt)
--- tiling-spacing-positive ---
// Test with spacing set to `(10pt, 10pt)`
#let t(..args) = tiling(size: (30pt, 30pt), ..args)[
#square(width: 100%, height: 100%, stroke: 1pt, fill: blue)
]
#set page(width: 100pt, height: 100pt)
#rect(fill: t(spacing: (10pt, 10pt,)), width: 100%, height: 100%, stroke: 1pt)
--- tiling-stroke ---
// Test tiling on strokes
#align(
center + top,
square(
size: 50pt,
fill: tiling(
size: (5pt, 5pt),
align(horizon + center, circle(fill: blue, radius: 2.5pt))
),
stroke: 7.5pt + tiling(
size: (5pt, 5pt),
align(horizon + center, circle(fill: red, radius: 2.5pt))
)
)
)
--- tiling-stroke-relative-parent ---
// Test tiling on strokes with relative set to `"parent"`
// The tiling on the circle should align with the tiling on the square.
#align(
center + top,
block(
width: 50pt,
height: 50pt,
fill: tiling(size: (5pt, 5pt), circle(radius: 2.5pt, fill: blue)),
align(center + horizon, circle(
radius: 15pt,
stroke: 7.5pt + tiling(
size: (5pt, 5pt), circle(radius: 2.5pt, fill: red), relative: "parent"
),
))
)
)
--- tiling-text ---
// Test a tiling on some text. You shouldn't be able to see the text, if you can
// then that means that the transform matrices are not being applied to the text
// correctly.
#let t = tiling(
size: (30pt, 30pt),
relative: "parent",
square(size: 30pt, fill: gradient.conic(..color.map.rainbow))
);
#set page(
width: 140pt,
height: 140pt,
fill: t
)
#rotate(45deg, scale(x: 50%, y: 70%, rect(
width: 100%,
height: 100%,
stroke: 1pt,
)[
#lorem(10)
#set text(fill: t)
#lorem(10)
]))
--- tiling-pattern-compatibility ---
#set page(width: auto, height: auto, margin: 0pt)
#let t = pattern(size: (10pt, 10pt), line(stroke: 4pt, start: (0%, 0%), end: (100%, 100%)))
#rect(width: 50pt, height: 50pt, fill: t)