* Comments: a text box's rectangle follows its edited text Editing a text box (or typewriter or callout) rewrote /Contents but kept the old /Rect: the appearance clipped the new text to the old box and silently dropped lines that no longer fit, while shorter text left an oversized box. set_contents now re-measures the text with the same Helvetica widths as at creation and re-fits the box: the wrap width and top edge stay, the height fits the wrapped lines. A callout's leader line keeps its place and re-attaches to the moved box (that geometry moved from set_rect into a shared apply_text_box). A locked box keeps its text editable, so it re-fits too. Covered by annot tests (grow and shrink with nothing clipped, callout leader re-attach, locked box) and a comment_edit round trip through the automation tools; parity entries cite them. Co-Authored-By: GLM <noreply@z.ai> * Polish edit text and images tool * feat(edit-text): select source font style * fix(edit-text): preserve detected PDF font styles * feat(edit-text): support Japanese replacements * fix(forms): escape non-ASCII format script strings * Forms: unwrap_or instead of map_or with identity (clippy map_or_identity) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: GLM <noreply@z.ai> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
printcraft-automation
Agent control for PrintCraft: a headless tool table over the engine, and an opt-in MCP server.
- Layer: L7 (architecture §13), but headless. It depends on
engine,renderandorganize, never on a UI toolkit. - Status: 38 tools covering inspection, rendering, text, page edits, page labels, bookmarks, comments, form filling, password protection, metadata, undo/redo, save, combine, extract and split. Comment tools take geometry in the same top-left-origin points as everything else, and
comment_addcan mark text by searching for it (find). Planned next: form authoring and redaction tools as those features land, and MCP resources for page images. The running app's UI is driven separately, through theui.*control channel inprintcraft-ui-egui.
API
let mut a = Automation::new().with_root("/work")?; // optional: confine all paths
let doc = a.call("doc_open", &json!({ "path": "in.pdf" }))?; // Vec<Content>
tools() -> Vec<ToolDef> // name, title, description, input_schema, read_only, destructive, command
mcp::McpServer::new(a).serve(stdin, stdout)? // newline-delimited JSON-RPC 2.0
Content is Json(Value) or Png { data, width, height }. Errors are UnknownTool, InvalidArgs (the call didn't match the schema) or Failed (a readable message for the agent).
Conventions
- Tool names are
snake_case(page_rotate): MCP clients reject dots.ToolDef::commandlinks a tool to the registry id it automates (page.rotate), andcommand_listreports the link. - Pages and positions are 1-based. Rectangles are PDF points with the origin at the top-left of the displayed page.
- Unknown arguments are rejected, so typos fail loudly.
- Tools that change a document return its summary (
doc,pages,dirty,undo,redo, …). doc_closerefuses to drop unsaved changes unlessdiscard_changes: true.doc_savewrites atomically, incrementally in place, and in full for a new path.- With a root set, every read and write path must resolve inside it (symlinks and
..included).
MCP server
Opt-in only. Nothing starts it automatically, and it opens no port. It runs while printcraft-cli mcp [--root DIR] runs, normally launched by an agent from its MCP configuration. It stops when stdin closes. The CLI's mcp Cargo feature (on by default) compiles it out entirely when disabled.
Implemented: initialize (protocol 2025-06-18, 2025-03-26, 2024-11-05), ping, tools/list (with readOnlyHint/destructiveHint annotations) and tools/call (JSON results also returned as structuredContent; images as image/png). Tool failures come back as isError: true results, so the agent can read and recover from them.
Adding a tool
- Add the engine capability first, with its tests (the tool is a thin adapter).
- Add a
ToolDefinsrc/tools.rs: a precise description, the schema,ro()/destructive(), andcmd()if a registry command exists. - Handle it in
Automation::call, validating pages and positions with the existing helpers. - Add an end-to-end test in
tests/automation.rs.tool_table_is_well_formedchecks names, schemas and command links.