Skip to content

Commit

Permalink
Interprete rgb and hsl colors literally
Browse files Browse the repository at this point in the history
ref #316
  • Loading branch information
frostburn committed May 13, 2024
1 parent fd4962d commit 254bd7a
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 28 deletions.
6 changes: 2 additions & 4 deletions documentation/dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,8 @@ Colors may be specified using
- [Keywords](https://www.w3.org/wiki/CSS/Properties/color/keywords) like `red`, `white` or `black`
- Short hexadecimal colors like `#d13` for crimson red
- Long hexadecimal colors like `#e6e6fa` for lavender
- RGB values like `rgb(160, 82, 45)` for sienna brown
- HSL values like `hsl(120, 60, 70)` for pastel green

SonicWeave doesn't have percentages so the CSS color `hsl(120, 60%, 70%)` is spelled without the percent signs.
- RGB values like `rgb(160 82 45)` for sienna brown
- HSL values like `hsl(120deg 60% 70%)` for pastel green

## Code comments

Expand Down
4 changes: 4 additions & 0 deletions src/grammars/base.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ RGB4
RGB8
= $('#' HexDigit|6|)

CSSNumber
= [+-]? ('0' / ([1-9] DecimalDigit*)) ('.' DecimalDigit*)?
/ [+-]? '.' DecimalDigit+

IdentifierName
= $(IdentifierStart IdentifierPart*)

Expand Down
13 changes: 13 additions & 0 deletions src/grammars/sonic-weave.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -1266,13 +1266,26 @@ NotANumberLiteral
InfinityLiteral
= InfinityToken { return { type: 'InfinityLiteral' }; }
// RGB and HSL use modern CSS syntax, no legacy support
ColorLiteral
= value: (@RGB8 / @RGB4) {
return {
type: 'ColorLiteral',
value,
};
}
/ 'rgb' 'a'? '(' __ CSSNumber '%'? __ CSSNumber '%'? __ CSSNumber '%'? (__ '/' __ __ CSSNumber '%'?)? __ ')' {
return {
type: 'ColorLiteral',
value: text(),
};
}
/ 'hsl' 'a'? '(' __ CSSNumber 'deg'? __ CSSNumber '%'? __ CSSNumber '%'? (__ '/' __ CSSNumber '%'?)? __ ')' {
return {
type: 'ColorLiteral',
value: text(),
};
}
VulgarFraction 'vulgar fraction'
= '¼' / 'q' / '½' / 's' / '¾' / 'Q' / [⅐-⅞] / ''
Expand Down
4 changes: 2 additions & 2 deletions src/interval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ export class Color {

/**
* SonicWeave representation of the CSS color.
* @returns A string without percentage signs.
* @returns The color value as a string.
*/
toString() {
return this.value.replace(/%/g, '');
return this.value;
}
}

Expand Down
14 changes: 7 additions & 7 deletions src/parser/__tests__/expression.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,16 +604,16 @@ describe('SonicWeave expression evaluator', () => {
});

it('supports hsl colors', () => {
const greenish = evaluate('hsl(123, 45, 67)') as Color;
expect(greenish.value).toBe('hsl(123.000, 45.000%, 67.000%)');
expect(greenish.toString()).toBe('hsl(123.000, 45.000, 67.000)');
const greenish = evaluate('hsl(123deg 45% 67%)') as Color;
expect(greenish.value).toBe('hsl(123deg 45% 67%)');
expect(greenish.toString()).toBe('hsl(123deg 45% 67%)');
const retry = evaluate(greenish.toString()) as Color;
expect(retry.value).toBe(greenish.value);
});

it('supports rgb color labels and reprs', () => {
const lightFifth = evaluate('repr(3/2 rgb(200, 222, 256))');
expect(lightFifth).toBe('(3/2 rgb(200.000, 222.000, 256.000))');
expect(lightFifth).toBe('(3/2 rgb(200.000 222.000 256.000))');
});

it('can concatenate strings', () => {
Expand Down Expand Up @@ -642,9 +642,9 @@ describe('SonicWeave expression evaluator', () => {
expect(nothing).toHaveLength(0);
});

it('interpretes cents as linear decimals in rgba', () => {
const faded = evaluate('rgba(255, 255, 255, 0.5)') as Color;
expect(faded.value).toBe('rgba(255.000, 255.000, 255.000, 0.50000)');
it('has rgba syntax', () => {
const faded = evaluate('rgba(255 255 255 / 0.5)') as Color;
expect(faded.value).toBe('rgba(255 255 255 / 0.5)');
});

it('preserves labels based on preference (left)', () => {
Expand Down
19 changes: 19 additions & 0 deletions src/parser/__tests__/sonic-weave-ast.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,25 @@ describe('SonicWeave Abstract Syntax Tree parser', () => {
],
});
});

it('parses CSS hsl literals', () => {
const ast = parseSingle('hsl( -.5deg -50% 10 / 40% )');
expect(ast).toEqual({
type: 'ExpressionStatement',
expression: {
type: 'ColorLiteral',
value: 'hsl( -.5deg -50% 10 / 40% )',
},
});
});

it('parses CSS rgb literals', () => {
const ast = parseSingle('rgba(255 50% 5 / .5)');
expect(ast).toEqual({
type: 'ExpressionStatement',
expression: {type: 'ColorLiteral', value: 'rgba(255 50% 5 / .5)'},
});
});
});

describe('Automatic semicolon insertion', () => {
Expand Down
12 changes: 6 additions & 6 deletions src/parser/__tests__/stdlib.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -710,15 +710,15 @@ describe('SonicWeave standard library', () => {

it('preserves labels when rotating', () => {
const scale = parseSource(
'5/4 yellow "third";3/2 white "fifth";2/1 rgba(255, 255, 255, 0.5) "octave";rotate()'
'5/4 yellow "third";3/2 white "fifth";2/1 rgba(255, 255, 255, 0.5e) "octave";rotate()'
);
expect(scale).toHaveLength(3);
expect(scale[0].valueOf()).toBeCloseTo(6 / 5);
expect(scale[0].color?.value).toBe('white');
expect(scale[0].label).toBe('fifth');
expect(scale[1].valueOf()).toBeCloseTo(8 / 5);
expect(scale[1].color?.value).toBe(
'rgba(255.000, 255.000, 255.000, 0.50000)'
'rgba(255.000 255.000 255.000 / 0.50000)'
);
expect(scale[1].label).toBe('octave');
expect(scale[2].valueOf()).toBeCloseTo(2);
Expand Down Expand Up @@ -1101,10 +1101,10 @@ describe('SonicWeave standard library', () => {
it('colors intervals based on deviation from 12ed2', () => {
const scale = expand('5/4;3/2;7/4;2;edColors()');
expect(scale).toEqual([
'5/4 hsl(310.729, 100.000, 50.000)',
'3/2 hsl(7.038, 100.000, 50.000)',
'7/4 hsl(247.773, 100.000, 50.000)',
'2 hsl(0.000, 100.000, 50.000)',
'5/4 hsl(310.729deg 100.000% 50.000%)',
'3/2 hsl(7.038deg 100.000% 50.000%)',
'7/4 hsl(247.773deg 100.000% 50.000%)',
'2 hsl(0.000deg 100.000% 50.000%)',
]);
});

Expand Down
11 changes: 4 additions & 7 deletions src/stdlib/builtin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2394,15 +2394,12 @@ help.__node__ = builtinNode(help);

// CSS color generation
function cc(x: Interval, fractionDigits = 3) {
if (x?.node?.type === 'CentsLiteral') {
return x.totalCents().toFixed(fractionDigits);
}
return x.value.valueOf().toFixed(fractionDigits);
}

function rgb(red: Interval, green: Interval, blue: Interval) {
requireParameters({red, green, blue});
return new Color(`rgb(${cc(red)}, ${cc(green)}, ${cc(blue)})`);
return new Color(`rgb(${cc(red)} ${cc(green)} ${cc(blue)})`);
}
rgb.__doc__ =
'RGB color (Red range 0-255, Green range 0-255, Blue range 0-255).';
Expand All @@ -2411,7 +2408,7 @@ rgb.__node__ = builtinNode(rgb);
function rgba(red: Interval, green: Interval, blue: Interval, alpha: Interval) {
requireParameters({red, green, blue, alpha});
return new Color(
`rgba(${cc(red)}, ${cc(green)}, ${cc(blue)}, ${cc(alpha, 5)})`
`rgba(${cc(red)} ${cc(green)} ${cc(blue)} / ${cc(alpha, 5)})`
);
}
rgba.__doc__ =
Expand All @@ -2420,7 +2417,7 @@ rgba.__node__ = builtinNode(rgba);

function hsl(hue: Interval, saturation: Interval, lightness: Interval) {
requireParameters({hue, saturation, lightness});
return new Color(`hsl(${cc(hue)}, ${cc(saturation)}%, ${cc(lightness)}%)`);
return new Color(`hsl(${cc(hue)}deg ${cc(saturation)}% ${cc(lightness)}%)`);
}
hsl.__doc__ =
'HSL color (Hue range 0-360, Saturation range 0-100, Lightness range 0-100).';
Expand All @@ -2434,7 +2431,7 @@ function hsla(
) {
requireParameters({hue, saturation, lightness, alpha});
return new Color(
`hsla(${cc(hue)}, ${cc(saturation)}%, ${cc(lightness)}%, ${cc(alpha, 5)})`
`hsla(${cc(hue)}deg ${cc(saturation)}% ${cc(lightness)}% / ${cc(alpha, 5)})`
);
}
hsla.__doc__ =
Expand Down
4 changes: 2 additions & 2 deletions src/stdlib/public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ export function centsColor(this: ExpressionVisitor, interval: Interval) {
const h = octaves * 360;
const s = Math.tanh(1 - octaves * 0.5) * 50 + 50;
const l = Math.tanh(octaves * 0.2) * 50 + 50;
return new Color(`hsl(${h.toFixed(3)}, ${s.toFixed(3)}%, ${l.toFixed(3)}%)`);
return new Color(`hsl(${h.toFixed(3)}deg ${s.toFixed(3)}% ${l.toFixed(3)}%)`);
}

// Prime colors for over/under.
Expand Down Expand Up @@ -426,7 +426,7 @@ export function factorColor(this: ExpressionVisitor, interval: Interval) {
b += prgb[2] * m;
}
}
return new Color(`rgb(${tanh255(r)}, ${tanh255(g)}, ${tanh255(b)})`);
return new Color(`rgb(${tanh255(r)} ${tanh255(g)} ${tanh255(b)})`);
}

/**
Expand Down

0 comments on commit 254bd7a

Please sign in to comment.