pub use crate::unicode::CharacterData;
use crate::unicode::{classify, linebreak_property, read_utf8, wrap_mask, LINEBREAK_HARD, LINEBREAK_NONE};
use crate::Font;
use crate::{
platform::{ceil, floor},
Metrics,
};
use alloc::vec::*;
use core::borrow::Borrow;
use core::hash::{Hash, Hasher};
#[derive(Copy, Clone, PartialEq)]
pub enum HorizontalAlign {
Left,
Center,
Right,
}
#[derive(Copy, Clone, PartialEq)]
pub enum VerticalAlign {
Top,
Middle,
Bottom,
}
#[derive(Copy, Clone, PartialEq)]
pub enum WrapStyle {
Word,
Letter,
}
#[derive(Copy, Clone, PartialEq)]
pub enum CoordinateSystem {
PositiveYUp,
PositiveYDown,
}
#[derive(Copy, Clone, PartialEq)]
pub struct LayoutSettings {
pub x: f32,
pub y: f32,
pub max_width: Option<f32>,
pub max_height: Option<f32>,
pub horizontal_align: HorizontalAlign,
pub vertical_align: VerticalAlign,
pub wrap_style: WrapStyle,
pub wrap_hard_breaks: bool,
}
impl Default for LayoutSettings {
fn default() -> LayoutSettings {
LayoutSettings {
x: 0.0,
y: 0.0,
max_width: None,
max_height: None,
horizontal_align: HorizontalAlign::Left,
vertical_align: VerticalAlign::Top,
wrap_style: WrapStyle::Word,
wrap_hard_breaks: true,
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct GlyphRasterConfig {
pub c: char,
pub px: f32,
pub font_index: usize,
}
impl Hash for GlyphRasterConfig {
fn hash<H: Hasher>(&self, state: &mut H) {
self.c.hash(state);
self.px.to_bits().hash(state);
self.font_index.hash(state);
}
}
impl PartialEq for GlyphRasterConfig {
fn eq(&self, other: &Self) -> bool {
self.c == other.c && self.px == other.px && self.font_index == other.font_index
}
}
impl Eq for GlyphRasterConfig {}
#[derive(Debug, Copy, Clone)]
pub struct GlyphPosition<U: Copy + Clone = ()> {
pub key: GlyphRasterConfig,
pub x: f32,
pub y: f32,
pub width: usize,
pub height: usize,
pub char_data: CharacterData,
pub user_data: U,
}
pub struct TextStyle<'a, U: Copy + Clone = ()> {
pub text: &'a str,
pub px: f32,
pub font_index: usize,
pub user_data: U,
}
impl<'a> TextStyle<'a> {
pub fn new(text: &'a str, px: f32, font_index: usize) -> TextStyle<'a> {
TextStyle {
text,
px,
font_index,
user_data: (),
}
}
}
impl<'a, U: Copy + Clone> TextStyle<'a, U> {
pub fn with_user_data(text: &'a str, px: f32, font_index: usize, user_data: U) -> TextStyle<'a, U> {
TextStyle {
text,
px,
font_index,
user_data,
}
}
}
#[derive(Debug, Copy, Clone)]
struct LineMetrics {
pub padding: f32,
pub ascent: f32,
pub new_line_size: f32,
pub x_start: f32,
pub end_index: usize,
}
impl Default for LineMetrics {
fn default() -> Self {
LineMetrics {
padding: 0.0,
ascent: 0.0,
x_start: 0.0,
new_line_size: 0.0,
end_index: 0,
}
}
}
pub struct Layout<U: Copy + Clone = ()> {
flip: bool,
x: f32,
y: f32,
wrap_mask: u8,
max_width: f32,
max_height: f32,
vertical_align: f32,
horizontal_align: f32,
output: Vec<GlyphPosition<U>>,
glyphs: Vec<GlyphPosition<U>>,
line_metrics: Vec<LineMetrics>,
linebreak_prev: u8,
linebreak_state: u8,
linebreak_pos: f32,
linebreak_idx: usize,
current_pos: f32,
current_ascent: f32,
current_new_line: f32,
current_px: f32,
start_pos: f32,
height: f32,
}
impl<'a, U: Copy + Clone> Layout<U> {
pub fn new(coordinate_system: CoordinateSystem) -> Layout<U> {
let mut layout = Layout {
flip: coordinate_system == CoordinateSystem::PositiveYDown,
x: 0.0,
y: 0.0,
wrap_mask: 0,
max_width: 0.0,
max_height: 0.0,
vertical_align: 0.0,
horizontal_align: 0.0,
output: Vec::new(),
glyphs: Vec::new(),
line_metrics: Vec::new(),
linebreak_prev: 0,
linebreak_state: 0,
linebreak_pos: 0.0,
linebreak_idx: 0,
current_pos: 0.0,
current_ascent: 0.0,
current_new_line: 0.0,
current_px: 0.0,
start_pos: 0.0,
height: 0.0,
};
layout.reset(&LayoutSettings::default());
layout
}
pub fn reset(&mut self, settings: &LayoutSettings) {
self.x = settings.x;
self.y = settings.y;
self.wrap_mask = wrap_mask(
settings.wrap_style == WrapStyle::Word,
settings.wrap_hard_breaks,
settings.max_width.is_some(),
);
self.max_width = settings.max_width.unwrap_or(core::f32::MAX);
self.max_height = settings.max_height.unwrap_or(core::f32::MAX);
self.vertical_align = if settings.max_height.is_none() {
0.0
} else {
match settings.vertical_align {
VerticalAlign::Top => 0.0,
VerticalAlign::Middle => 0.5,
VerticalAlign::Bottom => 1.0,
}
};
self.horizontal_align = if settings.max_width.is_none() {
0.0
} else {
match settings.horizontal_align {
HorizontalAlign::Left => 0.0,
HorizontalAlign::Center => 0.5,
HorizontalAlign::Right => 1.0,
}
};
self.clear();
}
pub fn clear(&mut self) {
self.glyphs.clear();
self.output.clear();
self.line_metrics.clear();
self.line_metrics.push(LineMetrics::default());
self.linebreak_prev = 0;
self.linebreak_state = 0;
self.linebreak_pos = 0.0;
self.linebreak_idx = 0;
self.current_pos = 0.0;
self.current_ascent = 0.0;
self.current_new_line = 0.0;
self.current_px = 0.0;
self.start_pos = 0.0;
self.height = 0.0;
}
pub fn height(&self) -> f32 {
if let Some(line) = self.line_metrics.last() {
self.height + line.new_line_size
} else {
0.0
}
}
pub fn lines(&self) -> usize {
self.line_metrics.len()
}
pub fn append<T: Borrow<Font>>(&mut self, fonts: &[T], style: &TextStyle<U>) {
let mut byte_offset = 0;
let font = &fonts[style.font_index];
if let Some(metrics) = font.borrow().horizontal_line_metrics(style.px) {
self.current_ascent = ceil(metrics.ascent);
self.current_new_line = ceil(metrics.new_line_size);
if let Some(line) = self.line_metrics.last_mut() {
if self.current_ascent > line.ascent {
line.ascent = self.current_ascent;
}
if self.current_new_line > line.new_line_size {
line.new_line_size = self.current_new_line;
}
}
}
while byte_offset < style.text.len() {
let c = read_utf8(style.text.as_bytes(), &mut byte_offset);
let char_index = font.borrow().lookup_glyph_index(c);
let char_data = classify(c, char_index);
let metrics = if !char_data.is_control() {
font.borrow().metrics_indexed(char_index, style.px)
} else {
Metrics::default()
};
let advance = ceil(metrics.advance_width);
let linebreak = linebreak_property(&mut self.linebreak_state, c) & self.wrap_mask;
if linebreak >= self.linebreak_prev {
self.linebreak_prev = linebreak;
self.linebreak_pos = self.current_pos;
self.linebreak_idx = self.glyphs.len();
}
if linebreak == LINEBREAK_HARD || (self.current_pos - self.start_pos + advance > self.max_width) {
self.linebreak_prev = LINEBREAK_NONE;
if let Some(line) = self.line_metrics.last_mut() {
line.end_index = self.linebreak_idx;
line.padding = self.max_width - (self.linebreak_pos - self.start_pos);
self.height += line.new_line_size;
}
self.line_metrics.push(LineMetrics {
padding: 0.0,
ascent: self.current_ascent,
x_start: self.linebreak_pos,
new_line_size: self.current_new_line,
end_index: 0,
});
self.start_pos = self.linebreak_pos;
}
let y = if self.flip {
floor(-metrics.bounds.height - metrics.bounds.ymin)
} else {
floor(metrics.bounds.ymin)
};
self.glyphs.push(GlyphPosition {
key: GlyphRasterConfig {
c,
px: style.px,
font_index: style.font_index,
},
x: self.current_pos + floor(metrics.bounds.xmin),
y,
width: metrics.width,
height: metrics.height,
char_data,
user_data: style.user_data,
});
self.current_pos += advance;
}
if let Some(line) = self.line_metrics.last_mut() {
line.padding = self.max_width - (self.current_pos - self.start_pos);
line.end_index = self.glyphs.len();
}
}
pub fn glyphs(&'a mut self) -> &'a Vec<GlyphPosition<U>> {
if self.glyphs.len() == self.output.len() {
return &self.output;
}
unsafe { self.output.set_len(0) };
self.output.reserve(self.glyphs.len());
let dir = if self.flip {
-1.0
} else {
1.0
};
let mut y = self.y - dir * floor((self.max_height - self.height()) * self.vertical_align);
let mut idx = 0;
for line in &self.line_metrics {
let x = self.x - line.x_start + floor(line.padding * self.horizontal_align);
y -= dir * line.ascent;
while idx < line.end_index {
let mut glyph = self.glyphs[idx];
glyph.x += x;
glyph.y += y;
self.output.push(glyph);
idx += 1;
}
y -= dir * (line.new_line_size - line.ascent);
}
&self.output
}
}