Gradient Part 1 - Color rework (#2171)
This commit is contained in:
parent
bb59f0e2b2
commit
163c2e1aa2
49
Cargo.lock
generated
49
Cargo.lock
generated
@ -103,6 +103,15 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.7"
|
||||
@ -705,6 +714,12 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fast-srgb8"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
@ -1535,15 +1550,6 @@ version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e25be21376a772d15f97ae789845340a9651d3c4246ff5ebb6a2b35f9c37bd31"
|
||||
|
||||
[[package]]
|
||||
name = "oklab"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "467e40ada50d13bab19019e3707862b5076ca15841f31ee1474c40397c1b9f11"
|
||||
dependencies = [
|
||||
"rgb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.18.0"
|
||||
@ -1591,6 +1597,29 @@ dependencies = [
|
||||
"zopfli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "palette"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2e2f34147767aa758aa649415b50a69eeb46a67f9dc7db8011eeb3d84b351dc"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"fast-srgb8",
|
||||
"libm",
|
||||
"palette_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "palette_derive"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7db010ec5ff3d4385e4f133916faacd9dad0f6a09394c92d825b3aed310fa0a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.8"
|
||||
@ -2715,8 +2744,8 @@ dependencies = [
|
||||
"indexmap 2.0.0",
|
||||
"log",
|
||||
"miniz_oxide",
|
||||
"oklab",
|
||||
"once_cell",
|
||||
"palette",
|
||||
"pdf-writer",
|
||||
"pixglyph",
|
||||
"regex",
|
||||
|
121
NOTICE
121
NOTICE
@ -1,5 +1,126 @@
|
||||
Licenses for third party components used by this project can be found below.
|
||||
|
||||
================================================================================
|
||||
The Creative Commons Zero v1.0 Universal License applies to:
|
||||
* The ICC profiles found in `crates/typst/icc/*`
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator and
|
||||
subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for the
|
||||
purpose of contributing to a commons of creative, cultural and scientific
|
||||
works ("Commons") that the public can reliably and without fear of later
|
||||
claims of infringement build upon, modify, incorporate in other works, reuse
|
||||
and redistribute as freely as possible in any form whatsoever and for any
|
||||
purposes, including without limitation commercial purposes. These owners may
|
||||
contribute to the Commons to promote the ideal of a free culture and the
|
||||
further production of creative, cultural and scientific works, or to gain
|
||||
reputation or greater distribution for their Work in part through the use and
|
||||
efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any expectation
|
||||
of additional consideration or compensation, the person associating CC0 with a
|
||||
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
|
||||
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
|
||||
and publicly distribute the Work under its terms, with knowledge of his or her
|
||||
Copyright and Related Rights in the Work and the meaning and intended legal
|
||||
effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not limited
|
||||
to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display, communicate,
|
||||
and translate a Work;
|
||||
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
|
||||
iii. publicity and privacy rights pertaining to a person's image or likeness
|
||||
depicted in a Work;
|
||||
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data in
|
||||
a Work;
|
||||
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation thereof,
|
||||
including any amended or successor version of such directive); and
|
||||
|
||||
vii. other similar, equivalent or corresponding rights throughout the world
|
||||
based on applicable law or treaty, and any national implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention of,
|
||||
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
|
||||
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
|
||||
and Related Rights and associated claims and causes of action, whether now
|
||||
known or unknown (including existing as well as future claims and causes of
|
||||
action), in the Work (i) in all territories worldwide, (ii) for the maximum
|
||||
duration provided by applicable law or treaty (including future time
|
||||
extensions), (iii) in any current or future medium and for any number of
|
||||
copies, and (iv) for any purpose whatsoever, including without limitation
|
||||
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
|
||||
the Waiver for the benefit of each member of the public at large and to the
|
||||
detriment of Affirmer's heirs and successors, fully intending that such Waiver
|
||||
shall not be subject to revocation, rescission, cancellation, termination, or
|
||||
any other legal or equitable action to disrupt the quiet enjoyment of the Work
|
||||
by the public as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason be
|
||||
judged legally invalid or ineffective under applicable law, then the Waiver
|
||||
shall be preserved to the maximum extent permitted taking into account
|
||||
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
|
||||
is so judged Affirmer hereby grants to each affected person a royalty-free,
|
||||
non transferable, non sublicensable, non exclusive, irrevocable and
|
||||
unconditional license to exercise Affirmer's Copyright and Related Rights in
|
||||
the Work (i) in all territories worldwide, (ii) for the maximum duration
|
||||
provided by applicable law or treaty (including future time extensions), (iii)
|
||||
in any current or future medium and for any number of copies, and (iv) for any
|
||||
purpose whatsoever, including without limitation commercial, advertising or
|
||||
promotional purposes (the "License"). The License shall be deemed effective as
|
||||
of the date CC0 was applied by Affirmer to the Work. Should any part of the
|
||||
License for any reason be judged legally invalid or ineffective under
|
||||
applicable law, such partial invalidity or ineffectiveness shall not
|
||||
invalidate the remainder of the License, and in such case Affirmer hereby
|
||||
affirms that he or she will not (i) exercise any of his or her remaining
|
||||
Copyright and Related Rights in the Work or (ii) assert any associated claims
|
||||
and causes of action with respect to the Work, in either case contrary to
|
||||
Affirmer's express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
|
||||
b. Affirmer offers the Work as-is and makes no representations or warranties
|
||||
of any kind concerning the Work, express, implied, statutory or otherwise,
|
||||
including without limitation warranties of title, merchantability, fitness
|
||||
for a particular purpose, non infringement, or the absence of latent or
|
||||
other defects, accuracy, or the present or absence of errors, whether or not
|
||||
discoverable, all to the greatest extent permissible under applicable law.
|
||||
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without limitation
|
||||
any person's Copyright and Related Rights in the Work. Further, Affirmer
|
||||
disclaims responsibility for obtaining any necessary consents, permissions
|
||||
or other rights required for any use of the Work.
|
||||
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to this
|
||||
CC0 or use of the Work.
|
||||
|
||||
For more information, please see
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
================================================================================
|
||||
The 0BSD License applies to:
|
||||
|
||||
|
@ -63,6 +63,7 @@ fn prelude(global: &mut Scope) {
|
||||
global.define("green", Color::GREEN);
|
||||
global.define("lime", Color::LIME);
|
||||
global.define("luma", Color::luma_data());
|
||||
global.define("oklab", Color::oklab_data());
|
||||
global.define("rgb", Color::rgb_data());
|
||||
global.define("cmyk", Color::cmyk_data());
|
||||
global.define("range", Array::range_data());
|
||||
|
@ -240,7 +240,7 @@ pub struct HighlightElem {
|
||||
/// ```example
|
||||
/// This is #highlight(fill: blue)[with blue].
|
||||
/// ```
|
||||
#[default(Color::Rgba(RgbaColor::new(0xFF, 0xFF, 0x5F, 0xFF)).into())]
|
||||
#[default(Color::from_u8(0xFF, 0xFF, 0x5F, 0xFF).into())]
|
||||
pub fill: Paint,
|
||||
|
||||
/// The top end of the background rectangle.
|
||||
|
@ -454,11 +454,12 @@ fn styled(piece: &str, foreground: Paint, style: synt::Style) -> Content {
|
||||
body
|
||||
}
|
||||
|
||||
fn to_typst(synt::Color { r, g, b, a }: synt::Color) -> RgbaColor {
|
||||
RgbaColor { r, g, b, a }
|
||||
fn to_typst(synt::Color { r, g, b, a }: synt::Color) -> Color {
|
||||
Color::from_u8(r, g, b, a)
|
||||
}
|
||||
|
||||
fn to_syn(RgbaColor { r, g, b, a }: RgbaColor) -> synt::Color {
|
||||
fn to_syn(color: Color) -> synt::Color {
|
||||
let [r, g, b, a] = color.to_vec4_u8();
|
||||
synt::Color { r, g, b, a }
|
||||
}
|
||||
|
||||
@ -628,7 +629,7 @@ fn item(
|
||||
synt::ThemeItem {
|
||||
scope: scope.parse().unwrap(),
|
||||
style: synt::StyleModifier {
|
||||
foreground: color.map(|s| to_syn(s.parse::<RgbaColor>().unwrap())),
|
||||
foreground: color.map(|s| to_syn(s.parse::<Color>().unwrap())),
|
||||
background: None,
|
||||
font_style,
|
||||
},
|
||||
|
@ -30,10 +30,10 @@ image = { version = "0.24", default-features = false, features = ["png", "jpeg",
|
||||
indexmap = { version = "2", features = ["serde"] }
|
||||
log = "0.4"
|
||||
miniz_oxide = "0.7"
|
||||
oklab = "1"
|
||||
once_cell = "1"
|
||||
pdf-writer = "0.8.1"
|
||||
pixglyph = "0.2"
|
||||
palette = { version = "0.7.3", default-features = false, features = ["approx", "libm"] }
|
||||
regex = "1"
|
||||
resvg = { version = "0.35.0", default-features = false, features = ["raster-images"] }
|
||||
roxmltree = "0.18"
|
||||
|
@ -13,8 +13,7 @@ use crate::export::PdfPageLabel;
|
||||
use crate::font::Font;
|
||||
use crate::geom::{
|
||||
self, rounded_rect, Abs, Axes, Color, Corners, Dir, Em, FixedAlign, FixedStroke,
|
||||
Geometry, Length, Numeric, Paint, Point, Rel, RgbaColor, Shape, Sides, Size,
|
||||
Transform,
|
||||
Geometry, Length, Numeric, Paint, Point, Rel, Shape, Sides, Size, Transform,
|
||||
};
|
||||
use crate::image::Image;
|
||||
use crate::model::{Content, Location, MetaElem, StyleChain};
|
||||
@ -352,8 +351,7 @@ impl Frame {
|
||||
0,
|
||||
Point::zero(),
|
||||
FrameItem::Shape(
|
||||
Geometry::Rect(self.size)
|
||||
.filled(RgbaColor { a: 100, ..Color::TEAL.to_rgba() }.into()),
|
||||
Geometry::Rect(self.size).filled(Color::TEAL.with_alpha(0.5).into()),
|
||||
Span::detached(),
|
||||
),
|
||||
);
|
||||
|
@ -378,6 +378,15 @@ impl PartialEq for Func {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&NativeFuncData> for Func {
|
||||
fn eq(&self, other: &&NativeFuncData) -> bool {
|
||||
match &self.repr {
|
||||
Repr::Native(native) => native.function == other.function,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Repr> for Func {
|
||||
fn from(repr: Repr) -> Self {
|
||||
Self { repr, span: Span::detached() }
|
||||
|
@ -604,7 +604,6 @@ primitive! { Plugin: "plugin", Plugin }
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::eval::{array, dict};
|
||||
use crate::geom::RgbaColor;
|
||||
|
||||
#[track_caller]
|
||||
fn test(value: impl IntoValue, exp: &str) {
|
||||
@ -623,7 +622,6 @@ mod tests {
|
||||
test(Ratio::one() / 2.0, "50%");
|
||||
test(Ratio::new(0.3) + Length::from(Abs::cm(2.0)), "30% + 56.69pt");
|
||||
test(Fr::one() * 7.55, "7.55fr");
|
||||
test(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "rgb(\"#010101\")");
|
||||
|
||||
// Collections.
|
||||
test("hello", r#""hello""#);
|
||||
|
428
crates/typst/src/export/pdf/color.rs
Normal file
428
crates/typst/src/export/pdf/color.rs
Normal file
@ -0,0 +1,428 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use pdf_writer::types::DeviceNSubtype;
|
||||
use pdf_writer::{writers, Dict, Filter, Name, PdfWriter, Ref};
|
||||
|
||||
use super::page::PageContext;
|
||||
use super::RefExt;
|
||||
use crate::export::pdf::deflate;
|
||||
use crate::geom::{Color, ColorSpace, Paint};
|
||||
|
||||
// The names of the color spaces.
|
||||
pub const SRGB: Name<'static> = Name(b"srgb");
|
||||
pub const D65_GRAY: Name<'static> = Name(b"d65gray");
|
||||
pub const OKLAB: Name<'static> = Name(b"oklab");
|
||||
pub const HSV: Name<'static> = Name(b"hsv");
|
||||
pub const HSL: Name<'static> = Name(b"hsl");
|
||||
pub const LINEAR_SRGB: Name<'static> = Name(b"linearrgb");
|
||||
|
||||
// The names of the color components.
|
||||
const OKLAB_L: Name<'static> = Name(b"L");
|
||||
const OKLAB_A: Name<'static> = Name(b"A");
|
||||
const OKLAB_B: Name<'static> = Name(b"B");
|
||||
const HSV_H: Name<'static> = Name(b"H");
|
||||
const HSV_S: Name<'static> = Name(b"S");
|
||||
const HSV_V: Name<'static> = Name(b"V");
|
||||
const HSL_H: Name<'static> = Name(b"H");
|
||||
const HSL_S: Name<'static> = Name(b"S");
|
||||
const HSL_L: Name<'static> = Name(b"L");
|
||||
|
||||
// The ICC profiles.
|
||||
const SRGB_ICC: &[u8] = include_bytes!("./icc/sRGB-v4.icc");
|
||||
const GRAY_ICC: &[u8] = include_bytes!("./icc/sGrey-v4.icc");
|
||||
|
||||
// The PostScript functions for color spaces.
|
||||
const OKLAB_SOURCE: &str = include_str!("./postscript/oklab.ps");
|
||||
const HSL_SOURCE: &str = include_str!("./postscript/hsl.ps");
|
||||
const HSV_SOURCE: &str = include_str!("./postscript/hsv.ps");
|
||||
|
||||
/// The color spaces present in the PDF document
|
||||
#[derive(Default)]
|
||||
pub struct ColorSpaces {
|
||||
oklab: Option<Ref>,
|
||||
srgb: Option<Ref>,
|
||||
d65_gray: Option<Ref>,
|
||||
hsv: Option<Ref>,
|
||||
hsl: Option<Ref>,
|
||||
use_linear_rgb: bool,
|
||||
}
|
||||
|
||||
impl ColorSpaces {
|
||||
/// Get a reference to the oklab color space.
|
||||
///
|
||||
/// # Warning
|
||||
/// The A and B components of the color must be offset by +0.4 before being
|
||||
/// encoded into the PDF file.
|
||||
pub fn oklab(&mut self, alloc: &mut Ref) -> Ref {
|
||||
*self.oklab.get_or_insert_with(|| alloc.bump())
|
||||
}
|
||||
|
||||
/// Get a reference to the srgb color space.
|
||||
pub fn srgb(&mut self, alloc: &mut Ref) -> Ref {
|
||||
*self.srgb.get_or_insert_with(|| alloc.bump())
|
||||
}
|
||||
|
||||
/// Get a reference to the gray color space.
|
||||
pub fn d65_gray(&mut self, alloc: &mut Ref) -> Ref {
|
||||
*self.d65_gray.get_or_insert_with(|| alloc.bump())
|
||||
}
|
||||
|
||||
/// Get a reference to the hsv color space.
|
||||
///
|
||||
/// # Warning
|
||||
/// The Hue component of the color must be in degrees and must be divided
|
||||
/// by 360.0 before being encoded into the PDF file.
|
||||
pub fn hsv(&mut self, alloc: &mut Ref) -> Ref {
|
||||
*self.hsv.get_or_insert_with(|| alloc.bump())
|
||||
}
|
||||
|
||||
/// Get a reference to the hsl color space.
|
||||
///
|
||||
/// # Warning
|
||||
/// The Hue component of the color must be in degrees and must be divided
|
||||
/// by 360.0 before being encoded into the PDF file.
|
||||
pub fn hsl(&mut self, alloc: &mut Ref) -> Ref {
|
||||
*self.hsl.get_or_insert_with(|| alloc.bump())
|
||||
}
|
||||
|
||||
/// Mark linear RGB as used.
|
||||
pub fn linear_rgb(&mut self) {
|
||||
self.use_linear_rgb = true;
|
||||
}
|
||||
|
||||
/// Write the color space on usage.
|
||||
pub fn write(
|
||||
&mut self,
|
||||
color_space: ColorSpace,
|
||||
writer: writers::ColorSpace,
|
||||
alloc: &mut Ref,
|
||||
) {
|
||||
match color_space {
|
||||
ColorSpace::Oklab => {
|
||||
let mut oklab = writer.device_n([OKLAB_L, OKLAB_A, OKLAB_B]);
|
||||
self.write(ColorSpace::LinearRgb, oklab.alternate_color_space(), alloc);
|
||||
oklab.tint_ref(self.oklab(alloc));
|
||||
oklab.attrs().subtype(DeviceNSubtype::DeviceN);
|
||||
}
|
||||
ColorSpace::Srgb => writer.icc_based(self.srgb(alloc)),
|
||||
ColorSpace::D65Gray => writer.icc_based(self.d65_gray(alloc)),
|
||||
ColorSpace::LinearRgb => {
|
||||
writer.cal_rgb(
|
||||
[0.9505, 1.0, 1.0888],
|
||||
None,
|
||||
Some([1.0, 1.0, 1.0]),
|
||||
Some([
|
||||
0.4124, 0.2126, 0.0193, 0.3576, 0.715, 0.1192, 0.1805, 0.0722,
|
||||
0.9505,
|
||||
]),
|
||||
);
|
||||
}
|
||||
ColorSpace::Hsl => {
|
||||
let mut hsl = writer.device_n([HSL_H, HSL_S, HSL_L]);
|
||||
self.write(ColorSpace::Srgb, hsl.alternate_color_space(), alloc);
|
||||
hsl.tint_ref(self.hsl(alloc));
|
||||
hsl.attrs().subtype(DeviceNSubtype::DeviceN);
|
||||
}
|
||||
ColorSpace::Hsv => {
|
||||
let mut hsv = writer.device_n([HSV_H, HSV_S, HSV_V]);
|
||||
self.write(ColorSpace::Srgb, hsv.alternate_color_space(), alloc);
|
||||
hsv.tint_ref(self.hsv(alloc));
|
||||
hsv.attrs().subtype(DeviceNSubtype::DeviceN);
|
||||
}
|
||||
ColorSpace::Cmyk => writer.device_cmyk(),
|
||||
}
|
||||
}
|
||||
|
||||
// Write the color spaces to the PDF file.
|
||||
pub fn write_color_spaces(&mut self, mut spaces: Dict, alloc: &mut Ref) {
|
||||
if self.oklab.is_some() {
|
||||
self.write(ColorSpace::Oklab, spaces.insert(OKLAB).start(), alloc);
|
||||
}
|
||||
|
||||
if self.srgb.is_some() {
|
||||
self.write(ColorSpace::Srgb, spaces.insert(SRGB).start(), alloc);
|
||||
}
|
||||
|
||||
if self.d65_gray.is_some() {
|
||||
self.write(ColorSpace::D65Gray, spaces.insert(D65_GRAY).start(), alloc);
|
||||
}
|
||||
|
||||
if self.hsv.is_some() {
|
||||
self.write(ColorSpace::Hsv, spaces.insert(HSV).start(), alloc);
|
||||
}
|
||||
|
||||
if self.hsl.is_some() {
|
||||
self.write(ColorSpace::Hsl, spaces.insert(HSL).start(), alloc);
|
||||
}
|
||||
|
||||
if self.use_linear_rgb {
|
||||
self.write(ColorSpace::LinearRgb, spaces.insert(LINEAR_SRGB).start(), alloc);
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the necessary color spaces functions and ICC profiles to the
|
||||
/// PDF file.
|
||||
pub fn write_functions(&self, writer: &mut PdfWriter) {
|
||||
// Write the Oklab function & color space
|
||||
if let Some(oklab) = self.oklab {
|
||||
let code = oklab_function();
|
||||
writer
|
||||
.post_script_function(oklab, &code)
|
||||
.domain([0.0, 1.0, 0.0, 1.0, 0.0, 1.0])
|
||||
.range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0])
|
||||
.filter(Filter::FlateDecode);
|
||||
}
|
||||
|
||||
// Write the HSV function & color space
|
||||
if let Some(hsv) = self.hsv {
|
||||
let code = hsv_function();
|
||||
writer
|
||||
.post_script_function(hsv, &code)
|
||||
.domain([0.0, 1.0, 0.0, 1.0, 0.0, 1.0])
|
||||
.range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0])
|
||||
.filter(Filter::FlateDecode);
|
||||
}
|
||||
|
||||
// Write the HSL function & color space
|
||||
if let Some(hsl) = self.hsl {
|
||||
let code = hsl_function();
|
||||
writer
|
||||
.post_script_function(hsl, &code)
|
||||
.domain([0.0, 1.0, 0.0, 1.0, 0.0, 1.0])
|
||||
.range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0])
|
||||
.filter(Filter::FlateDecode);
|
||||
}
|
||||
|
||||
// Write the sRGB color space
|
||||
if let Some(srgb) = self.srgb {
|
||||
let profile = srgb_icc();
|
||||
writer
|
||||
.icc_profile(srgb, &profile)
|
||||
.n(3)
|
||||
.range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0])
|
||||
.alternate()
|
||||
.srgb();
|
||||
}
|
||||
|
||||
// Write the gray color space
|
||||
if let Some(gray) = self.d65_gray {
|
||||
let profile = gray_icc();
|
||||
writer
|
||||
.icc_profile(gray, &profile)
|
||||
.n(1)
|
||||
.range([0.0, 1.0])
|
||||
.alternate()
|
||||
.d65_gray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deflated sRGB ICC profile
|
||||
#[comemo::memoize]
|
||||
fn srgb_icc() -> Arc<Vec<u8>> {
|
||||
Arc::new(deflate(SRGB_ICC))
|
||||
}
|
||||
|
||||
/// Deflated gray ICC profile
|
||||
#[comemo::memoize]
|
||||
fn gray_icc() -> Arc<Vec<u8>> {
|
||||
Arc::new(deflate(GRAY_ICC))
|
||||
}
|
||||
|
||||
/// Deflated Oklab PostScript function
|
||||
#[comemo::memoize]
|
||||
fn oklab_function() -> Arc<Vec<u8>> {
|
||||
let code = minify(OKLAB_SOURCE);
|
||||
Arc::new(deflate(code.as_bytes()))
|
||||
}
|
||||
|
||||
/// Deflated HSV PostScript function
|
||||
#[comemo::memoize]
|
||||
fn hsv_function() -> Arc<Vec<u8>> {
|
||||
let code = minify(HSV_SOURCE);
|
||||
Arc::new(deflate(code.as_bytes()))
|
||||
}
|
||||
|
||||
/// Deflated HSL PostScript function
|
||||
#[comemo::memoize]
|
||||
fn hsl_function() -> Arc<Vec<u8>> {
|
||||
let code = minify(HSL_SOURCE);
|
||||
Arc::new(deflate(code.as_bytes()))
|
||||
}
|
||||
|
||||
/// This function removes comments, line spaces and carriage returns from a
|
||||
/// PostScript program. This is necessary to optimize the size of the PDF file.
|
||||
fn minify(source: &str) -> String {
|
||||
let mut buf = String::with_capacity(source.len());
|
||||
let mut s = unscanny::Scanner::new(source);
|
||||
while let Some(c) = s.eat() {
|
||||
match c {
|
||||
'%' => {
|
||||
s.eat_until('\n');
|
||||
}
|
||||
c if c.is_whitespace() => {
|
||||
s.eat_whitespace();
|
||||
if buf.ends_with(|c: char| !c.is_whitespace()) {
|
||||
buf.push(' ');
|
||||
}
|
||||
}
|
||||
_ => buf.push(c),
|
||||
}
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
/// Encodes the color into four f32s, which can be used in a PDF file.
|
||||
/// Ensures that the values are in the range [0.0, 1.0].
|
||||
///
|
||||
/// # Why?
|
||||
/// - Oklab: The a and b components are in the range [-0.4, 0.4] and the PDF
|
||||
/// specifies (and some readers enforce) that all color values be in the range
|
||||
/// [0.0, 1.0]. This means that the PostScript function and the encoded color
|
||||
/// must be offset by 0.4.
|
||||
/// - HSV/HSL: The hue component is in the range [0.0, 360.0] and the PDF format
|
||||
/// specifies that it must be in the range [0.0, 1.0]. This means that the
|
||||
/// PostScript function and the encoded color must be divided by 360.0.
|
||||
pub trait ColorEncode {
|
||||
/// Performs the color to PDF f32 array conversion.
|
||||
fn encode(&self, color: Color) -> [f32; 4];
|
||||
}
|
||||
|
||||
impl ColorEncode for ColorSpace {
|
||||
fn encode(&self, color: Color) -> [f32; 4] {
|
||||
match self {
|
||||
ColorSpace::Oklab => {
|
||||
let [l, a, b, alpha] = color.to_oklab().to_vec4();
|
||||
[l, (a + 0.4).clamp(0.0, 1.0), (b + 0.4).clamp(0.0, 1.0), alpha]
|
||||
}
|
||||
ColorSpace::Hsl => {
|
||||
let [h, s, l, _] = color.to_hsl().to_vec4();
|
||||
[h / 360.0, s, l, 0.0]
|
||||
}
|
||||
ColorSpace::Hsv => {
|
||||
let [h, s, v, _] = color.to_hsv().to_vec4();
|
||||
[h / 360.0, s, v, 0.0]
|
||||
}
|
||||
_ => color.to_vec4(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes a paint into either a fill or stroke color.
|
||||
pub trait PaintEncode {
|
||||
/// Set the paint as the fill color.
|
||||
fn set_as_fill(&self, page_context: &mut PageContext);
|
||||
|
||||
/// Set the paint as the stroke color.
|
||||
fn set_as_stroke(&self, page_context: &mut PageContext);
|
||||
}
|
||||
|
||||
impl PaintEncode for Paint {
|
||||
fn set_as_fill(&self, ctx: &mut PageContext) {
|
||||
let Paint::Solid(color) = self;
|
||||
match color {
|
||||
Color::Luma(_) => {
|
||||
ctx.parent.colors.d65_gray(&mut ctx.parent.alloc);
|
||||
ctx.set_fill_color_space(D65_GRAY);
|
||||
|
||||
let [l, _, _, _] = ColorSpace::D65Gray.encode(*color);
|
||||
ctx.content.set_fill_color([l]);
|
||||
}
|
||||
Color::Oklab(_) => {
|
||||
ctx.parent.colors.oklab(&mut ctx.parent.alloc);
|
||||
ctx.set_fill_color_space(OKLAB);
|
||||
|
||||
let [l, a, b, _] = ColorSpace::Oklab.encode(*color);
|
||||
ctx.content.set_fill_color([l, a, b]);
|
||||
}
|
||||
Color::LinearRgb(_) => {
|
||||
ctx.parent.colors.linear_rgb();
|
||||
ctx.set_fill_color_space(LINEAR_SRGB);
|
||||
|
||||
let [r, g, b, _] = ColorSpace::LinearRgb.encode(*color);
|
||||
ctx.content.set_fill_color([r, g, b]);
|
||||
}
|
||||
Color::Rgba(_) => {
|
||||
ctx.parent.colors.srgb(&mut ctx.parent.alloc);
|
||||
ctx.set_fill_color_space(SRGB);
|
||||
|
||||
let [r, g, b, _] = ColorSpace::Srgb.encode(*color);
|
||||
ctx.content.set_fill_color([r, g, b]);
|
||||
}
|
||||
Color::Cmyk(_) => {
|
||||
ctx.reset_fill_color_space();
|
||||
|
||||
let [c, m, y, k] = ColorSpace::Cmyk.encode(*color);
|
||||
ctx.content.set_fill_cmyk(c, m, y, k);
|
||||
}
|
||||
Color::Hsl(_) => {
|
||||
ctx.parent.colors.hsl(&mut ctx.parent.alloc);
|
||||
ctx.set_fill_color_space(HSL);
|
||||
|
||||
let [h, s, l, _] = ColorSpace::Hsl.encode(*color);
|
||||
ctx.content.set_fill_color([h, s, l]);
|
||||
}
|
||||
Color::Hsv(_) => {
|
||||
ctx.parent.colors.hsv(&mut ctx.parent.alloc);
|
||||
ctx.set_fill_color_space(HSV);
|
||||
|
||||
let [h, s, v, _] = ColorSpace::Hsv.encode(*color);
|
||||
ctx.content.set_fill_color([h, s, v]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_as_stroke(&self, ctx: &mut PageContext) {
|
||||
let Paint::Solid(color) = self;
|
||||
match color {
|
||||
Color::Luma(_) => {
|
||||
ctx.parent.colors.d65_gray(&mut ctx.parent.alloc);
|
||||
ctx.set_stroke_color_space(D65_GRAY);
|
||||
|
||||
let [l, _, _, _] = ColorSpace::D65Gray.encode(*color);
|
||||
ctx.content.set_stroke_color([l]);
|
||||
}
|
||||
Color::Oklab(_) => {
|
||||
ctx.parent.colors.oklab(&mut ctx.parent.alloc);
|
||||
ctx.set_stroke_color_space(OKLAB);
|
||||
|
||||
let [l, a, b, _] = ColorSpace::Oklab.encode(*color);
|
||||
ctx.content.set_stroke_color([l, a, b]);
|
||||
}
|
||||
Color::LinearRgb(_) => {
|
||||
ctx.parent.colors.linear_rgb();
|
||||
ctx.set_stroke_color_space(LINEAR_SRGB);
|
||||
|
||||
let [r, g, b, _] = ColorSpace::LinearRgb.encode(*color);
|
||||
ctx.content.set_stroke_color([r, g, b]);
|
||||
}
|
||||
Color::Rgba(_) => {
|
||||
ctx.parent.colors.srgb(&mut ctx.parent.alloc);
|
||||
ctx.set_stroke_color_space(SRGB);
|
||||
|
||||
let [r, g, b, _] = ColorSpace::Srgb.encode(*color);
|
||||
ctx.content.set_stroke_color([r, g, b]);
|
||||
}
|
||||
Color::Cmyk(_) => {
|
||||
ctx.reset_stroke_color_space();
|
||||
|
||||
let [c, m, y, k] = ColorSpace::Cmyk.encode(*color);
|
||||
ctx.content.set_stroke_cmyk(c, m, y, k);
|
||||
}
|
||||
Color::Hsl(_) => {
|
||||
ctx.parent.colors.hsl(&mut ctx.parent.alloc);
|
||||
ctx.set_stroke_color_space(HSL);
|
||||
|
||||
let [h, s, l, _] = ColorSpace::Hsl.encode(*color);
|
||||
ctx.content.set_stroke_color([h, s, l]);
|
||||
}
|
||||
Color::Hsv(_) => {
|
||||
ctx.parent.colors.hsv(&mut ctx.parent.alloc);
|
||||
ctx.set_stroke_color_space(HSV);
|
||||
|
||||
let [h, s, v, _] = ColorSpace::Hsv.encode(*color);
|
||||
ctx.content.set_stroke_color([h, s, v]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
BIN
crates/typst/src/export/pdf/icc/sGrey-v4.icc
Normal file
BIN
crates/typst/src/export/pdf/icc/sGrey-v4.icc
Normal file
Binary file not shown.
BIN
crates/typst/src/export/pdf/icc/sRGB-v4.icc
Normal file
BIN
crates/typst/src/export/pdf/icc/sRGB-v4.icc
Normal file
Binary file not shown.
@ -1,11 +1,13 @@
|
||||
//! Exporting into PDF documents.
|
||||
|
||||
mod color;
|
||||
mod extg;
|
||||
mod font;
|
||||
mod image;
|
||||
mod outline;
|
||||
mod page;
|
||||
|
||||
pub use self::color::{ColorEncode, ColorSpaces};
|
||||
pub use self::page::{PdfPageLabel, PdfPageLabelStyle};
|
||||
|
||||
use std::cmp::Eq;
|
||||
@ -43,15 +45,12 @@ pub fn pdf(document: &Document) -> Vec<u8> {
|
||||
ctx.writer.finish()
|
||||
}
|
||||
|
||||
/// Identifies the color space definitions.
|
||||
const SRGB: Name<'static> = Name(b"srgb");
|
||||
const D65_GRAY: Name<'static> = Name(b"d65gray");
|
||||
|
||||
/// Context for exporting a whole PDF document.
|
||||
pub struct PdfContext<'a> {
|
||||
document: &'a Document,
|
||||
introspector: Introspector,
|
||||
writer: PdfWriter,
|
||||
colors: ColorSpaces,
|
||||
pages: Vec<Page>,
|
||||
page_heights: Vec<f32>,
|
||||
alloc: Ref,
|
||||
@ -81,6 +80,7 @@ impl<'a> PdfContext<'a> {
|
||||
document,
|
||||
introspector: Introspector::new(&document.pages),
|
||||
writer: PdfWriter::new(),
|
||||
colors: ColorSpaces::default(),
|
||||
pages: vec![],
|
||||
page_heights: vec![],
|
||||
alloc,
|
||||
|
@ -5,16 +5,16 @@ use pdf_writer::types::{
|
||||
ActionType, AnnotationType, ColorSpaceOperand, LineCapStyle, LineJoinStyle,
|
||||
NumberingStyle,
|
||||
};
|
||||
use pdf_writer::writers::ColorSpace;
|
||||
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str};
|
||||
|
||||
use super::color::PaintEncode;
|
||||
use super::extg::ExternalGraphicsState;
|
||||
use super::{deflate, AbsExt, EmExt, PdfContext, RefExt, D65_GRAY, SRGB};
|
||||
use super::{deflate, AbsExt, EmExt, PdfContext, RefExt};
|
||||
use crate::doc::{Destination, Frame, FrameItem, GroupItem, Meta, TextItem};
|
||||
use crate::font::Font;
|
||||
use crate::geom::{
|
||||
self, Abs, Color, Em, FixedStroke, Geometry, LineCap, LineJoin, Numeric, Paint,
|
||||
Point, Ratio, Shape, Size, Transform,
|
||||
self, Abs, Em, FixedStroke, Geometry, LineCap, LineJoin, Numeric, Paint, Point,
|
||||
Ratio, Shape, Size, Transform,
|
||||
};
|
||||
use crate::image::Image;
|
||||
|
||||
@ -86,10 +86,8 @@ pub fn write_page_tree(ctx: &mut PdfContext) {
|
||||
.kids(ctx.page_refs.iter().copied());
|
||||
|
||||
let mut resources = pages.resources();
|
||||
let mut spaces = resources.color_spaces();
|
||||
spaces.insert(SRGB).start::<ColorSpace>().srgb();
|
||||
spaces.insert(D65_GRAY).start::<ColorSpace>().d65_gray();
|
||||
spaces.finish();
|
||||
ctx.colors
|
||||
.write_color_spaces(resources.color_spaces(), &mut ctx.alloc);
|
||||
|
||||
let mut fonts = resources.fonts();
|
||||
for (font_ref, f) in ctx.font_map.pdf_indices(&ctx.font_refs) {
|
||||
@ -116,6 +114,9 @@ pub fn write_page_tree(ctx: &mut PdfContext) {
|
||||
|
||||
resources.finish();
|
||||
pages.finish();
|
||||
|
||||
// Write all of the functions used by the document.
|
||||
ctx.colors.write_functions(&mut ctx.writer);
|
||||
}
|
||||
|
||||
/// Write a page tree node.
|
||||
@ -196,11 +197,11 @@ pub struct Page {
|
||||
}
|
||||
|
||||
/// An exporter for the contents of a single PDF page.
|
||||
struct PageContext<'a, 'b> {
|
||||
parent: &'a mut PdfContext<'b>,
|
||||
pub struct PageContext<'a, 'b> {
|
||||
pub parent: &'a mut PdfContext<'b>,
|
||||
page_ref: Ref,
|
||||
label: Option<PdfPageLabel>,
|
||||
content: Content,
|
||||
pub content: Content,
|
||||
state: State,
|
||||
saves: Vec<State>,
|
||||
bottom: f32,
|
||||
@ -249,21 +250,13 @@ impl PageContext<'_, '_> {
|
||||
let stroke_opacity = stroke
|
||||
.map(|stroke| {
|
||||
let Paint::Solid(color) = stroke.paint;
|
||||
if let Color::Rgba(rgba_color) = color {
|
||||
rgba_color.a
|
||||
} else {
|
||||
255
|
||||
}
|
||||
color.alpha().map_or(255, |v| (v * 255.0).round() as u8)
|
||||
})
|
||||
.unwrap_or(255);
|
||||
let fill_opacity = fill
|
||||
.map(|paint| {
|
||||
let Paint::Solid(color) = paint;
|
||||
if let Color::Rgba(rgba_color) = color {
|
||||
rgba_color.a
|
||||
} else {
|
||||
255
|
||||
}
|
||||
color.alpha().map_or(255, |v| (v * 255.0).round() as u8)
|
||||
})
|
||||
.unwrap_or(255);
|
||||
self.set_external_graphics_state(&ExternalGraphicsState {
|
||||
@ -296,34 +289,19 @@ impl PageContext<'_, '_> {
|
||||
|
||||
fn set_fill(&mut self, fill: &Paint) {
|
||||
if self.state.fill.as_ref() != Some(fill) {
|
||||
let f = |c| c as f32 / 255.0;
|
||||
let Paint::Solid(color) = fill;
|
||||
match color {
|
||||
Color::Luma(c) => {
|
||||
self.set_fill_color_space(D65_GRAY);
|
||||
self.content.set_fill_gray(f(c.0));
|
||||
}
|
||||
Color::Rgba(c) => {
|
||||
self.set_fill_color_space(SRGB);
|
||||
self.content.set_fill_color([f(c.r), f(c.g), f(c.b)]);
|
||||
}
|
||||
Color::Cmyk(c) => {
|
||||
self.reset_fill_color_space();
|
||||
self.content.set_fill_cmyk(f(c.c), f(c.m), f(c.y), f(c.k));
|
||||
}
|
||||
}
|
||||
fill.set_as_fill(self);
|
||||
self.state.fill = Some(fill.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn set_fill_color_space(&mut self, space: Name<'static>) {
|
||||
pub fn set_fill_color_space(&mut self, space: Name<'static>) {
|
||||
if self.state.fill_space != Some(space) {
|
||||
self.content.set_fill_color_space(ColorSpaceOperand::Named(space));
|
||||
self.state.fill_space = Some(space);
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_fill_color_space(&mut self) {
|
||||
pub fn reset_fill_color_space(&mut self) {
|
||||
self.state.fill_space = None;
|
||||
}
|
||||
|
||||
@ -338,22 +316,7 @@ impl PageContext<'_, '_> {
|
||||
miter_limit,
|
||||
} = stroke;
|
||||
|
||||
let f = |c| c as f32 / 255.0;
|
||||
let Paint::Solid(color) = paint;
|
||||
match color {
|
||||
Color::Luma(c) => {
|
||||
self.set_stroke_color_space(D65_GRAY);
|
||||
self.content.set_stroke_gray(f(c.0));
|
||||
}
|
||||
Color::Rgba(c) => {
|
||||
self.set_stroke_color_space(SRGB);
|
||||
self.content.set_stroke_color([f(c.r), f(c.g), f(c.b)]);
|
||||
}
|
||||
Color::Cmyk(c) => {
|
||||
self.reset_stroke_color_space();
|
||||
self.content.set_stroke_cmyk(f(c.c), f(c.m), f(c.y), f(c.k));
|
||||
}
|
||||
}
|
||||
paint.set_as_stroke(self);
|
||||
|
||||
self.content.set_line_width(thickness.to_f32());
|
||||
if self.state.stroke.as_ref().map(|s| &s.line_cap) != Some(line_cap) {
|
||||
@ -379,14 +342,14 @@ impl PageContext<'_, '_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_stroke_color_space(&mut self, space: Name<'static>) {
|
||||
pub fn set_stroke_color_space(&mut self, space: Name<'static>) {
|
||||
if self.state.stroke_space != Some(space) {
|
||||
self.content.set_stroke_color_space(ColorSpaceOperand::Named(space));
|
||||
self.state.stroke_space = Some(space);
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_stroke_color_space(&mut self) {
|
||||
pub fn reset_stroke_color_space(&mut self) {
|
||||
self.state.stroke_space = None;
|
||||
}
|
||||
}
|
||||
|
63
crates/typst/src/export/pdf/postscript/hsl.ps
Normal file
63
crates/typst/src/export/pdf/postscript/hsl.ps
Normal file
@ -0,0 +1,63 @@
|
||||
|
||||
{
|
||||
% Starting stack: H, S, L
|
||||
% /!\ WARNING: The hue component **MUST** be encoded
|
||||
% in the range [0, 1] before calling this function.
|
||||
% This is because the function assumes that the
|
||||
% hue component are divided by a factor of 360
|
||||
% in order to meet the range requirements of the
|
||||
% PDF specification.
|
||||
|
||||
% First we do H = (H * 360.0) % 360
|
||||
3 2 roll 360 mul 3 1 roll
|
||||
|
||||
% Compute C = (1 - |2 * L - 1|) * S
|
||||
dup 1 exch 2 mul 1 sub abs sub 3 2 roll mul
|
||||
|
||||
% P = (H / 60) % 2
|
||||
3 2 roll dup 60 div 2
|
||||
2 copy div cvi mul exch sub abs
|
||||
|
||||
% X = C * (1 - |P - 1|)
|
||||
1 exch 1 sub abs sub 3 2 roll dup 3 1 roll mul
|
||||
|
||||
% Compute m = L - C / 2
|
||||
exch dup 2 div 5 4 roll exch sub
|
||||
|
||||
% Rotate so H is top
|
||||
4 3 roll exch 4 1 roll
|
||||
|
||||
% Construct the RGB stack
|
||||
dup 60 lt {
|
||||
% We need to build: (C, X, 0)
|
||||
pop 0 3 1 roll
|
||||
} {
|
||||
dup 120 lt {
|
||||
% We need to build: (X, C, 0)
|
||||
pop exch 0 3 1 roll
|
||||
} {
|
||||
dup 180 lt {
|
||||
% We need to build: (0, C, X)
|
||||
pop 0
|
||||
} {
|
||||
dup 240 lt {
|
||||
% We need to build: (0, X, C)
|
||||
pop exch 0
|
||||
} {
|
||||
300 lt {
|
||||
% We need to build: (X, 0, C)
|
||||
0 3 2 roll
|
||||
} {
|
||||
% We need to build: (C, 0, X)
|
||||
0 exch
|
||||
} ifelse
|
||||
} ifelse
|
||||
} ifelse
|
||||
} ifelse
|
||||
} ifelse
|
||||
|
||||
4 3 roll
|
||||
|
||||
% Add m to each component
|
||||
dup dup 6 2 roll add 5 2 roll add exch 4 3 roll add exch
|
||||
}
|
62
crates/typst/src/export/pdf/postscript/hsv.ps
Normal file
62
crates/typst/src/export/pdf/postscript/hsv.ps
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
% Starting stack: H, S, V
|
||||
% /!\ WARNING: The hue component **MUST** be encoded
|
||||
% in the range [0, 1] before calling this function.
|
||||
% This is because the function assumes that the
|
||||
% hue component are divided by a factor of 360
|
||||
% in order to meet the range requirements of the
|
||||
% PDF specification.
|
||||
|
||||
% First we do H = (H * 360.0) % 360
|
||||
3 2 roll 360 mul 3 1 roll
|
||||
|
||||
% Compute C = V * S
|
||||
dup 3 1 roll mul
|
||||
|
||||
% P = (H / 60) % 2
|
||||
3 2 roll dup 60 div 2
|
||||
2 copy div cvi mul exch sub abs
|
||||
|
||||
% X = C * (1 - |P - 1|)
|
||||
1 exch 1 sub abs sub 3 2 roll dup 3 1 roll mul
|
||||
|
||||
% Compute m = V - C
|
||||
exch dup 5 4 roll exch sub
|
||||
|
||||
% Rotate so H is top
|
||||
4 3 roll exch 4 1 roll
|
||||
|
||||
% Construct the RGB stack
|
||||
dup 60 lt {
|
||||
% We need to build: (C, X, 0)
|
||||
pop 0 3 1 roll
|
||||
} {
|
||||
dup 120 lt {
|
||||
% We need to build: (X, C, 0)
|
||||
pop exch 0 3 1 roll
|
||||
} {
|
||||
dup 180 lt {
|
||||
% We need to build: (0, C, X)
|
||||
pop 0
|
||||
} {
|
||||
dup 240 lt {
|
||||
% We need to build: (0, X, C)
|
||||
pop exch 0
|
||||
} {
|
||||
300 lt {
|
||||
% We need to build: (X, 0, C)
|
||||
0 3 2 roll
|
||||
} {
|
||||
% We need to build: (C, 0, X)
|
||||
0 exch
|
||||
} ifelse
|
||||
} ifelse
|
||||
} ifelse
|
||||
} ifelse
|
||||
} ifelse
|
||||
|
||||
4 3 roll
|
||||
|
||||
% Add m to each component
|
||||
dup dup 6 2 roll add 5 2 roll add exch 4 3 roll add exch
|
||||
}
|
78
crates/typst/src/export/pdf/postscript/oklab.ps
Normal file
78
crates/typst/src/export/pdf/postscript/oklab.ps
Normal file
@ -0,0 +1,78 @@
|
||||
{
|
||||
% Starting stack: L, A, B
|
||||
% /!\ WARNING: The A and B components **MUST** be encoded
|
||||
% in the range [0, 1] before calling this function.
|
||||
% This is because the function assumes that the
|
||||
% A and B components are offset by a factor of 0.4
|
||||
% in order to meet the range requirements of the
|
||||
% PDF specification.
|
||||
|
||||
exch 0.4 sub
|
||||
exch 0.4 sub
|
||||
|
||||
% Load L a and b into the stack
|
||||
2 index
|
||||
2 index
|
||||
2 index
|
||||
|
||||
% Compute f1 = ((0.3963377774 * a) + (0.2158037573 * b) + L)^3
|
||||
0.2158037573 mul exch
|
||||
0.3963377774 mul add add
|
||||
dup dup mul mul
|
||||
|
||||
% Load L, a, and b into the stack
|
||||
3 index
|
||||
3 index
|
||||
3 index
|
||||
|
||||
% Compute f2 = ((-0.1055613458 * a) + (-0.0638541728 * b) + L)^3
|
||||
-0.0638541728 mul exch
|
||||
-0.1055613458 mul add add
|
||||
dup dup mul mul
|
||||
|
||||
% Load L, a, and b into the stack
|
||||
4 index
|
||||
4 index
|
||||
4 index
|
||||
|
||||
% Compute f3 = ((-0.0894841775 * a) + (-1.2914855480 * b) + L)^3
|
||||
-1.2914855480 mul exch
|
||||
-0.0894841775 mul add add
|
||||
dup dup mul mul
|
||||
|
||||
% Discard L, a, and b by rolling the stack and popping
|
||||
6 3 roll pop pop pop
|
||||
|
||||
% Load f1, f2, and f3 into the stack
|
||||
2 index
|
||||
2 index
|
||||
2 index
|
||||
|
||||
% Compute R = f1 * 4.0767416621 + f2 * -3.3077115913 + f3 * 0.2309699292
|
||||
0.2309699292 mul exch
|
||||
-3.3077115913 mul add exch
|
||||
4.0767416621 mul add
|
||||
|
||||
% Load f1, f2, and f3 into the stack
|
||||
3 index
|
||||
3 index
|
||||
3 index
|
||||
|
||||
% Compute G = f1 * -1.2684380046 + f2 * 2.6097574011 + f3 * -0.3413193965
|
||||
-0.3413193965 mul exch
|
||||
2.6097574011 mul add exch
|
||||
-1.2684380046 mul add
|
||||
|
||||
% Load f1, f2, and f3 into the stack
|
||||
4 index
|
||||
4 index
|
||||
4 index
|
||||
|
||||
% Compute B = f1 * -0.0041960863 + f2 * -0.7034186147 + f3 * 1.7076147010
|
||||
1.7076147010 mul exch
|
||||
-0.7034186147 mul add exch
|
||||
-0.0041960863 mul add
|
||||
|
||||
% Discard f1, f2, and f3 by rolling the stack and popping
|
||||
6 3 roll pop pop pop
|
||||
}
|
@ -362,7 +362,7 @@ fn render_outline_glyph(
|
||||
let mh = bitmap.height;
|
||||
|
||||
let Paint::Solid(color) = text.fill;
|
||||
let c = color.to_rgba();
|
||||
let color = sk::ColorU8::from(color);
|
||||
|
||||
// Pad the pixmap with 1 pixel in each dimension so that we do
|
||||
// not get any problem with floating point errors along their border
|
||||
@ -370,7 +370,14 @@ fn render_outline_glyph(
|
||||
for x in 0..mw {
|
||||
for y in 0..mh {
|
||||
let alpha = bitmap.coverage[(y * mw + x) as usize];
|
||||
let color = sk::ColorU8::from_rgba(c.r, c.g, c.b, alpha).premultiply();
|
||||
let color = sk::ColorU8::from_rgba(
|
||||
color.red(),
|
||||
color.green(),
|
||||
color.blue(),
|
||||
alpha,
|
||||
)
|
||||
.premultiply();
|
||||
|
||||
pixmap.pixels_mut()[((y + 1) * (mw + 2) + (x + 1)) as usize] = color;
|
||||
}
|
||||
}
|
||||
@ -400,9 +407,7 @@ fn render_outline_glyph(
|
||||
|
||||
// Premultiply the text color.
|
||||
let Paint::Solid(color) = text.fill;
|
||||
let c = color.to_rgba();
|
||||
let color =
|
||||
bytemuck::cast(sk::ColorU8::from_rgba(c.r, c.g, c.b, 255).premultiply());
|
||||
let color = bytemuck::cast(sk::ColorU8::from(color).premultiply());
|
||||
|
||||
// Blend the glyph bitmap with the existing pixels on the canvas.
|
||||
let pixels = bytemuck::cast_slice_mut::<u8, u32>(canvas.data_mut());
|
||||
@ -629,8 +634,8 @@ impl From<&Paint> for sk::Paint<'static> {
|
||||
|
||||
impl From<Color> for sk::Color {
|
||||
fn from(color: Color) -> Self {
|
||||
let c = color.to_rgba();
|
||||
sk::Color::from_rgba8(c.r, c.g, c.b, c.a)
|
||||
let [r, g, b, a] = color.to_rgba().to_vec4_u8();
|
||||
sk::Color::from_rgba8(r, g, b, a)
|
||||
}
|
||||
}
|
||||
|
||||
@ -691,6 +696,13 @@ impl AbsExt for Abs {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for sk::ColorU8 {
|
||||
fn from(value: Color) -> Self {
|
||||
let [r, g, b, _] = value.to_rgba().to_vec4_u8();
|
||||
sk::ColorU8::from_rgba(r, g, b, 255)
|
||||
}
|
||||
}
|
||||
|
||||
// Alpha multiplication and blending are ported from:
|
||||
// https://skia.googlesource.com/skia/+/refs/heads/main/include/core/SkColorPriv.h
|
||||
|
||||
|
@ -10,8 +10,8 @@ use xmlwriter::XmlWriter;
|
||||
use crate::doc::{Frame, FrameItem, GroupItem, TextItem};
|
||||
use crate::font::Font;
|
||||
use crate::geom::{
|
||||
Abs, Axes, FixedStroke, Geometry, LineCap, LineJoin, Paint, PathItem, Ratio, Shape,
|
||||
Size, Transform,
|
||||
Abs, Angle, Axes, Color, FixedStroke, Geometry, LineCap, LineJoin, Paint, PathItem,
|
||||
Ratio, Shape, Size, Transform,
|
||||
};
|
||||
use crate::image::{Image, ImageFormat, RasterFormat, VectorFormat};
|
||||
use crate::util::hash128;
|
||||
@ -299,13 +299,13 @@ impl SVGRenderer {
|
||||
/// Write a fill attribute.
|
||||
fn write_fill(&mut self, fill: &Paint) {
|
||||
let Paint::Solid(color) = fill;
|
||||
self.xml.write_attribute("fill", &color.to_rgba().to_hex());
|
||||
self.xml.write_attribute("fill", &color.encode());
|
||||
}
|
||||
|
||||
/// Write a stroke attribute.
|
||||
fn write_stroke(&mut self, stroke: &FixedStroke) {
|
||||
let Paint::Solid(color) = stroke.paint;
|
||||
self.xml.write_attribute("stroke", &color.to_rgba().to_hex());
|
||||
self.xml.write_attribute("stroke", &color.encode());
|
||||
self.xml.write_attribute("stroke-width", &stroke.thickness.to_pt());
|
||||
self.xml.write_attribute(
|
||||
"stroke-linecap",
|
||||
@ -654,3 +654,74 @@ impl ttf_parser::OutlineBuilder for SvgPathBuilder {
|
||||
write!(&mut self.0, "Z ").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode the color as an SVG color.
|
||||
trait ColorEncode {
|
||||
/// Encode the color.
|
||||
fn encode(&self) -> EcoString;
|
||||
}
|
||||
|
||||
impl ColorEncode for Color {
|
||||
fn encode(&self) -> EcoString {
|
||||
match *self {
|
||||
c @ Color::Rgba(_)
|
||||
| c @ Color::Luma(_)
|
||||
| c @ Color::Cmyk(_)
|
||||
| c @ Color::Hsv(_) => c.to_hex(),
|
||||
Color::LinearRgb(rgb) => {
|
||||
if rgb.alpha != 1.0 {
|
||||
eco_format!(
|
||||
"color(srgb-linear {:.3} {:.3} {:.3} / {:.3})",
|
||||
rgb.red,
|
||||
rgb.green,
|
||||
rgb.blue,
|
||||
rgb.alpha
|
||||
)
|
||||
} else {
|
||||
eco_format!(
|
||||
"color(srgb-linear {:.3} {:.3} {:.3})",
|
||||
rgb.red,
|
||||
rgb.green,
|
||||
rgb.blue,
|
||||
)
|
||||
}
|
||||
}
|
||||
Color::Oklab(oklab) => {
|
||||
if oklab.alpha != 1.0 {
|
||||
eco_format!(
|
||||
"oklab({:?} {:.3} {:.3} / {:.3})",
|
||||
Ratio::new(oklab.l as f64),
|
||||
oklab.a,
|
||||
oklab.b,
|
||||
oklab.alpha
|
||||
)
|
||||
} else {
|
||||
eco_format!(
|
||||
"oklab({:?} {:.3} {:.3})",
|
||||
Ratio::new(oklab.l as f64),
|
||||
oklab.a,
|
||||
oklab.b,
|
||||
)
|
||||
}
|
||||
}
|
||||
Color::Hsl(hsl) => {
|
||||
if hsl.alpha != 1.0 {
|
||||
eco_format!(
|
||||
"hsla({:?} {:?} {:?} / {:.3})",
|
||||
Angle::deg(hsl.hue.into_degrees() as f64),
|
||||
Ratio::new(hsl.saturation as f64),
|
||||
Ratio::new(hsl.lightness as f64),
|
||||
hsl.alpha,
|
||||
)
|
||||
} else {
|
||||
eco_format!(
|
||||
"hsl({:?} {:?} {:?})",
|
||||
Angle::deg(hsl.hue.into_degrees() as f64),
|
||||
Ratio::new(hsl.saturation as f64),
|
||||
Ratio::new(hsl.lightness as f64),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -31,9 +31,7 @@ pub use self::abs::{Abs, AbsUnit};
|
||||
pub use self::align::{Align, FixedAlign, HAlign, VAlign};
|
||||
pub use self::angle::{Angle, AngleUnit};
|
||||
pub use self::axes::{Axes, Axis};
|
||||
pub use self::color::{
|
||||
CmykColor, Color, ColorSpace, LumaColor, RgbaColor, WeightedColor,
|
||||
};
|
||||
pub use self::color::{Color, ColorSpace, WeightedColor};
|
||||
pub use self::corners::{Corner, Corners};
|
||||
pub use self::dir::Dir;
|
||||
pub use self::ellipse::ellipse;
|
||||
|
@ -30,7 +30,7 @@ impl From<Scalar> for f64 {
|
||||
|
||||
impl Debug for Scalar {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
Debug::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 444 B After Width: | Height: | Size: 21 KiB |
Binary file not shown.
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 51 KiB |
@ -24,7 +24,7 @@ use typst::diag::{bail, FileError, FileResult, Severity, StrResult};
|
||||
use typst::doc::{Document, Frame, FrameItem, Meta};
|
||||
use typst::eval::{eco_format, func, Bytes, Datetime, Library, NoneValue, Tracer, Value};
|
||||
use typst::font::{Font, FontBook};
|
||||
use typst::geom::{Abs, Color, RgbaColor, Smart};
|
||||
use typst::geom::{Abs, Color, Smart};
|
||||
use typst::syntax::{FileId, PackageVersion, Source, Span, SyntaxNode, VirtualPath};
|
||||
use typst::{World, WorldExt};
|
||||
use typst_library::layout::{Margin, PageElem};
|
||||
@ -159,6 +159,14 @@ fn library() -> Library {
|
||||
Ok(NoneValue)
|
||||
}
|
||||
|
||||
#[func]
|
||||
fn test_repr(lhs: Value, rhs: Value) -> StrResult<NoneValue> {
|
||||
if lhs.repr() != rhs.repr() {
|
||||
bail!("Assertion failed: {lhs:?} != {rhs:?}");
|
||||
}
|
||||
Ok(NoneValue)
|
||||
}
|
||||
|
||||
#[func]
|
||||
fn print(#[variadic] values: Vec<Value>) -> NoneValue {
|
||||
let mut stdout = io::stdout().lock();
|
||||
@ -188,13 +196,14 @@ fn library() -> Library {
|
||||
|
||||
// Hook up helpers into the global scope.
|
||||
lib.global.scope_mut().define_func::<test>();
|
||||
lib.global.scope_mut().define_func::<test_repr>();
|
||||
lib.global.scope_mut().define_func::<print>();
|
||||
lib.global
|
||||
.scope_mut()
|
||||
.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
|
||||
.define("conifer", Color::from_u8(0x9f, 0xEB, 0x52, 0xFF));
|
||||
lib.global
|
||||
.scope_mut()
|
||||
.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
|
||||
.define("forest", Color::from_u8(0x43, 0xA1, 0x27, 0xFF));
|
||||
|
||||
lib
|
||||
}
|
||||
|
@ -18,9 +18,59 @@
|
||||
box(square(size: 9pt, fill: c.darken(x * 10%)))
|
||||
}
|
||||
|
||||
---
|
||||
// The the different color spaces
|
||||
#let col = rgb(50%, 64%, 16%)
|
||||
#box(square(size: 9pt, fill: col))
|
||||
#box(square(size: 9pt, fill: rgb(col)))
|
||||
#box(square(size: 9pt, fill: oklab(col)))
|
||||
#box(square(size: 9pt, fill: luma(col)))
|
||||
#box(square(size: 9pt, fill: cmyk(col)))
|
||||
#box(square(size: 9pt, fill: color.linear-rgb(col)))
|
||||
#box(square(size: 9pt, fill: color.hsl(col)))
|
||||
#box(square(size: 9pt, fill: color.hsv(col)))
|
||||
|
||||
---
|
||||
// Test hue rotation
|
||||
#let col = rgb(50%, 64%, 16%)
|
||||
|
||||
#for x in range(0, 11) {
|
||||
box(square(size: 9pt, fill: rgb(col).rotate(x * 36deg)))
|
||||
}
|
||||
|
||||
#for x in range(0, 11) {
|
||||
box(square(size: 9pt, fill: color.hsv(col).rotate(x * 36deg)))
|
||||
}
|
||||
|
||||
#for x in range(0, 11) {
|
||||
box(square(size: 9pt, fill: color.hsl(col).rotate(x * 36deg)))
|
||||
}
|
||||
|
||||
---
|
||||
// Test saturation
|
||||
#let col = color.hsl(180deg, 0%, 50%)
|
||||
#for x in range(0, 11) {
|
||||
box(square(size: 9pt, fill: col.saturate(x * 10%)))
|
||||
}
|
||||
|
||||
#let col = color.hsl(180deg, 100%, 50%)
|
||||
#for x in range(0, 11) {
|
||||
box(square(size: 9pt, fill: col.desaturate(x * 10%)))
|
||||
}
|
||||
|
||||
#let col = color.hsv(180deg, 0%, 50%)
|
||||
#for x in range(0, 11) {
|
||||
box(square(size: 9pt, fill: col.saturate(x * 10%)))
|
||||
}
|
||||
|
||||
#let col = color.hsv(180deg, 100%, 50%)
|
||||
#for x in range(0, 11) {
|
||||
box(square(size: 9pt, fill: col.desaturate(x * 10%)))
|
||||
}
|
||||
|
||||
---
|
||||
// Test gray color modification.
|
||||
// Ref: false
|
||||
#test(luma(20%).lighten(50%), luma(60%))
|
||||
#test(luma(80%).darken(20%), luma(63.9%))
|
||||
#test(luma(80%).negate(), luma(20%))
|
||||
#test-repr(luma(20%).lighten(50%), luma(60%))
|
||||
#test-repr(luma(80%).darken(20%), luma(64%))
|
||||
#test-repr(luma(80%).negate(), luma(20%))
|
||||
|
@ -97,39 +97,40 @@
|
||||
|
||||
---
|
||||
// Test color kind method.
|
||||
#test(rgb(1, 2, 3, 4).kind(), rgb)
|
||||
#test(cmyk(4%, 5%, 6%, 7%).kind(), cmyk)
|
||||
#test(luma(40).kind(), luma)
|
||||
#test(rgb(1, 2, 3, 4).kind() != luma, true)
|
||||
#test(rgb(1, 2, 3, 4).space(), rgb)
|
||||
#test(cmyk(4%, 5%, 6%, 7%).space(), cmyk)
|
||||
#test(luma(40).space(), luma)
|
||||
#test(rgb(1, 2, 3, 4).space() != luma, true)
|
||||
|
||||
---
|
||||
// Test color '.rgba()', '.cmyk()' and '.luma()' without conversions
|
||||
#test(rgb(1, 2, 3, 4).to-rgba(), (1, 2, 3, 4))
|
||||
#test(rgb(1, 2, 3).to-rgba(), (1, 2, 3, 255))
|
||||
#test(cmyk(20%, 20%, 40%, 20%).to-cmyk(), (20%, 20%, 40%, 20%))
|
||||
#test(luma(40).to-luma(), 40)
|
||||
// Test color '.components()' without conversions
|
||||
#test-repr(rgb(1, 2, 3, 4).components(), (0.39%, 0.78%, 1.18%, 1.57%))
|
||||
#test-repr(luma(40).components(), (15.69%, ))
|
||||
#test-repr(cmyk(4%, 5%, 6%, 7%).components(), (4%, 5%, 6%, 7%))
|
||||
#test-repr(oklab(10%, 0.2, 0.3).components(), (10%, 0.2, 0.3, 100%))
|
||||
#test-repr(color.linear-rgb(10%, 20%, 30%).components(), (10%, 20%, 30%, 100%))
|
||||
#test-repr(color.hsv(10deg, 20%, 30%).components(), (10deg, 20%, 30%, 100%))
|
||||
#test-repr(color.hsl(10deg, 20%, 30%).components(), (10deg, 20%, 30%, 100%))
|
||||
|
||||
---
|
||||
// Test color conversions.
|
||||
#test(rgb(1, 2, 3).to-hex(), "#010203")
|
||||
#test(rgb(1, 2, 3, 4).to-hex(), "#01020304")
|
||||
#test(cmyk(4%, 5%, 6%, 7%).to-rgba(), (228, 225, 223, 255))
|
||||
#test(cmyk(4%, 5%, 6%, 7%).to-hex(), "#e4e1df")
|
||||
#test(luma(40).to-rgba(), (40, 40, 40, 255))
|
||||
#test(luma(40).to-hex(), "#282828")
|
||||
#test(repr(luma(40).to-cmyk()), repr((11.76%, 10.59%, 10.59%, 14.12%)))
|
||||
|
||||
---
|
||||
// Error: 2-27 cannot obtain cmyk values from rgba color
|
||||
#rgb(1, 2, 3, 4).to-cmyk()
|
||||
|
||||
---
|
||||
// Error: 2-27 cannot obtain the luma value of rgba color
|
||||
#rgb(1, 2, 3, 4).to-luma()
|
||||
|
||||
---
|
||||
// Error: 2-32 cannot obtain the luma value of cmyk color
|
||||
#cmyk(4%, 5%, 6%, 7%).to-luma()
|
||||
#test-repr(cmyk(4%, 5%, 6%, 7%).to-hex(), "#e4e1df")
|
||||
#test-repr(rgb(cmyk(4%, 5%, 6%, 7%)).components(), (89.28%, 88.35%, 87.42%, 100%))
|
||||
#test-repr(rgb(luma(40%)).components(false), (40%, 40%, 40%))
|
||||
#test-repr(cmyk(luma(40)).components(), (11.76%, 10.67%, 10.51%, 14.12%))
|
||||
#test-repr(cmyk(rgb(1, 2, 3)), cmyk(66.67%, 33.33%, 0%, 98.82%))
|
||||
#test-repr(luma(rgb(1, 2, 3)), luma(0.73%))
|
||||
#test-repr(color.hsl(luma(40)), color.hsl(0deg, 0%, 15.69%))
|
||||
#test-repr(color.hsv(luma(40)), color.hsv(0deg, 0%, 15.69%))
|
||||
#test-repr(color.linear-rgb(luma(40)), color.linear-rgb(2.12%, 2.12%, 2.12%))
|
||||
#test-repr(color.linear-rgb(rgb(1, 2, 3)), color.linear-rgb(0.03%, 0.06%, 0.09%))
|
||||
#test-repr(color.hsl(rgb(1, 2, 3)), color.hsl(-150deg, 50%, 0.78%))
|
||||
#test-repr(color.hsv(rgb(1, 2, 3)), color.hsv(-150deg, 66.67%, 1.18%))
|
||||
#test-repr(oklab(luma(40)).components(), (27.68%, 0.0, 0.0, 100%))
|
||||
#test-repr(oklab(rgb(1, 2, 3)).components(), (8.23%, -0.004, -0.007, 100%))
|
||||
|
||||
---
|
||||
// Test alignment methods.
|
||||
|
@ -47,3 +47,13 @@
|
||||
#int \
|
||||
#type("hi") \
|
||||
#type((a: 1))
|
||||
|
||||
---
|
||||
#set text(0.8em)
|
||||
#blue \
|
||||
#color.linear-rgb(blue) \
|
||||
#oklab(blue) \
|
||||
#cmyk(blue) \
|
||||
#color.hsl(blue) \
|
||||
#color.hsv(blue) \
|
||||
#luma(blue)
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
---
|
||||
// Compare both ways.
|
||||
#test(rgb(0%, 30%, 70%), rgb("004db3"))
|
||||
#test-repr(rgb(0%, 30.2%, 70.2%), rgb("004db3"))
|
||||
|
||||
// Alpha channel.
|
||||
#test(rgb(255, 0, 0, 50%), rgb("ff000080"))
|
||||
@ -15,24 +15,80 @@
|
||||
#test(white.lighten(100%), white)
|
||||
|
||||
// Color mixing, in Oklab space by default.
|
||||
#test(color.mix(rgb("#ff0000"), rgb("#00ff00")), rgb("#d0a800"))
|
||||
#test(color.mix(rgb("#ff0000"), rgb("#00ff00"), space: "oklab"), rgb("#d0a800"))
|
||||
#test(color.mix(rgb("#ff0000"), rgb("#00ff00"), space: "srgb"), rgb("#808000"))
|
||||
#test(rgb(color.mix(rgb("#ff0000"), rgb("#00ff00"))), rgb("#d0a800"))
|
||||
#test(rgb(color.mix(rgb("#ff0000"), rgb("#00ff00"), space: oklab)), rgb("#d0a800"))
|
||||
#test(rgb(color.mix(rgb("#ff0000"), rgb("#00ff00"), space: rgb)), rgb("#808000"))
|
||||
|
||||
#test(color.mix(red, green, blue), rgb("#909282"))
|
||||
#test(color.mix(red, blue, green), rgb("#909282"))
|
||||
#test(color.mix(blue, red, green), rgb("#909282"))
|
||||
#test(rgb(color.mix(red, green, blue)), rgb("#909282"))
|
||||
#test(rgb(color.mix(red, blue, green)), rgb("#909282"))
|
||||
#test(rgb(color.mix(blue, red, green)), rgb("#909282"))
|
||||
|
||||
// Mix with weights.
|
||||
#test(color.mix((red, 50%), (green, 50%)), rgb("#c0983b"))
|
||||
#test(color.mix((red, 0.5), (green, 0.5)), rgb("#c0983b"))
|
||||
#test(color.mix((red, 5), (green, 5)), rgb("#c0983b"))
|
||||
#test(color.mix((green, 5), (white, 0), (red, 5)), rgb("#c0983b"))
|
||||
#test(color.mix((red, 100%), (green, 0%)), red)
|
||||
#test(color.mix((red, 0%), (green, 100%)), green)
|
||||
#test(color.mix((rgb("#aaff00"), 25%), (rgb("#aa00ff"), 75%), space: "srgb"), rgb("#aa40bf"))
|
||||
#test(color.mix((rgb("#aaff00"), 50%), (rgb("#aa00ff"), 50%), space: "srgb"), rgb("#aa8080"))
|
||||
#test(color.mix((rgb("#aaff00"), 75%), (rgb("#aa00ff"), 25%), space: "srgb"), rgb("#aabf40"))
|
||||
#test(rgb(color.mix((red, 50%), (green, 50%))), rgb("#c0983b"))
|
||||
#test(rgb(color.mix((red, 0.5), (green, 0.5))), rgb("#c0983b"))
|
||||
#test(rgb(color.mix((red, 5), (green, 5))), rgb("#c0983b"))
|
||||
#test(rgb(color.mix((green, 5), (white, 0), (red, 5))), rgb("#c0983b"))
|
||||
#test(color.mix((rgb("#aaff00"), 25%), (rgb("#aa00ff"), 75%), space: rgb), rgb("#aa40bf"))
|
||||
#test(color.mix((rgb("#aaff00"), 50%), (rgb("#aa00ff"), 50%), space: rgb), rgb("#aa8080"))
|
||||
#test(color.mix((rgb("#aaff00"), 75%), (rgb("#aa00ff"), 25%), space: rgb), rgb("#aabf40"))
|
||||
|
||||
---
|
||||
// Test color conversion method kinds
|
||||
#test(rgb(rgb(10, 20, 30)).space(), rgb)
|
||||
#test(color.linear-rgb(rgb(10, 20, 30)).space(), color.linear-rgb)
|
||||
#test(oklab(rgb(10, 20, 30)).space(), oklab)
|
||||
#test(color.hsl(rgb(10, 20, 30)).space(), color.hsl)
|
||||
#test(color.hsv(rgb(10, 20, 30)).space(), color.hsv)
|
||||
#test(cmyk(rgb(10, 20, 30)).space(), cmyk)
|
||||
#test(luma(rgb(10, 20, 30)).space(), luma)
|
||||
|
||||
#test(rgb(color.linear-rgb(10, 20, 30)).space(), rgb)
|
||||
#test(color.linear-rgb(color.linear-rgb(10, 20, 30)).space(), color.linear-rgb)
|
||||
#test(oklab(color.linear-rgb(10, 20, 30)).space(), oklab)
|
||||
#test(color.hsl(color.linear-rgb(10, 20, 30)).space(), color.hsl)
|
||||
#test(color.hsv(color.linear-rgb(10, 20, 30)).space(), color.hsv)
|
||||
#test(cmyk(color.linear-rgb(10, 20, 30)).space(), cmyk)
|
||||
#test(luma(color.linear-rgb(10, 20, 30)).space(), luma)
|
||||
|
||||
#test(rgb(oklab(10%, 20%, 30%)).space(), rgb)
|
||||
#test(color.linear-rgb(oklab(10%, 20%, 30%)).space(), color.linear-rgb)
|
||||
#test(oklab(oklab(10%, 20%, 30%)).space(), oklab)
|
||||
#test(color.hsl(oklab(10%, 20%, 30%)).space(), color.hsl)
|
||||
#test(color.hsv(oklab(10%, 20%, 30%)).space(), color.hsv)
|
||||
#test(cmyk(oklab(10%, 20%, 30%)).space(), cmyk)
|
||||
#test(luma(oklab(10%, 20%, 30%)).space(), luma)
|
||||
|
||||
#test(rgb(color.hsl(10deg, 20%, 30%)).space(), rgb)
|
||||
#test(color.linear-rgb(color.hsl(10deg, 20%, 30%)).space(), color.linear-rgb)
|
||||
#test(oklab(color.hsl(10deg, 20%, 30%)).space(), oklab)
|
||||
#test(color.hsl(color.hsl(10deg, 20%, 30%)).space(), color.hsl)
|
||||
#test(color.hsv(color.hsl(10deg, 20%, 30%)).space(), color.hsv)
|
||||
#test(cmyk(color.hsl(10deg, 20%, 30%)).space(), cmyk)
|
||||
#test(luma(color.hsl(10deg, 20%, 30%)).space(), luma)
|
||||
|
||||
#test(rgb(color.hsv(10deg, 20%, 30%)).space(), rgb)
|
||||
#test(color.linear-rgb(color.hsv(10deg, 20%, 30%)).space(), color.linear-rgb)
|
||||
#test(oklab(color.hsv(10deg, 20%, 30%)).space(), oklab)
|
||||
#test(color.hsl(color.hsv(10deg, 20%, 30%)).space(), color.hsl)
|
||||
#test(color.hsv(color.hsv(10deg, 20%, 30%)).space(), color.hsv)
|
||||
#test(cmyk(color.hsv(10deg, 20%, 30%)).space(), cmyk)
|
||||
#test(luma(color.hsv(10deg, 20%, 30%)).space(), luma)
|
||||
|
||||
#test(rgb(cmyk(10%, 20%, 30%, 40%)).space(), rgb)
|
||||
#test(color.linear-rgb(cmyk(10%, 20%, 30%, 40%)).space(), color.linear-rgb)
|
||||
#test(oklab(cmyk(10%, 20%, 30%, 40%)).space(), oklab)
|
||||
#test(color.hsl(cmyk(10%, 20%, 30%, 40%)).space(), color.hsl)
|
||||
#test(color.hsv(cmyk(10%, 20%, 30%, 40%)).space(), color.hsv)
|
||||
#test(cmyk(cmyk(10%, 20%, 30%, 40%)).space(), cmyk)
|
||||
#test(luma(cmyk(10%, 20%, 30%, 40%)).space(), luma)
|
||||
|
||||
#test(rgb(luma(10%)).space(), rgb)
|
||||
#test(color.linear-rgb(luma(10%)).space(), color.linear-rgb)
|
||||
#test(oklab(luma(10%)).space(), oklab)
|
||||
#test(color.hsl(luma(10%)).space(), color.hsl)
|
||||
#test(color.hsv(luma(10%)).space(), color.hsv)
|
||||
#test(cmyk(luma(10%)).space(), cmyk)
|
||||
#test(luma(luma(10%)).space(), luma)
|
||||
|
||||
---
|
||||
// Test gray color conversion.
|
||||
@ -70,9 +126,17 @@
|
||||
#color.mix((red, 1, 2))
|
||||
|
||||
---
|
||||
// Error: 31-38 expected "oklab" or "srgb"
|
||||
// Error: 31-38 expected `rgb`, `luma`, `cmyk`, `oklab`, `color.linear-rgb`, `color.hsl`, or `color.hsv`, found string
|
||||
#color.mix(red, green, space: "cyber")
|
||||
|
||||
---
|
||||
// Error: 31-36 expected `rgb`, `luma`, `cmyk`, `oklab`, `color.linear-rgb`, `color.hsl`, or `color.hsv`
|
||||
#color.mix(red, green, space: image)
|
||||
|
||||
---
|
||||
// Error: 31-41 expected `rgb`, `luma`, `cmyk`, `oklab`, `color.linear-rgb`, `color.hsl`, or `color.hsv`
|
||||
#color.mix(red, green, space: calc.round)
|
||||
|
||||
---
|
||||
// Ref: true
|
||||
#let envelope = symbol(
|
||||
|
Loading…
x
Reference in New Issue
Block a user