k2f_docx 0.2.2

K2F lock → DOCX exporter
Documentation
use crate::ir::{DocIR, PageElement, PageIR};
use crate::xml::word_hex_color;
use std::collections::BTreeMap;

use super::drawing::{
    drawing_run, element_has_bg_shape, page_bg_anchor, picture_anchor, raster_anchor, shape_anchor,
    textbox_anchor,
};

const NS: &str = r#"xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
            xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
            xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
            xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
            xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"
            xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
            xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            mc:Ignorable="w14""#;

pub fn document_xml(
    ir: &DocIR,
    hyperlink_rids: &BTreeMap<String, String>,
    picture_rids: &BTreeMap<String, String>,
    has_header: bool,
    has_footer: bool,
) -> String {
    let mut body = String::new();
    let mut doc_pr_id = 1u32;
    let n = ir.pages.len();
    for (i, page) in ir.pages.iter().enumerate() {
        body.push_str(&page_paragraph(
            page,
            ir.page_width_emu,
            ir.page_height_emu,
            &mut doc_pr_id,
            hyperlink_rids,
            picture_rids,
        ));
        if i + 1 < n {
            body.push_str(
                r#"    <w:p>
      <w:r>
        <w:br w:type="page"/>
      </w:r>
    </w:p>
"#,
            );
        }
    }
    let mut sect = String::new();
    if has_header {
        sect.push_str(r#"      <w:headerReference w:type="default" r:id="rIdH1"/>"#);
        sect.push('\n');
    }
    if has_footer {
        sect.push_str(r#"      <w:footerReference w:type="default" r:id="rIdF1"/>"#);
        sect.push('\n');
    }
    format!(
        r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document {NS}>
  <w:background w:color="{bg}"/>
  <w:body>
{body}    <w:sectPr>
{sect}      <w:pgSz w:w="{w}" w:h="{h}"{orient}/>
      <w:pgMar w:top="0" w:right="0" w:bottom="0" w:left="0"
               w:header="0" w:footer="0" w:gutter="0"/>
    </w:sectPr>
  </w:body>
</w:document>
"#,
        w = ir.page_width_twips,
        orient = if ir.page_width_twips > ir.page_height_twips {
            r#" w:orient="landscape""#
        } else {
            ""
        },
        h = ir.page_height_twips,
        bg = word_hex_color(
            ir.pages
                .first()
                .map(|p| p.bg_hex.as_str())
                .unwrap_or("FFFFFF")
        ),
    )
}

fn page_paragraph(
    page: &PageIR,
    cx: i64,
    cy: i64,
    doc_pr_id: &mut u32,
    hyperlink_rids: &BTreeMap<String, String>,
    picture_rids: &BTreeMap<String, String>,
) -> String {
    let mut runs = String::new();
    if !page.elements.iter().any(element_has_bg_shape) {
        runs.push_str(&drawing_run(&page_bg_anchor(
            &page.bg_hex,
            cx,
            cy,
            *doc_pr_id,
        )));
        *doc_pr_id += 1;
    }
    for el in &page.elements {
        runs.push_str(&drawing_run(&element_anchor(
            el,
            *doc_pr_id,
            hyperlink_rids,
            picture_rids,
        )));
        *doc_pr_id += 1;
    }
    format!(
        r#"    <w:p>
{runs}    </w:p>
"#
    )
}

pub fn hdrftr_xml(
    tag: &str,
    elements: &[PageElement],
    hyperlink_rids: &BTreeMap<String, String>,
    picture_rids: &BTreeMap<String, String>,
) -> String {
    let mut runs = String::new();
    let mut doc_pr_id = 1u32;
    for el in elements {
        runs.push_str(&drawing_run(&element_anchor(
            el,
            doc_pr_id,
            hyperlink_rids,
            picture_rids,
        )));
        doc_pr_id += 1;
    }
    format!(
        r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:{tag} {NS}>
  <w:p>
{runs}  </w:p>
</w:{tag}>
"#
    )
}

fn element_anchor(
    el: &PageElement,
    doc_pr_id: u32,
    hyperlink_rids: &BTreeMap<String, String>,
    picture_rids: &BTreeMap<String, String>,
) -> String {
    match el {
        PageElement::TextBox(tb) => textbox_anchor(tb, doc_pr_id, hyperlink_rids),
        PageElement::Shape(s) => shape_anchor(s, doc_pr_id),
        PageElement::Picture(p) => {
            let rid = picture_rids
                .get(&p.media_name)
                .map(String::as_str)
                .unwrap_or("");
            picture_anchor(p, doc_pr_id, rid)
        }
        PageElement::Raster(p) => {
            let rid = picture_rids
                .get(&p.media_name)
                .map(String::as_str)
                .unwrap_or("");
            raster_anchor(p, doc_pr_id, rid)
        }
        PageElement::Table(t) => crate::table::table_anchor(t, doc_pr_id, hyperlink_rids),
    }
}