use crate::platform::{ceil, floor};
use crate::unicode::{linebreak_property, read_utf8, wrap_mask};
use crate::{Font, ZERO_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 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,
pub include_whitespace: 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,
include_whitespace: false,
}
}
}
#[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, PartialEq)]
pub struct GlyphPosition {
pub key: GlyphRasterConfig,
pub x: f32,
pub y: f32,
pub width: usize,
pub height: usize,
}
pub struct TextStyle<'a> {
pub text: &'a str,
pub px: f32,
pub font_index: usize,
}
impl<'a> TextStyle<'a> {
pub fn new(text: &'a str, px: f32, font_index: usize) -> TextStyle<'a> {
TextStyle {
text,
px,
font_index,
}
}
}
#[derive(Debug, Copy, Clone)]
struct LineMetrics {
pub padding: f32,
pub ascent: f32,
pub new_line_size: f32,
pub end_index: usize,
}
pub struct Layout {
line_metrics: Vec<LineMetrics>,
}
impl Layout {
pub fn new() -> Layout {
Layout {
line_metrics: Vec::new(),
}
}
fn wrap_mask_from_settings(settings: &LayoutSettings) -> u8 {
let wrap_soft_breaks = settings.wrap_style == WrapStyle::Word;
let wrap_hard_breaks = settings.wrap_hard_breaks;
let wrap = settings.max_width.is_some() && (wrap_soft_breaks || wrap_hard_breaks);
wrap_mask(wrap, wrap_soft_breaks, wrap_hard_breaks)
}
fn horizontal_padding(settings: &LayoutSettings, remaining_width: f32) -> f32 {
if settings.max_width.is_none() {
0.0
} else {
match settings.horizontal_align {
HorizontalAlign::Left => 0.0,
HorizontalAlign::Center => floor(remaining_width / 2.0),
HorizontalAlign::Right => floor(remaining_width),
}
}
}
fn vertical_padding(settings: &LayoutSettings, height: f32) -> f32 {
if let Some(max_height) = settings.max_height {
if height >= max_height {
0.0
} else {
match settings.vertical_align {
VerticalAlign::Top => 0.0,
VerticalAlign::Middle => floor((max_height - height) / 2.0),
VerticalAlign::Bottom => floor(max_height - height),
}
}
} else {
0.0
}
}
pub fn layout_horizontal<T: Borrow<Font>>(
&mut self,
fonts: &[T],
styles: &[&TextStyle],
settings: &LayoutSettings,
output: &mut Vec<GlyphPosition>,
) {
unsafe {
self.line_metrics.set_len(0);
output.set_len(0);
}
let wrap_mask = Self::wrap_mask_from_settings(settings);
let max_width = settings.max_width.unwrap_or(core::f32::MAX);
let mut state: u8 = 0;
let mut last_linebreak_state = 0;
let mut last_linebreak_x = 0.0;
let mut last_linebreak_index = 0;
let mut current_x = 0.0;
let mut caret_x = 0.0;
let mut total_height = 0.0;
let mut next_line = LineMetrics {
padding: 0.0,
ascent: 0.0,
new_line_size: 0.0,
end_index: core::usize::MAX,
};
let mut current_ascent = 0.0;
let mut current_new_line_size = 0.0;
for style in styles {
let mut byte_offset = 0;
let font = &fonts[style.font_index];
if let Some(metrics) = font.borrow().horizontal_line_metrics(style.px) {
current_ascent = ceil(metrics.ascent);
current_new_line_size = ceil(metrics.new_line_size);
if current_ascent > next_line.ascent {
next_line.ascent = current_ascent;
}
if current_new_line_size > next_line.new_line_size {
next_line.new_line_size = current_new_line_size;
}
}
while byte_offset < style.text.len() {
let character = read_utf8(style.text, &mut byte_offset);
let linebreak_state = linebreak_property(&mut state, character) & wrap_mask;
let metrics = if character as u32 > 0x1F {
font.borrow().metrics(character, style.px)
} else {
ZERO_METRICS
};
let advance = ceil(metrics.advance_width);
if linebreak_state >= last_linebreak_state {
last_linebreak_state = linebreak_state;
last_linebreak_x = caret_x;
last_linebreak_index = output.len();
}
if caret_x - current_x + advance >= max_width || last_linebreak_state == 2 {
total_height += next_line.new_line_size;
next_line.padding = max_width - (last_linebreak_x - current_x);
next_line.end_index = last_linebreak_index;
self.line_metrics.push(next_line);
next_line.ascent = current_ascent;
next_line.new_line_size = current_new_line_size;
last_linebreak_state = 0;
current_x = last_linebreak_x;
}
if settings.include_whitespace || metrics.width != 0 {
output.push(GlyphPosition {
key: GlyphRasterConfig {
c: character,
px: style.px,
font_index: style.font_index,
},
x: caret_x + floor(metrics.bounds.xmin),
y: floor(metrics.bounds.ymin),
width: metrics.width,
height: metrics.height,
});
}
caret_x += advance;
}
}
total_height += next_line.new_line_size;
next_line.padding = max_width - (caret_x - current_x);
next_line.end_index = core::usize::MAX;
self.line_metrics.push(next_line);
let mut line_metrics_index = 0;
let mut next_line_index;
let mut current_index = 0;
let mut current_ascent;
let mut current_new_line_size;
let mut x_base = settings.x;
let mut y_base = settings.y - Self::vertical_padding(settings, total_height);
let line = self.line_metrics[0];
next_line_index = line.end_index;
current_ascent = line.ascent;
current_new_line_size = line.new_line_size;
x_base += Self::horizontal_padding(settings, line.padding);
for glyph in output {
if current_index == next_line_index {
line_metrics_index += 1;
let line = self.line_metrics[line_metrics_index];
x_base = settings.x - glyph.x;
y_base -= current_new_line_size;
next_line_index = line.end_index;
current_ascent = line.ascent;
current_new_line_size = line.new_line_size;
x_base += Self::horizontal_padding(settings, line.padding);
}
glyph.x += x_base;
glyph.y += y_base - current_ascent;
current_index += 1;
}
}
}