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
}
let solid = stroke
.dash
.as_ref()
.map(|pattern| pattern.array.is_empty())
.unwrap_or(true);
let solid = stroke.dash.as_ref().map(|dash| dash.array.is_empty()).unwrap_or(true);
let use_fill = solid && fill_corners(start, end, corners);
let shape = if use_fill {

View File

@ -144,8 +144,8 @@ pub fn add(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
| (Length(thickness), Gradient(gradient)) => {
Stroke::from_pair(gradient, thickness).into_value()
}
(Pattern(pattern), Length(thickness)) | (Length(thickness), Pattern(pattern)) => {
Stroke::from_pair(pattern, thickness).into_value()
(Tiling(tiling), Length(thickness)) | (Length(thickness), Tiling(tiling)) => {
Stroke::from_pair(tiling, thickness).into_value()
}
(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::text::{RawContent, RawElem, TextElem};
use crate::visualize::{Color, Gradient, Pattern};
use crate::visualize::{Color, Gradient, Tiling};
/// A computational value.
#[derive(Default, Clone)]
@ -50,8 +50,8 @@ pub enum Value {
Color(Color),
/// A gradient value: `gradient.linear(...)`.
Gradient(Gradient),
/// A pattern fill: `pattern(...)`.
Pattern(Pattern),
/// A tiling fill: `tiling(...)`.
Tiling(Tiling),
/// A symbol: `arrow.l`.
Symbol(Symbol),
/// A version.
@ -130,7 +130,7 @@ impl Value {
Self::Fraction(_) => Type::of::<Fr>(),
Self::Color(_) => Type::of::<Color>(),
Self::Gradient(_) => Type::of::<Gradient>(),
Self::Pattern(_) => Type::of::<Pattern>(),
Self::Tiling(_) => Type::of::<Tiling>(),
Self::Symbol(_) => Type::of::<Symbol>(),
Self::Version(_) => Type::of::<Version>(),
Self::Str(_) => Type::of::<Str>(),
@ -244,7 +244,7 @@ impl Debug for Value {
Self::Fraction(v) => Debug::fmt(v, f),
Self::Color(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::Version(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::Color(v) => v.repr(),
Self::Gradient(v) => v.repr(),
Self::Pattern(v) => v.repr(),
Self::Tiling(v) => v.repr(),
Self::Symbol(v) => v.repr(),
Self::Version(v) => v.repr(),
Self::Str(v) => v.repr(),
@ -333,7 +333,7 @@ impl Hash for Value {
Self::Fraction(v) => v.hash(state),
Self::Color(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::Version(v) => v.hash(state),
Self::Str(v) => v.hash(state),
@ -640,7 +640,7 @@ primitive! { Rel<Length>: "relative length",
primitive! { Fr: "fraction", Fraction }
primitive! { Color: "color", Color }
primitive! { Gradient: "gradient", Gradient }
primitive! { Pattern: "pattern", Pattern }
primitive! { Tiling: "tiling", Tiling }
primitive! { Symbol: "symbol", Symbol }
primitive! { Version: "version", Version }
primitive! {

View File

@ -161,7 +161,7 @@ impl<'a> Locator<'a> {
///
/// 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
/// introspection (e.g. drawable patterns).
/// introspection (e.g. tilings).
pub fn root() -> Self {
Self { local: 0, outer: None }
}

View File

@ -249,7 +249,7 @@ pub struct TextElem {
if paint.v.relative() == Smart::Custom(RelativeTo::Self_) {
bail!(
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"
);
}

View File

@ -6,10 +6,10 @@ mod image;
mod line;
mod paint;
mod path;
mod pattern;
mod polygon;
mod shape;
mod stroke;
mod tiling;
pub use self::color::*;
pub use self::gradient::*;
@ -17,12 +17,12 @@ pub use self::image::*;
pub use self::line::*;
pub use self::paint::*;
pub use self::path::*;
pub use self::pattern::*;
pub use self::polygon::*;
pub use self::shape::*;
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.
///
@ -37,7 +37,7 @@ pub(super) fn define(global: &mut Scope) {
global.category(VISUALIZE);
global.define_type::<Color>();
global.define_type::<Gradient>();
global.define_type::<Pattern>();
global.define_type::<Tiling>();
global.define_type::<Stroke>();
global.define_elem::<ImageElem>();
global.define_elem::<LineElem>();
@ -47,4 +47,7 @@ pub(super) fn define(global: &mut Scope) {
global.define_elem::<CircleElem>();
global.define_elem::<PolygonElem>();
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 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.
#[derive(Clone, Eq, PartialEq, Hash)]
@ -12,8 +12,8 @@ pub enum Paint {
Solid(Color),
/// A gradient.
Gradient(Gradient),
/// A pattern.
Pattern(Pattern),
/// A tiling.
Tiling(Tiling),
}
impl Paint {
@ -21,7 +21,7 @@ impl Paint {
pub fn unwrap_solid(&self) -> Color {
match self {
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 {
Self::Solid(_) => Smart::Auto,
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.clone().with_relative(RelativeTo::Parent))
}
Self::Pattern(pattern) => {
Self::Pattern(pattern.clone().with_relative(RelativeTo::Parent))
Self::Tiling(tiling) => {
Self::Tiling(tiling.clone().with_relative(RelativeTo::Parent))
}
}
}
@ -56,14 +56,14 @@ impl Debug for Paint {
match self {
Self::Solid(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 {
fn from(pattern: Pattern) -> Self {
Self::Pattern(pattern)
impl From<Tiling> for Paint {
fn from(tiling: Tiling) -> Self {
Self::Tiling(tiling)
}
}
@ -72,7 +72,7 @@ impl Repr for Paint {
match self {
Self::Solid(color) => color.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::Solid(color) => color.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),
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,
};
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.
///
@ -213,9 +213,9 @@ impl<T: Numeric> Stroke<T> {
thickness: self.thickness.map(&f),
cap: self.cap,
join: self.join,
dash: self.dash.map(|pattern| {
pattern.map(|pattern| DashPattern {
array: pattern
dash: self.dash.map(|dash| {
dash.map(|dash| DashPattern {
array: dash
.array
.into_iter()
.map(|l| match l {
@ -223,7 +223,7 @@ impl<T: Numeric> Stroke<T> {
DashLength::LineWidth => DashLength::LineWidth,
})
.collect(),
phase: f(pattern.phase),
phase: f(dash.phase),
})
}),
miter_limit: self.miter_limit,
@ -237,14 +237,10 @@ impl Stroke<Abs> {
let thickness = self.thickness.unwrap_or(default.thickness);
let dash = self
.dash
.map(|pattern| {
pattern.map(|pattern| DashPattern {
array: pattern
.array
.into_iter()
.map(|l| l.finish(thickness))
.collect(),
phase: pattern.phase,
.map(|dash| {
dash.map(|dash| DashPattern {
array: dash.array.into_iter().map(|l| l.finish(thickness)).collect(),
phase: dash.phase,
})
})
.unwrap_or(default.dash);
@ -372,8 +368,8 @@ cast! {
paint: Smart::Custom(gradient.into()),
..Default::default()
},
pattern: Pattern => Self {
paint: Smart::Custom(pattern.into()),
tiling: Tiling => Self {
paint: Smart::Custom(tiling.into()),
..Default::default()
},
mut dict: Dict => {

View File

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

View File

@ -222,7 +222,7 @@ impl PaintEncode for Paint {
match self {
Self::Solid(c) => c.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 {
Self::Solid(c) => c.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.
//!
//! 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`].
@ -96,7 +96,7 @@ pub struct Encoded {
/// objects only through resources.
///
/// Content streams can be used for page contents, but also to describe color
/// glyphs and patterns.
/// glyphs and tilings.
pub struct Builder<'a, R = ()> {
/// Settings for PDF export.
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)]
pub(super) struct Transforms {
/// The transform of the current item.
@ -229,7 +229,7 @@ impl Builder<'_, ()> {
let get_opacity = |paint: &Paint| {
let color = match paint {
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)
@ -330,10 +330,10 @@ impl Builder<'_, ()> {
self.content.set_line_join(to_pdf_line_join(*join));
}
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(
pattern.array.iter().map(|l| l.to_f32()),
pattern.phase.to_f32(),
dash.array.iter().map(|l| l.to_f32()),
dash.phase.to_f32(),
);
} else {
self.content.set_dash_pattern([], 0.0);

View File

@ -11,8 +11,8 @@ mod image;
mod named_destination;
mod outline;
mod page;
mod pattern;
mod resources;
mod tiling;
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
@ -39,10 +39,10 @@ use crate::gradient::{write_gradients, PdfGradient};
use crate::image::write_images;
use crate::named_destination::{write_named_destinations, NamedDestinations};
use crate::page::{alloc_page_refs, traverse_pages, write_page_tree, EncodedPage};
use crate::pattern::{write_patterns, PdfPattern};
use crate::resources::{
alloc_resources_refs, write_resource_dictionaries, Resources, ResourcesRefs,
};
use crate::tiling::{write_tilings, PdfTiling};
/// 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)?,
images: builder.run(write_images)?,
gradients: builder.run(write_gradients)?,
patterns: builder.run(write_patterns)?,
tilings: builder.run(write_tilings)?,
ext_gs: builder.run(write_graphic_states)?,
})
})?
@ -267,8 +267,8 @@ struct References {
images: HashMap<Image, Ref>,
/// The IDs of written gradients.
gradients: HashMap<PdfGradient, Ref>,
/// The IDs of written patterns.
patterns: HashMap<PdfPattern, Ref>,
/// The IDs of written tilings.
tilings: HashMap<PdfTiling, Ref>,
/// The IDs of written external graphics states.
ext_gs: HashMap<ExtGState, Ref>,
}

View File

@ -23,7 +23,7 @@ use crate::color_font::ColorFontMap;
use crate::extg::ExtGState;
use crate::gradient::PdfGradient;
use crate::image::EncodedImage;
use crate::pattern::PatternRemapper;
use crate::tiling::TilingRemapper;
use crate::{PdfChunk, Renumber, WithEverything, WithResources};
/// 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
/// 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`]),
/// 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
/// by the respective sub-resources.
///
/// 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
/// 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.
///
/// 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)>,
/// Deduplicates gradients used across the document.
pub gradients: Remapper<PdfGradient>,
/// Deduplicates patterns used across the document.
pub patterns: Option<Box<PatternRemapper<R>>>,
/// Deduplicates tilings used across the document.
pub tilings: Option<Box<TilingRemapper<R>>>,
/// Deduplicates external graphics states used across the document.
pub ext_gs: Remapper<ExtGState>,
/// Deduplicates color glyphs.
@ -107,8 +107,8 @@ impl<R: Renumber> Renumber for Resources<R> {
color_fonts.resources.renumber(offset);
}
if let Some(patterns) = &mut self.patterns {
patterns.resources.renumber(offset);
if let Some(tilings) = &mut self.tilings {
tilings.resources.renumber(offset);
}
}
}
@ -122,7 +122,7 @@ impl Default for Resources<()> {
images: Remapper::new("Im"),
deferred_images: HashMap::new(),
gradients: Remapper::new("Gr"),
patterns: None,
tilings: None,
ext_gs: Remapper::new("Gs"),
color_fonts: None,
languages: BTreeMap::new(),
@ -144,9 +144,9 @@ impl Resources<()> {
images: self.images,
deferred_images: self.deferred_images,
gradients: self.gradients,
patterns: self
.patterns
.zip(refs.patterns.as_ref())
tilings: self
.tilings
.zip(refs.tilings.as_ref())
.map(|(p, r)| Box::new(p.with_refs(r))),
ext_gs: self.ext_gs,
color_fonts: self
@ -172,8 +172,8 @@ impl<R> Resources<R> {
if let Some(color_fonts) = &self.color_fonts {
color_fonts.resources.traverse(process)?;
}
if let Some(patterns) = &self.patterns {
patterns.resources.traverse(process)?;
if let Some(tilings) = &self.tilings {
tilings.resources.traverse(process)?;
}
Ok(())
}
@ -186,7 +186,7 @@ impl<R> Resources<R> {
pub struct ResourcesRefs {
pub reference: Ref,
pub color_fonts: Option<Box<ResourcesRefs>>,
pub patterns: Option<Box<ResourcesRefs>>,
pub tilings: Option<Box<ResourcesRefs>>,
}
impl Renumber for ResourcesRefs {
@ -195,8 +195,8 @@ impl Renumber for ResourcesRefs {
if let Some(color_fonts) = &mut self.color_fonts {
color_fonts.renumber(offset);
}
if let Some(patterns) = &mut self.patterns {
patterns.renumber(offset);
if let Some(tilings) = &mut self.tilings {
tilings.renumber(offset);
}
}
}
@ -214,8 +214,8 @@ pub fn alloc_resources_refs(
.color_fonts
.as_ref()
.map(|c| Box::new(refs_for(&c.resources, chunk))),
patterns: resources
.patterns
tilings: resources
.tilings
.as_ref()
.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
/// 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, ())> {
let mut chunk = PdfChunk::new();
let mut used_color_spaces = ColorSpaces::default();
@ -266,8 +266,8 @@ pub fn write_resource_dictionaries(ctx: &WithEverything) -> SourceResult<(PdfChu
resources
.gradients
.write(&ctx.references.gradients, &mut patterns_dict);
if let Some(p) = &resources.patterns {
p.remapper.write(&ctx.references.patterns, &mut patterns_dict);
if let Some(p) = &resources.tilings {
p.remapper.write(&ctx.references.tilings, &mut patterns_dict);
}
patterns_dict.finish();

View File

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

View File

@ -2,7 +2,7 @@ use std::sync::Arc;
use tiny_skia as sk;
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};
@ -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
/// reuse it instead of recomputing it for each pixel.
#[derive(Clone, Copy)]
pub struct PatternSampler<'a> {
pub struct TilingSampler<'a> {
size: Size,
transform_to_parent: sk::Transform,
pixmap: &'a sk::Pixmap,
pixel_per_pt: f32,
}
impl<'a> PatternSampler<'a> {
impl<'a> TilingSampler<'a> {
pub fn new(
pattern: &'a Pattern,
tilings: &'a Tiling,
pixmap: &'a sk::Pixmap,
state: &State,
on_text: bool,
) -> Self {
let relative = pattern.unwrap_relative(on_text);
let relative = tilings.unwrap_relative(on_text);
let fill_transform = match relative {
RelativeTo::Self_ => sk::Transform::identity(),
RelativeTo::Parent => state.container_transform.invert().unwrap(),
@ -99,17 +99,17 @@ impl<'a> PatternSampler<'a> {
Self {
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,
pixel_per_pt: state.pixel_per_pt,
}
}
}
impl PaintSampler for PatternSampler<'_> {
impl PaintSampler for TilingSampler<'_> {
/// Samples a single point in a glyph.
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 };
self.transform_to_parent.map_point(&mut point);
@ -118,7 +118,7 @@ impl PaintSampler for PatternSampler<'_> {
let y =
(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()
}
}
@ -218,8 +218,8 @@ pub fn to_sk_paint<'a>(
sk_paint.anti_alias = gradient.anti_alias();
}
Paint::Pattern(pattern) => {
let relative = pattern.unwrap_relative(on_text);
Paint::Tiling(tilings) => {
let relative = tilings.unwrap_relative(on_text);
let fill_transform = match relative {
RelativeTo::Self_ => fill_transform.unwrap_or_default(),
@ -228,7 +228,7 @@ pub fn to_sk_paint<'a>(
.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));
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)
}
pub fn render_pattern_frame(state: &State, pattern: &Pattern) -> sk::Pixmap {
let size = pattern.size() + pattern.spacing();
pub fn render_tiling_frame(state: &State, tilings: &Tiling) -> sk::Pixmap {
let size = tilings.size() + tilings.spacing();
let mut canvas = sk::Pixmap::new(
(size.x.to_f32() * state.pixel_per_pt).round() as u32,
(size.y.to_f32() * state.pixel_per_pt).round() as u32,
)
.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 temp_state = State::new(pattern.size(), ts, state.pixel_per_pt);
crate::render_frame(&mut canvas, temp_state, pattern.frame());
let temp_state = State::new(tilings.size(), ts, state.pixel_per_pt);
crate::render_frame(&mut canvas, temp_state, tilings.frame());
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,
// 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 dash_array = pattern.array.iter().map(|l| l.to_f32()).cycle().take(len).collect();
sk::StrokeDash::new(dash_array, pattern.phase.to_f32())
let dash_array = dash.array.iter().map(|l| l.to_f32()).cycle().take(len).collect();
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::visualize::{FixedStroke, Paint};
use crate::paint::{self, GradientSampler, PaintSampler, PatternSampler};
use crate::paint::{self, GradientSampler, PaintSampler, TilingSampler};
use crate::{shape, AbsExt, State};
/// Render a text run into the canvas.
@ -145,9 +145,9 @@ fn render_outline_glyph(
paint::to_sk_color_u8(*color).premultiply(),
)?;
}
Paint::Pattern(pattern) => {
let pixmap = paint::render_pattern_frame(&state, pattern);
let sampler = PatternSampler::new(pattern, &pixmap, &state, true);
Paint::Tiling(tiling) => {
let pixmap = paint::render_tiling_frame(&state, tiling);
let sampler = TilingSampler::new(tiling, &pixmap, &state, true);
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,
Transform,
};
use typst_library::visualize::{Geometry, Gradient, Pattern};
use typst_library::visualize::{Geometry, Gradient, Tiling};
use typst_utils::hash128;
use xmlwriter::XmlWriter;
use crate::paint::{GradientRef, PatternRef, SVGSubGradient};
use crate::paint::{GradientRef, SVGSubGradient, TilingRef};
use crate::text::RenderedGlyph;
/// Export a frame into a SVG file.
@ -92,12 +92,12 @@ struct SVGRenderer {
/// different transforms. Therefore this allows us to reuse the same gradient
/// multiple times.
gradient_refs: Deduplicator<GradientRef>,
/// Deduplicated patterns with transform matrices. They use a reference
/// (`href`) to a "source" pattern instead of being defined inline.
/// This saves a lot of space since patterns are often reused but with
/// Deduplicated tilings with transform matrices. They use a reference
/// (`href`) to a "source" tiling instead of being defined inline.
/// This saves a lot of space since tilings are often reused but with
/// different transforms. Therefore this allows us to reuse the same gradient
/// multiple times.
pattern_refs: Deduplicator<PatternRef>,
tiling_refs: Deduplicator<TilingRef>,
/// These are the actual gradients being written in the SVG file.
/// These gradients are deduplicated because they do not contain the transform
/// 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 angle of the gradient.
gradients: Deduplicator<(Gradient, Ratio)>,
/// These are the actual patterns being written in the SVG file.
/// These patterns are deduplicated because they do not contain the transform
/// These are the actual tilings being written in the SVG file.
/// These tilings are deduplicated because they do not contain the transform
/// matrix, allowing them to be reused across multiple invocations.
///
/// The `String` is the rendered pattern frame.
patterns: Deduplicator<Pattern>,
/// The `String` is the rendered tiling frame.
tilings: Deduplicator<Tiling>,
/// These are the gradients that compose a conic gradient.
conic_subgradients: Deduplicator<SVGSubGradient>,
}
@ -163,8 +163,8 @@ impl SVGRenderer {
gradient_refs: Deduplicator::new('g'),
gradients: Deduplicator::new('f'),
conic_subgradients: Deduplicator::new('s'),
pattern_refs: Deduplicator::new('p'),
patterns: Deduplicator::new('t'),
tiling_refs: Deduplicator::new('p'),
tilings: Deduplicator::new('t'),
}
}
@ -272,8 +272,8 @@ impl SVGRenderer {
self.write_gradients();
self.write_gradient_refs();
self.write_subgradients();
self.write_patterns();
self.write_pattern_refs();
self.write_tilings();
self.write_tiling_refs();
self.xml.end_document()
}

View File

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

View File

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

View File

@ -165,7 +165,7 @@ impl SVGRenderer {
)
.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::Parent => state.transform.invert().unwrap(),
},

View File

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

View File

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

View File

@ -1077,7 +1077,7 @@ fn expr_with_paren(p: &mut Parser, atomic: bool) {
/// Parses either
/// - a parenthesized expression: `(1 + 2)`, 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 {
let mut state = GroupState {
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
## 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
colors is now a named parameter **(Breaking change)**
- Added support for the [Oklch]($color.oklch) color space

View File

@ -37,7 +37,7 @@ description: Changes in Typst 0.11.1
## Export
- 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
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.
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
we want to build zebra tables.
- 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 }
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.
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 ---
#let double-line = pattern(size: (1.5pt, 1.5pt), {
--- grid-stroke-tiling ---
#let double-line = tiling(size: (1.5pt, 1.5pt), {
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-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-align-array ---

View File

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