Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More flexible percentage and precision fix #370

Merged
merged 4 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion coloraide/__meta__.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,5 +193,5 @@ def parse_version(ver: str) -> Version:
return Version(major, minor, micro, release, pre, post, dev)


__version_info__ = Version(2, 11, 0, "final")
__version_info__ = Version(2, 12, 0, "final")
__version__ = __version_info__._get_canonical()
96 changes: 50 additions & 46 deletions coloraide/css/serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from .color_names import to_name
from ..channels import FLG_PERCENT, FLG_OPT_PERCENT, FLG_ANGLE
from ..types import Vector
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Sequence

if TYPE_CHECKING: # pragma: no cover
from ..color import Color
Expand All @@ -33,72 +33,76 @@ def named_color(
return to_name(get_coords(obj, fit, False, False) + [a])


def named_color_function(
def color_function(
obj: 'Color',
func: str,
func: str | None,
alpha: bool | None,
precision: int,
fit: str | bool,
none: bool,
percent: bool,
percent: bool | Sequence[bool],
legacy: bool,
scale: float
) -> str:
"""Translate to CSS function form `name(...)`."""

# Create the function `name` or `namea` if old legacy form.
# Prepare coordinates to be serialized
a = get_alpha(obj, alpha, none, legacy)
string = ['{}{}('.format(func, 'a' if legacy and a is not None else EMPTY)]

# Iterate the coordinates formatting them for percent, not percent, and even scaling them (sRGB).
coords = get_coords(obj, fit, none, legacy)
channels = obj._space.CHANNELS
if a is not None:
coords.append(a)

# `color` should include the color space serialized name.
if func is None:
string = ['color({} '.format(obj._space._serialize()[0])]
# Create the function `name` or `namea` if old legacy form.
else:
string = ['{}{}('.format(func, 'a' if legacy and a is not None else EMPTY)]

# Get channel object and calculate length and the alpha index (last)
channels = obj._space.channels
l = len(channels)
last = l - 1

# Ensure percent is configured
# - `True` assumes all but alpha are attempted to be formatted as percents.
# - A list of booleans will attempt formatting the associated channel as percent,
# anything not specified is assumed `False`.
if isinstance(percent, bool):
plist = obj._space._percents if percent else []
else:
diff = l - len(percent)
plist = list(percent) + ([False] * diff) if diff > 0 else list(percent)

# Iterate the coordinates formatting them by scaling the values, formatting for percent, etc.
for idx, value in enumerate(coords):
channel = channels[idx]
use_percent = channel.flags & FLG_PERCENT or (percent and channel.flags & FLG_OPT_PERCENT)
is_angle = channel.flags & FLG_ANGLE
if not use_percent and not is_angle:
value *= scale
if idx != 0:
is_last = idx == last
if is_last:
string.append(COMMA if legacy else SLASH)
elif idx != 0:
string.append(COMMA if legacy else SPACE)
channel = channels[idx]

if channel.flags & FLG_PERCENT or (plist and plist[idx] and channel.flags & FLG_OPT_PERCENT):
span, offset = channel.span, channel.offset
else:
span = offset = 0.0
if not channel.flags & FLG_ANGLE and not is_last:
value *= scale

string.append(
util.fmt_float(
value,
precision,
channel.span if use_percent else 0.0,
channel.offset if use_percent else 0.0
span,
offset
)
)

# Add alpha if needed
if a is not None:
string.append('{}{})'.format(COMMA if legacy else SLASH, util.fmt_float(a, max(precision, util.DEF_PREC))))
else:
string.append(')')
string.append(')')
return EMPTY.join(string)


def color_function(
obj: 'Color',
alpha: bool | None,
precision: int,
fit: str | bool,
none: bool
) -> str:
"""Color format."""

# Export in the `color(space ...)` format
coords = get_coords(obj, fit, none, False)
a = get_alpha(obj, alpha, none, False)
return (
'color({} {}{})'.format(
obj._space._serialize()[0],
SPACE.join([util.fmt_float(coord, precision) for coord in coords]),
SLASH + util.fmt_float(a, max(precision, util.DEF_PREC)) if a is not None else EMPTY
)
)


def get_coords(
obj: 'Color',
fit: str | bool,
Expand Down Expand Up @@ -168,7 +172,7 @@ def serialize_css(
precision: int | None = None,
fit: str | bool = True,
none: bool = False,
percent: bool = False,
percent: bool | Sequence[bool] = False,
hexa: bool = False,
upper: bool = False,
compress: bool = False,
Expand All @@ -183,7 +187,7 @@ def serialize_css(

# Color format
if color:
return color_function(obj, alpha, precision, fit, none)
return color_function(obj, None, alpha, precision, fit, none, percent, False, 1.0)

# CSS color names
if name:
Expand All @@ -197,6 +201,6 @@ def serialize_css(

# Normal CSS named function format
if func:
return named_color_function(obj, func, alpha, precision, fit, none, percent, legacy, scale)
return color_function(obj, func, alpha, precision, fit, none, percent, legacy, scale)

raise RuntimeError('Could not identify a CSS format to serialize to') # pragma: no cover
11 changes: 7 additions & 4 deletions coloraide/spaces/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Color base."""
from __future__ import annotations
from abc import ABCMeta, abstractmethod
from ..channels import Channel
from ..channels import Channel, FLG_OPT_PERCENT
from ..css import serialize
from ..deprecate import deprecated
from ..types import VectorLike, Vector, Plugin
from typing import Any, TYPE_CHECKING
from typing import Any, TYPE_CHECKING, Sequence
import math

if TYPE_CHECKING: # pragma: no cover
Expand Down Expand Up @@ -138,7 +138,7 @@ def indexes(self) -> list[int]:
return [self.get_channel_index(name) for name in self.names()] # type: ignore[attr-defined]


alpha_channel = Channel('alpha', 0.0, 1.0, bound=True, limit=(0.0, 1.0))
alpha_channel = Channel('alpha', 0.0, 1.0, bound=True, limit=(0.0, 1.0), flags=FLG_OPT_PERCENT)


class SpaceMeta(ABCMeta):
Expand Down Expand Up @@ -190,6 +190,7 @@ def __init__(self, **kwargs: Any) -> None:

self.channels = self.CHANNELS + (alpha_channel,)
self._color_ids = (self.NAME,) if not self.SERIALIZE else self.SERIALIZE
self._percents = ([True] * (len(self.channels) - 1)) + [False]

def get_channel_index(self, name: str) -> int:
"""Get channel index."""
Expand Down Expand Up @@ -234,6 +235,7 @@ def to_string(
precision: int | None = None,
fit: bool | str = True,
none: bool = False,
percent: bool | Sequence[bool] = False,
**kwargs: Any
) -> str:
"""Convert to CSS 'color' string: `color(space coords+ / alpha)`."""
Expand All @@ -244,7 +246,8 @@ def to_string(
alpha=alpha,
precision=precision,
fit=fit,
none=none
none=none,
percent=percent
)

def match(
Expand Down
17 changes: 14 additions & 3 deletions coloraide/spaces/hsl/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from ...css import parse
from ...css import serialize
from ...types import Vector
from typing import Any, TYPE_CHECKING
from typing import Any, TYPE_CHECKING, Sequence

if TYPE_CHECKING: # pragma: no cover
from ...color import Color
Expand All @@ -22,12 +22,23 @@ def to_string(
fit: str | bool = True,
none: bool = False,
color: bool = False,
percent: bool = True,
percent: bool | Sequence[bool] | None = None,
comma: bool = False,
**kwargs: Any
) -> str:
"""Convert to CSS."""

if percent is None:
if not color:
percent = True
else:
percent = False
elif isinstance(percent, bool):
if comma:
percent = True
elif comma:
percent = [False, True, True] + list(percent[3:4])

return serialize.serialize_css(
parent,
func='hsl',
Expand All @@ -37,7 +48,7 @@ def to_string(
none=none,
color=color,
legacy=comma,
percent=True if comma else percent,
percent=percent,
scale=100
)

Expand Down
7 changes: 5 additions & 2 deletions coloraide/spaces/hwb/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from ...css import parse
from ...css import serialize
from ...types import Vector
from typing import Any, TYPE_CHECKING
from typing import Any, TYPE_CHECKING, Sequence

if TYPE_CHECKING: # pragma: no cover
from ...color import Color
Expand All @@ -22,11 +22,14 @@ def to_string(
fit: str | bool = True,
none: bool = False,
color: bool = False,
percent: bool = True,
percent: bool | Sequence[bool] | None = None,
**kwargs: Any
) -> str:
"""Convert to CSS."""

if percent is None:
percent = False if color else True

return serialize.serialize_css(
parent,
func='hwb',
Expand Down
4 changes: 2 additions & 2 deletions coloraide/spaces/lab/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from ...css import parse
from ...css import serialize
from ...types import Vector
from typing import Any, TYPE_CHECKING
from typing import Any, TYPE_CHECKING, Sequence

if TYPE_CHECKING: # pragma: no cover
from ...color import Color
Expand All @@ -22,7 +22,7 @@ def to_string(
fit: str | bool = True,
none: bool = False,
color: bool = False,
percent: bool = False,
percent: bool | Sequence[bool] = False,
**kwargs: Any
) -> str:
"""Convert to CSS."""
Expand Down
4 changes: 2 additions & 2 deletions coloraide/spaces/lch/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from ...css import parse
from ...css import serialize
from ...types import Vector
from typing import Any, TYPE_CHECKING
from typing import Any, TYPE_CHECKING, Sequence

if TYPE_CHECKING: # pragma: no cover
from ...color import Color
Expand All @@ -22,7 +22,7 @@ def to_string(
fit: str | bool = True,
none: bool = False,
color: bool = False,
percent: bool = False,
percent: bool | Sequence[bool] = False,
**kwargs: Any
) -> str:
"""Convert to CSS."""
Expand Down
4 changes: 2 additions & 2 deletions coloraide/spaces/oklab/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from ...css import parse
from ...css import serialize
from ...types import Vector
from typing import Any, TYPE_CHECKING
from typing import Any, TYPE_CHECKING, Sequence

if TYPE_CHECKING: # pragma: no cover
from ...color import Color
Expand All @@ -22,7 +22,7 @@ def to_string(
fit: str | bool = True,
none: bool = False,
color: bool = False,
percent: bool = False,
percent: bool | Sequence[bool] = False,
**kwargs: Any
) -> str:
"""Convert to CSS."""
Expand Down
4 changes: 2 additions & 2 deletions coloraide/spaces/oklch/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from ...css import parse
from ...css import serialize
from ...types import Vector
from typing import Any, TYPE_CHECKING
from typing import Any, TYPE_CHECKING, Sequence

if TYPE_CHECKING: # pragma: no cover
from ...color import Color
Expand All @@ -22,7 +22,7 @@ def to_string(
fit: str | bool = True,
none: bool = False,
color: bool = False,
percent: bool = False,
percent: bool | Sequence[bool] = False,
**kwargs: Any
) -> str:
"""Convert to CSS."""
Expand Down
4 changes: 2 additions & 2 deletions coloraide/spaces/srgb/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .. import srgb as base
from ...css import parse
from ...css import serialize
from typing import Any, Tuple, TYPE_CHECKING
from typing import Any, Tuple, TYPE_CHECKING, Sequence
from ...types import Vector

if TYPE_CHECKING: # pragma: no cover
Expand All @@ -26,7 +26,7 @@ def to_string(
names: bool = False,
comma: bool = False,
upper: bool = False,
percent: bool = False,
percent: bool | Sequence[bool] = False,
compress: bool = False,
**kwargs: Any
) -> str:
Expand Down
1 change: 1 addition & 0 deletions docs/src/dictionary/en-custom.txt
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ barycentric
bezier
bilinear
boolean
booleans
broadcasted
bz
cd
Expand Down
8 changes: 8 additions & 0 deletions docs/src/markdown/about/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 2.12

- **NEW**: When serializing, `percent` can now take a sequence of booleans to indicate which channels are desired to
be represented as a percentage, alpha included.
- **NEW**: `color()` serializing now supports string output with `percent`.
- **FIX**: When serializing, the alpha channel is no longer handled special with a minimum value of `5`. Precision is
equally applied to all channels.

## 2.11

- **NEW**: Add new `css-linear` interpolator that provides compatibility with the CSS specification. This deviates
Expand Down
2 changes: 1 addition & 1 deletion docs/src/markdown/demos/3d_models.html
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@ <h1>ColorAide Color Space Models</h1>
let colorSpaces = null
let colorGamuts = null
let lastModel = null
let package = 'coloraide-2.11-py3-none-any.whl'
let package = 'coloraide-2.12-py3-none-any.whl'
const defaultSpace = 'lab'
const defaultGamut = 'srgb'
const exceptions = new Set(['hwb', 'ryb', 'ryb-biased'])
Expand Down
Loading
Loading