use crate::element::font_manager::FontManager;
use crate::element::paragraph::ParagraphParams;
use crate::element::text::simple_text_paragraph::{
chars_to_glyphs_vec, SimpleTextParagraph, TextBlock,
};
use crate::font::Font;
use crate::mrc::Mrc;
use crate::some_or_continue;
use crate::string::StringUtils;
use crate::text::TextStyle;
use log::warn;
use std::collections::HashMap;
thread_local! {
pub static FONT_MANAGER: FontManager = FontManager::new();
}
pub struct SimpleParagraphBuilder {
paragraph_params: ParagraphParams,
styles: Vec<TextStyle>,
text_blocks: Vec<TextBlock>,
font_manager: FontManager,
fallback_cache: Mrc<HashMap<char, Option<Font>>>,
}
impl SimpleParagraphBuilder {
pub fn new(style: &ParagraphParams) -> Self {
let mut text_style = TextStyle::new();
text_style.set_color(style.color);
text_style.set_font_size(style.font_size);
Self {
paragraph_params: style.clone(),
styles: vec![text_style],
text_blocks: Vec::new(),
font_manager: FONT_MANAGER.with(|fm| fm.clone()),
fallback_cache: Mrc::new(HashMap::new()),
}
}
pub fn push_style(&mut self, style: &TextStyle) {
self.styles.push(style.clone());
}
pub fn add_text(&mut self, text: impl Into<String>) {
let text = text.into();
let style = self.styles.last().unwrap().clone();
let font_families = style
.font_families()
.as_ref()
.unwrap_or(&self.paragraph_params.font_families);
let font_families_names = font_families
.as_slice()
.iter()
.map(|it| it.name())
.collect::<Vec<_>>();
let mut text_blocks = self.resolve_font(&font_families_names, &style, &text);
self.text_blocks.append(&mut text_blocks);
}
fn resolve_font(
&self,
font_families_names: &Vec<&str>,
style: &TextStyle,
text: &str,
) -> Vec<TextBlock> {
let font_families_names = font_families_names.clone();
let mut fonts = self
.font_manager
.match_best(&font_families_names, style.font_style());
if fonts.is_empty() {
warn!("No matching font found for {:?}", &font_families_names);
return Vec::new();
}
let chars = text.chars().collect::<Vec<_>>();
let (mut resolved_typefaces, _unresolved_count) = Self::do_resolve_font(&chars, &fonts);
for i in 0..chars.len() {
if resolved_typefaces[i] != -1 {
continue;
}
let ch = chars[i];
if ch == '\n' {
continue;
}
let cached_tf = match self.fallback_cache.get(&ch) {
Some(tf) => tf,
None => {
let tf = None;
self.fallback_cache.clone().insert(ch, tf);
self.fallback_cache.get(&ch).unwrap()
}
};
let tf = some_or_continue!(cached_tf);
if resolved_typefaces[i] == -1 {
fonts.push(tf.clone());
resolved_typefaces[i] = (fonts.len() - 1) as i32;
}
}
for i in 0..resolved_typefaces.len() {
if resolved_typefaces[i] == -1 {
resolved_typefaces[i] = 0;
}
}
let mut text_blocks = Vec::new();
let mut i = 0;
while i < chars.len() {
let begin = i;
let tf_idx = resolved_typefaces[i] as usize;
let font = fonts[tf_idx].clone();
i += 1;
while i < chars.len() && resolved_typefaces[i] as usize == tf_idx {
i += 1;
}
let end = i;
text_blocks.push(TextBlock {
text: text.substring(begin, end - begin).to_string(),
style: style.clone(),
font,
});
}
text_blocks
}
pub fn build(self) -> SimpleTextParagraph {
SimpleTextParagraph::new(self.text_blocks, self.paragraph_params.line_height)
}
fn do_resolve_font(chars: &Vec<char>, fonts: &Vec<Font>) -> (Vec<i32>, usize) {
let mut resolved_typefaces: Vec<i32> = Vec::new();
resolved_typefaces.resize(chars.len(), -1);
let mut unresolved_char_count = resolved_typefaces.len();
for tf_idx in 0..fonts.len() {
let glyphs_ids = chars_to_glyphs_vec(&fonts[tf_idx], chars);
for i in 0..glyphs_ids.len() {
if resolved_typefaces[i] == -1 && glyphs_ids[i] != 0 {
resolved_typefaces[i] = tf_idx as i32;
unresolved_char_count -= 1;
}
}
if unresolved_char_count == 0 {
break;
}
}
(resolved_typefaces, unresolved_char_count)
}
}
#[cfg(test)]
mod tests {
use crate::element::paragraph::simple_paragraph_builder::SimpleParagraphBuilder;
use crate::element::paragraph::{ParagraphParams, ZERO_WIDTH_WHITESPACE};
use crate::font::family::{FontFamilies, FontFamily};
use crate::style::font::FontStyle;
use measure_time::print_time;
use skia_safe::font_style::Weight;
#[test]
fn test_performance() {
for _ in 0..10 {
let params = ParagraphParams {
text_wrap: None,
line_height: None,
align: Default::default(),
color: Default::default(),
font_size: 14.0,
font_families: FontFamilies::new(vec![FontFamily::new("monospace")]),
font_weight: Weight::NORMAL,
font_style: FontStyle::Normal,
mask_char: None,
};
let mut pb = SimpleParagraphBuilder::new(¶ms);
let str = include_str!("../../../Cargo.lock");
{
print_time!("render");
pb.add_text(str.to_string());
}
let b = pb.build();
}
}
#[test]
fn test_get_char_bounds() {
let params = ParagraphParams {
text_wrap: None,
line_height: None,
align: Default::default(),
color: Default::default(),
font_size: 14.0,
font_families: FontFamilies::new(vec![FontFamily::new("monospace")]),
font_weight: Weight::NORMAL,
font_style: FontStyle::Normal,
mask_char: None,
};
let mut pb = SimpleParagraphBuilder::new(¶ms);
pb.add_text(format!("{}{}", "12", ZERO_WIDTH_WHITESPACE));
let mut paragraph = pb.build();
paragraph.layout(100.0);
let layout = paragraph.layout.as_ref().unwrap();
for offset in 0..3 {
let bounds = layout.get_char_bounds(offset);
assert!(bounds.is_some());
}
let bounds0 = layout.get_char_bounds(0).unwrap();
let bounds1 = layout.get_char_bounds(1).unwrap();
assert!(bounds1.left >= bounds0.right);
assert!(paragraph.max_intrinsic_width() > 0.0);
}
}