use freetypegl::{TextureFont, TextureGlyph};
use super::{Mesh, Vertex2DTex, Vertex2DTexColor, CoordinateOrigin, PrimitiveType};
use rin_math::{Vec2, Pnt2, pnt2, vec2, Rect, ToVec};
use rin_util::{Error, Result};
use std::f32;
use smallvec::{smallvec, SmallVec};
use color::ToRgba;
#[cfg(feature = "harfbuzz")]
use harfbuzz::sys::*;
#[cfg(feature = "harfbuzz")]
use std::ptr;
#[cfg(feature = "harfbuzz")]
use std::mem;
#[cfg(feature = "harfbuzz")]
use harfbuzz_ft_sys::hb_ft_font_set_funcs;
#[cfg(not(feature = "harfbuzz"))]
use std::iter;
#[derive(Clone,Copy,Debug,PartialEq,Eq)]
pub enum Antialiasing{
None=0,
Gray=1,
LCD=3
}
bitflags::bitflags!{
pub struct BoxFlags: u32 {
const Y_START_AT_BASELINE = 1 << 0;
const CUT_LETTERS = 1 << 1;
}
}
pub struct BoxMesh<V> {
pub mesh: Mesh<V>,
pub partial: bool,
pub last_position: Pnt2,
pub next_position: Pnt2,
pub rows: u32,
}
unsafe impl Send for Ttf{}
pub struct Ttf{
font: TextureFont,
antialiasing_type: Antialiasing,
#[cfg(feature = "harfbuzz")]
harfbuzz: *mut hb_font_t,
#[cfg(feature = "harfbuzz")]
hb_properties: hb_segment_properties_t,
}
#[derive(Clone, Copy, Debug)]
pub enum BoxCoordinatesX{
Pixels(f32),
Columns(u32)
}
#[derive(Clone, Copy, Debug)]
pub enum BoxCoordinatesY{
Pixels(f32),
Rows(u32)
}
#[macro_export]
macro_rules! hb_tag{
($a: expr, $b: expr, $c: expr, $d: expr) => {
((stringify!($a).as_bytes()[0] as u32) & 0xFF) << 24
| ((stringify!($b).as_bytes()[0] as u32) & 0xFF) << 16
| ((stringify!($c).as_bytes()[0] as u32) & 0xFF) << 8
| ((stringify!($d).as_bytes()[0] as u32) & 0xFF)
}
}
pub struct Builder<'a>{
path: Option<&'a str>,
bytes: Option<&'a [u8]>,
pt_height: f32,
antialiasing: Antialiasing,
}
impl<'a> Builder<'a>{
pub fn new(path: &'a str, pt_height: f32) -> Builder<'a>{
Builder{
path: Some(path),
bytes: None,
pt_height,
antialiasing: Antialiasing::LCD,
}
}
pub fn from_bytes(bytes: &'a [u8], pt_height: f32) -> Builder<'a>{
Builder{
path: None,
bytes: Some(bytes),
pt_height,
antialiasing: Antialiasing::LCD,
}
}
pub fn antialiasing(mut self, antialiasing: Antialiasing) -> Builder<'a>{
self.antialiasing = antialiasing;
self
}
pub fn build(self) -> Result<Ttf>{
let tex_font = if let Some(path) = self.path{
TextureFont::load(path, self.pt_height, self.antialiasing as usize)
.ok_or(Error::new("Couldn't load font from path"))?
}else{
TextureFont::load_from_memory(self.bytes.unwrap().to_vec(), self.pt_height, self.antialiasing as usize)
.ok_or(Error::new("Couldn't load font bytes"))?
};
#[cfg(feature = "harfbuzz")]
{
let harfbuzz = unsafe{ hb_ft_font_create_referenced(std::mem::transmute(tex_font.face())) };
if harfbuzz == ptr::null_mut() {
return Err(Error::new("Couldn't load harfbuzz font"));
}
unsafe{ hb_ft_font_set_funcs(harfbuzz) };
let mut buffer = harfbuzz::Buffer::new();
buffer.set_language(harfbuzz::Language::from_string("en"));
buffer.set_direction(harfbuzz::Direction::LTR);
buffer.set_script(HB_SCRIPT_LATIN);
let hb_properties = unsafe{
let mut hb_properties = mem::MaybeUninit::uninit();
hb_buffer_get_segment_properties(buffer.as_ptr(), hb_properties.as_mut_ptr());
hb_properties.assume_init()
};
Ok( Ttf{
font: tex_font,
antialiasing_type: self.antialiasing,
harfbuzz,
hb_properties,
})
}
#[cfg(not(feature = "harfbuzz"))]
{
Ok( Ttf{
font: tex_font,
antialiasing_type: self.antialiasing,
})
}
}
}
impl Ttf{
pub fn line_height(&self) -> f32{
self.font.height()
}
pub fn ascender(&self) -> f32{
self.font.ascender()
}
pub fn descender(&self) -> f32{
self.font.descender()
}
pub fn line_gap(&self) -> f32{
self.font.height() - self.font.ascender()
}
pub fn antialiasing_type(&self) -> Antialiasing{
self.antialiasing_type
}
pub fn mesh(&self, string: &str, pos: &Pnt2, coordinate_origin: CoordinateOrigin) -> Mesh<Vertex2DTex>{
let mut mesh = Mesh::new(Vec::with_capacity(string.len()), vec![], PrimitiveType::Triangles);
mesh.extend(self.shape_iter(string, pos, coordinate_origin)
.flat_map(|shape| shape.to_vertices()));
mesh
}
pub fn mesh_color<C: ToRgba>(&self, string: &str, pos: &Pnt2, coordinate_origin: CoordinateOrigin, color: &C) -> Mesh<Vertex2DTexColor>{
let mut mesh = Mesh::new(Vec::with_capacity(string.len()), vec![], PrimitiveType::Triangles);
mesh.extend(self.shape_iter(string, pos, coordinate_origin)
.flat_map(|shape| shape.to_vertices_color(color)));
mesh
}
pub fn append_mesh(&self, string: &str, pos: &Pnt2, coordinate_origin: CoordinateOrigin, mesh: &mut Mesh<Vertex2DTex>){
mesh.extend(self.shape_iter(string, pos, coordinate_origin)
.flat_map(|shape| shape.to_vertices()));
}
pub fn append_mesh_color<C: ToRgba>(&self, string: &str, pos: &Pnt2, coordinate_origin: CoordinateOrigin, color: &C, mesh: &mut Mesh<Vertex2DTexColor>){
mesh.extend(self.shape_iter(string, pos, coordinate_origin)
.flat_map(|shape| shape.to_vertices_color(color)));
}
pub fn bounding_box(&self, string: &str, pos: &Pnt2, coordinate_origin: CoordinateOrigin) -> Rect<f32>{
if string.is_empty(){
Rect{pos: *pos, width: 0., height: 0.}
}else{
let mut min = pnt2(f32::MAX as f32, f32::MAX as f32);
let mut max = pnt2(f32::MIN as f32, f32::MIN as f32);
for shape in self.shape_iter(string, pos, coordinate_origin){
let bl = vec2(shape.rect.min_x(), shape.rect.min_y());
let tr = vec2(shape.rect.max_x(), shape.rect.max_y());
min.x = min.x.min(bl.x);
min.y = min.y.min(bl.y);
max.x = max.x.max(tr.x);
max.y = max.y.max(tr.y);
}
Rect{pos: min, width: max.x-min.x, height: max.y-min.y}
}
}
#[cfg(not(feature="harfbuzz"))]
pub fn width(&self, string: &str) -> f32 {
if string.is_empty() {
0.
}else{
let mut min = f32::MAX;
let mut max = f32::MIN;
for glyph_shape in self.shape_iter(string, &pnt2!(0.), CoordinateOrigin::TopLeft){
min = min.min(glyph_shape.rect.pos.x);
max = max.max(glyph_shape.rect.pos.x + glyph_shape.rect.width);
}
max - min
}
}
#[cfg(feature="harfbuzz")]
pub fn width(&self, string: &str) -> f32 {
if string.is_empty() {
0.
}else{
let mut buffer = harfbuzz::Buffer::new();
let (min, max) = string.lines().fold((0, 0), |(lines_min, lines_max), line| {
buffer.clear_contents();
buffer.add_str(line);
unsafe{
hb_buffer_set_segment_properties(buffer.as_ptr(), &self.hb_properties)
};
let (positions, glyphs) = unsafe {
let buffer = buffer.as_ptr();
hb_shape(
self.harfbuzz,
buffer,
ptr::null(),
0
);
let mut length = 0;
let positions = hb_buffer_get_glyph_positions(buffer, &mut length);
let positions = std::slice::from_raw_parts(positions, length as usize);
let glyphs = hb_buffer_get_glyph_infos(buffer, &mut length);
let glyphs = std::slice::from_raw_parts(glyphs, length as usize);
(positions, glyphs)
};
let min = positions.first().map(|p| p.x_offset.min(0)).unwrap_or(0) / 64;
let max = positions.iter().zip(glyphs)
.fold((0, lines_max), |(x, max), (p, g)| {
let glyph = self.font.glyph_by_freetype_id(g.codepoint)
.or_else(|| self.font.glyph('?'))
.unwrap();
let xo = p.x_offset / 64;
let x0 = x + xo + glyph.offset_x();
let glyph_max = x + x0 + glyph.width() as i32;
(x + p.x_advance / 64, max.max(glyph_max))
}).1;
(min.min(lines_min), max)
});
(max - min) as f32
}
}
pub fn box_mesh<H>(
&self,
string: &str,
pos: &Pnt2,
w: BoxCoordinatesX,
h: H,
coordinate_origin: CoordinateOrigin,
flags: BoxFlags) -> BoxMesh<Vertex2DTex>
where H: Into<Option<BoxCoordinatesY>>
{
let mut next_position = *pos;
let mut last_position = *pos;
let mut partial = true;
let mut rows = 1;
let mesh = self
.box_shape_iter(string, pos, w, h, coordinate_origin, flags)
.flat_map(|shape| {
next_position = shape.next_position;
last_position = shape.last_position;
partial = !shape.is_last;
rows = shape.row + 1;
shape.to_vertices()
}).collect();
BoxMesh{
mesh,
next_position,
last_position,
partial,
rows,
}
}
pub fn box_mesh_color<H, C>(
&self,
string: &str,
pos: &Pnt2,
w: BoxCoordinatesX,
h: H,
coordinate_origin: CoordinateOrigin,
flags: BoxFlags,
color: &C) -> BoxMesh<Vertex2DTexColor>
where
H: Into<Option<BoxCoordinatesY>>,
C: ToRgba,
{
let mut next_position = *pos;
let mut last_position = *pos;
let mut partial = true;
let mut rows = 1;
let mesh = self
.box_shape_iter(string, pos, w, h, coordinate_origin, flags)
.flat_map(|shape| {
next_position = shape.next_position;
last_position = shape.last_position;
partial = !shape.is_last;
rows = shape.row + 1;
shape.to_vertices_color(color)
}).collect();
BoxMesh{
mesh,
next_position,
last_position,
partial,
rows,
}
}
pub fn box_size(&self, string: &str, w: BoxCoordinatesX) -> Vec2 {
if string.is_empty(){
return vec2!(0.)
}else{
let box_shape = self.box_shape_iter(
string,
&Pnt2::origin(),
w,
None,
CoordinateOrigin::TopLeft,
BoxFlags::empty()
);
let (min, max) = box_shape
.fold((pnt2!(0f32), pnt2!(0f32)), |(mut min, mut max), shape|
{
min.x = min.x.min(shape.rect.min_x());
min.y = min.y.min(shape.rect.min_y());
max.x = max.x.max(shape.rect.max_x());
max.y = max.y.max(shape.rect.max_y());
(min, max)
});
max - min
}
}
pub fn next_pos(&self, string: &str, pos: &Pnt2, coordinate_origin: CoordinateOrigin) -> Pnt2 {
self.shape_iter(string, pos, coordinate_origin)
.last()
.map(|shape| shape.next_position)
.unwrap_or(*pos)
}
#[cfg(not(feature="harfbuzz"))]
pub fn shape_iter<'a>(&'a self, string: &'a str, pos: &'a Pnt2, coordinate_origin: CoordinateOrigin) -> impl Iterator<Item = Shape> + 'a{
let mut xpos = pos.x;
let mut ypos = match coordinate_origin{
CoordinateOrigin::BottomLeft | CoordinateOrigin::CenterUp => pos.y + self.line_height(),
CoordinateOrigin::TopLeft | CoordinateOrigin::CenterDown => pos.y - self.line_height(),
};
let line_height = match coordinate_origin{
CoordinateOrigin::BottomLeft | CoordinateOrigin::CenterUp => -self.line_height(),
CoordinateOrigin::TopLeft | CoordinateOrigin::CenterDown => self.line_height(),
};
let total_num_lines = string.lines().count();
string.lines().enumerate().flat_map(move |(line_num, l)|{
xpos = pos.x;
ypos += line_height;
let mut prevc = None;
let line_num_chars = l.chars().count();
l.chars().enumerate().map(move |(char_num, c)|{
let glyph = self.font.glyph(c)
.or_else(|| self.font.glyph('?'))
.unwrap();
if let Some(prevc) = prevc {
xpos += glyph.kerning(prevc);
}
let (bl,tr) = match coordinate_origin{
CoordinateOrigin::BottomLeft | CoordinateOrigin::CenterUp => {
let x0 = ( xpos + glyph.offset_x() as f32 ) as i32 as f32;
let y1 = ( ypos + glyph.offset_y() as f32 ) as i32 as f32;
let x1 = ( x0 + glyph.width() as f32 ) as i32 as f32;
let y0 = ( y1 - glyph.height() as f32 ) as i32 as f32;
(pnt2(x0,y0), pnt2(x1,y1))
}
CoordinateOrigin::TopLeft | CoordinateOrigin::CenterDown => {
let x0 = ( xpos + glyph.offset_x() as f32 ) as i32 as f32;
let y0 = ( ypos - glyph.offset_y() as f32 ) as i32 as f32;
let x1 = ( x0 + glyph.width() as f32 ) as i32 as f32;
let y1 = ( y0 + glyph.height() as f32 ) as i32 as f32;
(pnt2(x0,y1), pnt2(x1,y0))
}
};
xpos += glyph.advance_x();
prevc = Some(c);
Shape{
rect: Rect{pos: bl, width: tr.x - bl.x, height: tr.y - bl.y},
glyph,
next_position: pnt2(xpos, ypos),
last_position: tr,
is_last: total_num_lines == line_num + 1 && line_num_chars == char_num + 1,
row: line_num as u32,
col: char_num as u32,
}
})
})
}
#[cfg(feature="harfbuzz")]
pub fn shape_iter<'a>(&'a self, string: &'a str, pos: &'a Pnt2, coordinate_origin: CoordinateOrigin) -> impl Iterator<Item = Shape> + 'a{
let mut buffer = harfbuzz::Buffer::new();
let line_height = match coordinate_origin{
CoordinateOrigin::BottomLeft | CoordinateOrigin::CenterUp => -self.line_height(),
CoordinateOrigin::TopLeft | CoordinateOrigin::CenterDown => self.line_height(),
};
let mut y = pos.y - line_height;
string.lines().enumerate().flat_map(move |(row, line)| {
buffer.clear_contents();
unsafe{
hb_buffer_set_segment_properties(buffer.as_ptr(), &self.hb_properties)
};
buffer.add_str(line);
let (mut positions, mut glyphs) = unsafe {
hb_shape(
self.harfbuzz,
buffer.as_ptr(),
ptr::null(),
0,
);
let mut length = 0;
let positions = hb_buffer_get_glyph_positions(buffer.as_ptr(), &mut length);
let positions = std::slice::from_raw_parts(positions, length as usize);
let glyphs = hb_buffer_get_glyph_infos(buffer.as_ptr(), &mut length);
let glyphs = std::slice::from_raw_parts(glyphs, length as usize);
(positions.into_iter().peekable(), glyphs.into_iter())
};
let mut x = pos.x;
y += line_height;
let mut col = 0;
std::iter::from_fn(move || {
let position = positions.next()?;
let glyph_info = glyphs.next()?;
let glyph = self.font.glyph_by_freetype_id(glyph_info.codepoint)
.or_else(|| self.font.glyph('?'))
.unwrap();
let xa = (position.x_advance / 64) as f32;
let xo = (position.x_offset / 64) as f32;
let yo = (position.y_offset / 64) as f32;
let x0 = x + xo + glyph.offset_x() as f32;
let y0;
let ya;
let height;
match coordinate_origin {
CoordinateOrigin::TopLeft | CoordinateOrigin::CenterDown => {
y0 = (y - yo - glyph.offset_y() as f32 + glyph.height() as f32).floor();
ya = (position.y_advance / 64) as f32;
height = -(glyph.height() as f32);
}
CoordinateOrigin::BottomLeft | CoordinateOrigin::CenterUp => {
y0 = (y + yo + glyph.offset_y() as f32 - glyph.height() as f32).floor();
ya = -(position.y_advance / 64) as f32;
height = glyph.height() as f32;
}
}
x += xa;
y += ya;
let shape = Some(Shape {
rect: Rect{
pos: pnt2(x0, y0),
width: glyph.width() as f32,
height: height,
},
next_position: pnt2(x, y),
last_position: pnt2(x0 + glyph.width() as f32, y0 + height),
glyph,
is_last: positions.peek().is_none(),
row: row as u32,
col,
});
col += 1;
shape
})
})
}
#[cfg(not(feature="harfbuzz"))]
pub fn box_shape_iter<'a, H>(
&'a self,
string: &'a str,
pos: &Pnt2,
w: BoxCoordinatesX,
h: H,
coordinate_origin: CoordinateOrigin,
flags: BoxFlags) -> impl Iterator<Item = Shape> + 'a
where H: Into<Option<BoxCoordinatesY>>
{
let line_height = match coordinate_origin {
CoordinateOrigin::BottomLeft | CoordinateOrigin::CenterUp => -self.line_height(),
CoordinateOrigin::TopLeft | CoordinateOrigin::CenterDown => self.line_height(),
};
let mut xpos = pos.x;
let mut ypos = if flags.contains(BoxFlags::Y_START_AT_BASELINE) {
pos.y
}else{
match coordinate_origin {
CoordinateOrigin::BottomLeft | CoordinateOrigin::CenterUp => pos.y - self.ascender(),
CoordinateOrigin::TopLeft | CoordinateOrigin::CenterDown => pos.y + self.ascender(),
}
};
let start_x = pos.x;
let start_y = pos.y;
let h = h.into();
let reached_height = move |ypos: f32, row: u32|{
match h {
Some(BoxCoordinatesY::Pixels(h)) => match coordinate_origin {
CoordinateOrigin::BottomLeft | CoordinateOrigin::CenterUp =>
ypos + self.descender() < start_y - h,
CoordinateOrigin::TopLeft | CoordinateOrigin::CenterDown =>
ypos - self.descender() > start_y + h,
},
Some(BoxCoordinatesY::Rows(max_rows)) => max_rows < row + 1,
None => false,
}
};
let reached_width = move |xpos: f32, col: u32|{
match w {
BoxCoordinatesX::Pixels(w) => xpos > start_x + w + 1.0,
BoxCoordinatesX::Columns(max_cols) => col + 1 > max_cols,
}
};
let mut row = 0;
let mut char_i = 0;
let total_len = string.lines().map(|l| l.chars().count()).sum::<usize>();
let mut done = false;
let mut first = true;
string.lines().flat_map(move |paragraph| {
let mut last = 0;
let mut indices = paragraph.match_indices(|c: char| c.is_whitespace());
let mut words = iter::from_fn(move || {
if let Some((index, matched)) = indices.next(){
let next = index + matched.len();
let slice = ¶graph[last .. next];
last = next;
Some(slice)
}else if last < paragraph.len() {
let slice = ¶graph[last ..];
last = paragraph.len();
Some(slice)
}else{
None
}
});
let mut word = None;
let mut word_chars = None;
let mut col = 0;
let mut prevc = None;
let mut c = None;
let paragraph_shape = iter::from_fn(move || {
if done {
return None;
}
while word.is_none() || c.is_none() {
word = Some(words.next()?);
word_chars = Some(word.as_ref().unwrap().chars());
let end_line = if flags.contains(BoxFlags::CUT_LETTERS) {
false
}else{
let word_width = self.next_pos(
word.as_ref().unwrap(),
&Pnt2::origin(),
coordinate_origin
).x;
reached_width(xpos + word_width, col)
};
if end_line {
if col == 0 {
done = true;
return None
}
ypos += line_height;
row += 1;
if reached_height(ypos, row) {
done = true;
return None;
}
xpos = start_x;
col = 0;
prevc = None;
}
c = word_chars.as_mut().unwrap().next();
}
let glyph = self.font.glyph(c.unwrap())
.or_else(|| self.font.glyph('?'))
.unwrap();
if let Some(prevc) = prevc {
xpos += glyph.kerning(prevc);
}
if flags.contains(BoxFlags::CUT_LETTERS)
&& reached_width(xpos + glyph.offset_x() as f32 + glyph.width() as f32, col)
{
if col == 0 {
done = true;
return None
}
ypos += line_height;
row += 1;
if reached_height(ypos, row) {
done = true;
return None;
}
xpos = start_x;
col = 0;
}
let (bl,tr) = match coordinate_origin{
CoordinateOrigin::BottomLeft | CoordinateOrigin::CenterUp => {
let x0 = ( xpos + glyph.offset_x() as f32 ) as i32 as f32;
let y1 = ( ypos + glyph.offset_y() as f32 ) as i32 as f32;
let x1 = ( x0 + glyph.width() as f32 ) as i32 as f32;
let y0 = ( y1 - glyph.height() as f32 ) as i32 as f32;
(pnt2(x0,y0), pnt2(x1,y1))
}
CoordinateOrigin::TopLeft | CoordinateOrigin::CenterDown => {
let x0 = ( xpos + glyph.offset_x() as f32 ) as i32 as f32;
let y0 = ( ypos - glyph.offset_y() as f32 ) as i32 as f32;
let x1 = ( x0 + glyph.width() as f32 ) as i32 as f32;
let y1 = ( y0 + glyph.height() as f32 ) as i32 as f32;
(pnt2(x0,y1), pnt2(x1,y0))
}
};
xpos += glyph.advance_x();
prevc = c;
c = word_chars.as_mut().unwrap().next();
col += 1;
char_i += 1;
Some(Shape{
rect: Rect{pos: bl, width: tr.x - bl.x, height: tr.y - bl.y},
glyph,
next_position: pnt2(xpos, ypos),
last_position: tr,
is_last: char_i == total_len,
row,
col,
})
});
if !first {
ypos += line_height;
row += 1;
xpos = start_x;
char_i += 1;
first = false;
}
paragraph_shape
})
}
#[cfg(feature="harfbuzz")]
pub fn box_shape_iter<'a, H>(
&'a self,
string: &'a str,
pos: &Pnt2,
w: BoxCoordinatesX,
h: H,
coordinate_origin: CoordinateOrigin,
flags: BoxFlags) -> impl Iterator<Item = Shape> + 'a
where H: Into<Option<BoxCoordinatesY>>
{
let mut buffer = harfbuzz::Buffer::new();
let cut_at_letters = flags.contains(BoxFlags::CUT_LETTERS);
let start_x = pos.x;
let start_y = pos.y;
let line_height = match coordinate_origin {
CoordinateOrigin::BottomLeft | CoordinateOrigin::CenterUp => -self.line_height(),
CoordinateOrigin::TopLeft | CoordinateOrigin::CenterDown => self.line_height(),
};
let ypos = if flags.contains(BoxFlags::Y_START_AT_BASELINE) {
pos.y
}else{
match coordinate_origin {
CoordinateOrigin::BottomLeft | CoordinateOrigin::CenterUp => pos.y - self.ascender(),
CoordinateOrigin::TopLeft | CoordinateOrigin::CenterDown => pos.y + self.ascender(),
}
};
let row = std::rc::Rc::new(std::cell::Cell::new(-1i32));
let is_space = move |glyph: &hb_glyph_info_t| {
self.font.glyph_by_freetype_id(glyph.codepoint)
.map(|g| g.codepoint() == ' ' as u8 as u32)
.unwrap_or(false)
};
let h = h.into();
let reached_height = move |y: f32, row: u32|{
match h {
Some(BoxCoordinatesY::Pixels(h)) => {
match coordinate_origin {
CoordinateOrigin::BottomLeft | CoordinateOrigin::CenterUp =>
y + self.descender() < start_y - h,
CoordinateOrigin::TopLeft | CoordinateOrigin::CenterDown =>
y - self.descender() > start_y + h,
}
}
Some(BoxCoordinatesY::Rows(max_rows))
=> row == max_rows,
None => false,
}
};
let mut i = 0;
let mut done = false;
string.lines().flat_map(move |line| {
buffer.clear_contents();
unsafe{
hb_buffer_set_segment_properties(buffer.as_ptr(), &self.hb_properties)
};
buffer.add_str(line);
let (positions, glyphs) = unsafe {
hb_shape(
self.harfbuzz,
buffer.as_ptr(),
ptr::null(),
0,
);
let mut length = 0;
let positions = hb_buffer_get_glyph_positions(buffer.as_ptr(), &mut length);
let positions = std::slice::from_raw_parts(positions, length as usize);
let glyphs = hb_buffer_get_glyph_infos(buffer.as_ptr(), &mut length);
let glyphs = std::slice::from_raw_parts(glyphs, length as usize);
(positions, glyphs)
};
let mut xpos = start_x;
let mut col = 0;
let mut skipping_spaces = 0;
row.set(row.get() + 1);
let mut ypos = ypos + row.get() as f32 * line_height;
if reached_height(ypos, row.get() as u32){
done = true;
}
let row = row.clone();
std::iter::from_fn(move || {
if done {
return None
}
if i == positions.len() {
return None
}
if !cut_at_letters && skipping_spaces == 0 && is_space(&glyphs[i]) {
let mut num_spaces = 0;
while is_space(&glyphs[i + num_spaces]) {
num_spaces += 1;
if i + num_spaces == positions.len() {
return None
}
}
let reset_y = match w {
BoxCoordinatesX::Pixels(w) => {
let mut initial_spaces = num_spaces;
let (word_end, _) = glyphs[i..].iter()
.zip(&positions[i..])
.take_while(|(glyph, _)| {
if initial_spaces > 0 {
initial_spaces -= 1;
true
}else{
!is_space(&glyph)
}
})
.fold((xpos, xpos), |(_, x_advance), (glyph_info, position)| {
let glyph = self.font.glyph_by_freetype_id(glyph_info.codepoint)
.or_else(|| self.font.glyph('?'))
.unwrap();
let xa = (position.x_advance / 64) as f32;
let xo = (position.x_offset / 64) as f32;
let word_end = x_advance + xo + glyph.offset_x() as f32 + glyph.width() as f32;
let x_advance = x_advance + xa;
(word_end, x_advance)
});
word_end > start_x + w
}
BoxCoordinatesX::Columns(w) => {
let mut initial_spaces = num_spaces;
let word_length = glyphs.iter().skip(i)
.take_while(|glyph|
if initial_spaces > 0 {
initial_spaces -= 1;
true
}else{
!is_space(&glyph)
})
.count();
col + word_length as u32 > w
}
};
if reset_y {
xpos = start_x;
col = 0;
ypos += line_height;
row.set(row.get() + 1);
if reached_height(ypos, row.get() as u32) {
return None
}
i += num_spaces;
}else{
skipping_spaces = num_spaces;
}
}
let glyph_info = &glyphs[i];
let position = &positions[i];
let glyph = self.font.glyph_by_freetype_id(glyph_info.codepoint)
.or_else(|| self.font.glyph('?'))
.unwrap();
if !cut_at_letters && glyph.codepoint() == ' ' as u8 as u32 {
skipping_spaces -= 1;
}
let xa = (position.x_advance / 64) as f32;
let xo = (position.x_offset / 64) as f32;
let yo = (position.y_offset / 64) as f32;
if cut_at_letters {
let reset_y = match w {
BoxCoordinatesX::Pixels(w) => {
let word_end = xpos + xo + glyph.offset_x() as f32 + glyph.width() as f32;
word_end > start_x + w
}
BoxCoordinatesX::Columns(w) => {
col + 1 > w
}
};
if reset_y {
xpos = start_x;
col = 0;
ypos += line_height;
row.set(row.get() + 1);
if reached_height(ypos, row.get() as u32) {
return None
}
}
}
let x0 = xpos + xo + glyph.offset_x() as f32;
let y0;
let ya;
let height;
match coordinate_origin {
CoordinateOrigin::TopLeft | CoordinateOrigin::CenterDown => {
y0 = (ypos - yo - glyph.offset_y() as f32 + glyph.height() as f32).floor();
ya = (position.y_advance / 64) as f32;
height = -(glyph.height() as f32);
}
CoordinateOrigin::BottomLeft | CoordinateOrigin::CenterUp => {
y0 = (ypos + yo + glyph.offset_y() as f32 - glyph.height() as f32).floor();
ya = -(position.y_advance / 64) as f32;
height = glyph.height() as f32;
}
}
xpos += xa;
ypos += ya;
i += 1;
let shape = Some(Shape {
rect: Rect{
pos: pnt2(x0, y0),
width: glyph.width() as f32,
height: height,
},
last_position: pnt2(x0 + glyph.width() as f32, y0 + height),
glyph,
next_position: pnt2(xpos, ypos),
is_last: i == positions.len(),
row: row.get() as u32,
col,
});
col += 1;
shape
})
})
}
pub fn freetypegl(&self) -> &TextureFont{
&self.font
}
}
#[derive(Debug)]
pub struct Shape{
pub rect: Rect<f32>,
pub glyph: TextureGlyph,
pub next_position: Pnt2,
pub last_position: Pnt2,
pub is_last: bool,
pub row: u32,
pub col: u32,
}
impl Shape {
pub fn to_vertices(&self) -> SmallVec<[Vertex2DTex; 6]> {
let bl = self.rect.pos.to_vec();
let tr = self.rect.pos.to_vec() + vec2(self.rect.width, self.rect.height);
let glyph = &self.glyph;
smallvec![
Vertex2DTex{ position: bl, texcoord: vec2(glyph.s0(), glyph.t1()) },
Vertex2DTex{ position: vec2(tr.x, bl.y), texcoord: vec2(glyph.s1(), glyph.t1()) },
Vertex2DTex{ position: tr, texcoord: vec2(glyph.s1(), glyph.t0()) },
Vertex2DTex{ position: bl, texcoord: vec2(glyph.s0(), glyph.t1()) },
Vertex2DTex{ position: tr, texcoord: vec2(glyph.s1(), glyph.t0()) },
Vertex2DTex{ position: vec2(bl.x,tr.y), texcoord: vec2(glyph.s0(), glyph.t0()) },
]
}
pub fn to_vertices_color<C: ToRgba>(&self, color: &C) -> SmallVec<[Vertex2DTexColor; 6]> {
let bl = self.rect.pos.to_vec();
let tr = self.rect.pos.to_vec() + vec2(self.rect.width, self.rect.height);
let glyph = &self.glyph;
let color = color.to_rgba().to_standard();
smallvec![
Vertex2DTexColor{ position: bl, texcoord: vec2(glyph.s0(), glyph.t1()), color },
Vertex2DTexColor{ position: vec2(tr.x, bl.y), texcoord: vec2(glyph.s1(), glyph.t1()), color },
Vertex2DTexColor{ position: tr, texcoord: vec2(glyph.s1(), glyph.t0()), color },
Vertex2DTexColor{ position: bl, texcoord: vec2(glyph.s0(), glyph.t1()), color },
Vertex2DTexColor{ position: tr, texcoord: vec2(glyph.s1(), glyph.t0()), color },
Vertex2DTexColor{ position: vec2(bl.x,tr.y), texcoord: vec2(glyph.s0(), glyph.t0()), color },
]
}
}
pub fn append_glyph(mesh: &mut Mesh<Vertex2DTex>, bl: Vec2, tr: Vec2, glyph: &TextureGlyph){
mesh.extend_from_slice(&[
Vertex2DTex{ position: bl, texcoord: vec2(glyph.s0(), glyph.t1()) },
Vertex2DTex{ position: vec2(tr.x, bl.y), texcoord: vec2(glyph.s1(), glyph.t1()) },
Vertex2DTex{ position: tr, texcoord: vec2(glyph.s1(), glyph.t0()) },
Vertex2DTex{ position: bl, texcoord: vec2(glyph.s0(), glyph.t1()) },
Vertex2DTex{ position: tr, texcoord: vec2(glyph.s1(), glyph.t0()) },
Vertex2DTex{ position: vec2(bl.x,tr.y), texcoord: vec2(glyph.s0(), glyph.t0()) },
])
}