piet_cosmic_text/lib.rs
1// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0
2// This file is a part of `piet-cosmic-text`.
3//
4// `piet-cosmic-text` is free software: you can redistribute it and/or modify it under the
5// terms of either:
6//
7// * GNU Lesser General Public License as published by the Free Software Foundation, either
8// version 3 of the License, or (at your option) any later version.
9// * Mozilla Public License as published by the Mozilla Foundation, version 2.
10//
11// `piet-cosmic-text` is distributed in the hope that it will be useful, but WITHOUT ANY
12// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
13// PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more
14// details.
15//
16// You should have received a copy of the GNU Lesser General Public License and the Mozilla
17// Public License along with `piet-cosmic-text`. If not, see <https://www.gnu.org/licenses/>.
18
19//! An implementation of [`piet`]'s text API using [`cosmic-text`].
20//!
21//! This library implements [`piet`]'s [`Text`] API using primitives from [`cosmic-text`].
22//! The intention is for this library to act as a stepping stone to be able to use drawing
23//! frameworks that do not natively support text rendering (like OpenGL) by using the
24//! [`cosmic-text`] library to render text to a texture, and then using that texture
25//! in the drawing framework.
26//!
27//! This library provides a [`Text`](crate::Text), a [`TextLayoutBuilder`] and a
28//! [`TextLayout`]. All of these are intended to be used in the same way as the
29//! corresponding types in [`piet`]. However, [`TextLayout`] has a `buffer` method that
30//! can be used to get the underlying text.
31//!
32//! The structures provided by this crate are completely renderer-agnostic. The [`TextLayout`]
33//! structure exposes the underlying [`Buffer`] structure, which can be used to render the text.
34//!
35//! # Embedded Fonts
36//!
37//! In order to make it easier to use this crate in a cross platform setting, it embeds a handful
38//! of fonts into the binary. These fonts are used as a fallback when the system fonts are not
39//! available. For instance, on web targets, there are no fonts available by default, so these
40//! fonts are used instead. In addition, if attributes fail to match any of the fonts on the
41//! system, these fonts are used as a fallback.
42//!
43//! Without compression, these fonts add around 1.5 megabytes to the final binary. With the
44//! `DEFLATE` compression algorithm, which is enabled by default, this is reduced to around
45//! 1.1 megabytes. In practice it's actually around 700 kilobytes, as the remaining data is used
46//! by the compressions algorithm. [`yazi`] is used to compress the font data; as it is also used
47//! by [`swash`], which is often used with [`cosmic-text`], the actual amount of data saved should
48//! be closer to the theoretical maximum.
49//!
50//! To disable font compression, disable the default `compress-fonts` feature. To disable embedding
51//! fonts altogether, disable the default `embed-fonts` feature.
52//!
53//! # Font Initialization
54//!
55//! The initialization of the [`FontSystem`] can take some time, especially on slower systems with
56//! many thousand fonts. In order to prevent font loading from blocking the main windowing thread,
57//! [`Text`] has an option to use a background thread to load the fonts. Enabling the `rayon`
58//! feature (not enabled by default) will export font loading to the [`rayon`] thread pool.
59//! Without this feature, font loading will be done on the current thread.
60//!
61//! As web targets do not support threads, enabling the `rayon` feature on web targets will lead
62//! to compilation errors.
63//!
64//! Sufficiently complex programs usually already have a system set up to handle blocking tasks.
65//! For `async` programs, this is usually [`tokio`]'s [`spawn_blocking`] function or the
66//! [`blocking`] thread pool. In these cases you can implement the [`ExportWork`] trait and then
67//! pass it to [`Text::with_thread`]. This will allow you to use the same thread pool for both
68//! font loading and other blocking tasks.
69//!
70//! The `is_loaded` method of [`Text`](crate::Text) can be used to check if the font system is
71//! fully loaded.
72//!
73//! # Limitations
74//!
75//! The text does not support variable font sizes. Attempting to use these will result in emitting
76//! an error to the logs, and the text size not actually being changed.
77//!
78//! [`piet`]: https://docs.rs/piet
79//! [`cosmic-text`]: https://docs.rs/cosmic-text
80//! [`Buffer`]: https://docs.rs/cosmic-text/latest/cosmic_text/struct.Buffer.html
81//! [`Text`]: https://docs.rs/piet/latest/piet/trait.Text.html
82//! [`FontSystem`]: https://docs.rs/cosmic-text/latest/cosmic_text/fontdb/struct.FontSystem.html
83//! [`yazi`]: https://docs.rs/yazi
84//! [`swash`]: https://docs.rs/swash
85//! [`rayon`]: https://docs.rs/rayon
86//! [`tokio`]: https://docs.rs/tokio
87//! [`spawn_blocking`]: https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html
88//! [`blocking`]: https://docs.rs/blocking
89
90#![allow(clippy::await_holding_refcell_ref)]
91#![forbid(unsafe_code, future_incompatible, rust_2018_idioms)]
92
93// Public dependencies.
94pub use cosmic_text;
95pub use piet;
96
97use cosmic_text as ct;
98
99use std::fmt;
100
101pub use export_work::{CurrentThread, ExportWork};
102pub use lines::{LineProcessor, StyledLine};
103pub use metadata::Metadata;
104pub use text::Text;
105pub use text_layout::TextLayout;
106pub use text_layout_builder::TextLayoutBuilder;
107
108#[cfg(feature = "rayon")]
109pub use export_work::Rayon;
110
111const STANDARD_DPI: f64 = 96.0;
112const POINTS_PER_INCH: f64 = 72.0;
113
114#[cfg(not(feature = "tracing"))]
115macro_rules! error {
116 ($($tt:tt)*) => {};
117}
118
119#[cfg(feature = "tracing")]
120macro_rules! error {
121 ($($tt:tt)*) => {
122 tracing::error!($($tt)*)
123 };
124}
125
126#[cfg(not(feature = "tracing"))]
127macro_rules! warn {
128 ($($tt:tt)*) => {};
129}
130
131#[cfg(feature = "tracing")]
132macro_rules! warn {
133 ($($tt:tt)*) => {
134 tracing::warn!($($tt)*)
135 };
136}
137
138#[cfg(not(feature = "tracing"))]
139macro_rules! trace {
140 ($($tt:tt)*) => {};
141}
142
143#[cfg(feature = "tracing")]
144macro_rules! trace {
145 ($($tt:tt)*) => {
146 tracing::trace!($($tt)*)
147 };
148}
149
150#[cfg(not(feature = "tracing"))]
151macro_rules! trace_span {
152 ($($tt:tt)*) => {
153 $crate::Span
154 };
155}
156
157#[cfg(feature = "tracing")]
158macro_rules! trace_span {
159 ($($tt:tt)*) => {
160 tracing::trace_span!($($tt)*)
161 };
162}
163
164#[cfg(not(feature = "tracing"))]
165macro_rules! warn_span {
166 ($($tt:tt)*) => {
167 $crate::Span
168 };
169}
170
171#[cfg(feature = "tracing")]
172macro_rules! warn_span {
173 ($($tt:tt)*) => {
174 tracing::warn_span!($($tt)*)
175 };
176}
177
178#[cfg(not(feature = "tracing"))]
179struct Span;
180
181#[cfg(not(feature = "tracing"))]
182impl Span {
183 fn enter(self) {}
184}
185
186mod attributes;
187mod channel;
188#[cfg(feature = "embed_fonts")]
189mod embedded_fonts;
190mod export_work;
191mod lines;
192mod metadata;
193mod text;
194mod text_layout;
195mod text_layout_builder;
196
197/// The error type for this library.
198#[derive(Debug)]
199pub(crate) enum FontError {
200 /// Attempted to mutably borrow the font system twice.
201 AlreadyBorrowed,
202
203 /// The font system is not loaded yet.
204 NotLoaded,
205
206 /// Attribute index mismatch.
207 InvalidAttributeIndex,
208}
209
210impl fmt::Display for FontError {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 match self {
213 Self::AlreadyBorrowed => f.write_str("the FontSystem is already mutably borrowed and cannot be accessed"),
214 Self::NotLoaded => f.write_str("the FontSystem is not loaded yet, check is_loaded() before accessing or use wait_for_load()"),
215 Self::InvalidAttributeIndex => f.write_str("invalid attribute index"),
216 }
217 }
218}
219
220impl std::error::Error for FontError {}
221
222fn cvt_color(p: piet::Color) -> ct::Color {
223 let (r, g, b, a) = p.as_rgba8();
224 ct::Color::rgba(r, g, b, a)
225}
226
227fn cvt_family(p: &piet::FontFamily) -> ct::Family<'_> {
228 macro_rules! generic {
229 ($piet:ident => $cosmic:ident) => {
230 if p == &piet::FontFamily::$piet {
231 return ct::Family::$cosmic;
232 }
233 };
234 }
235
236 if p.is_generic() {
237 generic!(SERIF => Serif);
238 generic!(SANS_SERIF => SansSerif);
239 generic!(MONOSPACE => Monospace);
240 }
241
242 ct::Family::Name(p.name())
243}
244
245fn cvt_style(p: piet::FontStyle) -> ct::Style {
246 use piet::FontStyle;
247
248 match p {
249 FontStyle::Italic => ct::Style::Italic,
250 FontStyle::Regular => ct::Style::Normal,
251 }
252}
253
254fn cvt_weight(p: piet::FontWeight) -> ct::Weight {
255 ct::Weight(p.to_raw())
256}