use crate::Engine;
use crate::glue::Glue;
use crate::nodes::{NodeData, NodeRef, RUNNING, Whatsit, glue_sub, id};
use crate::primitives_gen::{assign_dimen as dp, assign_int as ip};
pub trait Backend {
fn begin_page(&mut self, engine: &Engine, width: i32, height: i32);
fn glyph(&mut self, engine: &Engine, font: u32, chr: u32, x: i32, y: i32);
fn glyph_expanded(&mut self, engine: &Engine, font: u32, chr: u32, x: i32, y: i32, ef: i32) {
let _ = ef;
self.glyph(engine, font, chr, x, y);
}
fn rule(&mut self, x: i32, y: i32, width: i32, height: i32);
fn literal(&mut self, mode: u8, data: &[u8], x: i32, y: i32);
fn end_page(&mut self, engine: &Engine);
fn finish(&mut self, engine: &mut Engine) -> Option<Finished>;
fn save(&mut self, x: i32, y: i32) {
self.literal(0, b"q", x, y);
}
fn restore(&mut self, x: i32, y: i32) {
self.literal(0, b"Q", x, y);
}
fn set_matrix(&mut self, x: i32, y: i32, matrix: &[u8]) {
let mut d = matrix.to_vec();
d.extend_from_slice(b" 0 0 cm");
self.literal(0, &d, x, y);
}
fn abandon(&mut self, _remove: bool) {}
fn begin_object(&mut self, _engine: &Engine) {}
fn current_page_object(&self) -> Option<i32> {
None
}
fn close_page_contents(&mut self, _engine: &Engine, _items: &crate::pdfext::PageItems) {}
fn page_items(&mut self, _engine: &Engine, _items: &crate::pdfext::PageItems) {}
fn begin_box_form(
&mut self,
_engine: &Engine,
_index: u32,
_size: (i32, i32, i32),
_attr: &[u8],
_resources: &[u8],
) {
}
fn end_box_form(&mut self, _engine: &Engine) {}
fn close_box_form_resources(&mut self, _engine: &Engine) {}
fn close_page_resources(&mut self, _engine: &Engine) {}
fn box_form(
&mut self,
_engine: &Engine,
_index: u32,
_x: i32,
_y: i32,
_width: i32,
_height: i32,
) {
}
fn write_image(&mut self, _engine: &Engine, _index: u32) {}
fn image(
&mut self,
_engine: &Engine,
_index: u32,
_x: i32,
_y: i32,
_width: i32,
_height: i32,
) {
}
fn write_object(&mut self, _engine: &Engine, _obj: &crate::pdfext::PdfObject) {}
}
#[derive(Clone, Debug, Default)]
pub struct Finished {
pub file: String,
pub size: u64,
pub embedded: Vec<String>,
pub objstm: (u32, u32),
}
struct GlueState {
cur_g: i32,
cur_glue: f64,
order: u8,
sign: u8,
set: f64,
}
impl GlueState {
fn advance(&mut self, g: &Glue) -> i32 {
let mut w = g.width - self.cur_g;
if self.sign != 0 {
let applies = if self.sign == 1 {
g.stretch_order == self.order
} else {
g.shrink_order == self.order
};
if applies {
if self.sign == 1 {
self.cur_glue += f64::from(g.stretch);
} else {
self.cur_glue -= f64::from(g.shrink);
}
let t = (self.set * self.cur_glue).clamp(-1_000_000_000.0, 1_000_000_000.0);
self.cur_g = t.round() as i32;
}
}
w += self.cur_g;
w
}
}
impl Engine {
pub fn ship_out(&mut self, p: NodeRef) {
self.pdf.page_annots.clear();
let tracing = self.eqtb.int(ip::TRACINGOUTPUT) > 0;
if tracing {
self.printer.print_nl("");
self.printer.print_ln();
self.printer.print_str("Completed box being shipped out");
}
if self.printer.term_offset > self.printer.max_print_line.saturating_sub(9) {
self.printer.print_ln();
} else if self.printer.term_offset > 0 || self.printer.file_offset > 0 {
self.printer.print_char(' ' as u32);
}
self.printer.print_char('[' as u32);
let mut j = 9;
while j > 0 && self.eqtb.count[j].val == 0 {
j -= 1;
}
for k in 0..=j {
self.printer.print_int(self.eqtb.count[k].val.into());
if k < j {
self.printer.print_char('.' as u32);
}
}
self.printer.update_terminal();
if tracing {
self.printer.print_char(']' as u32);
self.begin_diagnostic();
self.show_box(p);
self.end_diagnostic(true);
}
self.ship_box(p);
if !tracing {
self.printer.print_char(']' as u32);
}
self.dead_cycles = 0;
self.printer.update_terminal();
self.nodes.flush_list(p);
}
fn ship_box(&mut self, p: NodeRef) {
let Some(b) = self.nodes.box_data(p).cloned() else {
return;
};
let pdf = self.eqtb.int(ip::OUTPUTMODE) > 0;
let h_origin = self.eqtb.dimen(crate::backend::H_ORIGIN);
let v_origin = self.eqtb.dimen(crate::backend::V_ORIGIN);
let h_off = self.eqtb.dimen(dp::HOFFSET);
let v_off = self.eqtb.dimen(dp::VOFFSET);
let mut width = self.eqtb.dimen(dp::PAGEWIDTH);
let mut height = self.eqtb.dimen(dp::PAGEHEIGHT);
if width <= 0 {
width = b.width + 2 * (h_origin + h_off);
}
if height <= 0 {
height = b.height + b.depth + 2 * (v_origin + v_off);
}
let mut backend = if pdf { self.backend.take() } else { None };
let forms_depth = self.pending_forms.len();
self.pending_forms.push(Vec::new());
if let Some(be) = backend.as_mut() {
self.pdf.page = Default::default();
be.begin_page(self, width, height);
if let Some(n) = be.current_page_object() {
let page = self.total_pages as i32 + 1;
self.pdf.page_refs.insert(page, n);
self.pdf.kinds.insert(n, crate::pdfext::ObjKind::Page);
}
let (x, y) = (h_origin + h_off, height - (v_origin + v_off + b.height));
for (mode, lit) in self.page_colorstack_literals() {
be.literal(mode, &lit, x, y);
}
}
let dvi = if pdf {
None
} else {
let mut d = std::mem::take(&mut self.dvi);
d.begin_page(self, (b.width, b.height, b.depth), (h_off, v_off));
Some(d)
};
let mut walker = Walker {
backend,
dvi,
h_origin: h_origin + h_off,
v_origin: v_origin + v_off,
page_height: height,
dvi_h: h_off,
dvi_v: v_off,
cur_s: -1,
doing_leaders: false,
page: true,
};
let cur_h = 0;
let cur_v = b.height;
if self.nodes.id(p) == id::VLIST {
self.vlist_out(&mut walker, p, cur_h, cur_v);
} else {
self.hlist_out(&mut walker, p, cur_h, cur_v);
}
if let Some(mut be) = walker.backend.take() {
self.flush_pending_objects(be.as_mut());
self.collect_page_texts();
let items = std::mem::take(&mut self.pdf.page);
be.close_page_contents(self, &items);
self.pdf.page = items;
self.resolve_link_actions();
let items = std::mem::take(&mut self.pdf.page);
be.page_items(self, &items);
be.end_page(self);
let mut be = self.write_pending_forms(be);
be.close_page_resources(self);
self.flush_pending_objects(be.as_mut());
self.backend = Some(be);
for n in std::mem::take(&mut self.pdf.page_images) {
self.print_image_name(n);
}
}
self.pending_forms.truncate(forms_depth);
if let Some(mut d) = walker.dvi.take() {
d.end_page();
self.dvi = d;
}
self.total_pages += 1;
}
fn flush_pending_objects(&mut self, be: &mut dyn Backend) {
for obj in std::mem::take(&mut self.pdf.pending) {
be.write_object(self, &obj);
}
}
pub(crate) fn print_map_file_note(&mut self, name: &str) {
let path = self
.kpse
.as_ref()
.and_then(|k| k.borrow_mut().find_file(name, retex_kpath::FileFormat::Map));
if let Some(path) = path {
let mut text = b"{".to_vec();
text.extend_from_slice(path.to_string_lossy().as_bytes());
text.push(b'}');
self.printer.print_lua_bytes(&text);
}
}
fn out_glyph(&self, w: &mut Walker, font: u32, chr: u32, h: i32, v: i32, ef: i32) {
self.out_glyph_at_depth(w, font, chr, h, v, ef, 0);
}
#[allow(clippy::too_many_arguments)]
fn out_glyph_at_depth(
&self,
w: &mut Walker,
font: u32,
chr: u32,
h: i32,
v: i32,
ef: i32,
depth: u32,
) {
if let Some(vf) = &self.font(font).vf
&& let Some(cmds) = vf.packets.get(&chr)
{
if depth < 8 {
self.out_packet(w, vf, cmds, h, v, ef, depth);
}
return;
}
let (x, y) = w.pos(h, v);
if let Some(be) = w.backend.as_deref_mut() {
if ef == 0 {
be.glyph(self, font, chr, x, y);
} else {
be.glyph_expanded(self, font, chr, x, y, ef);
}
}
if let Some(d) = w.dvi.as_mut()
&& self.font(font).chars.contains_key(&chr)
{
let wd = crate::expansion::expanded_width(self.font_char_dimen(font, chr, 0), ef);
d.glyph(self, font, chr, w.dvi_h + h, w.dvi_v + v, wd);
}
}
#[allow(clippy::too_many_arguments)]
fn out_packet(
&self,
w: &mut Walker,
vf: &crate::fonts::VirtualFont,
cmds: &[retex_font::vf::Command],
mut h: i32,
mut v: i32,
ef: i32,
depth: u32,
) {
use retex_font::vf::Command;
let mut f = vf.fonts.first().copied().unwrap_or(0);
let mut stack: Vec<(i32, i32)> = Vec::new();
for c in cmds {
match c {
Command::Font(i) => f = vf.fonts.get(*i).copied().unwrap_or(0),
Command::Char(c) => {
if f != 0 && self.font(f).chars.contains_key(c) {
self.out_glyph_at_depth(w, f, *c, h, v, ef, depth + 1);
h = h.wrapping_add(crate::expansion::expanded_width(
self.font_char_dimen(f, *c, 0),
ef,
));
}
}
Command::Rule { height, width } => {
if *height > 0 && *width > 0 {
Self::out_rule(w, h, v, *width, *height);
}
h = h.wrapping_add(*width);
}
Command::Right(x) => h = h.wrapping_add(*x),
Command::Down(y) => v = v.wrapping_add(*y),
Command::Push => stack.push((h, v)),
Command::Pop => {
if let Some((sh, sv)) = stack.pop() {
(h, v) = (sh, sv);
}
}
Command::Special(_) => {}
}
}
}
fn out_rule(w: &mut Walker, h: i32, v: i32, width: i32, height: i32) {
let (x, y) = w.pos(h, v);
if let Some(be) = w.backend.as_deref_mut() {
be.rule(x, y, width, height);
}
if let Some(d) = w.dvi.as_mut() {
d.rule(w.dvi_h + h, w.dvi_v + v, width, height);
}
}
fn out_box_resource(
&mut self,
w: &mut Walker,
index: i32,
h: i32,
v: i32,
width: i32,
height: i32,
) {
let n = index as u32;
let (x, y) = w.pos(h, v);
let Some(mut be) = w.backend.take() else {
return;
};
if self
.box_resource_slot(n)
.is_some_and(|i| !self.box_resources[i].written)
{
match self.pending_forms.last_mut() {
Some(p) => {
if !p.contains(&n) {
p.push(n);
}
}
None => be = self.write_box_resource(be, n),
}
}
be.box_form(self, n, x, y, width, height);
w.backend = Some(be);
}
pub fn write_box_resource(&mut self, be: Box<dyn Backend>, n: u32) -> Box<dyn Backend> {
let Some(slot) = self.box_resource_slot(n) else {
return be;
};
let r = self.box_resources[slot].clone();
if r.written {
return be;
}
let mut be = be;
let (bw, bh, bd) = r.size;
self.pending_forms.push(Vec::new());
self.form_objects.push(Vec::new());
be.begin_box_form(self, n, (bw, bh, bd), &r.attr, &r.resources);
let mut walker = Walker {
backend: Some(be),
dvi: None,
h_origin: 0,
v_origin: 0,
page_height: bh + bd,
dvi_h: 0,
dvi_v: 0,
cur_s: -1,
doing_leaders: false,
page: false,
};
if self.nodes.id(r.list) == id::VLIST {
self.vlist_out(&mut walker, r.list, 0, bh);
} else {
self.hlist_out(&mut walker, r.list, 0, bh);
}
let mut be = walker.backend.take().expect("the walker keeps the backend");
be.end_box_form(self);
self.box_resources[slot].written = true;
for obj in self.form_objects.pop().unwrap_or_default() {
be.write_object(self, &obj);
}
be = self.write_pending_forms(be);
be.close_box_form_resources(self);
be
}
fn write_pending_forms(&mut self, mut be: Box<dyn Backend>) -> Box<dyn Backend> {
for n in self.pending_forms.pop().unwrap_or_default() {
be = self.write_box_resource(be, n);
}
be
}
fn out_image(&mut self, w: &mut Walker, index: i32, h: i32, v: i32, width: i32, height: i32) {
let (x, y) = w.pos(h, v);
if let Some(be) = w.backend.as_deref_mut() {
be.image(self, index as u32, x, y, width, height);
if self.mark_image_written(index as u32) {
self.pdf.page_images.push(index as u32);
}
}
}
fn out_box(&mut self, w: &mut Walker, p: NodeRef, h: i32, v: i32) {
if self.nodes.id(p) == id::VLIST {
self.vlist_out(w, p, h, v);
} else {
self.hlist_out(w, p, h, v);
}
}
fn enter_box(w: &mut Walker) {
w.cur_s += 1;
if let Some(d) = w.dvi.as_mut() {
if w.cur_s > 0 {
d.push(w.cur_s);
} else {
d.note_depth(w.cur_s);
}
}
}
fn leave_box(w: &mut Walker) {
if w.cur_s > 0
&& let Some(d) = w.dvi.as_mut()
{
d.pop();
}
w.cur_s -= 1;
}
fn hlist_out(&mut self, w: &mut Walker, this_box: NodeRef, mut cur_h: i32, cur_v: i32) {
let Some(b) = self.nodes.box_data(this_box).cloned() else {
return;
};
let mut gs = GlueState {
cur_g: 0,
cur_glue: 0.0,
order: b.glue_order,
sign: b.glue_sign,
set: b.glue_set,
};
Self::enter_box(w);
let base_line = cur_v;
let left_edge = cur_h;
let ctx = crate::pdfship::BoxCtx {
left: left_edge,
base: base_line,
width: b.width,
height: b.height,
depth: b.depth,
};
if !w.doing_leaders {
self.ship_continue_links(w, &ctx);
}
let mut p = b.head;
while !p.is_null() {
let next = self.nodes.next(p);
let n = self.nodes.get(p);
match &n.data {
NodeData::Glyph(g) => {
let (font, chr, xo, yo, ef) =
(g.font, g.chr, g.xoffset, g.yoffset, g.expansion_factor);
if !self.pdf.map_note_done && w.backend.is_some() {
self.pdf.map_note_done = true;
self.print_map_file_note("pdftex.map");
}
self.out_glyph(w, font, chr, cur_h + xo, base_line - yo, ef);
cur_h +=
crate::expansion::expanded_width(self.font_char_dimen(font, chr, 0), ef);
}
NodeData::Box(inner) => {
let (iw, shift, head) = (inner.width, inner.shift, inner.head);
if !head.is_null() {
self.out_box(w, p, cur_h, base_line + shift);
}
cur_h += iw;
}
NodeData::Rule {
width,
depth,
height,
index,
..
} => {
let (wd, ht, dpt, idx) = (*width, *height, *depth, *index);
if n.subtype == crate::image::IMAGE_RULE {
self.out_image(w, idx, cur_h, base_line + dpt, wd, ht + dpt);
} else if n.subtype == crate::image::BOX_RESOURCE_RULE {
self.out_box_resource(w, idx, cur_h, base_line + dpt, wd, ht + dpt);
} else {
Self::hlist_rule(w, cur_h, base_line, wd, ht, dpt, &b);
}
cur_h += wd;
}
NodeData::Glue { spec, leader } => {
let (spec, leader, sub) = (*spec, *leader, n.subtype);
let wd = gs.advance(&spec);
if sub >= glue_sub::A_LEADERS && !leader.is_null() {
self.hlist_leaders(w, leader, sub, cur_h, left_edge, base_line, wd, &b);
}
cur_h += wd;
}
NodeData::Kern {
kern,
expansion_factor,
} => cur_h += kern + expansion_factor,
NodeData::Math { surround, glue } => {
let g = *glue;
cur_h += surround;
if g != Glue::ZERO {
cur_h += gs.advance(&g);
}
}
NodeData::MarginKern { width, .. } => cur_h += width,
NodeData::Whatsit(_) => self.out_what(w, p, cur_h, base_line, &ctx),
NodeData::Disc { replace, .. } => {
let r = *replace;
let mut q = r;
while !q.is_null() {
if let NodeData::Glyph(g) = &self.nodes.get(q).data {
let (font, chr, ef) = (g.font, g.chr, g.expansion_factor);
self.out_glyph(w, font, chr, cur_h, base_line, ef);
cur_h += crate::expansion::expanded_width(
self.font_char_dimen(font, chr, 0),
ef,
);
} else if let NodeData::Kern {
kern,
expansion_factor,
} = &self.nodes.get(q).data
{
cur_h += kern + expansion_factor;
}
q = self.nodes.next(q);
}
}
_ => {}
}
p = next;
}
if !w.doing_leaders {
self.ship_end_box_links(w, &ctx);
}
Self::leave_box(w);
}
fn hlist_rule(
w: &mut Walker,
cur_h: i32,
base_line: i32,
wd: i32,
ht: i32,
dpt: i32,
this_box: &crate::nodes::BoxData,
) {
let ht = if ht == RUNNING { this_box.height } else { ht };
let dpt = if dpt == RUNNING { this_box.depth } else { dpt };
let total = ht + dpt;
if total > 0 && wd > 0 {
Self::out_rule(w, cur_h, base_line + dpt, wd, total);
}
}
fn vlist_out(&mut self, w: &mut Walker, this_box: NodeRef, cur_h: i32, cur_v: i32) {
let Some(b) = self.nodes.box_data(this_box).cloned() else {
return;
};
let mut gs = GlueState {
cur_g: 0,
cur_glue: 0.0,
order: b.glue_order,
sign: b.glue_sign,
set: b.glue_set,
};
Self::enter_box(w);
let left_edge = cur_h;
let ctx = crate::pdfship::BoxCtx {
left: left_edge,
base: cur_v,
width: b.width,
height: b.height,
depth: b.depth,
};
let mut cur_v = cur_v - b.height;
let top_edge = cur_v;
let mut p = b.head;
while !p.is_null() {
let next = self.nodes.next(p);
let n = self.nodes.get(p);
match &n.data {
NodeData::Box(inner) => {
let (ih, idp, shift, head) =
(inner.height, inner.depth, inner.shift, inner.head);
if head.is_null() {
cur_v += ih + idp;
} else {
cur_v += ih;
self.out_box(w, p, left_edge + shift, cur_v);
cur_v += idp;
}
}
NodeData::Rule {
width,
depth,
height,
index,
..
} => {
let wd = if *width == RUNNING { b.width } else { *width };
let total = height + depth;
let idx = *index;
cur_v += total;
if n.subtype == crate::image::IMAGE_RULE {
self.out_image(w, idx, left_edge, cur_v, wd, total);
} else if n.subtype == crate::image::BOX_RESOURCE_RULE {
self.out_box_resource(w, idx, left_edge, cur_v, wd, total);
} else if total > 0 && wd > 0 {
Self::out_rule(w, left_edge, cur_v, wd, total);
}
}
NodeData::Glue { spec, leader } => {
let (spec, leader, sub) = (*spec, *leader, n.subtype);
let ht = gs.advance(&spec);
if sub >= glue_sub::A_LEADERS && !leader.is_null() {
cur_v =
self.vlist_leaders(w, leader, sub, cur_v, left_edge, top_edge, ht, &b);
} else {
cur_v += ht;
}
}
NodeData::Kern { kern, .. } => cur_v += kern,
NodeData::Whatsit(_) => self.out_what(w, p, left_edge, cur_v, &ctx),
_ => {}
}
p = next;
}
Self::leave_box(w);
}
fn leader_start(sub: u16, cur: i32, edge0: i32, size: i32, step: i32) -> (i32, i32) {
if sub == glue_sub::C_LEADERS || sub == glue_sub::X_LEADERS {
let lq = size / step;
let lr = size % step;
if sub == glue_sub::C_LEADERS {
(cur + lr / 2, 0)
} else {
let lx = lr / (lq + 1);
(cur + (lr - (lq - 1) * lx) / 2, lx)
}
} else {
let mut c = edge0 + step * ((cur - edge0) / step);
if c < cur {
c += step;
}
(c, 0)
}
}
#[allow(clippy::too_many_arguments)]
fn hlist_leaders(
&mut self,
w: &mut Walker,
leader: NodeRef,
sub: u16,
cur_h: i32,
left_edge: i32,
base_line: i32,
wd: i32,
this_box: &crate::nodes::BoxData,
) {
let lb = match &self.nodes.get(leader).data {
NodeData::Rule { height, depth, .. } => {
let (ht, dpt) = (*height, *depth);
Self::hlist_rule(w, cur_h, base_line, wd, ht, dpt, this_box);
return;
}
NodeData::Box(lb) => lb.clone(),
_ => return,
};
let leader_wd = lb.width;
if leader_wd <= 0 || wd <= 0 {
return;
}
let rule_wd = wd + 10; let edge = cur_h + rule_wd;
let (mut h, lx) = Self::leader_start(sub, cur_h, left_edge, rule_wd, leader_wd);
while h + leader_wd <= edge {
let outer = w.doing_leaders;
w.doing_leaders = true;
self.out_box(w, leader, h, base_line + lb.shift);
w.doing_leaders = outer;
h += leader_wd + lx;
}
}
#[allow(clippy::too_many_arguments)]
fn vlist_leaders(
&mut self,
w: &mut Walker,
leader: NodeRef,
sub: u16,
cur_v: i32,
left_edge: i32,
top_edge: i32,
ht: i32,
this_box: &crate::nodes::BoxData,
) -> i32 {
let lb = match &self.nodes.get(leader).data {
NodeData::Rule { width, .. } => {
let wd = if *width == RUNNING {
this_box.width
} else {
*width
};
if ht > 0 && wd > 0 {
Self::out_rule(w, left_edge, cur_v + ht, wd, ht);
}
return cur_v + ht;
}
NodeData::Box(lb) => lb.clone(),
_ => return cur_v + ht,
};
let leader_ht = lb.height + lb.depth;
if leader_ht <= 0 || ht <= 0 {
return cur_v + ht;
}
let rule_ht = ht + 10; let edge = cur_v + rule_ht;
let (mut v, lx) = Self::leader_start(sub, cur_v, top_edge, rule_ht, leader_ht);
while v + leader_ht <= edge {
let outer = w.doing_leaders;
w.doing_leaders = true;
self.out_box(w, leader, left_edge + lb.shift, v + lb.height);
w.doing_leaders = outer;
v += leader_ht + lx;
}
edge - 10
}
fn out_what(
&mut self,
w: &mut Walker,
p: NodeRef,
h: i32,
v: i32,
ctx: &crate::pdfship::BoxCtx,
) {
let NodeData::Whatsit(wh) = &self.nodes.get(p).data else {
return;
};
let (x, y) = w.pos(h, v);
match (**wh).clone() {
Whatsit::Open { stream, name } if !w.doing_leaders => {
self.open_write_stream(stream as i32, &name)
}
Whatsit::Write { stream, data } if !w.doing_leaders => self.write_out(stream, &data),
Whatsit::Close { stream } if !w.doing_leaders => self.close_write_stream(stream as i32),
Whatsit::Special { data } => {
if let Some(d) = w.dvi.as_mut() {
d.special(w.dvi_h + h, w.dvi_v + v, &data);
}
}
Whatsit::PdfLiteral { mode, data, .. } => {
let bytes = match data {
crate::nodes::LateData::Bytes(b) => b,
crate::nodes::LateData::Tokens(t) => {
let toks = self.expand_late_tokens(&t);
self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX))
}
};
if let Some(be) = w.backend.as_deref_mut() {
be.literal(mode, &bytes, x, y);
}
}
Whatsit::LateLua { data, .. } => {
let code = match data {
crate::nodes::LateData::Bytes(b) => b,
crate::nodes::LateData::Tokens(t) => {
let toks = self.expand_late_tokens(&t);
self.lua_code(&toks)
}
};
self.pdf.pos = (x, y);
let outer = self.pdf.late.replace(Default::default());
self.run_lua(&code);
let ctx = std::mem::replace(&mut self.pdf.late, outer).unwrap_or_default();
if let Some(be) = w.backend.as_deref_mut() {
for (mode, text) in &ctx.prints {
be.literal(*mode, text, x, y);
}
}
self.pdf
.page
.annots
.extend(ctx.annots.into_iter().map(|a| (a, None)));
}
Whatsit::PdfRefobj { objnum } if !w.doing_leaders => self.ship_refobj(w, objnum),
Whatsit::PdfAnnot { dims, objnum, data } if !w.doing_leaders => {
self.ship_annot(w, ctx, h, v, dims, objnum, &data)
}
Whatsit::PdfStartLink {
dims,
objnum,
attr,
action,
} if !w.doing_leaders => {
self.ship_start_link(w, ctx, h, v, dims, objnum, &attr, &action)
}
Whatsit::PdfEndLink if !w.doing_leaders => self.ship_end_link(w, h, v),
Whatsit::PdfDest { id, view } if !w.doing_leaders => {
self.ship_dest(w, ctx, h, v, &id, &view)
}
Whatsit::SavePos => {
self.last_x_pos = x;
self.last_y_pos = y;
}
Whatsit::PdfSave => {
if let Some(be) = w.backend.as_deref_mut() {
be.save(x, y);
}
}
Whatsit::PdfRestore => {
if let Some(be) = w.backend.as_deref_mut() {
be.restore(x, y);
}
}
Whatsit::PdfSetmatrix { data } => {
if let Some(be) = w.backend.as_deref_mut() {
be.set_matrix(x, y, &data);
}
}
Whatsit::PdfColorstack { stack, cmd, data } => {
let out = self.colorstack_op(stack, cmd, data);
if let (Some((mode, lit)), Some(be)) = (out, w.backend.as_deref_mut()) {
be.literal(mode, &lit, x, y);
}
}
_ => {}
}
}
fn colorstack_op(&mut self, stack: i32, cmd: u8, data: Vec<u8>) -> Option<(u8, Vec<u8>)> {
use crate::pdfext::colorstack_cmd as c;
self.pdf_ensure_colorstacks();
let def = self.pdf.colorstacks.get(stack.max(0) as usize).cloned()?;
let st = self
.colorstack_values
.entry(stack)
.or_insert_with(|| vec![def.init.clone()]);
match cmd {
c::SET => {
if let Some(top) = st.last_mut() {
*top = data;
} else {
st.push(data);
}
}
c::PUSH => st.push(data),
c::POP => {
if st.len() > 1 {
st.pop();
}
}
_ => {}
}
let lit = st.last().cloned().unwrap_or_default();
Some((def.mode, lit))
}
fn expand_late_tokens(&mut self, list: &crate::token::TokenList) -> Vec<crate::token::Token> {
use crate::token::Token;
use std::rc::Rc;
self.ins_list(Rc::from(vec![
Token::RIGHT_BRACE,
Token::cs(self.cs.end_write),
]));
self.begin_token_list(list.clone(), crate::input::TokKind::WriteText);
self.ins_list(Rc::from(vec![Token::LEFT_BRACE]));
let toks = self.scan_toks(false, true);
self.get_token();
toks
}
pub fn finish_backend(&mut self) {
if self.dvi.pages() > 0 {
self.finish_dvi();
} else if self.pdf.output_opened && self.eqtb.int(ip::OUTPUTMODE) <= 0 {
let _ = std::fs::write(self.output_path("dvi"), b"");
}
let Some(mut be) = self.backend.take() else {
return;
};
if self.pdf.fatal || self.total_pages == 0 {
be.abandon(self.pdf.fatal);
self.backend = Some(be);
return;
}
if self.total_pages > 0 {
self.pdf_finish_document();
self.flush_pending_objects(be.as_mut());
}
if self.total_pages > 0
&& let Some(fin) = be.finish(self)
{
for f in &fin.embedded {
let mut text = b"<".to_vec();
text.extend_from_slice(f.as_bytes());
text.push(b'>');
self.printer.print_lua_bytes(&text);
}
self.pdf.objstm_stats = fin.objstm;
self.print_output_written(&fin.file, self.total_pages, fin.size);
}
self.backend = Some(be);
}
pub(crate) fn output_path(&self, ext: &str) -> std::path::PathBuf {
let job =
String::from_utf8_lossy(self.job_name.as_deref().unwrap_or(b"texput")).into_owned();
let name = format!("{job}.{ext}");
match &self.files.output_dir {
Some(d) => d.join(&name),
None => std::path::PathBuf::from(&name),
}
}
fn finish_dvi(&mut self) {
let mut dvi = std::mem::take(&mut self.dvi);
let pages = dvi.pages();
let data = dvi.finish(self);
let path = self.output_path("dvi");
let name = path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_default();
if std::fs::write(&path, &data).is_err() {
self.printer.print_nl("! I can't write on file `");
self.printer.print_bytes(name.as_bytes());
self.printer.print_str("'.");
return;
}
self.print_output_written(&name, pages, data.len() as u64);
}
fn print_output_written(&mut self, name: &str, pages: u32, size: u64) {
self.output_written = Some((name.to_owned(), pages, size));
self.printer.print_nl("Output written on ");
self.printer.print_bytes(name.as_bytes());
self.printer.print_str(" (");
self.printer.print_int(i64::from(pages));
self.printer
.print_str(if pages == 1 { " page" } else { " pages" });
self.printer.print_str(", ");
self.printer.print_int(size as i64);
self.printer.print_str(" bytes).");
}
}
pub(crate) struct Walker {
backend: Option<Box<dyn Backend>>,
dvi: Option<crate::dvi::DviWriter>,
h_origin: i32,
v_origin: i32,
page_height: i32,
dvi_h: i32,
dvi_v: i32,
pub(crate) cur_s: i32,
doing_leaders: bool,
pub(crate) page: bool,
}
impl Walker {
pub(crate) fn pos(&self, h: i32, v: i32) -> (i32, i32) {
(self.h_origin + h, self.page_height - (self.v_origin + v))
}
}