use super::FontContext;
use super::context::LayoutContext;
use super::style::{Brush, StyleProperty, TextStyle, WhiteSpaceCollapse};
use super::layout::Layout;
use alloc::string::String;
use core::ops::{Bound, Range, RangeBounds};
use crate::InlineBoxKind;
use crate::inline_box::InlineBox;
use crate::resolve::{ResolvedStyle, StyleRun, tree::ItemKind};
#[must_use]
pub struct RangedBuilder<'a, B: Brush> {
pub(crate) scale: f32,
pub(crate) quantize: bool,
pub(crate) lcx: &'a mut LayoutContext<B>,
pub(crate) fcx: &'a mut FontContext,
}
impl<B: Brush> RangedBuilder<'_, B> {
pub fn push_default<'a>(&mut self, property: impl Into<StyleProperty<'a, B>>) {
let resolved = self
.lcx
.rcx
.resolve_property(self.fcx, &property.into(), self.scale);
self.lcx.ranged_style_builder.push_default(resolved);
}
pub fn push<'a>(
&mut self,
property: impl Into<StyleProperty<'a, B>>,
range: impl RangeBounds<usize>,
) {
let resolved = self
.lcx
.rcx
.resolve_property(self.fcx, &property.into(), self.scale);
self.lcx.ranged_style_builder.push(resolved, range);
}
pub fn push_inline_box(&mut self, inline_box: InlineBox) {
self.lcx.inline_boxes.push(inline_box);
}
pub fn build_into(self, layout: &mut Layout<B>, text: impl AsRef<str>) {
self.lcx
.ranged_style_builder
.finish(&mut self.lcx.style_table, &mut self.lcx.style_runs);
build_into_layout(
layout,
self.scale,
self.quantize,
text.as_ref(),
self.lcx,
self.fcx,
);
}
pub fn build(self, text: impl AsRef<str>) -> Layout<B> {
let mut layout = Layout::default();
self.build_into(&mut layout, text);
layout
}
}
#[must_use]
pub struct StyleRunBuilder<'a, B: Brush> {
pub(crate) scale: f32,
pub(crate) quantize: bool,
pub(crate) len: usize,
pub(crate) lcx: &'a mut LayoutContext<B>,
pub(crate) fcx: &'a mut FontContext,
pub(crate) cursor: usize,
}
impl<B: Brush> StyleRunBuilder<'_, B> {
pub fn reserve(&mut self, additional_styles: usize, additional_runs: usize) {
self.lcx.style_table.reserve(additional_styles);
self.lcx.style_runs.reserve(additional_runs);
}
pub fn push_style<'family, 'settings>(
&mut self,
style: TextStyle<'family, 'settings, B>,
) -> u16 {
let resolved = self
.lcx
.rcx
.resolve_entire_style_set(self.fcx, &style, self.scale);
let style_index = self.lcx.style_table.len();
assert!(style_index <= u16::MAX as usize, "too many styles");
self.lcx.style_table.push(resolved);
style_index as u16
}
pub fn push_style_run(&mut self, style_index: u16, range: impl RangeBounds<usize>) {
let range = resolve_range(range, self.len);
assert!(
range.start == self.cursor,
"StyleRunBuilder expects contiguous non-overlapping runs"
);
assert!(
range.start <= range.end,
"StyleRunBuilder expects ordered ranges"
);
assert!(
(style_index as usize) < self.lcx.style_table.len(),
"StyleRunBuilder expects style indices that were previously added via push_style"
);
self.lcx.style_runs.push(StyleRun {
style_index,
range: range.clone(),
});
self.cursor = range.end;
}
pub fn push_inline_box(&mut self, inline_box: InlineBox) {
self.lcx.inline_boxes.push(inline_box);
}
pub fn build_into(self, layout: &mut Layout<B>, text: impl AsRef<str>) {
assert!(
self.cursor == self.len,
"StyleRunBuilder requires runs that cover the full text"
);
build_into_layout(
layout,
self.scale,
self.quantize,
text.as_ref(),
self.lcx,
self.fcx,
);
}
pub fn build(self, text: impl AsRef<str>) -> Layout<B> {
let mut layout = Layout::default();
self.build_into(&mut layout, text);
layout
}
}
#[must_use]
pub struct TreeBuilder<'a, B: Brush> {
pub(crate) scale: f32,
pub(crate) quantize: bool,
pub(crate) lcx: &'a mut LayoutContext<B>,
pub(crate) fcx: &'a mut FontContext,
}
impl<B: Brush> TreeBuilder<'_, B> {
pub fn push_style_span(&mut self, style: TextStyle<'_, '_, B>) {
let resolved = self
.lcx
.rcx
.resolve_entire_style_set(self.fcx, &style, self.scale);
self.lcx.tree_style_builder.push_style_span(resolved);
}
pub fn push_style_modification_span<'s, 'iter>(
&mut self,
properties: impl IntoIterator<Item = &'iter StyleProperty<'s, B>>,
) where
's: 'iter,
B: 'iter,
{
self.lcx.tree_style_builder.push_style_modification_span(
properties
.into_iter()
.map(|p| self.lcx.rcx.resolve_property(self.fcx, p, self.scale)),
);
}
pub fn pop_style_span(&mut self) {
self.lcx.tree_style_builder.pop_style_span();
}
pub fn push_text(&mut self, text: &str) {
self.lcx.tree_style_builder.push_text(text);
}
pub fn push_inline_box(&mut self, mut inline_box: InlineBox) {
if inline_box.kind == InlineBoxKind::InFlow {
self.lcx.tree_style_builder.push_uncommitted_text(false);
self.lcx.tree_style_builder.set_is_span_first(false);
self.lcx
.tree_style_builder
.set_last_item_kind(ItemKind::InlineBox);
}
inline_box.index = self.lcx.tree_style_builder.current_text_len();
self.lcx.inline_boxes.push(inline_box);
}
pub fn set_white_space_mode(&mut self, white_space_collapse: WhiteSpaceCollapse) {
self.lcx
.tree_style_builder
.set_white_space_mode(white_space_collapse);
}
#[inline]
pub fn build_into(self, layout: &mut Layout<B>) -> String {
let text = self
.lcx
.tree_style_builder
.finish(&mut self.lcx.style_table, &mut self.lcx.style_runs);
build_into_layout(layout, self.scale, self.quantize, &text, self.lcx, self.fcx);
text
}
#[inline]
pub fn build(self) -> (Layout<B>, String) {
let mut layout = Layout::default();
let text = self.build_into(&mut layout);
(layout, text)
}
}
fn build_into_layout<B: Brush>(
layout: &mut Layout<B>,
scale: f32,
quantize: bool,
text: &str,
lcx: &mut LayoutContext<B>,
fcx: &mut FontContext,
) {
if text.is_empty() && lcx.style_runs.is_empty() {
lcx.style_table.push(ResolvedStyle::default());
lcx.style_runs.push(StyleRun {
style_index: 0,
range: 0..0,
});
}
assert!(
!lcx.style_runs.is_empty(),
"at least one style run is required"
);
crate::analysis::analyze_text(lcx, text);
layout.data.clear();
layout.data.scale = scale;
layout.data.quantize = quantize;
layout.data.base_level = lcx.bidi.base_level();
layout.data.text_len = text.len();
let mut char_index = 0;
for style_run in &lcx.style_runs {
for _ in text[style_run.range.clone()].chars() {
lcx.info[char_index].1 = style_run.style_index;
char_index += 1;
}
}
layout
.data
.styles
.extend(lcx.style_table.iter().map(|s| s.as_layout_style()));
lcx.inline_boxes.sort_by_key(|b| b.index);
{
let query = fcx.collection.query(&mut fcx.source_cache);
super::shape::shape_text(
&lcx.rcx,
query,
&lcx.style_table,
&lcx.inline_boxes,
&lcx.info,
lcx.bidi.levels(),
&mut lcx.scx,
text,
layout,
&lcx.analysis_data_sources,
);
}
layout.data.inline_boxes.clear();
core::mem::swap(&mut layout.data.inline_boxes, &mut lcx.inline_boxes);
layout.data.finish();
}
fn resolve_range(range: impl RangeBounds<usize>, len: usize) -> Range<usize> {
let start = match range.start_bound() {
Bound::Unbounded => 0,
Bound::Included(n) => *n,
Bound::Excluded(n) => *n + 1,
};
let end = match range.end_bound() {
Bound::Unbounded => len,
Bound::Included(n) => *n + 1,
Bound::Excluded(n) => *n,
};
start.min(len)..end.min(len)
}