Compare commits

...

6 Commits

Author SHA1 Message Date
Laurenz
3602d06a15 Support for generating native functions at runtime 2025-06-20 17:32:37 +02:00
Laurenz
15302dbe7a Add typst_utils::display 2025-06-20 17:32:37 +02:00
Laurenz
4580daf307 More type-safe color conversions 2025-06-20 17:32:37 +02:00
Laurenz
d821633f50 Generic casting for Axes<T> 2025-06-20 17:32:37 +02:00
Laurenz
3b35f0cecf Add Duration::decompose 2025-06-20 17:32:37 +02:00
Laurenz
fee6844045 Encode empty attributes with shorthand syntax 2025-06-20 17:32:37 +02:00
12 changed files with 192 additions and 139 deletions

View File

@ -69,16 +69,21 @@ fn write_element(w: &mut Writer, element: &HtmlElement) -> SourceResult<()> {
for (attr, value) in &element.attrs.0 { for (attr, value) in &element.attrs.0 {
w.buf.push(' '); w.buf.push(' ');
w.buf.push_str(&attr.resolve()); w.buf.push_str(&attr.resolve());
w.buf.push('=');
w.buf.push('"'); // If the string is empty, we can use shorthand syntax.
for c in value.chars() { // `<elem attr="">..</div` is equivalent to `<elem attr>..</div>`
if charsets::is_valid_in_attribute_value(c) { if !value.is_empty() {
w.buf.push(c); w.buf.push('=');
} else { w.buf.push('"');
write_escape(w, c).at(element.span)?; for c in value.chars() {
if charsets::is_valid_in_attribute_value(c) {
w.buf.push(c);
} else {
write_escape(w, c).at(element.span)?;
}
} }
w.buf.push('"');
} }
w.buf.push('"');
} }
w.buf.push('>'); w.buf.push('>');

View File

@ -16,6 +16,21 @@ impl Duration {
pub fn is_zero(&self) -> bool { pub fn is_zero(&self) -> bool {
self.0.is_zero() self.0.is_zero()
} }
/// Decomposes the time into whole weeks, days, hours, minutes, and seconds.
pub fn decompose(&self) -> [i64; 5] {
let mut tmp = self.0;
let weeks = tmp.whole_weeks();
tmp -= weeks.weeks();
let days = tmp.whole_days();
tmp -= days.days();
let hours = tmp.whole_hours();
tmp -= hours.hours();
let minutes = tmp.whole_minutes();
tmp -= minutes.minutes();
let seconds = tmp.whole_seconds();
[weeks, days, hours, minutes, seconds]
}
} }
#[scope] #[scope]
@ -118,34 +133,25 @@ impl Debug for Duration {
impl Repr for Duration { impl Repr for Duration {
fn repr(&self) -> EcoString { fn repr(&self) -> EcoString {
let mut tmp = self.0; let [weeks, days, hours, minutes, seconds] = self.decompose();
let mut vec = Vec::with_capacity(5); let mut vec = Vec::with_capacity(5);
let weeks = tmp.whole_seconds() / 604_800.0 as i64;
if weeks != 0 { if weeks != 0 {
vec.push(eco_format!("weeks: {}", weeks.repr())); vec.push(eco_format!("weeks: {}", weeks.repr()));
} }
tmp -= weeks.weeks();
let days = tmp.whole_days();
if days != 0 { if days != 0 {
vec.push(eco_format!("days: {}", days.repr())); vec.push(eco_format!("days: {}", days.repr()));
} }
tmp -= days.days();
let hours = tmp.whole_hours();
if hours != 0 { if hours != 0 {
vec.push(eco_format!("hours: {}", hours.repr())); vec.push(eco_format!("hours: {}", hours.repr()));
} }
tmp -= hours.hours();
let minutes = tmp.whole_minutes();
if minutes != 0 { if minutes != 0 {
vec.push(eco_format!("minutes: {}", minutes.repr())); vec.push(eco_format!("minutes: {}", minutes.repr()));
} }
tmp -= minutes.minutes();
let seconds = tmp.whole_seconds();
if seconds != 0 { if seconds != 0 {
vec.push(eco_format!("seconds: {}", seconds.repr())); vec.push(eco_format!("seconds: {}", seconds.repr()));
} }

View File

@ -307,7 +307,7 @@ impl Func {
) -> SourceResult<Value> { ) -> SourceResult<Value> {
match &self.repr { match &self.repr {
Repr::Native(native) => { Repr::Native(native) => {
let value = (native.function)(engine, context, &mut args)?; let value = (native.function.0)(engine, context, &mut args)?;
args.finish()?; args.finish()?;
Ok(value) Ok(value)
} }
@ -491,8 +491,8 @@ pub trait NativeFunc {
/// Defines a native function. /// Defines a native function.
#[derive(Debug)] #[derive(Debug)]
pub struct NativeFuncData { pub struct NativeFuncData {
/// Invokes the function from Typst. /// The implementation of the function.
pub function: fn(&mut Engine, Tracked<Context>, &mut Args) -> SourceResult<Value>, pub function: NativeFuncPtr,
/// The function's normal name (e.g. `align`), as exposed to Typst. /// The function's normal name (e.g. `align`), as exposed to Typst.
pub name: &'static str, pub name: &'static str,
/// The function's title case name (e.g. `Align`). /// The function's title case name (e.g. `Align`).
@ -504,11 +504,11 @@ pub struct NativeFuncData {
/// Whether this function makes use of context. /// Whether this function makes use of context.
pub contextual: bool, pub contextual: bool,
/// Definitions in the scope of the function. /// Definitions in the scope of the function.
pub scope: LazyLock<Scope>, pub scope: DynLazyLock<Scope>,
/// A list of parameter information for each parameter. /// A list of parameter information for each parameter.
pub params: LazyLock<Vec<ParamInfo>>, pub params: DynLazyLock<Vec<ParamInfo>>,
/// Information about the return value of this function. /// Information about the return value of this function.
pub returns: LazyLock<CastInfo>, pub returns: DynLazyLock<CastInfo>,
} }
cast! { cast! {
@ -516,6 +516,28 @@ cast! {
self => Func::from(self).into_value(), self => Func::from(self).into_value(),
} }
/// A pointer to a native function's implementation.
pub struct NativeFuncPtr(pub &'static NativeFuncSignature);
/// The signature of a native function's implementation.
type NativeFuncSignature =
dyn Fn(&mut Engine, Tracked<Context>, &mut Args) -> SourceResult<Value> + Send + Sync;
impl Debug for NativeFuncPtr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.pad("NativeFuncPtr(..)")
}
}
/// A `LazyLock` that uses a static closure for initialization instead of only
/// working with function pointers.
///
/// Can be created from a normal function or closure by prepending with a `&`,
/// e.g. `LazyLock::new(&|| "hello")`. Can be created from a dynamic closure
/// by allocating and then leaking it. This is equivalent to having it
/// statically allocated, but allows for it to be generated at runtime.
type DynLazyLock<T> = LazyLock<T, &'static (dyn Fn() -> T + Send + Sync)>;
/// Describes a function parameter. /// Describes a function parameter.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ParamInfo { pub struct ParamInfo {

View File

@ -4,9 +4,12 @@ use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Deref, Not};
use typst_utils::Get; use typst_utils::Get;
use crate::diag::bail; use crate::diag::{bail, HintedStrResult};
use crate::foundations::{array, cast, Array, Resolve, Smart, StyleChain}; use crate::foundations::{
use crate::layout::{Abs, Dir, Length, Ratio, Rel, Size}; array, cast, Array, CastInfo, FromValue, IntoValue, Reflect, Resolve, Smart,
StyleChain, Value,
};
use crate::layout::{Abs, Dir, Rel, Size};
/// A container with a horizontal and vertical component. /// A container with a horizontal and vertical component.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
@ -275,40 +278,39 @@ impl BitAndAssign for Axes<bool> {
} }
} }
cast! { impl<T: Reflect> Reflect for Axes<T> {
Axes<Rel<Length>>, fn input() -> CastInfo {
self => array![self.x, self.y].into_value(), Array::input()
array: Array => { }
let mut iter = array.into_iter();
match (iter.next(), iter.next(), iter.next()) { fn output() -> CastInfo {
(Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?), Array::output()
_ => bail!("point array must contain exactly two entries"), }
}
}, fn castable(value: &Value) -> bool {
Array::castable(value)
}
} }
cast! { impl<T: FromValue> FromValue for Axes<T> {
Axes<Ratio>, fn from_value(value: Value) -> HintedStrResult<Self> {
self => array![self.x, self.y].into_value(), let array = value.cast::<Array>()?;
array: Array => {
let mut iter = array.into_iter(); let mut iter = array.into_iter();
match (iter.next(), iter.next(), iter.next()) { match (iter.next(), iter.next(), iter.next()) {
(Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?), (Some(a), Some(b), None) => Ok(Axes::new(a.cast()?, b.cast()?)),
_ => bail!("ratio array must contain exactly two entries"), _ => bail!(
"array must contain exactly two items";
hint: "the first item determines the value for the X axis \
and the second item the value for the Y axis"
),
} }
}, }
} }
cast! { impl<T: IntoValue> IntoValue for Axes<T> {
Axes<Length>, fn into_value(self) -> Value {
self => array![self.x, self.y].into_value(), array![self.x.into_value(), self.y.into_value()].into_value()
array: Array => { }
let mut iter = array.into_iter();
match (iter.next(), iter.next(), iter.next()) {
(Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?),
_ => bail!("length array must contain exactly two entries"),
}
},
} }
impl<T: Resolve> Resolve for Axes<T> { impl<T: Resolve> Resolve for Axes<T> {

View File

@ -836,7 +836,7 @@ fn to_typst(synt::Color { r, g, b, a }: synt::Color) -> Color {
} }
fn to_syn(color: Color) -> synt::Color { fn to_syn(color: Color) -> synt::Color {
let [r, g, b, a] = color.to_rgb().to_vec4_u8(); let (r, g, b, a) = color.to_rgb().into_format::<u8, u8>().into_components();
synt::Color { r, g, b, a } synt::Color { r, g, b, a }
} }

View File

@ -262,7 +262,7 @@ impl Color {
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_luma() Color::Luma(color.to_luma())
} else { } else {
let Component(gray) = let Component(gray) =
args.expect("gray component").unwrap_or(Component(Ratio::one())); args.expect("gray component").unwrap_or(Component(Ratio::one()));
@ -318,7 +318,7 @@ impl Color {
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_oklab() Color::Oklab(color.to_oklab())
} else { } else {
let RatioComponent(l) = args.expect("lightness component")?; let RatioComponent(l) = args.expect("lightness component")?;
let ChromaComponent(a) = args.expect("A component")?; let ChromaComponent(a) = args.expect("A component")?;
@ -374,7 +374,7 @@ impl Color {
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_oklch() Color::Oklch(color.to_oklch())
} else { } else {
let RatioComponent(l) = args.expect("lightness component")?; let RatioComponent(l) = args.expect("lightness component")?;
let ChromaComponent(c) = args.expect("chroma component")?; let ChromaComponent(c) = args.expect("chroma component")?;
@ -434,7 +434,7 @@ impl Color {
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_linear_rgb() Color::LinearRgb(color.to_linear_rgb())
} else { } else {
let Component(r) = args.expect("red component")?; let Component(r) = args.expect("red component")?;
let Component(g) = args.expect("green component")?; let Component(g) = args.expect("green component")?;
@ -505,7 +505,7 @@ impl Color {
Ok(if let Some(string) = args.find::<Spanned<Str>>()? { Ok(if let Some(string) = args.find::<Spanned<Str>>()? {
Self::from_str(&string.v).at(string.span)? Self::from_str(&string.v).at(string.span)?
} else if let Some(color) = args.find::<Color>()? { } else if let Some(color) = args.find::<Color>()? {
color.to_rgb() Color::Rgb(color.to_rgb())
} else { } else {
let Component(r) = args.expect("red component")?; let Component(r) = args.expect("red component")?;
let Component(g) = args.expect("green component")?; let Component(g) = args.expect("green component")?;
@ -565,7 +565,7 @@ impl Color {
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_cmyk() Color::Cmyk(color.to_cmyk())
} else { } else {
let RatioComponent(c) = args.expect("cyan component")?; let RatioComponent(c) = args.expect("cyan component")?;
let RatioComponent(m) = args.expect("magenta component")?; let RatioComponent(m) = args.expect("magenta component")?;
@ -622,7 +622,7 @@ impl Color {
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_hsl() Color::Hsl(color.to_hsl())
} else { } else {
let h: Angle = args.expect("hue component")?; let h: Angle = args.expect("hue component")?;
let Component(s) = args.expect("saturation component")?; let Component(s) = args.expect("saturation component")?;
@ -679,7 +679,7 @@ impl Color {
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_hsv() Color::Hsv(color.to_hsv())
} else { } else {
let h: Angle = args.expect("hue component")?; let h: Angle = args.expect("hue component")?;
let Component(s) = args.expect("saturation component")?; let Component(s) = args.expect("saturation component")?;
@ -830,7 +830,7 @@ impl Color {
/// omitted if it is equal to `ff` (255 / 100%). /// omitted if it is equal to `ff` (255 / 100%).
#[func] #[func]
pub fn to_hex(self) -> EcoString { pub fn to_hex(self) -> EcoString {
let [r, g, b, a] = self.to_rgb().to_vec4_u8(); let (r, g, b, a) = self.to_rgb().into_format::<u8, u8>().into_components();
if a != 255 { if a != 255 {
eco_format!("#{:02x}{:02x}{:02x}{:02x}", r, g, b, a) eco_format!("#{:02x}{:02x}{:02x}{:02x}", r, g, b, a)
} else { } else {
@ -886,20 +886,21 @@ impl Color {
/// The factor to saturate the color by. /// The factor to saturate the color by.
factor: Ratio, factor: Ratio,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
let f = factor.get() as f32;
Ok(match self { Ok(match self {
Self::Luma(_) => { Self::Luma(_) => bail!(
bail!( span, "cannot saturate grayscale color";
span, "cannot saturate grayscale color"; hint: "try converting your color to RGB first"
hint: "try converting your color to RGB first" ),
); Self::Hsl(c) => Self::Hsl(c.saturate(f)),
Self::Hsv(c) => Self::Hsv(c.saturate(f)),
Self::Oklab(_)
| Self::Oklch(_)
| Self::LinearRgb(_)
| Self::Rgb(_)
| Self::Cmyk(_) => {
Color::Hsv(self.to_hsv().saturate(f)).to_space(self.space())
} }
Self::Oklab(_) => self.to_hsv().saturate(span, factor)?.to_oklab(),
Self::Oklch(_) => self.to_hsv().saturate(span, factor)?.to_oklch(),
Self::LinearRgb(_) => self.to_hsv().saturate(span, factor)?.to_linear_rgb(),
Self::Rgb(_) => self.to_hsv().saturate(span, factor)?.to_rgb(),
Self::Cmyk(_) => self.to_hsv().saturate(span, factor)?.to_cmyk(),
Self::Hsl(c) => Self::Hsl(c.saturate(factor.get() as f32)),
Self::Hsv(c) => Self::Hsv(c.saturate(factor.get() as f32)),
}) })
} }
@ -911,20 +912,21 @@ impl Color {
/// The factor to desaturate the color by. /// The factor to desaturate the color by.
factor: Ratio, factor: Ratio,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
let f = factor.get() as f32;
Ok(match self { Ok(match self {
Self::Luma(_) => { Self::Luma(_) => bail!(
bail!( span, "cannot desaturate grayscale color";
span, "cannot desaturate grayscale color"; hint: "try converting your color to RGB first"
hint: "try converting your color to RGB first" ),
); Self::Hsl(c) => Self::Hsl(c.desaturate(f)),
Self::Hsv(c) => Self::Hsv(c.desaturate(f)),
Self::Oklab(_)
| Self::Oklch(_)
| Self::LinearRgb(_)
| Self::Rgb(_)
| Self::Cmyk(_) => {
Color::Hsv(self.to_hsv().desaturate(f)).to_space(self.space())
} }
Self::Oklab(_) => self.to_hsv().desaturate(span, factor)?.to_oklab(),
Self::Oklch(_) => self.to_hsv().desaturate(span, factor)?.to_oklch(),
Self::LinearRgb(_) => self.to_hsv().desaturate(span, factor)?.to_linear_rgb(),
Self::Rgb(_) => self.to_hsv().desaturate(span, factor)?.to_rgb(),
Self::Cmyk(_) => self.to_hsv().desaturate(span, factor)?.to_cmyk(),
Self::Hsl(c) => Self::Hsl(c.desaturate(factor.get() as f32)),
Self::Hsv(c) => Self::Hsv(c.desaturate(factor.get() as f32)),
}) })
} }
@ -994,23 +996,17 @@ impl Color {
) -> SourceResult<Color> { ) -> SourceResult<Color> {
Ok(match space { Ok(match space {
ColorSpace::Oklch => { ColorSpace::Oklch => {
let Self::Oklch(oklch) = self.to_oklch() else { let oklch = self.to_oklch();
unreachable!();
};
let rotated = oklch.shift_hue(angle.to_deg() as f32); let rotated = oklch.shift_hue(angle.to_deg() as f32);
Self::Oklch(rotated).to_space(self.space()) Self::Oklch(rotated).to_space(self.space())
} }
ColorSpace::Hsl => { ColorSpace::Hsl => {
let Self::Hsl(hsl) = self.to_hsl() else { let hsl = self.to_hsl();
unreachable!();
};
let rotated = hsl.shift_hue(angle.to_deg() as f32); let rotated = hsl.shift_hue(angle.to_deg() as f32);
Self::Hsl(rotated).to_space(self.space()) Self::Hsl(rotated).to_space(self.space())
} }
ColorSpace::Hsv => { ColorSpace::Hsv => {
let Self::Hsv(hsv) = self.to_hsv() else { let hsv = self.to_hsv();
unreachable!();
};
let rotated = hsv.shift_hue(angle.to_deg() as f32); let rotated = hsv.shift_hue(angle.to_deg() as f32);
Self::Hsv(rotated).to_space(self.space()) Self::Hsv(rotated).to_space(self.space())
} }
@ -1281,19 +1277,19 @@ impl Color {
pub fn to_space(self, space: ColorSpace) -> Self { pub fn to_space(self, space: ColorSpace) -> Self {
match space { match space {
ColorSpace::Oklab => self.to_oklab(), ColorSpace::D65Gray => Self::Luma(self.to_luma()),
ColorSpace::Oklch => self.to_oklch(), ColorSpace::Oklab => Self::Oklab(self.to_oklab()),
ColorSpace::Srgb => self.to_rgb(), ColorSpace::Oklch => Self::Oklch(self.to_oklch()),
ColorSpace::LinearRgb => self.to_linear_rgb(), ColorSpace::Srgb => Self::Rgb(self.to_rgb()),
ColorSpace::Hsl => self.to_hsl(), ColorSpace::LinearRgb => Self::LinearRgb(self.to_linear_rgb()),
ColorSpace::Hsv => self.to_hsv(), ColorSpace::Cmyk => Self::Cmyk(self.to_cmyk()),
ColorSpace::Cmyk => self.to_cmyk(), ColorSpace::Hsl => Self::Hsl(self.to_hsl()),
ColorSpace::D65Gray => self.to_luma(), ColorSpace::Hsv => Self::Hsv(self.to_hsv()),
} }
} }
pub fn to_luma(self) -> Self { pub fn to_luma(self) -> Luma {
Self::Luma(match self { match self {
Self::Luma(c) => c, Self::Luma(c) => c,
Self::Oklab(c) => Luma::from_color(c), Self::Oklab(c) => Luma::from_color(c),
Self::Oklch(c) => Luma::from_color(c), Self::Oklch(c) => Luma::from_color(c),
@ -1302,11 +1298,11 @@ impl Color {
Self::Cmyk(c) => Luma::from_color(c.to_rgba()), Self::Cmyk(c) => Luma::from_color(c.to_rgba()),
Self::Hsl(c) => Luma::from_color(c), Self::Hsl(c) => Luma::from_color(c),
Self::Hsv(c) => Luma::from_color(c), Self::Hsv(c) => Luma::from_color(c),
}) }
} }
pub fn to_oklab(self) -> Self { pub fn to_oklab(self) -> Oklab {
Self::Oklab(match self { match self {
Self::Luma(c) => Oklab::from_color(c), Self::Luma(c) => Oklab::from_color(c),
Self::Oklab(c) => c, Self::Oklab(c) => c,
Self::Oklch(c) => Oklab::from_color(c), Self::Oklch(c) => Oklab::from_color(c),
@ -1315,11 +1311,11 @@ impl Color {
Self::Cmyk(c) => Oklab::from_color(c.to_rgba()), Self::Cmyk(c) => Oklab::from_color(c.to_rgba()),
Self::Hsl(c) => Oklab::from_color(c), Self::Hsl(c) => Oklab::from_color(c),
Self::Hsv(c) => Oklab::from_color(c), Self::Hsv(c) => Oklab::from_color(c),
}) }
} }
pub fn to_oklch(self) -> Self { pub fn to_oklch(self) -> Oklch {
Self::Oklch(match self { match self {
Self::Luma(c) => Oklch::from_color(c), Self::Luma(c) => Oklch::from_color(c),
Self::Oklab(c) => Oklch::from_color(c), Self::Oklab(c) => Oklch::from_color(c),
Self::Oklch(c) => c, Self::Oklch(c) => c,
@ -1328,11 +1324,11 @@ impl Color {
Self::Cmyk(c) => Oklch::from_color(c.to_rgba()), Self::Cmyk(c) => Oklch::from_color(c.to_rgba()),
Self::Hsl(c) => Oklch::from_color(c), Self::Hsl(c) => Oklch::from_color(c),
Self::Hsv(c) => Oklch::from_color(c), Self::Hsv(c) => Oklch::from_color(c),
}) }
} }
pub fn to_rgb(self) -> Self { pub fn to_rgb(self) -> Rgb {
Self::Rgb(match self { match self {
Self::Luma(c) => Rgb::from_color(c), Self::Luma(c) => Rgb::from_color(c),
Self::Oklab(c) => Rgb::from_color(c), Self::Oklab(c) => Rgb::from_color(c),
Self::Oklch(c) => Rgb::from_color(c), Self::Oklch(c) => Rgb::from_color(c),
@ -1341,11 +1337,11 @@ impl Color {
Self::Cmyk(c) => Rgb::from_color(c.to_rgba()), Self::Cmyk(c) => Rgb::from_color(c.to_rgba()),
Self::Hsl(c) => Rgb::from_color(c), Self::Hsl(c) => Rgb::from_color(c),
Self::Hsv(c) => Rgb::from_color(c), Self::Hsv(c) => Rgb::from_color(c),
}) }
} }
pub fn to_linear_rgb(self) -> Self { pub fn to_linear_rgb(self) -> LinearRgb {
Self::LinearRgb(match self { match self {
Self::Luma(c) => LinearRgb::from_color(c), Self::Luma(c) => LinearRgb::from_color(c),
Self::Oklab(c) => LinearRgb::from_color(c), Self::Oklab(c) => LinearRgb::from_color(c),
Self::Oklch(c) => LinearRgb::from_color(c), Self::Oklch(c) => LinearRgb::from_color(c),
@ -1354,11 +1350,11 @@ impl Color {
Self::Cmyk(c) => LinearRgb::from_color(c.to_rgba()), Self::Cmyk(c) => LinearRgb::from_color(c.to_rgba()),
Self::Hsl(c) => Rgb::from_color(c).into_linear(), Self::Hsl(c) => Rgb::from_color(c).into_linear(),
Self::Hsv(c) => Rgb::from_color(c).into_linear(), Self::Hsv(c) => Rgb::from_color(c).into_linear(),
}) }
} }
pub fn to_cmyk(self) -> Self { pub fn to_cmyk(self) -> Cmyk {
Self::Cmyk(match self { match self {
Self::Luma(c) => Cmyk::from_luma(c), Self::Luma(c) => Cmyk::from_luma(c),
Self::Oklab(c) => Cmyk::from_rgba(Rgb::from_color(c)), Self::Oklab(c) => Cmyk::from_rgba(Rgb::from_color(c)),
Self::Oklch(c) => Cmyk::from_rgba(Rgb::from_color(c)), Self::Oklch(c) => Cmyk::from_rgba(Rgb::from_color(c)),
@ -1367,11 +1363,11 @@ impl Color {
Self::Cmyk(c) => c, Self::Cmyk(c) => c,
Self::Hsl(c) => Cmyk::from_rgba(Rgb::from_color(c)), Self::Hsl(c) => Cmyk::from_rgba(Rgb::from_color(c)),
Self::Hsv(c) => Cmyk::from_rgba(Rgb::from_color(c)), Self::Hsv(c) => Cmyk::from_rgba(Rgb::from_color(c)),
}) }
} }
pub fn to_hsl(self) -> Self { pub fn to_hsl(self) -> Hsl {
Self::Hsl(match self { match self {
Self::Luma(c) => Hsl::from_color(c), Self::Luma(c) => Hsl::from_color(c),
Self::Oklab(c) => Hsl::from_color(c), Self::Oklab(c) => Hsl::from_color(c),
Self::Oklch(c) => Hsl::from_color(c), Self::Oklch(c) => Hsl::from_color(c),
@ -1380,11 +1376,11 @@ impl Color {
Self::Cmyk(c) => Hsl::from_color(c.to_rgba()), Self::Cmyk(c) => Hsl::from_color(c.to_rgba()),
Self::Hsl(c) => c, Self::Hsl(c) => c,
Self::Hsv(c) => Hsl::from_color(c), Self::Hsv(c) => Hsl::from_color(c),
}) }
} }
pub fn to_hsv(self) -> Self { pub fn to_hsv(self) -> Hsv {
Self::Hsv(match self { match self {
Self::Luma(c) => Hsv::from_color(c), Self::Luma(c) => Hsv::from_color(c),
Self::Oklab(c) => Hsv::from_color(c), Self::Oklab(c) => Hsv::from_color(c),
Self::Oklch(c) => Hsv::from_color(c), Self::Oklch(c) => Hsv::from_color(c),
@ -1393,7 +1389,7 @@ impl Color {
Self::Cmyk(c) => Hsv::from_color(c.to_rgba()), Self::Cmyk(c) => Hsv::from_color(c.to_rgba()),
Self::Hsl(c) => Hsv::from_color(c), Self::Hsl(c) => Hsv::from_color(c),
Self::Hsv(c) => c, Self::Hsv(c) => c,
}) }
} }
} }

View File

@ -315,15 +315,15 @@ fn create_func_data(func: &Func) -> TokenStream {
quote! { quote! {
#foundations::NativeFuncData { #foundations::NativeFuncData {
function: #closure, function: #foundations::NativeFuncPtr(&#closure),
name: #name, name: #name,
title: #title, title: #title,
docs: #docs, docs: #docs,
keywords: &[#(#keywords),*], keywords: &[#(#keywords),*],
contextual: #contextual, contextual: #contextual,
scope: ::std::sync::LazyLock::new(|| #scope), scope: ::std::sync::LazyLock::new(&|| #scope),
params: ::std::sync::LazyLock::new(|| ::std::vec![#(#params),*]), params: ::std::sync::LazyLock::new(&|| ::std::vec![#(#params),*]),
returns: ::std::sync::LazyLock::new(|| <#returns as #foundations::Reflect>::output()), returns: ::std::sync::LazyLock::new(&|| <#returns as #foundations::Reflect>::output()),
} }
} }
} }

View File

@ -255,13 +255,13 @@ pub fn to_sk_paint<'a>(
} }
pub fn to_sk_color(color: Color) -> sk::Color { pub fn to_sk_color(color: Color) -> sk::Color {
let [r, g, b, a] = color.to_rgb().to_vec4(); let (r, g, b, a) = color.to_rgb().into_components();
sk::Color::from_rgba(r, g, b, a) sk::Color::from_rgba(r, g, b, a)
.expect("components must always be in the range [0..=1]") .expect("components must always be in the range [0..=1]")
} }
pub fn to_sk_color_u8(color: Color) -> sk::ColorU8 { pub fn to_sk_color_u8(color: Color) -> sk::ColorU8 {
let [r, g, b, a] = color.to_rgb().to_vec4_u8(); let (r, g, b, a) = color.to_rgb().into_format::<u8, u8>().into_components();
sk::ColorU8::from_rgba(r, g, b, a) sk::ColorU8::from_rgba(r, g, b, a)
} }

View File

@ -23,7 +23,7 @@ pub use self::scalar::Scalar;
#[doc(hidden)] #[doc(hidden)]
pub use once_cell; pub use once_cell;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Display, Formatter};
use std::hash::Hash; use std::hash::Hash;
use std::iter::{Chain, Flatten, Rev}; use std::iter::{Chain, Flatten, Rev};
use std::num::{NonZeroU32, NonZeroUsize}; use std::num::{NonZeroU32, NonZeroUsize};
@ -52,6 +52,25 @@ where
Wrapper(f) Wrapper(f)
} }
/// Turn a closure into a struct implementing [`Display`].
pub fn display<F>(f: F) -> impl Display
where
F: Fn(&mut Formatter) -> std::fmt::Result,
{
struct Wrapper<F>(F);
impl<F> Display for Wrapper<F>
where
F: Fn(&mut Formatter) -> std::fmt::Result,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0(f)
}
}
Wrapper(f)
}
/// Calculate a 128-bit siphash of a value. /// Calculate a 128-bit siphash of a value.
pub fn hash128<T: Hash + ?Sized>(value: &T) -> u128 { pub fn hash128<T: Hash + ?Sized>(value: &T) -> u128 {
let mut state = SipHasher13::new(); let mut state = SipHasher13::new();

View File

@ -84,7 +84,8 @@
--- line-bad-point-array --- --- line-bad-point-array ---
// Test errors. // Test errors.
// Error: 12-19 point array must contain exactly two entries // Error: 12-19 array must contain exactly two items
// Hint: 12-19 the first item determines the value for the X axis and the second item the value for the Y axis
#line(end: (50pt,)) #line(end: (50pt,))
--- line-bad-point-component-type --- --- line-bad-point-component-type ---

View File

@ -76,7 +76,8 @@
#path(((0%, 0%), (0%, 0%), (0%, 0%), (0%, 0%))) #path(((0%, 0%), (0%, 0%), (0%, 0%), (0%, 0%)))
--- path-bad-point-array --- --- path-bad-point-array ---
// Error: 7-31 point array must contain exactly two entries // Error: 7-31 array must contain exactly two items
// Hint: 7-31 the first item determines the value for the X axis and the second item the value for the Y axis
// Warning: 2-6 the `path` function is deprecated, use `curve` instead // Warning: 2-6 the `path` function is deprecated, use `curve` instead
#path(((0%, 0%), (0%, 0%, 0%))) #path(((0%, 0%), (0%, 0%, 0%)))

View File

@ -49,7 +49,8 @@
) )
--- polygon-bad-point-array --- --- polygon-bad-point-array ---
// Error: 10-17 point array must contain exactly two entries // Error: 10-17 array must contain exactly two items
// Hint: 10-17 the first item determines the value for the X axis and the second item the value for the Y axis
#polygon((50pt,)) #polygon((50pt,))
--- polygon-infinite-size --- --- polygon-infinite-size ---