Skip to content

Commit

Permalink
Allow coord grammar to be specified for the color syntax
Browse files Browse the repository at this point in the history
Some non CSS level 4 color spaces need coordinate grammars
that go beyond what is currently allowed for the color() syntax.

For example the luv colorspace requires <percentage>[-1, 1] to
be specified for its u and v coordinates. Other color spaces
such as hsluv, hpluv, okhsl, okhsv have coordinates that are
angles.
  • Loading branch information
lloydk authored and LeaVerou committed Feb 1, 2024
1 parent 2d127d5 commit e906469
Showing 1 changed file with 58 additions and 37 deletions.
95 changes: 58 additions & 37 deletions src/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,56 @@ import ColorSpace from "./space.js";

const noneTypes = new Set(["<number>", "<percentage>", "<angle>"]);

/**
* Validates the coordinates of a color against a format's coord grammar and
* maps the coordinates to the range or refRange of the coordinates.
* @param {ColorSpace} space - Colorspace the coords are in
* @param {object} format - the format object to validate against
* @param {string} name - the name of the color function. e.g. "oklab" or "color"
* @returns {object[]} - an array of type metadata for each coordinate
*/
function coerceCoords (space, format, name, coords) {
let types = Object.entries(space.coords).map(([id, coordMeta], i) => {
let coordGrammar = format.coordGrammar[i];
let arg = coords[i];
let providedType = arg?.type;

// Find grammar alternative that matches the provided type
// Non-strict equals is intentional because we are comparing w/ string objects
let type;
if (arg.none) {
type = coordGrammar.find(c => noneTypes.has(c));
}
else {
type = coordGrammar.find(c => c == providedType);
}

// Check that each coord conforms to its grammar
if (!type) {
// Type does not exist in the grammar, throw
let coordName = coordMeta.name || id;
throw new TypeError(`${providedType ?? arg.raw} not allowed for ${coordName} in ${name}()`);
}

let fromRange = type.range;

if (providedType === "<percentage>") {
fromRange ||= [0, 1];
}

let toRange = coordMeta.range || coordMeta.refRange;

if (fromRange && toRange) {
coords[i] = util.mapRange(fromRange, toRange, coords[i]);
}

return type;
});

return types;
}


/**
* Convert a CSS Color string to a color object
* @param {string} str
Expand Down Expand Up @@ -40,8 +90,14 @@ export default function parse (str, {meta} = {}) {
// If less <number>s or <percentage>s are provided than parameters that the colorspace takes, the missing parameters default to 0. (This is particularly convenient for multichannel printers where the additional inks are spot colors or varnishes that most colors on the page won’t use.)
const coords = Object.keys(space.coords).map((_, i) => env.parsed.args[i] || 0);

let types;

if (colorSpec.coordGrammar) {
types = coerceCoords(space, colorSpec, "color", coords);
}

if (meta) {
meta.formatId = "color";
Object.assign(meta, {formatId: "color", types});
}

return {spaceId: space.id, coords, alpha};
Expand Down Expand Up @@ -78,42 +134,7 @@ export default function parse (str, {meta} = {}) {
let types;

if (format.coordGrammar) {
types = Object.entries(space.coords).map(([id, coordMeta], i) => {
let coordGrammar = format.coordGrammar[i];
let arg = coords[i];
let providedType = arg?.type;

// Find grammar alternative that matches the provided type
// Non-strict equals is intentional because we are comparing w/ string objects
let type;
if (arg.none) {
type = coordGrammar.find(c => noneTypes.has(c));
}
else {
type = coordGrammar.find(c => c == providedType);
}

// Check that each coord conforms to its grammar
if (!type) {
// Type does not exist in the grammar, throw
let coordName = coordMeta.name || id;
throw new TypeError(`${providedType ?? arg.raw} not allowed for ${coordName} in ${name}()`);
}

let fromRange = type.range;

if (providedType === "<percentage>") {
fromRange ||= [0, 1];
}

let toRange = coordMeta.range || coordMeta.refRange;

if (fromRange && toRange) {
coords[i] = util.mapRange(fromRange, toRange, coords[i]);
}

return type;
});
types = coerceCoords(space, format, name, coords);
}

if (meta) {
Expand Down

0 comments on commit e906469

Please sign in to comment.