use std::fmt::Write as _;
use std::ops::Range;
use kurbo::Affine;
use pdfrum_object::{Object, names as pdf_names};
use super::{Canvas, Paint, write_f64};
use crate::Error;
use crate::font::glyph::{Claim, GlyphFont};
use crate::write_matrix;
#[derive(Debug, Clone, PartialEq)]
pub struct RunGlyph {
pub id: u16,
pub x: f64,
pub y: f64,
pub text: Range<usize>,
}
#[derive(Debug, Clone)]
pub struct GlyphRun<'a> {
pub font: &'a GlyphFont,
pub size: f64,
pub transform: Affine,
pub glyphs: &'a [RunGlyph],
pub text: &'a str,
pub paint: Paint,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Voice {
Mapped,
Actual(String),
}
#[derive(Debug)]
struct Stretch {
glyphs: Range<usize>,
voice: Voice,
}
impl Canvas<'_, '_> {
pub fn glyphs(&mut self, run: &GlyphRun<'_>) {
if run.glyphs.is_empty() || !(run.size.is_finite() && run.size > 0.0) {
return;
}
let Some(face) = self.edit.glyph_face(run.font) else {
return self.fail(Error::ForeignGlyphFont);
};
let ids: Vec<u16> = run.glyphs.iter().map(|glyph| glyph.id).collect();
let cids = match face.cids(&ids) {
Ok(cids) => cids,
Err(error) => return self.fail(error),
};
let stretches = stretches(run, |cid, text| face.claim(cid, text), &cids);
let name = self.realize(pdf_names::FONT, Object::Ref(run.font.object()));
let mode = match run.paint {
Paint::Fill(_) => None,
Paint::Stroke(_) => Some(1),
Paint::FillStroke(..) => Some(2),
};
self.out.push_str("q\n");
self.set_paint(run.paint.clone());
self.out.push_str("BT\n/");
self.push_name(&name);
self.out.push(' ');
write_f64(&mut self.out, run.size);
self.out.push_str(" Tf\n");
if let Some(mode) = mode {
let _ = writeln!(self.out, "{mode} Tr");
}
for stretch in &stretches {
self.stretch(run, &cids, stretch);
}
self.out.push_str("ET\nQ\n");
}
fn stretch(&mut self, run: &GlyphRun<'_>, cids: &[(u16, u32)], stretch: &Stretch) {
let (Some(first), Some(codes)) = (
run.glyphs.get(stretch.glyphs.start),
cids.get(stretch.glyphs.clone()),
) else {
return;
};
if let Voice::Actual(text) = &stretch.voice {
self.out.push_str("/Span <</ActualText <FEFF");
for unit in text.encode_utf16() {
let _ = write!(self.out, "{unit:04X}");
}
self.out.push_str(">>> BDC\n");
}
write_matrix(
&mut self.out,
run.transform * Affine::translate((first.x, first.y)),
);
self.out.push_str(" Tm\n[");
let glyphs = run.glyphs.get(stretch.glyphs.clone()).unwrap_or_default();
for (at, ((cid, width), glyph)) in codes.iter().zip(glyphs).enumerate() {
let _ = write!(self.out, "<{cid:04X}>");
if let Some(next) = glyphs.get(at + 1) {
let adjustment = f64::from(*width) - (next.x - glyph.x) * 1000.0 / run.size;
if adjustment.abs() > 1e-3 {
write_f64(&mut self.out, adjustment);
}
}
}
self.out.push_str("] TJ\n");
if matches!(stretch.voice, Voice::Actual(_)) {
self.out.push_str("EMC\n");
}
}
}
fn stretches(
run: &GlyphRun<'_>,
mut claim: impl FnMut(u16, &str) -> Claim,
cids: &[(u16, u32)],
) -> Vec<Stretch> {
let mut out: Vec<Stretch> = Vec::new();
let mut start = 0;
while start < run.glyphs.len() {
let Some(range) = run.glyphs.get(start).map(|glyph| glyph.text.clone()) else {
break;
};
let end = run
.glyphs
.iter()
.skip(start)
.position(|glyph| glyph.text != range)
.map_or(run.glyphs.len(), |offset| start + offset);
let text = run.text.get(range).unwrap_or_default();
let voice = match (end - start, cids.get(start)) {
(1, Some((cid, _))) => match claim(*cid, text) {
Claim::Mapped => Voice::Mapped,
Claim::Taken => Voice::Actual(text.to_owned()),
},
_ if text.is_empty() => Voice::Mapped,
_ => Voice::Actual(text.to_owned()),
};
let same_line = |a: usize, b: usize| match (run.glyphs.get(a), run.glyphs.get(b)) {
(Some(a), Some(b)) => (a.y - b.y).abs() < 1e-9,
_ => false,
};
match out.last_mut() {
Some(last)
if voice == Voice::Mapped
&& last.voice == Voice::Mapped
&& same_line(last.glyphs.start, start)
&& (start..end).all(|at| same_line(start, at)) =>
{
last.glyphs.end = end;
}
_ => {
let mut from = start;
for at in start + 1..=end {
if at == end || !same_line(from, at) {
out.push(Stretch {
glyphs: from..at,
voice: voice.clone(),
});
from = at;
}
}
}
}
start = end;
}
out
}