k2f_docx 0.2.2

K2F lock → DOCX exporter
Documentation
use super::cell::{build_cell, cell_paints};
use super::TableIndex;
use crate::coord::{millipt_to_emu, millipt_to_twips, pt_to_emu, pt_to_twips};
use crate::geo::find_geo;
use crate::ir::{LineDash, ShapeBox, TableBox, TableRow};
use crate::text::FontCtx;
use crate::DocxError;
use k2f_core::{GeometryNode, Page, PaintOp, Rect, RunningBlockNode, SemanticNode};

#[allow(dead_code)]
pub(crate) fn table_on_page(
    index: &TableIndex,
    table_id: &str,
    page: &Page,
    ops: &[PaintOp],
    root: &SemanticNode,
    running: &[RunningBlockNode],
    fonts: &FontCtx,
    relative_height: u32,
) -> Result<Option<TableBox>, DocxError> {
    let Some(meta) = index.tables.get(table_id) else {
        return Ok(None);
    };
    let Some(geo) = find_geo(&page.root, table_id) else {
        return Ok(None);
    };
    let ncols = meta.ncols;
    if ncols == 0 {
        return Ok(None);
    }
    let complete = geo.children.len() / ncols;
    if complete == 0 {
        return Ok(None);
    }
    let mut row_slices: Vec<&[GeometryNode]> = Vec::with_capacity(complete);
    for r in 0..complete {
        let start = r * ncols;
        row_slices.push(&geo.children[start..start + ncols]);
    }
    let paints = cell_paints(ops)?;
    let col_widths_twips = col_widths_from_row(row_slices[0], geo);
    let heights = row_heights_twips(&row_slices, geo);
    let mut rows_out = Vec::with_capacity(complete);
    for (r, cells_geo) in row_slices.iter().enumerate() {
        let header = r < meta.header_rows;
        let mut cells = Vec::with_capacity(ncols);
        for (c, g) in cells_geo.iter().enumerate() {
            let w = col_widths_twips.get(c).copied().unwrap_or(0);
            let node = super::cell::find_node(root, running, &g.id);
            cells.push(build_cell(g, w, paints.get(&g.id), node, header, fonts)?);
        }
        rows_out.push(TableRow {
            height_twips: heights.get(r).copied().unwrap_or(0),
            cells,
        });
    }
    Ok(Some(TableBox {
        node_id: table_id.to_string(),
        x_emu: pt_to_emu(geo.x),
        y_emu: pt_to_emu(geo.y),
        cx_emu: pt_to_emu(geo.width),
        cy_emu: pt_to_emu(geo.height),
        width_twips: pt_to_twips(geo.width),
        col_widths_twips,
        rows: rows_out,
        relative_height,
        fill_hex: None,
    }))
}

pub(crate) fn table_ref_placeholder(node_id: &str, rect: &Rect, relative_height: u32) -> ShapeBox {
    ShapeBox {
        node_id: node_id.to_string(),
        x_emu: pt_to_emu(rect.x),
        y_emu: pt_to_emu(rect.y),
        cx_emu: pt_to_emu(rect.width),
        cy_emu: pt_to_emu(rect.height),
        fill_hex: None,
        fill_alpha: 255,
        gradient: None,
        corner_emu: 0,
        line_hex: Some("808080".into()),
        line_w_emu: millipt_to_emu(1_000),
        line_dash: LineDash::Solid,
        behind_doc: false,
        relative_height,
    }
}

fn col_widths_from_row(row: &[GeometryNode], table: &GeometryNode) -> Vec<i64> {
    let n = row.len();
    (0..n)
        .map(|i| {
            let millipt = if i + 1 < n {
                row[i + 1].x.0 - row[i].x.0
            } else {
                (table.x.0 + table.width.0) - row[i].x.0
            };
            millipt_to_twips(i64::try_from(millipt.max(0)).unwrap_or(0))
        })
        .collect()
}

fn row_heights_twips(rows: &[&[GeometryNode]], table: &GeometryNode) -> Vec<i64> {
    rows.iter()
        .enumerate()
        .map(|(i, row)| {
            let y = row.first().map(|c| c.y.0).unwrap_or(table.y.0);
            let next_y = rows
                .get(i + 1)
                .and_then(|r| r.first())
                .map(|c| c.y.0)
                .unwrap_or(table.y.0 + table.height.0);
            let from_gap = next_y - y;
            let from_cell = row.iter().map(|c| c.height.0).max().unwrap_or(0);
            millipt_to_twips(i64::try_from(from_gap.max(from_cell).max(0)).unwrap_or(0))
        })
        .collect()
}