@litfamily/litopencode 1.0.31 → 1.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README-Ko-KR.md +6 -6
- package/README.md +6 -6
- package/TRADEMARK.md +12 -0
- package/dist/activation-primary-prompts.js +5 -1
- package/dist/activation-probe.js +1 -0
- package/dist/activation-prompt-utils.d.ts +1 -1
- package/dist/activation-reentry.d.ts +20 -0
- package/dist/activation-reentry.js +110 -1
- package/dist/activation-routing.d.ts +2 -1
- package/dist/activation-routing.js +100 -11
- package/dist/activation-workflow-prompts.d.ts +4 -0
- package/dist/activation-workflow-prompts.js +30 -0
- package/dist/activation.d.ts +1 -1
- package/dist/activation.js +44 -5
- package/dist/adhd-next.d.ts +51 -0
- package/dist/adhd-next.js +150 -0
- package/dist/agents/defaults.js +1 -1
- package/dist/cli/managed-skill-assets.d.ts +1 -1
- package/dist/cli/managed-skill-assets.js +7 -1
- package/dist/cli/native-skill-integrity.js +1 -1
- package/dist/cli.js +7 -1
- package/dist/commands.d.ts +28 -1
- package/dist/commands.js +58 -5
- package/dist/durable-plan.d.ts +5 -1
- package/dist/durable-plan.js +14 -2
- package/dist/features.d.ts +80 -1
- package/dist/features.js +105 -1
- package/dist/hooks.js +3 -0
- package/dist/ledger.js +12 -1
- package/dist/plan-scope-roots.d.ts +23 -0
- package/dist/plan-scope-roots.js +116 -0
- package/dist/restart-notice.d.ts +7 -0
- package/dist/restart-notice.js +40 -0
- package/dist/secret-shapes.js +5 -5
- package/dist/server.js +4 -0
- package/dist/session-liveness.d.ts +20 -0
- package/dist/session-liveness.js +99 -0
- package/dist/skills.d.ts +22 -1
- package/dist/skills.js +36 -0
- package/dist/start-work-typed.d.ts +34 -0
- package/dist/start-work-typed.js +167 -0
- package/dist/state.d.ts +3 -0
- package/dist/state.js +3 -0
- package/dist/tools.js +22 -2
- package/dist/tui/mission-band.d.ts +22 -1
- package/dist/tui/mission-band.js +75 -16
- package/dist/tui/mission-looks.d.ts +38 -0
- package/dist/tui/mission-looks.js +73 -0
- package/dist/tui/mission-model.d.ts +2 -0
- package/dist/tui/mission-model.js +3 -1
- package/dist/tui/mission-rainbow.d.ts +33 -0
- package/dist/tui/mission-rainbow.js +90 -0
- package/dist/tui/mission-source.d.ts +49 -0
- package/dist/tui/mission-source.js +217 -2
- package/dist/tui/mission-text.d.ts +2 -0
- package/dist/tui/mission-text.js +9 -1
- package/dist/tui/mission-threads.d.ts +58 -0
- package/dist/tui/mission-threads.js +128 -0
- package/dist/tui.js +112 -7
- package/dist/uiux-visual-catalog.js +8 -1
- package/dist/v2/events.js +3 -0
- package/dist/v2/session-hooks.js +38 -5
- package/dist/workflow-stack.d.ts +53 -0
- package/dist/workflow-stack.js +196 -0
- package/docs/assets/readme/badge-version.svg +1 -1
- package/docs/assets/skills/lit-schematic.webp +0 -0
- package/docs/reference-Ko-KR.md +48 -3
- package/docs/reference.md +54 -3
- package/package.json +1 -1
- package/skills/frontend-ui-ux/SKILL.md +3 -2
- package/skills/frontend-ui-ux/kit/components.css +414 -0
- package/skills/frontend-ui-ux/kit/fonts.json +22 -0
- package/skills/frontend-ui-ux/kit/layouts/landing.html +164 -0
- package/skills/frontend-ui-ux/kit/layouts/settings.html +114 -0
- package/skills/frontend-ui-ux/kit/layouts/workspace.html +115 -0
- package/skills/frontend-ui-ux/kit/styles/computational-brutalism.json +227 -0
- package/skills/frontend-ui-ux/kit/styles/field-notes.json +205 -0
- package/skills/frontend-ui-ux/kit/styles/graphite.json +200 -0
- package/skills/frontend-ui-ux/kit/styles/meadow.json +199 -0
- package/skills/frontend-ui-ux/kit/styles/neo-brutalism.json +222 -0
- package/skills/frontend-ui-ux/kit/ui.js +226 -0
- package/skills/frontend-ui-ux/references/brand-and-imagery.md +2 -2
- package/skills/frontend-ui-ux/references/complete-contract.md +2 -1
- package/skills/frontend-ui-ux/references/craft-floor.md +6 -2
- package/skills/frontend-ui-ux/references/default-editorial-pixel.json +3 -0
- package/skills/frontend-ui-ux/references/house-styles.md +236 -0
- package/skills/frontend-ui-ux/references/motion-guide.md +2 -2
- package/skills/frontend-ui-ux/references/production.md +3 -1
- package/skills/frontend-ui-ux/references/slop-register.md +4 -4
- package/skills/frontend-ui-ux/scripts/design-lock.mjs +400 -0
- package/skills/frontend-ui-ux/scripts/pixel-art.mjs +60 -0
- package/skills/frontend-ui-ux/scripts/probe-lock.mjs +235 -0
- package/skills/frontend-ui-ux/scripts/probe-page.mjs +34 -12
- package/skills/frontend-ui-ux/scripts/probe-static.mjs +16 -3
- package/skills/frontend-ui-ux/scripts/probe-thresholds.json +8 -2
- package/skills/frontend-ui-ux/scripts/probe.mjs +35 -7
- package/skills/lit-adhd/SKILL.md +209 -0
- package/skills/lit-adhd/references/brief-format.md +46 -0
- package/skills/lit-adhd/references/card-examples.md +103 -0
- package/skills/lit-adhd/references/status-taxonomy.md +34 -0
- package/skills/lit-adhd/scripts/rewind.mjs +933 -0
- package/skills/lit-diagram-drawer/SKILL.md +4 -3
- package/skills/lit-diagram-drawer/references/style-guide.md +20 -11
- package/skills/lit-diagram-drawer/references/type-architecture.md +3 -3
- package/skills/lit-diagram-drawer/references/type-dependency.md +1 -1
- package/skills/lit-diagram-drawer/references/type-tree.md +3 -3
- package/skills/lit-diagram-drawer/references/type-wardley.md +1 -1
- package/skills/lit-diagram-drawer/references/verifier-guide.md +1 -0
- package/skills/lit-diagram-drawer/scripts/craft-thresholds.json +1 -2
- package/skills/lit-diagram-drawer/scripts/visual-quality.mjs +1 -2
- package/skills/lit-docx/SKILL.md +2 -1
- package/skills/lit-handoff/SKILL.md +2 -0
- package/skills/lit-hijack/SKILL.md +422 -0
- package/skills/lit-hijack/references/capture-and-provenance.md +87 -0
- package/skills/lit-hijack/references/clean-room-build.md +111 -0
- package/skills/lit-hijack/references/distil-and-licence.md +100 -0
- package/skills/lit-hijack/references/gap-and-port-design.md +126 -0
- package/skills/lit-hijack/references/parity-and-report.md +110 -0
- package/skills/lit-hijack/scripts/hijack.mjs +993 -0
- package/skills/lit-plan/SKILL.md +8 -0
- package/skills/lit-pptx/SKILL.md +27 -11
- package/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Bold.ttf +0 -0
- package/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf +0 -0
- package/skills/lit-pptx/fonts/jetbrains-mono/OFL.txt +93 -0
- package/skills/lit-pptx/fonts/jetbrains-mono/SOURCE.md +17 -0
- package/skills/lit-pptx/references/density-and-fill.md +9 -3
- package/skills/lit-pptx/references/direction-step.md +21 -6
- package/skills/lit-pptx/references/examples/04-lecture-chalk-ko.md +3 -6
- package/skills/lit-pptx/references/examples/05-research-talk-paper-en.md +1 -5
- package/skills/lit-pptx/references/examples/08-reliability-keynote-night-en.md +0 -4
- package/skills/lit-pptx/references/examples/09-status-update-ledger-en.md +2 -2
- package/skills/lit-pptx/references/examples/11-forecast-review-night-ko.md +4 -7
- package/skills/lit-pptx/references/examples/13-engineering-review-computational-brutalism-ko.md +173 -0
- package/skills/lit-pptx/references/examples/14-launch-pitch-neo-brutalism-ko.md +154 -0
- package/skills/lit-pptx/references/examples/15-design-lecture-bauhaus-ko.md +139 -0
- package/skills/lit-pptx/references/examples/16-community-programme-riso-ko.md +158 -0
- package/skills/lit-pptx/references/examples/17-public-data-briefing-newsroom-ko.md +179 -0
- package/skills/lit-pptx/references/layout-families.md +65 -47
- package/skills/lit-pptx/references/title-treatments.md +83 -134
- package/skills/lit-pptx/references/tonalities/atlas.md +3 -3
- package/skills/lit-pptx/references/tonalities/bauhaus.md +140 -0
- package/skills/lit-pptx/references/tonalities/chalk.md +7 -6
- package/skills/lit-pptx/references/tonalities/computational-brutalism.md +150 -0
- package/skills/lit-pptx/references/tonalities/gazette.md +1 -1
- package/skills/lit-pptx/references/tonalities/ledger.md +1 -1
- package/skills/lit-pptx/references/tonalities/neo-brutalism.md +161 -0
- package/skills/lit-pptx/references/tonalities/newsroom.md +144 -0
- package/skills/lit-pptx/references/tonalities/night.md +1 -1
- package/skills/lit-pptx/references/tonalities/paper.md +2 -2
- package/skills/lit-pptx/references/tonalities/riso.md +137 -0
- package/skills/lit-pptx/references/tonalities/signal.md +3 -3
- package/skills/lit-pptx/references/tonalities/studio.md +7 -6
- package/skills/lit-pptx/run.mjs +2 -1
- package/skills/lit-pptx/scripts/compile-deck.js +1 -1
- package/skills/lit-pptx/scripts/craft-thresholds.json +4 -8
- package/skills/lit-pptx/scripts/craft_extras.py +56 -3
- package/skills/lit-pptx/scripts/deck_output.py +75 -90
- package/skills/lit-pptx/scripts/embed_fonts.py +13 -1
- package/skills/lit-pptx/scripts/layout_inventory.py +12 -5
- package/skills/lit-pptx/scripts/lib/grid-resolver.js +101 -110
- package/skills/lit-pptx/scripts/lib/layout-resolver.js +9 -15
- package/skills/lit-pptx/scripts/lib/render-pack.js +373 -566
- package/skills/lit-pptx/scripts/lib/slide-grid.js +104 -0
- package/skills/lit-pptx/scripts/lib/template-registry.js +57 -6
- package/skills/lit-pptx/scripts/qa_deck.py +10 -11
- package/skills/lit-pptx/scripts/render_pages.py +118 -0
- package/skills/lit-pptx/templates/tonalities/atlas/pack.yaml +1 -1
- package/skills/lit-pptx/templates/tonalities/bauhaus/pack.yaml +98 -0
- package/skills/lit-pptx/templates/tonalities/chalk/pack.yaml +1 -1
- package/skills/lit-pptx/templates/tonalities/computational-brutalism/pack.yaml +99 -0
- package/skills/lit-pptx/templates/tonalities/gazette/pack.yaml +1 -1
- package/skills/lit-pptx/templates/tonalities/ledger/pack.yaml +1 -1
- package/skills/lit-pptx/templates/tonalities/neo-brutalism/pack.yaml +107 -0
- package/skills/lit-pptx/templates/tonalities/newsroom/pack.yaml +99 -0
- package/skills/lit-pptx/templates/tonalities/night/pack.yaml +1 -1
- package/skills/lit-pptx/templates/tonalities/paper/pack.yaml +1 -1
- package/skills/lit-pptx/templates/tonalities/riso/pack.yaml +99 -0
- package/skills/lit-pptx/templates/tonalities/signal/pack.yaml +2 -2
- package/skills/lit-pptx/templates/tonalities/studio/pack.yaml +1 -1
- package/skills/lit-recap/SKILL.md +4 -0
- package/skills/lit-schematic/SKILL.md +118 -0
- package/skills/lit-schematic/examples/iso-heat-exchanger/build.mjs +38 -0
- package/skills/lit-schematic/examples/iso-heat-exchanger/scene.mjs +133 -0
- package/skills/lit-schematic/examples/journal-adsorption-rig/build.mjs +87 -0
- package/skills/lit-schematic/examples/pid-dosing-skid/build.mjs +85 -0
- package/skills/lit-schematic/references/cutaway-outputs.md +70 -0
- package/skills/lit-schematic/references/isometric-craft.md +101 -0
- package/skills/lit-schematic/references/journal-figures.md +72 -0
- package/skills/lit-schematic/references/kit-api.md +134 -0
- package/skills/lit-schematic/references/pfd-and-layout.md +53 -0
- package/skills/lit-schematic/references/pid-drafting.md +103 -0
- package/skills/lit-schematic/references/pid-symbols.md +163 -0
- package/skills/lit-schematic/references/verification.md +100 -0
- package/skills/lit-schematic/scripts/doctor.mjs +57 -0
- package/skills/lit-schematic/scripts/lib/figure.mjs +81 -0
- package/skills/lit-schematic/scripts/lib/fonts.mjs +25 -0
- package/skills/lit-schematic/scripts/lib/geom.mjs +113 -0
- package/skills/lit-schematic/scripts/lib/interactive.mjs +292 -0
- package/skills/lit-schematic/scripts/lib/isa.mjs +115 -0
- package/skills/lit-schematic/scripts/lib/iso.mjs +719 -0
- package/skills/lit-schematic/scripts/lib/legend-sheet.mjs +162 -0
- package/skills/lit-schematic/scripts/lib/pid.mjs +937 -0
- package/skills/lit-schematic/scripts/lib/png.mjs +132 -0
- package/skills/lit-schematic/scripts/lib/qa-core.mjs +658 -0
- package/skills/lit-schematic/scripts/lib/route.mjs +218 -0
- package/skills/lit-schematic/scripts/lib/sheet.mjs +890 -0
- package/skills/lit-schematic/scripts/lib/svg.mjs +82 -0
- package/skills/lit-schematic/scripts/lib/theme.mjs +115 -0
- package/skills/lit-schematic/scripts/motion.mjs +129 -0
- package/skills/lit-schematic/scripts/qa.mjs +92 -0
- package/skills/lit-schematic/scripts/render.mjs +233 -0
- package/skills/lit-schematic/scripts/scaffold.mjs +60 -0
- package/skills/lit-scientific-visualization/SKILL.md +27 -4
- package/skills/lit-typographic-motion/SKILL.md +19 -17
- package/skills/lit-typographic-motion/THIRD_PARTY_NOTICES +9 -0
- package/skills/lit-typographic-motion/engine/accents.mjs +6 -0
- package/skills/lit-typographic-motion/engine/assets.mjs +282 -0
- package/skills/lit-typographic-motion/engine/blender/runner.py +228 -0
- package/skills/lit-typographic-motion/engine/blender.mjs +126 -0
- package/skills/lit-typographic-motion/engine/board.mjs +138 -0
- package/skills/lit-typographic-motion/engine/cover/assets.mjs +11 -8
- package/skills/lit-typographic-motion/engine/cover/capture.mjs +6 -6
- package/skills/lit-typographic-motion/engine/cover/render.mjs +20 -7
- package/skills/lit-typographic-motion/engine/cover/treatment.mjs +30 -3
- package/skills/lit-typographic-motion/engine/fill.mjs +54 -0
- package/skills/lit-typographic-motion/engine/gallery.mjs +118 -0
- package/skills/lit-typographic-motion/engine/house.mjs +42 -0
- package/skills/lit-typographic-motion/engine/look.mjs +65 -8
- package/skills/lit-typographic-motion/engine/loudness.mjs +101 -0
- package/skills/lit-typographic-motion/engine/scaffold.mjs +219 -0
- package/skills/lit-typographic-motion/engine/score.mjs +594 -0
- package/skills/lit-typographic-motion/engine/sound.mjs +9 -57
- package/skills/lit-typographic-motion/engine/stage/capture.mjs +23 -9
- package/skills/lit-typographic-motion/engine/stage/clock.js +8 -2
- package/skills/lit-typographic-motion/engine/stage/cover-init.js +12 -1
- package/skills/lit-typographic-motion/engine/stage/frame-worker.mjs +15 -7
- package/skills/lit-typographic-motion/engine/stage/gate.mjs +48 -4
- package/skills/lit-typographic-motion/engine/stage/qa-page.js +16 -5
- package/skills/lit-typographic-motion/engine/stage/qa.mjs +148 -7
- package/skills/lit-typographic-motion/engine/stage/render.mjs +69 -7
- package/skills/lit-typographic-motion/engine/stage/scan.mjs +20 -1
- package/skills/lit-typographic-motion/engine/stage/stage-kit.js +85 -4
- package/skills/lit-typographic-motion/engine/stills.mjs +1 -1
- package/skills/lit-typographic-motion/engine/styles.mjs +221 -0
- package/skills/lit-typographic-motion/engine/text.mjs +1 -1
- package/skills/lit-typographic-motion/engine/treatment.mjs +190 -6
- package/skills/lit-typographic-motion/engine/vendor/three/LICENSE +21 -0
- package/skills/lit-typographic-motion/engine/vendor/three/addons/environments/RoomEnvironment.js +144 -0
- package/skills/lit-typographic-motion/engine/vendor/three/addons/geometries/RoundedBoxGeometry.js +155 -0
- package/skills/lit-typographic-motion/engine/vendor/three/three.module.js +54571 -0
- package/skills/lit-typographic-motion/gate.mjs +18 -2
- package/skills/lit-typographic-motion/probe.mjs +13 -1
- package/skills/lit-typographic-motion/recipes/hero-3d/expected.json +64 -0
- package/skills/lit-typographic-motion/recipes/hero-3d/index.html +132 -0
- package/skills/lit-typographic-motion/recipes/orbit-loop/expected.json +2 -0
- package/skills/lit-typographic-motion/recipes/painted-light/expected.json +2 -0
- package/skills/lit-typographic-motion/recipes/weight-tide/expected.json +2 -0
- package/skills/lit-typographic-motion/references/cover.md +56 -13
- package/skills/lit-typographic-motion/references/craft-loop.md +20 -7
- package/skills/lit-typographic-motion/references/depth.md +112 -0
- package/skills/lit-typographic-motion/references/runtime.md +21 -6
- package/skills/lit-typographic-motion/references/sound.md +76 -0
- package/skills/lit-typographic-motion/references/stage.md +50 -7
- package/skills/lit-typographic-motion/references/studio.md +162 -0
- package/skills/lit-typographic-motion/references/style-bibles.md +18 -0
- package/skills/lit-typographic-motion/references/treatment.md +108 -9
- package/skills/lit-typographic-motion/render.mjs +60 -3
- package/skills/lit-typographic-motion/styles/paper.json +185 -0
- package/skills/lit-typographic-motion/styles/swiss-signal.json +188 -0
- package/skills/lit-typographic-motion/styles/terminalcore.json +182 -0
- package/skills/lit-typographic-motion/styles/tidal.json +187 -0
- package/skills/litresearch/SKILL.md +2 -0
- package/skills/managed-skill-manifest.json +517 -88
- package/skills/readme-studio/SKILL.md +1 -1
- package/skills/review-work/SKILL.md +2 -0
- package/skills/start-work/SKILL.md +8 -0
- package/skills/workflow-loop/SKILL.md +4 -0
- package/tools/check-pack-payload.mjs +5 -2
- package/tools/payload-substance-parity.json +4 -3
- package/tools/qa-cover-recipes.mjs +4 -3
- package/tools/version-manifests.json +2 -2
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* slide-grid.js — where things sit on a slide, the same for every tonality.
|
|
5
|
+
*
|
|
6
|
+
* One column grid per canvas and one set of zones per slide kind. A tonality changes how a zone
|
|
7
|
+
* looks (face, weight, size, colour, rules, fills, ornaments), never where it is: every title,
|
|
8
|
+
* header, body and footer of a deck lands on the coordinates below whichever pack draws it, so two
|
|
9
|
+
* tonalities of one source differ in look and never in placement. The density dial sets type and
|
|
10
|
+
* spacing inside the zones; it no longer moves the margins. Values are points on the 960 x 540
|
|
11
|
+
* (16:9) or 720 x 540 (4:3) canvas; `emu()` gives the same box in EMU (12,700 per point).
|
|
12
|
+
*
|
|
13
|
+
* Content slides
|
|
14
|
+
* header the band over the title: grid marks, the slide index, a kicker label
|
|
15
|
+
* title the title frame of every treatment (top-rule, band, top-plain-large, kicker-numeral,
|
|
16
|
+
* side-rail, bottom-anchor); a treatment styles it and never moves it
|
|
17
|
+
* aside right of the title: the kicker numeral
|
|
18
|
+
* body everything the slide says, from the first block to the floor
|
|
19
|
+
* footer the sample-data tag and the source line's foot
|
|
20
|
+
* Display slides (statement, quote, section, closing statement)
|
|
21
|
+
* header the part number or kicker over the sentence
|
|
22
|
+
* title the sentence, centred in its frame
|
|
23
|
+
* support the line under it (the quote's speaker, what the part opens with)
|
|
24
|
+
* side the columns right of the sentence, bled to the page edge (a composition, a rail)
|
|
25
|
+
* Covers
|
|
26
|
+
* header the meta line (date, team, presenter)
|
|
27
|
+
* title the deck title, standing on the frame's foot
|
|
28
|
+
* subtitle the line under the title
|
|
29
|
+
* strip the headline figures or the parts, across the page above the footer
|
|
30
|
+
* side the columns right of the title, bled to the page edge (a field, a picture, a numeral)
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const CANVAS = { "16:9": { W: 960, H: 540 }, "4:3": { W: 720, H: 540 } };
|
|
34
|
+
|
|
35
|
+
// Side margin, column width and gutter: 12 columns and 11 gutters that sum exactly to the width.
|
|
36
|
+
const COLUMNS = { "16:9": { margin: 24, col: 54, gutter: 24 }, "4:3": { margin: 24, col: 45, gutter: 12 } };
|
|
37
|
+
|
|
38
|
+
const FOOTER = { cols: [1, 12], y: 498, h: 18 };
|
|
39
|
+
|
|
40
|
+
const ZONES = {
|
|
41
|
+
content: {
|
|
42
|
+
header: { cols: [1, 12], y: 12, h: 18 },
|
|
43
|
+
title: { cols: [1, 10], y: 36, h: 72 },
|
|
44
|
+
aside: { cols: [11, 12], y: 36, h: 72 },
|
|
45
|
+
body: { cols: [1, 12], y: 120, h: 366 },
|
|
46
|
+
footer: FOOTER,
|
|
47
|
+
},
|
|
48
|
+
display: {
|
|
49
|
+
header: { cols: [1, 9], y: 84, h: 36 },
|
|
50
|
+
title: { cols: [1, 9], y: 132, h: 216 },
|
|
51
|
+
support: { cols: [1, 8], y: 372, h: 114 },
|
|
52
|
+
side: { cols: [10, 12], y: 0, h: 540, bleed: true },
|
|
53
|
+
footer: FOOTER,
|
|
54
|
+
},
|
|
55
|
+
cover: {
|
|
56
|
+
header: { cols: [1, 8], y: 36, h: 24 },
|
|
57
|
+
title: { cols: [1, 9], y: 144, h: 168 },
|
|
58
|
+
subtitle: { cols: [1, 8], y: 324, h: 72 },
|
|
59
|
+
strip: { cols: [1, 12], y: 414, h: 66 },
|
|
60
|
+
side: { cols: [10, 12], y: 0, h: 540, bleed: true },
|
|
61
|
+
footer: FOOTER,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function columns(canvas) {
|
|
66
|
+
const c = COLUMNS[canvas];
|
|
67
|
+
if (!c) throw new Error(`unknown canvas "${canvas}"; canvases: ${Object.keys(COLUMNS).join(", ")}`);
|
|
68
|
+
return c;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Columns a…b (1-based, inclusive) as x and width in points. */
|
|
72
|
+
function span(canvas, a, b) {
|
|
73
|
+
const c = columns(canvas);
|
|
74
|
+
const pitch = c.col + c.gutter;
|
|
75
|
+
return { x: c.margin + pitch * (a - 1), w: pitch * (b - a + 1) - c.gutter };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* One zone as a box in points. A bled zone starts half a gutter left of its first column and runs
|
|
80
|
+
* to the page's right edge, so what fills it meets the edge and keeps the gutter from the text.
|
|
81
|
+
*/
|
|
82
|
+
function zone(canvas, kind, name) {
|
|
83
|
+
const spec = (ZONES[kind] || {})[name];
|
|
84
|
+
if (!spec) throw new Error(`no ${name} zone on a ${kind} slide`);
|
|
85
|
+
const s = span(canvas, spec.cols[0], spec.cols[1]);
|
|
86
|
+
if (spec.bleed) {
|
|
87
|
+
const x = s.x - columns(canvas).gutter / 2;
|
|
88
|
+
return { x, y: spec.y, w: CANVAS[canvas].W - x, h: spec.h, a: spec.cols[0], b: spec.cols[1] };
|
|
89
|
+
}
|
|
90
|
+
return { x: s.x, y: spec.y, w: s.w, h: spec.h, a: spec.cols[0], b: spec.cols[1] };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Every zone of every slide kind on a canvas, in points. */
|
|
94
|
+
function zones(canvas) {
|
|
95
|
+
return Object.fromEntries(Object.entries(ZONES).map(([kind, set]) => [kind, Object.fromEntries(Object.keys(set).map((name) => [name, zone(canvas, kind, name)]))]));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** A box in points as EMU, the unit a .pptx file stores. */
|
|
99
|
+
function emu(box) {
|
|
100
|
+
const e = (v) => Math.round(v * 12700);
|
|
101
|
+
return { x: e(box.x), y: e(box.y), w: e(box.w), h: e(box.h) };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = { CANVAS, COLUMNS, ZONES, span, zone, zones, emu };
|
|
@@ -428,19 +428,29 @@ const FACES = {
|
|
|
428
428
|
"A2Z Medium": { font: "에이투지체 5 Medium", bold: false },
|
|
429
429
|
"A2Z Bold": { font: "에이투지체 7 Bold", bold: false },
|
|
430
430
|
"A2Z Black": { font: "에이투지체 9 Black", bold: false },
|
|
431
|
+
// A Latin-only face: Hangul and other East Asian text in its runs is set in Pretendard.
|
|
432
|
+
"JetBrains Mono Regular": { font: "JetBrains Mono", bold: false, ea: "Pretendard" },
|
|
433
|
+
"JetBrains Mono Bold": { font: "JetBrains Mono", bold: true, ea: "Pretendard" },
|
|
431
434
|
};
|
|
432
|
-
const DECORATIONS = ["accent-rule", "hairline-rule", "header-band", "rail-fill", "colour-field", "caption-band", "column-hairlines", "box-outline", "mark-underline", "numeral"];
|
|
435
|
+
const DECORATIONS = ["accent-rule", "hairline-rule", "header-band", "rail-fill", "colour-field", "caption-band", "column-hairlines", "box-outline", "mark-underline", "numeral", "grid-marks"];
|
|
433
436
|
const SLIDE_ROLES = ["content", "data", "data-takeaway", "sequence", "image", "statement", "reference", "definition"];
|
|
434
437
|
const FILL_POLICIES = ["distribute", "step-up", "anchor-visual", "change-family"];
|
|
435
438
|
// How a pack carries its display slides: the device on a type-led cover, around a statement,
|
|
436
439
|
// behind a big number and under a closing's next step (`display.index` adds the part index to
|
|
437
440
|
// section slides).
|
|
438
441
|
const DISPLAY_DEVICES = {
|
|
439
|
-
cover: ["plain", "drench", "plate", "rail", "band", "figures", "rules", "numeral"],
|
|
440
|
-
statement: ["open", "drench", "rules", "plate", "offset"],
|
|
442
|
+
cover: ["plain", "drench", "plate", "rail", "band", "figures", "rules", "numeral", "geometry"],
|
|
443
|
+
statement: ["open", "drench", "rules", "plate", "offset", "geometry"],
|
|
441
444
|
number: ["field", "tint", "outline"],
|
|
442
445
|
closing: ["band", "box", "rules"],
|
|
443
446
|
};
|
|
447
|
+
// Allowances a pack may claim from the gate, each a bounded value (see craft_extras.py). The accent
|
|
448
|
+
// limits only widen: four hue families pass every slide by default, so a pack may allow five or six.
|
|
449
|
+
const QA_ALLOWANCES = {
|
|
450
|
+
"accent-families": (v) => Number.isInteger(v) && v >= 5 && v <= 6,
|
|
451
|
+
"display-accent-families": (v) => Number.isInteger(v) && v >= 5 && v <= 6,
|
|
452
|
+
"offset-shadow": (v) => typeof v === "boolean",
|
|
453
|
+
};
|
|
444
454
|
// Contrast pairs every pack must meet (text pairs 4.5:1, large marks 3:1).
|
|
445
455
|
const CONTRAST_PAIRS = [["ink", "ground", 4.5], ["ink", "surface", 4.5], ["ink-muted", "ground", 4.5], ["ink-muted", "surface", 4.5],
|
|
446
456
|
["accent", "ground", 3.0], ["accent-deep", "accent-tint", 4.5], ["ink", "accent-tint", 4.5], ["on-field", "field", 4.5],
|
|
@@ -473,9 +483,12 @@ function validatePack(pack, file) {
|
|
|
473
483
|
for (const key of ["faces", "faces-a2z"]) {
|
|
474
484
|
if (key === "faces-a2z" && pack[key] == null) continue;
|
|
475
485
|
const faces = pack[key] || {};
|
|
486
|
+
// A Latin-only face may set the display roles; body text always stays in a Korean face.
|
|
476
487
|
for (const role of FACE_KEYS) {
|
|
477
|
-
|
|
488
|
+
const own = key === "faces-a2z" || /^Pretendard /u.test(faces[role]) || (role !== "body" && FACES[faces[role]] && FACES[faces[role]].ea);
|
|
489
|
+
need(FACES[faces[role]] && own, `${key}.${role} must name a bundled face${key === "faces-a2z" ? "" : role === "body" ? " (Pretendard Regular or Bold)" : " (Pretendard Regular or Bold, or JetBrains Mono Regular or Bold)"}`);
|
|
478
490
|
}
|
|
491
|
+
need(!(FACES[faces.body] && FACES[faces.body].ea), `${key}.body must be a face that sets Hangul`);
|
|
479
492
|
const distinct = new Set(FACE_KEYS.map((r) => faces[r]));
|
|
480
493
|
need(distinct.size <= 3, `${key} uses ${distinct.size} faces; at most 3`);
|
|
481
494
|
need(new Set([...distinct].map((f) => String(f).split(" ")[0])).size <= 2, `${key} mixes more than two families`);
|
|
@@ -497,10 +510,32 @@ function validatePack(pack, file) {
|
|
|
497
510
|
}
|
|
498
511
|
need(["presented", "reading"].includes(pack.ramp), "ramp must be presented or reading");
|
|
499
512
|
need(typeof pack.hero === "boolean", "hero must be true or false");
|
|
513
|
+
// A report pack reads at its density step; a bold pack (a pitch, a launch, a campaign) may set presented type.
|
|
514
|
+
// Any pack may take the hero step on its cover and statements. Either way the words stay in the slide grid's
|
|
515
|
+
// zones: the register and the hero step change size, never place.
|
|
516
|
+
const register = pack.register == null ? "report" : pack.register;
|
|
517
|
+
need(["report", "bold"].includes(register), "register must be report or bold");
|
|
518
|
+
need(register === "bold" || pack.ramp === "reading", "presented type needs register: bold; a report pack keeps ramp: reading");
|
|
500
519
|
need(Number.isInteger(pack.density) && pack.density >= 1 && pack.density <= 10, "density must be an integer 1-10");
|
|
501
520
|
need(Number.isInteger(pack.variance) && pack.variance >= 4 && pack.variance <= 10, "variance must be an integer 4-10");
|
|
502
521
|
need(pack.radius === 0 || pack.radius === 6, "radius must be 0 or 6");
|
|
503
522
|
need(["border", "fill", "fill+border"].includes(pack.edge), "edge must be border, fill or fill+border");
|
|
523
|
+
// A heavier border and a hard offset shadow (zero blur) under surfaces; the shadow needs the border.
|
|
524
|
+
const weight = pack["edge-weight"];
|
|
525
|
+
need(weight == null || (typeof weight === "number" && weight >= 0.5 && weight <= 4), "edge-weight must be 0.5-4 pt");
|
|
526
|
+
const shadow = pack.shadow;
|
|
527
|
+
if (shadow != null) {
|
|
528
|
+
need(typeof shadow === "object" && typeof shadow.offset === "number" && shadow.offset >= 3 && shadow.offset <= 10, "shadow.offset must be 3-10 pt");
|
|
529
|
+
need(shadow && PALETTE_ROLES.includes(shadow.colour), "shadow.colour must name a palette role");
|
|
530
|
+
need(pack.edge !== "fill", "a shadow needs a bordered edge (border or fill+border)");
|
|
531
|
+
}
|
|
532
|
+
need(pack["edge-colour"] == null || PALETTE_ROLES.includes(pack["edge-colour"]), "edge-colour must name a palette role");
|
|
533
|
+
// What the gate allows this pack beyond the default craft floor (read by craft_extras.py).
|
|
534
|
+
const qa = pack.qa || {};
|
|
535
|
+
need(typeof qa === "object" && !Array.isArray(qa), "qa must be a map");
|
|
536
|
+
for (const k of Object.keys(qa)) need(QA_ALLOWANCES[k], `unknown qa allowance "${k}" (known: ${Object.keys(QA_ALLOWANCES).join(", ")})`);
|
|
537
|
+
for (const [k, ok] of Object.entries(QA_ALLOWANCES)) if (qa[k] != null) need(ok(qa[k]), `qa.${k} is out of range`);
|
|
538
|
+
need(!qa["offset-shadow"] || shadow != null, "qa.offset-shadow needs a shadow");
|
|
504
539
|
const treatments = isList(pack.treatments) ? pack.treatments : [];
|
|
505
540
|
need(treatments.length >= 3 && treatments.length <= 5, "treatments must list 3-5 title treatments");
|
|
506
541
|
for (const t of treatments) need(grid.TREATMENTS.includes(t), `unknown treatment "${t}" (known: ${grid.TREATMENTS.join(", ")})`);
|
|
@@ -514,6 +549,10 @@ function validatePack(pack, file) {
|
|
|
514
549
|
need(treatments.includes(t) && grid.FAMILIES[f] && grid.FAMILIES[f].allow.includes(t), `structure.${f} must be a treatment of the pack that the family allows`);
|
|
515
550
|
}
|
|
516
551
|
for (const d of pack.decoration || []) need(DECORATIONS.includes(d), `unknown decoration "${d}"`);
|
|
552
|
+
// Grid marks sit in the top margin on the ground: a band or a filled rail there would cover them.
|
|
553
|
+
if ((pack.decoration || []).includes("grid-marks")) {
|
|
554
|
+
need(!treatments.includes("band") && !(pack.decoration || []).includes("rail-fill"), "grid-marks needs the top margin on the ground (no band treatment, no rail-fill)");
|
|
555
|
+
}
|
|
517
556
|
need(pack.rail == null || ["surface", "field"].includes(pack.rail), "rail must be surface or field");
|
|
518
557
|
const display = pack.display || {};
|
|
519
558
|
need(typeof display === "object" && !Array.isArray(display), "display must be a map");
|
|
@@ -634,12 +673,22 @@ function loadTonality(id, options = {}) {
|
|
|
634
673
|
if (!pack.canvas.includes(canvas)) {
|
|
635
674
|
throw new Error(`Tonality "${pack.id}" supports ${pack.canvas.join(", ")}, not canvas ${canvas}`);
|
|
636
675
|
}
|
|
637
|
-
|
|
638
|
-
const
|
|
676
|
+
// Only a bold register sets presented type; a report pack reads at its density step.
|
|
677
|
+
const tok = grid.densityTokens(density, pack.register === "bold" ? pack.ramp : "reading");
|
|
678
|
+
const g = grid.makeGrid(canvas);
|
|
639
679
|
// Pretendard by default; the A2Z map only when the source asks for it (`faces: a2z`).
|
|
640
680
|
const wantsA2z = String(options.faces || "").toLowerCase() === "a2z" && pack["faces-a2z"];
|
|
641
681
|
const faceMap = wantsA2z ? pack["faces-a2z"] : pack.faces;
|
|
642
682
|
const faces = Object.fromEntries(FACE_KEYS.map((role) => [role, FACES[faceMap[role]]]));
|
|
683
|
+
// The deck may add decoration kinds to the pack's own list and turn the hero step off (`hero: false`,
|
|
684
|
+
// the calm opt-out); both arrive as front-matter strings.
|
|
685
|
+
const extra = String(options.decoration || "").split(",").map((d) => d.trim()).filter(Boolean);
|
|
686
|
+
for (const d of extra) if (!DECORATIONS.includes(d)) throw new Error(`decoration "${d}" is not a known kind (${DECORATIONS.join(", ")})`);
|
|
687
|
+
const decoration = [...new Set([...(pack.decoration || []), ...extra])];
|
|
688
|
+
if (decoration.includes("grid-marks") && (pack.treatments.includes("band") || decoration.includes("rail-fill"))) {
|
|
689
|
+
throw new Error(`grid-marks needs the top margin on the ground (no band treatment, no rail-fill); tonality "${pack.id}" has one`);
|
|
690
|
+
}
|
|
691
|
+
const hero = String(options.hero).toLowerCase() === "false" || options.hero === false ? false : pack.hero;
|
|
643
692
|
const P = pack.palette;
|
|
644
693
|
const template = {
|
|
645
694
|
name: pack.name,
|
|
@@ -662,6 +711,8 @@ function loadTonality(id, options = {}) {
|
|
|
662
711
|
capabilities: { font_roles: {} },
|
|
663
712
|
pack: {
|
|
664
713
|
...pack,
|
|
714
|
+
decoration,
|
|
715
|
+
hero,
|
|
665
716
|
legacy: false,
|
|
666
717
|
capabilities: [],
|
|
667
718
|
faces,
|
|
@@ -7,12 +7,12 @@ exits non-zero if ANY real defect is found:
|
|
|
7
7
|
1. Layout issues — inventory.py --issues-only (overflow / overlap / off-slide)
|
|
8
8
|
2. Anti-slop — validate_pptx.py (forbidden/placeholder terms, default fonts)
|
|
9
9
|
3. WCAG contrast — text colour vs its fill (body < 4.5:1, large < 3:1)
|
|
10
|
-
4. Craft floor — craft_extras.py (OF-101..OF-109 per slide) and
|
|
11
|
-
(OF-110..OF-
|
|
12
|
-
variety, empty body band, one
|
|
13
|
-
no empty region, no light figure card on a dark ground; with
|
|
14
|
-
--sibling, OF-116: another tonality of the same source
|
|
15
|
-
|
|
10
|
+
4. Craft floor — craft_extras.py (OF-101..OF-109 per slide, OF-120 hard shadows) and
|
|
11
|
+
deck_output.py (OF-110..OF-119 over the deck: title-treatment count,
|
|
12
|
+
composition variety, empty body band, one shared title frame, label
|
|
13
|
+
titles, no empty region, no light figure card on a dark ground; with
|
|
14
|
+
--sibling, OF-116: another tonality of the same source sets titles and
|
|
15
|
+
body in the same places)
|
|
16
16
|
|
|
17
17
|
No LibreOffice / rendering engine required: everything is read from the OOXML
|
|
18
18
|
via python-pptx. (A renderer is only ever needed for *visual* preview, which is
|
|
@@ -35,7 +35,7 @@ from pptx.dml.color import RGBColor
|
|
|
35
35
|
from pptx.enum.shapes import MSO_SHAPE_TYPE
|
|
36
36
|
from pptx.opc.constants import RELATIONSHIP_TYPE as RT
|
|
37
37
|
from craft_extras import analyse as analyse_craft
|
|
38
|
-
from deck_output import
|
|
38
|
+
from deck_output import compare_placement
|
|
39
39
|
|
|
40
40
|
ROOT = Path(__file__).resolve().parents[1]
|
|
41
41
|
VALIDATE = ROOT / "scripts" / "validate_pptx.py"
|
|
@@ -617,9 +617,8 @@ def check_composition(path: Path) -> dict[str, Any]:
|
|
|
617
617
|
for slide_no, slide in enumerate(prs.slides, 1):
|
|
618
618
|
content = []
|
|
619
619
|
table_count = chart_count = 0
|
|
620
|
-
#
|
|
621
|
-
|
|
622
|
-
top, floor = (0.45, title.top / 914400) if title is not None else (1.7, 6.7)
|
|
620
|
+
# Every treatment styles the one title zone, so the content area is the shared body zone under it.
|
|
621
|
+
top, floor = 120 / 72 - 0.03, 6.7
|
|
623
622
|
held = [box for shape in slide.shapes if (box := _bbox_in(shape)) is not None and (
|
|
624
623
|
getattr(shape, "has_table", False) or getattr(shape, "has_chart", False)
|
|
625
624
|
or shape.shape_type == MSO_SHAPE_TYPE.PICTURE or _shape_text(shape))]
|
|
@@ -733,7 +732,7 @@ def qa(pptx: Path, sibling: Path | None = None) -> dict[str, Any]:
|
|
|
733
732
|
composition = check_composition(pptx)
|
|
734
733
|
craft = analyse_craft(pptx)
|
|
735
734
|
if sibling is not None:
|
|
736
|
-
craft["findings"] +=
|
|
735
|
+
craft["findings"] += compare_placement(pptx, sibling)
|
|
737
736
|
craft["pass"] = not any(item["severity"] == "HIGH" for item in craft["findings"])
|
|
738
737
|
|
|
739
738
|
reasons: list[str] = []
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""render_pages.py - render a .pptx to a PDF and PNG pages so a person (or the model) can look at them.
|
|
3
|
+
|
|
4
|
+
LibreOffice (soffice) converts the deck to PDF in a private profile directory, so parallel runs
|
|
5
|
+
never share a lock. LibreOffice ignores the font parts a deck embeds, so the plain font files in
|
|
6
|
+
`ppt/fonts/*.fntdata` (what embed_fonts.py writes) are copied into that profile's font folder
|
|
7
|
+
first: the pages then show the faces PowerPoint shows, JetBrains Mono and Pretendard included,
|
|
8
|
+
even where the fonts are not installed. `pdftoppm` (poppler) rasterises the PDF when it is on
|
|
9
|
+
PATH; without it the PDF is the result. Without soffice nothing can be rendered: the script
|
|
10
|
+
prints SKIP with the reason and exits 0, and the caller must say the visual pass did not happen.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
python3 scripts/render_pages.py <deck.pptx> --out-dir DIR [--pages N] [--dpi 110] [--json]
|
|
14
|
+
|
|
15
|
+
Writes DIR/<stem>.pdf and DIR/page-01.png ... Exit 1 on a failed conversion.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import argparse
|
|
21
|
+
import json
|
|
22
|
+
import shutil
|
|
23
|
+
import subprocess
|
|
24
|
+
import sys
|
|
25
|
+
import tempfile
|
|
26
|
+
import zipfile
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def profile_fonts(source: Path, profile: Path) -> list[Path]:
|
|
31
|
+
"""Copy the plain font files a .pptx embeds (ppt/fonts/*.fntdata) into the profile's font folder."""
|
|
32
|
+
placed = []
|
|
33
|
+
try:
|
|
34
|
+
with zipfile.ZipFile(source) as package:
|
|
35
|
+
for name in package.namelist():
|
|
36
|
+
if not (name.startswith("ppt/fonts/") and name.endswith(".fntdata")):
|
|
37
|
+
continue
|
|
38
|
+
data = package.read(name)
|
|
39
|
+
# Only a TrueType or OpenType file; an obfuscated or EOT part is left to the renderer.
|
|
40
|
+
kind = ".otf" if data[:4] == b"OTTO" else ".ttf" if data[:4] in (b"\x00\x01\x00\x00", b"true") else None
|
|
41
|
+
if not kind:
|
|
42
|
+
continue
|
|
43
|
+
target = profile / "user" / "fonts" / (Path(name).stem + kind)
|
|
44
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
target.write_bytes(data)
|
|
46
|
+
placed.append(target)
|
|
47
|
+
except zipfile.BadZipFile:
|
|
48
|
+
return []
|
|
49
|
+
return placed
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def to_pdf(source: Path, out_dir: Path) -> tuple[Path | None, str]:
|
|
53
|
+
soffice = shutil.which("soffice") or shutil.which("libreoffice")
|
|
54
|
+
if not soffice:
|
|
55
|
+
return None, "LibreOffice (soffice) is not on PATH"
|
|
56
|
+
with tempfile.TemporaryDirectory(prefix="litopencode-soffice-") as profile:
|
|
57
|
+
profile_fonts(source, Path(profile))
|
|
58
|
+
result = subprocess.run(
|
|
59
|
+
[soffice, f"-env:UserInstallation={Path(profile).as_uri()}", "--headless", "--convert-to", "pdf",
|
|
60
|
+
"--outdir", str(out_dir), str(source)],
|
|
61
|
+
capture_output=True, text=True, timeout=300,
|
|
62
|
+
)
|
|
63
|
+
pdf = out_dir / f"{source.stem}.pdf"
|
|
64
|
+
if result.returncode != 0 or not pdf.exists():
|
|
65
|
+
raise RuntimeError(f"soffice conversion failed: {(result.stderr or result.stdout).strip()[-400:]}")
|
|
66
|
+
return pdf, "converted with soffice"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def rasterise(pdf: Path, out_dir: Path, pages: int, dpi: int) -> list[Path]:
|
|
70
|
+
pdftoppm = shutil.which("pdftoppm")
|
|
71
|
+
if not pdftoppm:
|
|
72
|
+
return []
|
|
73
|
+
subprocess.run([pdftoppm, "-png", "-r", str(dpi), "-l", str(pages), str(pdf), str(out_dir / "page")],
|
|
74
|
+
capture_output=True, text=True, timeout=300, check=True)
|
|
75
|
+
written = []
|
|
76
|
+
for png in sorted(out_dir.glob("page-*.png")):
|
|
77
|
+
# pdftoppm pads the page number to the page count's width; page-01.png is the stable name.
|
|
78
|
+
number = int(png.stem.split("-")[1])
|
|
79
|
+
target = out_dir / f"page-{number:02d}.png"
|
|
80
|
+
if png != target:
|
|
81
|
+
png.replace(target)
|
|
82
|
+
written.append(target)
|
|
83
|
+
return written
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def main(argv: list[str]) -> int:
|
|
87
|
+
parser = argparse.ArgumentParser(description="Render a deck to PDF and PNG pages.")
|
|
88
|
+
parser.add_argument("input", type=Path)
|
|
89
|
+
parser.add_argument("--out-dir", type=Path, required=True)
|
|
90
|
+
parser.add_argument("--pages", type=int, default=12)
|
|
91
|
+
parser.add_argument("--dpi", type=int, default=110)
|
|
92
|
+
parser.add_argument("--json", action="store_true")
|
|
93
|
+
args = parser.parse_args(argv)
|
|
94
|
+
|
|
95
|
+
source = args.input.expanduser().resolve()
|
|
96
|
+
out_dir = args.out_dir.expanduser().resolve()
|
|
97
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
try:
|
|
99
|
+
pdf, how = to_pdf(source, out_dir)
|
|
100
|
+
pngs = rasterise(pdf, out_dir, args.pages, args.dpi) if pdf else []
|
|
101
|
+
except (RuntimeError, subprocess.SubprocessError) as error:
|
|
102
|
+
report = {"status": "fail", "input": str(source), "detail": str(error), "pages": []}
|
|
103
|
+
print(json.dumps(report, ensure_ascii=False) if args.json else f"FAIL {error}")
|
|
104
|
+
return 1
|
|
105
|
+
status = "rendered" if pdf else "skipped"
|
|
106
|
+
detail = how if pngs or not pdf else f"{how}; pdftoppm is not on PATH, so only the PDF was written"
|
|
107
|
+
report = {"status": status, "input": str(source), "detail": detail, "pdf": str(pdf) if pdf else None, "pages": [str(p) for p in pngs]}
|
|
108
|
+
if args.json:
|
|
109
|
+
print(json.dumps(report, ensure_ascii=False, indent=2))
|
|
110
|
+
elif pdf:
|
|
111
|
+
print(f"PASS rendered {len(pngs)} page(s) -> {out_dir}" if pngs else f"PASS {detail}")
|
|
112
|
+
else:
|
|
113
|
+
print(f"SKIP {how}; visual pages were not rendered")
|
|
114
|
+
return 0
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
raise SystemExit(main(sys.argv[1:]))
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Bauhaus: primary colours and elementary geometry, kept to the display slides. The cover and the
|
|
2
|
+
# statements carry a composition of a square, a circle, a bar and a triangle; content slides stay
|
|
3
|
+
# quiet, on an off-white ground with black type and one red accent. Wrong for data-heavy business
|
|
4
|
+
# reviews, finance, public-sector reports and sombre topics.
|
|
5
|
+
id: bauhaus
|
|
6
|
+
name: Bauhaus
|
|
7
|
+
version: "1.0.0"
|
|
8
|
+
intent: "Design, architecture and culture lectures: primary colours and geometry on the display slides."
|
|
9
|
+
canvas: ["16:9", "4:3"]
|
|
10
|
+
|
|
11
|
+
# Pretendard by default; the A2Z map below applies only when the source asks (faces: a2z).
|
|
12
|
+
faces:
|
|
13
|
+
display: Pretendard Bold
|
|
14
|
+
title: Pretendard Bold
|
|
15
|
+
body: Pretendard Regular
|
|
16
|
+
label: Pretendard Bold
|
|
17
|
+
numeral: Pretendard Bold
|
|
18
|
+
faces-a2z:
|
|
19
|
+
display: A2Z Black
|
|
20
|
+
title: A2Z Black
|
|
21
|
+
body: Pretendard Regular
|
|
22
|
+
label: Pretendard Bold
|
|
23
|
+
numeral: A2Z Black
|
|
24
|
+
|
|
25
|
+
palette:
|
|
26
|
+
ground: "#F4F1EA"
|
|
27
|
+
surface: "#E8E3D6"
|
|
28
|
+
ink: "#141414"
|
|
29
|
+
ink-muted: "#4A4740"
|
|
30
|
+
line: "#141414"
|
|
31
|
+
accent: "#C8261C"
|
|
32
|
+
accent-deep: "#7A140E"
|
|
33
|
+
# The yellow of the geometry: the square on a display slide, the notice and a highlighted table row.
|
|
34
|
+
accent-tint: "#F2C230"
|
|
35
|
+
field: "#1F3C99"
|
|
36
|
+
on-field: "#FFFFFF"
|
|
37
|
+
positive: "#1E6B3A"
|
|
38
|
+
# The accent is red, so a loss keeps the ink colour and carries its ▼ sign instead.
|
|
39
|
+
negative: "#141414"
|
|
40
|
+
# Red, blue, black, then an ochre: the geometry's yellow darkened so a bar holds 3:1 on the ground.
|
|
41
|
+
series: ["#C8261C", "#1F3C99", "#141414", "#9E7700"]
|
|
42
|
+
|
|
43
|
+
ramp: reading
|
|
44
|
+
hero: true
|
|
45
|
+
type-voice:
|
|
46
|
+
title-tracking: -1
|
|
47
|
+
display-tracking: -2
|
|
48
|
+
|
|
49
|
+
density: 10
|
|
50
|
+
variance: 7
|
|
51
|
+
radius: 0
|
|
52
|
+
edge: fill
|
|
53
|
+
|
|
54
|
+
treatments: [top-rule, side-rail, kicker-numeral, statement, bottom-anchor]
|
|
55
|
+
role-defaults:
|
|
56
|
+
content: top-rule
|
|
57
|
+
data: top-rule
|
|
58
|
+
data-takeaway: side-rail
|
|
59
|
+
sequence: kicker-numeral
|
|
60
|
+
image: bottom-anchor
|
|
61
|
+
statement: statement
|
|
62
|
+
reference: top-rule
|
|
63
|
+
definition: side-rail
|
|
64
|
+
|
|
65
|
+
decoration: [accent-rule, numeral, colour-field]
|
|
66
|
+
layout-families: [asymmetric-feature, big-number, image-split, text-two-column, comparison, process, step-diagram, timeline, chart-insight, table-insight, kpi-row, quote, statement, agenda]
|
|
67
|
+
# Display slides: the device a type-led cover, a statement, a big number and a closing's next step
|
|
68
|
+
# are drawn with (see references/layout-families.md, "Display slides"). Geometry lives here only.
|
|
69
|
+
display:
|
|
70
|
+
cover: geometry
|
|
71
|
+
statement: geometry
|
|
72
|
+
number: field
|
|
73
|
+
closing: rules
|
|
74
|
+
covers: [cover-typographic, cover-split-field, cover-numeral]
|
|
75
|
+
sections: [section-field, section-numeral]
|
|
76
|
+
closings: [closing-ask, closing-summary-list, closing-statement]
|
|
77
|
+
|
|
78
|
+
table:
|
|
79
|
+
header: none
|
|
80
|
+
header-ink: ink
|
|
81
|
+
rules: header-rule
|
|
82
|
+
banding: none
|
|
83
|
+
totals: bold
|
|
84
|
+
chart:
|
|
85
|
+
gridlines: none
|
|
86
|
+
labels: direct
|
|
87
|
+
highlight: accent-on-muted
|
|
88
|
+
image:
|
|
89
|
+
frame: none
|
|
90
|
+
|
|
91
|
+
fill-order: [change-family, step-up, anchor-visual, distribute]
|
|
92
|
+
# The composition on a display slide sets red, blue and yellow beside one another; content slides keep one accent.
|
|
93
|
+
avoid:
|
|
94
|
+
- geometry on content slides (it belongs to the cover and the statements)
|
|
95
|
+
- a circle, square or triangle used as a bullet or an icon
|
|
96
|
+
- more than one accent colour on a content slide
|
|
97
|
+
- yellow text on the off-white ground
|
|
98
|
+
- this look for grief, safety or financial-loss topics
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Computational Brutalism: retro-futurist computing with its structure left in view. A monospaced
|
|
2
|
+
# Latin face for titles, labels and figures, the twelve-column grid marked in the top margin, ruled
|
|
3
|
+
# tables, a black terminal field with phosphor text and one electric-blue signal. Wrong for boards,
|
|
4
|
+
# finance or public-sector reports and for long Korean reading: the mono face tires over paragraphs.
|
|
5
|
+
id: computational-brutalism
|
|
6
|
+
name: Computational Brutalism
|
|
7
|
+
version: "1.0.0"
|
|
8
|
+
intent: "Engineering reviews and developer launches: the grid, the numbers and the system shown."
|
|
9
|
+
canvas: ["16:9", "4:3"]
|
|
10
|
+
|
|
11
|
+
# JetBrains Mono sets the Latin of titles, labels and figures; Hangul in those runs is set in
|
|
12
|
+
# Pretendard. Body text stays Pretendard. There is no A2Z map: the mono face is the look.
|
|
13
|
+
faces:
|
|
14
|
+
display: JetBrains Mono Bold
|
|
15
|
+
title: JetBrains Mono Bold
|
|
16
|
+
body: Pretendard Regular
|
|
17
|
+
label: JetBrains Mono Regular
|
|
18
|
+
numeral: JetBrains Mono Bold
|
|
19
|
+
|
|
20
|
+
palette:
|
|
21
|
+
ground: "#EEEDE7"
|
|
22
|
+
surface: "#E2E1D9"
|
|
23
|
+
ink: "#111111"
|
|
24
|
+
ink-muted: "#4D4C47"
|
|
25
|
+
line: "#9A988F"
|
|
26
|
+
accent: "#1F43E0"
|
|
27
|
+
accent-deep: "#14267A"
|
|
28
|
+
accent-tint: "#D9DEF2"
|
|
29
|
+
# The terminal: a black field with phosphor-green text.
|
|
30
|
+
field: "#111111"
|
|
31
|
+
on-field: "#8CF5A6"
|
|
32
|
+
positive: "#1C6B35"
|
|
33
|
+
negative: "#B3261E"
|
|
34
|
+
series: ["#1F43E0", "#111111", "#7A786F"]
|
|
35
|
+
|
|
36
|
+
ramp: reading
|
|
37
|
+
hero: true
|
|
38
|
+
type-voice:
|
|
39
|
+
title-tracking: 0
|
|
40
|
+
display-tracking: -1
|
|
41
|
+
|
|
42
|
+
density: 10
|
|
43
|
+
variance: 7
|
|
44
|
+
radius: 0
|
|
45
|
+
edge: border
|
|
46
|
+
edge-weight: 1
|
|
47
|
+
edge-colour: ink
|
|
48
|
+
|
|
49
|
+
treatments: [top-rule, top-plain-large, side-rail, bottom-anchor, statement]
|
|
50
|
+
role-defaults:
|
|
51
|
+
content: top-rule
|
|
52
|
+
data: top-rule
|
|
53
|
+
data-takeaway: bottom-anchor
|
|
54
|
+
sequence: top-rule
|
|
55
|
+
image: bottom-anchor
|
|
56
|
+
statement: statement
|
|
57
|
+
reference: top-rule
|
|
58
|
+
definition: side-rail
|
|
59
|
+
# A family's own title in this pack: a chart under a ruled title, a table read up to its title, a
|
|
60
|
+
# comparison set as a spec sheet beside its rail, a sequence under a large mono headline.
|
|
61
|
+
structure:
|
|
62
|
+
chart-insight: top-rule
|
|
63
|
+
table-insight: bottom-anchor
|
|
64
|
+
comparison: side-rail
|
|
65
|
+
step-diagram: top-plain-large
|
|
66
|
+
# The grid and its numbers are the decoration; nothing is filled for ornament.
|
|
67
|
+
decoration: [hairline-rule, column-hairlines, box-outline, grid-marks, numeral]
|
|
68
|
+
layout-families: [dashboard-grid, kpi-row, chart-insight, full-chart, table-insight, ledger-table, method, step-diagram, matrix-2x2, comparison, text-two-column, references-appendix, quote, statement]
|
|
69
|
+
# Display slides: the device a type-led cover, a statement, a big number and a closing's next step
|
|
70
|
+
# are drawn with (see references/layout-families.md, "Display slides").
|
|
71
|
+
display:
|
|
72
|
+
cover: drench
|
|
73
|
+
statement: drench
|
|
74
|
+
number: outline
|
|
75
|
+
closing: box
|
|
76
|
+
covers: [cover-typographic, cover-index, cover-figures]
|
|
77
|
+
sections: [section-numeral, section-rule]
|
|
78
|
+
closings: [closing-decision-box, closing-ask, closing-summary-list]
|
|
79
|
+
|
|
80
|
+
table:
|
|
81
|
+
header: none
|
|
82
|
+
header-ink: ink
|
|
83
|
+
rules: grid
|
|
84
|
+
banding: none
|
|
85
|
+
totals: bold
|
|
86
|
+
protagonist: accent-tint
|
|
87
|
+
chart:
|
|
88
|
+
gridlines: hairline
|
|
89
|
+
labels: direct
|
|
90
|
+
highlight: accent-on-muted
|
|
91
|
+
image:
|
|
92
|
+
frame: hairline
|
|
93
|
+
|
|
94
|
+
fill-order: [change-family, distribute, anchor-visual, step-up]
|
|
95
|
+
avoid:
|
|
96
|
+
- mono body paragraphs (body text stays Pretendard)
|
|
97
|
+
- the accent as decoration (it marks the protagonist number or series)
|
|
98
|
+
- rounded corners, shadows, gradients
|
|
99
|
+
- a green other than the phosphor text on the terminal field
|