Skip to content

feat(docpdf): draw DOT and PlantUML diagrams in PDF output and export every form from VS Code - #356

Open
devin-ai-integration[bot] wants to merge 9 commits into
developfrom
feature/dot-plantuml-pdf-and-export
Open

devin-ai-integration[bot] wants to merge 9 commits into
developfrom
feature/dot-plantuml-pdf-and-export

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

What and why

PDF output draws DOT and PlantUML blocks

-render-document -doc-form pdf -diagram-form dot|plantuml kept every DOT and PlantUML block as source under a notice; only Mermaid was drawn. The PDF backend now draws all three forms, each through an external tool located the way mmdc already is:

  • DOT through Graphviz dot, located by OPENSYSML_DOT or PATH. The layout engine is the one the block's // layout: header names (dot, neato, neato -n, neato -n2), so the positioned layouts the DOT writer emits render where they were placed.
  • PlantUML through the PlantUML jar (java -jar <jar> -tsvg -pipe, source on stdin, SVG on stdout), the jar located by OPENSYSML_PLANTUML_JAR, java by OPENSYSML_JAVA or PATH. When Graphviz was located, PlantUML is pointed at the same dot through GRAPHVIZ_DOT.
  • Both tools are optional: without one, its blocks stay as source under the notice, which now names the tool and the variable to set. A tool that runs and fails is the typed ErrorToolFailed mmdc failures already produce, carrying the tool's stderr. A tool that exits 0 without writing an SVG is the same typed failure. Mermaid stays required.
  • The Markdown and HTML document forms are unchanged.

SVG, not PDF, for Graphviz. Measured with WeasyPrint 69.0: an <img> referencing dot -Tpdf output is refused (ERROR: Failed to load image at 'file:///…/g.pdf', the figure is left blank), while -Tsvg output embeds with its text and edges intact. PlantUML's -tsvg is used for the same reason.

Structure. The rasterization lives in docpdf's tool layer: converter.go gains the tool table entries and a toolRun (the subprocess mechanics runTool already had, plus stdin/stdout/env), graphviz.go and plantuml.go sit beside mermaid.go behind one small rasterizer interface, and diagrams.go produces one diagram{Image | Notice} per diagram block in document order. html.go and markdown.go's only change is the one call site that dispatches on the block's form, so the HTML-renderer migration of the PDF backend deletes them cleanly.

Toolchain and CI. scripts/download-doc-pdf-toolchain.sh now also pins Graphviz 16.1.0 (the upstream Ubuntu 22.04/24.04/26.04 .deb bundle, unpacked under build/doc-pdf/graphviz with a wrapper that points it at its own libraries and plugin table; neato links libgts-0.7.so.5, which the script checks for and the CI job installs) and PlantUML 1.2026.8, both by SHA-256, and prints OPENSYSML_DOT/OPENSYSML_PLANTUML_JAR with the others. No job ran that script in CI before, so the installed-tool tests only ever skipped there; a new pdf-toolchain job provisions the toolchain (cached on the script's digest) and runs go test -run Installed ./internal/docpdf with OPENSYSML_REQUIRE_PDF_TOOLCHAIN=1, under which an absent provisioned tool fails instead of skipping (Prince, commercial and never provisioned, still skips).

The VS Code panel exports every form

SysML: Export Diagram rendered through opensysml/render without a form, so it always saved the server's default (Mermaid). It now:

  • asks which form to save through a quick pick, listing the forms the server advertises in initialize under capabilities.experimental.openSysmlRenderForms (text, mermaid, markdown, dot, plantuml), falling back to that documented list against an older server;
  • sends the pick as form on opensysml/render;
  • opens the save dialog on the form the server answered with — .mmd Mermaid, .dot Graphviz DOT, .puml PlantUML, .md Markdown, .txt Text — each with its own filter, and writes the artifact.

One command, as before (the pick is inside it), so the title-bar and context-menu entries in package.json are untouched. The pick → request → save sequence is editors/vscode/src/export.ts, written against a small ExportHost so it is unit-testable; diagram.ts supplies the VS Code implementation.

The LSP side is RenderFormsCapability in internal/lsp/render.go, advertised from view.Forms() so the list cannot drift from what the server writes.

A WrongFormError for a rendering of no named view (the panel's whole-document tree) used to open with : ; it now opens with the kind.

Documentation

docs/project/view-rendering-forms.md, docs/manual/outputs.md, docs/reference/cli.md, docs/reference/environment.md (the ten PDF tool variables and OPENSYSML_REQUIRE_PDF_TOOLCHAIN), docs/reference/lsp.md (openSysmlRenderForms), docs/guide/08-editors.md (Exporting a diagram), editors/vscode/README.md, .agents/skills/testing-doc-pdf/SKILL.md, and docs/project/spec-compliance.md's DOT, PlantUML and PDF rows.

Specification basis

Not a language-semantics change. In docs/project/spec-compliance.md the PDF document-rendering row moves from "DOT and PlantUML blocks are kept as source" to drawn-through-optional-tools, and the DOT/PlantUML form rows say where Graphviz and the jar now run; the Known Limitation that PDF output draws Mermaid only is replaced by the optional-tool statement. Known limitation kept: Java is the one tool download-doc-pdf-toolchain.sh does not provision (the CI runner's JDK is used).

How it was verified

$ go build ./... && go vet ./... && gofmt -l .        # clean, nothing printed
$ go test ./...                                       # all ok
$ make lint                                           # staticcheck + gosec clean
$ make docs-check && python3 scripts/changelog.py check && mkdocs build --strict   # clean
$ OPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1 \
  go test -count=1 ./internal/core/model -run 'TestTrainingExamples|TestPilotCorpora'
ok  	github.com/Open-MBEE/OpenSysML/internal/core/model	0.989s
$ (cd editors/vscode && npm run typecheck && npm test)   # tsc clean; 168 tests pass

PDF, with the toolchain provisioned by the extended script

OPENSYSML_REQUIRE_PDF_TOOLCHAIN=1, so a skip would have failed:

$ go test -count=1 -v -run Installed ./internal/docpdf
--- PASS: TestRenderWithInstalledEngines (1.02s)          (weasyprint, pandoc pass; prince skips: not provisioned)
--- PASS: TestRenderInlineRunsWithInstalledEngines (1.16s)
--- PASS: TestRenderFormulasWithInstalledKatex (2.63s)
--- PASS: TestRenderDiagramsWithInstalledMermaid (1.07s)
--- PASS: TestRenderDiagramsWithInstalledGraphviz (0.54s)
--- PASS: TestRenderDiagramsWithInstalledPlantUML (1.72s)
PASS
ok  	github.com/Open-MBEE/OpenSysML/internal/docpdf	8.155s

TestRenderDiagramsWithInstalledGraphviz renders a // layout: neato -n block and checks the SVG keeps the stated positions (Tank right of Pump); TestRenderDiagramsWithInstalledPlantUML renders a diagram through the jar and checks a block the jar rejects is ErrorToolFailed carrying its Syntax Error message.

Without the tools (PATH holding only Go, no OPENSYSML_*), the same three tests skip with the reason:

--- SKIP: TestRenderDiagramsWithInstalledMermaid    mmdc not installed: … point OPENSYSML_MMDC at it
--- SKIP: TestRenderDiagramsWithInstalledGraphviz   weasyprint not installed: … point OPENSYSML_WEASYPRINT at it …
--- SKIP: TestRenderDiagramsWithInstalledPlantUML   the PlantUML jar not installed: … point OPENSYSML_PLANTUML_JAR at it

The worked example (docs/manual/examples/observatory.sysml, Observatory::MassReport) rendered to PDF under each -diagram-form, checked with pypdf: under dot and plantuml the PDF carries no notice and the two figures' labels («part», camera : Camera, …) are vector text, as the Mermaid ones are; with OPENSYSML_DOT=/nonexistent the render still succeeds and the text carries the notice naming OPENSYSML_DOT above the digraph source; with OPENSYSML_PLANTUML_JAR=/nonexistent.jar, the notice names OPENSYSML_PLANTUML_JAR above the @startuml source.

Unit tests with fake tools (no toolchain needed), internal/docpdf/diagrams_test.go and cmd/sysml/render_document_pdf_test.go: missing Graphviz / jar / java keeping the source under a notice naming the variable; a fake dot and a fake java drawing SVGs; -Tsvg and the layout engine picked per header (dot, neato, neato -n, neato -n2, unknown header); pipe-mode arguments and source on stdin; Graphviz, java and PlantUML failures carrying stderr; a tool that exits 0 without an SVG; block ordering of a mixed Mermaid/DOT/PlantUML document; Mermaid still required; both -diagram-form values end to end through the CLI.

LSP

internal/lsp/render_test.go:TestRenderAnswersEveryAdvertisedForm reads openSysmlRenderForms from initialize (exactly text, mermaid, markdown, dot, plantuml) and renders a view in each, checking the answer's form and a non-empty artifact.

VS Code

editors/vscode/src/export.test.ts: forms from the capability, the documented fallback, de-duplication; the .mmd/.dot/.puml/.md/.txt mapping and matching filters; picker labels; the request carrying form; the saved artifact; cancel at the pick, cancel at the save dialog; a server refusal; the server answering another form than asked.

Driven in a real VS Code (1.137.0) with the packaged extension and a freshly built sysml-lsp:

  1. make build-lsp && make vscode-package; installed the .vsix into an isolated --user-data-dir/--extensions-dir profile with opensysml.server.path at bin/sysml-lsp and opensysml.trace.server: verbose; opened a workspace holding a vehicle.sysml (part def Vehicle { part engine; part wheels; } part car : Vehicle;).
  2. Opened the file, Alt+D: the panel drew Vehicle with engine and wheels, and car.
  3. Command palette → SysML: Export Diagram: the quick pick listed exactly text, mermaid, markdown, dot, plantuml, each with its extension and description.
  4. Picked dot: the save dialog defaulted to vehicle.dot in the workspace with the Graphviz DOT filter; saved; the file opens with // layout: dot and digraph, naming Vehicle, engine, wheels and car.
  5. Repeated for plantuml (vehicle.puml, PlantUML filter, file starts @startuml), mermaid (vehicle.mmd, flowchart TD) and text (vehicle.txt, one element per line).
  6. In the SysML v2 output channel the verbose trace shows Sending request 'opensysml/render - (11)' with "view": "#tree" and "form": "dot", the initialize response advertising the five forms, and one Exported <form> of vehicle.sysml to <path> line per save; each saved file's bytes equal the artifact in the response.
  7. Opened the pick and pressed Esc: no dialog, no error, workspace file hashes and the render-request count unchanged.
  8. Picked markdown for the tree: no dialog, the error notification Rendering vehicle.sysml failed: a tree rendering is not written as markdown; ask for text, mermaid, dot or plantuml, no vehicle.md written. (The run that found the doubled colon predates the WrongFormError fix in this PR.)

Not exercised by hand: a table view exported as Markdown, and the title-bar / context-menu entry points (they run the same command).

Checklist

  • make test and make lint pass locally
  • Tests added or updated for the change
  • Documentation extended where it already covers the surface (see CONTRIBUTING.md)
  • Changelog entry added as changes/unreleased/<slug>.<section>.md, not as an edit to CHANGELOG.md
  • baselines regenerated and make docs-counts run if a gate count moved (compliance rows need nothing: the census is counted at docs build)
  • No internal work-item labels (waves, slices, F4, K5) in the body, docs, or changelog

DOT blocks are drawn through Graphviz (OPENSYSML_DOT, or dot on PATH),
with the layout engine the block's header names, and PlantUML blocks
through the PlantUML jar (OPENSYSML_PLANTUML_JAR) run by java
(OPENSYSML_JAVA, or java on PATH) in pipe mode. Both tools are optional:
without one, its blocks stay as source under a notice naming the
variable to set; a tool that fails is the typed error mmdc failures
already produce, with its stderr.

The toolchain script pins Graphviz and the PlantUML jar by digest, and
a CI job runs the PDF integration tests against the pinned toolchain,
with OPENSYSML_REQUIRE_PDF_TOOLCHAIN turning an absent tool into a
failure.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

devin-ai-integration Bot and others added 5 commits September 17, 2026 03:50
…ts provision

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…dot and PlantUML as .puml

The server lists the forms opensysml/render writes under the experimental
capability openSysmlRenderForms; the extension offers those (or the documented
five) in a quick pick and sends the choice as form on the request.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
… their variables and the export form picker

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration
devin-ai-integration Bot marked this pull request as ready for review September 17, 2026 04:46
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits September 17, 2026 04:50
Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ml-pdf-and-export

Co-Authored-By: jason.han <hanhuijun@gmail.com>

# Conflicts:
#	docs/reference/environment.md
devin-ai-integration[bot]

This comment was marked as resolved.

…ing it in the render directory

Co-Authored-By: jason.han <hanhuijun@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant