Improve color and gradient docs

This commit is contained in:
Laurenz 2023-10-25 16:17:30 +02:00
parent a7fbe5151e
commit 38f59c2a27
4 changed files with 372 additions and 408 deletions

View File

@ -356,7 +356,7 @@ fn code_block(resolver: &dyn Resolver, lang: &str, text: &str) -> Html {
md::escape::escape_html(&mut buf, &display).unwrap();
buf.push_str("</pre>");
return Html::new(buf);
} else if !matches!(lang, "example" | "typ") {
} else if !matches!(lang, "example" | "typ" | "preview") {
let set = &*typst_library::text::SYNTAXES;
let buf = syntect::html::highlighted_html_for_string(
&display,
@ -369,10 +369,14 @@ fn code_block(resolver: &dyn Resolver, lang: &str, text: &str) -> Html {
return Html::new(buf);
}
let root = typst::syntax::parse(&display);
let highlighted = Html::new(typst::syntax::highlight_html(&root));
if lang == "typ" {
return Html::new(format!("<pre>{}</pre>", highlighted.as_str()));
let mut highlighted = None;
if matches!(lang, "example" | "typ") {
let root = typst::syntax::parse(&display);
let html = Html::new(typst::syntax::highlight_html(&root));
if lang == "typ" {
return Html::new(format!("<pre>{}</pre>", html.as_str()));
}
highlighted = Some(html);
}
let id = FileId::new(None, VirtualPath::new("main.typ"));

View File

@ -77,7 +77,7 @@ pub trait Resolver {
fn image(&self, filename: &str, data: &[u8]) -> String;
/// Produce HTML for an example.
fn example(&self, hash: u128, source: Html, frames: &[Frame]) -> Html;
fn example(&self, hash: u128, source: Option<Html>, frames: &[Frame]) -> Html;
/// Determine the commits between two tags.
fn commits(&self, from: &str, to: &str) -> Vec<Commit>;
@ -780,7 +780,7 @@ mod tests {
None
}
fn example(&self, _: u128, _: Html, _: &[Frame]) -> Html {
fn example(&self, _: u128, _: Option<Html>, _: &[Frame]) -> Html {
Html::new(String::new())
}

View File

@ -32,158 +32,122 @@ const ANGLE_EPSILON: f32 = 1e-5;
/// - HSL through the [`color.hsl` function]($color.hsl)
/// - HSV through the [`color.hsv` function]($color.hsv)
///
/// Typst provides the following built-in colors:
///
/// `black`, `gray`, `silver`, `white`, `navy`, `blue`, `aqua`, `teal`,
/// `eastern`, `purple`, `fuchsia`, `maroon`, `red`, `orange`, `yellow`,
/// `olive`, `green`, and `lime`.
///
/// # Example
/// The predefined colors and the color constructors are available globally and
/// also in the color type's scope, so you can write either of the following
/// two:
///
/// ```example
/// #rect(fill: aqua)
/// #rect(fill: color.aqua)
/// ```
///
/// ## Color maps
/// Typst also includes a number of preset color maps. In the following section,
/// the list of available color maps is given, along with a sample of each gradient
/// and relevant comments. Most of these color maps are chosen to be color blind
/// friendly.
/// # Predefined colors
/// Typst defines the following built-in colors:
///
/// ### Turbo
/// The [`turbo`]($color.map.turbo) gradient is a rainbow-like gradient that is
/// perceptually uniform. You can learn more about the turbo color map on
/// Google's [blog post](https://ai.googleblog.com/2019/08/turbo-improved-rainbow-colormap-for.html).
/// | Color | Definition |
/// |-----------|:-------------------|
/// | `black` | `{luma(0)}` |
/// | `gray` | `{luma(170)}` |
/// | `silver` | `{luma(221)}` |
/// | `white` | `{luma(255)}` |
/// | `navy` | `{rgb("#001f3f")}` |
/// | `blue` | `{rgb("#0074d9")}` |
/// | `aqua` | `{rgb("#7fdbff")}` |
/// | `teal` | `{rgb("#39cccc")}` |
/// | `eastern` | `{rgb("#239dad")}` |
/// | `purple` | `{rgb("#b10dc9")}` |
/// | `fuchsia` | `{rgb("#f012be")}` |
/// | `maroon` | `{rgb("#85144b")}` |
/// | `red` | `{rgb("#ff4136")}` |
/// | `orange` | `{rgb("#ff851b")}` |
/// | `yellow` | `{rgb("#ffdc00")}` |
/// | `olive` | `{rgb("#3d9970")}` |
/// | `green` | `{rgb("#2ecc40")}` |
/// | `lime` | `{rgb("#01ff70")}` |
///
/// ```example
/// #rect(width: 100%, height: 20pt, fill: gradient.linear(..color.map.turbo))
/// ```
/// The predefined colors and the most important color constructors are
/// available globally and also in the color type's scope, so you can write
/// either `color.red` or just `red`.
///
/// ### Cividis
/// The [`cividis`]($color.map.cividis) gradient is a blue to gray to yellow
/// gradient. You can learn more about the Cividis color map on the
/// Berkley Institute for Data Science's [blog post](https://bids.github.io/colormap/).
/// ```preview
/// #let colors = (
/// "black", "gray", "silver", "white",
/// "navy", "blue", "aqua", "teal",
/// "eastern", "purple", "fuchsia",
/// "maroon", "red", "orange", "yellow",
/// "olive", "green", "lime",
/// )
///
/// ```example
/// #rect(width: 100%, height: 20pt, fill: gradient.linear(..color.map.cividis))
/// ```
///
/// ### Rainbow
/// The [`rainbow`]($color.map.rainbow) gradient cycles through the full color
/// spectrum. This color map is best used by setting the interpolation color
/// space to [HSL]($color.hsl).
///
/// **Attention:** The rainbow gradient is _not suitable_ for data visualization
/// because it is not perceptually uniform, so the differences between values
/// become unclear to your readers. It should only be used for decorative
/// purposes.
///
/// ```example
/// #rect(
/// width: 100%,
/// height: 20pt,
/// fill: gradient.linear(..color.map.rainbow, space: color.hsl)
/// #set text(font: "PT Sans")
/// #set page(width: auto)
/// #grid(
/// columns: 9,
/// gutter: 10pt,
/// ..colors.map(name => {
/// let c = eval(name)
/// let cp = c.components()
/// let x = cp.sum() / cp.len()
/// set text(fill: white) if x < 50%
/// set square(stroke: black) if c == white
/// set align(center + horizon)
/// square(size: 50pt, fill: c, name)
/// })
/// )
/// ```
///
/// ### Spectral
/// The [`spectral`]($color.map.spectral) gradient is a red to yellow to blue
/// gradient. Spectral does not take any parameters.
/// # Predefined color maps
/// Typst also includes a number of preset color maps that can be used for
/// gradients. Most of these color maps are chosen to be color blind friendly.
///
/// ```example
/// #rect(width: 100%, height: 20pt, fill: gradient.linear(..color.map.spectral))
/// | Map | Details |
/// |------------|:------------------------------------------------------------|
/// | `turbo` | A perceptually uniform rainbow-like color map. Read [this blog post](https://ai.googleblog.com/2019/08/turbo-improved-rainbow-colormap-for.html) for more details. |
/// | `cividis` | A blue to gray to yellow color map. See [this blog post](https://bids.github.io/colormap/) for more details. |
/// | `rainbow` | Cycles through the full color spectrum. This color map is best used by setting the interpolation color space to [HSL]($color.hsl). The rainbow gradient is **not suitable** for data visualization because it is not perceptually uniform, so the differences between values become unclear to your readers. It should only be used for decorative purposes. |
/// | `spectral` | Red to yellow to blue color map. |
/// | `viridis` | A purple to teal to yellow color map. |
/// | `inferno` | A black to red to yellow color map. |
/// | `magma` | A black to purple to yellow color map. |
/// | `plasma` | A purple to pink to yellow color map. |
/// | `rocket` | A black to red to white color map. |
/// | `mako` | A black to teal to yellow color map. |
/// | `vlag` | A light blue to white to red color map. |
/// | `icefire` | A light teal to black to yellow color map. |
/// | `flare` | A orange to purple color map that is perceptually uniform. |
/// | `crest` | A blue to white to red color map. |
///
/// Some popular presets are not included because they are not available under a
/// free licence. Others, like
/// [Jet](https://jakevdp.github.io/blog/2014/10/16/how-bad-is-your-colormap/),
/// are not included because they are not not color blind friendly. Feel free to
/// use or create a package with other presets that are useful to you!
///
/// ```preview
/// #set page(width: auto, height: auto)
/// #set text(font: "PT Sans", size: 8pt)
///
/// #let maps = (
/// "turbo", "cividis", "rainbow", "spectral",
/// "viridis", "inferno", "magma", "plasma",
/// "rocket", "mako", "vlag", "icefire",
/// "flare", "crest",
/// )
///
/// #stack(dir: ltr, spacing: 3pt, ..maps.map((name) => {
/// let map = eval("color.map." + name)
/// stack(
/// dir: ttb,
/// block(
/// width: 15pt,
/// height: 100pt,
/// fill: gradient.linear(..map, angle: 90deg),
/// ),
/// block(
/// width: 15pt,
/// height: 32pt,
/// move(dy: 8pt, rotate(90deg, name)),
/// ),
/// )
/// }))
/// ```
///
/// ### Viridis
/// The [`viridis`]($color.map.viridis) gradient is a purple to teal to yellow
/// gradient. Viridis does not take any parameters.
///
/// ```example
/// #rect(width: 100%, height: 20pt, fill: gradient.linear(..color.map.viridis))
/// ```
///
/// ### Inferno
/// The [`inferno`]($color.map.inferno) gradient is a black to red to yellow
/// gradient. Inferno does not take any parameters.
///
/// ```example
/// #rect(width: 100%, height: 20pt, fill: gradient.linear(..color.map.inferno))
/// ```
///
/// ### Magma
/// The [`magma`]($color.map.magma) gradient is a black to purple to yellow
/// gradient. Magma does not take any parameters.
///
/// ```example
/// #rect(width: 100%, height: 20pt, fill: gradient.linear(..color.map.magma))
/// ```
///
/// ### Plasma
/// The [`plasma`]($color.map.plasma) gradient is a purple to pink to yellow
/// gradient. Plasma does not take any parameters.
///
/// ```example
/// #rect(width: 100%, height: 20pt, fill: gradient.linear(..color.map.plasma))
/// ```
///
/// ### Rocket
/// The [`rocket`]($color.map.rocket) gradient is a black to red to white
/// gradient. Rocket does not take any parameters.
///
/// ```example
/// #rect(width: 100%, height: 20pt, fill: gradient.linear(..color.map.rocket))
/// ```
///
/// ### Mako
/// The [`mako`]($color.map.mako) gradient is a black to teal to yellow gradient
///. Mako does not take any parameters.
///
/// ```example
/// #rect(width: 100%, height: 20pt, fill: gradient.linear(..color.map.mako))
/// ```
///
/// ### Vlag
/// The [`vlag`]($color.map.vlag) gradient is a light blue to white to red
/// gradient. Vlag does not take any parameters.
///
/// ```example
/// #rect(width: 100%, height: 20pt, fill: gradient.linear(..color.map.vlag))
/// ```
///
/// ### Icefire
/// The [`icefire`]($color.map.icefire) gradient is a light teal to black to
/// yellow gradient. Icefire does not take any parameters.
///
/// ```example
/// #rect(width: 100%, height: 20pt, fill: gradient.linear(..color.map.icefire))
/// ```
///
/// ### Flare
/// The [`flare`]($color.map.flare) gradient is an orange to purple gradient that
/// is perceptually uniform. Flare does not take any parameters.
///
/// ```example
/// #rect(width: 100%, height: 20pt, fill: gradient.linear(..color.map.flare))
/// ```
///
/// ### Crest
/// The [`crest`]($color.map.crest) gradient is a blue to white to red gradient .
///Crest does not take any parameters.
///
/// ```example
/// #rect(width: 100%, height: 20pt, fill: gradient.linear(..color.map.crest))
/// ```
///
/// ### On other presets
/// [Jet](https://jakevdp.github.io/blog/2014/10/16/how-bad-is-your-colormap/)
/// is not color blind friendly and should not be used for data visualization,
/// which is why it is not included in Typst. Other popular presets are not
/// neccesarily under a free licence, which is why we could not include them.
///
/// Feel free to use or create a package with other presets useful to you!
#[ty(scope)]
#[derive(Debug, Copy, Clone)]
pub enum Color {
@ -251,7 +215,6 @@ impl Color {
pub const MAP: fn() -> Module = || {
// Lazy to avoid re-allocating.
static MODULE: Lazy<Module> = Lazy::new(map);
MODULE.clone()
};
@ -276,10 +239,11 @@ impl Color {
/// Create a grayscale color.
///
/// A grayscale color is represented internally by a single `lightness` component.
/// A grayscale color is represented internally by a single `lightness`
/// component.
///
/// These components are also available using the [`components`]($color.components)
/// method.
/// These components are also available using the
/// [`components`]($color.components) method.
///
/// ```example
/// #for x in range(250, step: 50) {
@ -294,7 +258,9 @@ impl Color {
/// The lightness component.
#[external]
lightness: Component,
/// The color to convert to grayscale.
/// Alternatively: The color to convert to grayscale.
///
/// If this is given, the `lightness` should not be given.
#[external]
color: Color,
) -> SourceResult<Color> {
@ -315,14 +281,15 @@ impl Color {
/// - Creating grayscale images with uniform perceived lightness
/// - Creating smooth and uniform color transition and gradients
///
/// A linear Oklab color is represented internally by an array of four components:
/// A linear Oklab color is represented internally by an array of four
/// components:
/// - lightness ([`ratio`]($ratio))
/// - a ([`float`]($float) in the range `[-0.4..0.4]`)
/// - b ([`float`]($float) in the range `[-0.4..0.4]`)
/// - alpha ([`ratio`]($ratio))
///
/// These components are also available using the [`components`]($color.components)
/// method.
/// These components are also available using the
/// [`components`]($color.components) method.
///
/// ```example
/// #square(
@ -346,7 +313,9 @@ impl Color {
/// The key component.
#[external]
alpha: RatioComponent,
/// The color to convert to Oklab.
/// Alternatively: The color to convert to Oklab.
///
/// If this is given, the individual components should not be given.
#[external]
color: Color,
) -> SourceResult<Color> {
@ -375,19 +344,20 @@ impl Color {
/// perform color operations such as blending and interpolation. Although,
/// you should prefer to use the [`oklab` function]($color.oklab) for these.
///
/// A linear RGB(A) color is represented internally by an array of four components:
/// A linear RGB(A) color is represented internally by an array of four
/// components:
/// - red ([`ratio`]($ratio))
/// - green ([`ratio`]($ratio))
/// - blue ([`ratio`]($ratio))
/// - alpha ([`ratio`]($ratio))
///
/// These components are also available using the [`components`]($color.components)
/// method.
/// These components are also available using the
/// [`components`]($color.components) method.
///
/// ```example
/// #square(
/// fill: color.linear-rgb(30%, 50%, 10%)
/// )
/// #square(fill: color.linear-rgb(
/// 30%, 50%, 10%,
/// ))
/// ```
#[func(title = "Linear RGB")]
pub fn linear_rgb(
@ -406,7 +376,9 @@ impl Color {
/// The alpha component.
#[external]
alpha: Component,
/// The color to convert to linear RGB(A).
/// Alternatively: The color to convert to linear RGB(A).
///
/// If this is given, the individual components should not be given.
#[external]
color: Color,
) -> SourceResult<Color> {
@ -450,20 +422,6 @@ impl Color {
/// The real arguments (the other arguments are just for the docs, this
/// function is a bit involved, so we parse the arguments manually).
args: Args,
/// The color in hexadecimal notation.
///
/// Accepts three, four, six or eight hexadecimal digits and optionally
/// a leading hash.
///
/// If this string is given, the individual components should not be given.
///
/// ```example
/// #text(16pt, rgb("#239dad"))[
/// *Typst*
/// ]
/// ```
#[external]
hex: Str,
/// The red component.
#[external]
red: Component,
@ -476,7 +434,23 @@ impl Color {
/// The alpha component.
#[external]
alpha: Component,
/// The color to convert to RGB(A).
/// Alternatively: The color in hexadecimal notation.
///
/// Accepts three, four, six or eight hexadecimal digits and optionally
/// a leading hash.
///
/// If this is given, the individual components should not be given.
///
/// ```example
/// #text(16pt, rgb("#239dad"))[
/// *Typst*
/// ]
/// ```
#[external]
hex: Str,
/// Alternatively: The color to convert to RGB(a).
///
/// If this is given, the individual components should not be given.
#[external]
color: Color,
) -> SourceResult<Color> {
@ -511,8 +485,8 @@ impl Color {
/// - yellow ([`ratio`]($ratio))
/// - key ([`ratio`]($ratio))
///
/// These components are also available using the [`components`]($color.components)
/// method.
/// These components are also available using the
/// [`components`]($color.components) method.
///
/// ```example
/// #square(
@ -536,7 +510,9 @@ impl Color {
/// The key component.
#[external]
key: RatioComponent,
/// The color to convert to CMYK.
/// Alternatively: The color to convert to CMYK.
///
/// If this is given, the individual components should not be given.
#[external]
color: Color,
) -> SourceResult<Color> {
@ -569,8 +545,8 @@ impl Color {
/// - lightness ([`ratio`]($ratio))
/// - alpha ([`ratio`]($ratio))
///
/// These components are also available using the [`components`]($color.components)
/// method.
/// These components are also available using the
/// [`components`]($color.components) method.
///
/// ```example
/// #square(
@ -594,7 +570,9 @@ impl Color {
/// The alpha component.
#[external]
alpha: Component,
/// The color to convert to HSL.
/// Alternatively: The color to convert to HSL.
///
/// If this is given, the individual components should not be given.
#[external]
color: Color,
) -> SourceResult<Color> {
@ -627,8 +605,8 @@ impl Color {
/// - value ([`ratio`]($ratio))
/// - alpha ([`ratio`]($ratio))
///
/// These components are also available using the [`components`]($color.components)
/// method.
/// These components are also available using the
/// [`components`]($color.components) method.
///
/// ```example
/// #square(
@ -652,7 +630,9 @@ impl Color {
/// The alpha component.
#[external]
alpha: Component,
/// The color to convert to HSL.
/// Alternatively: The color to convert to HSL.
///
/// If this is given, the individual components should not be given.
#[external]
color: Color,
) -> SourceResult<Color> {
@ -673,7 +653,7 @@ impl Color {
})
}
/// Converts this color into its components.
/// Extracts the components of this color.
///
/// The size and values of this array depends on the color space. You can
/// obtain the color space using [`space`]($color.space). Below is a table

View File

@ -19,29 +19,41 @@ use crate::syntax::{Span, Spanned};
/// the [`gradient.radial` function]($gradient.radial), and conic gradients
/// through the [`gradient.conic` function]($gradient.conic).
///
/// A gradient can be used for the following purposes:
/// - As a fill to paint the interior of a shape:
/// `{rect(fill: gradient.linear(..))}`
/// - As a stroke to paint the outline of a shape:
/// `{rect(stroke: 1pt + gradient.linear(..))}`
/// - As the fill of text:
/// `{set text(fill: gradient.linear(..))}`
/// - As a color map you can [sample]($gradient.sample) from:
/// `{gradient.linear(..).sample(0.5)}`
///
/// # Examples
/// ```example
/// >>> #set square(size: 50pt)
/// #stack(
/// dir: ltr,
/// square(size: 50pt, fill: gradient.linear(..color.map.rainbow)),
/// square(size: 50pt, fill: gradient.radial(..color.map.rainbow)),
/// square(size: 50pt, fill: gradient.conic(..color.map.rainbow)),
/// spacing: 1fr,
/// square(fill: gradient.linear(..color.map.rainbow)),
/// square(fill: gradient.radial(..color.map.rainbow)),
/// square(fill: gradient.conic(..color.map.rainbow)),
/// )
/// ```
///
/// # Gradients on text
/// Gradients are supported on text but only when setting the relativeness to
/// either `{auto}` (the default value) or `{"parent"}`. It was decided that
/// glyph-by-glyph gradients would not be supported out-of-the-box but can be
/// emulated using [show rules]($styling/#show-rules).
///
/// You can use gradients on text as follows:
/// Gradients are also supported on text, but only when setting the
/// [relativeness]($gradient.relative) to either `{auto}` (the default value) or
/// `{"parent"}`. To create word-by-word or glyph-by-glyph gradients, you can
/// wrap the words or characters of your text in [boxes]($box) manually or
/// through a [show rule]($styling/#show-rules).
///
/// ```example
/// #set page(margin: 1pt)
/// >>> #set page(width: auto, height: auto, margin: 12pt)
/// >>> #set text(size: 12pt)
/// #set text(fill: gradient.linear(red, blue))
/// #let rainbow(content) = {
/// set text(fill: gradient.linear(..color.map.rainbow))
/// box(content)
/// set text(fill: gradient.linear(..color.map.rainbow))
/// box(content)
/// }
///
/// This is a gradient on text, but with a #rainbow[twist]!
@ -56,24 +68,13 @@ use crate::syntax::{Span, Spanned};
/// the offsets when defining a gradient. In this case, Typst will space all
/// stops evenly.
///
/// # Usage
/// Gradients can be used for the following purposes:
/// - As fills to paint the interior of a shape:
/// `{rect(fill: gradient.linear(..))}`
/// - As strokes to paint the outline of a shape:
/// `{rect(stroke: 1pt + gradient.linear(..))}`
/// - As color maps you can [sample]($gradient.sample) from:
/// `{gradient.linear(..).sample(0.5)}`
///
/// Gradients are not currently supported on text.
///
/// # Relativeness
/// The location of the `{0%}` and `{100%}` stops is dependant on the dimensions
/// of a container. This container can either be the shape they are painted on,
/// or to the closest container ancestor. This is controlled by the `relative`
/// argument of a gradient constructor. By default, gradients are relative to
/// the shape they are painted on, unless the gradient is applied on text, in
/// which case they are relative to the closest ancestor container.
/// or to the closest surrounding container. This is controlled by the
/// `relative` argument of a gradient constructor. By default, gradients are
/// relative to the shape they are painted on, unless the gradient is applied on
/// text, in which case they are relative to the closest ancestor container.
///
/// Typst determines the ancestor container as follows:
/// - For shapes that are placed at the root/top level of the document, the
@ -98,16 +99,16 @@ use crate::syntax::{Span, Spanned};
/// choosing an interpolation space.
///
/// | Color space | Perceptually uniform? |
/// | ------------------------------- |:----------------------|
/// | [Oklab]($color.oklab) | *Yes* |
/// | [sRGB]($color.rgb) | *No* |
/// | ------------------------------- |-----------------------|
/// | [Oklab]($color.oklab) | *Yes* |
/// | [sRGB]($color.rgb) | *No* |
/// | [linear-RGB]($color.linear-rgb) | *Yes* |
/// | [CMYK]($color.cmyk) | *No* |
/// | [Grayscale]($color.luma) | *Yes* |
/// | [HSL]($color.hsl) | *No* |
/// | [HSV]($color.hsv) | *No* |
/// | [CMYK]($color.cmyk) | *No* |
/// | [Grayscale]($color.luma) | *Yes* |
/// | [HSL]($color.hsl) | *No* |
/// | [HSV]($color.hsv) | *No* |
///
/// ```example
/// ```preview
/// >>> #set text(fill: white, font: "IBM Plex Sans", 8pt)
/// >>> #set block(spacing: 0pt)
/// #let spaces = (
@ -141,52 +142,20 @@ use crate::syntax::{Span, Spanned};
/// right-to-left, and 270° from bottom-to-top.
///
/// ```example
/// #set block(spacing: 0pt)
/// >>> #set square(size: 50pt)
/// #stack(
/// dir: ltr,
/// square(size: 50pt, fill: gradient.linear(red, blue, angle: 0deg)),
/// square(size: 50pt, fill: gradient.linear(red, blue, angle: 90deg)),
/// square(size: 50pt, fill: gradient.linear(red, blue, angle: 180deg)),
/// square(size: 50pt, fill: gradient.linear(red, blue, angle: 270deg)),
/// spacing: 1fr,
/// square(fill: gradient.linear(red, blue, angle: 0deg)),
/// square(fill: gradient.linear(red, blue, angle: 90deg)),
/// square(fill: gradient.linear(red, blue, angle: 180deg)),
/// square(fill: gradient.linear(red, blue, angle: 270deg)),
/// )
/// ```
///
/// # Presets
/// You can find the full list of presets in the documentation of [`color`]($color),
/// below is an overview of them. Note that not all presets are suitable for data
/// visualization and full details and relevant sources can be found in the
/// documentation of [`color`]($color).
///
/// ```example
/// #set text(fill: white, size: 18pt)
/// #set text(top-edge: "bounds", bottom-edge: "bounds")
/// #let presets = (
/// ("turbo", color.map.turbo),
/// ("cividis", color.map.cividis),
/// ("rainbow", color.map.rainbow),
/// ("spectral", color.map.spectral),
/// ("viridis", color.map.viridis),
/// ("inferno", color.map.inferno),
/// ("magma", color.map.magma),
/// ("plasma", color.map.plasma),
/// ("rocket", color.map.rocket),
/// ("mako", color.map.mako),
/// ("vlag", color.map.vlag),
/// ("icefire", color.map.icefire),
/// ("flare", color.map.flare),
/// ("crest", color.map.crest),
/// )
///
/// #stack(
/// spacing: 3pt,
/// ..presets.map(((name, preset)) => block(
/// width: 100%,
/// height: 20pt,
/// fill: gradient.linear(..preset),
/// align(center + horizon, smallcaps(name)),
/// ))
/// )
/// ```
/// Typst predefines color maps that you can use with your gradients. See the
/// [`color`]($color/#predefined-color-maps) documentation for more details.
#[ty(scope)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Gradient {
@ -198,13 +167,16 @@ pub enum Gradient {
#[scope]
#[allow(clippy::too_many_arguments)]
impl Gradient {
/// Creates a new linear gradient.
/// Creates a new linear gradient, in which colors transition along a
/// straight line.
///
/// ```example
/// #rect(
/// width: 100%,
/// height: 20pt,
/// fill: gradient.linear(..color.map.viridis)
/// fill: gradient.linear(
/// ..color.map.viridis,
/// ),
/// )
/// ```
#[func(title = "Linear Gradient")]
@ -225,9 +197,10 @@ impl Gradient {
space: ColorSpace,
/// The [relative placement](#relativeness) of the gradient.
///
/// 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 innermost block,
/// box, column, grid, or stack that contains the element.
/// 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
/// innermost block, box, column, grid, or stack that contains the
/// element.
#[named]
#[default(Smart::Auto)]
relative: Smart<Relative>,
@ -267,31 +240,34 @@ impl Gradient {
})))
}
/// Creates a new radial gradient.
/// Creates a new radial gradient, in which colors radiate away from an
/// origin.
///
/// The gradient is defined by two circles: the focal circle and the end
/// circle. The focal circle is a circle with center `focal-center` and
/// radius `focal-radius`, that defines the points at which the gradient
/// starts and has the color of the first stop. The end circle is a circle
/// with center `center` and radius `radius`, that defines the points at
/// which the gradient ends and has the color of the last stop. The gradient
/// is then interpolated between these two circles.
///
/// Using these four values, also called the focal point for the starting
/// circle and the center and radius for the end circle, we can define a
/// gradient with more interesting properties than a basic radial gradient:
///
/// ```example
/// #circle(
/// radius: 20pt,
/// fill: gradient.radial(..color.map.viridis)
/// )
/// ```
///
/// _Focal Point_
/// The gradient is defined by two circles: the focal circle and the end circle.
/// The focal circle is a circle with center `focal-center` and radius `focal-radius`,
/// that defines the points at which the gradient starts and has the color of the
/// first stop. The end circle is a circle with center `center` and radius `radius`,
/// that defines the points at which the gradient ends and has the color of the last
/// stop. The gradient is then interpolated between these two circles.
///
/// Using these four values, also called the focal point for the starting circle and
/// the center and radius for the end circle, we can define a gradient with more
/// interesting properties than a basic radial gradient:
///
/// ```example
/// #circle(
/// radius: 20pt,
/// fill: gradient.radial(..color.map.viridis, focal-center: (10%, 40%), focal-radius: 5%)
/// >>> #set circle(radius: 30pt)
/// #stack(
/// dir: ltr,
/// spacing: 1fr,
/// circle(fill: gradient.radial(
/// ..color.map.viridis,
/// )),
/// circle(fill: gradient.radial(
/// ..color.map.viridis,
/// focal-center: (10%, 40%),
/// focal-radius: 5%,
/// )),
/// )
/// ```
#[func]
@ -316,14 +292,14 @@ impl Gradient {
#[named]
#[default(Smart::Auto)]
relative: Smart<Relative>,
/// The center of the last circle of the gradient.
/// The center of the end circle of the gradient.
///
/// A value of `{(50%, 50%)}` means that the end circle is
/// centered inside of its container.
#[named]
#[default(Axes::splat(Ratio::new(0.5)))]
center: Axes<Ratio>,
/// The radius of the last circle of the gradient.
/// The radius of the end circle of the gradient.
///
/// By default, it is set to `{50%}`. The ending radius must be bigger
/// than the focal radius.
@ -384,24 +360,24 @@ impl Gradient {
})))
}
/// Creates a new conic gradient (i.e a gradient whose color changes
/// radially around a center point).
/// Creates a new conic gradient, in which colors change radially around a
/// center point.
///
/// ```example
/// #circle(
/// radius: 20pt,
/// fill: gradient.conic(..color.map.viridis)
/// )
/// ```
///
/// _Center Point_
/// You can control the center point of the gradient by using the `center`
/// argument. By default, the center point is the center of the shape.
///
/// ```example
/// #circle(
/// radius: 20pt,
/// fill: gradient.conic(..color.map.viridis, center: (10%, 40%))
/// >>> #set circle(radius: 30pt)
/// #stack(
/// dir: ltr,
/// spacing: 1fr,
/// circle(fill: gradient.conic(
/// ..color.map.viridis,
/// )),
/// circle(fill: gradient.conic(
/// ..color.map.viridis,
/// center: (20%, 30%),
/// )),
/// )
/// ```
#[func]
@ -453,117 +429,18 @@ impl Gradient {
})))
}
/// Returns the stops of this gradient.
#[func]
pub fn stops(&self) -> Vec<Stop> {
match self {
Self::Linear(linear) => linear
.stops
.iter()
.map(|(color, offset)| Stop { color: *color, offset: Some(*offset) })
.collect(),
Self::Radial(radial) => radial
.stops
.iter()
.map(|(color, offset)| Stop { color: *color, offset: Some(*offset) })
.collect(),
Self::Conic(conic) => conic
.stops
.iter()
.map(|(color, offset)| Stop { color: *color, offset: Some(*offset) })
.collect(),
}
}
/// Returns the mixing space of this gradient.
#[func]
pub fn space(&self) -> ColorSpace {
match self {
Self::Linear(linear) => linear.space,
Self::Radial(radial) => radial.space,
Self::Conic(conic) => conic.space,
}
}
/// Returns the relative placement of this gradient.
#[func]
pub fn relative(&self) -> Smart<Relative> {
match self {
Self::Linear(linear) => linear.relative,
Self::Radial(radial) => radial.relative,
Self::Conic(conic) => conic.relative,
}
}
/// Returns the angle of this gradient.
#[func]
pub fn angle(&self) -> Option<Angle> {
match self {
Self::Linear(linear) => Some(linear.angle),
Self::Radial(_) => None,
Self::Conic(conic) => Some(conic.angle),
}
}
/// Returns the kind of this gradient.
#[func]
pub fn kind(&self) -> Func {
match self {
Self::Linear(_) => Self::linear_data().into(),
Self::Radial(_) => Self::radial_data().into(),
Self::Conic(_) => Self::conic_data().into(),
}
}
/// Sample the gradient at a given position.
///
/// The position is either a position along the gradient (a [ratio]($ratio)
/// between `{0%}` and `{100%}`) or an [angle]($angle). Any value outside
/// of this range will be clamped.
///
/// _The angle will be used for conic gradients once they are available._
#[func]
pub fn sample(
&self,
/// The position at which to sample the gradient.
t: RatioOrAngle,
) -> Color {
let value: f64 = t.to_ratio().get();
match self {
Self::Linear(linear) => sample_stops(&linear.stops, linear.space, value),
Self::Radial(radial) => sample_stops(&radial.stops, radial.space, value),
Self::Conic(conic) => sample_stops(&conic.stops, conic.space, value),
}
}
/// Samples the gradient at the given positions.
///
/// The position is either a position along the gradient (a [ratio]($ratio)
/// between `{0%}` and `{100%}`) or an [angle]($angle). Any value outside
/// of this range will be clamped.
///
/// _The angle will be used for conic gradients once they are available._
#[func]
pub fn samples(
&self,
/// The positions at which to sample the gradient.
#[variadic]
ts: Vec<RatioOrAngle>,
) -> Array {
ts.into_iter().map(|t| self.sample(t).into_value()).collect()
}
/// Creates a sharp version of this gradient.
///
/// _Sharp gradients_ have discreet jumps between colors, instead of a
/// Sharp gradients have discreet jumps between colors, instead of a
/// smooth transition. They are particularly useful for creating color
/// lists for a preset gradient.
///
/// ```example
/// #set rect(width: 100%, height: 20pt)
/// #let grad = gradient.linear(..color.map.rainbow)
/// #rect(width: 100%, height: 20pt, fill: grad)
/// #rect(width: 100%, height: 20pt, fill: grad.sharp(5))
/// #rect(fill: grad)
/// #rect(fill: grad.sharp(5))
/// #rect(fill: grad.sharp(5, smoothness: 20%))
/// ```
#[func]
pub fn sharp(
@ -651,6 +528,15 @@ impl Gradient {
/// Repeats this gradient a given number of times, optionally mirroring it
/// at each repetition.
///
/// ```example
/// #circle(
/// radius: 40pt,
/// fill: gradient
/// .radial(aqua, white)
/// .repeat(4),
/// )
/// ```
#[func]
pub fn repeat(
&self,
@ -721,6 +607,100 @@ impl Gradient {
})),
})
}
/// Returns the kind of this gradient.
#[func]
pub fn kind(&self) -> Func {
match self {
Self::Linear(_) => Self::linear_data().into(),
Self::Radial(_) => Self::radial_data().into(),
Self::Conic(_) => Self::conic_data().into(),
}
}
/// Returns the stops of this gradient.
#[func]
pub fn stops(&self) -> Vec<Stop> {
match self {
Self::Linear(linear) => linear
.stops
.iter()
.map(|(color, offset)| Stop { color: *color, offset: Some(*offset) })
.collect(),
Self::Radial(radial) => radial
.stops
.iter()
.map(|(color, offset)| Stop { color: *color, offset: Some(*offset) })
.collect(),
Self::Conic(conic) => conic
.stops
.iter()
.map(|(color, offset)| Stop { color: *color, offset: Some(*offset) })
.collect(),
}
}
/// Returns the mixing space of this gradient.
#[func]
pub fn space(&self) -> ColorSpace {
match self {
Self::Linear(linear) => linear.space,
Self::Radial(radial) => radial.space,
Self::Conic(conic) => conic.space,
}
}
/// Returns the relative placement of this gradient.
#[func]
pub fn relative(&self) -> Smart<Relative> {
match self {
Self::Linear(linear) => linear.relative,
Self::Radial(radial) => radial.relative,
Self::Conic(conic) => conic.relative,
}
}
/// Returns the angle of this gradient.
#[func]
pub fn angle(&self) -> Option<Angle> {
match self {
Self::Linear(linear) => Some(linear.angle),
Self::Radial(_) => None,
Self::Conic(conic) => Some(conic.angle),
}
}
/// Sample the gradient at a given position.
///
/// The position is either a position along the gradient (a [ratio]($ratio)
/// between `{0%}` and `{100%}`) or an [angle]($angle). Any value outside
/// of this range will be clamped.
#[func]
pub fn sample(
&self,
/// The position at which to sample the gradient.
t: RatioOrAngle,
) -> Color {
let value: f64 = t.to_ratio().get();
match self {
Self::Linear(linear) => sample_stops(&linear.stops, linear.space, value),
Self::Radial(radial) => sample_stops(&radial.stops, radial.space, value),
Self::Conic(conic) => sample_stops(&conic.stops, conic.space, value),
}
}
/// Samples the gradient at multiple positions at once and returns the
/// results as an array.
#[func]
pub fn samples(
&self,
/// The positions at which to sample the gradient.
#[variadic]
ts: Vec<RatioOrAngle>,
) -> Array {
ts.into_iter().map(|t| self.sample(t).into_value()).collect()
}
}
impl Gradient {