Merge branch 'main' into krilla-port

# Conflicts:
#	crates/typst-cli/src/compile.rs
#	crates/typst-pdf/src/catalog.rs
#	crates/typst-pdf/src/color.rs
#	crates/typst-pdf/src/content.rs
#	crates/typst-pdf/src/lib.rs
#	crates/typst-pdf/src/resources.rs
#	crates/typst-pdf/src/tiling.rs
This commit is contained in:
Laurenz Stampfl 2024-12-17 13:29:05 +01:00
commit c5b1a61c7f
80 changed files with 730 additions and 822 deletions

View File

@ -123,6 +123,36 @@ pub fn named_items<T>(
} }
} }
if let Some(v) = parent.cast::<ast::Closure>().filter(|v| {
// Check if the node is in the body of the closure.
let body = parent.find(v.body().span());
body.is_some_and(|n| n.find(node.span()).is_some())
}) {
for param in v.params().children() {
match param {
ast::Param::Pos(pattern) => {
for ident in pattern.bindings() {
if let Some(t) = recv(NamedItem::Var(ident)) {
return Some(t);
}
}
}
ast::Param::Named(n) => {
if let Some(t) = recv(NamedItem::Var(n.name())) {
return Some(t);
}
}
ast::Param::Spread(s) => {
if let Some(sink_ident) = s.sink_ident() {
if let Some(t) = recv(NamedItem::Var(sink_ident)) {
return Some(t);
}
}
}
}
}
}
ancestor = Some(parent.clone()); ancestor = Some(parent.clone());
continue; continue;
} }
@ -269,6 +299,17 @@ mod tests {
assert!(!has_named_items(r#"#let a = 1;#let b = 2;"#, 8, "b")); assert!(!has_named_items(r#"#let a = 1;#let b = 2;"#, 8, "b"));
} }
#[test]
fn test_param_named_items() {
// Has named items
assert!(has_named_items(r#"#let f(a) = 1;#let b = 2;"#, 12, "a"));
assert!(has_named_items(r#"#let f(a: b) = 1;#let b = 2;"#, 15, "a"));
// Doesn't have named items
assert!(!has_named_items(r#"#let f(a) = 1;#let b = 2;"#, 19, "a"));
assert!(!has_named_items(r#"#let f(a: b) = 1;#let b = 2;"#, 15, "b"));
}
#[test] #[test]
fn test_import_named_items() { fn test_import_named_items() {
// Cannot test much. // Cannot test much.

View File

@ -17,7 +17,7 @@ pub fn distribute(composer: &mut Composer, regions: Regions) -> FlowResult<Frame
regions, regions,
items: vec![], items: vec![],
sticky: None, sticky: None,
stickable: false, stickable: None,
}; };
let init = distributor.snapshot(); let init = distributor.snapshot();
let forced = match distributor.run() { let forced = match distributor.run() {
@ -42,9 +42,26 @@ struct Distributor<'a, 'b, 'x, 'y, 'z> {
/// A snapshot which can be restored to migrate a suffix of sticky blocks to /// A snapshot which can be restored to migrate a suffix of sticky blocks to
/// the next region. /// the next region.
sticky: Option<DistributionSnapshot<'a, 'b>>, sticky: Option<DistributionSnapshot<'a, 'b>>,
/// Whether there was at least one proper block. Otherwise, sticky blocks /// Whether the current group of consecutive sticky blocks are still sticky
/// are disabled (or else they'd keep being migrated). /// and may migrate with the attached frame. This is `None` while we aren't
stickable: bool, /// processing sticky blocks. On the first sticky block, this will become
/// `Some(true)` if migrating sticky blocks as usual would make a
/// difference - this is given by `regions.may_progress()`. Otherwise, it
/// is set to `Some(false)`, which is usually the case when the first
/// sticky block in the group is at the very top of the page (then,
/// migrating it would just lead us back to the top of the page, leading
/// to an infinite loop). In that case, all sticky blocks of the group are
/// also disabled, until this is reset to `None` on the first non-sticky
/// frame we find.
///
/// While this behavior of disabling stickiness of sticky blocks at the
/// very top of the page may seem non-ideal, it is only problematic (that
/// is, may lead to orphaned sticky blocks / headings) if the combination
/// of 'sticky blocks + attached frame' doesn't fit in one page, in which
/// case there is nothing Typst can do to improve the situation, as sticky
/// blocks are supposed to always be in the same page as the subsequent
/// frame, but that is impossible in that case, which is thus pathological.
stickable: Option<bool>,
} }
/// A snapshot of the distribution state. /// A snapshot of the distribution state.
@ -314,13 +331,31 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
// If the frame is sticky and we haven't remembered a preceding // If the frame is sticky and we haven't remembered a preceding
// sticky element, make a checkpoint which we can restore should we // sticky element, make a checkpoint which we can restore should we
// end on this sticky element. // end on this sticky element.
if self.stickable && self.sticky.is_none() { //
// The first sticky block within consecutive sticky blocks
// determines whether this group of sticky blocks has stickiness
// disabled or not.
//
// The criteria used here is: if migrating this group of sticky
// blocks together with the "attached" block can't improve the lack
// of space, since we're at the start of the region, then we don't
// do so, and stickiness is disabled (at least, for this region).
// Otherwise, migration is allowed.
//
// Note that, since the whole region is checked, this ensures sticky
// blocks at the top of a block - but not necessarily of the page -
// can still be migrated.
if self.sticky.is_none()
&& *self.stickable.get_or_insert_with(|| self.regions.may_progress())
{
self.sticky = Some(self.snapshot()); self.sticky = Some(self.snapshot());
} }
} else if !frame.is_empty() { } else if !frame.is_empty() {
// If the frame isn't sticky, we can forget a previous snapshot. // If the frame isn't sticky, we can forget a previous snapshot. We
self.stickable = true; // interrupt a group of sticky blocks, if there was one, so we reset
// the saved stickable check for the next group of sticky blocks.
self.sticky = None; self.sticky = None;
self.stickable = None;
} }
// Handle footnotes. // Handle footnotes.

View File

@ -74,6 +74,7 @@ pub fn layout_enum(
regions: Regions, regions: Regions,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
let numbering = elem.numbering(styles); let numbering = elem.numbering(styles);
let reversed = elem.reversed(styles);
let indent = elem.indent(styles); let indent = elem.indent(styles);
let body_indent = elem.body_indent(styles); let body_indent = elem.body_indent(styles);
let gutter = elem.spacing(styles).unwrap_or_else(|| { let gutter = elem.spacing(styles).unwrap_or_else(|| {
@ -86,7 +87,9 @@ pub fn layout_enum(
let mut cells = vec![]; let mut cells = vec![];
let mut locator = locator.split(); let mut locator = locator.split();
let mut number = elem.start(styles); let mut number =
elem.start(styles)
.unwrap_or_else(|| if reversed { elem.children.len() } else { 1 });
let mut parents = EnumElem::parents_in(styles); let mut parents = EnumElem::parents_in(styles);
let full = elem.full(styles); let full = elem.full(styles);
@ -127,7 +130,8 @@ pub fn layout_enum(
item.body.clone().styled(EnumElem::set_parents(smallvec![number])), item.body.clone().styled(EnumElem::set_parents(smallvec![number])),
locator.next(&item.body.span()), locator.next(&item.body.span()),
)); ));
number = number.saturating_add(1); number =
if reversed { number.saturating_sub(1) } else { number.saturating_add(1) };
} }
let grid = CellGrid::new( let grid = CellGrid::new(

View File

@ -33,8 +33,17 @@ pub fn layout_repeat(
let fill = region.size.x; let fill = region.size.x;
let width = piece.width(); let width = piece.width();
// count * width + (count - 1) * gap = fill, but count is an integer so // We need to fit the body N times, but the number of gaps is (N - 1):
// we need to round down and get the remainder. // N * w + (N - 1) * g ≤ F
// where N - body count (count)
// w - body width (width)
// g - gap width (gap)
// F - available space to fill (fill)
//
// N * w + N * g - g ≤ F
// N * (w + g) ≤ F + g
// N ≤ (F + g) / (w + g)
// N = ⌊(F + g) / (w + g)⌋
let count = ((fill + gap) / (width + gap)).floor(); let count = ((fill + gap) / (width + gap)).floor();
let remaining = (fill + gap) % (width + gap); let remaining = (fill + gap) % (width + gap);
@ -52,7 +61,7 @@ pub fn layout_repeat(
if width > Abs::zero() { if width > Abs::zero() {
for _ in 0..(count as usize).min(1000) { for _ in 0..(count as usize).min(1000) {
frame.push_frame(Point::with_x(offset), piece.clone()); frame.push_frame(Point::with_x(offset), piece.clone());
offset += piece.width() + gap; offset += width + gap;
} }
} }

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

@ -119,7 +119,6 @@ pub(super) fn define(global: &mut Scope, inputs: Dict, features: &Features) {
global.define_func::<panic>(); global.define_func::<panic>();
global.define_func::<assert>(); global.define_func::<assert>();
global.define_func::<eval>(); global.define_func::<eval>();
global.define_func::<style>();
if features.is_enabled(Feature::Html) { if features.is_enabled(Feature::Html) {
global.define_func::<target>(); global.define_func::<target>();
} }

View File

@ -36,11 +36,6 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
(Symbol(a), Content(b)) => Content(TextElem::packed(a.get()) + b), (Symbol(a), Content(b)) => Content(TextElem::packed(a.get()) + b),
(Array(a), Array(b)) => Array(a + b), (Array(a), Array(b)) => Array(a + b),
(Dict(a), Dict(b)) => Dict(a + b), (Dict(a), Dict(b)) => Dict(a + b),
// Type compatibility.
(Type(a), Str(b)) => Str(format_str!("{a}{b}")),
(Str(a), Type(b)) => Str(format_str!("{a}{b}")),
(a, b) => mismatch!("cannot join {} with {}", a, b), (a, b) => mismatch!("cannot join {} with {}", a, b),
}) })
} }
@ -149,18 +144,14 @@ 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),
(Datetime(a), Duration(b)) => Datetime(a + b), (Datetime(a), Duration(b)) => Datetime(a + b),
(Duration(a), Datetime(b)) => Datetime(b + a), (Duration(a), Datetime(b)) => Datetime(b + a),
// Type compatibility.
(Type(a), Str(b)) => Str(format_str!("{a}{b}")),
(Str(a), Type(b)) => Str(format_str!("{a}{b}")),
(Dyn(a), Dyn(b)) => { (Dyn(a), Dyn(b)) => {
// Alignments can be summed. // Alignments can be summed.
if let (Some(&a), Some(&b)) = if let (Some(&a), Some(&b)) =
@ -469,9 +460,6 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool {
rat == rel.rel && rel.abs.is_zero() rat == rel.rel && rel.abs.is_zero()
} }
// Type compatibility.
(Type(ty), Str(str)) | (Str(str), Type(ty)) => ty.compat_name() == str.as_str(),
_ => false, _ => false,
} }
} }
@ -569,10 +557,6 @@ pub fn contains(lhs: &Value, rhs: &Value) -> Option<bool> {
(Str(a), Dict(b)) => Some(b.contains(a)), (Str(a), Dict(b)) => Some(b.contains(a)),
(a, Array(b)) => Some(b.contains(a.clone())), (a, Array(b)) => Some(b.contains(a.clone())),
// Type compatibility.
(Type(a), Str(b)) => Some(b.as_str().contains(a.compat_name())),
(Type(a), Dict(b)) => Some(b.contains(a.compat_name())),
_ => Option::None, _ => Option::None,
} }
} }

View File

@ -3,75 +3,19 @@ use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::{mem, ptr}; use std::{mem, ptr};
use comemo::{Track, Tracked}; use comemo::Tracked;
use ecow::{eco_vec, EcoString, EcoVec}; use ecow::{eco_vec, EcoString, EcoVec};
use smallvec::SmallVec; use smallvec::SmallVec;
use typst_syntax::Span; use typst_syntax::Span;
use typst_utils::LazyHash; use typst_utils::LazyHash;
use crate::diag::{warning, SourceResult, Trace, Tracepoint}; use crate::diag::{SourceResult, Trace, Tracepoint};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
cast, elem, func, ty, Content, Context, Element, Func, NativeElement, Packed, Repr, cast, ty, Content, Context, Element, Func, NativeElement, Repr, Selector,
Selector, Show,
}; };
use crate::introspection::Locatable;
use crate::text::{FontFamily, FontList, TextElem}; use crate::text::{FontFamily, FontList, TextElem};
/// Provides access to active styles.
///
/// **Deprecation planned.** Use [context] instead.
///
/// ```example
/// #let thing(body) = style(styles => {
/// let size = measure(body, styles)
/// [Width of "#body" is #size.width]
/// })
///
/// #thing[Hey] \
/// #thing[Welcome]
/// ```
#[func]
pub fn style(
/// The engine.
engine: &mut Engine,
/// The call site span.
span: Span,
/// A function to call with the styles. Its return value is displayed
/// in the document.
///
/// This function is called once for each time the content returned by
/// `style` appears in the document. That makes it possible to generate
/// content that depends on the style context it appears in.
func: Func,
) -> Content {
engine.sink.warn(warning!(
span, "`style` is deprecated";
hint: "use a `context` expression instead"
));
StyleElem::new(func).pack().spanned(span)
}
/// Executes a style access.
#[elem(Locatable, Show)]
struct StyleElem {
/// The function to call with the styles.
#[required]
func: Func,
}
impl Show for Packed<StyleElem> {
#[typst_macros::time(name = "style", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let context = Context::new(self.location(), Some(styles));
Ok(self
.func()
.call(engine, context.track(), [styles.to_map()])?
.display())
}
}
/// A list of style properties. /// A list of style properties.
#[ty(cast)] #[ty(cast)]
#[derive(Default, PartialEq, Clone, Hash)] #[derive(Default, PartialEq, Clone, Hash)]

View File

@ -246,8 +246,48 @@ impl Debug for List {
impl crate::foundations::Repr for Symbol { impl crate::foundations::Repr for Symbol {
fn repr(&self) -> EcoString { fn repr(&self) -> EcoString {
eco_format!("\"{}\"", self.get()) match &self.0 {
Repr::Single(c) => eco_format!("symbol(\"{}\")", *c),
Repr::Complex(variants) => {
eco_format!("symbol{}", repr_variants(variants.iter().copied(), ""))
} }
Repr::Modified(arc) => {
let (list, modifiers) = arc.as_ref();
if modifiers.is_empty() {
eco_format!("symbol{}", repr_variants(list.variants(), ""))
} else {
eco_format!("symbol{}", repr_variants(list.variants(), modifiers))
}
}
}
}
}
fn repr_variants<'a>(
variants: impl Iterator<Item = (&'a str, char)>,
applied_modifiers: &str,
) -> String {
crate::foundations::repr::pretty_array_like(
&variants
.filter(|(variant, _)| {
// Only keep variants that can still be accessed, i.e., variants
// that contain all applied modifiers.
parts(applied_modifiers).all(|am| variant.split('.').any(|m| m == am))
})
.map(|(variant, c)| {
let trimmed_variant = variant
.split('.')
.filter(|&m| parts(applied_modifiers).all(|am| m != am));
if trimmed_variant.clone().all(|m| m.is_empty()) {
eco_format!("\"{c}\"")
} else {
let trimmed_modifiers = trimmed_variant.collect::<Vec<_>>().join(".");
eco_format!("(\"{}\", \"{}\")", trimmed_modifiers, c)
}
})
.collect::<Vec<_>>(),
false,
)
} }
impl Serialize for Symbol { impl Serialize for Symbol {

View File

@ -44,16 +44,6 @@ use crate::foundations::{
/// #type(int) \ /// #type(int) \
/// #type(type) /// #type(type)
/// ``` /// ```
///
/// # Compatibility
/// In Typst 0.7 and lower, the `type` function returned a string instead of a
/// type. Compatibility with the old way will remain for a while to give package
/// authors time to upgrade, but it will be removed at some point.
///
/// - Checks like `{int == "integer"}` evaluate to `{true}`
/// - Adding/joining a type and string will yield a string
/// - The `{in}` operator on a type and a dictionary will evaluate to `{true}`
/// if the dictionary has a string key matching the type's name
#[ty(scope, cast)] #[ty(scope, cast)]
#[derive(Copy, Clone, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Type(Static<NativeTypeData>); pub struct Type(Static<NativeTypeData>);
@ -111,14 +101,6 @@ impl Type {
} }
} }
// Type compatibility.
impl Type {
/// The type's backward-compatible name.
pub fn compat_name(&self) -> &str {
self.long_name()
}
}
#[scope] #[scope]
impl Type { impl Type {
/// Determines a value's type. /// Determines a value's type.

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

@ -122,8 +122,8 @@ impl HtmlTag {
let bytes = string.as_bytes(); let bytes = string.as_bytes();
let mut i = 0; let mut i = 0;
while i < bytes.len() { while i < bytes.len() {
if !bytes[i].is_ascii_alphanumeric() { if !bytes[i].is_ascii() || !charsets::is_valid_in_tag_name(bytes[i] as char) {
panic!("constant tag name must be ASCII alphanumeric"); panic!("not all characters are valid in a tag name");
} }
i += 1; i += 1;
} }
@ -220,8 +220,10 @@ impl HtmlAttr {
let bytes = string.as_bytes(); let bytes = string.as_bytes();
let mut i = 0; let mut i = 0;
while i < bytes.len() { while i < bytes.len() {
if !bytes[i].is_ascii_alphanumeric() { if !bytes[i].is_ascii()
panic!("constant attribute name must be ASCII alphanumeric"); || !charsets::is_valid_in_attribute_name(bytes[i] as char)
{
panic!("not all characters are valid in an attribute name");
} }
i += 1; i += 1;
} }
@ -621,5 +623,9 @@ pub mod attr {
href href
name name
value value
role
} }
#[allow(non_upper_case_globals)]
pub const aria_level: HtmlAttr = HtmlAttr::constant("aria-level");
} }

View File

@ -7,7 +7,7 @@ use smallvec::{smallvec, SmallVec};
use typst_syntax::Span; use typst_syntax::Span;
use typst_utils::NonZeroExt; use typst_utils::NonZeroExt;
use crate::diag::{bail, warning, At, HintedStrResult, SourceResult}; use crate::diag::{bail, At, HintedStrResult, SourceResult};
use crate::engine::{Engine, Route, Sink, Traced}; use crate::engine::{Engine, Route, Sink, Traced};
use crate::foundations::{ use crate::foundations::{
cast, elem, func, scope, select_where, ty, Args, Array, Construct, Content, Context, cast, elem, func, scope, select_where, ty, Args, Array, Construct, Content, Context,
@ -353,7 +353,7 @@ impl Counter {
} }
/// Shared implementation of displaying between `counter.display` and /// Shared implementation of displaying between `counter.display` and
/// `DisplayElem`, which will be deprecated. /// `CounterDisplayElem`.
fn display_impl( fn display_impl(
&self, &self,
engine: &mut Engine, engine: &mut Engine,
@ -366,10 +366,9 @@ impl Counter {
.custom() .custom()
.or_else(|| { .or_else(|| {
let styles = styles?; let styles = styles?;
let CounterKey::Selector(Selector::Elem(func, _)) = self.0 else { match self.0 {
return None; CounterKey::Page => PageElem::numbering_in(styles).clone(),
}; CounterKey::Selector(Selector::Elem(func, _)) => {
if func == HeadingElem::elem() { if func == HeadingElem::elem() {
HeadingElem::numbering_in(styles).clone() HeadingElem::numbering_in(styles).clone()
} else if func == FigureElem::elem() { } else if func == FigureElem::elem() {
@ -381,6 +380,9 @@ impl Counter {
} else { } else {
None None
} }
}
_ => None,
}
}) })
.unwrap_or_else(|| NumberingPattern::from_str("1.1").unwrap().into()); .unwrap_or_else(|| NumberingPattern::from_str("1.1").unwrap().into());
@ -439,11 +441,6 @@ impl Counter {
/// Displays the current value of the counter with a numbering and returns /// Displays the current value of the counter with a numbering and returns
/// the formatted output. /// the formatted output.
///
/// _Compatibility:_ For compatibility with Typst 0.10 and lower, this
/// function also works without an established context. Then, it will create
/// opaque contextual content rather than directly returning the output of
/// the numbering. This behaviour will be removed in a future release.
#[func(contextual)] #[func(contextual)]
pub fn display( pub fn display(
self, self,
@ -472,19 +469,8 @@ impl Counter {
#[default(false)] #[default(false)]
both: bool, both: bool,
) -> SourceResult<Value> { ) -> SourceResult<Value> {
if let Ok(loc) = context.location() { let loc = context.location().at(span)?;
self.display_impl(engine, loc, numbering, both, context.styles().ok()) self.display_impl(engine, loc, numbering, both, context.styles().ok())
} else {
engine.sink.warn(warning!(
span, "`counter.display` without context is deprecated";
hint: "use it in a `context` expression instead"
));
Ok(CounterDisplayElem::new(self, numbering, both)
.pack()
.spanned(span)
.into_value())
}
} }
/// Retrieves the value of the counter at the given location. Always returns /// Retrieves the value of the counter at the given location. Always returns
@ -493,10 +479,6 @@ impl Counter {
/// The `selector` must match exactly one element in the document. The most /// The `selector` must match exactly one element in the document. The most
/// useful kinds of selectors for this are [labels]($label) and /// useful kinds of selectors for this are [labels]($label) and
/// [locations]($location). /// [locations]($location).
///
/// _Compatibility:_ For compatibility with Typst 0.10 and lower, this
/// function also works without a known context if the `selector` is a
/// location. This behaviour will be removed in a future release.
#[func(contextual)] #[func(contextual)]
pub fn at( pub fn at(
&self, &self,
@ -524,21 +506,8 @@ impl Counter {
context: Tracked<Context>, context: Tracked<Context>,
/// The callsite span. /// The callsite span.
span: Span, span: Span,
/// _Compatibility:_ This argument is deprecated. It only exists for
/// compatibility with Typst 0.10 and lower and shouldn't be used
/// anymore.
#[default]
location: Option<Location>,
) -> SourceResult<CounterState> { ) -> SourceResult<CounterState> {
if location.is_none() { context.introspect().at(span)?;
context.location().at(span)?;
} else {
engine.sink.warn(warning!(
span, "calling `counter.final` with a location is deprecated";
hint: "try removing the location argument"
));
}
let sequence = self.sequence(engine)?; let sequence = self.sequence(engine)?;
let (mut state, page) = sequence.last().unwrap().clone(); let (mut state, page) = sequence.last().unwrap().clone();
if self.is_page() { if self.is_page() {
@ -759,8 +728,6 @@ impl Count for Packed<CounterUpdateElem> {
} }
/// Executes a display of a counter. /// Executes a display of a counter.
///
/// **Deprecation planned.**
#[elem(Construct, Locatable, Show)] #[elem(Construct, Locatable, Show)]
pub struct CounterDisplayElem { pub struct CounterDisplayElem {
/// The counter. /// The counter.
@ -786,7 +753,6 @@ impl Construct for CounterDisplayElem {
} }
impl Show for Packed<CounterDisplayElem> { impl Show for Packed<CounterDisplayElem> {
#[typst_macros::time(name = "counter.display", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
Ok(self Ok(self
.counter .counter

View File

@ -1,13 +1,9 @@
use comemo::{Track, Tracked}; use comemo::Tracked;
use typst_syntax::Span;
use crate::diag::{warning, HintedStrResult, SourceResult}; use crate::diag::HintedStrResult;
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{func, Context, LocatableSelector};
cast, elem, func, Content, Context, Func, LocatableSelector, NativeElement, Packed, use crate::introspection::Location;
Show, StyleChain, Value,
};
use crate::introspection::{Locatable, Location};
/// Determines the location of an element in the document. /// Determines the location of an element in the document.
/// ///
@ -26,23 +22,12 @@ use crate::introspection::{Locatable, Location};
/// ///
/// = Introduction <intro> /// = Introduction <intro>
/// ``` /// ```
///
/// # Compatibility
/// In Typst 0.10 and lower, the `locate` function took a closure that made the
/// current location in the document available (like [`here`] does now). This
/// usage pattern is deprecated. Compatibility with the old way will remain for
/// a while to give package authors time to upgrade. To that effect, `locate`
/// detects whether it received a selector or a user-defined function and
/// adjusts its semantics accordingly. This behaviour will be removed in the
/// future.
#[func(contextual)] #[func(contextual)]
pub fn locate( pub fn locate(
/// The engine. /// The engine.
engine: &mut Engine, engine: &mut Engine,
/// The callsite context. /// The callsite context.
context: Tracked<Context>, context: Tracked<Context>,
/// The span of the `locate` call.
span: Span,
/// A selector that should match exactly one element. This element will be /// A selector that should match exactly one element. This element will be
/// located. /// located.
/// ///
@ -50,70 +35,7 @@ pub fn locate(
/// - [`here`] to locate the current context, /// - [`here`] to locate the current context,
/// - a [`location`] retrieved from some queried element via the /// - a [`location`] retrieved from some queried element via the
/// [`location()`]($content.location) method on content. /// [`location()`]($content.location) method on content.
selector: LocateInput, selector: LocatableSelector,
) -> HintedStrResult<LocateOutput> { ) -> HintedStrResult<Location> {
Ok(match selector { selector.resolve_unique(engine.introspector, context)
LocateInput::Selector(selector) => {
LocateOutput::Location(selector.resolve_unique(engine.introspector, context)?)
}
LocateInput::Func(func) => {
engine.sink.warn(warning!(
span, "`locate` with callback function is deprecated";
hint: "use a `context` expression instead"
));
LocateOutput::Content(LocateElem::new(func).pack().spanned(span))
}
})
}
/// Compatible input type.
pub enum LocateInput {
Selector(LocatableSelector),
Func(Func),
}
cast! {
LocateInput,
v: Func => {
if v.element().is_some() {
Self::Selector(Value::Func(v).cast()?)
} else {
Self::Func(v)
}
},
v: LocatableSelector => Self::Selector(v),
}
/// Compatible output type.
pub enum LocateOutput {
Location(Location),
Content(Content),
}
cast! {
LocateOutput,
self => match self {
Self::Location(v) => v.into_value(),
Self::Content(v) => v.into_value(),
},
v: Location => Self::Location(v),
v: Content => Self::Content(v),
}
/// Executes a `locate` call.
#[elem(Locatable, Show)]
struct LocateElem {
/// The function to call with the location.
#[required]
func: Func,
}
impl Show for Packed<LocateElem> {
#[typst_macros::time(name = "locate", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let location = self.location().unwrap();
let context = Context::new(Some(location), Some(styles));
Ok(self.func().call(engine, context.track(), [location])?.display())
}
} }

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

@ -1,10 +1,8 @@
use comemo::Tracked; use comemo::Tracked;
use typst_syntax::Span;
use crate::diag::{warning, HintedStrResult}; use crate::diag::HintedStrResult;
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{func, Array, Context, LocatableSelector, Value}; use crate::foundations::{func, Array, Context, LocatableSelector, Value};
use crate::introspection::Location;
/// Finds elements in the document. /// Finds elements in the document.
/// ///
@ -142,8 +140,6 @@ pub fn query(
engine: &mut Engine, engine: &mut Engine,
/// The callsite context. /// The callsite context.
context: Tracked<Context>, context: Tracked<Context>,
/// The span of the `query` call.
span: Span,
/// Can be /// Can be
/// - an element function like a `heading` or `figure`, /// - an element function like a `heading` or `figure`,
/// - a `{<label>}`, /// - a `{<label>}`,
@ -152,20 +148,8 @@ pub fn query(
/// ///
/// Only [locatable]($location/#locatable) element functions are supported. /// Only [locatable]($location/#locatable) element functions are supported.
target: LocatableSelector, target: LocatableSelector,
/// _Compatibility:_ This argument is deprecated. It only exists for
/// compatibility with Typst 0.10 and lower and shouldn't be used anymore.
#[default]
location: Option<Location>,
) -> HintedStrResult<Array> { ) -> HintedStrResult<Array> {
if location.is_none() {
context.introspect()?; context.introspect()?;
} else {
engine.sink.warn(warning!(
span, "calling `query` with a location is deprecated";
hint: "try removing the location argument"
));
}
let vec = engine.introspector.query(&target.0); let vec = engine.introspector.query(&target.0);
Ok(vec.into_iter().map(Value::Content).collect()) Ok(vec.into_iter().map(Value::Content).collect())
} }

View File

@ -2,7 +2,7 @@ use comemo::{Track, Tracked, TrackedMut};
use ecow::{eco_format, eco_vec, EcoString, EcoVec}; use ecow::{eco_format, eco_vec, EcoString, EcoVec};
use typst_syntax::Span; use typst_syntax::Span;
use crate::diag::{bail, warning, At, SourceResult}; use crate::diag::{bail, At, SourceResult};
use crate::engine::{Engine, Route, Sink, Traced}; use crate::engine::{Engine, Route, Sink, Traced};
use crate::foundations::{ use crate::foundations::{
cast, elem, func, scope, select_where, ty, Args, Construct, Content, Context, Func, cast, elem, func, scope, select_where, ty, Args, Construct, Content, Context, Func,
@ -305,10 +305,6 @@ impl State {
/// The `selector` must match exactly one element in the document. The most /// The `selector` must match exactly one element in the document. The most
/// useful kinds of selectors for this are [labels]($label) and /// useful kinds of selectors for this are [labels]($label) and
/// [locations]($location). /// [locations]($location).
///
/// _Compatibility:_ For compatibility with Typst 0.10 and lower, this
/// function also works without a known context if the `selector` is a
/// location. This behaviour will be removed in a future release.
#[typst_macros::time(name = "state.at", span = span)] #[typst_macros::time(name = "state.at", span = span)]
#[func(contextual)] #[func(contextual)]
pub fn at( pub fn at(
@ -336,21 +332,8 @@ impl State {
context: Tracked<Context>, context: Tracked<Context>,
/// The callsite span. /// The callsite span.
span: Span, span: Span,
/// _Compatibility:_ This argument is deprecated. It only exists for
/// compatibility with Typst 0.10 and lower and shouldn't be used
/// anymore.
#[default]
location: Option<Location>,
) -> SourceResult<Value> { ) -> SourceResult<Value> {
if location.is_none() { context.introspect().at(span)?;
context.location().at(span)?;
} else {
engine.sink.warn(warning!(
span, "calling `state.final` with a location is deprecated";
hint: "try removing the location argument"
));
}
let sequence = self.sequence(engine)?; let sequence = self.sequence(engine)?;
Ok(sequence.last().unwrap().clone()) Ok(sequence.last().unwrap().clone())
} }
@ -375,30 +358,6 @@ impl State {
) -> Content { ) -> Content {
StateUpdateElem::new(self.key, update).pack().spanned(span) StateUpdateElem::new(self.key, update).pack().spanned(span)
} }
/// Displays the current value of the state.
///
/// **Deprecation planned:** Use [`get`]($state.get) instead.
#[func]
pub fn display(
self,
/// The engine.
engine: &mut Engine,
/// The span of the `display` call.
span: Span,
/// A function which receives the value of the state and can return
/// arbitrary content which is then displayed. If this is omitted, the
/// value is directly displayed.
#[default]
func: Option<Func>,
) -> Content {
engine.sink.warn(warning!(
span, "`state.display` is deprecated";
hint: "use `state.get` in a `context` expression instead"
));
StateDisplayElem::new(self, func).pack().spanned(span)
}
} }
impl Repr for State { impl Repr for State {
@ -446,38 +405,3 @@ impl Show for Packed<StateUpdateElem> {
Ok(Content::empty()) Ok(Content::empty())
} }
} }
/// Executes a display of a state.
///
/// **Deprecation planned.**
#[elem(Construct, Locatable, Show)]
struct StateDisplayElem {
/// The state.
#[required]
#[internal]
state: State,
/// The function to display the state with.
#[required]
#[internal]
func: Option<Func>,
}
impl Show for Packed<StateDisplayElem> {
#[typst_macros::time(name = "state.display", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let location = self.location().unwrap();
let context = Context::new(Some(location), Some(styles));
let value = self.state().at_loc(engine, location)?;
Ok(match self.func() {
Some(func) => func.call(engine, context.track(), [value])?.display(),
None => value.display(),
})
}
}
impl Construct for StateDisplayElem {
fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
bail!(args.span, "cannot be constructed manually");
}
}

View File

@ -1,11 +1,9 @@
use comemo::Tracked; use comemo::Tracked;
use typst_syntax::Span; use typst_syntax::Span;
use crate::diag::{warning, At, SourceResult}; use crate::diag::{At, SourceResult};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{dict, func, Content, Context, Dict, Resolve, Smart};
dict, func, Content, Context, Dict, Resolve, Smart, StyleChain, Styles,
};
use crate::introspection::{Locator, LocatorLink}; use crate::introspection::{Locator, LocatorLink};
use crate::layout::{Abs, Axes, Length, Region, Size}; use crate::layout::{Abs, Axes, Length, Region, Size};
@ -76,23 +74,9 @@ pub fn measure(
height: Smart<Length>, height: Smart<Length>,
/// The content whose size to measure. /// The content whose size to measure.
content: Content, content: Content,
/// _Compatibility:_ This argument is deprecated. It only exists for
/// compatibility with Typst 0.10 and lower and shouldn't be used anymore.
#[default]
styles: Option<Styles>,
) -> SourceResult<Dict> { ) -> SourceResult<Dict> {
let styles = match &styles {
Some(styles) => {
engine.sink.warn(warning!(
span, "calling `measure` with a styles argument is deprecated";
hint: "try removing the styles argument"
));
StyleChain::new(styles)
}
None => context.styles().at(span)?,
};
// Create a pod region with the available space. // Create a pod region with the available space.
let styles = context.styles().at(span)?;
let pod = Region::new( let pod = Region::new(
Axes::new( Axes::new(
width.resolve(styles).unwrap_or(Abs::inf()), width.resolve(styles).unwrap_or(Abs::inf()),

View File

@ -9,7 +9,7 @@ use crate::foundations::{
cast, elem, scope, Array, Content, NativeElement, Packed, Show, Smart, StyleChain, cast, elem, scope, Array, Content, NativeElement, Packed, Show, Smart, StyleChain,
Styles, TargetElem, Styles, TargetElem,
}; };
use crate::html::{attr, tag, HtmlElem}; use crate::html::{attr, tag, HtmlAttr, HtmlElem};
use crate::layout::{Alignment, BlockElem, Em, HAlignment, Length, VAlignment, VElem}; use crate::layout::{Alignment, BlockElem, Em, HAlignment, Length, VAlignment, VElem};
use crate::model::{ListItemLike, ListLike, Numbering, NumberingPattern, ParElem}; use crate::model::{ListItemLike, ListLike, Numbering, NumberingPattern, ParElem};
@ -127,8 +127,7 @@ pub struct EnumElem {
/// [Ahead], /// [Ahead],
/// ) /// )
/// ``` /// ```
#[default(1)] pub start: Smart<usize>,
pub start: usize,
/// Whether to display the full numbering, including the numbers of /// Whether to display the full numbering, including the numbers of
/// all parent enumerations. /// all parent enumerations.
@ -144,6 +143,17 @@ pub struct EnumElem {
#[default(false)] #[default(false)]
pub full: bool, pub full: bool,
/// Whether to reverse the numbering for this enumeration.
///
/// ```example
/// #set enum(reversed: true)
/// + Coffee
/// + Tea
/// + Milk
/// ```
#[default(false)]
pub reversed: bool,
/// The indentation of each item. /// The indentation of each item.
#[resolve] #[resolve]
pub indent: Length, pub indent: Length,
@ -217,7 +227,12 @@ impl EnumElem {
impl Show for Packed<EnumElem> { impl Show for Packed<EnumElem> {
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
if TargetElem::target_in(styles).is_html() { if TargetElem::target_in(styles).is_html() {
return Ok(HtmlElem::new(tag::ol) let mut elem = HtmlElem::new(tag::ol);
if self.reversed(styles) {
elem =
elem.with_attr(const { HtmlAttr::constant("reversed") }, "reversed");
}
return Ok(elem
.with_body(Some(Content::sequence(self.children.iter().map(|item| { .with_body(Some(Content::sequence(self.children.iter().map(|item| {
let mut li = HtmlElem::new(tag::li); let mut li = HtmlElem::new(tag::li);
if let Some(nr) = item.number(styles) { if let Some(nr) = item.number(styles) {

View File

@ -1,14 +1,15 @@
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use ecow::eco_format;
use typst_utils::NonZeroExt; use typst_utils::NonZeroExt;
use crate::diag::SourceResult; use crate::diag::{warning, SourceResult};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
elem, Content, NativeElement, Packed, Resolve, Show, ShowSet, Smart, StyleChain, elem, Content, NativeElement, Packed, Resolve, Show, ShowSet, Smart, StyleChain,
Styles, Synthesize, TargetElem, Styles, Synthesize, TargetElem,
}; };
use crate::html::{tag, HtmlElem}; use crate::html::{attr, tag, HtmlElem};
use crate::introspection::{ use crate::introspection::{
Count, Counter, CounterUpdate, Locatable, Locator, LocatorLink, Count, Counter, CounterUpdate, Locatable, Locator, LocatorLink,
}; };
@ -272,9 +273,26 @@ impl Show for Packed<HeadingElem> {
// Meanwhile, a level 1 Typst heading is a section heading. For this // Meanwhile, a level 1 Typst heading is a section heading. For this
// reason, levels are offset by one: A Typst level 1 heading becomes // reason, levels are offset by one: A Typst level 1 heading becomes
// a `<h2>`. // a `<h2>`.
let level = self.resolve_level(styles); let level = self.resolve_level(styles).get();
let t = [tag::h2, tag::h3, tag::h4, tag::h5, tag::h6][level.get().min(5) - 1]; if level >= 6 {
engine.sink.warn(warning!(span,
"heading of level {} was transformed to \
<div role=\"heading\" aria-level=\"{}\">, which is not \
supported by all assistive technology",
level, level + 1;
hint: "HTML only supports <h1> to <h6>, not <h{}>", level + 1;
hint: "you may want to restructure your document so that \
it doesn't contain deep headings"));
HtmlElem::new(tag::div)
.with_body(Some(realized))
.with_attr(attr::role, "heading")
.with_attr(attr::aria_level, eco_format!("{}", level + 1))
.pack()
.spanned(span)
} else {
let t = [tag::h2, tag::h3, tag::h4, tag::h5, tag::h6][level - 1];
HtmlElem::new(t).with_body(Some(realized)).pack().spanned(span) HtmlElem::new(t).with_body(Some(realized)).pack().spanned(span)
}
} else { } else {
let realized = BlockBody::Content(realized); let realized = BlockBody::Content(realized);
BlockElem::new().with_body(Some(realized)).pack().spanned(span) BlockElem::new().with_body(Some(realized)).pack().spanned(span)

View File

@ -137,10 +137,6 @@ pub struct OutlineElem {
/// `{n => n * 2em}` would be equivalent to just specifying `{2em}`, while /// `{n => n * 2em}` would be equivalent to just specifying `{2em}`, while
/// `{n => [→ ] * n}` would indent with one arrow per nesting level. /// `{n => [→ ] * n}` would indent with one arrow per nesting level.
/// ///
/// *Migration hints:* Specifying `{true}` (equivalent to `{auto}`) or
/// `{false}` (equivalent to `{none}`) for this option is deprecated and
/// will be removed in a future release.
///
/// ```example /// ```example
/// #set heading(numbering: "1.a.") /// #set heading(numbering: "1.a.")
/// ///
@ -294,7 +290,6 @@ pub trait Outlinable: Refable {
/// Defines how an outline is indented. /// Defines how an outline is indented.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq, Hash)]
pub enum OutlineIndent { pub enum OutlineIndent {
Bool(bool),
Rel(Rel<Length>), Rel(Rel<Length>),
Func(Func), Func(Func),
} }
@ -310,10 +305,10 @@ impl OutlineIndent {
) -> SourceResult<()> { ) -> SourceResult<()> {
match indent { match indent {
// 'none' | 'false' => no indenting // 'none' | 'false' => no indenting
None | Some(Smart::Custom(OutlineIndent::Bool(false))) => {} None => {}
// 'auto' | 'true' => use numbering alignment for indenting // 'auto' | 'true' => use numbering alignment for indenting
Some(Smart::Auto | Smart::Custom(OutlineIndent::Bool(true))) => { Some(Smart::Auto) => {
// Add hidden ancestors numberings to realize the indent. // Add hidden ancestors numberings to realize the indent.
let mut hidden = Content::empty(); let mut hidden = Content::empty();
for ancestor in ancestors { for ancestor in ancestors {
@ -368,11 +363,9 @@ impl OutlineIndent {
cast! { cast! {
OutlineIndent, OutlineIndent,
self => match self { self => match self {
Self::Bool(v) => v.into_value(),
Self::Rel(v) => v.into_value(), Self::Rel(v) => v.into_value(),
Self::Func(v) => v.into_value() Self::Func(v) => v.into_value()
}, },
v: bool => OutlineIndent::Bool(v),
v: Rel<Length> => OutlineIndent::Rel(v), v: Rel<Length> => OutlineIndent::Rel(v),
v: Func => OutlineIndent::Func(v), v: Func => OutlineIndent::Func(v),
} }

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

@ -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,30 +86,29 @@ 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(pattern_size.x.to_pt() / size.x.to_pt()), Ratio::new(tiling_size.x.to_pt() / size.x.to_pt()),
Ratio::new(pattern_size.y.to_pt() / size.y.to_pt()), Ratio::new(tiling_size.y.to_pt() / size.y.to_pt()),
), ),
}) })
} }
@ -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

@ -59,13 +59,13 @@ _Thanks to [@PgBiel](https://github.com/PgBiel) for his work on tables!_
- When context is available, [`counter.display`] now directly returns the result - When context is available, [`counter.display`] now directly returns the result
of applying the numbering instead of yielding opaque content. It should not be of applying the numbering instead of yielding opaque content. It should not be
used anymore without context. (Deprecation planned) used anymore without context. (Deprecation planned)
- The [`state.display`] function should not be used anymore, use [`state.get`] - The `state.display` function should not be used anymore, use [`state.get`]
instead (Deprecation planned) instead (Deprecation planned)
- The `location` argument of [`query`], [`counter.final`], and [`state.final`] - The `location` argument of [`query`], [`counter.final`], and [`state.final`]
should not be used anymore (Deprecation planned) should not be used anymore (Deprecation planned)
- The [`styles`]($measure.styles) argument of the `measure` function should not - The `styles` argument of the `measure` function should not be used anymore
be used anymore (Deprecation planned) (Deprecation planned)
- The [`style`] function should not be used anymore, use context instead - The `style` function should not be used anymore, use context instead
(Deprecation planned) (Deprecation planned)
- The correct context is now also provided in various other places where it is - The correct context is now also provided in various other places where it is
available, e.g. in show rules, layout callbacks, and numbering functions in available, e.g. in show rules, layout callbacks, and numbering functions in

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

@ -380,11 +380,11 @@ description: Changes in Typst 0.12.0
- [`counter.display`] without an established context - [`counter.display`] without an established context
- [`counter.final`] with a location - [`counter.final`] with a location
- [`state.final`] with a location - [`state.final`] with a location
- [`state.display`] - `state.display`
- [`query`] with a location as the second argument - [`query`] with a location as the second argument
- [`locate`] with a callback function - [`locate`] with a callback function
- [`measure`] with styles - [`measure`] with styles
- [`style`] - `style`
## Development ## Development
- Added `typst-kit` crate which provides useful APIs for `World` implementors - Added `typst-kit` crate which provides useful APIs for `World` implementors

View File

@ -83,7 +83,7 @@ description: Changes in early, unversioned Typst
- New [`measure`] function - New [`measure`] function
- Measure the layouted size of elements - Measure the layouted size of elements
- To be used in combination with the new [`style`] function that lets you - To be used in combination with the new `style` function that lets you
generate different content based on the style context something is inserted generate different content based on the style context something is inserted
into (because that affects the measured size of content) into (because that affects the measured size of content)

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 6.9 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

@ -37,46 +37,6 @@
// Error: 11-12 variables from outside the context expression are read-only and cannot be modified // Error: 11-12 variables from outside the context expression are read-only and cannot be modified
#context (i = 1) #context (i = 1)
--- context-compatibility-locate ---
#let s = state("x", 0)
#let compute(expr) = [
#s.update(x =>
eval(expr.replace("x", str(x)))
)
// Warning: 17-28 `state.display` is deprecated
// Hint: 17-28 use `state.get` in a `context` expression instead
New value is #s.display().
]
// Warning: 1:2-6:3 `locate` with callback function is deprecated
// Hint: 1:2-6:3 use a `context` expression instead
#locate(loc => {
// Warning: 14-32 calling `query` with a location is deprecated
// Hint: 14-32 try removing the location argument
let elem = query(<here>, loc).first()
test(s.at(elem.location()), 13)
})
#compute("10") \
#compute("x + 3") \
*Here.* <here> \
#compute("x * 2") \
#compute("x - 5")
--- context-compatibility-styling ---
// Warning: 2-53 `style` is deprecated
// Hint: 2-53 use a `context` expression instead
// Warning: 18-39 calling `measure` with a styles argument is deprecated
// Hint: 18-39 try removing the styles argument
#style(styles => measure([it], styles).width < 20pt)
--- context-compatibility-counter-display ---
#counter(heading).update(10)
// Warning: 2-44 `counter.display` without context is deprecated
// Hint: 2-44 use it in a `context` expression instead
#counter(heading).display(n => test(n, 10))
--- context-delayed-warning --- --- context-delayed-warning ---
// Ensure that the warning that triggers in the first layout iteration is not // Ensure that the warning that triggers in the first layout iteration is not
// surfaced since it goes away in the second one. Just like errors in show // surfaced since it goes away in the second one. Just like errors in show

View File

@ -3,14 +3,6 @@
#test(type(ltr), direction) #test(type(ltr), direction)
#test(type(10 / 3), float) #test(type(10 / 3), float)
--- type-string-compatibility ---
#test(type(10), int)
#test(type(10), "integer")
#test("is " + type(10), "is integer")
#test(int in ("integer", "string"), true)
#test(int in "integers or strings", true)
#test(str in "integers or strings", true)
--- issue-3110-type-constructor --- --- issue-3110-type-constructor ---
// Let the error message report the type name. // Let the error message report the type name.
// Error: 2-9 type content does not have a constructor // Error: 2-9 type content does not have a constructor

View File

@ -102,6 +102,14 @@ B
#set page(numbering: "1 / 1", margin: (bottom: 20pt)) #set page(numbering: "1 / 1", margin: (bottom: 20pt))
#counter(page).update(5) #counter(page).update(5)
--- counter-page-display ---
// Counter display should use numbering from style chain.
#set page(
numbering: "A",
margin: (bottom: 20pt),
footer: context align(center, counter(page).display())
)
--- counter-figure --- --- counter-figure ---
// Count figures. // Count figures.
#figure(numbering: "A", caption: [Four 'A's], kind: image, supplement: "Figure")[_AAAA!_] #figure(numbering: "A", caption: [Four 'A's], kind: image, supplement: "Figure")[_AAAA!_]

View File

@ -279,3 +279,28 @@ First!
// Test box in 100% width block. // Test box in 100% width block.
#block(width: 100%, fill: red, box("a box")) #block(width: 100%, fill: red, box("a box"))
#block(width: 100%, fill: red, [#box("a box") #box()]) #block(width: 100%, fill: red, [#box("a box") #box()])
--- issue-5296-block-sticky-in-block-at-top ---
#set page(height: 3cm)
#v(1.6cm)
#block(height: 2cm, breakable: true)[
#block(sticky: true)[*A*]
b
]
--- issue-5296-block-sticky-spaced-from-top-of-page ---
#set page(height: 3cm)
#v(2cm)
#block(sticky: true)[*A*]
b
--- issue-5296-block-sticky-weakly-spaced-from-top-of-page ---
#set page(height: 3cm)
#v(2cm, weak: true)
#block(sticky: true)[*A*]
b

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

@ -71,6 +71,23 @@ a + 0.
+ First + First
+ Nested + Nested
--- enum-numbering-reversed ---
// Test reverse numbering.
#set enum(reversed: true)
+ Coffee
+ Tea
+ Milk
--- enum-numbering-reversed-overriden ---
// Test reverse numbering with overriden numbers.
#set enum(reversed: true)
+ A
+ B
+ C
9. D
+ E
+ F
--- enum-numbering-closure --- --- enum-numbering-closure ---
// Test numbering with closure. // Test numbering with closure.
#enum( #enum(

View File

@ -43,8 +43,6 @@ A
#set outline(fill: none) #set outline(fill: none)
#context test(outline.indent, none) #context test(outline.indent, none)
#outline(indent: false)
#outline(indent: true)
#outline(indent: none) #outline(indent: none)
#outline(indent: auto) #outline(indent: auto)
#outline(indent: 2em) #outline(indent: 2em)
@ -62,8 +60,6 @@ A
#show heading: none #show heading: none
#set outline(fill: none) #set outline(fill: none)
#outline(indent: false)
#outline(indent: true)
#outline(indent: none) #outline(indent: none)
#outline(indent: auto) #outline(indent: auto)
#outline(indent: n => 2em * n) #outline(indent: n => 2em * n)

View File

@ -13,12 +13,8 @@ fi
#let c = counter("mycounter") #let c = counter("mycounter")
#c.update(1) #c.update(1)
// Warning: 1:2-7:3 `locate` with callback function is deprecated #context [
// Hint: 1:2-7:3 use a `context` expression instead
#locate(loc => [
#c.update(2) #c.update(2)
#c.at(loc) \ #c.get() \
// Warning: 12-36 `locate` with callback function is deprecated Second: #context c.get()
// Hint: 12-36 use a `context` expression instead ]
Second: #locate(loc => c.at(loc))
])

View File

@ -49,3 +49,65 @@
--- symbol-unknown-modifier --- --- symbol-unknown-modifier ---
// Error: 13-20 unknown symbol modifier // Error: 13-20 unknown symbol modifier
#emoji.face.garbage #emoji.face.garbage
--- symbol-repr ---
#test(
repr(sym.amp),
`symbol("&", ("inv", "⅋"))`.text,
)
#test(
repr(sym.amp.inv),
`symbol("⅋")`.text,
)
#test(
repr(sym.arrow.double.r),
```
symbol(
"⇒",
("bar", "⤇"),
("long", "⟹"),
("long.bar", "⟾"),
("not", "⇏"),
("l", "⇔"),
("l.long", "⟺"),
("l.not", "⇎"),
)
```.text,
)
#test(repr(sym.smash), "symbol(\"\")")
#let envelope = symbol(
"🖂",
("stamped", "🖃"),
("stamped.pen", "🖆"),
("lightning", "🖄"),
("fly", "🖅"),
)
#test(
repr(envelope),
```
symbol(
"🖂",
("stamped", "🖃"),
("stamped.pen", "🖆"),
("lightning", "🖄"),
("fly", "🖅"),
)
```.text,
)
#test(
repr(envelope.stamped),
`symbol("🖃", ("pen", "🖆"))`.text,
)
#test(
repr(envelope.stamped.pen),
`symbol("🖆")`.text,
)
#test(
repr(envelope.lightning),
`symbol("🖄")`.text,
)
#test(
repr(envelope.fly),
`symbol("🖅")`.text,
)

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)