use crate::direction::{direction, Direction};
use footile::PathOp;
#[derive(Copy, Clone, Debug)]
pub enum TextAlign {
Left,
Center,
Right,
Justified,
Vertical,
}
pub const NONE: char = '\x01';
pub const BOLD: char = '\x02';
pub const ITALIC: char = '\x03';
#[derive(Debug)]
struct LangFont<'a>(ttf_parser::Font<'a>, f32);
#[derive(Debug)]
struct StyledFont<'a> {
none: LangFont<'a>,
}
#[derive(Default, Debug)]
pub struct Font<'a> {
fonts: Vec<StyledFont<'a>>,
}
impl<'a> Font<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn push<B: Into<&'a [u8]>>(mut self, none: B) -> Option<Self> {
let none = ttf_parser::Font::from_data(none.into(), 0)?;
let em_per_height = f32::from(none.height()).recip();
let none = LangFont(none, em_per_height);
self.fonts.push(StyledFont { none });
Some(self)
}
pub fn render(
&'a self,
text: &'a str,
bbox: (f32, f32, f32, f32),
wh: (f32, f32),
text_align: TextAlign,
) -> (TextPathIterator<'a>, usize) {
let mut pixel_length = 0.0;
let mut last = None;
let mut left_over = None;
let mut last_space = 0;
for (i, c) in text.char_indices() {
match c {
' ' => last_space = i,
'\n' => {
left_over = Some(i);
break;
}
_ if c == BOLD => continue,
_ if c == ITALIC => continue,
_ if c == NONE => continue,
_ => {}
}
let mut index = 0;
let glyph_id = loop {
match self.fonts[index].none.0.glyph_index(c) {
Some(v) => break v,
None => {
index += 1;
if index == self.fonts.len() {
index = 0;
break self.fonts[0]
.none
.0
.glyph_index('�')
.unwrap();
}
}
}
};
let selected_font = &self.fonts[index].none;
let wh = (wh.0 * selected_font.1, -wh.1 * selected_font.1);
let advance = match selected_font.0.glyph_hor_advance(glyph_id) {
Some(adv) => {
(f32::from(adv)
+ if let Some(last) = last {
selected_font
.0
.glyphs_kerning(glyph_id, last)
.unwrap_or(0)
.into()
} else {
0f32
})
* wh.0
}
None => 0.0,
};
pixel_length += advance;
if pixel_length > bbox.2 {
if last_space != 0 {
left_over = Some(last_space);
break;
} else {
left_over = Some(i);
break;
}
}
last = Some(glyph_id);
}
let mut bbox = (bbox.0, bbox.1, bbox.0 + bbox.2, bbox.1 + bbox.3);
let mut vertical = false;
match text_align {
TextAlign::Left => { }
TextAlign::Right => bbox.0 = bbox.2 - pixel_length,
TextAlign::Center => {
bbox.0 = (bbox.0 + bbox.2 - pixel_length) * 0.5
}
TextAlign::Justified => { }
TextAlign::Vertical => vertical = true,
}
bbox.1 += wh.1;
(
TextPathIterator {
text: if let Some(i) = left_over {
text[..i].chars().peekable()
} else {
text.chars().peekable()
},
temp: vec![],
back: false,
path: CharPathIterator::new(self, bbox, wh, vertical),
},
left_over.unwrap_or_else(|| text.bytes().len()),
)
}
}
struct CharPathIterator<'a> {
font: &'a Font<'a>,
path: Vec<PathOp>,
size: (f32, f32),
bbox: (f32, f32, f32, f32),
wh: (f32, f32),
xy: (f32, f32),
direction: Direction,
last: Option<ttf_parser::GlyphId>,
vertical: bool,
bold: bool,
italic: bool,
offset: f32,
}
impl<'a> CharPathIterator<'a> {
fn new(
font: &'a Font<'a>,
bbox: (f32, f32, f32, f32),
size: (f32, f32),
vertical: bool,
) -> Self {
Self {
font,
path: vec![],
bbox,
size,
wh: (0.0, 0.0),
xy: (bbox.0, bbox.1),
direction: Direction::CheckNext,
last: None,
vertical,
bold: false,
italic: false,
offset: 0.0,
}
}
fn set(&mut self, c: char) {
if c == BOLD {
self.bold = true;
return;
} else if c == ITALIC {
self.italic = true;
return;
} else if c == NONE {
self.bold = false;
self.italic = false;
return;
}
if self.direction == Direction::CheckNext {
self.direction = direction(c);
self.xy = (self.bbox.0, self.bbox.1);
}
let mut index = 0;
let glyph_id = loop {
match self.font.fonts[index].none.0.glyph_index(c) {
Some(v) => break v,
None => {
index += 1;
if index == self.font.fonts.len() {
index = 0;
break self.font.fonts[0]
.none
.0
.glyph_index('�')
.unwrap();
}
}
}
};
let selected_font = &self.font.fonts[index].none;
self.path.clear();
self.wh = (
self.size.0 * selected_font.1,
-self.size.1 * selected_font.1,
);
let em_per_unit =
f32::from(selected_font.0.units_per_em().ok_or("em").unwrap())
.recip();
let h = selected_font.1 * self.size.1 / em_per_unit;
self.offset = -self.size.1 + h;
selected_font.0.outline_glyph(glyph_id, self);
if self.vertical {
self.xy.1 += self.size.1;
} else {
let advance = match selected_font.0.glyph_hor_advance(glyph_id) {
Some(adv) => {
(f32::from(adv)
+ if let Some(last) = self.last {
selected_font
.0
.glyphs_kerning(glyph_id, last)
.unwrap_or(0)
.into()
} else {
0f32
})
* self.wh.0
}
None => 0.0,
};
self.xy.0 += advance;
}
self.path.reverse();
self.last = Some(glyph_id);
}
}
impl ttf_parser::OutlineBuilder for CharPathIterator<'_> {
fn move_to(&mut self, x: f32, y: f32) {
self.path.push(PathOp::Move(
x * self.wh.0 + self.xy.0,
y * self.wh.1 + self.xy.1 + self.offset,
));
}
fn line_to(&mut self, x: f32, y: f32) {
self.path.push(PathOp::Line(
x * self.wh.0 + self.xy.0,
y * self.wh.1 + self.xy.1 + self.offset,
));
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
self.path.push(PathOp::Quad(
x1 * self.wh.0 + self.xy.0,
y1 * self.wh.1 + self.xy.1 + self.offset,
x * self.wh.0 + self.xy.0,
y * self.wh.1 + self.xy.1 + self.offset,
));
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
self.path.push(PathOp::Cubic(
x1 * self.wh.0 + self.xy.0,
y1 * self.wh.1 + self.xy.1 + self.offset,
x2 * self.wh.0 + self.xy.0,
y2 * self.wh.1 + self.xy.1 + self.offset,
x * self.wh.0 + self.xy.0,
y * self.wh.1 + self.xy.1 + self.offset,
));
}
fn close(&mut self) {
self.path.push(PathOp::Close());
}
}
impl Iterator for CharPathIterator<'_> {
type Item = PathOp;
fn next(&mut self) -> Option<Self::Item> {
self.path.pop()
}
}
#[allow(missing_debug_implementations)]
pub struct TextPathIterator<'a> {
text: std::iter::Peekable<std::str::Chars<'a>>,
temp: Vec<char>,
back: bool,
path: CharPathIterator<'a>,
}
impl Iterator for TextPathIterator<'_> {
type Item = PathOp;
fn next(&mut self) -> Option<PathOp> {
if let Some(op) = self.path.next() {
Some(op)
} else if let Some(c) = self.text.peek() {
let dir = direction(*c);
let dir = if dir == Direction::CheckNext {
if self.back {
Direction::RightLeft
} else {
Direction::LeftRight
}
} else {
dir
};
if dir == Direction::RightLeft {
let c = self.text.next().unwrap();
if !self.back {
self.back = true;
}
self.temp.push(c);
} else if let Some(c) = self.temp.pop() {
self.path.set(c);
} else {
let c = self.text.next().unwrap();
self.path.set(c);
}
self.next()
} else if let Some(c) = self.temp.pop() {
self.path.set(c);
self.next()
} else {
None
}
}
}
#[cfg(feature = "monospace-font")]
pub fn monospace_font() -> Font<'static> {
const FONTA: &[u8] = include_bytes!("font/dejavu/SansMono.ttf");
const FONTB: &[u8] = include_bytes!("font/noto/SansDevanagari.ttf");
const FONTC: &[u8] = include_bytes!("font/noto/SansHebrew.ttf");
const FONTD: &[u8] = include_bytes!("font/droid/SansFallback.ttf");
Font::new()
.push(FONTA)
.unwrap()
.push(FONTB)
.unwrap()
.push(FONTC)
.unwrap()
.push(FONTD)
.unwrap()
}
#[cfg(feature = "normal-font")]
pub fn normal_font() -> Font<'static> {
const FONTA: &[u8] = include_bytes!("font/dejavu/Sans.ttf");
const FONTB: &[u8] = include_bytes!("font/noto/SansDevanagari.ttf");
const FONTC: &[u8] = include_bytes!("font/noto/SansHebrew.ttf");
const FONTD: &[u8] = include_bytes!("font/droid/SansFallback.ttf");
Font::new()
.push(FONTA)
.unwrap()
.push(FONTB)
.unwrap()
.push(FONTC)
.unwrap()
.push(FONTD)
.unwrap()
}
#[cfg(any(feature = "monospace-font", feature = "normal-font"))]
pub fn licenses() -> &'static str {
include_str!("bin-licenses.txt")
}