use crate::Engine;
use crate::nodes::{NodeData, NodeRef, id, kern_sub};
use crate::primitives_gen::assign_int as ip;
use crate::protrusion::round_div;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct FontExpansion {
pub stretch: i32,
pub shrink: i32,
pub step: i32,
}
impl FontExpansion {
pub fn new(stretch: i32, shrink: i32, step: i32) -> Option<FontExpansion> {
let step = step.clamp(0, 1000);
if step == 0 {
return None;
}
let limit = |l: i32, max: i32| l.clamp(0, max) / step * step;
let e = FontExpansion {
stretch: limit(stretch, 1000),
shrink: limit(shrink, 500),
step,
};
(e.stretch > 0 || e.shrink > 0).then_some(e)
}
fn amounts(self, w: i32, e: i64) -> FontStretch {
let full = |limit: i32| i64::from(expanded_width(w, limit * 1000)) - i64::from(w);
FontStretch {
stretch: round_div(full(self.stretch) * e, 1000) as i32,
shrink: round_div(-full(-self.shrink) * e, 1000) as i32,
}
}
}
#[inline]
pub fn expanded_width(w: i32, ef: i32) -> i32 {
if ef == 0 {
w
} else {
round_div(i64::from(w) * (1_000_000 + i64::from(ef)), 1_000_000) as i32
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct FontStretch {
pub stretch: i32,
pub shrink: i32,
}
impl Engine {
#[inline]
pub fn font_expansion(&self, f: u32) -> Option<FontExpansion> {
self.font(f).expansion
}
pub fn ef_code(&self, f: u32, c: u32) -> i32 {
self.font(f).char_codes[2].get(&c).copied().unwrap_or(1000)
}
pub fn set_font_expansion(&mut self, f: u32, stretch: i32, shrink: i32, step: i32) {
let e = FontExpansion::new(stretch, shrink, step);
if let Some(fi) = self.fonts.fonts.get_mut(f as usize) {
fi.expansion = e;
}
}
pub(crate) fn glyph_font_stretch(&self, p: NodeRef) -> FontStretch {
let Some(g) = self.nodes.glyph(p) else {
return FontStretch::default();
};
let Some(x) = self.font_expansion(g.font) else {
return FontStretch::default();
};
let e = i64::from(self.ef_code(g.font, g.chr).max(0));
let w = self.font_char_dimen(g.font, g.chr, 0);
x.amounts(w, e)
}
pub(crate) fn kern_font_stretch(
&self,
prev: NodeRef,
k: NodeRef,
next: NodeRef,
) -> FontStretch {
if prev.is_null() || next.is_null() || self.nodes.subtype(k) != kern_sub::FONT {
return FontStretch::default();
}
let (Some(a), Some(b)) = (self.nodes.glyph(prev), self.nodes.glyph(next)) else {
return FontStretch::default();
};
let Some(x) = self.font_expansion(a.font) else {
return FontStretch::default();
};
let NodeData::Kern { kern, .. } = self.nodes.data(k) else {
return FontStretch::default();
};
let mean = i64::from((self.ef_code(a.font, a.chr) + self.ef_code(b.font, b.chr)) / 2);
x.amounts(*kern, mean)
}
pub(crate) fn list_font_stretch(&self, mut p: NodeRef, kerns: bool) -> FontStretch {
let mut t = FontStretch::default();
let mut prev = NodeRef::NULL;
while !p.is_null() {
let s = match self.nodes.id(p) {
id::GLYPH => self.glyph_font_stretch(p),
id::KERN if kerns => self.kern_font_stretch(prev, p, self.nodes.next(p)),
_ => FontStretch::default(),
};
t.stretch += s.stretch;
t.shrink += s.shrink;
prev = p;
p = self.nodes.next(p);
}
t
}
pub(crate) fn expand_line(&mut self, list: NodeRef, width: i32) -> bool {
let kerns = self.eqtb.int(ip::ADJUSTSPACING) != 3;
let mut nat: i64 = 0;
let mut fs = FontStretch::default();
let mut any = false;
let mut infinite_stretch = false;
let mut prev = NodeRef::NULL;
let mut p = list;
while !p.is_null() {
let next = self.nodes.next(p);
match self.nodes.id(p) {
id::GLYPH => {
let (f, c) = match self.nodes.data_mut(p) {
NodeData::Glyph(g) => {
g.expansion_factor = 0;
(g.font, g.chr)
}
_ => unreachable!("glyph node without glyph data"),
};
nat += i64::from(self.font_char_dimen(f, c, 0));
if self.font_expansion(f).is_some() {
any = true;
let s = self.glyph_font_stretch(p);
fs.stretch += s.stretch;
fs.shrink += s.shrink;
}
}
id::KERN => {
if let NodeData::Kern {
kern,
expansion_factor,
} = self.nodes.data_mut(p)
{
*expansion_factor = 0;
nat += i64::from(*kern);
}
if kerns {
let s = self.kern_font_stretch(prev, p, next);
fs.stretch += s.stretch;
fs.shrink += s.shrink;
}
}
id::DISC => {
let (_, _, r) = self.nodes.disc_lists(p);
let mut q = r;
while !q.is_null() {
if let NodeData::Glyph(g) = self.nodes.data_mut(q) {
g.expansion_factor = 0;
let f = g.font;
if self.font_expansion(f).is_some() {
any = true;
let s = self.glyph_font_stretch(q);
fs.stretch += s.stretch;
fs.shrink += s.shrink;
}
}
q = self.nodes.next(q);
}
nat += i64::from(self.list_natural_width(r));
}
_ => {
if let NodeData::Glue { spec, .. } = self.nodes.data(p)
&& spec.stretch_order > 0
&& spec.stretch != 0
{
infinite_stretch = true;
}
nat += i64::from(self.node_natural_width(p));
}
}
prev = p;
p = next;
}
if !any {
return false;
}
let x = i64::from(width) - nat;
if x > 0 && infinite_stretch {
return false;
}
let (ratio, total) = if x > 0 {
(1, fs.stretch)
} else if x < 0 {
(-1, fs.shrink)
} else {
return false;
};
if total <= 0 {
return false;
}
let r = (round_div(x.abs() * 1000, i64::from(total))).min(1000) * ratio;
if r == 0 {
return true;
}
let mut prev = NodeRef::NULL;
let mut p = list;
while !p.is_null() {
let next = self.nodes.next(p);
match self.nodes.id(p) {
id::GLYPH => self.set_glyph_expansion(p, r, x > 0),
id::DISC => {
let (_, _, rl) = self.nodes.disc_lists(p);
let mut q = rl;
while !q.is_null() {
if self.nodes.id(q) == id::GLYPH {
self.set_glyph_expansion(q, r, x > 0);
}
q = self.nodes.next(q);
}
}
id::KERN if kerns => {
let s = self.kern_font_stretch(prev, p, next);
let amount = if x > 0 { s.stretch } else { s.shrink };
if let NodeData::Kern {
expansion_factor, ..
} = self.nodes.data_mut(p)
{
*expansion_factor = amount;
}
}
_ => {}
}
prev = p;
p = next;
}
false
}
fn set_glyph_expansion(&mut self, p: NodeRef, r: i64, stretching: bool) {
let Some((f, c)) = self.nodes.glyph(p).map(|g| (g.font, g.chr)) else {
return;
};
let Some(e) = self.font_expansion(f) else {
return;
};
let limit = i64::from(if stretching { e.stretch } else { e.shrink });
let code = i64::from(self.ef_code(f, c));
let step = i64::from(e.step);
let n = round_div(round_div(r * limit * code, 1_000_000), step) * step;
if let NodeData::Glyph(g) = self.nodes.data_mut(p) {
g.expansion_factor = (n.clamp(-limit, limit) * 1000) as i32;
}
}
fn node_natural_width(&self, p: NodeRef) -> i32 {
match self.nodes.data(p) {
NodeData::Glue { spec, .. } => spec.width,
NodeData::Box(b) => b.width,
NodeData::Unset { b, .. } => b.width,
NodeData::Rule { width, .. } => *width,
NodeData::MarginKern { width, .. } => *width,
NodeData::Math { surround, glue } => {
if glue.width != 0 || glue.stretch != 0 || glue.shrink != 0 {
glue.width
} else {
*surround
}
}
_ => 0,
}
}
fn list_natural_width(&self, mut p: NodeRef) -> i32 {
let mut w = 0;
while !p.is_null() {
w += match self.nodes.data(p) {
NodeData::Glyph(_) => self.glyph_dimensions(p).0,
NodeData::Kern {
kern,
expansion_factor,
} => kern + expansion_factor,
_ => self.node_natural_width(p),
};
p = self.nodes.next(p);
}
w
}
}
#[cfg(test)]
mod tests {
use super::{FontExpansion, FontStretch, expanded_width};
#[test]
fn limits() {
let e = |a, b, c| FontExpansion::new(a, b, c).map(|e| (e.stretch, e.shrink, e.step));
assert_eq!(e(20, 20, 1), Some((20, 20, 1)));
assert_eq!(e(1500, 600, 7), Some((994, 497, 7)));
assert_eq!(e(20, 20, 3), Some((18, 18, 3)));
assert_eq!(e(20, 0, 20), Some((20, 0, 20)));
assert_eq!(e(20, 20, 0), None);
assert_eq!(e(0, 0, 1), None);
}
#[test]
fn amounts() {
let x = |a, b| FontExpansion::new(a, b, 1).unwrap();
let st = |stretch, shrink| FontStretch { stretch, shrink };
assert_eq!(x(500, 500).amounts(400_425, 1000), st(200_213, 200_212));
assert_eq!(x(20, 20).amounts(-45_875, 1000), st(-918, -917));
assert_eq!(x(20, 20).amounts(45_875, 1000), st(918, 917));
assert_eq!(x(20, 20).amounts(400_425, 999), st(8001, 8000));
assert_eq!(x(20, 20).amounts(-45_875, 749), st(-688, -687));
assert_eq!(x(20, 20).amounts(345_886, 1000), st(6918, 6918));
}
#[test]
fn expanded_widths() {
assert_eq!(expanded_width(491_521, 20_000), 501_351);
assert_eq!(expanded_width(491_521, -20_000), 481_691);
assert_eq!(expanded_width(327_681, 0), 327_681);
}
}