use crate::metadata::Metadata;
use crate::text::FontSystemAndDefaults;
use crate::{cvt_color, cvt_family, cvt_style, cvt_weight};
use cosmic_text as ct;
use ct::{Attrs, AttrsList, AttrsOwned};
use piet::{util, Error, TextAttribute};
use tinyvec::TinyVec;
use std::collections::BTreeMap;
use std::fmt;
use std::ops::Range;
#[derive(Default)]
pub(crate) struct Attributes {
attributes: Vec<TextAttribute>,
ends: BTreeMap<usize, TinyVec<[RangeEnd; 1]>>,
}
impl fmt::Debug for Attributes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
struct FmtTextAttribute<'a>(&'a Attributes, usize);
impl fmt::Debug for FmtTextAttribute<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let attr = self.0.attributes.get(self.1).unwrap();
fmt::Debug::fmt(attr, f)
}
}
struct WrapFmt<'a, T>(&'a str, T);
impl<T: fmt::Debug> fmt::Debug for WrapFmt<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple(self.0).field(&self.1).finish()
}
}
struct FmtRangeEnds<'a>(&'a Attributes, &'a [RangeEnd]);
impl fmt::Debug for FmtRangeEnds<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ends = self.1.iter().map(|end| match end {
RangeEnd::Start(index) => WrapFmt("Start", FmtTextAttribute(self.0, *index)),
RangeEnd::End(index) => WrapFmt("End", FmtTextAttribute(self.0, *index)),
});
f.debug_list().entries(ends).finish()
}
}
struct FmtEnds<'a>(&'a Attributes);
impl fmt::Debug for FmtEnds<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map()
.entries(
self.0
.ends
.iter()
.map(|(&index, ends)| (index, FmtRangeEnds(self.0, ends))),
)
.finish()
}
}
f.debug_tuple("Attributes").field(&FmtEnds(self)).finish()
}
}
#[derive(Debug)]
enum RangeEnd {
Start(usize),
End(usize),
}
impl Default for RangeEnd {
fn default() -> Self {
Self::Start(0)
}
}
impl Attributes {
pub(crate) fn push(&mut self, range: Range<usize>, attr: TextAttribute) {
let index = self.attributes.len();
self.attributes.push(attr);
macro_rules! push_index {
($pl:ident,$en:ident) => {{
let end = self.ends.entry(range.$pl).or_default();
end.push(RangeEnd::$en(index));
}};
}
push_index!(start, Start);
push_index!(end, End);
}
fn collect_attributes<'a>(
&'a self,
system: &mut FontSystemAndDefaults,
mut attrs: Attrs<'a>,
indices: impl Iterator<Item = usize>,
) -> Result<AttrsOwned, Error> {
macro_rules! with_metadata {
($closure:expr) => {{
#[inline]
fn closure_slot(metadata: &mut Metadata, closure: impl FnOnce(&mut Metadata)) {
closure(metadata);
}
let mut metadata = Metadata::from_raw(attrs.metadata);
closure_slot(&mut metadata, $closure);
attrs.metadata = metadata.into_raw();
}};
}
for index in indices {
let piet_attr = self.attributes.get(index).ok_or_else(|| {
Error::BackendError(crate::FontError::InvalidAttributeIndex.into())
})?;
match piet_attr {
TextAttribute::FontFamily(family) => {
attrs.family = cvt_family(family);
}
TextAttribute::FontSize(_size) => {
error!("piet-cosmic-text does not support variable size fonts yet");
}
TextAttribute::Strikethrough(st) => {
with_metadata!(|meta| meta.set_strikethrough(*st));
}
TextAttribute::Underline(ul) => {
with_metadata!(|meta| meta.set_underline(*ul));
}
TextAttribute::Style(style) => {
attrs.style = cvt_style(*style);
}
TextAttribute::Weight(weight) => {
attrs.weight = cvt_weight(*weight);
with_metadata!(|meta| meta.set_boldness(*weight));
}
TextAttribute::TextColor(color) => {
if *color != util::DEFAULT_TEXT_COLOR {
attrs.color_opt = Some(cvt_color(*color));
} else {
attrs.color_opt = None;
}
}
}
}
Ok(system.fix_attrs(attrs))
}
pub(crate) fn text_attributes<'a>(
&'a self,
system: &mut FontSystemAndDefaults,
range: Range<usize>,
defaults: Attrs<'a>,
) -> Result<AttrsList, Error> {
let span = trace_span!("text_attributes", start = range.start, end = range.end);
let _guard = span.enter();
let mut last_index = 0;
let mut result = AttrsList::new(defaults);
let mut attr_list = vec![];
let mut ranges = self
.ends
.iter()
.filter(|(&index, _)| index < range.end)
.peekable();
while let Some((_, ends)) = ranges.next_if(|(&index, _)| index < range.start) {
for end in ends {
match end {
RangeEnd::Start(index) => {
trace!("adding pre-attribute {}", index);
attr_list.push(*index);
}
RangeEnd::End(index) => {
trace!("removing pre-attribute {}", index);
attr_list.retain(|&i| i != *index);
}
}
}
}
trace!("end of pre-attributes");
let ranges = ranges.map(|(index, ends)| (index - range.start, ends));
for (index, ends) in ranges {
let current_range = last_index..index;
if !current_range.is_empty() {
let new_attrs =
self.collect_attributes(system, defaults, attr_list.iter().copied())?;
trace!("adding span {:?}", current_range);
result.add_span(current_range, new_attrs.as_attrs());
} else {
trace!("skipping empty span {:?}", current_range);
}
for end in ends {
match end {
RangeEnd::Start(index) => {
trace!("adding attribute {}", index);
attr_list.push(*index);
}
RangeEnd::End(index) => {
trace!("removing attribute {}", index);
attr_list.retain(|&i| i != *index);
}
}
}
last_index = index;
}
let current_range = last_index..range.end;
if !current_range.is_empty() {
let new_attrs = self.collect_attributes(system, defaults, attr_list.into_iter())?;
trace!("adding final span {:?}", current_range);
result.add_span(current_range, new_attrs.as_attrs());
} else {
trace!("skipping empty final span {:?}", current_range);
}
Ok(result)
}
}