use quillmark_core::{Diagnostic, RenderError, Severity};
use quillmark_pdf::{FieldSpec, FieldType, CHECKBOX_ON_STATE};
use typst_layout::PagedDocument;
mod extract;
mod span_scan;
pub(crate) use span_scan::FieldWindow;
pub(crate) fn scan_content_regions(
doc: &PagedDocument,
world: &crate::world::QuillWorld,
helper: &typst::syntax::Source,
windows: &[FieldWindow],
) -> Vec<quillmark_core::RenderedRegion> {
span_scan::scan(doc, world, helper, windows)
}
pub(crate) fn field_at(
doc: &PagedDocument,
world: &crate::world::QuillWorld,
helper: &typst::syntax::Source,
windows: &[FieldWindow],
page: usize,
x: f32,
y: f32,
) -> Option<String> {
span_scan::field_at(doc, world, helper, windows, page, x, y)
}
pub(crate) fn position_at(
doc: &PagedDocument,
world: &crate::world::QuillWorld,
helper: &typst::syntax::Source,
windows: &[FieldWindow],
page: usize,
x: f32,
y: f32,
) -> Option<quillmark_core::ContentHit> {
span_scan::position_at(doc, world, helper, windows, page, x, y)
}
pub(crate) fn locate(
doc: &PagedDocument,
world: &crate::world::QuillWorld,
helper: &typst::syntax::Source,
windows: &[FieldWindow],
field: &str,
pos: usize,
) -> Option<quillmark_core::RenderedRegion> {
span_scan::locate(doc, world, helper, windows, field, pos)
}
pub(crate) fn scalar_windows(
source: &typst::syntax::Source,
fields: &[String],
) -> Vec<(String, std::ops::Range<usize>)> {
span_scan::scalar_windows(source, fields)
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum FieldKind {
Text {
multiline: bool,
value: Option<String>,
},
Checkbox { checked: bool },
Choice {
options: Vec<String>,
value: Option<String>,
},
Signature,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct FieldPlacement {
pub name: String,
pub schema_field: Option<String>,
pub page: usize,
pub rect_typst_pt: [f32; 4],
pub kind: FieldKind,
}
pub(crate) fn err(code: &'static str, msg: impl Into<String>) -> RenderError {
RenderError::from_diag(Diagnostic::new(Severity::Error, msg.into()).with_code(code.into()))
}
pub(crate) fn default_producer() -> String {
format!("Quillmark {}", env!("CARGO_PKG_VERSION"))
}
pub(crate) fn extract(doc: &PagedDocument) -> Result<Vec<FieldPlacement>, RenderError> {
extract::extract(doc)
}
pub(crate) fn build_field_specs(
doc: &PagedDocument,
placements: &[FieldPlacement],
) -> Result<Vec<FieldSpec>, RenderError> {
let page_heights: Vec<f32> = doc
.pages()
.iter()
.map(|p| p.frame.size().y.to_pt() as f32)
.collect();
placements
.iter()
.map(|p| {
let page_h = *page_heights.get(p.page).ok_or_else(|| {
err(
"typst::form_field_page_out_of_range",
format!(
"form-field {:?} targets page {} but the document has {} page(s)",
p.name,
p.page,
page_heights.len()
),
)
})?;
let [x0, y0, x1, y1] = p.rect_typst_pt;
let (field_type, value) = match &p.kind {
FieldKind::Text { multiline, value } => (
FieldType::Text {
multiline: *multiline,
},
value.clone(),
),
FieldKind::Checkbox { checked } => (
FieldType::Checkbox,
checked.then(|| CHECKBOX_ON_STATE.to_string()),
),
FieldKind::Choice { options, value } => {
let bound = value
.as_ref()
.filter(|v| options.iter().any(|o| o == *v))
.cloned();
(
FieldType::Choice {
options: options.clone(),
},
bound,
)
}
FieldKind::Signature => (FieldType::Signature, None),
};
Ok(FieldSpec {
name: p.name.clone(),
schema_field: p.schema_field.clone(),
page: p.page,
rect: [x0, page_h - y1, x1, page_h - y0],
field_type,
value,
tooltip: None,
})
})
.collect()
}