kas_core/config/font.rs
1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4// https://www.apache.org/licenses/LICENSE-2.0
5
6//! Font configuration
7
8use crate::ConfigAction;
9use crate::text::fonts::FontSelector;
10use crate::theme::TextClass;
11use std::collections::BTreeMap;
12
13/// A message which may be used to update [`FontConfig`]
14#[derive(Clone, Debug)]
15#[non_exhaustive]
16pub enum FontConfigMsg {
17 /// Standard font size, in units of pixels-per-Em
18 Size(f32),
19}
20
21/// Font configuration
22#[derive(Clone, Debug, PartialEq)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub struct FontConfig {
25 /// Standard font size, in units of pixels-per-Em
26 #[cfg_attr(feature = "serde", serde(default = "defaults::size"))]
27 size: f32,
28
29 /// Standard fonts
30 ///
31 /// Changing this at run-tme is not currently supported.
32 ///
33 /// TODO: read/write support.
34 #[cfg_attr(feature = "serde", serde(default = "defaults::fonts"))]
35 fonts: BTreeMap<TextClass, FontSelector>,
36
37 /// Text glyph rastering settings
38 ///
39 /// Changing this at run-tme is not currently supported.
40 #[cfg_attr(feature = "serde", serde(default))]
41 raster: RasterConfig,
42}
43
44impl Default for FontConfig {
45 fn default() -> Self {
46 FontConfig {
47 size: defaults::size(),
48 fonts: defaults::fonts(),
49 raster: Default::default(),
50 }
51 }
52}
53
54/// Sub-pixel font rendering control
55#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57pub enum SubpixelMode {
58 /// No sub-pixel rendering
59 ///
60 /// This is the default because it is the simplest, always supported and never wrong.
61 #[default]
62 None,
63 /// Horizontal RGB sub-pixels
64 ///
65 /// This is the most common LCD display type.
66 HorizontalRGB,
67}
68
69impl SubpixelMode {
70 /// Returns true if any subpixel rendering mode is enabled
71 pub fn any_subpixel(self) -> bool {
72 self != SubpixelMode::None
73 }
74}
75
76/// Font raster settings
77///
78/// These are not used by the theme, but passed through to the rendering
79/// backend.
80#[derive(Clone, Debug, PartialEq, Eq)]
81#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
82pub struct RasterConfig {
83 //// Raster mode/engine (backend dependent)
84 ////
85 /// The `mode` parameter selects rendering mode (though depending on crate
86 /// features, not all options will be available). The default mode is `4`.
87 /// In case the crate is built without the required `fontdue` or `swash`
88 /// feature, the rasterer falls back to mode `0`.
89 ///
90 /// - `mode == 0`: use `ab_glyph` for rastering
91 /// - `mode == 1`: use `ab_glyph` and align glyphs to side bearings
92 /// - `mode == 2`: deprecated
93 /// - `mode == 3`: use `swash` for rastering
94 /// - `mode == 4`: use `swash` for rastering with hinting
95 #[cfg_attr(feature = "serde", serde(default = "defaults::mode"))]
96 pub mode: u8,
97 /// Subpixel positioning threshold
98 ///
99 /// Text with height `h` less than this threshold will use sub-pixel
100 /// positioning (see below), which should make letter spacing more
101 /// accurate for small fonts.
102 ///
103 /// Units: physical pixels per Em ("dpem"). Default value: 18.
104 #[cfg_attr(feature = "serde", serde(default = "defaults::subpixel_threshold"))]
105 pub subpixel_threshold: u8,
106 /// Subpixel steps (horizontal)
107 ///
108 /// The number of sub-pixel positioning steps to use on the x-axis for
109 /// horizontal text (when enabled; see [`Self::subpixel_threshold`]).
110 ///
111 /// Minimum: 1 (no sub-pixel positioning). Maximum: 16. Default: 3.
112 #[cfg_attr(feature = "serde", serde(default = "defaults::subpixel_x_steps"))]
113 pub subpixel_x_steps: u8,
114 /// Subpixel rendering mode
115 pub subpixel_mode: SubpixelMode,
116}
117
118impl Default for RasterConfig {
119 fn default() -> Self {
120 RasterConfig {
121 mode: defaults::mode(),
122 subpixel_threshold: defaults::subpixel_threshold(),
123 subpixel_x_steps: defaults::subpixel_x_steps(),
124 subpixel_mode: Default::default(),
125 }
126 }
127}
128
129/// Getters
130impl FontConfig {
131 /// Get font size
132 ///
133 /// Units: logical (unscaled) pixels per Em.
134 ///
135 /// To convert to Points, multiply by three quarters.
136 #[inline]
137 pub fn get_dpem(&self, class: TextClass) -> f32 {
138 if class != TextClass::Small {
139 self.size
140 } else {
141 self.size * 0.8
142 }
143 }
144
145 /// Get a [`FontSelector`] for `class`
146 #[inline]
147 pub fn get_font_selector(&self, class: TextClass) -> FontSelector {
148 self.fonts.get(&class).cloned().unwrap_or_default()
149 }
150}
151
152/// Setters
153impl FontConfig {
154 /// Set standard font size
155 ///
156 /// Units: logical (unscaled) pixels per Em.
157 ///
158 /// To convert to Points, multiply by three quarters.
159 pub fn set_size(&mut self, pt_size: f32) -> ConfigAction {
160 if self.size != pt_size {
161 self.size = pt_size;
162 ConfigAction::THEME
163 } else {
164 ConfigAction::empty()
165 }
166 }
167}
168
169/// Other functions
170impl FontConfig {
171 /// Apply config effects which only happen on startup
172 pub(super) fn init(&self) {}
173
174 /// Get raster config
175 #[inline]
176 pub fn raster(&self) -> &RasterConfig {
177 &self.raster
178 }
179}
180
181mod defaults {
182 use kas_text::fonts::FamilySelector;
183
184 use super::*;
185
186 pub fn size() -> f32 {
187 16.0
188 }
189
190 pub fn fonts() -> BTreeMap<TextClass, FontSelector> {
191 let list = [
192 (TextClass::Serif, FamilySelector::SERIF.into()),
193 (TextClass::SansSerif, FamilySelector::SANS_SERIF.into()),
194 (TextClass::Monospace, FamilySelector::MONOSPACE.into()),
195 ];
196 list.into_iter().collect()
197 }
198
199 pub fn mode() -> u8 {
200 4
201 }
202 pub fn subpixel_threshold() -> u8 {
203 18
204 }
205 pub fn subpixel_x_steps() -> u8 {
206 3
207 }
208}