New color stuff
- CMYK function - More default colors - Interpret RGB values as sRGB
This commit is contained in:
parent
62cf2a19d7
commit
fe70db1f4c
28
NOTICE
28
NOTICE
@ -104,6 +104,34 @@ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
================================================================================
|
||||
|
||||
================================================================================
|
||||
The MIT License applies to:
|
||||
|
||||
* The default color set defined in `src/geom/color.rs` which is adapted from
|
||||
the colors.css project
|
||||
(https://clrs.cc/)
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
================================================================================
|
||||
|
||||
================================================================================
|
||||
The Apache License Version 2.0 applies to:
|
||||
|
||||
|
@ -39,7 +39,7 @@ use syntect::parsing::SyntaxSet;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult};
|
||||
use crate::geom::{Angle, Fractional, Length, Paint, Relative, RgbaColor};
|
||||
use crate::geom::{Angle, Color, Fractional, Length, Paint, Relative};
|
||||
use crate::image::ImageStore;
|
||||
use crate::layout::RootNode;
|
||||
use crate::library::{self, DecoLine, TextNode};
|
||||
@ -278,8 +278,8 @@ impl RawNode {
|
||||
let foreground = THEME
|
||||
.settings
|
||||
.foreground
|
||||
.map(RgbaColor::from)
|
||||
.unwrap_or(RgbaColor::BLACK)
|
||||
.map(Color::from)
|
||||
.unwrap_or(Color::BLACK)
|
||||
.into();
|
||||
|
||||
match syntax {
|
||||
|
@ -7,8 +7,10 @@ use std::sync::Arc;
|
||||
|
||||
use image::{DynamicImage, GenericImageView, ImageFormat, ImageResult, Rgba};
|
||||
use pdf_writer::types::{
|
||||
ActionType, AnnotationType, CidFontType, FontFlags, SystemInfo, UnicodeCmap,
|
||||
ActionType, AnnotationType, CidFontType, ColorSpaceOperand, FontFlags, SystemInfo,
|
||||
UnicodeCmap,
|
||||
};
|
||||
use pdf_writer::writers::ColorSpace;
|
||||
use pdf_writer::{Content, Filter, Finish, Name, PdfWriter, Rect, Ref, Str, TextStr};
|
||||
use ttf_parser::{name_id, GlyphId, Tag};
|
||||
|
||||
@ -30,6 +32,9 @@ pub fn pdf(ctx: &Context, frames: &[Arc<Frame>]) -> Vec<u8> {
|
||||
PdfExporter::new(ctx).export(frames)
|
||||
}
|
||||
|
||||
/// Identifies the sRGB color space definition.
|
||||
pub const SRGB: Name<'static> = Name(b"sRGB");
|
||||
|
||||
/// An exporter for a whole PDF document.
|
||||
struct PdfExporter<'a> {
|
||||
fonts: &'a FontStore,
|
||||
@ -316,6 +321,8 @@ impl<'a> PdfExporter<'a> {
|
||||
pages.count(page_refs.len() as i32).kids(page_refs);
|
||||
|
||||
let mut resources = pages.resources();
|
||||
resources.color_spaces().insert(SRGB).start::<ColorSpace>().srgb();
|
||||
|
||||
let mut fonts = resources.fonts();
|
||||
for (font_ref, f) in self.face_map.pdf_indices(&self.face_refs) {
|
||||
let name = format_eco!("F{}", f);
|
||||
@ -390,6 +397,8 @@ impl<'a> PageExporter<'a> {
|
||||
// Make the coordinate system start at the top-left.
|
||||
self.bottom = frame.size.y.to_f32();
|
||||
self.content.transform([1.0, 0.0, 0.0, -1.0, 0.0, self.bottom]);
|
||||
self.content.set_fill_color_space(ColorSpaceOperand::Named(SRGB));
|
||||
self.content.set_stroke_color_space(ColorSpaceOperand::Named(SRGB));
|
||||
self.write_frame(frame);
|
||||
Page {
|
||||
size: frame.size,
|
||||
@ -624,23 +633,32 @@ impl<'a> PageExporter<'a> {
|
||||
|
||||
fn set_fill(&mut self, fill: Paint) {
|
||||
if self.state.fill != Some(fill) {
|
||||
let Paint::Solid(Color::Rgba(c)) = fill;
|
||||
self.content.set_fill_rgb(
|
||||
c.r as f32 / 255.0,
|
||||
c.g as f32 / 255.0,
|
||||
c.b as f32 / 255.0,
|
||||
);
|
||||
let f = |c| c as f32 / 255.0;
|
||||
let Paint::Solid(color) = fill;
|
||||
match color {
|
||||
Color::Rgba(c) => {
|
||||
self.content.set_fill_color([f(c.r), f(c.g), f(c.b)]);
|
||||
}
|
||||
Color::Cmyk(c) => {
|
||||
self.content.set_fill_cmyk(f(c.c), f(c.m), f(c.y), f(c.k));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_stroke(&mut self, stroke: Stroke) {
|
||||
if self.state.stroke != Some(stroke) {
|
||||
let Paint::Solid(Color::Rgba(c)) = stroke.paint;
|
||||
self.content.set_stroke_rgb(
|
||||
c.r as f32 / 255.0,
|
||||
c.g as f32 / 255.0,
|
||||
c.b as f32 / 255.0,
|
||||
);
|
||||
let f = |c| c as f32 / 255.0;
|
||||
let Paint::Solid(color) = stroke.paint;
|
||||
match color {
|
||||
Color::Rgba(c) => {
|
||||
self.content.set_stroke_color([f(c.r), f(c.g), f(c.b)]);
|
||||
}
|
||||
Color::Cmyk(c) => {
|
||||
self.content.set_stroke_cmyk(f(c.c), f(c.m), f(c.y), f(c.k));
|
||||
}
|
||||
}
|
||||
|
||||
self.content.set_line_width(stroke.thickness.to_f32());
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use usvg::FitTo;
|
||||
|
||||
use crate::font::{Face, FaceId};
|
||||
use crate::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text};
|
||||
use crate::geom::{self, Color, Length, Paint, PathElement, Size, Transform};
|
||||
use crate::geom::{self, Length, Paint, PathElement, Size, Transform};
|
||||
use crate::image::{Image, RasterImage, Svg};
|
||||
use crate::Context;
|
||||
|
||||
@ -279,7 +279,8 @@ fn render_outline_glyph(
|
||||
let bottom = top + mh;
|
||||
|
||||
// Premultiply the text color.
|
||||
let Paint::Solid(Color::Rgba(c)) = text.fill;
|
||||
let Paint::Solid(color) = text.fill;
|
||||
let c = color.to_rgba();
|
||||
let color = sk::ColorU8::from_rgba(c.r, c.g, c.b, 255).premultiply().get();
|
||||
|
||||
// Blend the glyph bitmap with the existing pixels on the canvas.
|
||||
@ -453,7 +454,8 @@ impl From<Transform> for sk::Transform {
|
||||
impl From<Paint> for sk::Paint<'static> {
|
||||
fn from(paint: Paint) -> Self {
|
||||
let mut sk_paint = sk::Paint::default();
|
||||
let Paint::Solid(Color::Rgba(c)) = paint;
|
||||
let Paint::Solid(color) = paint;
|
||||
let c = color.to_rgba();
|
||||
sk_paint.set_color_rgba8(c.r, c.g, c.b, c.a);
|
||||
sk_paint.anti_alias = true;
|
||||
sk_paint
|
||||
|
@ -26,12 +26,44 @@ where
|
||||
pub enum Color {
|
||||
/// An 8-bit RGBA color.
|
||||
Rgba(RgbaColor),
|
||||
/// An 8-bit CMYK color.
|
||||
Cmyk(CmykColor),
|
||||
}
|
||||
|
||||
impl Color {
|
||||
pub const BLACK: Self = Self::Rgba(RgbaColor::new(0x00, 0x00, 0x00, 0xFF));
|
||||
pub const GRAY: Self = Self::Rgba(RgbaColor::new(0xAA, 0xAA, 0xAA, 0xFF));
|
||||
pub const SILVER: Self = Self::Rgba(RgbaColor::new(0xDD, 0xDD, 0xDD, 0xFF));
|
||||
pub const WHITE: Self = Self::Rgba(RgbaColor::new(0xFF, 0xFF, 0xFF, 0xFF));
|
||||
pub const NAVY: Self = Self::Rgba(RgbaColor::new(0x00, 0x1f, 0x3f, 0xFF));
|
||||
pub const BLUE: Self = Self::Rgba(RgbaColor::new(0x00, 0x74, 0xD9, 0xFF));
|
||||
pub const AQUA: Self = Self::Rgba(RgbaColor::new(0x7F, 0xDB, 0xFF, 0xFF));
|
||||
pub const TEAL: Self = Self::Rgba(RgbaColor::new(0x39, 0xCC, 0xCC, 0xFF));
|
||||
pub const EASTERN: Self = Self::Rgba(RgbaColor::new(0x23, 0x9D, 0xAD, 0xFF));
|
||||
pub const PURPLE: Self = Self::Rgba(RgbaColor::new(0xB1, 0x0D, 0xC9, 0xFF));
|
||||
pub const FUCHSIA: Self = Self::Rgba(RgbaColor::new(0xF0, 0x12, 0xBE, 0xFF));
|
||||
pub const MAROON: Self = Self::Rgba(RgbaColor::new(0x85, 0x14, 0x4b, 0xFF));
|
||||
pub const RED: Self = Self::Rgba(RgbaColor::new(0xFF, 0x41, 0x36, 0xFF));
|
||||
pub const ORANGE: Self = Self::Rgba(RgbaColor::new(0xFF, 0x85, 0x1B, 0xFF));
|
||||
pub const YELLOW: Self = Self::Rgba(RgbaColor::new(0xFF, 0xDC, 0x00, 0xFF));
|
||||
pub const OLIVE: Self = Self::Rgba(RgbaColor::new(0x3D, 0x99, 0x70, 0xFF));
|
||||
pub const GREEN: Self = Self::Rgba(RgbaColor::new(0x2E, 0xCC, 0x40, 0xFF));
|
||||
pub const LIME: Self = Self::Rgba(RgbaColor::new(0x01, 0xFF, 0x70, 0xFF));
|
||||
|
||||
/// Convert this color to RGBA.
|
||||
pub fn to_rgba(self) -> RgbaColor {
|
||||
match self {
|
||||
Self::Rgba(rgba) => rgba,
|
||||
Self::Cmyk(cmyk) => cmyk.to_rgba(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Color {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Rgba(c) => Debug::fmt(c, f),
|
||||
Self::Cmyk(c) => Debug::fmt(c, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -59,19 +91,13 @@ pub struct RgbaColor {
|
||||
}
|
||||
|
||||
impl RgbaColor {
|
||||
/// Black color.
|
||||
pub const BLACK: Self = Self { r: 0, g: 0, b: 0, a: 255 };
|
||||
|
||||
/// White color.
|
||||
pub const WHITE: Self = Self { r: 255, g: 255, b: 255, a: 255 };
|
||||
|
||||
/// Construct a new RGBA color.
|
||||
pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
Self { r, g, b, a }
|
||||
}
|
||||
|
||||
/// Construct a new, opaque gray color.
|
||||
pub fn gray(luma: u8) -> Self {
|
||||
pub const fn gray(luma: u8) -> Self {
|
||||
Self::new(luma, luma, luma, 255)
|
||||
}
|
||||
}
|
||||
@ -155,6 +181,72 @@ impl Display for RgbaError {
|
||||
|
||||
impl std::error::Error for RgbaError {}
|
||||
|
||||
/// An 8-bit CMYK color.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct CmykColor {
|
||||
/// The cyan component.
|
||||
pub c: u8,
|
||||
/// The magenta component.
|
||||
pub m: u8,
|
||||
/// The yellow component.
|
||||
pub y: u8,
|
||||
/// The key (black) component.
|
||||
pub k: u8,
|
||||
}
|
||||
|
||||
impl CmykColor {
|
||||
/// Construct a new CMYK color.
|
||||
pub const fn new(c: u8, m: u8, y: u8, k: u8) -> Self {
|
||||
Self { c, m, y, k }
|
||||
}
|
||||
|
||||
/// Construct a new, opaque gray color as a fraction of true black.
|
||||
pub fn gray(luma: u8) -> Self {
|
||||
Self::new(
|
||||
(luma as f64 * 0.75) as u8,
|
||||
(luma as f64 * 0.68) as u8,
|
||||
(luma as f64 * 0.67) as u8,
|
||||
(luma as f64 * 0.90) as u8,
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert this color to RGBA.
|
||||
pub fn to_rgba(self) -> RgbaColor {
|
||||
let k = self.k as f32 / 255.0;
|
||||
let f = |c| {
|
||||
let c = c as f32 / 255.0;
|
||||
(255.0 * (1.0 - c) * (1.0 - k)).round() as u8
|
||||
};
|
||||
|
||||
RgbaColor {
|
||||
r: f(self.c),
|
||||
g: f(self.m),
|
||||
b: f(self.y),
|
||||
a: 255,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for CmykColor {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let g = |c| c as f64 / 255.0;
|
||||
write!(
|
||||
f,
|
||||
"cmyk({:.1}%, {:.1}%, {:.1}%, {:.1}%)",
|
||||
g(self.c),
|
||||
g(self.m),
|
||||
g(self.y),
|
||||
g(self.k),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CmykColor> for Color {
|
||||
fn from(cmyk: CmykColor) -> Self {
|
||||
Self::Cmyk(cmyk)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -142,6 +142,7 @@ pub fn new() -> Scope {
|
||||
std.def_func("mod", modulo);
|
||||
std.def_func("range", range);
|
||||
std.def_func("rgb", rgb);
|
||||
std.def_func("cmyk", cmyk);
|
||||
std.def_func("lower", lower);
|
||||
std.def_func("upper", upper);
|
||||
std.def_func("roman", roman);
|
||||
@ -150,12 +151,24 @@ pub fn new() -> Scope {
|
||||
std.def_func("sorted", sorted);
|
||||
|
||||
// Predefined colors.
|
||||
// TODO: More colors.
|
||||
std.def_const("white", RgbaColor::WHITE);
|
||||
std.def_const("black", RgbaColor::BLACK);
|
||||
std.def_const("eastern", RgbaColor::new(0x23, 0x9D, 0xAD, 0xFF));
|
||||
std.def_const("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
|
||||
std.def_const("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
|
||||
std.def_const("black", Color::BLACK);
|
||||
std.def_const("gray", Color::GRAY);
|
||||
std.def_const("silver", Color::SILVER);
|
||||
std.def_const("white", Color::WHITE);
|
||||
std.def_const("navy", Color::NAVY);
|
||||
std.def_const("blue", Color::BLUE);
|
||||
std.def_const("aqua", Color::AQUA);
|
||||
std.def_const("teal", Color::TEAL);
|
||||
std.def_const("eastern", Color::EASTERN);
|
||||
std.def_const("purple", Color::PURPLE);
|
||||
std.def_const("fuchsia", Color::FUCHSIA);
|
||||
std.def_const("maroon", Color::MAROON);
|
||||
std.def_const("red", Color::RED);
|
||||
std.def_const("orange", Color::ORANGE);
|
||||
std.def_const("yellow", Color::YELLOW);
|
||||
std.def_const("olive", Color::OLIVE);
|
||||
std.def_const("green", Color::GREEN);
|
||||
std.def_const("lime", Color::LIME);
|
||||
|
||||
// Other constants.
|
||||
std.def_const("ltr", Dir::LTR);
|
||||
|
@ -125,7 +125,7 @@ impl<S: ShapeKind> Layout for ShapeNode<S> {
|
||||
let thickness = styles.get(Self::THICKNESS);
|
||||
let stroke = styles
|
||||
.get(Self::STROKE)
|
||||
.unwrap_or(fill.is_none().then(|| RgbaColor::BLACK.into()))
|
||||
.unwrap_or(fill.is_none().then(|| Color::BLACK.into()))
|
||||
.map(|paint| Stroke { paint, thickness });
|
||||
|
||||
if fill.is_some() || stroke.is_some() {
|
||||
|
@ -21,7 +21,7 @@ impl TableNode {
|
||||
/// The secondary cell fill color.
|
||||
pub const SECONDARY: Option<Paint> = None;
|
||||
/// How the stroke the cells.
|
||||
pub const STROKE: Option<Paint> = Some(RgbaColor::BLACK.into());
|
||||
pub const STROKE: Option<Paint> = Some(Color::BLACK.into());
|
||||
/// The stroke's thickness.
|
||||
pub const THICKNESS: Length = Length::pt(1.0);
|
||||
/// How much to pad the cells's content.
|
||||
|
@ -51,7 +51,7 @@ impl TextNode {
|
||||
/// Whether a monospace font should be preferred.
|
||||
pub const MONOSPACE: bool = false;
|
||||
/// The glyph fill color.
|
||||
pub const FILL: Paint = RgbaColor::BLACK.into();
|
||||
pub const FILL: Paint = Color::BLACK.into();
|
||||
/// Decorative lines.
|
||||
#[fold(|a, b| a.into_iter().chain(b).collect())]
|
||||
pub const LINES: Vec<Decoration> = vec![];
|
||||
|
@ -94,21 +94,50 @@ pub fn rgb(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
Err(_) => bail!(string.span, "invalid hex string"),
|
||||
}
|
||||
} else {
|
||||
let r = args.expect("red component")?;
|
||||
let g = args.expect("green component")?;
|
||||
let b = args.expect("blue component")?;
|
||||
let a = args.eat()?.unwrap_or(Spanned::new(1.0, Span::detached()));
|
||||
let f = |Spanned { v, span }: Spanned<f64>| {
|
||||
if (0.0 ..= 1.0).contains(&v) {
|
||||
Ok((v * 255.0).round() as u8)
|
||||
struct Component(u8);
|
||||
|
||||
castable! {
|
||||
Component,
|
||||
Expected: "integer or relative",
|
||||
Value::Int(v) => match v {
|
||||
0 ..= 255 => Self(v as u8),
|
||||
_ => Err("must be between 0 and 255")?,
|
||||
},
|
||||
Value::Relative(v) => if (0.0 ..= 1.0).contains(&v.get()) {
|
||||
Self((v.get() * 255.0).round() as u8)
|
||||
} else {
|
||||
bail!(span, "value must be between 0.0 and 1.0");
|
||||
}
|
||||
};
|
||||
RgbaColor::new(f(r)?, f(g)?, f(b)?, f(a)?)
|
||||
Err("must be between 0% and 100%")?
|
||||
},
|
||||
}
|
||||
|
||||
let Component(r) = args.expect("red component")?;
|
||||
let Component(g) = args.expect("green component")?;
|
||||
let Component(b) = args.expect("blue component")?;
|
||||
let Component(a) = args.eat()?.unwrap_or(Component(255));
|
||||
RgbaColor::new(r, g, b, a)
|
||||
},
|
||||
))
|
||||
}
|
||||
/// Create an CMYK color.
|
||||
pub fn cmyk(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
struct Component(u8);
|
||||
|
||||
castable! {
|
||||
Component,
|
||||
Expected: "relative",
|
||||
Value::Relative(v) => if (0.0 ..= 1.0).contains(&v.get()) {
|
||||
Self((v.get() * 255.0).round() as u8)
|
||||
} else {
|
||||
Err("must be between 0% and 100%")?
|
||||
},
|
||||
}
|
||||
|
||||
let Component(c) = args.expect("cyan component")?;
|
||||
let Component(m) = args.expect("magenta component")?;
|
||||
let Component(y) = args.expect("yellow component")?;
|
||||
let Component(k) = args.expect("key component")?;
|
||||
Ok(Value::Color(CmykColor::new(c, m, y, k).into()))
|
||||
}
|
||||
|
||||
/// The absolute value of a numeric value.
|
||||
pub fn abs(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
|
BIN
tests/ref/utility/color.png
Normal file
BIN
tests/ref/utility/color.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 168 B |
@ -8,8 +8,8 @@
|
||||
)
|
||||
|
||||
#let shaded = {
|
||||
let v = 0
|
||||
let next() = { v += 0.1; rgb(v, v, v) }
|
||||
let v = 0%
|
||||
let next() = { v += 10%; rgb(v, v, v) }
|
||||
w => rect(width: w, height: 10pt, fill: next())
|
||||
}
|
||||
|
||||
|
@ -3,20 +3,21 @@
|
||||
|
||||
---
|
||||
// Compare both ways.
|
||||
#test(rgb(0.0, 0.3, 0.7), rgb("004db3"))
|
||||
#test(rgb(0%, 30%, 70%), rgb("004db3"))
|
||||
|
||||
// Alpha channel.
|
||||
#test(rgb(1.0, 0.0, 0.0, 0.5), rgb("ff000080"))
|
||||
#test(rgb(255, 0, 0, 50%), rgb("ff000080"))
|
||||
|
||||
---
|
||||
// Test CMYK color conversion.
|
||||
// Ref: true
|
||||
#rect(fill: cmyk(69%, 11%, 69%, 41%))
|
||||
#rect(fill: cmyk(50%, 64%, 16%, 17%))
|
||||
|
||||
---
|
||||
// Error for values that are out of range.
|
||||
// Error: 11-14 value must be between 0.0 and 1.0
|
||||
#test(rgb(-30, 15.5, 0.5))
|
||||
|
||||
---
|
||||
// Error for values that are out of range.
|
||||
// Error: 26-30 value must be between 0.0 and 1.0
|
||||
#test(rgb(0.1, 0.2, 0.3, -0.1))
|
||||
// Error: 11-14 must be between 0 and 255
|
||||
#test(rgb(-30, 15, 50))
|
||||
|
||||
---
|
||||
// Error: 6-11 invalid hex string
|
||||
@ -31,5 +32,5 @@
|
||||
#rgb(0, 1)
|
||||
|
||||
---
|
||||
// Error: 21-26 expected float, found boolean
|
||||
#rgb(0.1, 0.2, 0.3, false)
|
||||
// Error: 21-26 expected integer or relative, found boolean
|
||||
#rgb(10%, 20%, 30%, false)
|
||||
|
@ -11,7 +11,7 @@ use walkdir::WalkDir;
|
||||
use typst::diag::Error;
|
||||
use typst::eval::{Smart, StyleMap, Value};
|
||||
use typst::frame::{Element, Frame};
|
||||
use typst::geom::Length;
|
||||
use typst::geom::{Length, RgbaColor};
|
||||
use typst::library::{PageNode, TextNode};
|
||||
use typst::loading::FsLoader;
|
||||
use typst::parse::Scanner;
|
||||
@ -77,6 +77,8 @@ fn main() {
|
||||
|
||||
// Hook up an assert function into the global scope.
|
||||
let mut std = typst::library::new();
|
||||
std.def_const("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
|
||||
std.def_const("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
|
||||
std.def_func("test", move |_, args| {
|
||||
let lhs = args.expect::<Value>("left-hand side")?;
|
||||
let rhs = args.expect::<Value>("right-hand side")?;
|
||||
|
Loading…
Reference in New Issue
Block a user