Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

Themes

A theme is one JSON file that says how a tenant's documents look. It carries fonts, colours, typography scales, page geometry, component styles (callouts, tables), and a chart palette. ScaiScribe compiles each theme into format-specific artefacts — a Word styles.xml, a PPTX masters config, an XLSX styles block, an ECharts theme JSON — and uses those compiled forms when rendering.

A tenant pins their documents to a theme by name. Change the theme; re-render; the document picks up the new look without any spec change.

Theme structure#

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
{
  "theme_id": "office",
  "version": "1.0.0",
  "extends": null,
  "fonts": {
    "heading": { "family": "Aptos Display", "weight": 700 },
    "body":    { "family": "Aptos",         "weight": 400 },
    "mono":    { "family": "Cascadia Mono", "weight": 400 },
    "fallbacks": ["Calibri", "Segoe UI", "Helvetica Neue", "Helvetica", "Arial", "sans-serif"]
  },
  "colors": {
    "primary":    "#2B579A",
    "secondary":  "#44546A",
    "accent":     "#5B9BD5",
    "background": "#FFFFFF",
    "text":       "#000000",
    "muted":      "#7F7F7F",
    "palette":    ["#2B579A", "#5B9BD5", "#70AD47", "#FFC000", "#ED7D31", "#A5A5A5"]
  },
  "typography": {
    "h1": { "size_pt": 28, "weight": 700, "color": "primary",   "spacing_after_pt": 12 },
    "h2": { "size_pt": 22, "weight": 700, "color": "secondary", "spacing_after_pt": 10 },
    "h3": { "size_pt": 18, "weight": 700, "color": "text",      "spacing_after_pt": 8 },
    "body":    { "size_pt": 11, "line_height": 1.45 },
    "code":    { "font": "mono", "size_pt": 10, "background": "#F2F2F2" }
  },
  "components": {
    "callouts": [
      { "id": "info", "left_bar": "primary",  "fill": "#DEEBF7" },
      { "id": "warn", "left_bar": "#D97706",  "fill": "#FFF7ED" },
      { "id": "note", "left_bar": "muted",    "fill": "#F2F2F2" }
    ],
    "chart_palette": ["#2B579A", "#5B9BD5", "#70AD47", "#FFC000", "#ED7D31", "#A5A5A5"]
  },
  "page": {
    "size": "A4",
    "margins_mm": { "top": 25, "right": 20, "bottom": 25, "left": 20 }
  },
  "pptx": {
    "size": "16:9",
    "typography": {
      "slide_title":    { "size_pt": 36 },
      "slide_subtitle": { "size_pt": 22 },
      "bullet_body":    { "size_pt": 18 }
    },
    "masters": [
      { "id": "title",   "zones": { "title": { "x": 0.5, "y": 2.2, "w": 12.33, "h": 1.2 },
                                    "subtitle": { "x": 0.5, "y": 3.5, "w": 12.33, "h": 0.8 } } },
      { "id": "content", "zones": { "title":    { "x": 0.5, "y": 0.3, "w": 12.33, "h": 0.9 },
                                    "subtitle": { "x": 0.5, "y": 1.2, "w": 12.33, "h": 0.5 },
                                    "body":     { "x": 0.5, "y": 1.9, "w": 12.33, "h": 5.1 } } }
    ]
  },
  "xlsx": {
    "header_row": { "fill": "primary", "font_color": "#FFFFFF", "bold": true },
    "alternating_row": { "fill": "#F2F2F2" },
    "totals_row": { "top_border": "primary", "bold": true },
    "number_formats": {
      "currency_eur": "€#,##0.00",
      "percent":      "0.0%"
    }
  }
}

Format-specific compilation#

A single theme produces five compiled artefacts. Each renderer consumes the one it needs:

Compiled form Consumed by Notes
docx_styles (XML fragment) render-docx.js Injected via dolanmiu/docx's externalStyles. Carries Normal, Heading1–4, CalloutInfo/Warn/Note, CodeBlock, PageBreak.
pptx_master (JSON config) render-pptx.js Drives pres.defineSlideMaster calls + slide colours/fonts + per-master layout zones (title/subtitle/body rectangles).
xlsx_styles (JSON config) render-xlsx.js Carries the header_row/alternating_row/totals_row cell blocks + the number_formats catalogue.
html_css (CSS bytes) render-html-preview.js Wraps the mammoth-emitted HTML for DOCX preview.
echarts (JSON theme) render-chart-image.js Theme palette for the SVG chart fallback.

Compilation is cached by content hash. The same theme, compiled twice, hits the cache the second time — so theme changes propagate but don't re-cost the CPU.

Inheritance via extends#

A theme can inherit from a parent:

json
1
2
3
4
5
{
  "theme_id": "acme_variant",
  "extends": "office",
  "colors": { "primary": "#1F4ED8" }
}

The child overrides what it specifies; everything else is inherited from the parent. v1.0 supports single-level extension (no chains). Override semantics are per-property: setting colors.primary doesn't blow away the rest of colors; it merges in.

Slide layout zones#

PPTX masters declare per-layout zones — rectangles for title, subtitle, and body content. The renderer uses these to place slide elements: title and subtitle land in their fixed slots, and body-shaped elements (paragraph, bullet_list, image, chart) stack sequentially inside the body zone with heights proportional to estimated content weight. This is what stops the classic "paragraph plus bullet list on the same slide render on top of each other" problem.

Themes that omit zones fall back to the renderer's defaults (sized for a 13.33″ × 7.5″ LAYOUT_WIDE canvas).

Tenant fonts#

Themes can reference fonts by family name. If the tenant has uploaded the matching face via POST /v1/fonts, the renderer picks it up automatically — font upload extracts family/weight/style via FontTools and indexes the binary for the workers. If no tenant-uploaded face matches, the renderer falls back to the theme's fonts.fallbacks array.

For PDF specifically, embedded fonts get baked into the output via Gotenberg + LibreOffice.

How themes apply by format#

  • DOCX: theme styles become Word styles (Heading1, CalloutInfo, CodeBlock). Authors writing {"type":"heading","level":1,...} get Heading1 automatically.
  • PPTX: each layout in a slide references a master id from pptx.masters[]. Render-pptx calls pres.addSlide({masterName: layout}) and lays elements out using the master's zones.
  • XLSX: cells with style_id get the matching xlsx.{header_row,alternating_row,totals_row} block applied. Numbers with number_format use the theme's format string.

Built-in themes#

Theme Use case
office Default. Aptos with Calibri fallback and a neutral palette — the same look Microsoft Office 2024+ ships with. Right for almost every request; the theme every caller gets when no theme is passed.
minimal Helvetica, monochrome. Good baseline for white-label resellers.
acme_variant Example of extends — inherits from office, overrides primary colour. Intended as a sample for tenants building their own brand theme.

Tenant-owned themes#

Brand themes live on tenants, not in the builtin bundle. A tenant registers its theme via the admin path and gets a scoped theme_id addressable in any spec created under that tenant. Cross-tenant theme references are refused — a caller asking for a brand theme that isn't theirs gets 404 theme not found.

scribe_list_themes returns the visible set for the caller's tenant (builtins plus their own tenant themes, each stamped scope: "builtin" or scope: "tenant").

Where to next#

  • Templates — themes vs templates (themes are styling, templates are pre-filled binary skeletons).
  • Spec format — how a spec references a theme.
  • Charts — chart_palette colours flow into native + image rendering.
Updated 2026-07-03 13:00:09 View source (.md) rev 10