// Shared fresh-DOCX authoring: turns a parsed docx.batch/2 script plus its
// preloaded image bytes into a written package, centralizing the batch ->
// writer mapping (body build + comment/note spec construction). The office
// fresh-authoring surface (office/docx) uses this; the standalone `docx batch`
// CLI still carries its own inline copy (adopting this is a tracked follow-up,
// deferred to avoid churning its separate cram/acceptance suite). Serialization
// is bounded by the optional `limits`, so an over-budget package is refused as
// a typed WriteResourceLimit before the archive payloads are materialized.
///|
/// Maximum distinct media parts a fresh-authoring script may emit (one per
/// image OCCURRENCE, not per unique asset), and the aggregate emitted-media
/// byte ceiling summed over occurrences. Enforced BEFORE the writer graph is
/// built, so a script that references one asset thousands of times is refused
/// as a typed media limit rather than after materializing every part.
pub const MAX_AUTHORED_MEDIA_PARTS : Int = 1000
///|
/// Maximum aggregate emitted-media bytes, summed over image occurrences rather
/// than unique assets.
pub const MAX_AUTHORED_MEDIA_BYTES : Int64 = 64L * 1024L * 1024L
///|
/// Builds a fresh DOCX from an already-parsed `docx.batch/2` script and the
/// image bytes its `image_paths()` referenced. Body/comment/note construction
/// errors surface as `Unsupported` carrying the script op address; a bounded
/// serialization or emitted-media ceiling breach surfaces as
/// `WriteResourceLimit`. Nothing is written — the caller publishes the returned
/// bytes through its own transaction/atomic-write boundary.
pub fn author_docx_bytes(
script : @batch.BatchScript,
images : Map[String, Bytes],
limits? : @opc.PackageLimits,
) -> Bytes raise DocxError {
// Emitted-media preflight, BEFORE build_body materializes per-occurrence
// document nodes / relationships / media metadata.
let occurrences = script.image_occurrences()
if occurrences.length() > MAX_AUTHORED_MEDIA_PARTS {
raise WriteResourceLimit(
kind="media_parts",
limit=MAX_AUTHORED_MEDIA_PARTS.to_int64(),
actual=occurrences.length().to_int64(),
message="the authoring script emits \{occurrences.length()} media parts; the limit is \{MAX_AUTHORED_MEDIA_PARTS}",
)
}
let mut media_bytes = 0L
for path in occurrences {
media_bytes = media_bytes +
(match images.get(path) {
Some(bytes) => bytes.length()
None => 0
}).to_int64()
if media_bytes > MAX_AUTHORED_MEDIA_BYTES {
raise WriteResourceLimit(
kind="media_bytes",
limit=MAX_AUTHORED_MEDIA_BYTES,
actual=media_bytes,
message="the authoring script's emitted media exceeds the \{MAX_AUTHORED_MEDIA_BYTES}-byte ceiling",
)
}
}
let body = script.build_body(images) catch {
BatchError(message) => raise Unsupported(message~)
}
let comments = []
let batch_comments = script.build_comments() catch {
BatchError(message) => raise Unsupported(message~)
}
for comment in batch_comments {
let spec = match
(comment.reply_to(), comment.from_block(), comment.to_block()) {
(Some(parent), _, _) =>
comment_reply(
author=comment.author(),
initials?=comment.initials(),
date?=comment.date(),
reply_to=parent,
done?=comment.done(),
comment.body(),
) catch {
err =>
raise Unsupported(message="\{comment.at()}: \{err.description()}")
}
(None, Some(from), Some(to)) =>
comment_spec(
author=comment.author(),
initials?=comment.initials(),
date?=comment.date(),
from~,
to~,
done?=comment.done(),
comment.body(),
) catch {
err =>
raise Unsupported(message="\{comment.at()}: \{err.description()}")
}
_ =>
raise Unsupported(
message="\{comment.at()}: comment has neither an anchor nor a reply target",
)
}
comments.push(spec)
}
let footnotes = []
for
note in (script.build_footnotes() catch {
BatchError(message) => raise Unsupported(message~)
}) {
footnotes.push(
note_spec(note.body()) catch {
err => raise Unsupported(message="\{note.at()}: \{err.description()}")
},
)
}
let endnotes = []
for
note in (script.build_endnotes() catch {
BatchError(message) => raise Unsupported(message~)
}) {
endnotes.push(
note_spec(note.body()) catch {
err => raise Unsupported(message="\{note.at()}: \{err.description()}")
},
)
}
write_docx_with_annotations(body, comments~, footnotes~, endnotes~, limits?)
}