use crate::direction::{direction, Direction};
use footile::{PathOp, Pt};
use ttf_parser::kern::Subtable;
#[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>);
#[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 = LangFont(ttf_parser::Font::from_data(none.into(), 0)?);
self.fonts.push(StyledFont { none });
Some(self)
}
pub fn render(
&'a self,
text: &'a str,
row: 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 + 1);
break;
}
chr if chr == BOLD => continue,
chr if chr == ITALIC => continue,
chr if chr == 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 fh = selected_font.0.height() as f32;
let font_size = (fh.recip(), fh.recip());
let advance = match selected_font.0.glyph_hor_advance(glyph_id) {
Some(adv) => {
font_size.0
* (f32::from(adv)
+ if let Some(last) = last {
selected_font
.0
.kerning_subtables()
.next()
.unwrap_or_else(Subtable::default)
.glyphs_kerning(glyph_id, last)
.unwrap_or(0)
.into()
} else {
0f32
})
}
None => 0.0,
};
pixel_length += advance;
if pixel_length > row {
if last_space != 0 {
left_over = Some(last_space + 1);
break;
} else {
left_over = Some(i + 1);
break;
}
}
last = Some(glyph_id);
}
let mut xy = (0.0, 0.0);
let mut vertical = false;
match text_align {
TextAlign::Left => { }
TextAlign::Right => xy.0 = row - pixel_length,
TextAlign::Center => xy.0 = (row - pixel_length) * 0.5,
TextAlign::Justified => { }
TextAlign::Vertical => vertical = true,
}
(
TextPathIterator {
text: if let Some(i) = left_over {
text[..i].chars().peekable()
} else {
text.chars().peekable()
},
temp: vec![],
back: false,
path: CharPathIterator::new(self, xy, vertical),
},
left_over.unwrap_or_else(|| text.bytes().len()),
)
}
}
struct CharPathIterator<'a> {
font: &'a Font<'a>,
path: Vec<PathOp>,
xy: (f32, f32),
direction: Direction,
last: Option<ttf_parser::GlyphId>,
vertical: bool,
bold: bool,
italic: bool,
font_ascender: f32,
font_size: (f32, f32),
}
impl<'a> CharPathIterator<'a> {
fn new(font: &'a Font<'a>, xy: (f32, f32), vertical: bool) -> Self {
Self {
font,
path: vec![],
xy,
direction: Direction::CheckNext,
last: None,
vertical,
bold: false,
italic: false,
font_ascender: 0.0,
font_size: (0.0, 0.0),
}
}
fn set(&mut self, c: char) {
match c {
BOLD => {
self.bold = true;
return;
}
ITALIC => {
self.italic = true;
return;
}
NONE => {
self.bold = false;
self.italic = false;
return;
}
'\n' => return,
_ => {}
}
if self.direction == Direction::CheckNext {
self.direction = direction(c);
}
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();
let font_height = selected_font.0.height() as f32;
self.font_ascender = selected_font.0.ascender() as f32;
self.font_size = (font_height.recip(), font_height.recip());
selected_font.0.outline_glyph(glyph_id, self);
if self.vertical {
self.xy.1 += 1.0;
} else {
let advance = match selected_font.0.glyph_hor_advance(glyph_id) {
Some(adv) => {
self.font_size.0
* (f32::from(adv)
+ if let Some(last) = self.last {
selected_font
.0
.kerning_subtables()
.next()
.unwrap_or_else(Subtable::default)
.glyphs_kerning(glyph_id, last)
.unwrap_or(0)
.into()
} else {
0f32
})
}
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) {
let y = self.font_ascender - y;
self.path.push(PathOp::Move(Pt(
x * self.font_size.0 + self.xy.0,
y * self.font_size.1 + self.xy.1,
)));
}
fn line_to(&mut self, x: f32, y: f32) {
let y = self.font_ascender - y;
self.path.push(PathOp::Line(Pt(
x * self.font_size.0 + self.xy.0,
y * self.font_size.1 + self.xy.1,
)));
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
let y1 = self.font_ascender - y1;
let y = self.font_ascender - y;
self.path.push(PathOp::Quad(
Pt(
x1 * self.font_size.0 + self.xy.0,
y1 * self.font_size.1 + self.xy.1,
),
Pt(
x * self.font_size.0 + self.xy.0,
y * self.font_size.1 + self.xy.1,
),
));
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
let y1 = self.font_ascender - y1;
let y2 = self.font_ascender - y2;
let y = self.font_ascender - y;
self.path.push(PathOp::Cubic(
Pt(
x1 * self.font_size.0 + self.xy.0,
y1 * self.font_size.1 + self.xy.1,
),
Pt(
x2 * self.font_size.0 + self.xy.0,
y2 * self.font_size.1 + self.xy.1,
),
Pt(
x * self.font_size.0 + self.xy.0,
y * self.font_size.1 + self.xy.1,
),
));
}
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")
}