use std::cell::RefCell;
use std::ffi::OsStr;
use std::fs;
use std::hash::{
Hash,
Hasher,
};
use std::ops::Range;
use std::path::Path as FilePath;
use std::rc::Rc;
use fnv::{
FnvBuildHasher,
FnvHashMap,
FnvHasher,
};
use generational_arena::{
Arena,
Index,
};
use lru::LruCache;
use unicode_bidi::BidiInfo;
use unicode_segmentation::UnicodeSegmentation;
use crate::{
Canvas,
Color,
ErrorKind,
FillRule,
ImageFlags,
ImageId,
ImageInfo,
Paint,
PixelFormat,
RenderTarget,
Renderer,
};
mod atlas;
pub use atlas::Atlas;
mod font;
use font::Font;
pub use font::FontMetrics;
use self::font::GlyphRendering;
const GLYPH_PADDING: u32 = 1;
const GLYPH_MARGIN: u32 = 1;
const TEXTURE_SIZE: usize = 512;
const LRU_CACHE_CAPACITY: usize = 1000;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct FontId(Index);
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub enum Baseline {
Top,
Middle,
Alphabetic,
Bottom,
}
impl Default for Baseline {
fn default() -> Self {
Self::Alphabetic
}
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub enum Align {
Left,
Center,
Right,
}
impl Default for Align {
fn default() -> Self {
Self::Left
}
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum RenderMode {
Fill,
Stroke,
}
impl Default for RenderMode {
fn default() -> Self {
Self::Fill
}
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub(crate) struct RenderedGlyphId {
glyph_index: u32,
font_id: FontId,
size: u32,
line_width: u32,
render_mode: RenderMode,
subpixel_location: u8,
}
impl RenderedGlyphId {
fn new(glyph_index: u32, font_id: FontId, paint: &Paint, mode: RenderMode, subpixel_location: u8) -> Self {
Self {
glyph_index,
font_id,
size: (paint.font_size * 10.0).trunc() as u32,
line_width: (paint.line_width * 10.0).trunc() as u32,
render_mode: mode,
subpixel_location,
}
}
}
#[allow(dead_code)]
#[derive(Copy, Clone, Debug)]
pub(crate) struct RenderedGlyph {
texture_index: usize,
width: u32,
height: u32,
bearing_y: i32,
atlas_x: u32,
atlas_y: u32,
padding: u32,
color_glyph: bool,
}
#[derive(Copy, Clone, Debug)]
pub struct ShapedGlyph {
pub x: f32,
pub y: f32,
pub c: char,
pub byte_index: usize,
pub font_id: FontId,
pub codepoint: u32,
pub width: f32,
pub height: f32,
pub advance_x: f32,
pub advance_y: f32,
pub offset_x: f32,
pub offset_y: f32,
pub bearing_x: f32,
pub bearing_y: f32,
pub bitmap_glyph: bool,
}
#[derive(Clone, Debug, Default)]
struct ShapedWord {
glyphs: Vec<ShapedGlyph>,
width: f32,
}
#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
struct ShapingId {
size: u32,
word_hash: u64,
font_ids: [Option<FontId>; 8],
}
impl ShapingId {
fn new(paint: &Paint, word: &str, max_width: Option<f32>) -> Self {
let mut hasher = FnvHasher::default();
word.hash(&mut hasher);
if let Some(max_width) = max_width {
(max_width.trunc() as i32).hash(&mut hasher);
}
Self {
size: (paint.font_size * 10.0).trunc() as u32,
word_hash: hasher.finish(),
font_ids: paint.font_ids,
}
}
}
type ShapedWordsCache<H> = LruCache<ShapingId, Result<ShapedWord, ErrorKind>, H>;
type ShapingRunCache<H> = LruCache<ShapingId, TextMetrics, H>;
pub(crate) struct FontTexture {
atlas: Atlas,
pub(crate) image_id: ImageId,
}
#[derive(Default, Clone)]
pub struct TextContext(pub(crate) Rc<RefCell<TextContextImpl>>);
impl TextContext {
pub fn add_font_dir<T: AsRef<FilePath>>(&self, path: T) -> Result<Vec<FontId>, ErrorKind> {
self.0.as_ref().borrow_mut().add_font_dir(path)
}
pub fn add_font_file<T: AsRef<FilePath>>(&self, path: T) -> Result<FontId, ErrorKind> {
self.0.as_ref().borrow_mut().add_font_file(path)
}
pub fn add_font_mem(&self, data: &[u8]) -> Result<FontId, ErrorKind> {
self.0.as_ref().borrow_mut().add_font_mem(data)
}
pub fn add_shared_font_with_index<T: AsRef<[u8]> + 'static>(
&self,
data: T,
face_index: u32,
) -> Result<FontId, ErrorKind> {
self.0
.as_ref()
.borrow_mut()
.add_shared_font_with_index(data, face_index)
}
pub fn measure_text<S: AsRef<str>>(&self, x: f32, y: f32, text: S, paint: Paint) -> Result<TextMetrics, ErrorKind> {
self.0.as_ref().borrow_mut().measure_text(x, y, text, paint)
}
pub fn break_text<S: AsRef<str>>(&self, max_width: f32, text: S, paint: Paint) -> Result<usize, ErrorKind> {
self.0.as_ref().borrow_mut().break_text(max_width, text, paint)
}
pub fn break_text_vec<S: AsRef<str>>(
&self,
max_width: f32,
text: S,
paint: Paint,
) -> Result<Vec<Range<usize>>, ErrorKind> {
self.0.as_ref().borrow_mut().break_text_vec(max_width, text, paint)
}
pub fn measure_font(&self, paint: Paint) -> Result<FontMetrics, ErrorKind> {
self.0.as_ref().borrow_mut().measure_font(paint)
}
}
pub(crate) struct TextContextImpl {
fonts: Arena<Font>,
shaping_run_cache: ShapingRunCache<FnvBuildHasher>,
shaped_words_cache: ShapedWordsCache<FnvBuildHasher>,
}
impl Default for TextContextImpl {
fn default() -> Self {
let fnv_run = FnvBuildHasher::default();
let fnv_words = FnvBuildHasher::default();
Self {
fonts: Default::default(),
shaping_run_cache: LruCache::with_hasher(LRU_CACHE_CAPACITY, fnv_run),
shaped_words_cache: LruCache::with_hasher(LRU_CACHE_CAPACITY, fnv_words),
}
}
}
impl TextContextImpl {
pub fn add_font_dir<T: AsRef<FilePath>>(&mut self, path: T) -> Result<Vec<FontId>, ErrorKind> {
let path = path.as_ref();
let mut fonts = Vec::new();
if path.is_dir() {
for entry in fs::read_dir(path)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
self.add_font_dir(&path)?;
} else if let Some("ttf") = path.extension().and_then(OsStr::to_str) {
fonts.push(self.add_font_file(path)?);
} else if let Some("ttc") = path.extension().and_then(OsStr::to_str) {
fonts.extend(self.add_font_file_collection(path)?);
}
}
}
Ok(fonts)
}
pub fn add_font_file<T: AsRef<FilePath>>(&mut self, path: T) -> Result<FontId, ErrorKind> {
let data = std::fs::read(path)?;
self.add_font_mem(&data)
}
pub fn add_font_file_collection<T: AsRef<FilePath>>(
&mut self,
path: T,
) -> Result<impl Iterator<Item = FontId> + '_, ErrorKind> {
let data = std::fs::read(path)?;
let count = ttf_parser::fonts_in_collection(&data).unwrap_or(1);
Ok((0..count).filter_map(move |index| self.add_font_mem_with_index(&data, index).ok()))
}
pub fn add_font_mem(&mut self, data: &[u8]) -> Result<FontId, ErrorKind> {
self.add_font_mem_with_index(data, 0)
}
pub fn add_font_mem_with_index(&mut self, data: &[u8], face_index: u32) -> Result<FontId, ErrorKind> {
self.clear_caches();
let font = Font::new_with_data(data.to_owned(), face_index)?;
Ok(FontId(self.fonts.insert(font)))
}
pub fn add_shared_font_with_index<T: AsRef<[u8]> + 'static>(
&mut self,
data: T,
face_index: u32,
) -> Result<FontId, ErrorKind> {
self.clear_caches();
let font = Font::new_with_data(data, face_index)?;
Ok(FontId(self.fonts.insert(font)))
}
pub fn font(&self, id: FontId) -> Option<&Font> {
self.fonts.get(id.0)
}
pub fn font_mut(&mut self, id: FontId) -> Option<&mut Font> {
self.fonts.get_mut(id.0)
}
pub fn find_font<F, T>(&mut self, paint: &Paint, mut callback: F) -> Result<T, ErrorKind>
where
F: FnMut((FontId, &mut Font)) -> (bool, T),
{
for maybe_font_id in paint.font_ids.iter() {
if let Some(font_id) = maybe_font_id {
if let Some(font) = self.fonts.get_mut(font_id.0) {
let (has_missing, result) = callback((*font_id, font));
if !has_missing {
return Ok(result);
}
}
} else {
break;
}
}
for (id, font) in &mut self.fonts {
let (has_missing, result) = callback((FontId(id), font));
if !has_missing {
return Ok(result);
}
}
if let Some((id, font)) = self.fonts.iter_mut().next() {
return Ok(callback((FontId(id), font)).1);
}
Err(ErrorKind::NoFontFound)
}
fn clear_caches(&mut self) {
self.shaped_words_cache.clear();
}
pub fn measure_text<S: AsRef<str>>(
&mut self,
x: f32,
y: f32,
text: S,
paint: Paint,
) -> Result<TextMetrics, ErrorKind> {
shape(x, y, self, &paint, text.as_ref(), None)
}
pub fn break_text<S: AsRef<str>>(&mut self, max_width: f32, text: S, paint: Paint) -> Result<usize, ErrorKind> {
let layout = shape(0.0, 0.0, self, &paint, text.as_ref(), Some(max_width))?;
Ok(layout.final_byte_index)
}
pub fn break_text_vec<S: AsRef<str>>(
&mut self,
max_width: f32,
text: S,
paint: Paint,
) -> Result<Vec<Range<usize>>, ErrorKind> {
let text = text.as_ref();
let mut res = Vec::new();
let mut start = 0;
while start < text.len() {
if let Ok(index) = self.break_text(max_width, &text[start..], paint) {
if index == 0 {
break;
}
let index = start + index;
res.push(start..index);
start += &text[start..index].len();
} else {
break;
}
}
Ok(res)
}
pub fn measure_font(&mut self, paint: Paint) -> Result<FontMetrics, ErrorKind> {
if let Some(Some(id)) = paint.font_ids.get(0) {
if let Some(font) = self.font(*id) {
return Ok(font.metrics(paint.font_size));
}
}
Err(ErrorKind::NoFontFound)
}
}
#[derive(Clone, Default, Debug)]
pub struct TextMetrics {
pub x: f32,
pub y: f32,
width: f32,
height: f32,
pub glyphs: Vec<ShapedGlyph>,
pub(crate) final_byte_index: usize,
}
impl TextMetrics {
pub(crate) fn scale(&mut self, scale: f32) {
self.x *= scale;
self.y *= scale;
self.width *= scale;
self.height *= scale;
for glyph in &mut self.glyphs {
glyph.x *= scale;
glyph.y *= scale;
glyph.width *= scale;
glyph.height *= scale;
}
}
pub fn width(&self) -> f32 {
self.width
}
pub fn height(&self) -> f32 {
self.height
}
pub(crate) fn has_bitmap_glyphs(&self) -> bool {
self.glyphs.iter().find(|g| g.bitmap_glyph).is_some()
}
}
pub(crate) fn shape(
x: f32,
y: f32,
context: &mut TextContextImpl,
paint: &Paint,
text: &str,
max_width: Option<f32>,
) -> Result<TextMetrics, ErrorKind> {
let id = ShapingId::new(paint, text, max_width);
if !context.shaping_run_cache.contains(&id) {
let metrics = shape_run(context, paint, text, max_width)?;
context.shaping_run_cache.put(id, metrics);
}
if let Some(mut metrics) = context.shaping_run_cache.get(&id).cloned() {
layout(x, y, context, &mut metrics, paint)?;
return Ok(metrics);
}
Err(ErrorKind::UnknownError)
}
fn shape_run(
context: &mut TextContextImpl,
paint: &Paint,
text: &str,
max_width: Option<f32>,
) -> Result<TextMetrics, ErrorKind> {
let mut result = TextMetrics {
x: 0.0,
y: 0.0,
width: 0.0,
height: 0.0,
glyphs: Vec::with_capacity(text.len()),
final_byte_index: 0,
};
let bidi_info = BidiInfo::new(text, Some(unicode_bidi::Level::ltr()));
if let Some(paragraph) = bidi_info.paragraphs.get(0) {
let line = paragraph.range.clone();
let (levels, runs) = bidi_info.visual_runs(paragraph, line);
for run in runs.iter() {
let sub_text = &text[run.clone()];
if sub_text.is_empty() {
continue;
}
let hb_direction = if levels[run.start].is_rtl() {
rustybuzz::Direction::RightToLeft
} else {
rustybuzz::Direction::LeftToRight
};
let mut words = Vec::new();
let mut word_break_reached = false;
let mut byte_index = run.start;
for word in sub_text.split_word_bounds() {
let id = ShapingId::new(paint, word, max_width);
if !context.shaped_words_cache.contains(&id) {
let word = shape_word(word, hb_direction, context, paint);
context.shaped_words_cache.put(id, word);
}
if let Some(Ok(word)) = context.shaped_words_cache.get(&id) {
let mut word = word.clone();
if let Some(max_width) = max_width {
if result.width + word.width >= max_width {
word_break_reached = true;
break;
}
}
result.width += word.width;
for glyph in &mut word.glyphs {
glyph.byte_index += byte_index;
debug_assert!(text.get(glyph.byte_index..).is_some());
}
words.push(word);
}
byte_index += word.len();
}
if levels[run.start].is_rtl() {
words.reverse();
}
for word in words {
result.glyphs.extend(word.glyphs.clone());
}
result.final_byte_index = byte_index;
if word_break_reached {
break;
}
}
}
Ok(result)
}
fn shape_word(
word: &str,
hb_direction: rustybuzz::Direction,
context: &mut TextContextImpl,
paint: &Paint,
) -> Result<ShapedWord, ErrorKind> {
context.find_font(paint, |(font_id, font)| {
let output = {
let face = font.face_ref();
let mut buffer = rustybuzz::UnicodeBuffer::new();
buffer.push_str(word);
buffer.set_direction(hb_direction);
rustybuzz::shape(face, &[], buffer)
};
let positions = output.glyph_positions();
let infos = output.glyph_infos();
let mut shaped_word = ShapedWord {
glyphs: Vec::with_capacity(positions.len()),
width: 0.0,
};
let mut has_missing = false;
for (position, (info, c)) in positions.iter().zip(infos.iter().zip(word.chars())) {
if info.glyph_id == 0 {
has_missing = true;
}
let scale = font.scale(paint.font_size);
let mut g = ShapedGlyph {
x: 0.0,
y: 0.0,
c,
byte_index: info.cluster as usize,
font_id,
codepoint: info.glyph_id,
width: 0.0,
height: 0.0,
advance_x: position.x_advance as f32 * scale,
advance_y: position.y_advance as f32 * scale,
offset_x: position.x_offset as f32 * scale,
offset_y: position.y_offset as f32 * scale,
bearing_x: 0.0,
bearing_y: 0.0,
bitmap_glyph: false,
};
if let Some(glyph) = font.glyph(info.glyph_id as u16) {
g.width = glyph.metrics.width * scale;
g.height = glyph.metrics.height * scale;
g.bearing_x = glyph.metrics.bearing_x * scale;
g.bearing_y = glyph.metrics.bearing_y * scale;
g.bitmap_glyph = glyph.path.is_none();
}
shaped_word.width += g.advance_x + paint.letter_spacing;
shaped_word.glyphs.push(g);
}
(has_missing, shaped_word)
})
}
fn layout(
x: f32,
y: f32,
context: &mut TextContextImpl,
res: &mut TextMetrics,
paint: &Paint,
) -> Result<(), ErrorKind> {
let mut cursor_x = x;
let mut cursor_y = y;
match paint.text_align {
Align::Center => cursor_x -= res.width / 2.0,
Align::Right => cursor_x -= res.width,
_ => (),
}
res.x = cursor_x;
let mut min_y = cursor_y;
let mut max_y = cursor_y;
let mut ascender: f32 = 0.;
let mut descender: f32 = 0.;
for glyph in &mut res.glyphs {
let font = context.font_mut(glyph.font_id).ok_or(ErrorKind::NoFontFound)?;
let metrics = font.metrics(paint.font_size);
ascender = ascender.max(metrics.ascender());
descender = descender.min(metrics.descender());
}
let primary_metrics = context.find_font(paint, |(_, font)| (false, font.metrics(paint.font_size)))?;
if ascender.abs() < std::f32::EPSILON {
ascender = primary_metrics.ascender();
}
if descender.abs() < std::f32::EPSILON {
descender = primary_metrics.descender();
}
let alignment_offset_y = match paint.text_baseline {
Baseline::Top => ascender,
Baseline::Middle => (ascender + descender) / 2.0,
Baseline::Alphabetic => 0.0,
Baseline::Bottom => descender,
};
for glyph in &mut res.glyphs {
glyph.x = cursor_x + glyph.offset_x + glyph.bearing_x;
glyph.y = (cursor_y + alignment_offset_y).round() + glyph.offset_y - glyph.bearing_y;
min_y = min_y.min(glyph.y);
max_y = max_y.max(glyph.y + glyph.height);
cursor_x += glyph.advance_x + paint.letter_spacing;
cursor_y += glyph.advance_y;
}
res.y = min_y;
res.height = max_y - min_y;
Ok(())
}
#[derive(Clone, Debug)]
pub(crate) struct DrawCmd {
pub image_id: ImageId,
pub quads: Vec<Quad>,
}
#[derive(Copy, Clone, Default, Debug)]
pub(crate) struct Quad {
pub x0: f32,
pub y0: f32,
pub s0: f32,
pub t0: f32,
pub x1: f32,
pub y1: f32,
pub s1: f32,
pub t1: f32,
}
pub(crate) struct GlyphDrawCommands {
pub(crate) alpha_glyphs: Vec<DrawCmd>,
pub(crate) color_glyphs: Vec<DrawCmd>,
}
#[derive(Default)]
pub(crate) struct GlyphAtlas {
pub rendered_glyphs: RefCell<FnvHashMap<RenderedGlyphId, RenderedGlyph>>,
pub glyph_textures: RefCell<Vec<FontTexture>>,
}
impl GlyphAtlas {
pub(crate) fn render_atlas<T: Renderer>(
&self,
canvas: &mut Canvas<T>,
text_layout: &TextMetrics,
paint: &Paint,
mode: RenderMode,
) -> Result<GlyphDrawCommands, ErrorKind> {
let mut alpha_cmd_map = FnvHashMap::default();
let mut color_cmd_map = FnvHashMap::default();
let line_width_offset = if mode == RenderMode::Stroke {
(paint.line_width / 2.0).ceil()
} else {
0.0
};
let initial_render_target = canvas.current_render_target;
for glyph in &text_layout.glyphs {
let subpixel_location = crate::geometry::quantize(glyph.x.fract(), 0.1) * 10.0;
let id = RenderedGlyphId::new(glyph.codepoint, glyph.font_id, paint, mode, subpixel_location as u8);
if !self.rendered_glyphs.borrow().contains_key(&id) {
let glyph = self.render_glyph(canvas, paint, mode, glyph)?;
self.rendered_glyphs.borrow_mut().insert(id, glyph);
}
let rendered_glyphs = self.rendered_glyphs.borrow();
let rendered = rendered_glyphs.get(&id).unwrap();
if let Some(texture) = self.glyph_textures.borrow().get(rendered.texture_index) {
let image_id = texture.image_id;
let size = texture.atlas.size();
let itw = 1.0 / size.0 as f32;
let ith = 1.0 / size.1 as f32;
let cmd_map = if rendered.color_glyph {
&mut color_cmd_map
} else {
&mut alpha_cmd_map
};
let cmd = cmd_map.entry(rendered.texture_index).or_insert_with(|| DrawCmd {
image_id,
quads: Vec::new(),
});
let mut q = Quad::default();
let line_width_offset = if rendered.color_glyph { 0. } else { line_width_offset };
q.x0 = glyph.x.trunc() - line_width_offset - GLYPH_PADDING as f32;
q.y0 = (glyph.y + glyph.bearing_y).round()
- rendered.bearing_y as f32
- line_width_offset
- GLYPH_PADDING as f32;
q.x1 = q.x0 + rendered.width as f32;
q.y1 = q.y0 + rendered.height as f32;
q.s0 = rendered.atlas_x as f32 * itw;
q.t0 = rendered.atlas_y as f32 * ith;
q.s1 = (rendered.atlas_x + rendered.width) as f32 * itw;
q.t1 = (rendered.atlas_y + rendered.height) as f32 * ith;
cmd.quads.push(q);
}
}
canvas.set_render_target(initial_render_target);
Ok(GlyphDrawCommands {
alpha_glyphs: alpha_cmd_map.drain().map(|(_, cmd)| cmd).collect(),
color_glyphs: color_cmd_map.drain().map(|(_, cmd)| cmd).collect(),
})
}
fn render_glyph<T: Renderer>(
&self,
canvas: &mut Canvas<T>,
paint: &Paint,
mode: RenderMode,
glyph: &ShapedGlyph,
) -> Result<RenderedGlyph, ErrorKind> {
let padding = GLYPH_PADDING + GLYPH_MARGIN;
let text_context = canvas.text_context.clone();
let mut text_context = text_context.borrow_mut();
let (mut maybe_glyph_representation, scale) = {
let font = text_context.font_mut(glyph.font_id).ok_or(ErrorKind::NoFontFound)?;
let scale = font.scale(paint.font_size);
let maybe_glyph_representation =
font.glyph_rendering_representation(glyph.codepoint as u16, paint.font_size as u16);
(maybe_glyph_representation, scale)
};
#[cfg(feature = "image-loading")]
let color_glyph = matches!(maybe_glyph_representation, Some(GlyphRendering::RenderAsImage(..)));
#[cfg(not(feature = "image-loading"))]
let color_glyph = false;
let line_width = if color_glyph || mode != RenderMode::Stroke {
0.0
} else {
paint.line_width
};
let line_width_offset = (line_width / 2.0).ceil();
let width = glyph.width.ceil() as u32 + (line_width_offset * 2.0) as u32 + padding * 2;
let height = glyph.height.ceil() as u32 + (line_width_offset * 2.0) as u32 + padding * 2;
let (dst_index, dst_image_id, (dst_x, dst_y)) =
self.find_texture_or_alloc(canvas, width as usize, height as usize)?;
canvas.save();
canvas.reset();
let rendered_bearing_y = glyph.bearing_y.round();
let x_quant = crate::geometry::quantize(glyph.x.fract(), 0.1);
let x = dst_x as f32 - glyph.bearing_x + line_width_offset + padding as f32 + x_quant;
let y = TEXTURE_SIZE as f32 - dst_y as f32 - rendered_bearing_y - line_width_offset - padding as f32;
let rendered_glyph = RenderedGlyph {
width: width - 2 * GLYPH_MARGIN,
height: height - 2 * GLYPH_MARGIN,
bearing_y: rendered_bearing_y as i32,
atlas_x: dst_x as u32 + GLYPH_MARGIN,
atlas_y: dst_y as u32 + GLYPH_MARGIN,
texture_index: dst_index,
padding: padding - GLYPH_MARGIN,
color_glyph,
};
match maybe_glyph_representation.as_mut() {
Some(GlyphRendering::RenderAsPath(ref mut path)) => {
canvas.translate(x, y);
canvas.set_render_target(RenderTarget::Image(dst_image_id));
canvas.clear_rect(
dst_x as u32,
TEXTURE_SIZE as u32 - dst_y as u32 - height as u32,
width as u32,
height as u32,
Color::black(),
);
let factor = 1.0 / 8.0;
let mut mask_paint = Paint::color(Color::rgbf(factor, factor, factor));
mask_paint.set_fill_rule(FillRule::EvenOdd);
mask_paint.set_anti_alias(false);
if mode == RenderMode::Stroke {
mask_paint.line_width = line_width / scale;
}
canvas.global_composite_blend_func(crate::BlendFactor::SrcAlpha, crate::BlendFactor::One);
let points = [
(-7.0 / 16.0, -1.0 / 16.0),
(-1.0 / 16.0, -5.0 / 16.0),
(3.0 / 16.0, -7.0 / 16.0),
(5.0 / 16.0, -3.0 / 16.0),
(7.0 / 16.0, 1.0 / 16.0),
(1.0 / 16.0, 5.0 / 16.0),
(-3.0 / 16.0, 7.0 / 16.0),
(-5.0 / 16.0, 3.0 / 16.0),
];
for point in &points {
canvas.save();
canvas.translate(point.0, point.1);
canvas.scale(scale, scale);
if mode == RenderMode::Stroke {
canvas.stroke_path(path, mask_paint);
} else {
canvas.fill_path(path, mask_paint);
}
canvas.restore();
}
}
#[cfg(feature = "image-loading")]
Some(GlyphRendering::RenderAsImage(image_buffer)) => {
use std::convert::TryFrom;
let target_x = rendered_glyph.atlas_x as usize;
let target_y = rendered_glyph.atlas_y as usize;
let target_width = rendered_glyph.width as u32;
let target_height = rendered_glyph.height as u32;
let image_buffer =
image_buffer.resize(target_width, target_height, image::imageops::FilterType::Nearest);
if let Ok(image) = crate::image::ImageSource::try_from(&image_buffer) {
canvas.update_image(dst_image_id, image, target_x, target_y).unwrap();
}
}
_ => {}
}
canvas.restore();
Ok(rendered_glyph)
}
fn find_texture_or_alloc<T: Renderer>(
&self,
canvas: &mut Canvas<T>,
width: usize,
height: usize,
) -> Result<(usize, ImageId, (usize, usize)), ErrorKind> {
let mut texture_search_result = {
let mut glyph_textures = self.glyph_textures.borrow_mut();
let mut textures = glyph_textures.iter_mut().enumerate();
textures.find_map(|(index, texture)| {
texture
.atlas
.add_rect(width, height)
.map(|loc| (index, texture.image_id, loc))
})
};
if texture_search_result.is_none() {
let mut atlas = Atlas::new(TEXTURE_SIZE, TEXTURE_SIZE);
let loc = atlas
.add_rect(width, height)
.ok_or(ErrorKind::FontSizeTooLargeForAtlas)?;
let info = ImageInfo::new(ImageFlags::empty(), atlas.size().0, atlas.size().1, PixelFormat::Rgba8);
let image_id = canvas.images.alloc(&mut canvas.renderer, info)?;
#[cfg(feature = "debug_inspector")]
if cfg!(debug_assertions) {
if let Ok(size) = canvas.image_size(image_id) {
#[cfg(feature = "image-loading")]
{
use rgb::FromSlice;
let clear_image = image::RgbaImage::from_pixel(
size.0 as u32,
size.1 as u32,
image::Rgba::<u8>([255, 0, 0, 0]),
);
canvas
.update_image(
image_id,
crate::image::ImageSource::from(imgref::Img::new(
clear_image.as_ref().as_rgba(),
clear_image.width() as usize,
clear_image.height() as usize,
)),
0,
0,
)
.unwrap();
}
#[cfg(not(feature = "image-loading"))]
{
canvas.save();
canvas.reset();
canvas.set_render_target(RenderTarget::Image(image_id));
canvas.clear_rect(
0,
0,
size.0 as u32,
size.1 as u32,
Color::rgb(255, 0, 0), );
canvas.restore();
}
}
}
self.glyph_textures.borrow_mut().push(FontTexture { atlas, image_id });
let index = self.glyph_textures.borrow().len() - 1;
texture_search_result = Some((index, image_id, loc));
}
texture_search_result.ok_or(ErrorKind::UnknownError)
}
pub(crate) fn clear<T: Renderer>(&self, canvas: &mut Canvas<T>) {
let image_ids = std::mem::take(&mut *self.glyph_textures.borrow_mut())
.into_iter()
.map(|font_texture| font_texture.image_id);
image_ids.for_each(|id| canvas.delete_image(id));
self.rendered_glyphs.borrow_mut().clear();
}
}
pub(crate) fn render_direct<T: Renderer>(
canvas: &mut Canvas<T>,
text_layout: &TextMetrics,
paint: &Paint,
mode: RenderMode,
invscale: f32,
) -> Result<(), ErrorKind> {
let mut paint = *paint;
paint.set_fill_rule(FillRule::EvenOdd);
let text_context = canvas.text_context.clone();
let mut text_context = text_context.borrow_mut();
let mut scaled = false;
for glyph in &text_layout.glyphs {
let (glyph_rendering, scale) = {
let font = text_context.font_mut(glyph.font_id).ok_or(ErrorKind::NoFontFound)?;
let scale = font.scale(paint.font_size);
let glyph_rendering = if let Some(glyph_rendering) =
font.glyph_rendering_representation(glyph.codepoint as u16, paint.font_size as u16)
{
glyph_rendering
} else {
continue;
};
(glyph_rendering, scale)
};
canvas.save();
if mode == RenderMode::Stroke && !scaled {
paint.line_width /= scale;
scaled = true;
}
canvas.translate(
(glyph.x - glyph.bearing_x) * invscale,
(glyph.y + glyph.bearing_y) * invscale,
);
canvas.scale(scale * invscale, -scale * invscale);
match glyph_rendering {
GlyphRendering::RenderAsPath(path) => {
if mode == RenderMode::Stroke {
canvas.stroke_path(path, paint);
} else {
canvas.fill_path(path, paint);
}
}
#[cfg(feature = "image-loading")]
GlyphRendering::RenderAsImage(_) => unreachable!(),
}
canvas.restore();
}
Ok(())
}