From 2af5c80ecc81ae99511abc2f7fa902f0da336d43 Mon Sep 17 00:00:00 2001 From: Robert Grimm Date: Sun, 4 Aug 2024 18:20:41 -0400 Subject: [PATCH] rename Sampler to Translator --- CHANGELOG.md | 8 ++ docs/src/deepdive/progressbar.md | 10 +- docs/src/links.md | 2 +- docs/src/overview/integration.md | 103 ++++++++-------- docs/src/overview/summary.md | 8 +- prettypretty/color.pyi | 20 ++-- prettypretty/darkmode.py | 13 +- prettypretty/grid.py | 27 +++-- prettypretty/plot.py | 8 +- prettypretty/progress.py | 4 +- prettypretty/style.py | 8 +- prettypretty/terminal.py | 10 +- prettypretty/theme.py | 20 ++-- src/lib.rs | 8 +- src/object.rs | 4 +- src/style.rs | 172 +++++++++++++++++++++++++++ src/term_color.rs | 15 ++- src/translation.rs | 196 +++++++++++++++---------------- test/test_color.py | 6 +- 19 files changed, 416 insertions(+), 226 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81d0951..7254461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## v0.11.0 (2024-xx-xx) + +### Changes + + * Rename `Sampler` to `Translator`. + * Edit documentation for correctness and clarity. + + ## v0.10.0 (2024-07-12) ### New Features diff --git a/docs/src/deepdive/progressbar.md b/docs/src/deepdive/progressbar.md index 8b39810..302ba1b 100644 --- a/docs/src/deepdive/progressbar.md +++ b/docs/src/deepdive/progressbar.md @@ -181,17 +181,17 @@ in Python even more ergonomic and is well worth the extra engineering effort. ### 4. Adjust Styles to Reality Once the terminal has been set up, the progress bar script uses the -[`current_sampler`]'s [`is_dark_theme`] to pick the attendant style and then +[`current_translator`]'s [`is_dark_theme`] to pick the attendant style and then adjusts that style to the terminal's [`Terminal.fidelity`]: ```python -style = DARK_MODE_BAR if current_sampler().is_dark_theme() else LIGHT_MODE_BAR +style = DARK_MODE_BAR if current_translator().is_dark_theme() else LIGHT_MODE_BAR style = style.prepare(term.fidelity) ``` Doing so once during startup means that the resulting styles are ready for (repeated) display and incurs the overhead of color conversion only once. -Between [`Style.prepare`] and [`Sampler.cap`], updating styles and colors to +Between [`Style.prepare`] and [`Translator.cap`], updating styles and colors to match a given fidelity level also is positively easy. In other words, "reality," as far as this progress bar is concerned, has two @@ -291,11 +291,10 @@ a complete change of direction. -[`current_sampler`]: https://apparebit.github.io/prettypretty/python/prettypretty/theme.html#prettypretty.theme.current_sampler +[`current_translator`]: https://apparebit.github.io/prettypretty/python/prettypretty/theme.html#prettypretty.theme.current_translator [`is_dark_theme`]: https://apparebit.github.io/prettypretty/python/prettypretty/darkmode.html#prettypretty.darkmode.is_dark_theme [`rich`]: https://apparebit.github.io/prettypretty/python/prettypretty/style.html#prettypretty.style.rich [`RichText`]: https://apparebit.github.io/prettypretty/python/prettypretty/style.html#prettypretty.style.RichText -[`Sampler.cap`]: https://apparebit.github.io/prettypretty/prettypretty/struct.Sampler.html#method.cap [`Style.prepare`]: https://apparebit.github.io/prettypretty/python/prettypretty/style.html#prettypretty.style.Style.prepare [`Terminal`]: https://apparebit.github.io/prettypretty/python/prettypretty/terminal.html#prettypretty.terminal.Terminal [`Terminal.alternate_screen`]: https://apparebit.github.io/prettypretty/python/prettypretty/terminal.html#prettypretty.terminal.Terminal.alternate_screen @@ -303,3 +302,4 @@ a complete change of direction. [`Terminal.bracketed_paste`]: https://apparebit.github.io/prettypretty/python/prettypretty/terminal.html#prettypretty.terminal.Terminal.bracketed_paste [`Terminal.fidelity`]: https://apparebit.github.io/prettypretty/python/prettypretty/terminal.html#prettypretty.terminal.Terminal.fidelity [`Terminal.window_title`]: https://apparebit.github.io/prettypretty/python/prettypretty/terminal.html#prettypretty.terminal.Terminal.window_title +[`Translator.cap`]: https://apparebit.github.io/prettypretty/prettypretty/struct.Translator.html#method.cap diff --git a/docs/src/links.md b/docs/src/links.md index e0c3dbe..729f711 100644 --- a/docs/src/links.md +++ b/docs/src/links.md @@ -17,9 +17,9 @@ [`Layer`]: https://apparebit.github.io/prettypretty/prettypretty/enum.Layer.html [`OkVersion`]: https://apparebit.github.io/prettypretty/prettypretty/enum.OkVersion.html [`OutOfBoundsError`]: https://apparebit.github.io/prettypretty/prettypretty/struct.OutOfBoundsError.html -[`Sampler`]: https://apparebit.github.io/prettypretty/prettypretty/struct.Sampler.html [`TerminalColor`]: https://apparebit.github.io/prettypretty/prettypretty/enum.TerminalColor.html [`ThemeEntry`]: https://apparebit.github.io/prettypretty/prettypretty/enum.ThemeEntry.html [`ThemeEntryIterator`]: https://apparebit.github.io/prettypretty/prettypretty/struct.ThemeEntryIterator.html +[`Translator`]: https://apparebit.github.io/prettypretty/prettypretty/struct.Translator.html [`TrueColor`]: https://apparebit.github.io/prettypretty/prettypretty/struct.TrueColor.html [`VGA_COLORS`]: https://apparebit.github.io/prettypretty/prettypretty/constant.VGA_COLORS.html diff --git a/docs/src/overview/integration.md b/docs/src/overview/integration.md index f1bdafd..182f62c 100644 --- a/docs/src/overview/integration.md +++ b/docs/src/overview/integration.md @@ -35,28 +35,28 @@ default and ANSI colors. ## Translation Is Necessarily Stateful Since the default and ANSI colors are abstract, translation to high-resolution -colors necessarily requires some form of lookup table, i.e., the so-called color -theme. Prettypretty relies on the same abstraction to store that table as well -as the derived state for translating high-resolution colors to terminal colors -again: +colors necessarily requires some form of lookup table, i.e., a color theme. +Prettypretty relies on the same abstraction to store that table as well as the +derived state for translating high-resolution colors to terminal colors again: - * [`Sampler`] provides the logic and state for translating between + * [`Translator`] provides the logic and state for translating between terminal and high-resolution colors. There is ample precedent for the use of color themes to provide concrete values -for abstract colors. Most terminal emulators feature robust support for the -[plethora of such themes](https://gogh-co.github.io/Gogh/) readily available on -the web. However, asking users to configure theme colors yet again most -certainly is the wrong approach. Luckily, ANSI escape codes include sequences -for querying a terminal for its current theme colors, making it possible to -automatically and transparently adjust to the runtime environment. +for abstract colors. In fact, most terminal emulators feature robust support for +the [plethora of such themes](https://gogh-co.github.io/Gogh/) readily available +on the web. However, asking users to configure theme colors after they already +configured their terminals decidedly is the wrong approach. Luckily, ANSI escape +codes include sequences for querying a terminal for its current theme colors, +making it possible to automatically and transparently adjust to the runtime +environment. ## The Fall From High-Resolution Theme colors turn the translation of terminal to high-resolution colors into a -simple lookup. The difficulty of translation in the other direction, from -high-resolution to terminal colors, very much depends on the target colors: +simple lookup. The level of difficulty when translating in the other direction, +from high-resolution to terminal colors, very much depends on the target colors: ### 24-Bit Colors @@ -64,9 +64,10 @@ high-resolution to terminal colors, very much depends on the target colors: In the best case, when the source color is in-gamut for sRGB and the target are 24-bit "true" colors, a loss of numeric resolution is the only concern. It probably is imperceptible as well. However, if the source color is out of sRGB -gamut, even when still targeting 24-bit colors and, like [`Sampler`], using -gamut-mapping, the difference between source and target colors becomes clearly -noticeable. It only becomes more obvious when targeting 8-bit or ANSI colors. +gamut, even when still targeting 24-bit colors and, like [`Translator`], using +gamut-mapping, the difference between source and target colors may become +clearly noticeable. It only becomes more obvious when targeting 8-bit or ANSI +colors. ### 8-Bit Colors @@ -97,7 +98,7 @@ time*. But because there are so few candidates, the closest matching color may just violate basic human expectations about what is a match, e.g., that warm tones remain warm, cold tones remain cold, light tones remain light, dark tones remain dark, and last but not least color remains color. -[`Sampler::to_closest_ansi`](https://apparebit.github.io/prettypretty/prettypretty/struct.Sampler.html#method.to_closest_ansi)'s +[`Translator::to_closest_ansi`](https://apparebit.github.io/prettypretty/prettypretty/struct.Translator.html#method.to_closest_ansi)'s documentation provides an example that violates the latter expectation, with a light orange tone turning into a light gray. That is jarring, especially in context of other colors that are *not* mapped to gray. @@ -107,91 +108,91 @@ leverages not only uses color pragmatics, i.e., the coordinates of theme colors, but also color semantics, i.e., their intended appearance. In other words, the algorithm leverages the very fact that ANSI colors are abstract colors to improve the quality of matches. As implemented by -[`Sampler::to_ansi_hue_lightness`](https://apparebit.github.io/prettypretty/prettypretty/struct.Sampler.html#method.to_ansi_hue_lightness), +[`Translator::to_ansi_hue_lightness`](https://apparebit.github.io/prettypretty/prettypretty/struct.Translator.html#method.to_ansi_hue_lightness), the algorithm first uses hue in Oklrch to find a pair of regular and bright colors and second uses lightness to pick the closer one. In my evaluation so far, it is indeed more robust than brute force search. But it also won't work if -the theme colors themselves are inconsistent with theme semantics. Since that is -detectable, -[`Sampler::to_ansi`](https://apparebit.github.io/prettypretty/prettypretty/struct.Sampler.html#method.to_ansi) +the theme colors themselves are inconsistent with theme semantics. Since that +can be automatically checked, +[`Translator::to_ansi`](https://apparebit.github.io/prettypretty/prettypretty/struct.Translator.html#method.to_ansi) transparently picks the best possible method. -## Sampler Methods +## Translator Methods Now that we understand the challenges and the algorithms for overcoming them, we -turn to [`Sampler`]'s interface. We group its method by task: +turn to [`Translator`]'s interface. We group its method by task: - 1. [`Sampler::resolve`](https://apparebit.github.io/prettypretty/prettypretty/struct.Sampler.html#method.resolve) + 1. [`Translator::resolve`](https://apparebit.github.io/prettypretty/prettypretty/struct.Translator.html#method.resolve) translates terminal colors to high-resolution colors. Thanks to the `Into` trait, Rust code can invoke the method with an instance of `u8`, [`DefaultColor`], [`AnsiColor`], [`EmbeddedRgb`], [`GrayGradient`], [`TrueColor`], or [`TerminalColor`]. Thanks to a custom PyO3 conversion function, Python code can do the exact same. - 2. [`Sampler::to_closest_8bit`](https://apparebit.github.io/prettypretty/prettypretty/struct.Sampler.html#method.to_closest_8bit) + 2. [`Translator::to_closest_8bit`](https://apparebit.github.io/prettypretty/prettypretty/struct.Translator.html#method.to_closest_8bit) and - [`Sampler::to_ansi`](https://apparebit.github.io/prettypretty/prettypretty/struct.Sampler.html#method.to_ansi) + [`Translator::to_ansi`](https://apparebit.github.io/prettypretty/prettypretty/struct.Translator.html#method.to_ansi) translate high-resolution colors to low-resolution terminal colors. Prettypretty does not support conversion to the default colors and high-resolution colors can be directly converted to true colors, without - requiring mediation through [`Sampler`]. + requiring mediation through [`Translator`]. The - [`Sampler::supports_hue_lightness`](https://apparebit.github.io/prettypretty/prettypretty/struct.Sampler.html#method.supports_hue_lightness), - [`Sampler::to_ansi_hue_lightness`](https://apparebit.github.io/prettypretty/prettypretty/struct.Sampler.html#method.to_ansi_hue_lightness), - [`Sampler::to_closest_ansi`](https://apparebit.github.io/prettypretty/prettypretty/struct.Sampler.html#method.to_closest_ansi), + [`Translator::supports_hue_lightness`](https://apparebit.github.io/prettypretty/prettypretty/struct.Translator.html#method.supports_hue_lightness), + [`Translator::to_ansi_hue_lightness`](https://apparebit.github.io/prettypretty/prettypretty/struct.Translator.html#method.to_ansi_hue_lightness), + [`Translator::to_closest_ansi`](https://apparebit.github.io/prettypretty/prettypretty/struct.Translator.html#method.to_closest_ansi), and - [`Sampler::to_ansi_rgb`](https://apparebit.github.io/prettypretty/prettypretty/struct.Sampler.html#method.to_ansi_rgb) + [`Translator::to_ansi_rgb`](https://apparebit.github.io/prettypretty/prettypretty/struct.Translator.html#method.to_ansi_rgb) methods provide direct access to individual algorithms for converting to ANSI colors. For instance, I use these methods for comparing the effectiveness of different approaches. But your code is probably better off using - [`Sampler::to_ansi`](https://apparebit.github.io/prettypretty/prettypretty/struct.Sampler.html#method.to_ansi), + [`Translator::to_ansi`](https://apparebit.github.io/prettypretty/prettypretty/struct.Translator.html#method.to_ansi), which automatically picks `to_ansi_hue_lightness` or `to_closest_ansi`. In any case, I strongly recommend avoiding `to_ansi_rgb`. It only exists to evaluate the approach taken by the popular JavaScript library [Chalk](https://github.com/chalk/chalk) and reliably produces subpar results. Ironically, Chalk's tagline is "Terminal string styling done right." - 3. [`Sampler::cap`](https://apparebit.github.io/prettypretty/prettypretty/struct.Sampler.html#method.cap) + 3. [`Translator::cap`](https://apparebit.github.io/prettypretty/prettypretty/struct.Translator.html#method.cap) tanslates terminal colors to terminal colors. Under the hood, it may very well translate a terminal color to a high-resolution color and then match - against that color to produce a terminal color again. This is the method to - use for adjusting terminal colors to the runtime environment and user - preferences, which can be concisely expressed by the [`Fidelity`] level. - 4. [`Sampler::is_dark_theme`]() determines whether the color theme used by this - sampler instance is a dark theme. + against that color to produce a terminal color again. Use this method to + adjust terminal colors to the runtime environment and user preferences, + which can be concisely expressed by a [`Fidelity`] level. + 4. [`Translator::is_dark_theme`]() determines whether the color theme used by this + translator instance is a dark theme. -[`Sampler`] eagerly creates the necessary tables with colors for brute force and +[`Translator`] eagerly creates the necessary tables with colors for brute force and hue-lightness search in the constructor. Altogether, an instance of this struct owns 306 colors, which take up 7,160 bytes on macOS. As long as the terminal -color theme doesn't change, a sampler need not be regenerated. That also means +color theme doesn't change, a translator need not be regenerated. That also means that it can be used concurrently without locking—as long as threads have their own references. -## Sampler Samples +## Translator The example code below illustrates the use of each major entry point besides `to_closest_8bit`, which isn't that different from `to_ansi`: ```rust # extern crate prettypretty; -# use prettypretty::{AnsiColor, Color, ColorFormatError, Sampler, VGA_COLORS}; +# use prettypretty::{AnsiColor, Color, ColorFormatError, Translator, VGA_COLORS}; # use prettypretty::{OkVersion, TrueColor, Fidelity, EmbeddedRgb}; # use std::str::FromStr; let red = &VGA_COLORS[AnsiColor::BrightRed as usize + 2]; assert_eq!(red, &Color::srgb(1.0, 0.333333333333333, 0.333333333333333)); -let sampler = Sampler::new(OkVersion::Revised, VGA_COLORS.clone()); -let also_red = &sampler.resolve(AnsiColor::BrightRed); +let translator = Translator::new(OkVersion::Revised, VGA_COLORS.clone()); +let also_red = &translator.resolve(AnsiColor::BrightRed); assert_eq!(red, also_red); -let black = sampler.to_ansi(&Color::srgb(0.15, 0.15, 0.15)); +let black = translator.to_ansi(&Color::srgb(0.15, 0.15, 0.15)); assert_eq!(black, AnsiColor::Black); -let maroon = sampler.cap(TrueColor::new(148, 23, 81), Fidelity::EightBit); +let maroon = translator.cap(TrueColor::new(148, 23, 81), Fidelity::EightBit); assert_eq!(maroon, Some(EmbeddedRgb::new(2,0,1).unwrap().into())); # Ok::<(), ColorFormatError>(()) ``` @@ -206,19 +207,19 @@ assert_eq!(maroon, Some(EmbeddedRgb::new(2,0,1).unwrap().into())); The Python version is a close match: ```python -~from prettypretty.color import AnsiColor, Color, OkVersion, Sampler, Fidelity +~from prettypretty.color import AnsiColor, Color, OkVersion, Translator, Fidelity ~from prettypretty.theme import VGA red = VGA[AnsiColor.BrightRed.to_8bit() + 2] assert red == Color.srgb(1.0, 0.333333333333333, 0.333333333333333) -sampler = Sampler(OkVersion.Revised, VGA) -also_red = sampler.resolve(AnsiColor.BrightRed) +translator = Translator(OkVersion.Revised, VGA) +also_red = translator.resolve(AnsiColor.BrightRed) assert red == also_red -black = sampler.to_ansi(Color.srgb(0.15, 0.15, 0.15)) +black = translator.to_ansi(Color.srgb(0.15, 0.15, 0.15)) assert black == AnsiColor.Black -maroon = sampler.cap(TrueColor(148, 23, 81), Fidelity.EightBit) +maroon = translator.cap(TrueColor(148, 23, 81), Fidelity.EightBit) assert maroon == TerminalColor.Rgb6(EmbeddedRgb(2, 0, 1)) ```
diff --git a/docs/src/overview/summary.md b/docs/src/overview/summary.md index 86774d3..7739a8f 100644 --- a/docs/src/overview/summary.md +++ b/docs/src/overview/summary.md @@ -21,10 +21,10 @@ In more detail: lossless and partial conversions between color representations including, for example, conversion from EmbeddedRgb to `u8` index values as well true, terminal, and high-resolution colors. - * [`Sampler`] performs the more difficult translation from ANSI to - high-resolution colors, from high-resolution to 8-bit or ANSI colors, - and the downgrading of terminal colors based on terminal capabilities - and user preferences. + * [`Translator`] performs the more difficult translation from ANSI to + high-resolution colors, from high-resolution to 8-bit or ANSI colors, and + the downgrading of terminal colors based on terminal capabilities and user + preferences. {{#include ../links.md}} diff --git a/prettypretty/color.pyi b/prettypretty/color.pyi index 76c141b..0eef03b 100644 --- a/prettypretty/color.pyi +++ b/prettypretty/color.pyi @@ -356,38 +356,34 @@ class ThemeEntryIterator: def __next__(self) -> None | ThemeEntry: ... -class Sampler: - """A color sampler for translating between terminal and high-resolution colors.""" +class Translator: + """A class for translating between terminal and high-resolution colors.""" @staticmethod def theme_entries() -> Iterator[ThemeEntry]: ... def __new__(cls, version: OkVersion, theme_colors: Sequence[Color]) -> Self: ... def __repr__(self) -> str: ... - """Interrogate color theme.""" + # Interrogate the color theme def is_dark_theme(self) -> bool: ... - """Translate to high-resolution colors.""" + # Translate terminal to high-resolution colors def resolve( self, color: TerminalColor|DefaultColor|AnsiColor|EmbeddedRgb|GrayGradient|TrueColor|int, ) -> Color: ... - """Translate to ANSI colors.""" - def to_ansi(self, color: Color) -> Color: - """ - Translate high-resolution to ANSI colors using the best available - algorithm. - """ + # Translate high-resolution to ANSI colors + def to_ansi(self, color: Color) -> Color: ... def supports_hue_lightness(self) -> bool: ... def to_ansi_hue_lightness(self, color: Color) -> None | AnsiColor: ... def to_closest_ansi(self, color: Color) -> AnsiColor: ... def to_ansi_rgb(self, color: Color) -> AnsiColor: ... - """Translate to 8-bit colors.""" + # Translate high-resolution to 8-bit colors def to_closest_8bit(self, color: Color) -> TerminalColor: ... def to_closest_8bit_with_ansi(self, color: Color) -> TerminalColor: ... - """Cap terminal colors.""" + # Cap terminal colors def cap( self, color: TerminalColor|DefaultColor|AnsiColor|EmbeddedRgb|GrayGradient|TrueColor|int, diff --git a/prettypretty/darkmode.py b/prettypretty/darkmode.py index 4086358..c758a65 100644 --- a/prettypretty/darkmode.py +++ b/prettypretty/darkmode.py @@ -10,12 +10,13 @@ def is_dark_mode() -> None | bool: ``True`` for dark mode, ``False`` for light mode, and ``None`` if the mode could not be determined. - The implementation builds on answers to [this StackOverflow - question](https://stackoverflow.com/questions/65294987/detect-os-dark-mode-in-python) - and [the darkdetect package](https://github.com/albertosottile/darkdetect). - The latter seems both over- and under-engineered. In contrast, this module - provides the one interesting bit, whether the system is in dark mode, if - available and nothing else. + The implementation builds on answers to `this StackOverflow question + `_ + and `the darkdetect package + `_. The latter seems both + over- and under-engineered. In contrast, this module provides the one + interesting bit, whether the system is in dark mode, if available and + nothing else. """ try: if sys.platform == "darwin": diff --git a/prettypretty/grid.py b/prettypretty/grid.py index 5b466fe..d3c24a3 100644 --- a/prettypretty/grid.py +++ b/prettypretty/grid.py @@ -8,7 +8,7 @@ from typing import cast, Literal from .color import AnsiColor, Color, EmbeddedRgb, Fidelity, Layer, TrueColor -from .theme import MACOS_TERMINAL, VGA, XTERM, current_sampler +from .theme import MACOS_TERMINAL, VGA, XTERM, current_translator from .terminal import Terminal @@ -145,8 +145,11 @@ def write_color_cube( label: determines whether boxes are labelled with their color components """ - sampler = current_sampler() - if not sampler.supports_hue_lightness() and strategy is AnsiConversion.HueLightness: + translator = current_translator() + if ( + not translator.supports_hue_lightness() + and strategy is AnsiConversion.HueLightness + ): show_error( term, "Terminal theme violates requirements of hue/lightness algorithm. " @@ -155,7 +158,7 @@ def write_color_cube( return theme_colors: list[TrueColor] = [ - TrueColor.from_color(sampler.resolve(i)) for i in range(16) + TrueColor.from_color(translator.resolve(i)) for i in range(16) ] def closest_theme_color(color: TrueColor) -> int: @@ -199,20 +202,20 @@ def closest_theme_color(color: TrueColor) -> int: if strategy == AnsiConversion.NoConversion: eight_bit = embedded.to_8bit() elif strategy == AnsiConversion.HueLightness: - maybe_value = sampler.to_ansi_hue_lightness(source) + maybe_value = translator.to_ansi_hue_lightness(source) if maybe_value is None: raise ValueError('hue/lightness reduction unavailable') eight_bit = maybe_value.to_8bit() elif strategy == AnsiConversion.OklabDistance: - eight_bit = sampler.to_closest_ansi(source).to_8bit() + eight_bit = translator.to_closest_ansi(source).to_8bit() elif strategy == AnsiConversion.RgbDistance: eight_bit = closest_theme_color(TrueColor(*embedded.to_24bit())) elif strategy == AnsiConversion.RgbRounding: - eight_bit = sampler.to_ansi_rgb(source).to_8bit() + eight_bit = translator.to_ansi_rgb(source).to_8bit() else: raise ValueError(f'invalid strategy "{strategy}"') - target = sampler.resolve(eight_bit) + target = translator.resolve(eight_bit) # Pick black or white for target, not source color. if layer is Layer.Background: @@ -249,11 +252,11 @@ def write_hires_slice( + label ) - sampler = current_sampler() + translator = current_translator() def emit_box(r: int, g: int, b: int) -> None: if eight_bit_only: - color = sampler.to_closest_8bit(Color.from_24bit(r, g, b)).try_to_8bit(), + color = translator.to_closest_8bit(Color.from_24bit(r, g, b)).try_to_8bit(), else: color = r, g, b @@ -289,10 +292,10 @@ def write_theme_test(term: Terminal, show_label: bool = True): frame = FramedBoxes(term, 2, max_width=40) frame.top('Actual vs Claimed Color') - sampler = current_sampler() + translator = current_translator() for index in range(16): - color = sampler.resolve(index) + color = translator.resolve(index) fg = 16 if color.use_black_text() else 231 bg = color.to_24bit() diff --git a/prettypretty/plot.py b/prettypretty/plot.py index 17fea45..81eb480 100644 --- a/prettypretty/plot.py +++ b/prettypretty/plot.py @@ -20,7 +20,7 @@ from .terminal import Terminal from .color import close_enough, Color, ColorSpace, ThemeEntry -from .theme import current_sampler, VGA +from .theme import current_translator, VGA def create_parser() -> argparse.ArgumentParser: @@ -511,11 +511,11 @@ def main() -> None: if not options.theme: terminal_id = term.request_terminal_identity() - sampler = current_sampler() + translator = current_translator() for index in [0, 1, 3, 2, 6, 4, 5, 7]: - color = sampler.resolve(index) + color = translator.resolve(index) plotter.add(ThemeEntry.try_from_index(index + 2).name(), color) - color = sampler.resolve(index + 8) + color = translator.resolve(index + 8) plotter.add(ThemeEntry.try_from_index(index + 8 + 2).name(), color) for color in [Color.parse("#" + c) for c in cast(list[str], options.colors) or []]: diff --git a/prettypretty/progress.py b/prettypretty/progress.py index eec7427..c4c2bff 100644 --- a/prettypretty/progress.py +++ b/prettypretty/progress.py @@ -6,7 +6,7 @@ from prettypretty.color import Color from prettypretty.style import rich, RichText, Style from prettypretty.terminal import Terminal -from prettypretty.theme import current_sampler +from prettypretty.theme import current_translator def create_parser() -> argparse.ArgumentParser: @@ -72,7 +72,7 @@ def main() -> None: .hidden_cursor() .scoped_style() ) as term: - style = DARK_MODE_BAR if current_sampler().is_dark_theme() else LIGHT_MODE_BAR + style = DARK_MODE_BAR if current_translator().is_dark_theme() else LIGHT_MODE_BAR style = style.prepare(term.fidelity) fg = style.foreground diff --git a/prettypretty/style.py b/prettypretty/style.py index 2d96660..873f004 100644 --- a/prettypretty/style.py +++ b/prettypretty/style.py @@ -22,7 +22,7 @@ from .ansi import Ansi from .color import Color, DefaultColor, Fidelity, Layer, TerminalColor -from .theme import current_sampler +from .theme import current_translator class TextAttribute(enum.Enum): @@ -339,17 +339,17 @@ def prepare(self, fidelity: Fidelity) -> Self: # Erase all styles return type(self)() - sampler = current_sampler() + translator = current_translator() if self.foreground is None: fg = None else: - fg = sampler.cap(self.foreground, fidelity) + fg = translator.cap(self.foreground, fidelity) if self.background is None: bg = None else: - bg = sampler.cap(self.background, fidelity) + bg = translator.cap(self.background, fidelity) return dataclasses.replace(self, foreground=fg, background=bg) diff --git a/prettypretty/terminal.py b/prettypretty/terminal.py index c62f370..6db0937 100644 --- a/prettypretty/terminal.py +++ b/prettypretty/terminal.py @@ -24,7 +24,7 @@ from .ansi import Ansi, RawAnsi from .color import Color, Fidelity, Layer, TerminalColor, ThemeEntry from .color_types import IntoTerminalColor -from .theme import new_theme, current_sampler +from .theme import new_theme, current_translator from .ident import identify_terminal, normalize_terminal_name from .style import RichText, RichTextElement @@ -1248,8 +1248,8 @@ def fg( elif isinstance(c1, Color): c1 = TerminalColor.from_color(c1) - sampler = current_sampler() - color = sampler.cap(c1, self._fidelity) + translator = current_translator() + color = translator.cap(c1, self._fidelity) if color is not None: self.write_control( Ansi.CSI, @@ -1287,8 +1287,8 @@ def bg( elif isinstance(c1, Color): c1 = TerminalColor.from_color(c1) - sampler = current_sampler() - color = sampler.cap(c1, self._fidelity) + translator = current_translator() + color = translator.cap(c1, self._fidelity) if color is not None: self.write_control( Ansi.CSI, diff --git a/prettypretty/theme.py b/prettypretty/theme.py index 4e0c9a6..d001a6a 100644 --- a/prettypretty/theme.py +++ b/prettypretty/theme.py @@ -6,7 +6,7 @@ from collections.abc import Iterator from contextlib import contextmanager -from .color import Color, Sampler, OkVersion +from .color import Color, Translator, OkVersion MACOS_TERMINAL = ( Color.parse("#000000"), @@ -74,24 +74,24 @@ ) -_current_sampler: list[Sampler] = [Sampler(OkVersion.Revised, VGA)] +_current_translator: list[Translator] = [Translator(OkVersion.Revised, VGA)] @contextmanager -def new_theme(theme_colors: list[Color]) -> Iterator[Sampler]: +def new_theme(theme_colors: list[Color]) -> Iterator[Translator]: """ Create a new context manager to make the theme colors the current theme colors. This function expects exactly 18 colors. """ - _current_sampler.append(Sampler(OkVersion.Revised, theme_colors)) + _current_translator.append(Translator(OkVersion.Revised, theme_colors)) try: - yield _current_sampler[-1] + yield _current_translator[-1] finally: - _current_sampler.pop() + _current_translator.pop() -def current_sampler() -> Sampler: +def current_translator() -> Translator: """ - Access the current sampler. + Access the current translator. - The sampler is automatically updated when using different theme colors. + The translator is automatically updated when using different theme colors. """ - return _current_sampler[-1] + return _current_translator[-1] diff --git a/src/lib.rs b/src/lib.rs index 6d943c4..d40769a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,7 @@ interfaces as well as Python ingration, with the `pyffi` feature flag enabled.** //! lossless and partial conversions between color representations //! including, for example, conversion from EmbeddedRgb to `u8` index values //! as well true, terminal, and high-resolution colors. -//! * [`Sampler`] performs the more difficult translation from ANSI to +//! * [`Translator`] performs the more difficult translation from ANSI to //! high-resolution colors, from high-resolution to 8-bit or ANSI colors, //! and the downgrading of terminal colors based on terminal capabilities //! and user preferences. @@ -83,7 +83,7 @@ interfaces as well as Python ingration, with the `pyffi` feature flag enabled.** //! include its own facilities for styled text or terminal I/O. Instead, it is //! designed to be a lightweight addition that focuses on color management only. //! To use this crate, an application should create its own instance of -//! [`Sampler`] with the colors of the current terminal theme. +//! [`Translator`] with the colors of the current terminal theme. //! //! An application should use the ANSI escape sequences //! ```text @@ -153,7 +153,7 @@ pub use object::{Color, Interpolator, OkVersion}; pub use term_color::{ AnsiColor, DefaultColor, EmbeddedRgb, Fidelity, GrayGradient, Layer, TerminalColor, TrueColor, }; -pub use translation::{Sampler, Theme, ThemeEntry, ThemeEntryIterator, VGA_COLORS}; +pub use translation::{Theme, ThemeEntry, ThemeEntryIterator, Translator, VGA_COLORS}; #[cfg(feature = "pyffi")] use pyo3::prelude::*; @@ -175,7 +175,7 @@ pub fn color(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; - m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/src/object.rs b/src/object.rs index 9154f0a..8328ca5 100644 --- a/src/object.rs +++ b/src/object.rs @@ -1090,7 +1090,7 @@ impl Color { /// /// Because it is generic, this method is available in Rust only. A /// specialized version is available in Python through - /// [`Sampler`](crate::Sampler). + /// [`Translator`](crate::Translator). /// /// # Examples /// @@ -1138,7 +1138,7 @@ impl Color { /// /// Because it is generic, this method is available in Rust only. A /// specialized version is available in Python through - /// [`Sampler`](crate::Sampler). + /// [`Translator`](crate::Translator). pub fn find_closest<'c, C, F>( &self, candidates: C, diff --git a/src/style.rs b/src/style.rs index 1cb0664..e026492 100644 --- a/src/style.rs +++ b/src/style.rs @@ -2,6 +2,178 @@ use crate::{Layer, TerminalColor}; +// mod bit { +// pub(super) type Flags = u16; + +// pub(super) const NONE: Flags = 0; + +// pub(super) const REGULAR: Flags = 1 << 1; +// pub(super) const BOLD: Flags = 1 << 2; +// pub(super) const THIN: Flags = 1 << 3; +// pub(super) const CLEAR_WEIGHT: Flags = !(REGULAR | BOLD | THIN); + +// pub(super) const UPRIGHT: Flags = 1 << 4; +// pub(super) const ITALIC: Flags = 1 << 5; +// pub(super) const CLEAR_SLANT: Flags = !(UPRIGHT | ITALIC); + +// pub(super) const NOT_UNDERLINED: Flags = 1 << 6; +// pub(super) const UNDERLINED: Flags = 1 << 7; +// pub(super) const CLEAR_UNDERLINE: Flags = !(NOT_UNDERLINED | UNDERLINED); + +// pub(super) const NOT_BLINKING: Flags = 1 << 8; +// pub(super) const BLINKING: Flags = 1 << 9; +// pub(super) const CLEAR_BLINKING: Flags = !(NOT_BLINKING | BLINKING); + +// pub(super) const NOT_REVERSED: Flags = 1 << 10; +// pub(super) const REVERSED: Flags = 1 << 11; + +// pub(super) const NOT_HIDDEN: Flags = 1 << 12; +// pub(super) const HIDDEN: Flags = 1 << 13; + +// pub(super) const NOT_STRICKEN: Flags = 1 << 14; +// pub(super) const STRICKEN: Flags = 1 << 15; + +// pub(super) const ALL_NON_DEFAULT: [Flags; 8] = [ +// BOLD, THIN, ITALIC, UNDERLINED, BLINKING, REVERSED, HIDDEN, STRICKEN, +// ]; + +// pub(super) const SIMPLE_NON_DEFAULT: [Flags; 6] = [ +// ITALIC, UNDERLINED, BLINKING, REVERSED, HIDDEN, STRICKEN, +// ]; + +// pub(super) fn negate(flag: Flags) -> Flags { +// match flag { +// BOLD | THIN => REGULAR, +// ITALIC => UPRIGHT, +// UNDERLINED => NOT_UNDERLINED, +// BLINKING => NOT_BLINKING, +// REVERSED => NOT_REVERSED, +// HIDDEN => NOT_HIDDEN, +// STRICKEN => NOT_STRICKEN, +// _ => 0, +// } +// } +// } + +// struct Attr(bit::Flags); + +// impl Attr { +// pub fn new() -> Self { +// Self(0) +// } + +// #[inline] +// fn test(&self, flags: bit::Flags) -> bool { +// self.0 & flags != 0 +// } + +// pub fn bold(self) -> Self { +// Self(self.0 & bit::CLEAR_WEIGHT | bit::BOLD) +// } + +// pub fn thin(self) -> Self { +// Self(self.0 & bit::CLEAR_WEIGHT | bit::THIN) +// } + +// pub fn italic(self) -> Self { +// Self(self.0 & bit::CLEAR_SLANT | bit::ITALIC) +// } + +// pub fn blink(self) -> Self { +// Self(self.0 & bit::CLEAR_BLINKING | bit::BLINKING) +// } + +// pub fn negate(self) -> Self { +// let mut result: bit::Flags = 0; + +// for flag in bit::ALL_NON_DEFAULT { +// if self.test(flag) { +// result |= bit::negate(flag); +// } +// } + +// Self(result) +// } + +// pub fn subtract_flag(&self, other: &Self) -> Self { +// let mut result: bit::Flags = 0; + +// if self.test(bit::BOLD) { +// if !other.test(bit::BOLD) { +// result |= bit::BOLD; +// } +// } else if self.test(bit::THIN) { +// if !other.test(bit::THIN) { +// result |= bit::THIN; +// } +// } else if other.test(bit::BOLD | bit::THIN) { +// result |= bit::REGULAR; +// } + +// for flag in bit::SIMPLE_NON_DEFAULT { +// if self.test(flag) { +// if !other.test(flag) { +// result |= flag; +// } +// } else if other.test(flag) { +// result |= bit::negate(flag) +// } +// } + +// Self(result) +// } + +// pub fn sgr_parameters(&self) -> Vec { +// let mut parameters = Vec::new(); + +// if self.0 & bit::REGULAR != 0 { +// parameters.push(22); +// } else if self.0 & bit::BOLD != 0 { +// parameters.push(1); +// } else if self.0 & bit::ITALIC != 0 { +// parameters.push(2); +// } + +// if self.0 & bit::UPRIGHT != 0 { +// parameters.push(23); +// } else if self.0 & bit::ITALIC != 0 { +// parameters.push(3); +// } + +// if self.0 & bit::NOT_UNDERLINED != 0 { +// parameters.push(24); +// } else if self.0 & bit::UNDERLINED != 0 { +// parameters.push(4); +// } + +// if self.0 & bit::NOT_BLINKING != 0 { +// parameters.push(25); +// } else if self.0 & bit::BLINKING != 0 { +// parameters.push(5); +// } + +// if self.0 & bit::NOT_REVERSED != 0 { +// parameters.push(27); +// } else if self.0 & bit::REVERSED != 0 { +// parameters.push(7); +// } + +// if self.0 & bit::NOT_HIDDEN != 0 { +// parameters.push(28); +// } else if self.0 & bit::BLINKING != 0 { +// parameters.push(8); +// } + +// if self.0 & bit::NOT_STRICKEN != 0 { +// parameters.push(29); +// } else if self.0 & bit::STRICKEN != 0 { +// parameters.push(9); +// } + +// parameters +// } +// } + pub trait TextAttribute: Copy + Default + PartialEq { /// Determine whether the variant is the default. /// diff --git a/src/term_color.rs b/src/term_color.rs index e28d43b..0b4c42a 100644 --- a/src/term_color.rs +++ b/src/term_color.rs @@ -65,7 +65,7 @@ impl From for TerminalColor { )] /// Since ANSI colors have no intrinsic color values, conversion from/to /// high-resolution colors requires additional machinery, as provided by -/// [`Sampler`](crate::Sampler). +/// [`Translator`](crate::Translator). /// /// The ANSI colors are ordered because they are ordered as theme colors and as /// indexed colors. @@ -982,7 +982,8 @@ impl TerminalColor { } } - /// Convert this terminal color to an 8-bit index color. + /// Convert this terminal color to an 8-bit index color. #[cfg(feature = "pyffi")] pub fn try_to_8bit(&self) -> PyResult { u8::try_from(*self).map_err(|_| { @@ -990,7 +991,7 @@ impl TerminalColor { }) } - /// Convert this terminal color to 24-bit. + /// Convert this terminal color to 24-bit. #[cfg(feature = "pyffi")] pub fn try_to_24bit(&self) -> PyResult<[u8; 3]> { <[u8; 3]>::try_from(*self).map_err(|_| { @@ -1087,6 +1088,14 @@ impl From for TerminalColor { } } +impl From<[u8; 3]> for TerminalColor { + fn from(value: [u8; 3]) -> Self { + Self::Rgb256 { + color: TrueColor(value), + } + } +} + impl From<&Color> for TerminalColor { /// Convert a high-resolution color to a terminal color. /// diff --git a/src/translation.rs b/src/translation.rs index a31e049..4ce4493 100644 --- a/src/translation.rs +++ b/src/translation.rs @@ -383,7 +383,7 @@ impl HueLightnessTable { } // ==================================================================================================================== -// Sampler +// Translator // ==================================================================================================================== // Convert the constituent terminal colors to their wrapped versions. @@ -401,18 +401,18 @@ fn into_terminal_color(obj: &Bound<'_, PyAny>) -> PyResult { .or_else(|_| obj.extract::().map(|c| c.into())) } -/// A color sampler. +/// A color translator. /// /// Instances of this struct translate between [`TerminalColor`] and [`Color`] /// and maintain the state for doing so efficiently. The [user /// guide](https://apparebit.github.io/prettypretty/overview/integration.html) /// includes a detailed discussion of challenges posed by translation, solution -/// approaches, and sampler's interface. +/// approaches, and translator's interface. /// -/// Since a sampler incorporates theme colors, an application should regenerate -/// its sampler if the current theme changes. +/// Since a translator incorporates theme colors, an application should +/// regenerate its translator if the current theme changes. #[cfg_attr(feature = "pyffi", pyclass)] -pub struct Sampler { +pub struct Translator { /// The theme colors. For converting *to* high-resolution colors. theme: Theme, /// The table for matching by hue and lightness. @@ -459,8 +459,8 @@ fn eight_bit_coordinates(space: ColorSpace, theme: &Theme) -> [[Float; 3]; 256] #[cfg(feature = "pyffi")] #[pymethods] -impl Sampler { - /// Create a new sampler for the given Oklab version and theme colors. +impl Translator { + /// Create a new translator for the given Oklab version and theme colors. #[new] pub fn new(version: OkVersion, theme: Theme) -> Self { let hue_lightness_table = HueLightnessTable::new(&theme); @@ -487,7 +487,7 @@ impl Sampler { ThemeEntryIterator::new() } - /// Determine whether this sampler's color theme is a dark theme. + /// Determine whether this translator's color theme is a dark theme. pub fn is_dark_theme(&self) -> bool { let yf = self.theme[0].to(ColorSpace::Xyz)[1]; let yb = self.theme[1].to(ColorSpace::Xyz)[1]; @@ -504,15 +504,15 @@ impl Sampler { /// # Examples /// /// ``` - /// # use prettypretty::{AnsiColor, Color, DefaultColor, OkVersion, Sampler, TrueColor}; + /// # use prettypretty::{AnsiColor, Color, DefaultColor, OkVersion, Translator, TrueColor}; /// # use prettypretty::{VGA_COLORS}; - /// let black = sampler.resolve(DefaultColor::Foreground); + /// let black = Translator.resolve(DefaultColor::Foreground); /// assert_eq!(black, Color::srgb(0.0, 0.0, 0.0)); /// - /// let blue = sampler.resolve(AnsiColor::Blue); + /// let blue = Translator.resolve(AnsiColor::Blue); /// assert_eq!(blue, Color::srgb(0.0, 0.0, 0.666666666666667)); /// - /// let maroon = sampler.resolve(TrueColor::new(148, 23, 81)); + /// let maroon = Translator.resolve(TrueColor::new(148, 23, 81)); /// assert_eq!(maroon, Color::srgb( /// 0.5803921568627451, 0.09019607843137255, 0.3176470588235294 /// )); @@ -532,17 +532,17 @@ impl Sampler { /// Convert the high-resolution color into an ANSI color. /// /// If the current theme meets the requirements for hue/lightness search, - /// this method forwards to [`Sampler::to_ansi_hue_lightness`]. Otherwise, - /// it falls back on [`Sampler::to_closest_ansi`]. Use - /// [`Sampler::supports_hue_lightness`] to test whether the current theme + /// this method forwards to [`Translator::to_ansi_hue_lightness`]. + /// Otherwise, it falls back on [`Translator::to_closest_ansi`]. Use + /// [`Translator::supports_hue_lightness`] to test whether the current theme /// supports hue-lightness search. pub fn to_ansi(&self, color: &Color) -> AnsiColor { self.to_ansi_hue_lightness(color) .unwrap_or_else(|| self.to_closest_ansi(color)) } - /// Determine whether this sampler instance supports color translation with - /// the hue/lightness search algorithm. + /// Determine whether this translator instance supports color translation + /// with the hue/lightness search algorithm. pub fn supports_hue_lightness(&self) -> bool { self.hue_lightness_table.is_some() } @@ -563,15 +563,15 @@ impl Sampler { /// increasing hue magnitude. Note that this does allow hues to be /// arbitrarily shifted along the circle. Furthermore, it does not prescribe /// an order for regular and bright versions of the same abstract ANSI - /// color. If the theme colors passed to this sampler's constructor did not - /// meet this requirement, this method returns `None`. + /// color. If the theme colors passed to this translator's constructor did + /// not meet this requirement, this method returns `None`. /// /// # Examples /// - /// The documentation for [`Sampler::to_closest_ansi`] gives the example of - /// two colors that yield subpar results with an exhaustive search for the - /// closest color and then sketches an alternative approach that searches - /// for the closest hue. + /// The documentation for [`Translator::to_closest_ansi`] gives the example + /// of two colors that yield subpar results with an exhaustive search for + /// the closest color and then sketches an alternative approach that + /// searches for the closest hue. /// /// The algorithm implemented by this method goes well beyond that sketch by /// not only leveraging color pragmatics (i.e., their coordinates) but also @@ -580,26 +580,26 @@ impl Sampler { /// one out of two colors with the closest lightness. /// /// As this example illustrates, that strategy works well for the light - /// orange colors from [`Sampler::to_closest_ansi`]. They both match the + /// orange colors from [`Translator::to_closest_ansi`]. They both match the /// yellow pair by hue and then bright yellow by lightness. Alas, it is no /// panacea because color themes may not observe the necessary semantic /// constraints. This method detects such cases and returns `None`. - /// [`Sampler::to_ansi`] instead automatically falls back onto searching for - /// the closest color. + /// [`Translator::to_ansi`] instead automatically falls back onto searching + /// for the closest color. /// /// ``` - /// # use prettypretty::{Color, ColorFormatError, ColorSpace, Sampler}; + /// # use prettypretty::{Color, ColorFormatError, ColorSpace, Translator}; /// # use prettypretty::{VGA_COLORS, OkVersion}; /// # use std::str::FromStr; - /// let sampler = Sampler::new( + /// let translator = Translator::new( /// OkVersion::Revised, VGA_COLORS.clone()); /// /// let orange1 = Color::from_str("#ffa563")?; - /// let ansi = sampler.to_ansi_hue_lightness(&orange1); + /// let ansi = translator.to_ansi_hue_lightness(&orange1); /// assert_eq!(ansi.unwrap(), AnsiColor::BrightYellow); /// /// let orange2 = Color::from_str("#ff9600")?; - /// let ansi = sampler.to_ansi_hue_lightness(&orange2); + /// let ansi = translator.to_ansi_hue_lightness(&orange2); /// assert_eq!(ansi.unwrap(), AnsiColor::BrightYellow); /// # Ok::<(), ColorFormatError>(()) /// ``` @@ -629,27 +629,27 @@ impl Sampler { /// (more or less) saturated color. /// /// ``` - /// # use prettypretty::{Color, ColorFormatError, ColorSpace, Sampler}; + /// # use prettypretty::{Color, ColorFormatError, ColorSpace, Translator}; /// # use prettypretty::{VGA_COLORS, OkVersion}; /// # use std::str::FromStr; - /// let original_sampler = Sampler::new( + /// let original_translator = Translator::new( /// OkVersion::Original, VGA_COLORS.clone()); /// /// let orange1 = Color::from_str("#ffa563")?; - /// let ansi = original_sampler.to_closest_ansi(&orange1); + /// let ansi = original_translator.to_closest_ansi(&orange1); /// assert_eq!(ansi, AnsiColor::White); /// /// let orange2 = Color::from_str("#ff9600")?; - /// let ansi = original_sampler.to_closest_ansi(&orange2); + /// let ansi = original_translator.to_closest_ansi(&orange2); /// assert_eq!(ansi, AnsiColor::BrightRed); /// // --------------------------------------------------------------------- - /// let revised_sampler = Sampler::new( + /// let revised_translator = Translator::new( /// OkVersion::Revised, VGA_COLORS.clone()); /// - /// let ansi = revised_sampler.to_closest_ansi(&orange1); + /// let ansi = revised_translator.to_closest_ansi(&orange1); /// assert_eq!(ansi, AnsiColor::White); /// - /// let ansi = revised_sampler.to_closest_ansi(&orange2); + /// let ansi = revised_translator.to_closest_ansi(&orange2); /// assert_eq!(ansi, AnsiColor::BrightRed); /// # Ok::<(), ColorFormatError>(()) /// ``` @@ -693,7 +693,7 @@ impl Sampler { /// more accurate, we'll be comparing colors in Oklrch. We start by /// preparing a list with the color values for the 16 extended ANSI colors /// in that color space. That, by the way, is pretty much what - /// [`Sampler::new`] does as well. + /// [`Translator::new`] does as well. /// ``` /// # use prettypretty::{AnsiColor, Color, ColorFormatError, ColorSpace, VGA_COLORS}; /// # use std::str::FromStr; @@ -751,7 +751,7 @@ impl Sampler { /// The hue-based comparison picks ANSI color 3, VGA's orange yellow, just /// as expected. It appears that our hue-based proof-of-concept works. /// However, a production-ready version does need to account for lightness, - /// too. The method to do so is [`Sampler::to_ansi_hue_lightness`]. + /// too. The method to do so is [`Translator::to_ansi_hue_lightness`]. pub fn to_closest_ansi(&self, color: &Color) -> AnsiColor { use crate::core::{delta_e_ok, find_closest}; @@ -790,17 +790,17 @@ impl Sampler { /// /// The example below converts every color of the RGB cube embedded in 8-bit /// colors to a high-resolution color in sRGB, which is validated by the - /// first two assertions, and then uses a sampler to convert that color back - /// to an embedded RGB color. The result is the original color, now wrapped - /// as a terminal color, which is validated by the third assertion. The - /// example demonstrates that the 216 colors in the embedded RGB cube still - /// are closest to themselves after conversion to Oklrch. + /// first two assertions, and then uses a translator to convert that color + /// back to an embedded RGB color. The result is the original color, now + /// wrapped as a terminal color, which is validated by the third assertion. + /// The example demonstrates that the 216 colors in the embedded RGB cube + /// still are closest to themselves after conversion to Oklrch. /// /// ``` /// # use prettypretty::{Color, ColorSpace, VGA_COLORS, TerminalColor, Float}; - /// # use prettypretty::{EmbeddedRgb, OutOfBoundsError, Sampler, OkVersion}; + /// # use prettypretty::{EmbeddedRgb, OutOfBoundsError, Translator, OkVersion}; /// # use prettypretty::assert_close_enough; - /// let sampler = Sampler::new(OkVersion::Revised, VGA_COLORS.clone()); + /// let translator = Translator::new(OkVersion::Revised, VGA_COLORS.clone()); /// /// for r in 0..5 { /// for g in 0..5 { @@ -816,7 +816,7 @@ impl Sampler { /// }; /// assert_close_enough!(color[0], c1); /// - /// let result = sampler.to_closest_8bit(&color); + /// let result = translator.to_closest_8bit(&color); /// assert_eq!(result, TerminalColor::Rgb6 { color: embedded }); /// } /// } @@ -886,8 +886,8 @@ impl Sampler { } #[cfg(not(feature = "pyffi"))] -impl Sampler { - /// Create a new sampler for the given Oklab version and theme colors. +impl Translator { + /// Create a new translator for the given Oklab version and theme colors. pub fn new(version: OkVersion, theme: Theme) -> Self { let hue_lightness_table = HueLightnessTable::new(&theme); let space = version.cartesian_space(); @@ -908,7 +908,7 @@ impl Sampler { ThemeEntryIterator::new() } - /// Determine whether this sampler's color theme is a dark theme. + /// Determine whether this translator's color theme is a dark theme. /// /// The Y component of a color in XYZ represents it luminance. This method /// exploits that property of XYZ and checks whether the default foreground @@ -929,16 +929,16 @@ impl Sampler { /// achieve the same effect. /// /// ``` - /// # use prettypretty::{AnsiColor, Color, DefaultColor, OkVersion, Sampler, TrueColor}; + /// # use prettypretty::{AnsiColor, Color, DefaultColor, OkVersion, Translator, TrueColor}; /// # use prettypretty::{VGA_COLORS}; - /// let sampler = Sampler::new(OkVersion::Revised, VGA_COLORS.clone()); - /// let blue = sampler.resolve(AnsiColor::Blue); + /// let translator = Translator::new(OkVersion::Revised, VGA_COLORS.clone()); + /// let blue = translator.resolve(AnsiColor::Blue); /// assert_eq!(blue, Color::srgb(0.0, 0.0, 0.666666666666667)); /// - /// let black = sampler.resolve(DefaultColor::Foreground); + /// let black = translator.resolve(DefaultColor::Foreground); /// assert_eq!(black, Color::srgb(0.0, 0.0, 0.0)); /// - /// let maroon = sampler.resolve(TrueColor::new(148, 23, 81)); + /// let maroon = translator.resolve(TrueColor::new(148, 23, 81)); /// assert_eq!(maroon, Color::srgb( /// 0.5803921568627451, 0.09019607843137255, 0.3176470588235294 /// )); @@ -955,17 +955,17 @@ impl Sampler { /// Convert the high-resolution color into an ANSI color. /// /// If the current theme meets the requirements for hue/lightness search, - /// this method forwards to [`Sampler::to_ansi_hue_lightness`]. Otherwise, - /// it falls back on [`Sampler::to_closest_ansi`]. Use - /// [`Sampler::supports_hue_lightness`] to test whether the current theme + /// this method forwards to [`Translator::to_ansi_hue_lightness`]. + /// Otherwise, it falls back on [`Translator::to_closest_ansi`]. Use + /// [`Translator::supports_hue_lightness`] to test whether the current theme /// supports hue-lightness search. pub fn to_ansi(&self, color: &Color) -> AnsiColor { self.to_ansi_hue_lightness(color) .unwrap_or_else(|| self.to_closest_ansi(color)) } - /// Determine whether this sampler instance supports color translation with - /// the hue/lightness search algorithm. + /// Determine whether this translator instance supports color translation + /// with the hue/lightness search algorithm. pub fn supports_hue_lightness(&self) -> bool { self.hue_lightness_table.is_some() } @@ -986,15 +986,15 @@ impl Sampler { /// increasing hue magnitude. Note that this does allow hues to be /// arbitrarily shifted along the circle. Furthermore, it does not prescribe /// an order for regular and bright versions of the same abstract ANSI - /// color. If the theme colors passed to this sampler's constructor did not - /// meet this requirement, this method returns `None`. + /// color. If the theme colors passed to this translator's constructor did + /// not meet this requirement, this method returns `None`. /// /// # Examples /// - /// The documentation for [`Sampler::to_closest_ansi`] gives the example of - /// two colors that yield subpar results with an exhaustive search for the - /// closest color and then sketches an alternative approach that searches - /// for the closest hue. + /// The documentation for [`Translator::to_closest_ansi`] gives the example + /// of two colors that yield subpar results with an exhaustive search for + /// the closest color and then sketches an alternative approach that + /// searches for the closest hue. /// /// The algorithm implemented by this method goes well beyond that sketch by /// not only leveraging color pragmatics (i.e., their coordinates) but also @@ -1003,26 +1003,26 @@ impl Sampler { /// one out of two colors with the closest lightness. /// /// As this example illustrates, that strategy works well for the light - /// orange colors from [`Sampler::to_closest_ansi`]. They both match the + /// orange colors from [`Translator::to_closest_ansi`]. They both match the /// yellow pair by hue and then bright yellow by lightness. Alas, it is no /// panacea because color themes may not observe the necessary semantic /// constraints. This method detects such cases and returns `None`. - /// [`Sampler::to_ansi`] instead automatically falls back onto searching for - /// the closest color. + /// [`Translator::to_ansi`] instead automatically falls back onto searching + /// for the closest color. /// /// ``` - /// # use prettypretty::{Color, ColorFormatError, ColorSpace, Sampler}; + /// # use prettypretty::{Color, ColorFormatError, ColorSpace, Translator}; /// # use prettypretty::{VGA_COLORS, OkVersion, AnsiColor}; /// # use std::str::FromStr; - /// let sampler = Sampler::new( + /// let translator = Translator::new( /// OkVersion::Revised, VGA_COLORS.clone()); /// /// let orange1 = Color::from_str("#ffa563")?; - /// let ansi = sampler.to_ansi_hue_lightness(&orange1); + /// let ansi = translator.to_ansi_hue_lightness(&orange1); /// assert_eq!(ansi.unwrap(), AnsiColor::BrightYellow); /// /// let orange2 = Color::from_str("#ff9600")?; - /// let ansi = sampler.to_ansi_hue_lightness(&orange2); + /// let ansi = translator.to_ansi_hue_lightness(&orange2); /// assert_eq!(ansi.unwrap(), AnsiColor::BrightYellow); /// # Ok::<(), ColorFormatError>(()) /// ``` @@ -1052,27 +1052,27 @@ impl Sampler { /// (more or less) saturated color. /// /// ``` - /// # use prettypretty::{Color, ColorFormatError, ColorSpace, Sampler}; + /// # use prettypretty::{Color, ColorFormatError, ColorSpace, Translator}; /// # use prettypretty::{VGA_COLORS, OkVersion, AnsiColor}; /// # use std::str::FromStr; - /// let original_sampler = Sampler::new( + /// let original_translator = Translator::new( /// OkVersion::Original, VGA_COLORS.clone()); /// /// let orange1 = Color::from_str("#ffa563")?; - /// let ansi = original_sampler.to_closest_ansi(&orange1); + /// let ansi = original_translator.to_closest_ansi(&orange1); /// assert_eq!(ansi, AnsiColor::White); /// /// let orange2 = Color::from_str("#ff9600")?; - /// let ansi = original_sampler.to_closest_ansi(&orange2); + /// let ansi = original_translator.to_closest_ansi(&orange2); /// assert_eq!(ansi, AnsiColor::BrightRed); /// // --------------------------------------------------------------------- - /// let revised_sampler = Sampler::new( + /// let revised_translator = Translator::new( /// OkVersion::Revised, VGA_COLORS.clone()); /// - /// let ansi = revised_sampler.to_closest_ansi(&orange1); + /// let ansi = revised_translator.to_closest_ansi(&orange1); /// assert_eq!(ansi, AnsiColor::White); /// - /// let ansi = revised_sampler.to_closest_ansi(&orange2); + /// let ansi = revised_translator.to_closest_ansi(&orange2); /// assert_eq!(ansi, AnsiColor::BrightRed); /// # Ok::<(), ColorFormatError>(()) /// ``` @@ -1116,7 +1116,7 @@ impl Sampler { /// more accurate, we'll be comparing colors in Oklrch. We start by /// preparing a list with the color values for the 16 extended ANSI colors /// in that color space. That, by the way, is pretty much what - /// [`Sampler::new`] does as well. + /// [`Translator::new`] does as well. /// ``` /// # use prettypretty::{AnsiColor, Color, ColorFormatError, ColorSpace, VGA_COLORS}; /// # use std::str::FromStr; @@ -1174,7 +1174,7 @@ impl Sampler { /// The hue-based comparison picks ANSI color 3, VGA's orange yellow, just /// as expected. It appears that our hue-based proof-of-concept works. /// However, a production-ready version does need to account for lightness, - /// too. The method to do so is [`Sampler::to_ansi_hue_lightness`]. + /// too. The method to do so is [`Translator::to_ansi_hue_lightness`]. pub fn to_closest_ansi(&self, color: &Color) -> AnsiColor { use crate::core::{delta_e_ok, find_closest}; @@ -1217,17 +1217,17 @@ impl Sampler { /// /// The example below converts every color of the RGB cube embedded in 8-bit /// colors to a high-resolution color in sRGB, which is validated by the - /// first two assertions, and then uses a sampler to convert that color back - /// to an embedded RGB color. The result is the original color, now wrapped - /// as a terminal color, which is validated by the third assertion. The - /// example demonstrates that the 216 colors in the embedded RGB cube still - /// are closest to themselves after conversion to Oklrch. + /// first two assertions, and then uses a translator to convert that color + /// back to an embedded RGB color. The result is the original color, now + /// wrapped as a terminal color, which is validated by the third assertion. + /// The example demonstrates that the 216 colors in the embedded RGB cube + /// still are closest to themselves after conversion to Oklrch. /// /// ``` /// # use prettypretty::{Color, ColorSpace, VGA_COLORS, TerminalColor, Float}; - /// # use prettypretty::{EmbeddedRgb, OutOfBoundsError, Sampler, OkVersion}; + /// # use prettypretty::{EmbeddedRgb, OutOfBoundsError, Translator, OkVersion}; /// # use prettypretty::assert_close_enough; - /// let sampler = Sampler::new(OkVersion::Revised, VGA_COLORS.clone()); + /// let translator = Translator::new(OkVersion::Revised, VGA_COLORS.clone()); /// /// for r in 0..5 { /// for g in 0..5 { @@ -1243,7 +1243,7 @@ impl Sampler { /// }; /// assert_close_enough!(color[0], c1); /// - /// let result = sampler.to_closest_8bit(&color); + /// let result = translator.to_closest_8bit(&color); /// assert_eq!(result, TerminalColor::Rgb6 { color: embedded }); /// } /// } @@ -1307,7 +1307,7 @@ impl Sampler { } } -impl Sampler { +impl Translator { #[inline] fn do_resolve(&self, color: impl Into) -> Color { match color.into() { @@ -1365,11 +1365,11 @@ impl Sampler { } } -impl std::fmt::Debug for Sampler { +impl std::fmt::Debug for Translator { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!( f, - "Sampler({}, [", + "Translator({}, [", if self.space == ColorSpace::Oklab { "OkVersion.Original" } else { @@ -1389,14 +1389,14 @@ impl std::fmt::Debug for Sampler { #[cfg(test)] mod test { - use super::{Sampler, VGA_COLORS}; + use super::{Translator, VGA_COLORS}; use crate::{AnsiColor, Color, OkVersion, OutOfBoundsError}; #[test] - fn test_sampler() -> Result<(), OutOfBoundsError> { - let sampler = Sampler::new(OkVersion::Revised, VGA_COLORS.clone()); + fn test_translator() -> Result<(), OutOfBoundsError> { + let translator = Translator::new(OkVersion::Revised, VGA_COLORS.clone()); - let result = sampler.to_closest_ansi(&Color::srgb(1.0, 1.0, 0.0)); + let result = translator.to_closest_ansi(&Color::srgb(1.0, 1.0, 0.0)); assert_eq!(result, AnsiColor::BrightYellow); Ok(()) diff --git a/test/test_color.py b/test/test_color.py index 9f30612..0ec36de 100644 --- a/test/test_color.py +++ b/test/test_color.py @@ -4,7 +4,7 @@ from prettypretty.color import ( AnsiColor, Color, ColorSpace, EmbeddedRgb, TerminalColor, TrueColor ) -from prettypretty.theme import current_sampler +from prettypretty.theme import current_translator class ColorValues: @@ -134,8 +134,8 @@ def test_same_color(self) -> None: self.assertEqual(green_too.space(), ColorSpace.Srgb) self.assertEqual(green_too.coordinates(), [0.0, 1.0, 0.0]) - sampler = current_sampler() - green3 = sampler.resolve(also_green) + translator = current_translator() + green3 = translator.resolve(also_green) self.assertEqual(green_too, green3)