Skip to content

Commit f3869d9

Browse files
committed
Properly serialize out-of-gamut {ok,}l{ab,ch} colors
1 parent cd6a903 commit f3869d9

File tree

1 file changed

+66
-18
lines changed

1 file changed

+66
-18
lines changed

lib/src/visitor/serialize.dart

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -608,14 +608,35 @@ final class _SerializeVisitor
608608
_maybeWriteSlashAlpha(value);
609609
_buffer.writeCharCode($rparen);
610610

611-
// case ColorSpace.lab ||
612-
// ColorSpace.oklab ||
613-
// ColorSpace.lch ||
614-
// ColorSpace.oklch when fuzzyInRange(value.channel0, 0, 100) && !value.isChannel1Missing && !value.isChannel2Missing:
615-
// case ColorSpace.lch ||
616-
// ColorSpace.oklch when fuzzyLessThan(value.channel1, 0) && !value.isChannel0Missing && !value.isChannel1Missing:
617-
// // color-mix() is currently more widely supported than relative color
618-
// // syntax, so we use it to serialize out-of-gamut
611+
case ColorSpace.lab ||
612+
ColorSpace.oklab ||
613+
ColorSpace.lch ||
614+
ColorSpace.oklch
615+
when !_inspect &&
616+
!fuzzyInRange(value.channel0, 0, 100) &&
617+
!value.isChannel1Missing &&
618+
!value.isChannel2Missing:
619+
case ColorSpace.lch || ColorSpace.oklch
620+
when !_inspect &&
621+
fuzzyLessThan(value.channel1, 0) &&
622+
!value.isChannel0Missing &&
623+
!value.isChannel1Missing:
624+
// color-mix() is currently more widely supported than relative color
625+
// syntax, so we use it to serialize out-of-gamut colors in a way that
626+
// maintains the color space defined in Sass while (per spec) not
627+
// clamping their values. In practice, all browsers clamp out-of-gamut
628+
// values, but there's not much we can do about that at time of writing.
629+
_buffer.write('color-mix(in ');
630+
_buffer.write(value.space);
631+
_buffer.write(_commaSeparator);
632+
// The XYZ space has no gamut restrictions, so we use it to represent
633+
// the out-of-gamut color before converting into the target space.
634+
_writeColorFunction(value.toSpace(ColorSpace.xyzD65));
635+
_writeOptionalSpace();
636+
_buffer.write('100%');
637+
_buffer.write(_commaSeparator);
638+
_buffer.write(_isCompressed ? 'red' : 'black');
639+
_buffer.writeCharCode($rparen);
619640

620641
case ColorSpace.lab ||
621642
ColorSpace.oklab ||
@@ -624,6 +645,21 @@ final class _SerializeVisitor
624645
_buffer
625646
..write(value.space)
626647
..writeCharCode($lparen);
648+
649+
// color-mix() can't represent out-of-bounds colors with missing
650+
// channels, so in this case we use the less-supported but
651+
// more-expressive relative color syntax instead. Relative color syntax
652+
// never clamps channels.
653+
var polar = value.space.channels[2].isPolarAngle;
654+
if (!_inspect &&
655+
(!fuzzyInRange(value.channel0, 0, 100) ||
656+
(polar && fuzzyLessThan(value.channel1, 0)))) {
657+
_buffer
658+
..write('from ')
659+
..write(_isCompressed ? 'red' : 'black')
660+
..writeCharCode($space);
661+
}
662+
627663
if (!_isCompressed && !value.isChannel0Missing) {
628664
var max = (value.space.channels[0] as LinearChannel).max;
629665
_writeNumber(value.channel0 * 100 / max);
@@ -635,22 +671,14 @@ final class _SerializeVisitor
635671
_writeChannel(value.channel1OrNull);
636672
_buffer.writeCharCode($space);
637673
_writeChannel(value.channel2OrNull);
638-
if (!_isCompressed &&
639-
!value.isChannel2Missing &&
640-
value.space.channels[2].isPolarAngle) {
674+
if (!_isCompressed && !value.isChannel2Missing && polar) {
641675
_buffer.write('deg');
642676
}
643677
_maybeWriteSlashAlpha(value);
644678
_buffer.writeCharCode($rparen);
645679

646680
case _:
647-
_buffer
648-
..write('color(')
649-
..write(value.space)
650-
..writeCharCode($space);
651-
_writeBetween(value.channelsOrNull, ' ', _writeChannel);
652-
_maybeWriteSlashAlpha(value);
653-
_buffer.writeCharCode($rparen);
681+
_writeColorFunction(value);
654682
}
655683
}
656684

@@ -874,6 +902,26 @@ final class _SerializeVisitor
874902
_buffer.writeCharCode($rparen);
875903
}
876904

905+
/// Writes [color] using the `color()` function syntax.
906+
void _writeColorFunction(SassColor color) {
907+
assert(!{
908+
ColorSpace.rgb,
909+
ColorSpace.hsl,
910+
ColorSpace.hwb,
911+
ColorSpace.lab,
912+
ColorSpace.oklab,
913+
ColorSpace.lch,
914+
ColorSpace.oklch
915+
}.contains(color.space));
916+
_buffer
917+
..write('color(')
918+
..write(color.space)
919+
..writeCharCode($space);
920+
_writeBetween(color.channelsOrNull, ' ', _writeChannel);
921+
_maybeWriteSlashAlpha(color);
922+
_buffer.writeCharCode($rparen);
923+
}
924+
877925
/// Returns whether [color]'s hex pair representation is symmetrical (e.g.
878926
/// `FF`).
879927
bool _isSymmetricalHex(int color) => color & 0xF == color >> 4;

0 commit comments

Comments
 (0)