Holds the colors of a theme, and makes sure that they meet the minimums of WCAG and APCA
A palette has the 16 colors of a base16 scheme, base00 to base0F. The
scheme gives each color a purpose: base00 is the background, base05 the
text, base03 the comments, and base08 to base0F the accents. new/4
makes a palette, and it gives each role of the theme one of the colors.
A role is a color that the style sheet reads as a custom property, such as
--text or --code-keyword. roles/0 lists them.
problems/1 returns each role that does not meet a minimum. Each minimum has
two measures: the contrast ratio of WCAG 2.2, and the lightness contrast Lc
of APCA. The contrast ratio is too high for a color on a dark background,
and APCA corrects this. Thus a color must meet both. These are the minimums:
- Each role of text, on its background: 4.5:1 and Lc 60. 4.5:1 is the minimum of WCAG 2.2 for normal text, criterion 1.4.3. Lc 60 is the minimum of APCA for the text of content. A slide shows large text, but a projector and the light of a room lower the contrast, so a theme does not use the smaller minimums of large text.
- Each role of text with the dim opacity of the palette, on its
background: 3:1 and Lc 30. 3:1 is the minimum of criterion 1.4.11 for a
part that the reader must see. Lc 30 is the minimum of APCA for any
text that the reader must be able to read, such as a text that the
presenter does not talk about now. A dimmed element is less important, and it
must stay readable. The dimming applies to each color of the element,
such as a comment of a dimmed line of code, so the role with the lowest
contrast sets the opacity. 3:1 is also the minimum of WCAG for large
text, and not for normal text. The
dim_contrastoption of a deck raises it, such as to 4.5:1 for criterion 1.4.3. Seewith_dim_contrast/2.
docs/architecture.md gives the formulas, and it tells why the dimming of a
theme is small.
The accent also colors the progress bar and the frame of the selected page
in the overview, and 4.5:1 is more than the 3:1 that criterion 1.4.11 gives
for them. adjust/1 changes only the lightness of each role that fails,
until it meets the minimum, and it never changes a background.
Expresso.Palette.Builtin adjusts each built-in theme, and
Expresso.ThemeVerifier gives a warning for a palette of a deck that fails.
Summary
Types
A slot of a base16 scheme, from :base00 to :base0F
A palette: its name, its variant, its 16 colors, and the color of each role
The value of the theme option of a deck
Functions
Change the lightness of each role that fails, until each role meets its minimum
Return the colors of the series of a chart for a variant, in order
Return the custom properties of the palette, as declarations for a rule
Make a palette from the 16 colors of a base16 scheme
Return the palette of one theme of the theme option of a deck
Return each role that does not meet a minimum, with the measure, its value and the minimum
Return each role, the slot of its color, and the role of its background
Return the slots of a base16 scheme, in order
Return a color that passes for each slot with a role of text that fails
Validate the value of the theme option, for the schema of the DSL
Return the palette of each variant of the theme option of a deck
Dim the palette to a higher minimum contrast ratio
Types
@type slot() :: atom()
A slot of a base16 scheme, from :base00 to :base0F
@type t() :: %Expresso.Palette{ change: float(), colors: %{required(slot()) => Expresso.Color.t()}, dim_opacity: float(), dim_ratio: float(), dimmed: %{required(atom()) => {float(), Expresso.Color.t()}}, name: String.t(), roles: %{required(atom()) => Expresso.Color.t()}, variant: :dark | :light }
A palette: its name, its variant, its 16 colors, and the color of each role
change is the largest change of lightness that adjust/1 made, in OKLab,
from 0 to 1. dimmed holds, for each role of text, its dimmed color and the
opacity that gives it. dim_opacity is one opacity that dims each role of
text to its minimums. dim_ratio is the minimum contrast ratio of a dimmed
role: 3.0 by default, and the dim_contrast option of a deck can raise it.
See with_dim_contrast/2. The style sheet dims an image, an SVG file and an
embed with it, because their colors are not roles.
@type theme() :: atom() | %{required(slot()) => Expresso.Color.t()} | keyword()
The value of the theme option of a deck
A name of a built-in theme, a map with a color for each slot, or a keyword
list. The keyword list has colors for one theme, or dark and light for
two variants, and adjust to adjust each map.
Functions
Change the lightness of each role that fails, until each role meets its minimum
The function keeps the hue of each color and each background. It returns
:error when no lightness gives a role its minimum.
@spec chart_colors(:light | :dark) :: [String.t()]
Return the colors of the series of a chart for a variant, in order
iex> Expresso.Palette.chart_colors(:light) |> length()
8
Return the custom properties of the palette, as declarations for a rule
Each role gets a property, such as --text. Each role of text also gets
the property of its dimmed color, such as --text-dim. --dim-opacity
holds the dim opacity of the palette, and --chart-1 to --chart-8 hold
the colors of the series of a chart for the variant of the palette.
iex> palette = Expresso.Palette.Builtin.fetch!(:default)
iex> Expresso.Palette.declarations(palette) =~ "--text: #000000;"
true
iex> Expresso.Palette.declarations(palette) =~ "--text-dim: #"
true
@spec new(String.t(), :dark | :light, %{required(slot()) => Expresso.Color.t()}) :: t()
Make a palette from the 16 colors of a base16 scheme
colors holds a #rrggbb color for each slot. Each role of text gets a
dimmed color: the role blended on its background at the smallest multiple
of 0.05 that keeps the role at 3:1 and at Lc 30. Each role dims as far as
its own contrast lets it.
Return the palette of one theme of the theme option of a deck
The option is the name of a built-in theme, or a map with a #rrggbb color
for each slot of base16. A palette of a map is not adjusted. Its variant is
dark when its background is dark. A keyword list returns its first variant
from variants/1.
Return each role that does not meet a minimum, with the measure, its value and the minimum
The measure is :wcag for the contrast ratio, or :apca for the lightness
contrast. A role can fail both. The role :dimmed_text stands for the role
of text with the lowest contrast at the dim opacity. A palette that meets
each minimum returns an empty list.
Return each role, the slot of its color, and the role of its background
A background has nil in place of a background.
@spec slots() :: [slot()]
Return the slots of a base16 scheme, in order
@spec suggestions(t()) :: %{required(slot()) => Expresso.Color.t()}
Return a color that passes for each slot with a role of text that fails
A slot can color more than one role, such as base05 for text on
base00 and for code_text on base01. The color of the slot changes
its lightness with Expresso.Color.adjust/4 against each background in
turn, and it must then meet the minimums on each one. A slot with no such
color is not in the map. Expresso.ThemeVerifier puts the color into its
warning.
iex> colors = Expresso.Palette.Builtin.fetch!(:default).colors
iex> palette = Expresso.Palette.of(%{colors | base03: "#dddddd"})
iex> %{base03: color} = Expresso.Palette.suggestions(palette)
iex> problems = Expresso.Palette.problems(Expresso.Palette.of(%{colors | base03: color}))
iex> Enum.filter(problems, &(elem(&1, 0) == :code_comment))
[]
Validate the value of the theme option, for the schema of the DSL
The value is the name of a built-in theme, or a map with exactly the 16
slots of base16 and a #rrggbb color for each one. The contrast is not part
of the validation: Expresso.ThemeVerifier gives a warning for it.
The value can also be a keyword list:
colorswith a name or a map, for one theme, ordarkandlight, each with a name or a map, for two variants,and
adjustwithtrueorfalse, optional.iex> Expresso.Palette.validate(dark: :dracula, light: :default, adjust: true)
iex> {:error, message} = Expresso.Palette.validate(dark: :dracula) iex> message =~ "needs colors, or both dark and light" true
iex> Expresso.Palette.validate(:dracula)
iex> {:error, message} = Expresso.Palette.validate(:plaid) iex> message =~ "is not a built-in theme" true
Return the palette of each variant of the theme option of a deck
A theme with one variant returns [{nil, palette}]. A keyword list with
dark and light returns [light: palette, dark: palette]. With
adjust: true, the palette of each map gets adjust/1, as a built-in theme
does. A map that no lightness can adjust keeps its colors, and
Expresso.ThemeVerifier then gives a warning for each role that fails.
iex> [{nil, palette}] = Expresso.Palette.variants(:dracula)
iex> palette.name
"Dracula"
iex> Expresso.Palette.variants(dark: :dracula, light: :default) |> Keyword.keys()
[:light, :dark]
Dim the palette to a higher minimum contrast ratio
Each role of text then dims only as far as the role keeps ratio on its
background, and the dim opacity follows. The minimum of APCA stays at Lc 30.
A ratio of 4.5 is the minimum of WCAG 2.2 for normal text, criterion 1.4.3.
nil keeps the palette as it is.
iex> palette = Expresso.Palette.Builtin.fetch!(:default)
iex> Expresso.Palette.with_dim_contrast(palette, 4.5).dim_opacity >= palette.dim_opacity
true