use std::cell::{RefCell, RefMut};
use std::ops::Range;
use std::rc::Rc;
use crate::color::rgb;
use crate::text::type3::ColoredGlyph;
use crate::text::Glyph;
use crate::text::{FontContainer, FontIdentifier};
pub(crate) enum GlyphSpan<'a, T>
where
T: Glyph,
{
Unspanned(&'a [T]),
Spanned(&'a [T], &'a str),
}
impl<T> GlyphSpan<'_, T>
where
T: Glyph,
{
pub(crate) fn glyphs(&self) -> &[T] {
match self {
GlyphSpan::Unspanned(glyphs) => glyphs,
GlyphSpan::Spanned(glyphs, _) => glyphs,
}
}
pub(crate) fn actual_text(&self) -> Option<&str> {
match self {
GlyphSpan::Unspanned(_) => None,
GlyphSpan::Spanned(_, text) => Some(text),
}
}
}
pub(crate) struct GlyphSpanner<'a, T>
where
T: Glyph,
{
slice: &'a [T],
context_color: rgb::Color,
reversed: bool,
forbid_invalid_codepoints: bool,
font_container: Rc<RefCell<FontContainer>>,
text: &'a str,
}
impl<'a, T> GlyphSpanner<'a, T>
where
T: Glyph,
{
pub(crate) fn new(
slice: &'a [T],
text: &'a str,
reversed: bool,
forbid_invalid_codepoints: bool,
context_color: rgb::Color,
font_container: Rc<RefCell<FontContainer>>,
) -> Self {
Self {
slice,
context_color,
reversed,
forbid_invalid_codepoints,
text,
font_container,
}
}
}
impl<'a, T> Iterator for GlyphSpanner<'a, T>
where
T: Glyph,
{
type Item = GlyphSpan<'a, T>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
fn func<U>(
g: &U,
context_color: rgb::Color,
previous_range: Option<Range<usize>>,
forbid_invalid_codepoints: bool,
mut font_container: RefMut<FontContainer>,
text: &str,
) -> (Range<usize>, bool)
where
U: Glyph,
{
let (identifier, pdf_glyph) =
font_container.add_glyph(ColoredGlyph::new(g.glyph_id(), context_color));
let pdf_font = font_container
.get_from_identifier_mut(identifier.clone())
.unwrap();
let range = g.text_range().clone();
let text = &text[range.clone()];
let codepoints = pdf_font.get_codepoints(pdf_glyph);
let incompatible_codepoint = codepoints.is_some_and(|c| c != text);
if !incompatible_codepoint
&& (previous_range != Some(range.clone()) || forbid_invalid_codepoints)
&& codepoints != Some(text)
{
pdf_font.set_codepoints(pdf_glyph, text.to_string(), g.location());
}
(range, incompatible_codepoint)
}
let mut use_span = None;
let mut count = 1;
let mut iter = self.slice.iter();
let (first_range, first_incompatible) = func(
iter.next()?,
self.context_color,
None,
self.forbid_invalid_codepoints,
self.font_container.borrow_mut(),
self.text,
);
let mut prev_range = first_range.clone();
for next in iter {
let (next_range, next_incompatible) = func(
next,
self.context_color,
Some(prev_range.clone()),
self.forbid_invalid_codepoints,
self.font_container.borrow_mut(),
self.text,
);
match use_span {
None => {
if prev_range == next_range {
use_span = Some(true);
} else {
use_span = Some(first_incompatible);
if first_incompatible || next_incompatible {
break;
}
}
}
Some(true) => {
if prev_range != next_range {
break;
}
}
Some(false) => {
if prev_range == next_range {
count -= 1;
break;
}
if next_incompatible {
break;
}
}
}
prev_range = next_range;
count += 1;
if self.reversed && count > 1 && &self.text[prev_range.clone()] == " " {
break;
}
}
if count == 1 {
use_span = Some(first_incompatible);
}
let (head, tail) = self.slice.split_at(count);
self.slice = tail;
let fragment = match use_span.unwrap_or(false) {
true => GlyphSpan::Spanned(head, &self.text[first_range]),
false => GlyphSpan::Unspanned(head),
};
Some(fragment)
}
}
pub(crate) struct GlyphGroup<'a, T>
where
T: Glyph,
{
pub(crate) font_identifier: FontIdentifier,
pub(crate) glyphs: &'a [T],
pub(crate) y_offset: f32,
pub(crate) y_advance: f32,
}
impl<'a, T> GlyphGroup<'a, T>
where
T: Glyph,
{
pub fn from_props(glyphs: &'a [T], props: GlyphProps) -> Self {
GlyphGroup {
font_identifier: props.font_identifier,
glyphs,
y_offset: props.y_offset,
y_advance: props.y_advance,
}
}
}
pub(crate) struct GlyphGrouper<'a, T>
where
T: Glyph,
{
font_container: Rc<RefCell<FontContainer>>,
context_color: rgb::Color,
slice: &'a [T],
}
impl<'a, T> GlyphGrouper<'a, T>
where
T: Glyph,
{
pub fn new(
font_container: Rc<RefCell<FontContainer>>,
context_color: rgb::Color,
slice: &'a [T],
) -> Self {
Self {
font_container,
context_color,
slice,
}
}
}
impl<'a, T> Iterator for GlyphGrouper<'a, T>
where
T: Glyph,
{
type Item = GlyphGroup<'a, T>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let (head, tail, props) = {
let mut count = 1;
let mut iter = self.slice.iter();
let first = get_glyph_props(
iter.next()?,
self.context_color,
&mut self.font_container.borrow_mut(),
);
for next in iter {
let temp_glyph = get_glyph_props(
next,
self.context_color,
&mut self.font_container.borrow_mut(),
);
if first.font_identifier != temp_glyph.font_identifier
|| first.y_offset != temp_glyph.y_offset
|| first.y_advance != 0.0
|| temp_glyph.y_advance != 0.0
{
break;
}
count += 1;
}
let (head, tail) = self.slice.split_at(count);
(head, tail, first)
};
self.slice = tail;
let glyph_group = GlyphGroup::from_props(head, props);
Some(glyph_group)
}
}
pub(crate) struct GlyphProps {
font_identifier: FontIdentifier,
y_offset: f32,
y_advance: f32,
}
pub(crate) fn get_glyph_props<U>(
g: &U,
context_color: rgb::Color,
font_container: &mut FontContainer,
) -> GlyphProps
where
U: Glyph,
{
let font_identifier = font_container
.font_identifier(ColoredGlyph::new(g.glyph_id(), context_color))
.unwrap();
GlyphProps {
font_identifier,
y_offset: g.y_offset(1.0),
y_advance: g.y_advance(1.0),
}
}
pub fn use_text_spanner(
glyphs: &[impl Glyph],
text: &str,
context_color: rgb::Color,
font_container: &mut FontContainer,
) -> bool {
if glyphs.is_empty() {
return false;
}
let mut do_text_span = false;
let mut check_single = |glyph, do_text_span: &mut bool| {
if !*do_text_span {
check_text_span_prop(glyph, text, context_color, font_container, do_text_span);
}
};
for glyphs in glyphs.windows(2) {
let prev = &glyphs[0];
let next = &glyphs[1];
do_text_span |= prev.text_range() == next.text_range();
check_single(prev, &mut do_text_span);
}
check_single(glyphs.last().unwrap(), &mut do_text_span);
do_text_span
}
fn check_text_span_prop(
glyph: &impl Glyph,
text: &str,
context_color: rgb::Color,
font_container: &mut FontContainer,
do_text_span: &mut bool,
) {
let (identifier, pdf_glyph) =
font_container.add_glyph(ColoredGlyph::new(glyph.glyph_id(), context_color));
let pdf_font = font_container
.get_from_identifier_mut(identifier.clone())
.unwrap();
let range = glyph.text_range().clone();
let text = &text[range.clone()];
let codepoints = pdf_font.get_codepoints(pdf_glyph);
if codepoints.is_some_and(|t| t != text) {
*do_text_span = true;
} else if codepoints.is_none() {
pdf_font.set_codepoints(pdf_glyph, text.to_string(), glyph.location());
}
}