CapitalKnowledge

How Capital Publisher Works

9. Fonts in Service Explorer Diagrams

Status: tested 2026-09-28 on Capital 2512.2608. Every statement below came from a real publish, the Service Explorer server that ships inside the package, and measurements in Edge, unless it is marked not tested. The tool is tools/embed_diagram_font.py in the private repository capital-publisher-config; the full record and evidence images are in its handoffs/2026-09-28-iewd-font-embedding.md.

The problem

A Service Explorer diagram names its font by family only. Capital writes a CSS class inside each diagram SVG, for example .I,.J{font-family:Lucida Sans Unicode;...}, with no fallback list and no @font-face. The viewer runtime ships no font files. So a PC without that font installed renders the diagram in some other face, and right- or centre-justified text no longer lines up.

Nothing in Publisher's settings embeds a font for Service Explorer. The only documented font embedding is PDF-only (fontFilePath in pdf\pdfConfiguration.xml, vendor page ee-publisher/pdf-packager).

Why the viewer's custom.css cannot fix it

Custom Runtime\styles\custom.css is loaded by the viewer page, but each diagram is loaded in an <object> element as its own SVG document. Page CSS does not cross into it, so custom.css cannot change diagram text. The viewer has no supported hook for custom script either: index.html loads a fixed list of scripts, and the Custom Runtime\scripts folder is empty and referenced by nothing.

What works: ship the font inside the package

The Service Explorer server serves every file inside a package's contents.zip, under /zipped/data/<package>/<entry>, and a relative URL inside a diagram resolves to another entry of the same zip. (The viewer's own introduction page loads its images this way.) So a font stored in contents.zip and declared with @font-face in each diagram reaches every browser, with nothing installed on the viewing PC.

embed_diagram_font.py does exactly that, after publishing:

python tools/embed_diagram_font.py <package folder> --family "Monospac821 BT" --font <licensed .ttf>

It converts the font to WOFF2, stores it once per package at fonts/<family>.woff2, adds the @font-face to every diagram SVG whose CSS uses that family, backs up the original zip, and reads the result back before reporting. Exit code 0 means done, 1 means no diagram uses that family (nothing written; it lists the families it found), 2 means it could not run.

Measured result (font uninstalled from the test PC, text widths of "iiiiiiiiii" and "MMMMMMMMMM" in the diagram's own text class):

State i M Font used
Monospace 821 installed, no fix 8739 8739 installed font
Font uninstalled, no fix 4033 12906 fallback
Font uninstalled, script applied 8739 8739 embedded font

The embedded font renders with exactly the widths of the installed one.

Rules for using it

  • Run it after every publish. A re-publish writes a new contents.zip and undoes it. A Capital update does not, because no Siemens runtime file is changed.
  • Use the family name stored in the font file, not the marketing name. Monospace 821 BT's family name is Monospac821 BT (no "e"). Capital's font list shows that name and a publish writes font-family:Monospac821 BT.
  • Install the font on the machine that publishes (the client for interactive publishes, the CIS machine for background ones). Capital lays text out with that machine's fonts; embedding fixes the viewer, not the layout.
  • The font file is licensed: web use may need a web-font licence. Keep it out of git.
  • The script needs Python with fonttools and brotli for TTF or OTF input.
  • The font file is served without a Content-Type header. Edge loaded it anyway; Firefox and Safari are not tested.

Doing it in the Publish click: the Embed Diagram Font plugin

Publisher accepts generator plugins (Java jars in <Capital install>\plugins, API reference in doc\plugin\api, interfaces IXResourceGenerator and IXDiagramDataGenerator). Each installed generator appears as a checkbox under Includes in Generate Documentation. Tests with a probe plugin showed that, in a desktop publish, generators run after Capital writes the diagram SVGs as loose files and before it zips them into contents.zip, and that Capital zips whatever is in that folder, including files a plugin adds.

The Embed Diagram Font plugin (plugins/embed-diagram-font in capital-publisher-config, shipped as a zip with its own INSTALL guide) uses that: tick it under Includes and every diagram using the configured family gets the @font-face and the font file, with no step after the publish. It supports separate bold and italic faces. Tested end to end on a desktop publish: the real viewer rendered the embedded face.

Three things the tests established, which the install guide repeats:

  • Background (CIS) publishes cannot use it. The package is built elsewhere and copied out after the plugin has run, so there is nothing for it to change. Run embed_diagram_font.py after the background task instead.
  • Publisher does not show plugin errors. Desktop and background publishes both report no errors even when the plugin fails. The plugin writes embed-diagram-font-report.txt into every package folder (OK: or NOT EMBEDDED: with the reason).
  • Verify before deploying with embed_diagram_font.py <package> --family "..." --check. It follows each diagram's font URLs, so it checks packages made by the plugin or the script.

Where the diagram font is set

Publisher takes fonts from the style set used for the publish (for example Default_Documentation), edited in Publisher under Edit All Styling Data. Style sets live in the Capital database, not in publisher\styleset.xml. Each object type has its own font entries, for example the wire label is Wire > Decorations > Horizontal > Composite Texts > WireCSA + " - " + WireMaterial + " - ". In Default_Documentation all 44 font entries are Lucida Sans Unicode. A style set cannot be copied from that dialog.

Alignment: what the font fixes and what it does not

Capital places every text by its left x, calculated from the font on the publishing machine. Capital 2512.2608 also writes textLength on every text, which forces the browser to draw the run at Capital's width. Measured on a right-justified label (target right edge 14152):

Diagram Right edge Off target
As published, font missing 14152 0 (fallback glyphs stretched to fit)
textLength removed, font missing 7996 -6156
As published, font embedded 14152 0, real glyphs
textLength removed, font embedded 12144 -2008 (spaces collapsed, see below)

So with textLength present, alignment already holds and embedding makes the glyphs right. Without it (older builds, version not established), the font removes most of the shift but not all of it, for two reasons:

  • Capital's text widths are 4 to 11 percent narrower than the font's real width at the size Capital writes, with the correct font installed, Lucida as well as Monospace 821.
  • Browsers collapse repeated and trailing spaces in these SVGs (Chromium ignores xml:space="preserve"). A label such as 0.0 - - loses two characters' width.

To check which case a site is in, look for textLength in one published diagram SVG.

Related findings from the same tests

  • A headless PackagingTask (REST) with "Application Files" produces an incomplete package: no launcher, no WEB-INF, no Packets\client-info.json, no Custom Runtime files. Generate complete packages from the desktop client.
  • The package server's HTTPS certificate is always lib\keystore in the package, copied from <CAPITAL_HOME>\publisher\server-config\keystore. The keystorePath setting in server-config.xml is ignored; the passwords in it are used (Jetty OBF: form). To serve your own certificate, replace that keystore file and set both passwords in all four explorer XMLs, which share it.

Source: Tested 2026-09-28 on Capital 2512.2608 (desktop and headless publishes, the package's own Service Explorer server, Edge); tool and evidence in repository capital-publisher-config · retrieved 2026-09-28