From 9835e2ae736235810b4ea1c162ca5e65c547e770 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 18 May 2024 04:49:50 +0200 Subject: Merging upstream version 1.71.1+dfsg1. Signed-off-by: Daniel Baumann --- vendor/plotters/src/style/font/font_desc.rs | 220 +++++++++++++++++++ vendor/plotters/src/style/font/mod.rs | 55 +++++ vendor/plotters/src/style/font/naive.rs | 40 ++++ vendor/plotters/src/style/font/ttf.rs | 321 ++++++++++++++++++++++++++++ vendor/plotters/src/style/font/web.rs | 46 ++++ 5 files changed, 682 insertions(+) create mode 100644 vendor/plotters/src/style/font/font_desc.rs create mode 100644 vendor/plotters/src/style/font/mod.rs create mode 100644 vendor/plotters/src/style/font/naive.rs create mode 100644 vendor/plotters/src/style/font/ttf.rs create mode 100644 vendor/plotters/src/style/font/web.rs (limited to 'vendor/plotters/src/style/font') diff --git a/vendor/plotters/src/style/font/font_desc.rs b/vendor/plotters/src/style/font/font_desc.rs new file mode 100644 index 000000000..a101a5d8b --- /dev/null +++ b/vendor/plotters/src/style/font/font_desc.rs @@ -0,0 +1,220 @@ +use super::{FontData, FontDataInternal}; +use crate::style::text_anchor::Pos; +use crate::style::{Color, TextStyle}; + +use std::convert::From; + +pub use plotters_backend::{FontFamily, FontStyle, FontTransform}; + +/// The error type for the font implementation +pub type FontError = ::ErrorType; + +/// The type we used to represent a result of any font operations +pub type FontResult = Result; + +/// Describes a font +#[derive(Clone)] +pub struct FontDesc<'a> { + size: f64, + family: FontFamily<'a>, + data: FontResult, + transform: FontTransform, + style: FontStyle, +} + +impl<'a> FontDesc<'a> { + /// Create a new font + /// + /// - `family`: The font family name + /// - `size`: The size of the font + /// - `style`: The font variations + /// - **returns** The newly created font description + pub fn new(family: FontFamily<'a>, size: f64, style: FontStyle) -> Self { + Self { + size, + family, + data: FontDataInternal::new(family, style), + transform: FontTransform::None, + style, + } + } + + /// Create a new font desc with the same font but different size + /// + /// - `size`: The new size to set + /// - **returns** The newly created font descriptor with a new size + pub fn resize(&self, size: f64) -> Self { + Self { + size, + family: self.family, + data: self.data.clone(), + transform: self.transform.clone(), + style: self.style, + } + } + + /// Set the style of the font + /// + /// - `style`: The new style + /// - **returns** The new font description with this style applied + pub fn style(&self, style: FontStyle) -> Self { + Self { + size: self.size, + family: self.family, + data: self.data.clone(), + transform: self.transform.clone(), + style, + } + } + + /// Set the font transformation + /// + /// - `trans`: The new transformation + /// - **returns** The new font description with this font transformation applied + pub fn transform(&self, trans: FontTransform) -> Self { + Self { + size: self.size, + family: self.family, + data: self.data.clone(), + transform: trans, + style: self.style, + } + } + + /// Get the font transformation description + pub fn get_transform(&self) -> FontTransform { + self.transform.clone() + } + + /** Returns a new text style object with the specified `color`. + + # Example + + ``` + use plotters::prelude::*; + let text_style = ("sans-serif", 20).into_font().color(&RED); + let drawing_area = SVGBackend::new("font_desc_color.svg", (200, 100)).into_drawing_area(); + drawing_area.fill(&WHITE).unwrap(); + drawing_area.draw_text("This is a big red label", &text_style, (10, 50)); + ``` + + The result is a text label colorized accordingly: + + ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@f030ed3/apidoc/font_desc_color.svg) + + # See also + + [`IntoTextStyle::with_color()`](crate::style::IntoTextStyle::with_color) + + [`IntoTextStyle::into_text_style()`](crate::style::IntoTextStyle::into_text_style) for a more succinct example + + */ + pub fn color(&self, color: &C) -> TextStyle<'a> { + TextStyle { + font: self.clone(), + color: color.to_backend_color(), + pos: Pos::default(), + } + } + + /// Returns the font family + pub fn get_family(&self) -> FontFamily { + self.family + } + + /// Get the name of the font + pub fn get_name(&self) -> &str { + self.family.as_str() + } + + /// Get the name of the style + pub fn get_style(&self) -> FontStyle { + self.style + } + + /// Get the size of font + pub fn get_size(&self) -> f64 { + self.size + } + + /// Get the size of the text if rendered in this font + /// + /// For a TTF type, zero point of the layout box is the left most baseline char of the string + /// Thus the upper bound of the box is most likely be negative + pub fn layout_box(&self, text: &str) -> FontResult<((i32, i32), (i32, i32))> { + match &self.data { + Ok(ref font) => font.estimate_layout(self.size, text), + Err(e) => Err(e.clone()), + } + } + + /// Get the size of the text if rendered in this font. + /// This is similar to `layout_box` function, but it apply the font transformation + /// and estimate the overall size of the font + pub fn box_size(&self, text: &str) -> FontResult<(u32, u32)> { + let ((min_x, min_y), (max_x, max_y)) = self.layout_box(text)?; + let (w, h) = self.get_transform().transform(max_x - min_x, max_y - min_y); + Ok((w.unsigned_abs(), h.unsigned_abs())) + } + + /// Actually draws a font with a drawing function + pub fn draw Result<(), E>>( + &self, + text: &str, + (x, y): (i32, i32), + draw: DrawFunc, + ) -> FontResult> { + match &self.data { + Ok(ref font) => font.draw((x, y), self.size, text, draw), + Err(e) => Err(e.clone()), + } + } +} + +impl<'a> From<&'a str> for FontDesc<'a> { + fn from(from: &'a str) -> FontDesc<'a> { + FontDesc::new(from.into(), 12.0, FontStyle::Normal) + } +} + +impl<'a> From> for FontDesc<'a> { + fn from(family: FontFamily<'a>) -> FontDesc<'a> { + FontDesc::new(family, 12.0, FontStyle::Normal) + } +} + +impl<'a, T: Into> From<(FontFamily<'a>, T)> for FontDesc<'a> { + fn from((family, size): (FontFamily<'a>, T)) -> FontDesc<'a> { + FontDesc::new(family, size.into(), FontStyle::Normal) + } +} + +impl<'a, T: Into> From<(&'a str, T)> for FontDesc<'a> { + fn from((typeface, size): (&'a str, T)) -> FontDesc<'a> { + FontDesc::new(typeface.into(), size.into(), FontStyle::Normal) + } +} + +impl<'a, T: Into, S: Into> From<(FontFamily<'a>, T, S)> for FontDesc<'a> { + fn from((family, size, style): (FontFamily<'a>, T, S)) -> FontDesc<'a> { + FontDesc::new(family, size.into(), style.into()) + } +} + +impl<'a, T: Into, S: Into> From<(&'a str, T, S)> for FontDesc<'a> { + fn from((typeface, size, style): (&'a str, T, S)) -> FontDesc<'a> { + FontDesc::new(typeface.into(), size.into(), style.into()) + } +} + +/// The trait that allows some type turns into a font description +pub trait IntoFont<'a> { + /// Make the font description from the source type + fn into_font(self) -> FontDesc<'a>; +} + +impl<'a, T: Into>> IntoFont<'a> for T { + fn into_font(self) -> FontDesc<'a> { + self.into() + } +} diff --git a/vendor/plotters/src/style/font/mod.rs b/vendor/plotters/src/style/font/mod.rs new file mode 100644 index 000000000..305978fd0 --- /dev/null +++ b/vendor/plotters/src/style/font/mod.rs @@ -0,0 +1,55 @@ +/// The implementation of an actual font implementation +/// +/// This exists since for the image rendering task, we want to use +/// the system font. But in wasm application, we want the browser +/// to handle all the font issue. +/// +/// Thus we need different mechanism for the font implementation + +#[cfg(all( + not(all(target_arch = "wasm32", not(target_os = "wasi"))), + feature = "ttf" +))] +mod ttf; +#[cfg(all( + not(all(target_arch = "wasm32", not(target_os = "wasi"))), + feature = "ttf" +))] +use ttf::FontDataInternal; + +#[cfg(all( + not(all(target_arch = "wasm32", not(target_os = "wasi"))), + not(feature = "ttf") +))] +mod naive; +#[cfg(all( + not(all(target_arch = "wasm32", not(target_os = "wasi"))), + not(feature = "ttf") +))] +use naive::FontDataInternal; + +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] +mod web; +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] +use web::FontDataInternal; + +mod font_desc; +pub use font_desc::*; + +/// Represents a box where a text label can be fit +pub type LayoutBox = ((i32, i32), (i32, i32)); + +pub trait FontData: Clone { + type ErrorType: Sized + std::error::Error + Clone; + fn new(family: FontFamily, style: FontStyle) -> Result; + fn estimate_layout(&self, size: f64, text: &str) -> Result; + fn draw Result<(), E>>( + &self, + _pos: (i32, i32), + _size: f64, + _text: &str, + _draw: DrawFunc, + ) -> Result, Self::ErrorType> { + panic!("The font implementation is unable to draw text"); + } +} diff --git a/vendor/plotters/src/style/font/naive.rs b/vendor/plotters/src/style/font/naive.rs new file mode 100644 index 000000000..99530401b --- /dev/null +++ b/vendor/plotters/src/style/font/naive.rs @@ -0,0 +1,40 @@ +use super::{FontData, FontFamily, FontStyle, LayoutBox}; + +#[derive(Debug, Clone)] +pub struct FontError; + +impl std::fmt::Display for FontError { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + write!(fmt, "General Error")?; + Ok(()) + } +} + +impl std::error::Error for FontError {} + +#[derive(Clone)] +pub struct FontDataInternal(String, String); + +impl FontData for FontDataInternal { + type ErrorType = FontError; + fn new(family: FontFamily, style: FontStyle) -> Result { + Ok(FontDataInternal( + family.as_str().into(), + style.as_str().into(), + )) + } + + /// Note: This is only a crude estimatation, since for some backend such as SVG, we have no way to + /// know the real size of the text anyway. Thus using font-kit is an overkill and doesn't helps + /// the layout. + fn estimate_layout(&self, size: f64, text: &str) -> Result { + let em = size / 1.24 / 1.24; + Ok(( + (0, -em.round() as i32), + ( + (em * 0.7 * text.len() as f64).round() as i32, + (em * 0.24).round() as i32, + ), + )) + } +} diff --git a/vendor/plotters/src/style/font/ttf.rs b/vendor/plotters/src/style/font/ttf.rs new file mode 100644 index 000000000..e6feadc82 --- /dev/null +++ b/vendor/plotters/src/style/font/ttf.rs @@ -0,0 +1,321 @@ +use std::borrow::{Borrow, Cow}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::i32; +use std::sync::{Arc, RwLock}; + +use lazy_static::lazy_static; + +use font_kit::{ + canvas::{Canvas, Format, RasterizationOptions}, + error::{FontLoadingError, GlyphLoadingError}, + family_name::FamilyName, + font::Font, + handle::Handle, + hinting::HintingOptions, + properties::{Properties, Style, Weight}, + source::SystemSource, +}; + +use ttf_parser::{Face, GlyphId}; + +use pathfinder_geometry::transform2d::Transform2F; +use pathfinder_geometry::vector::{Vector2F, Vector2I}; + +use super::{FontData, FontFamily, FontStyle, LayoutBox}; + +type FontResult = Result; + +#[derive(Debug, Clone)] +pub enum FontError { + LockError, + NoSuchFont(String, String), + FontLoadError(Arc), + GlyphError(Arc), +} + +impl std::fmt::Display for FontError { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + match self { + FontError::LockError => write!(fmt, "Could not lock mutex"), + FontError::NoSuchFont(family, style) => { + write!(fmt, "No such font: {} {}", family, style) + } + FontError::FontLoadError(e) => write!(fmt, "Font loading error {}", e), + FontError::GlyphError(e) => write!(fmt, "Glyph error {}", e), + } + } +} + +impl std::error::Error for FontError {} + +lazy_static! { + static ref DATA_CACHE: RwLock>> = + RwLock::new(HashMap::new()); +} + +thread_local! { + static FONT_SOURCE: SystemSource = SystemSource::new(); + static FONT_OBJECT_CACHE: RefCell> = RefCell::new(HashMap::new()); +} + +const PLACEHOLDER_CHAR: char = '�'; + +#[derive(Clone)] +struct FontExt { + inner: Font, + face: Option>, +} + +impl Drop for FontExt { + fn drop(&mut self) { + // We should make sure the face object dead first + self.face.take(); + } +} + +impl FontExt { + fn new(font: Font) -> Self { + let handle = font.handle(); + let (data, idx) = match handle.as_ref() { + Some(Handle::Memory { bytes, font_index }) => (&bytes[..], *font_index), + _ => unreachable!(), + }; + let face = unsafe { + std::mem::transmute::<_, Option>>( + ttf_parser::Face::from_slice(data, idx).ok(), + ) + }; + Self { inner: font, face } + } + + fn query_kerning_table(&self, prev: u32, next: u32) -> f32 { + if let Some(face) = self.face.as_ref() { + if let Some(kern) = face.tables().kern { + let kern = kern + .subtables + .into_iter() + .filter(|st| st.horizontal && !st.variable) + .filter_map(|st| st.glyphs_kerning(GlyphId(prev as u16), GlyphId(next as u16))) + .next() + .unwrap_or(0); + return kern as f32; + } + } + 0.0 + } +} + +impl std::ops::Deref for FontExt { + type Target = Font; + fn deref(&self) -> &Font { + &self.inner + } +} + +/// Lazily load font data. Font type doesn't own actual data, which +/// lives in the cache. +fn load_font_data(face: FontFamily, style: FontStyle) -> FontResult { + let key = match style { + FontStyle::Normal => Cow::Borrowed(face.as_str()), + _ => Cow::Owned(format!("{}, {}", face.as_str(), style.as_str())), + }; + + // First, we try to find the font object for current thread + if let Some(font_object) = FONT_OBJECT_CACHE.with(|font_object_cache| { + font_object_cache + .borrow() + .get(Borrow::::borrow(&key)) + .map(Clone::clone) + }) { + return Ok(font_object); + } + + // Then we need to check if the data cache contains the font data + let cache = DATA_CACHE.read().unwrap(); + if let Some(data) = cache.get(Borrow::::borrow(&key)) { + return data.clone().map(|handle| { + handle + .load() + .map(FontExt::new) + .map_err(|e| FontError::FontLoadError(Arc::new(e))) + })?; + } + drop(cache); + + // Otherwise we should load from system + let mut properties = Properties::new(); + match style { + FontStyle::Normal => properties.style(Style::Normal), + FontStyle::Italic => properties.style(Style::Italic), + FontStyle::Oblique => properties.style(Style::Oblique), + FontStyle::Bold => properties.weight(Weight::BOLD), + }; + + let family = match face { + FontFamily::Serif => FamilyName::Serif, + FontFamily::SansSerif => FamilyName::SansSerif, + FontFamily::Monospace => FamilyName::Monospace, + FontFamily::Name(name) => FamilyName::Title(name.to_owned()), + }; + + let make_not_found_error = + || FontError::NoSuchFont(face.as_str().to_owned(), style.as_str().to_owned()); + + if let Ok(handle) = FONT_SOURCE + .with(|source| source.select_best_match(&[family, FamilyName::SansSerif], &properties)) + { + let font = handle + .load() + .map(FontExt::new) + .map_err(|e| FontError::FontLoadError(Arc::new(e))); + let (should_cache, data) = match font.as_ref().map(|f| f.handle()) { + Ok(None) => (false, Err(FontError::LockError)), + Ok(Some(handle)) => (true, Ok(handle)), + Err(e) => (true, Err(e.clone())), + }; + + if should_cache { + DATA_CACHE + .write() + .map_err(|_| FontError::LockError)? + .insert(key.clone().into_owned(), data); + } + + if let Ok(font) = font.as_ref() { + FONT_OBJECT_CACHE.with(|font_object_cache| { + font_object_cache + .borrow_mut() + .insert(key.into_owned(), font.clone()); + }); + } + + return font; + } + Err(make_not_found_error()) +} + +#[derive(Clone)] +pub struct FontDataInternal(FontExt); + +impl FontData for FontDataInternal { + type ErrorType = FontError; + + fn new(family: FontFamily, style: FontStyle) -> Result { + Ok(FontDataInternal(load_font_data(family, style)?)) + } + + fn estimate_layout(&self, size: f64, text: &str) -> Result { + let font = &self.0; + let pixel_per_em = size / 1.24; + let metrics = font.metrics(); + + let font = &self.0; + + let mut x_in_unit = 0f32; + + let mut prev = None; + let place_holder = font.glyph_for_char(PLACEHOLDER_CHAR); + + for c in text.chars() { + if let Some(glyph_id) = font.glyph_for_char(c).or(place_holder) { + if let Ok(size) = font.advance(glyph_id) { + x_in_unit += size.x(); + } + if let Some(pc) = prev { + x_in_unit += font.query_kerning_table(pc, glyph_id); + } + prev = Some(glyph_id); + } + } + + let x_pixels = x_in_unit * pixel_per_em as f32 / metrics.units_per_em as f32; + + Ok(((0, 0), (x_pixels as i32, pixel_per_em as i32))) + } + + fn draw Result<(), E>>( + &self, + (base_x, mut base_y): (i32, i32), + size: f64, + text: &str, + mut draw: DrawFunc, + ) -> Result, Self::ErrorType> { + let em = (size / 1.24) as f32; + + let mut x = base_x as f32; + let font = &self.0; + let metrics = font.metrics(); + + let canvas_size = size as usize; + + base_y -= (0.24 * em) as i32; + + let mut prev = None; + let place_holder = font.glyph_for_char(PLACEHOLDER_CHAR); + + let mut result = Ok(()); + + for c in text.chars() { + if let Some(glyph_id) = font.glyph_for_char(c).or(place_holder) { + if let Some(pc) = prev { + x += font.query_kerning_table(pc, glyph_id) * em / metrics.units_per_em as f32; + } + + let mut canvas = Canvas::new(Vector2I::splat(canvas_size as i32), Format::A8); + + result = font + .rasterize_glyph( + &mut canvas, + glyph_id, + em as f32, + Transform2F::from_translation(Vector2F::new(0.0, em as f32)), + HintingOptions::None, + RasterizationOptions::GrayscaleAa, + ) + .map_err(|e| FontError::GlyphError(Arc::new(e))) + .and(result); + + let base_x = x as i32; + + for dy in 0..canvas_size { + for dx in 0..canvas_size { + let alpha = canvas.pixels[dy * canvas_size + dx] as f32 / 255.0; + if let Err(e) = draw(base_x + dx as i32, base_y + dy as i32, alpha) { + return Ok(Err(e)); + } + } + } + + x += font.advance(glyph_id).map(|size| size.x()).unwrap_or(0.0) * em + / metrics.units_per_em as f32; + + prev = Some(glyph_id); + } + } + result?; + Ok(Ok(())) + } +} + +#[cfg(test)] +mod test { + + use super::*; + + #[test] + fn test_font_cache() -> FontResult<()> { + // We cannot only check the size of font cache, because + // the test case may be run in parallel. Thus the font cache + // may contains other fonts. + let _a = load_font_data(FontFamily::Serif, FontStyle::Normal)?; + assert!(DATA_CACHE.read().unwrap().contains_key("serif")); + + let _b = load_font_data(FontFamily::Serif, FontStyle::Normal)?; + assert!(DATA_CACHE.read().unwrap().contains_key("serif")); + + // TODO: Check they are the same + + return Ok(()); + } +} diff --git a/vendor/plotters/src/style/font/web.rs b/vendor/plotters/src/style/font/web.rs new file mode 100644 index 000000000..e70e7b1aa --- /dev/null +++ b/vendor/plotters/src/style/font/web.rs @@ -0,0 +1,46 @@ +use super::{FontData, FontFamily, FontStyle, LayoutBox}; +use wasm_bindgen::JsCast; +use web_sys::{window, HtmlElement}; + +#[derive(Debug, Clone)] +pub enum FontError { + UnknownError, +} + +impl std::fmt::Display for FontError { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + match self { + _ => write!(fmt, "Unknown error"), + } + } +} + +impl std::error::Error for FontError {} + +#[derive(Clone)] +pub struct FontDataInternal(String, String); + +impl FontData for FontDataInternal { + type ErrorType = FontError; + fn new(family: FontFamily, style: FontStyle) -> Result { + Ok(FontDataInternal( + family.as_str().into(), + style.as_str().into(), + )) + } + fn estimate_layout(&self, size: f64, text: &str) -> Result { + let window = window().unwrap(); + let document = window.document().unwrap(); + let body = document.body().unwrap(); + let span = document.create_element("span").unwrap(); + span.set_text_content(Some(text)); + span.set_attribute("style", &format!("display: inline-block; font-family:{}; font-size: {}px; position: fixed; top: 100%", self.0, size)).unwrap(); + let span = span.into(); + body.append_with_node_1(&span).unwrap(); + let elem = JsCast::dyn_into::(span).unwrap(); + let height = elem.offset_height() as i32; + let width = elem.offset_width() as i32; + elem.remove(); + Ok(((0, 0), (width, height))) + } +} -- cgit v1.2.3