use super::{
authored::{refs, typed, Author},
pdf_fill_types::PdfFillAnnotationRequest,
};
use crate::pdf_vector::{FidelityReport, PreparedPdfVectorPage};
use ifc_lite_core::{AttributeValue as A, IfcType};
use serde::Serialize;
pub const PROPERTY_SET_NAME: &str = "IfcLite_PdfVectorConversion";
const MAX_OMISSIONS_TEXT: usize = 4000;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct OmissionEntry<'a> {
kind: &'a str,
count: u32,
visible: u32,
}
pub(super) fn omissions_text(fidelity: &FidelityReport) -> String {
let mut entries: Vec<OmissionEntry<'_>> = Vec::new();
let mut rendered = String::from("[]");
for (index, entry) in fidelity.summary.iter().enumerate() {
entries.push(OmissionEntry {
kind: &entry.kind,
count: entry.count,
visible: entry.visible_count,
});
let candidate = serde_json::to_string(&entries).unwrap_or_default();
if candidate.len() > MAX_OMISSIONS_TEXT - 64 {
entries.pop();
let rest: u32 = fidelity.summary[index..].iter().map(|s| s.count).sum();
entries.push(OmissionEntry {
kind: "...",
count: rest,
visible: fidelity.summary[index..].iter().map(|s| s.visible_count).sum(),
});
return serde_json::to_string(&entries).unwrap_or_default();
}
rendered = candidate;
}
rendered
}
fn json_numbers(values: &[f64]) -> String {
serde_json::to_string(values).unwrap_or_default()
}
pub(super) struct Provenance<'a> {
pub request: &'a PdfFillAnnotationRequest,
pub prepared: &'a PreparedPdfVectorPage,
pub grid_size_metres: f64,
pub fill_regions: usize,
}
impl Provenance<'_> {
fn properties(&self) -> Vec<(&'static str, A)> {
let page = &self.request.page;
let fidelity = &self.prepared.fidelity;
let text = |s: String| typed("IfcText", A::String(s));
let label = |s: &str| typed("IfcLabel", A::String(s.into()));
let identifier = |s: &str| typed("IfcIdentifier", A::String(s.into()));
let integer = |n: i64| typed("IfcInteger", A::Integer(n));
let real = |x: f64| typed("IfcReal", A::Float(x));
let boolean = |b: bool| typed("IfcBoolean", A::Enum(if b { "T" } else { "F" }.into()));
vec![
("SourcePdfSha256", identifier(&page.pdf_sha256)),
(
"SourcePdfFormatVersion",
label(page.pdf_format_version.as_deref().unwrap_or("not reported")),
),
("SourcePageNumber", integer(i64::from(page.page_number))),
("SourceCropBox", text(json_numbers(&page.view_box))),
("ConversionClipPdf", text(json_numbers(&self.prepared.page_clip_pdf))),
("SourceUserUnit", real(page.user_unit)),
("SourceRotation", integer(i64::from(page.intrinsic_rotation))),
("DecoderVersion", label(&format!("PDF.js {}", page.decoder_version))),
("CalibrationKey", text(page.calibration_key.clone())),
("ModelMetresFromPdf", text(json_numbers(&page.model_metres_from_pdf))),
("ToleranceMetres", real(page.tolerance_metres)),
("GridSizeMetres", real(self.grid_size_metres)),
("Algorithm", label(super::pdf_fill::ALGORITHM)),
("RequestSha256", identifier(&self.prepared.request_sha256)),
("FidelityAlgorithm", label(fidelity.algorithm)),
("FidelitySha256", identifier(&fidelity.sha256)),
("ExactConversion", boolean(fidelity.exact)),
("AcceptedPartialConversion", boolean(!fidelity.exact)),
("ConvertedPaths", integer(i64::from(fidelity.convertible_paths))),
("FillRegions", integer(self.fill_regions as i64)),
("OmittedPaints", integer(i64::from(fidelity.omitted_paints))),
("Omissions", text(omissions_text(fidelity))),
]
}
pub fn reserve(&self) -> usize {
self.properties().len() + 2
}
pub fn author(&self, author: &mut Author, owner: &A, annotation: u32) -> Result<u32, String> {
let mut values = Vec::new();
for (name, value) in self.properties() {
super::wire_text::validate(name, "PDF provenance property name")?;
values.push(author.add(
IfcType::IfcPropertySingleValue,
vec![A::String(name.into()), A::Null, value, A::Null],
));
}
let set = author.add(
IfcType::IfcPropertySet,
vec![
A::String(self.request.property_set_global_id.clone()),
owner.clone(),
A::String(PROPERTY_SET_NAME.into()),
A::String(format!("PDF vector conversion provenance ({})", self.prepared.fidelity.describe())),
refs(&values),
],
);
author.add(
IfcType::IfcRelDefinesByProperties,
vec![
A::String(self.request.property_relation_global_id.clone()),
owner.clone(),
A::Null,
A::Null,
refs(&[annotation]),
A::EntityRef(set),
],
);
Ok(set)
}
}