use super::decoration::{TextDecoration, VerticalAlign};
use super::inline_box::InlineBoxSlot;
use super::font_spec::FontSpec;
use super::protrusion::ProtrusionTable;
use crate::linebreak::{BreakStrategy, Hyphenation, LineBreakParams};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct StyledRun<'a> {
pub text: &'a str,
pub font: FontSpec,
pub color: Option<u32>,
pub decoration: TextDecoration,
pub letter_spacing: f64,
pub vertical_align: VerticalAlign,
}
impl<'a> StyledRun<'a> {
pub fn new(text: &'a str, font: FontSpec) -> Self {
Self { text, font, color: None, decoration: TextDecoration::NONE, letter_spacing: 0.0, vertical_align: VerticalAlign::Baseline }
}
pub fn with_color(mut self, color: u32) -> Self {
self.color = Some(color);
self
}
pub fn with_decoration(mut self, decoration: TextDecoration) -> Self {
self.decoration = decoration;
self
}
pub fn with_letter_spacing(mut self, letter_spacing: f64) -> Self {
self.letter_spacing = letter_spacing;
self
}
pub fn with_vertical_align(mut self, vertical_align: VerticalAlign) -> Self {
self.vertical_align = vertical_align;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ParagraphAlign {
#[default]
Left,
Center,
Right,
Justify,
}
#[derive(Debug, Clone, Copy)]
pub struct Paragraph<'a> {
pub runs: &'a [StyledRun<'a>],
pub inline_boxes: &'a [InlineBoxSlot],
pub align: ParagraphAlign,
pub line_height: Option<f64>,
pub max_width: f64,
pub break_strategy: BreakStrategy,
pub hyphenation: Hyphenation,
pub max_consecutive_hyphens: Option<u8>,
pub line_break_params: LineBreakParams,
pub protrusion: Option<&'a ProtrusionTable>,
}
impl<'a> Paragraph<'a> {
pub fn new(runs: &'a [StyledRun<'a>], max_width: f64) -> Self {
Self {
runs,
inline_boxes: &[],
align: ParagraphAlign::default(),
line_height: None,
max_width,
break_strategy: BreakStrategy::default(),
hyphenation: Hyphenation::default(),
max_consecutive_hyphens: None,
line_break_params: LineBreakParams::default(),
protrusion: None,
}
}
pub fn with_align(mut self, align: ParagraphAlign) -> Self {
self.align = align;
self
}
pub fn with_inline_boxes(mut self, inline_boxes: &'a [InlineBoxSlot]) -> Self {
self.inline_boxes = inline_boxes;
self
}
pub fn with_line_height(mut self, line_height: f64) -> Self {
self.line_height = Some(line_height);
self
}
pub fn with_break_strategy(mut self, break_strategy: BreakStrategy) -> Self {
self.break_strategy = break_strategy;
self
}
pub fn with_hyphenation(mut self, hyphenation: Hyphenation) -> Self {
self.hyphenation = hyphenation;
self
}
pub fn with_max_consecutive_hyphens(mut self, max: u8) -> Self {
self.max_consecutive_hyphens = Some(max);
self
}
pub fn with_line_break_params(mut self, params: LineBreakParams) -> Self {
self.line_break_params = params;
self
}
pub fn with_protrusion(mut self, table: &'a ProtrusionTable) -> Self {
self.protrusion = Some(table);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use uzor::fonts::FontFamily;
#[test]
fn styled_run_new_has_no_color_override() {
let font = FontSpec::default();
let run = StyledRun::new("hi", font);
assert_eq!(run.color, None);
assert!(run.decoration.is_none(), "styled_run::new must default to no decoration");
assert_eq!(run.letter_spacing, 0.0);
assert_eq!(run.vertical_align, VerticalAlign::Baseline);
}
#[test]
fn styled_run_builders_set_decoration_letter_spacing_and_vertical_align() {
let font = FontSpec::default();
let run = StyledRun::new("hi", font)
.with_decoration(TextDecoration::underline())
.with_letter_spacing(2.5)
.with_vertical_align(VerticalAlign::Super);
assert_eq!(run.decoration, TextDecoration::underline());
assert_eq!(run.letter_spacing, 2.5);
assert_eq!(run.vertical_align, VerticalAlign::Super);
}
#[test]
fn styled_run_with_color_sets_the_override() {
let font = FontSpec::default();
let run = StyledRun::new("hi", font).with_color(0x11223344);
assert_eq!(run.color, Some(0x11223344));
}
#[test]
fn paragraph_new_defaults_to_left_no_boxes_no_line_height_override() {
let font = FontSpec::new(FontFamily::Roboto, 16.0);
let runs = [StyledRun::new("hi", font)];
let p = Paragraph::new(&runs, 200.0);
assert_eq!(p.align, ParagraphAlign::Left);
assert!(p.inline_boxes.is_empty());
assert_eq!(p.line_height, None);
assert_eq!(p.max_width, 200.0);
assert_eq!(p.break_strategy, BreakStrategy::Greedy, "Phase 5 regression floor: default stays Greedy");
assert_eq!(p.hyphenation, Hyphenation::None);
assert_eq!(p.line_break_params, LineBreakParams::default(), "T5 regression floor: default line-break params");
assert_eq!(p.protrusion, None, "T4 regression floor: default is no protrusion");
}
#[test]
fn paragraph_builders_set_the_expected_fields() {
let font = FontSpec::new(FontFamily::Roboto, 16.0);
let runs = [StyledRun::new("hi", font)];
let p = Paragraph::new(&runs, 200.0).with_align(ParagraphAlign::Justify).with_line_height(30.0);
assert_eq!(p.align, ParagraphAlign::Justify);
assert_eq!(p.line_height, Some(30.0));
}
#[test]
fn paragraph_with_break_strategy_and_hyphenation_set_the_expected_fields() {
let font = FontSpec::new(FontFamily::Roboto, 16.0);
let runs = [StyledRun::new("hi", font)];
let p = Paragraph::new(&runs, 200.0)
.with_break_strategy(BreakStrategy::KnuthPlass)
.with_hyphenation(Hyphenation::English);
assert_eq!(p.break_strategy, BreakStrategy::KnuthPlass);
assert_eq!(p.hyphenation, Hyphenation::English);
}
#[test]
fn paragraph_with_line_break_params_sets_the_expected_field() {
let font = FontSpec::new(FontFamily::Roboto, 16.0);
let runs = [StyledRun::new("hi", font)];
let params = LineBreakParams { hyphen_penalty: 999.0, ..LineBreakParams::default() };
let p = Paragraph::new(&runs, 200.0).with_line_break_params(params);
assert_eq!(p.line_break_params, params);
}
#[test]
fn paragraph_with_protrusion_sets_the_expected_field() {
use super::super::ProtrusionTable;
let font = FontSpec::new(FontFamily::Roboto, 16.0);
let runs = [StyledRun::new("hi", font)];
let table = ProtrusionTable::default_punctuation();
let p = Paragraph::new(&runs, 200.0).with_protrusion(&table);
assert!(p.protrusion.is_some());
}
}