Touch up docs

This commit is contained in:
Laurenz 2023-04-26 13:46:42 +02:00
parent 59957746e9
commit 3680c854a2
38 changed files with 451 additions and 303 deletions

View File

@ -29,6 +29,7 @@ flate2 = "1"
fontdb = "0.9"
if_chain = "1"
image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] }
indexmap = "1.9.3"
log = "0.4"
miniz_oxide = "0.7"
once_cell = "1"
@ -43,6 +44,7 @@ siphasher = "0.3"
subsetter = "0.1.1"
svg2pdf = "0.4"
tiny-skia = "0.6.6"
tracing = "0.1.37"
ttf-parser = "0.18.1"
unicode-math-class = "0.1"
unicode-segmentation = "1"
@ -50,8 +52,6 @@ unicode-xid = "0.2"
unscanny = "0.1"
usvg = { version = "0.22", default-features = false, features = ["text"] }
xmp-writer = "0.1"
tracing = "0.1.37"
indexmap = "1.9.3"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
stacker = "0.1.15"

View File

@ -0,0 +1,3 @@
title = "Secret project"
version = 2
authors = ["Mr Robert", "Miss Enola"]

View File

@ -1,11 +0,0 @@
authors = ["Mr Robert", "Miss Enola", "Mr Jonathan"]
version = "1.0.3"
series = [
{ name = "attack on titan", fans = 500},
{ name = "demon slayer", fans = 10}
]
[informations]
location = "room A204"
pages = 47

View File

@ -8,4 +8,4 @@ inline_table = { first = "amazing", second = "greater" }
[table]
element = 5
others = [false, "indeed", 7]
others = [false, "indeed", 7]

View File

@ -22,24 +22,24 @@ doc = false
typst = { path = ".." }
typst-library = { path = "../library" }
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
clap = { version = "4.2.4", features = ["derive", "env"] }
codespan-reporting = "0.11"
comemo = "0.2.2"
dirs = "5"
elsa = "1.8"
inferno = "0.11.15"
memmap2 = "0.5"
notify = "5"
once_cell = "1"
open = "4.0.2"
same-file = "1"
siphasher = "0.3"
walkdir = "2"
clap = { version = "4.2.4", features = ["derive", "env"] }
open = "4.0.2"
tracing = "0.1.37"
tracing-subscriber = "0.3.17"
tracing-flame = "0.2.0"
tracing-error = "0.2"
inferno = "0.11.15"
tempfile = "3.5.0"
tracing = "0.1.37"
tracing-error = "0.2"
tracing-flame = "0.2.0"
tracing-subscriber = "0.3.17"
walkdir = "2"
[build-dependencies]
clap = { version = "4.2.4", features = ["derive", "string"] }

View File

@ -12,14 +12,14 @@ bench = false
[dependencies]
typst = { path = ".." }
typst-library = { path = "../library" }
unscanny = "0.1"
include_dir = "0.7"
pulldown-cmark = "0.9"
comemo = "0.2.2"
heck = "0.4"
include_dir = "0.7"
once_cell = "1"
pulldown-cmark = "0.9"
serde = { version = "1", features = ["derive"] }
serde_yaml = "0.8"
heck = "0.4"
yaml-front-matter = "0.1"
unicode_names2 = "0.6.0"
once_cell = "1"
unscanny = "0.1"
ureq = { version = "2.6", features = ["json"] }
yaml-front-matter = "0.1"

View File

@ -300,7 +300,7 @@ pub struct FuncModel {
pub name: &'static str,
pub display: &'static str,
pub oneliner: &'static str,
pub showable: bool,
pub element: bool,
pub details: Html,
pub params: Vec<ParamModel>,
pub returns: Vec<&'static str>,
@ -340,7 +340,7 @@ fn func_model(resolver: &dyn Resolver, func: &Func, info: &FuncInfo) -> FuncMode
name: info.name,
display: info.display,
oneliner: oneliner(docs),
showable: func.element().is_some(),
element: func.element().is_some(),
details: Html::markdown(resolver, docs),
params: info.params.iter().map(|param| param_model(resolver, param)).collect(),
returns: info.returns.clone(),

View File

@ -64,7 +64,7 @@ Content and code blocks can be nested arbitrarily. In the example below,
}
```
## Let bindings { #bindings }
## Bindings and Destructuring { #bindings }
As already demonstrated above, variables can be defined with `{let}` bindings.
The variable is assigned the value of the expression that follows the `=` sign.
The assignment of a value is optional, if no value is assigned, the variable
@ -82,7 +82,10 @@ Sum is #add(2, 3).
```
Let bindings can also be used to destructure [arrays]($type/array) and
[dictionaries]($type/dictionary).
[dictionaries]($type/dictionary). In this case, the left-hand side of the
assignment should mirror an array or dictionary. The `..` operator can be used
once in the pattern to collect the remainder of the array's or dictionary's
items.
```example
#let (x, y) = (1, 2)
@ -117,6 +120,28 @@ You can use the underscore to discard elements in a destructuring pattern:
The y coordinate is #y.
```
Destructuring also work in argument lists of functions ...
```example
#let left = (2, 4, 5)
#let right = (3, 2, 6)
#left.zip(right).map(
((a,b)) => a + b
)
```
... and on the left-hand side of normal assignments. This can be useful to
swap variables among other things.
```example
#{
let a = 1
let b = 2
(a, b) = (b, a)
[a = #a, b = #b]
}
```
## Conditionals { #conditionals }
With a conditional, you can display or compute different things depending on
whether some condition is fulfilled. Typst supports `{if}`, `{else if}` and
@ -206,12 +231,19 @@ can be either:
- a [dictionary]($type/dictionary) that has the specified key,
- a [symbol]($type/symbol) that has the specified modifier,
- a [module]($type/module) containing the specified definition,
- [content]($type/content) that has the specified field.
- [content]($type/content) consisting of an element that has the specified
field. The available fields match the arguments of the
[element function]($type/function/#element-functions) that were given when
the element was constructed.
```example
#let dict = (greet: "Hello")
#dict.greet \
#emoji.face
#let it = [= Heading]
#it.body \
#it.level
```
## Methods { #methods }

View File

@ -12,11 +12,12 @@ of elements.
## Set rules { #set-rules }
With set rules, you can customize the appearance of elements. They are written
as a [function call]($type/function) to the respective function preceded by the
`{set}` keyword (or `[#set]` in markup). Only optional parameters of that
function can be provided to the set rule. Refer to each function's documentation
to see which parameters are optional. In the example below, we use two set rules
to change the [font family]($func/text.family) and
as a [function call]($type/function) to an
[element function]($type/function/#element-functions) preceded by the `{set}`
keyword (or `[#set]` in markup). Only optional parameters of that function can
be provided to the set rule. Refer to each function's documentation to see which
parameters are optional. In the example below, we use two set rules to change
the [font family]($func/text.family) and
[heading numbering]($func/heading.numbering).
```example
@ -62,9 +63,10 @@ a _set-if_ rule.
## Show rules { #show-rules }
With show rules, you can deeply customize the look of a type of element. The
most basic form of show rule is a _show-set rule._ Such a rule is written as the
`{show}` keyword followed by a function name, a colon and then a set rule. This
lets the set rule only apply to the selected element. In the example below,
headings become dark blue while all other text stays black.
`{show}` keyword followed by a [selector]($type/selector), a colon and then a set rule. The most basic form of selector is an
[element function]($type/function/#element-functions). This lets the set rule
only apply to the selected element. In the example below, headings become dark
blue while all other text stays black.
```example
#show heading: set text(navy)
@ -78,7 +80,7 @@ achieve many different effects. But they still limit you to what is predefined
in Typst. For maximum flexibility, you can instead write a show rule that
defines how to format an element from scratch. To write such a show rule,
replace the set rule behind the colon with an arbitrary
[function]($type/function). This functions receives the element in question and
[function]($type/function). This function receives the element in question and
can return arbitrary content. Different
[fields]($scripting/#fields) are available on the element passed
to the function. Below, we define a show rule that formats headings for a

View File

@ -644,14 +644,14 @@ Folds all items into a single value using an accumulator function.
Sums all items (works for any types that can be added).
- default: any (named)
If set and the array is empty, sum will return this.
What to return if the array is empty. Must be set if the array can be empty.
- returns: any
### product()
Calculates the product all items (works for any types that can be multiplied)
- default: any (named)
If set and the array is empty, sum will return this.
What to return if the array is empty. Must be set if the array can be empty.
- returns: any
### any()
@ -814,6 +814,13 @@ documentation about [set]($styling/#set-rules) and
[show]($styling/#show-rules) rules to learn about additional ways
you can work with functions in Typst.
### Element functions { #element-functions }
Some functions are associated with _elements_ like [headings]($func/heading) or
[tables]($func/table). When called, these create an element of their respective
kind. In contrast to normal functions, they can further be used in
[set rules]($styling/#set-rules), [show rules]($styling/#show-rules), and
[selectors]($type/selector).
### Defining functions { #definitions }
You can define your own function with a
[let binding]($scripting/#bindings) that has a parameter list after
@ -916,30 +923,11 @@ Returns the captured named arguments as a dictionary.
- returns: dictionary
# Module
An evaluated module, either built-in or resulting from a file.
You can access definitions from the module using
[field access notation]($scripting/#fields) and interact with it using the
[import and include syntaxes]($scripting/#modules).
## Example
```example
<<< #import "utils.typ"
<<< #utils.add(2, 5)
<<< #import utils: sub
<<< #sub(1, 4)
>>> #7
>>>
>>> #(-3)
```
# Selector
A filter for selecting elements within the document.
You can construct a selector in the following ways:
- you can use an element function
- you can use an element [function]($type/function)
- you can filter for an element function with
[specific fields]($type/function.where)
- you can use a [string]($type/string) or [regular expression]($func/regex)
@ -948,13 +936,16 @@ You can construct a selector in the following ways:
- call the [`selector`]($func/selector) function to convert any of the above
types into a selector value and use the methods below to refine it
A selector is what you can use to query the document for certain types
of elements. It can also be used to apply styling rules to element. You can
combine multiple selectors using the methods shown below.
Selectors are used to [apply styling rules]($styling/#show-rules) to elements.
You can also use selectors to [query]($func/query) the document for certain
types of elements.
Selectors can also be passed to several of Typst's built-in functions to
Furthermore, you can pass a selector to several of Typst's built-in functions to
configure their behaviour. One such example is the [outline]($func/outline)
where it can be use to change which elements are listed within the outline.
where it can be used to change which elements are listed within the outline.
Multiple selectors can be combined using the methods shown below. However, not
all kinds of selectors are supported in all places, at the moment.
## Example
```example
@ -1004,3 +995,22 @@ first match of the selector argument.
- inclusive: boolean (named)
Whether `start` itself should match or not. This is only relevant if both
selectors match the same type of element. Defaults to `{true}`.
# Module
An evaluated module, either built-in or resulting from a file.
You can access definitions from the module using
[field access notation]($scripting/#fields) and interact with it using the
[import and include syntaxes]($scripting/#modules).
## Example
```example
<<< #import "utils.typ"
<<< #utils.add(2, 5)
<<< #import utils: sub
<<< #sub(1, 4)
>>> #7
>>>
>>> #(-3)
```

View File

@ -16,6 +16,7 @@ bench = false
[dependencies]
typst = { path = ".." }
chinese-number = { version = "0.7.2", default-features = false, features = ["number-to-chinese"] }
comemo = "0.2.2"
csv = "1"
ecow = "0.1"
@ -31,6 +32,8 @@ serde_json = "1"
serde_yaml = "0.8"
smallvec = "1.10"
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
toml = { version = "0.7.3", default-features = false, features = ["parse"] }
tracing = "0.1.37"
ttf-parser = "0.18.1"
typed-arena = "2"
unicode-bidi = "0.3.13"
@ -38,6 +41,3 @@ unicode-math-class = "0.1"
unicode-script = "0.5"
unicode-segmentation = "1"
xi-unicode = "0.3"
chinese-number = { version = "0.7.2", default-features = false, features = ["number-to-chinese"] }
tracing = "0.1.37"
toml = { version = "0.7.3", default-features = false, features = ["parse"]}

View File

@ -41,6 +41,7 @@ pub fn module() -> Module {
scope.define("even", even);
scope.define("odd", odd);
scope.define("rem", rem);
scope.define("mod", mod_);
scope.define("quo", quo);
scope.define("inf", Value::Float(f64::INFINITY));
scope.define("nan", Value::Float(f64::NAN));
@ -912,6 +913,27 @@ pub fn rem(
dividend.apply2(divisor.v, Rem::rem, Rem::rem).value()
}
/// Calculate the modulus of two numbers. (Deprecated)
///
/// **This function is deprecated in favor of `rem`. It will be removed in
/// a future update.**
///
/// Display: Modulus
/// Category: calculate
/// Returns: integer or float
#[func]
pub fn mod_(
/// The dividend of the remainder.
dividend: Num,
/// The divisor of the remainder.
divisor: Spanned<Num>,
) -> Value {
if divisor.v.float() == 0.0 {
bail!(divisor.span, "divisor must not be zero");
}
dividend.apply2(divisor.v, Rem::rem, Rem::rem).value()
}
/// Calculate the quotient of two numbers.
///
/// ## Example

View File

@ -201,7 +201,6 @@ fn convert_json(value: serde_json::Value) -> Value {
}
/// Format the user-facing JSON error message.
#[track_caller]
fn format_json_error(error: serde_json::Error) -> EcoString {
assert!(error.is_syntax() || error.is_eof());
eco_format!("failed to parse json file: syntax error in line {}", error.line())
@ -209,30 +208,24 @@ fn format_json_error(error: serde_json::Error) -> EcoString {
/// Read structured data from a TOML file.
///
/// The file must contain a valid TOML table. Tables will be
/// The file must contain a valid TOML table. TOML tables will be
/// converted into Typst dictionaries, and TOML arrays will be converted into
/// Typst arrays. Strings and booleans will be converted into the Typst
/// equivalents, numbers will be converted to floats or integers depending on
/// whether they are whole numbers. TOML DateTim will be converted to strings.
/// equivalents and numbers will be converted to floats or integers depending on
/// whether they are whole numbers. For the time being, datetimes will be
/// converted to strings as Typst does not have a built-in datetime yet.
///
/// The function returns a dictionary.
///
/// The JSON files in the example contain objects with the keys `temperature`,
/// `unit`, and `weather`.
/// The TOML file in the example consists of a table with the keys `title`,
/// `version`, and `authors`.
///
/// ## Example
/// ```example
/// #let informations(content) = {
/// [This work is made by #content.authors.join(", ", last: " and "). We are currently at version #content.version.
/// The favorites series of the audience are ]
/// for serie in content.series [
/// - #serie.name with #serie.fans fans.
/// ]
/// [We need to submit our work in #content.informations.location, we currently have #content.informations.pages pages.]
/// }
/// #let details = toml("details.toml")
///
///
/// #informations(toml("informations.toml"))
/// Title: #details.title \
/// Version: #details.version \
/// Authors: #(details.authors
/// .join(", ", last: " and "))
/// ```
///
/// Display: TOML
@ -268,23 +261,22 @@ fn convert_toml(value: toml::Value) -> Value {
.map(|(key, value)| (key.into(), convert_toml(value)))
.collect(),
),
// Todo: make it use native date/time object(s) once it is implemented.
// TODO: Make it use native date/time object(s) once it is implemented.
toml::Value::Datetime(v) => Value::Str(v.to_string().into()),
}
}
/// Format the user-facing TOML error message.
#[track_caller]
fn format_toml_error(error: toml::de::Error) -> String {
fn format_toml_error(error: toml::de::Error) -> EcoString {
if let Some(range) = error.span() {
format!(
"failed to parse toml file: {message}, index {start}-{end}",
message = error.message(),
start = range.start,
end = range.end
eco_format!(
"failed to parse toml file: {}, index {}-{}",
error.message(),
range.start,
range.end
)
} else {
format!("failed to parse toml file: {message}", message = error.message())
eco_format!("failed to parse toml file: {}", error.message())
}
}
@ -373,7 +365,6 @@ fn convert_yaml_key(key: serde_yaml::Value) -> Option<Str> {
}
/// Format the user-facing YAML error message.
#[track_caller]
fn format_yaml_error(error: serde_yaml::Error) -> EcoString {
eco_format!("failed to parse yaml file: {}", error.to_string().trim())
}

View File

@ -31,6 +31,9 @@ pub fn type_(
/// in monospace with syntax-highlighting. The exceptions are `{none}`,
/// integers, floats, strings, content, and functions.
///
/// **Note:** This function is for debugging purposes. Its output should not be
/// considered stable and may change at any time!
///
/// ## Example
/// ```example
/// #none vs #repr(none) \

View File

@ -5,6 +5,9 @@ use crate::prelude::*;
/// The `measure` function lets you determine the layouted size of content.
/// Note that an infinite space is assumed, therefore the measured height/width
/// may not necessarily match the final height/width of the measured content.
/// If you want to measure in the current layout dimensions, you can combined
/// `measure` and [`layout`]($func/layout).
///
/// The same content can have a different size depending on the styles that
/// are active when it is layouted. For example, in the example below
/// `[#content]` is of course bigger when we increase the font size.

View File

@ -8,10 +8,6 @@ use crate::prelude::*;
/// descriptions span over multiple lines, they use hanging indent to
/// communicate the visual hierarchy.
///
/// ## Syntax
/// This function also has dedicated syntax: Starting a line with a slash,
/// followed by a term, a colon and a description creates a term list item.
///
/// ## Example
/// ```example
/// / Ligature: A merged glyph.
@ -19,6 +15,10 @@ use crate::prelude::*;
/// between two adjacent letters.
/// ```
///
/// ## Syntax
/// This function also has dedicated syntax: Starting a line with a slash,
/// followed by a term, a colon and a description creates a term list item.
///
/// Display: Term List
/// Category: layout
#[element(Layout)]

View File

@ -228,25 +228,25 @@ fn items() -> LangItems {
equation: |body, block| math::EquationElem::new(body).with_block(block).pack(),
math_align_point: || math::AlignPointElem::new().pack(),
math_delimited: |open, body, close| math::LrElem::new(open + body + close).pack(),
math_attach: |base, top, bottom, topleft, bottomleft, topright, bottomright| {
math_attach: |base, t, b, tl, bl, tr, br| {
let mut elem = math::AttachElem::new(base);
if let Some(top) = top {
elem.push_t(Some(top));
if let Some(t) = t {
elem.push_t(Some(t));
}
if let Some(bottom) = bottom {
elem.push_b(Some(bottom));
if let Some(b) = b {
elem.push_b(Some(b));
}
if let Some(topleft) = topleft {
elem.push_tl(Some(topleft));
if let Some(tl) = tl {
elem.push_tl(Some(tl));
}
if let Some(bottomleft) = bottomleft {
elem.push_bl(Some(bottomleft));
if let Some(bl) = bl {
elem.push_bl(Some(bl));
}
if let Some(topright) = topright {
elem.push_tr(Some(topright));
if let Some(tr) = tr {
elem.push_tr(Some(tr));
}
if let Some(bottomright) = bottomright {
elem.push_br(Some(bottomright));
if let Some(br) = br {
elem.push_br(Some(br));
}
elem.pack()
},

View File

@ -2,16 +2,23 @@ use super::*;
/// A base with optional attachments.
///
/// ## Syntax
/// This function also has dedicated syntax for attachments after the base: Use the
/// underscore (`_`) to indicate a subscript i.e. bottom attachment and the hat (`^`)
/// to indicate a superscript i.e. top attachment.
///
/// ## Example
/// ```example
/// // With syntax.
/// $ sum_(i=0)^n a_i = 2^(1+i) $
///
/// // With function call.
/// $ attach(
/// Pi, t: alpha, b: beta,
/// tl: 1, tr: 2, bl: 3, br: 4,
/// ) $
/// ```
///
/// ## Syntax
/// This function also has dedicated syntax for attachments after the base: Use
/// the underscore (`_`) to indicate a subscript i.e. bottom attachment and the
/// hat (`^`) to indicate a superscript i.e. top attachment.
///
/// Display: Attachment
/// Category: math
#[element(LayoutMath)]
@ -21,41 +28,42 @@ pub struct AttachElem {
pub base: Content,
/// The top attachment, smartly positioned at top-right or above the base.
/// Use limits() or scripts() on the base to override the smart positioning.
///
/// You can wrap the base in `{limits()}` or `{scripts()}` to override the
/// smart positioning.
pub t: Option<Content>,
/// The bottom attachment, smartly positioned at the bottom-right or below the base.
/// Use limits() or scripts() on the base to override the smart positioning.
/// The bottom attachment, smartly positioned at the bottom-right or below
/// the base. You can wrap the base in `{limits()}` or `{scripts()}` to
/// override the smart positioning.
pub b: Option<Content>,
/// The top-left attachment before the base.
/// The top-left attachment (before the base).
pub tl: Option<Content>,
/// The bottom-left attachment before base.
/// The bottom-left attachment (before base).
pub bl: Option<Content>,
/// The top-right attachment after the base.
/// The top-right attachment (after the base).
pub tr: Option<Content>,
/// The bottom-right attachment after the base.
/// The bottom-right attachment (after the base).
pub br: Option<Content>,
}
type GetAttachmentContent =
fn(&AttachElem, styles: ::typst::model::StyleChain) -> Option<Content>;
impl LayoutMath for AttachElem {
#[tracing::instrument(skip(ctx))]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
let base = ctx.layout_fragment(&self.base())?;
let getarg = |ctx: &mut MathContext, getter: GetAttachmentContent| {
type GetAttachment = fn(&AttachElem, styles: StyleChain) -> Option<Content>;
let getarg = |ctx: &mut MathContext, getter: GetAttachment| {
getter(self, ctx.styles())
.map(|elem| ctx.layout_fragment(&elem))
.transpose()
.unwrap()
};
let base = ctx.layout_fragment(&self.base())?;
ctx.style(ctx.style.for_superscript());
let arg_tl = getarg(ctx, Self::tl);
let arg_tr = getarg(ctx, Self::tr);

View File

@ -2,10 +2,14 @@ use super::*;
/// Displays a diagonal line over a part of an equation.
///
/// This is commonly used to show the eliminiation of a term.
///
/// ## Example
/// ```example
/// >>> #set page(width: 140pt)
/// Here, we can simplify:
/// $ (a dot.c b dot.c cancel(x)) / cancel(x) $
/// $ (a dot b dot cancel(x)) /
/// cancel(x) $
/// ```
///
/// Display: Cancel
@ -23,18 +27,22 @@ pub struct CancelElem {
/// Defaults to `{100% + 3pt}`.
///
/// ```example
/// $ a + cancel(x, length: #200%) - b - cancel(x, length: #200%) $
/// >>> #set page(width: 140pt)
/// $ a + cancel(x, length: #200%)
/// - cancel(x, length: #200%) $
/// ```
#[default(Rel::new(Ratio::one(), Abs::pt(3.0).into()))]
pub length: Rel<Length>,
/// If the cancel line should be inverted (heading northwest instead of
/// northeast).
/// If the cancel line should be inverted (pointing to the top left instead
/// of top right).
///
/// Defaults to `{false}`.
///
/// ```example
/// $ (a cancel((b + c), inverted: #true)) / cancel(b + c, inverted: #true) $
/// >>> #set page(width: 140pt)
/// $ (a cancel((b + c), inverted: #true)) /
/// cancel(b + c, inverted: #true) $
/// ```
#[default(false)]
pub inverted: bool,
@ -45,7 +53,8 @@ pub struct CancelElem {
/// Defaults to `{false}`.
///
/// ```example
/// $ cancel(x, cross: #true) $
/// >>> #set page(width: 140pt)
/// $ cancel(Pi, cross: #true) $
/// ```
#[default(false)]
pub cross: bool,
@ -54,7 +63,8 @@ pub struct CancelElem {
/// [line's documentation]($func/line.angle) for more details.
///
/// ```example
/// $ cancel(x, rotation: #30deg) $
/// >>> #set page(width: 140pt)
/// $ cancel(Pi, rotation: #30deg) $
/// ```
#[default(Angle::zero())]
pub rotation: Angle,
@ -63,7 +73,15 @@ pub struct CancelElem {
/// [line's documentation]($func/line.stroke) for more details.
///
/// ```example
/// $ cancel(x, stroke: #{red + 1.5pt}) $
/// >>> #set page(width: 140pt)
/// $ cancel(
/// sum x,
/// stroke: #(
/// paint: red,
/// thickness: 1.5pt,
/// dash: "dashed",
/// ),
/// ) $
/// ```
#[resolve]
#[fold]

View File

@ -19,7 +19,7 @@ use crate::text::TextElem;
/// If you just want to link to a labelled element and not get an automatic
/// textual reference, consider using the [`link`]($func/link) function instead.
///
/// # Example
/// ## Example
/// ```example
/// #set heading(numbering: "1.")
/// #set math.equation(numbering: "(1)")
@ -51,6 +51,36 @@ use crate::text::TextElem;
/// To customize the supplement, add content in square brackets after the
/// reference: `[@intro[Chapter]]`.
///
/// ## Customization
/// If you write a show rule for references, you can access the referenced
/// element through the `element` field of the reference. The `element` may
/// be `{none}` even if it exists if Typst hasn't discovered it yet, so you
/// always need to handle that case in your code.
///
/// ```example
/// #set heading(numbering: "1.")
/// #set math.equation(numbering: "(1)")
///
/// #show ref: it => {
/// let eq = math.equation
/// let el = it.element
/// if el != none and el.func() == eq {
/// // Override equation references.
/// numbering(
/// el.numbering,
/// ..counter(eq).at(el.location())
/// )
/// } else {
/// // Other references as usual.
/// it
/// }
/// }
///
/// = Beginnings <beginning>
/// In @beginning we prove @pythagoras.
/// $ a^2 + b^2 = c^2 $ <pythagoras>
/// ```
///
/// Display: Reference
/// Category: meta
#[element(Synthesize, Locatable, Show)]
@ -86,35 +116,7 @@ pub struct RefElem {
#[synthesized]
pub citation: Option<CiteElem>,
/// Content of the element, it should be referable.
///
/// ```example
/// #set heading(numbering: (..nums) => {
/// nums.pos().map(str).join(".")
/// }, supplement: [Chapt])
///
/// #show ref: it => {
/// if it.has("element") and it.element.func() == heading {
/// let element = it.element
/// "["
/// element.supplement
/// "-"
/// numbering(element.numbering, ..counter(heading).at(element.location()))
/// "]"
/// } else {
/// it
/// }
/// }
///
/// = Introduction <intro>
/// = Summary <sum>
/// == Subsection <sub>
/// @intro
///
/// @sum
///
/// @sub
/// ```
/// The referenced element.
#[synthesized]
pub element: Option<Content>,
}
@ -123,22 +125,14 @@ impl Synthesize for RefElem {
fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
let citation = self.to_citation(vt, styles)?;
self.push_citation(Some(citation));
self.push_element(None);
if !vt.introspector.init() {
self.push_element(None);
return Ok(());
}
// find the element content
let target = self.target();
let elem = vt.introspector.query_label(&self.target());
// not in bibliography, but in document, then push the element
if let (false, Ok(elem)) =
(BibliographyElem::has(vt, &target.0), elem.at(self.span()))
{
self.push_element(Some(elem));
} else {
self.push_element(None);
if vt.introspector.init() && !BibliographyElem::has(vt, &target.0) {
if let Ok(elem) = vt.introspector.query_label(&target) {
self.push_element(Some(elem));
return Ok(());
}
}
Ok(())

View File

@ -9,19 +9,11 @@ use crate::layout::BlockElem;
use crate::meta::{Figurable, LocalName};
use crate::prelude::*;
/// Raw text with optional syntax highlighting.
/// Raw text with optionalw syntax highlighting.
///
/// Displays the text verbatim and in a monospace font. This is typically used
/// to embed computer code into your document.
///
/// ## Syntax
/// This function also has dedicated syntax. You can enclose text in 1 or 3+
/// backticks (`` ` ``) to make it raw. Two backticks produce empty raw text.
/// When you use three or more backticks, you can additionally specify a
/// language tag for syntax highlighting directly after the opening backticks.
/// Within raw blocks, everything is rendered as is, in particular, there are no
/// escape sequences.
///
/// ## Example
/// ````example
/// Adding `rbx` to `rcx` gives
@ -34,6 +26,14 @@ use crate::prelude::*;
/// ```
/// ````
///
/// ## Syntax
/// This function also has dedicated syntax. You can enclose text in 1 or 3+
/// backticks (`` ` ``) to make it raw. Two backticks produce empty raw text.
/// When you use three or more backticks, you can additionally specify a
/// language tag for syntax highlighting directly after the opening backticks.
/// Within raw blocks, everything is rendered as is, in particular, there are no
/// escape sequences.
///
/// Display: Raw Text / Code
/// Category: text
#[element(Synthesize, Show, Finalize, LocalName, Figurable)]

View File

@ -5,8 +5,13 @@ use crate::prelude::*;
/// ## Example
/// ```example
/// #set page(height: 100pt)
///
/// #line(length: 100%)
/// #line(end: (50%, 50%))
/// #line(
/// length: 4cm,
/// stroke: 2pt + maroon,
/// )
/// ```
///
/// Display: Line
@ -41,30 +46,37 @@ pub struct LineElem {
/// - A stroke combined from color and thickness using the `+` operator as
/// in `{2pt + red}`.
/// - A stroke described by a dictionary with any of the following keys:
/// - `color`: the color to use for the stroke
/// - `thickness`: the stroke's thickness
/// - `cap`: one of `"butt"`, `"round"` or `"square"`, the line cap of the stroke
/// - `join`: one of `"miter"`, `"round"` or `"bevel"`, the line join of the stroke
/// - `miter-limit`: the miter limit to use if `join` is `"miter"`, defaults to 4.0
/// - `dash`: the dash pattern to use. Can be any of the following:
/// - One of the strings `"solid"`, `"dotted"`, `"densely-dotted"`, `"loosely-dotted"`,
/// `"dashed"`, `"densely-dashed"`, `"loosely-dashed"`, `"dashdotted"`,
/// `"densely-dashdotted"` or `"loosely-dashdotted"`
/// - An array with elements that specify the lengths of dashes and gaps, alternating.
/// Elements can also be the string `"dot"` for a length equal to the line thickness.
/// - A dict with the keys `array`, same as the array above, and `phase`, the offset to
/// the start of the first dash.
///
/// - `paint`: The [color]($type/color) to use for the stroke.
/// - `thickness`: The stroke's thickness as a [length]($type/length).
/// - `cap`: How the line terminates. One of `{"butt"}`, `{"round"}`, or
/// `{"square"}`.
/// - `join`: How sharp turns of a contour are rendered. One of
/// `{"miter"}`, `{"round"}`, or `{"bevel"}`. Not applicable to lines
/// but to [polygons]($func/polygon) or [paths]($func/path).
/// - `miter-limit`: Number at which protruding sharp angles are rendered
/// with a bevel instead. The higher the number, the sharper an angle
/// can be before it is bevelled. Only applicable if `join` is
/// `{"miter"}`. Defaults to `{4.0}`.
/// - `dash`: The dash pattern to use. Can be any of the following:
/// - One of the predefined patterns `{"solid"}`, `{"dotted"}`,
/// `{"densely-dotted"}`, `{"loosely-dotted"}`, `{"dashed"}`,
/// `{"densely-dashed"}`, `{"loosely-dashed"}`, `{"dash-dotted"}`,
/// `{"densely-dash-dotted"}` or `{"loosely-dash-dotted"}`
/// - An [array]($type/array) with alternating lengths for dashes and
/// gaps. You can also use the string `{"dot"}` for a length equal to
/// the line thickness.
/// - A [dictionary]($type/dictionary) with the keys `array` (same as
/// the array above), and `phase` (of type [length]($type/length)),
/// which defines where in the pattern to start drawing.
///
/// ```example
/// #set line(length: 100%)
/// #stack(
/// line(length: 100%, stroke: 2pt + red),
/// v(1em),
/// line(length: 100%, stroke: (color: blue, thickness: 4pt, cap: "round")),
/// v(1em),
/// line(length: 100%, stroke: (color: blue, thickness: 1pt, dash: "dashed")),
/// v(1em),
/// line(length: 100%, stroke: (color: blue, thickness: 1pt, dash: ("dot", 2pt, 4pt, 2pt))),
/// spacing: 1em,
/// line(stroke: 2pt + red),
/// line(stroke: (paint: blue, thickness: 4pt, cap: "round")),
/// line(stroke: (paint: blue, thickness: 1pt, dash: "dashed")),
/// line(stroke: (paint: blue, thickness: 1pt, dash: ("dot", 2pt, 4pt, 2pt))),
/// )
/// ```
#[resolve]

View File

@ -55,8 +55,8 @@ pub struct RectElem {
/// - `miter-limit`: the miter limit to use if `join` is `"miter"`, defaults to 4.0
/// - `dash`: the dash pattern to use. Can be any of the following:
/// - One of the strings `"solid"`, `"dotted"`, `"densely-dotted"`, `"loosely-dotted"`,
/// `"dashed"`, `"densely-dashed"`, `"loosely-dashed"`, `"dashdotted"`,
/// `"densely-dashdotted"` or `"loosely-dashdotted"`
/// `"dashed"`, `"densely-dashed"`, `"loosely-dashed"`, `"dash-dotted"`,
/// `"densely-dash-dotted"` or `"loosely-dash-dotted"`
/// - An array with elements that specify the lengths of dashes and gaps, alternating.
/// Elements can also be the string `"dot"` for a length equal to the line thickness.
/// - A dict with the keys `array`, same as the array above, and `phase`, the offset to

View File

@ -16,7 +16,7 @@ doctest = false
bench = false
[dependencies]
heck = "0.4"
proc-macro2 = "1"
quote = "1"
syn = { version = "1", features = ["full", "extra-traits"] }
heck = "0.4"

View File

@ -91,13 +91,13 @@ pub struct LangItems {
pub math_attach: fn(
base: Content,
// Positioned smartly.
top: Option<Content>,
bottom: Option<Content>,
t: Option<Content>,
b: Option<Content>,
// Fixed positions.
topleft: Option<Content>,
bottomleft: Option<Content>,
topright: Option<Content>,
bottomright: Option<Content>,
tl: Option<Content>,
bl: Option<Content>,
tr: Option<Content>,
br: Option<Content>,
) -> Content,
/// A base with an accent: `arrow(x)`.
pub math_accent: fn(base: Content, accent: char) -> Content,

View File

@ -48,8 +48,9 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::diag::{
bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint,
};
use crate::model::ShowableSelector;
use crate::model::{
Content, Introspector, Label, Recipe, Selector, StabilityProvider, Styles, Transform,
Content, Introspector, Label, Recipe, StabilityProvider, Styles, Transform,
Unlabellable, Vt,
};
use crate::syntax::ast::AstNode;
@ -1428,8 +1429,9 @@ impl Eval for ast::ShowRule {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let selector = self
.selector()
.map(|sel| sel.eval(vm)?.cast::<Selector>().at(sel.span()))
.transpose()?;
.map(|sel| sel.eval(vm)?.cast::<ShowableSelector>().at(sel.span()))
.transpose()?
.map(|selector| selector.0);
let transform = self.transform();
let span = transform.span();

View File

@ -256,9 +256,9 @@ cast_from_value! {
"dashed" => vec![Abs::pt(3.0).into(), Abs::pt(3.0).into()].into(),
"densely-dashed" => vec![Abs::pt(3.0).into(), Abs::pt(2.0).into()].into(),
"loosely-dashed" => vec![Abs::pt(3.0).into(), Abs::pt(6.0).into()].into(),
"dashdotted" => vec![Abs::pt(3.0).into(), Abs::pt(2.0).into(), DashLength::LineWidth, Abs::pt(2.0).into()].into(),
"densely-dashdotted" => vec![Abs::pt(3.0).into(), Abs::pt(1.0).into(), DashLength::LineWidth, Abs::pt(1.0).into()].into(),
"loosely-dashdotted" => vec![Abs::pt(3.0).into(), Abs::pt(4.0).into(), DashLength::LineWidth, Abs::pt(4.0).into()].into(),
"dash-dotted" => vec![Abs::pt(3.0).into(), Abs::pt(2.0).into(), DashLength::LineWidth, Abs::pt(2.0).into()].into(),
"densely-dash-dotted" => vec![Abs::pt(3.0).into(), Abs::pt(1.0).into(), DashLength::LineWidth, Abs::pt(1.0).into()].into(),
"loosely-dash-dotted" => vec![Abs::pt(3.0).into(), Abs::pt(4.0).into(), DashLength::LineWidth, Abs::pt(4.0).into()].into(),
array: Vec<DashLength> => {
Self {
array,
@ -314,14 +314,14 @@ cast_from_value! {
.transpose()?.map(Smart::Custom).unwrap_or(Smart::Auto))
}
let paint = take::<Paint>(&mut dict, "color")?;
let paint = take::<Paint>(&mut dict, "paint")?;
let thickness = take::<Length>(&mut dict, "thickness")?;
let line_cap = take::<LineCap>(&mut dict, "cap")?;
let line_join = take::<LineJoin>(&mut dict, "join")?;
let dash_pattern = take::<Option<DashPattern>>(&mut dict, "dash")?;
let miter_limit = take::<f64>(&mut dict, "miter-limit")?;
dict.finish(&["color", "thickness", "cap", "join", "dash", "miter-limit"])?;
dict.finish(&["paint", "thickness", "cap", "join", "dash", "miter-limit"])?;
Self {
paint,

View File

@ -584,7 +584,6 @@ impl Fold for Vec<Meta> {
/// The missing key access error message.
#[cold]
#[track_caller]
fn missing_field(key: &str) -> EcoString {
eco_format!("content does not contain field {:?}", Str::from(key))
}

View File

@ -412,9 +412,7 @@ impl Selector {
Self::Or(selectors) => selectors.iter().any(move |sel| sel.matches(target)),
Self::And(selectors) => selectors.iter().all(move |sel| sel.matches(target)),
Self::Location(location) => target.location() == Some(*location),
Self::Before { .. } | Self::After { .. } => {
panic!("Cannot match a `Selector::Before` or `Selector::After` selector")
}
Self::Before { .. } | Self::After { .. } => false,
}
}
}
@ -491,7 +489,7 @@ impl Cast for LocatableSelector {
fn cast(value: Value) -> StrResult<Self> {
fn validate(selector: &Selector) -> StrResult<()> {
match &selector {
match selector {
Selector::Elem(elem, _) => {
if !elem.can::<dyn Locatable>() {
Err(eco_format!("{} is not locatable", elem.name()))?
@ -533,6 +531,56 @@ impl Cast for LocatableSelector {
])
}
}
/// A selector that can be used with show rules.
#[derive(Clone, PartialEq, Hash)]
pub struct ShowableSelector(pub Selector);
impl Cast for ShowableSelector {
fn is(value: &Value) -> bool {
matches!(value, Value::Str(_) | Value::Label(_) | Value::Func(_))
|| value.type_name() == "regular expression"
|| value.type_name() == "selector"
}
fn cast(value: Value) -> StrResult<Self> {
fn validate(selector: &Selector) -> StrResult<()> {
match selector {
Selector::Elem(_, _) => {}
Selector::Label(_) => {}
Selector::Regex(_) => {}
Selector::Or(_)
| Selector::And(_)
| Selector::Location(_)
| Selector::Can(_)
| Selector::Before { .. }
| Selector::After { .. } => {
Err("this selector cannot be used with show")?
}
}
Ok(())
}
if !Self::is(&value) {
return <Self as Cast>::error(value);
}
let selector = Selector::cast(value)?;
validate(&selector)?;
Ok(Self(selector))
}
fn describe() -> CastInfo {
CastInfo::Union(vec![
CastInfo::Type("function"),
CastInfo::Type("label"),
CastInfo::Type("string"),
CastInfo::Type("regular expression"),
CastInfo::Type("selector"),
])
}
}
/// A show rule transformation that can be applied to a match.
#[derive(Clone, PartialEq, Hash)]
pub enum Transform {

View File

@ -850,12 +850,19 @@ fn item(p: &mut Parser, keyed: bool) -> SyntaxKind {
return SyntaxKind::Spread;
}
if !p.eat_if(SyntaxKind::Underscore) {
code_expr_or_pattern(p);
} else {
return SyntaxKind::Underscore;
if p.at(SyntaxKind::Underscore) {
// This is a temporary workaround to fix `v.map(_ => {})`.
let mut lexer = p.lexer.clone();
let next =
std::iter::from_fn(|| Some(lexer.next())).find(|kind| !kind.is_trivia());
if next != Some(SyntaxKind::Arrow) {
p.eat();
return SyntaxKind::Underscore;
}
}
code_expr_or_pattern(p);
if !p.eat_if(SyntaxKind::Colon) {
return SyntaxKind::Int;
}

View File

@ -0,0 +1,5 @@
// Test that lone underscore works.
// Ref: false
---
#test((1, 2, 3).map(_ => {}).len(), 3)

View File

@ -96,7 +96,7 @@ Hey
= Heading
---
// Error: 7-10 expected function, label, string, regular expression, location, or selector, found color
// Error: 7-10 expected function, label, string, regular expression, or selector, found color
#show red: []
---

View File

@ -37,3 +37,7 @@ the ```rs &mut T``` reference.
= Red
== Blue
=== Green
---
// Error: 7-35 this selector cannot be used with show
#show selector(heading).or(strong): none

View File

@ -43,7 +43,7 @@
---
// Test reading TOML data.
#let data = toml("/toml_types.toml")
#let data = toml("/toml-types.toml")
#test(data.string, "wonderful")
#test(data.integer, 42)
#test(data.float, 3.14)
@ -60,7 +60,7 @@
---
// Test reading YAML data
#let data = yaml("/yamltypes.yaml")
#let data = yaml("/yaml-types.yaml")
#test(data.len(), 7)
#test(data.null_key, (none, none))
#test(data.string, "text")

View File

@ -23,10 +23,10 @@ $ mat(
---
// Test baseline alignment.
$ mat(
a, b^2;
sum_(x \ y) x, a^(1/2);
zeta, alpha;
) $
a, b^2;
sum_(x \ y) x, a^(1/2);
zeta, alpha;
) $
---
// Test alternative delimiter with set rule.

View File

@ -11,18 +11,18 @@
#set text(size: 12pt, weight: "regular")
#outline(
title: "Chapter outline",
indent: true,
target: heading
.where(level: 1)
.or(heading.where(level: 2))
.after(it.location(), inclusive: true)
.before(
heading
.where(level: 1, outlined: true)
.after(it.location(), inclusive: false),
inclusive: false,
)
title: "Chapter outline",
indent: true,
target: heading
.where(level: 1)
.or(heading.where(level: 2))
.after(it.location(), inclusive: true)
.before(
heading
.where(level: 1, outlined: true)
.after(it.location(), inclusive: false),
inclusive: false,
)
)
]

View File

@ -2,22 +2,19 @@
---
// Some simple test lines
#line(length: 60pt, stroke: red)
#v(3pt)
#line(length: 60pt, stroke: 2pt)
#v(3pt)
#line(length: 60pt, stroke: blue + 1.5pt)
#v(3pt)
#line(length: 60pt, stroke: (color: red, thickness: 1pt, dash: "dashed"))
#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: "dashed"))
#v(3pt)
#line(length: 60pt, stroke: (color: red, thickness: 4pt, cap: "round"))
#line(length: 60pt, stroke: (paint: red, thickness: 4pt, cap: "round"))
---
// Set rules with stroke
#set line(stroke: (color: red, thickness: 1pt, cap: "butt", dash: "dashdotted"))
#set line(stroke: (paint: red, thickness: 1pt, cap: "butt", dash: "dash-dotted"))
#line(length: 60pt)
#v(3pt)
#line(length: 60pt, stroke: blue)
@ -26,79 +23,78 @@
---
// Rectangle strokes
#rect(width: 20pt, height: 20pt, stroke: red)
#v(3pt)
#rect(width: 20pt, height: 20pt, stroke: (rest: red, top: (color: blue, dash: "dashed")))
#rect(width: 20pt, height: 20pt, stroke: (rest: red, top: (paint: blue, dash: "dashed")))
#v(3pt)
#rect(width: 20pt, height: 20pt, stroke: (thickness: 5pt, join: "round"))
---
// Dashing
#line(length: 60pt, stroke: (color: red, thickness: 1pt, dash: ("dot", 1pt)))
#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: ("dot", 1pt)))
#v(3pt)
#line(length: 60pt, stroke: (color: red, thickness: 1pt, dash: ("dot", 1pt, 4pt, 2pt)))
#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: ("dot", 1pt, 4pt, 2pt)))
#v(3pt)
#line(length: 60pt, stroke: (color: red, thickness: 1pt, dash: (array: ("dot", 1pt, 4pt, 2pt), phase: 5pt)))
#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: (array: ("dot", 1pt, 4pt, 2pt), phase: 5pt)))
#v(3pt)
#line(length: 60pt, stroke: (color: red, thickness: 1pt, dash: ()))
#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: ()))
#v(3pt)
#line(length: 60pt, stroke: (color: red, thickness: 1pt, dash: (1pt, 3pt, 9pt)))
#line(length: 60pt, stroke: (paint: red, thickness: 1pt, dash: (1pt, 3pt, 9pt)))
---
// Line joins
#stack(dir: ltr,
polygon(stroke: (thickness: 4pt, color: blue, join: "round"),
#stack(
dir: ltr,
spacing: 1em,
polygon(stroke: (thickness: 4pt, paint: blue, join: "round"),
(0pt, 20pt), (15pt, 0pt), (0pt, 40pt), (15pt, 45pt)),
h(1em),
polygon(stroke: (thickness: 4pt, color: blue, join: "bevel"),
polygon(stroke: (thickness: 4pt, paint: blue, join: "bevel"),
(0pt, 20pt), (15pt, 0pt), (0pt, 40pt), (15pt, 45pt)),
h(1em),
polygon(stroke: (thickness: 4pt, color: blue, join: "miter"),
polygon(stroke: (thickness: 4pt, paint: blue, join: "miter"),
(0pt, 20pt), (15pt, 0pt), (0pt, 40pt), (15pt, 45pt)),
h(1em),
polygon(stroke: (thickness: 4pt, color: blue, join: "miter", miter-limit: 20.0),
polygon(stroke: (thickness: 4pt, paint: blue, join: "miter", miter-limit: 20.0),
(0pt, 20pt), (15pt, 0pt), (0pt, 40pt), (15pt, 45pt)),
)
---
// Error: 29-56 unexpected key "thicknes", valid keys are "color", "thickness", "cap", "join", "dash", and "miter-limit"
#line(length: 60pt, stroke: (color: red, thicknes: 1pt))
// Error: 29-56 unexpected key "thicknes", valid keys are "paint", "thickness", "cap", "join", "dash", and "miter-limit"
#line(length: 60pt, stroke: (paint: red, thicknes: 1pt))
---
// Error: 29-55 expected "solid", "dotted", "densely-dotted", "loosely-dotted", "dashed", "densely-dashed", "loosely-dashed", "dashdotted", "densely-dashdotted", "loosely-dashdotted", array, dictionary, dash pattern, or none
#line(length: 60pt, stroke: (color: red, dash: "dash"))
// Error: 29-55 expected "solid", "dotted", "densely-dotted", "loosely-dotted", "dashed", "densely-dashed", "loosely-dashed", "dash-dotted", "densely-dash-dotted", "loosely-dash-dotted", array, dictionary, dash pattern, or none
#line(length: 60pt, stroke: (paint: red, dash: "dash"))
---
// 0pt strokes must function exactly like 'none' strokes and not draw anything
#rect(width: 10pt, height: 10pt, stroke: none)
#rect(width: 10pt, height: 10pt, stroke: 0pt)
#rect(width: 10pt, height: 10pt, stroke: none, fill: blue)
#rect(width: 10pt, height: 10pt, stroke: 0pt + red, fill: blue)
#line(length: 30pt, stroke: 0pt)
#line(length: 30pt, stroke: (color: red, thickness: 0pt, dash: ("dot", 1pt)))
#line(length: 30pt, stroke: (paint: red, thickness: 0pt, dash: ("dot", 1pt)))
#table(columns: 2, stroke: none)[A][B]
#table(columns: 2, stroke: 0pt)[A][B]
#path(
fill: red,
stroke: none,
closed: true,
((0%, 0%), (4%, -4%)),
((50%, 50%), (4%, -4%)),
((0%, 50%), (4%, 4%)),
((50%, 0%), (4%, 4%)),
fill: red,
stroke: none,
closed: true,
((0%, 0%), (4%, -4%)),
((50%, 50%), (4%, -4%)),
((0%, 50%), (4%, 4%)),
((50%, 0%), (4%, 4%)),
)
#path(
fill: red,
stroke: 0pt,
closed: true,
((0%, 0%), (4%, -4%)),
((50%, 50%), (4%, -4%)),
((0%, 50%), (4%, 4%)),
((50%, 0%), (4%, 4%)),
fill: red,
stroke: 0pt,
closed: true,
((0%, 0%), (4%, -4%)),
((50%, 50%), (4%, -4%)),
((0%, 50%), (4%, 4%)),
((50%, 0%), (4%, 4%)),
)