diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0be37bb..8e21a39 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -96,6 +96,18 @@ jobs: go test ./... go run . -src ../../slides-src -out ../.. + # The translations pair up with the English pages: every page of + # content/en exists in each language of hugo.yaml, and a translation keeps + # the code, the shortcodes, the link targets and the headings' anchors the + # English page has. A missing page fails here, before the build. + - name: Check the translations + env: + GOWORK: "off" + working-directory: tools/i18n + run: | + go test ./... + go run . -root ../.. + # Each page's creation (first commit, followed across renames) and its # author, for the page history at its foot (params.page.history). - name: Page history @@ -112,6 +124,15 @@ jobs: hugo --gc --minify --panicOnWarning --printPathWarnings --baseURL "${DOCS_URL}/${{ steps.version.outputs.version }}/" + # In the built site, each translated page's headings have the ids of the + # English page's, so that a link into a page reaches the same place in + # every language. + - name: Check the translated anchors + env: + GOWORK: "off" + working-directory: tools/i18n + run: go run . -root ../.. -built ../../public + # Internal links and anchors, offline, with the site laid out under # /docs// as it is served. - name: Lay out the site for the link check diff --git a/README.md b/README.md index b79543a..107400a 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,10 @@ against that release (flags from its `slides -h`). | `assets/css/custom.css` | the brand indigo as Hextra's primary colour, with its contrast measured | | `themes/hextra` | submodule: tannevaled/hextra at tag v0.16.0 | | `branding` | submodule: [go-slides/brand](https://github.com/go-slides/brand) | +| `content//` | the translations (fr, de, es, zh, ko, ja), with the English file names; Chinese is Simplified (`zh-Hans`). Code, flags, directive names and file names stay as in English; each translated heading carries the English heading's `{#anchor}` | +| `tools/i18n` | a Go program (stdlib only, with tests): the CI fails when a page of `content/en` is missing in a language of `hugo.yaml` (or exists only in a translation), and warns when a translation changes code, shortcodes, link targets or anchors (`-strict` fails on those too); `-fix` adds the anchors, `-built public` compares the built site's heading ids with the English ones | +| `static/images/flags` | the language flags, lipis/flag-icons (MIT) | +| `layouts/baseof.html` | Hextra's, with `lang` taken from the language's `locale` (`zh-Hans`) | | `tools/gallery` | a Go program (stdlib only, with tests) that renders the gallery: it reads go-slides/slides' `examples/`, renders each deck's first slide with the slides command, and writes `data/gallery.json` and `static/gallery/` (both ignored by git) | | `layouts/_shortcodes/gallery.html` | the gallery page's cards, from `data/gallery.json` | | `scripts/publish-version.sh` | puts one build into a `gh-pages` checkout and rewrites `versions.json`, `latest` and the root redirect | diff --git a/assets/css/custom.css b/assets/css/custom.css index 9f15e9c..1cd35c1 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -29,6 +29,27 @@ * stays on screen at the end of the navbar. */ .fs-version-select .hextra-nav-menu-items { left: auto !important; right: 0; transform: none !important; } +/* The navbar's language switch: its flag at the size of the other icons. */ +.hextra-language-switcher[data-location="top"] img { width: 18px; height: 13.5px; } + +/* Chinese, Japanese and Korean: the system font stack (Hextra's own, which + * leaves the choice to the system) with the CJK system fonts named after + * system-ui, so that each language's glyph variants are found on macOS, iOS, + * Windows, Android and Linux. lang tells the browser which variants of the + * Han characters shared by the three languages to draw. */ +html:lang(zh-Hans) { + --hx-font-sans: ui-sans-serif, system-ui, "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Source Han Sans SC", "Microsoft YaHei", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji"; +} +html:lang(ja) { + --hx-font-sans: ui-sans-serif, system-ui, "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic", "Noto Sans CJK JP", "Source Han Sans JP", Meiryo, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji"; +} +html:lang(ko) { + --hx-font-sans: ui-sans-serif, system-ui, "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans CJK KR", "Source Han Sans KR", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji"; +} +/* Long unbroken runs (a heading, a table cell) wrap rather than overflow. */ +html:lang(zh-Hans) .content, html:lang(ja) .content, html:lang(ko) .content { overflow-wrap: break-word; } +html:lang(ja) .content, html:lang(zh-Hans) .content { line-break: strict; } + /* The gallery (layouts/_shortcodes/gallery.html): a card per deck. */ .gallery { display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr)); margin-top: 1.5rem; } .gallery-card { border: 1px solid #d1d5db; border-radius: 0.75rem; padding: 0.75rem; min-width: 0; } diff --git a/content/de/_index.md b/content/de/_index.md new file mode 100644 index 0000000..0cbd709 --- /dev/null +++ b/content/de/_index.md @@ -0,0 +1,60 @@ +--- +title: "go-slides-Dokumentation" +linkTitle: "Start" +type: docs +cascade: + type: docs +description: "Marp-kompatible Foliensätze in reinem Go (CGO=0), mit Go-Vorlagen zur Interpolation und Folientypen (Kinds)." +--- + +**`slides` macht aus einem Marp-Foliensatz eine Präsentation, in reinem Go.** Es liest +das Markdown, das auch [Marp](https://marp.app) liest – die Direktiven von Marpit, `---` zwischen +den Folien, `![bg]`-Hintergründe, Sprechernotizen, die Themes `default`, `gaia` und +`uncover` – und ergänzt zwei Dinge, die Marp nicht kennt: + +- **[Go-Vorlagen]({{< relref "templates" >}})** im Foliensatz, die ausgewertet werden, + bevor das Markdown geparst wird, sodass ein Foliensatz seine eigenen Folien erzeugen kann; +- **[Kinds (Folientypen)]({{< relref "kinds" >}})**: Eine Folie gibt an, was sie ist + (`title`, `section` …), statt immer wieder zu wiederholen, wie sie aussieht. + +{{< callout type="warning" >}} +**v0.9.0.** Diese Seiten beschreiben +[go-slides/slides v0.9.0](https://github.com/go-slides/slides/releases/tag/v0.9.0) +und sind unter `/docs/0.9/` veröffentlicht. +{{< /callout >}} + +## Was es heute kann {#what-it-does-today} + +| | | +| --- | --- | +| `slides build` (der Standard) | der Foliensatz als eine HTML-Seite: jede Folie ein `
` im SVG-Rahmen, der sich dem Fenster anpasst, dazu das CSS des Themes und ein Tastatur-Viewer | +| `slides build --pdf`, `--images png`, `--image jpeg` | der Foliensatz als PDF (eine Seite pro Folie, mit Gliederung) oder als PNG-/JPEG-Bilder, ohne Browser: [go-webengine](https://github.com/go-webengine) rendert, [go-pdfkit](https://github.com/go-pdfkit) schreibt das PDF – seit v0.4.0 als Vektoren, mit auswählbarem Text: [Vektor-PDF]({{< relref "cli#vector-pdf" >}}) | +| Mathematik und Code | `$…$` und `$$…$$` setzt go-tex/math, Codeblöcke hebt go-rouge hervor, in HTML, Bildern und PDF: [Mathematik und Code]({{< relref "math-and-code" >}}) | +| Emoji | `:smile:` und 😄 werden zu eingebetteten Twemoji-Bildern, brauchen also kein Netzwerk und erscheinen in jeder Ausgabe: [Emoji]({{< relref "emoji" >}}) (seit v0.4.0) | +| `slides build --pptx` | der Foliensatz als PowerPoint-Datei mit Sprechernotizen und den Metadaten des Foliensatzes: in v0.4.0 ein Bild pro Folie wie bei marp-cli; seit v0.5.0 **editierbar** – Textfelder, Formen, Bilder, eingebettete Schriften ([Editierbares PPTX und ODP]({{< relref "cli#editable-pptx-and-odp" >}})) | +| `slides build --odp` | dasselbe als OpenDocument-Präsentation (`.odp`) | +| einpassende Überschriften | `# Title` füllt die Folienbreite in einer Zeile, auf der Seite und in jedem Export | +| `slides expand` | der Foliensatz als reines Marp-Markdown: Vorlagen ausgewertet, Kinds durch die Spot-Direktiven ersetzt, für die sie stehen | +| `slides notes` | die Sprechernotizen | +| `slides kinds`, `slides themes` | was ein Foliensatz benennen kann | +| `slides.yaml` | gemeinsame Kinds, Themes, Daten, HTML-Richtlinie und Sprache für einen Ordner voller Foliensätze | +| HCL | `---hcl`-Front-Matter, `slides.hcl`, Kinds- und Datendateien in HCL2, mit Ausdrücken und Funktionen: [HCL]({{< relref "hcl" >}}) | + +## Geplant {#planned} + +Noch nicht gebaut, und hier aufgeführt, damit niemand raten muss: Formeln als Vektoren im +PDF (dort sind sie Bitmaps). Der Unterschied zu +marp-cli liegt im Weg: Es rendert Bilder und PDF, indem es einen Headless-Chrome steuert; +hier erledigen das Go-Bibliotheken. + +## Seiten {#pages} + +- [Erste Schritte]({{< relref "getting-started" >}}) +- [Galerie]({{< relref "gallery" >}}): zwölf Beispiel-Foliensätze, zum Öffnen im Playground +- [Marp-Kompatibilität]({{< relref "marp" >}}) +- [Vorlagen]({{< relref "templates" >}}) +- [Kinds]({{< relref "kinds" >}}) +- [HCL]({{< relref "hcl" >}}) +- [Mathematik und Code]({{< relref "math-and-code" >}}) +- [Emoji]({{< relref "emoji" >}}) +- [Kommandozeile]({{< relref "cli" >}}) diff --git a/content/de/cli.md b/content/de/cli.md new file mode 100644 index 0000000..1be3f1f --- /dev/null +++ b/content/de/cli.md @@ -0,0 +1,351 @@ +--- +title: "Kommandozeile" +weight: 50 +description: "Jeder Befehl und jedes Flag von slides sowie die Konfigurationsdatei slides.yaml." +tags: [cli] +--- + +{{< callout type="warning" >}} +Diese Seite beschreibt v0.9.0. +{{< /callout >}} + +```text +slides [deck.md] [flags] +slides [command] [flags] deck.md +``` + +## Befehle {#commands} + +| Befehl | Was er tut | +| --- | --- | +| `build` | schreibt die Präsentation als HTML oder als PDF oder Bilder (siehe unten). **Der Standard**: `slides deck.md` ist `slides build deck.md` | +| `expand` | gibt den Foliensatz als reines Marp-Markdown aus: Vorlagen expandiert, Kinds (Folientypen) durch die Direktiven ersetzt, für die sie stehen | +| `notes` | gibt die Sprechernotizen aus | +| `kinds` | listet die Kinds auf, die der Foliensatz verwenden kann, und was sie setzen; `--json` (in v0.6.0) schreibt sie als `[{"name", "extends", "directives": [{"key", "value"}]}]`, `extends` aufgelöst | +| `themes` | listet die Themes auf | +| `version` | gibt die Version aus (ebenso `slides --version`) | +| `completion` | gibt ein Shell-Vervollständigungsskript aus: `bash`, `zsh`, `fish` oder `powershell` | + +`slides --help` gibt die Flags eines Befehls aus. Die Flags dürfen vor +oder nach dem Foliensatz stehen: `slides deck.md -o out.html` und `slides -o out.html +deck.md` sind dasselbe. Lange Flags haben zwei Striche (`--html`); `-o` ist das einzige +kurze. `build` schreibt `deck.html` neben `deck.md`, sofern `-o` nichts anderes +angibt. + +## Flags {#flags} + +| Flag | Befehle | Was es tut | +| --- | --- | --- | +| `-o`, `--output file` | `build`, `expand`, `notes` | Ausgabedatei; `-` für die Standardausgabe | +| `-I`, `--input-dir dir` | `build` | wandelt die Markdown-Dateien unter `dir` rekursiv um (in v0.6.0); siehe unten | +| `-P`, `--parallel n` | `build` | gleichzeitig gebaute Foliensätze (in v0.6.0): standardmäßig 5, höchstens die Anzahl der Kerne; 1 baut sie nacheinander | +| `-w`, `--watch` | `build` | baut erneut, wenn sich ein Foliensatz, ein Theme, eine Kinds- oder Datendatei oder die Konfiguration ändert, und lädt die geöffneten Seiten neu (in v0.6.0); siehe unten | +| `-s`, `--server dir` | `build` | stellt ein Verzeichnis bereit und wandelt seine Foliensätze auf Anfrage um (in v0.6.0); siehe unten | +| `-p`, `--preview` | `build` | öffnet das Ergebnis im Standardbrowser (in v0.6.0): die HTML-Seiten mit `--watch` (impliziert), den Server mit `--server` | +| `--host addr` | `build` | die Adresse, auf der `--server` lauscht, standardmäßig `127.0.0.1` (in v0.6.0) | +| `--template bespoke\|bare` | `build` | die Vorlage der HTML-Seite (in v0.6.0): `bespoke`, der Standard, mit Präsentationsskript, Bedienelementen auf dem Bildschirm und Übergängen, oder `bare`, die Folien untereinander (`--bare`) | +| `--bespoke.osc`, `--bespoke.progress`, `--bespoke.transition` | `build` | die Optionen der bespoke-Vorlage, mit den Standardwerten von marp-cli (in v0.6.0): Bedienelemente an, **Fortschrittsbalken aus** (er wurde zuvor immer gezeichnet), Übergänge an; `--bespoke.osc=false` schaltet eines ab | +| `-c`, `--config-file file`, `--no-config-file` | `build` | die Konfigurationsdatei von marp-cli, JSON oder YAML (in v0.6.0); siehe unten. `--config` ist die eigene Datei von `slides` | +| `--browser`, `--browser-path`, `--browser-protocol`, `--browser-timeout` | `build` | werden akzeptiert und mit einer Warnung ignoriert (in v0.6.0): Es gibt keinen Browser | +| `--pptx-editable`, `--no-parallel` | `build` | die Schreibweisen von marp-cli für `--pptx` und `-P 1` (in v0.6.0) | +| `--config file` | alle | Konfigurationsdatei; standardmäßig `slides.yaml` neben dem Foliensatz, falls vorhanden | +| `--no-config` | alle | keine Konfigurationsdatei lesen | +| `--theme-set path` | alle | eine Theme-CSS-Datei oder ein Verzeichnis davon (wiederholbar) | +| `--kinds file` | alle | eine YAML-Datei mit Kinds (wiederholbar) | +| `--data file` | alle | eine YAML- oder JSON-Datei mit Vorlagendaten (wiederholbar) | +| `--set key=value` | alle | ein Vorlagenwert (wiederholbar) | +| `--no-template` | alle | Go-Vorlagen nicht expandieren | +| `--strict-yaml` | alle | Direktiven als striktes YAML parsen, wie es reines Marpit tut (Marp Core parst sie nachsichtig) | +| `--no-emoji` | alle | `:shortcodes:` und Unicode-Emoji als Text belassen, statt sie als Bilder zu zeichnen (seit v0.4.0: [Emoji]({{< relref "emoji" >}})) | +| `--html` | `build` | alles rohe HTML erlauben; standardmäßig die Allowlist von Marp Core | +| `--lang code` | `build` | das `lang` der Seite, wenn der Foliensatz keines setzt | +| `--bare` | `build` | die Folien untereinander stellen, ohne Präsentationsskript | +| `--pdf` | `build` | ein PDF schreiben, eine Seite pro Folie, mit Gliederung – Text und Formen als Vektoren (auch durch `-o file.pdf` gewählt) | +| `--pdf-raster` | `build` | das PDF schreiben, wobei jede Seite ein Bild der Folie ist, wie es v0.3.0 tat | +| `--notes` | `build` | die Sprechernotizen als Textdatei schreiben, Folien durch `---` getrennt wie bei marp-cli (in v0.6.0; auch durch `-o file.txt` gewählt). Der Befehl `notes` gibt sie in einem anderen Layout aus | +| `--pdf-notes` | `build` | die Sprechernotizen jeder Folie als Notizanmerkung auf ihre PDF-Seite setzen, wie es marp-cli tut (in v0.6.0). Verlangt ein PDF, wenn nichts anderes sagt, was geschrieben werden soll | +| `--pdf-outlines`, `--pdf-outlines.pages`, `--pdf-outlines.headings` | `build` | die Lesezeichen des PDFs (in v0.6.0): „Page N“ pro Folie und darunter verschachtelt die Überschriften. Hier standardmäßig an, anders als bei marp-cli; `=false` schaltet eines ab. Siehe unten | +| `--images png\|jpeg` | `build` | ein Bild pro Folie schreiben: `deck.001.png`, … | +| `--image png\|jpeg` | `build` | die erste Folie als Bild schreiben (auch durch `-o file.png` oder `-o file.jpg` gewählt) | +| `--pptx` | `build` | eine PowerPoint-Datei mit ihren Sprechernotizen schreiben (auch durch `-o file.pptx` gewählt): seit v0.5.0 editierbar, in v0.4.0 ein Bild pro Folie | +| `--pptx-raster` | `build` | eine PowerPoint-Datei mit einem ganzseitigen Bild pro Folie schreiben, wie es marp-cli tut (seit v0.5.0); `--image-scale` ist standardmäßig 2 | +| `--odp` | `build` | eine OpenDocument-Präsentation mit ihren Sprechernotizen schreiben (auch durch `-o file.odp` gewählt): seit v0.5.0 editierbar | +| `--odp-raster` | `build` | eine OpenDocument-Präsentation mit einem Bild pro Folie schreiben (seit v0.5.0) | +| `--jpeg-quality n` | `build` | JPEG-Qualität, 1 bis 100 (Standard 85) | +| `--image-scale n` | `build` | Geräte-Pixel pro CSS-Pixel für Bilder und PDF (Standard 1); 2 ergibt 2560×1440 bei 16:9, wobei Text und Vektoren in dieser Dichte gezeichnet und nicht vergrößert werden | +| `--allow-local-files` | `build` | Bildern und PDF das Lesen lokaler Dateien erlauben, nur unterhalb des Verzeichnisses des Foliensatzes; standardmäßig abgelehnt, wie bei marp-cli | +| `--theme name\|file.css` | `build` | das Theme des Foliensatzes überschreiben (in v0.6.0): ein Theme-Name oder eine CSS-Datei, die zum Theme wird, welches `@theme` sie auch deklariert; ein Verzeichnis wird abgelehnt, verwenden Sie `--theme-set` | +| `--title`, `--description`, `--author`, `--keywords`, `--url`, `--og-image` | `build` | die Metadaten der Seite (in v0.6.0), vor den Direktiven `title`, `description`, `author`, `keywords`, `url` und `image` des Foliensatzes; siehe unten | + +## Mehrere Foliensätze und ein Verzeichnis {#several-decks-and-a-directory} + +Ab v0.6.0 nimmt `build` mehrere Foliensätze, ein Verzeichnis (die darunter +rekursiv gefundenen Foliensätze) oder ein Muster entgegen und baut bis zu `--parallel` davon gleichzeitig. +`-` ist die Standardeingabe; anders als bei marp-cli wird eine Pipe nur gelesen, wenn `-` es verlangt. + +Ein Muster wird so gelesen, wie es der Glob von marp-cli liest (`**` und `{a,b}` in v0.7.0; +setzen Sie es in Anführungszeichen, damit die Shell es `slides` überlässt): `*`, `?`, `[a-z]` und +`{md,txt}` innerhalb eines Namens, `**` für beliebig viele Verzeichnisse, sodass +`'docs/**/*.md'` die Foliensätze des Baums erfasst. Ein Platzhalter erreicht keinen Namen, +der mit einem Punkt beginnt, in `node_modules` wird nicht gesucht, die Groß- und Kleinschreibung zählt +(`*.md` findet `UPPER.MD` nicht), und was immer ein Muster trifft, wird umgewandelt, +gleich welche Endung es hat. Ein Muster ohne Treffer ist ein Fehler. + +- **`-I dir`** findet die Markdown-Dateien (`.md`, `.mdown`, `.markdown`, + `.markdn`, in Kleinbuchstaben) unter `dir`, wie marp-cli es tut: `node_modules` und + Punkt-Verzeichnisse werden nicht durchsucht. Mit `-o out` wird jeder Foliensatz mit seinem + Pfad unter `dir` gespiegelt unter `out` geschrieben: `dir/sub/b.md` ergibt + `out/sub/b.html` (`b.pdf`, `b.001.png`, `b.txt`, … je nachdem, was die Flags sagen). + Ohne `-o` landen die Ausgaben neben ihren Foliensätzen. Foliensätze zusätzlich zu + `-I` zu nennen ist ein Fehler, ebenso `-o file` bei mehreren Foliensätzen (`-o -` ist erlaubt). +- **`-P n`** baut `n` Foliensätze gleichzeitig. Das Rendern eines einzelnen Foliensatzes nutzt schon die + Kerne, deshalb ist `n` durch deren Anzahl begrenzt; mit `-o -` werden Foliensätze nacheinander + gebaut. Die Meldungen eines Foliensatzes bleiben beisammen, und ein Foliensatz, der scheitert, hält die + anderen nicht auf: Der Exit-Status ist 1, und die Meldung listet die Fehlschläge auf. + +## Übergänge und die Präsentationsseite {#transitions-and-the-presenting-page} + +Ab v0.6.0 funktioniert die Direktive `transition` von marp-cli: ``, ein Name und eine optionale Dauer, für die Folie und die folgenden, +oder `` für eine einzelne. Die eingebauten Namen sind die von marp-cli: +`clockwise`, `counterclockwise`, `cover`, `coverflow`, `cube`, `cylinder`, +`diamond`, `drop`, `explode`, `fade`, `fade-out`, `fall`, `flip`, `glow`, +`implode`, `in-out`, `iris-in`, `iris-out`, `melt`, `overlap`, `pivot`, `pull`, +`push`, `reveal`, `rotate`, `slide`, `star`, `swap`, `swipe`, `swoosh`, `wipe`, +`wiper` und `zoom`; `none` schaltet ihn ab. Die Seite spielt ihn mit der +**View Transition API** des Browsers ab, wenn der Betrachter die Folie, die ihn hat, vorwärts verlässt, +und wenn er zu ihr zurückkehrt; Browser ohne die API wechseln die Folien wie zuvor, +und ein Betrachter, der reduzierte Bewegung verlangt, erhält eine einfache Überblendung. Ein Theme kann +eigene Keyframes mitbringen, benannt `marp-transition-NAME` (oder `marp-outgoing-` und +`marp-incoming-` sowie `-backward`), die vor den eingebauten verwendet werden. + +Die Seite hat die **Bedienelemente auf dem Bildschirm** – zurück, Seite, weiter, Vollbild –, +die erscheinen, wenn sich der Mauszeiger oder eine Taste bewegt, und nach einem Moment verschwinden +(`--bespoke.osc=false` entfernt sie), und mit `--bespoke.progress` einen Fortschrittsbalken. +**Der Fortschrittsbalken ist jetzt standardmäßig aus**, wie bei marp-cli; zuvor wurde er immer +gezeichnet. + +### Referentenansicht {#presenter-view} + +Ab v0.7.0 hat die Seite die Referentenansicht von marp-cli. Drücken Sie `p` oder die letzte +Schaltfläche der Bedienelemente, und ein Fenster öffnet sich mit der aktuellen Folie, dem Schritt +danach, Ihren Sprechernotizen, der Seite, der Uhrzeit und einem Timer (ein Klick auf den Timer +setzt ihn zurück; `+` und `-` ändern die Größe der Notizen; die Leiste zwischen den Folien und den +Notizen lässt sich ziehen). Die beiden Fenster folgen einander in beide Richtungen: die +Pfeiltasten, ein Klick auf den nächsten Schritt oder auf Previous/Next in einem von beiden bewegt das +andere, jeweils ein Fragment auf einmal. + +Sie funktioniert wie die von marp-cli, gelesen aus dessen Quellen der Version 4.5.1: Das Fenster ist +`?view=presenter&sync=KEY` derselben Datei, der nächste Schritt ist dieselbe Seite als +`?view=next` in einem Frame, und die Fenster teilen ihre Position über `localStorage` (`bespoke-marp-sync-KEY`), +sodass sie weder Server noch Netzwerk braucht und in einem Browser ohne +`localStorage` nicht funktioniert (die Schaltfläche sagt es). Die Notizen sind Text: `&`, `<` und `>` werden maskiert, sodass eine Notiz +nie Markup sein kann. Sie stehen versteckt in der Seite, damit die Referentenansicht sie übernehmen kann, was sie +aus dem heraushält, was ein Screenreader dem Publikum vorliest. `--bare`-Seiten haben nichts davon. +Die Ansicht fügt einer Seite etwa 10 KB hinzu. + +### Übersichtsansicht {#overview-view} + +Ab v0.8.0 zeigt sich jede Folie ganz in einem Raster über der Seite, wenn Sie `o` drücken (oder `Escape`: marp-cli schaltet mit +beiden um), die Rasterschaltfläche der Bedienelemente oder den Seitenzähler der Referentenansicht. +Die Pfeiltasten wählen eine Folie (aufwärts und abwärts zeilenweise), Pos1 und Ende (`Home` und `End`) springen zu den Enden, +und die anderen Fenster folgen; `Enter`, `Space` oder ein Klick wählt eine Folie und schließt, über der Seite, die +Übersicht. Über der Referentenansicht lässt eine Wahl sie geöffnet (der Referent wählt +die zu zeigende Folie und schaut weiter). Die Schaltfläche „Close overview“, `o` und +`Escape` schließen sie. Sie ist die von marp-cli: die Seite noch einmal in einem Frame unter +`?view=overview&sync=KEY`, die Position über `localStorage` teilend, ohne +Netzwerk. Die Folien im Raster sind fokussierbar und mit „Slide N“ beschriftet, und die +geschlossene Übersicht ist inert, sodass Tastatur- und Screenreader-Nutzer nie darin landen. +Die beiden Ansichten zusammen fügen einer Seite etwa 15 KB hinzu. + +### Konfigurationsdateien {#configuration-files} + +marp-cli liest seine Optionen aus einer Datei im Arbeitsverzeichnis, und `slides` tut es auch, +sofern die Datei aus Daten besteht: `.marprc` (YAML, JSON ist auch YAML), +`.marprc.json`, `.marprc.yaml`, `.marprc.yml`, `.config/marprc*` oder der +Schlüssel `marp` in `package.json`; `--config-file` (`-c`) benennt jede JSON- oder YAML- +Datei, `marp.config.json` eine solche. Durchsucht wird nur das Arbeitsverzeichnis, nicht +seine übergeordneten, wie bei der Suche von marp-cli. Eine JavaScript- oder TypeScript-Datei +(`marp.config.js` ...) ist ein Programm, das `slides` nicht ausführt: Es sagt das und +macht weiter. Die Optionen sind die aus der Dokumentation von marp-cli, die hier etwas +bedeuten: `title`, `description`, `author`, `keywords`, `url`, `ogImage`, `lang`, +`theme`, `themeSet`, `html`, `allowLocalFiles`, `template`, `bespoke`, +`inputDir`, `output`, `parallel`, `pdf`, `pptx`, `notes`, `pdfNotes`, +`pdfOutlines`, `images`, `image`, `imageScale`, `jpegQuality`, `watch`, +`server`, `preview`; Pfade sind relativ zur Datei. Die Kommandozeile hat Vorrang. +`engine`, `options` und `browser*` brauchen die Engine von Marp und einen Browser und werden +mit einer Warnung ignoriert, ebenso wie die Flags `--browser*`: Für marp-cli geschriebene Skripte +funktionieren weiter. (`slides.yaml` und `slides.hcl`, mit den Kinds und den +Vorlagendaten, sind getrennt und werden wie bisher gelesen.) + +## Beobachten, bereitstellen und Vorschau {#watch-serve-and-preview} + +Ab v0.6.0, mit den Routen und dem Live-Reload-Protokoll von marp-cli (gelesen aus dessen +Quellen der Version 4.5.1 und verglichen mit einem laufenden `marp -s`). + +**`slides --server dir`** (`-s`) stellt das Verzeichnis unter `http://127.0.0.1:8080/` bereit +(die Umgebungsvariable `PORT` ändert den Port; ein bereits belegter Port ist ein +Fehler, der das sagt): + +| Anfrage | Antwort | +| --- | --- | +| `/deck.md` | der Foliensatz als HTML, auf Anfrage umgewandelt | +| `/deck.md?pdf`, `?pptx`, `?png`, `?jpg` (oder `?jpeg`), `?notes` (oder `?txt`) | der Foliensatz, umgewandelt wie in der Abfrage verlangt; das PPTX ist ein Anhang | +| ein Verzeichnis | sein `index.md` oder `PITCHME.md` als Seite, sonst ein Index mit einem PDF- und einem PPTX-Link für jeden Foliensatz | +| jede andere Datei | die Datei | +| ein Foliensatz, der sich nicht umwandeln lässt | 503 mit dem Fehler | + +Eine Seite öffnet einen WebSocket zu `/.__marp-cli-watch-notifier__/`, wobei die ID +der SHA-256 des Pfades des Foliensatzes ist; der Server sendet `ready`, dann `reload`, wenn sich +der Foliensatz oder irgendetwas unter dem Verzeichnis ändert. Ein Verzeichnis, das ohne +den abschließenden Schrägstrich angefordert wird, wird dorthin umgeleitet (marp-cli liefert seinen Index dort aus, +was die relativen Links der Seite zerbricht). + +**`--watch`** (`-w`) baut die Foliensätze und baut dann den, der sich ändert, erneut +und, wenn sich eine `--theme-set`-Datei, eine Kinds- oder Datendatei oder die Konfiguration +ändert, alle. Die Seiten, die es schreibt, öffnen einen Socket auf dem Loopback (Port +37717 oder der nächste freie) und laden neu, sobald ihr Foliensatz erneut gebaut wird. Ein Foliensatz, +der nicht gebaut werden kann, wird gemeldet und weiter beobachtet. **`--preview`** (`-p`) öffnet +den Standardbrowser auf dem Server oder auf den soeben gebauten HTML-Seiten; es gibt kein +eigenes Fenster, da `slides` keinen Browser steuern kann. + +Der Server ist ein Zugang zur Maschine und deshalb strenger als der von marp-cli, der +auf jeder Schnittstelle lauscht und nichts prüft: + +- er lauscht auf **127.0.0.1**; `--host` ändert das mit einer Warnung, und eine + Wildcard-Adresse kann den `Host`-Header nicht mehr prüfen; +- der **`Host`**-Header muss ein Loopback-Name sein (`localhost`, `127.0.0.1`, + `[::1]`, `*.localhost`) oder der Host, auf dem er lauscht, was DNS-Rebinding unterbindet; +- der **`Origin`** eines WebSockets muss der des Servers selbst sein; die Sockets des Watch-Modus + akzeptieren auch den `null`-Origin einer aus einer Datei geöffneten Seite; eine Anfrage von + einer anderen Website, die keine Navigation ist (ein Skript, ein Bild, ein `fetch`), wird + abgelehnt; +- ein Pfad wird bereinigt, seine symbolischen Links werden aufgelöst, und das Ergebnis muss + im Verzeichnis liegen: `..` und ein Link hinaus werden abgelehnt, ebenso + `?pdf` eines Foliensatzes außerhalb; Namen, die mit einem Punkt beginnen (`.git`, `.env`), werden nicht + ausgeliefert; +- nur `GET` und `HEAD`; wenige Umwandlungen gleichzeitig; höchstens 4096 Verzeichnisse + beobachtet, und keines in `node_modules` oder unter einem Punkt. + +## Seitenmetadaten {#page-metadata} + +Die Kommandozeile hat Vorrang vor dem Foliensatz, wie bei marp-cli, wo die Flags +als letzter Direktivenkommentar an den Foliensatz angehängt werden. Die Regeln sind aus marp-cli +4.5.1 gelesen und durch Tests gegen dessen Ausgabe festgelegt (in v0.6.0): + +- Ein Foliensatz ohne `title` übernimmt den Text seiner ersten Überschrift der niedrigsten + Ebene, wie geschrieben (`# **Bold** title` ergibt `**Bold** title`). Ohne + Überschrift gibt es kein ``. +- `keywords` werden an Kommas getrennt, getrimmt und dedupliziert; eine Liste im Front-Matter + wird so übernommen, wie sie ist. Wie bei marp-cli trägt die Seite das Tag `keywords` ab + dem zweiten Schlüsselwort, nicht bei nur einem. +- Eine ungültige `url` (nicht absolut) wird mit einer Warnung ignoriert, und der vorherige + Wert bleibt; eine gültige schreibt außerdem `<link rel="canonical">` und `og:url`. +- `author` und `description` kommen mit ihren Open-Graph-Gegenstücken; `og:image:alt` + und die große Twitter-Karte brauchen sowohl einen Titel als auch ein Bild. + +## Bilder, PDF, PPTX und ODP {#images-pdf-pptx-and-odp} + +`-o` mit der Endung `.pdf`, `.pptx`, `.odp`, `.png` oder `.jpg` wählt das Format, wie bei marp-cli. +Ausgabeverzeichnisse werden angelegt. Ein PDF hat eine Seite pro Folie, in der +Größe der Folie, und eine Gliederung der Folien und ihrer Überschriften. + +### PDF-Notizen und Gliederung {#pdf-notes-and-outline} + +Ab v0.6.0 übernimmt das PDF die beiden Optionen von marp-cli, und sie werden mit +den PDFs verglichen, die marp-cli 4.5.1 für denselben Foliensatz schreibt: + +- `--pdf-notes` setzt die Sprechernotizen jeder Folie als Notizanmerkung auf ihre Seite: + ein gelbes Notiz-Symbol unten links, dessen Inhalt die + Notizen sind (mehrere Kommentare durch eine Leerzeile verbunden) und dessen Titel der `author` + des Foliensatzes ist. `--pdf-notes` allein verlangt ein PDF, wie bei marp-cli. +- Die Gliederung enthält ein fettes Lesezeichen „Page N“ pro Folie und darunter die + Überschriften der Folie nach Ebene verschachtelt (`--pdf-outlines.pages` und + `--pdf-outlines.headings` wählen die beiden). **Die Gliederung ist hier standardmäßig an und + bei marp-cli aus.** `slides` hat schon immer eine geschrieben; sie kostet ein paar + hundert Byte und macht einen langen Foliensatz navigierbar, also bleibt der Standard. + `--pdf-outlines=false` oder `--no-pdf-outlines` von marp-cli schaltet sie ab, und + `=false` bei `.pages` oder `.headings` lässt diesen Teil weg. Ein Überschriften-Lesezeichen springt zu + seiner Folie; marp-cli scrollt außerdem zur Überschrift. + +Das Rendern übernimmt [go-webengine](https://github.com/go-webengine/engine), +und das PDF schreibt [go-pdfkit](https://github.com/go-pdfkit/pdfkit); +die Schriften, die die eingebauten Themes verlangen (Lato, Roboto Mono), stammen aus +[go-opentype/fonts](https://github.com/go-opentype/fonts) und sind eingebettet, sodass +kein Rendern das Netzwerk berührt. Es gibt keinen Chrome. + +`--image-scale 2` rendert mit zwei Geräte-Pixeln pro CSS-Pixel. Lokale Bilder werden +abgelehnt, solange `--allow-local-files` nicht angegeben ist, und selbst dann werden nur Dateien unter +dem Verzeichnis des Foliensatzes gelesen: Ein Foliensatz von anderswo kann `~/.ssh` nicht erreichen. +Mehrere und geteilte Hintergründe (`![bg left:40%]`) werden gerendert, wie Marp ihre Ebenen rendert. + +`--pptx` schreibt den Foliensatz mit `WritePresentation` von [go-ooxml](https://github.com/go-ooxml), +mit den Sprechernotizen und den Metadaten des Foliensatzes. Seit +v0.5.0 sind die Folien editierbar (unten); `--pptx-raster` schreibt ein ganzseitiges +Bild pro Folie, wie marp-cli es tut, mit einem Maßstab, der standardmäßig 2 ist und den +`--image-scale` überschreibt. LibreOffice öffnet das Ergebnis. Getestet wurde nur mit LibreOffice; +PowerPoint und Keynote wurden nicht getestet. + +`--odp` schreibt eine OpenDocument-Präsentation über `WritePresentation` von go-odf, mit +ihren Sprechernotizen; `--odp-raster` schreibt ein Bild pro Folie. + +### Editierbares PPTX und ODP {#editable-pptx-and-odp} + +Seit v0.5.0 sind die Folien eines PPTX oder ODP **editierbar** – das PPTX von marp-cli +ist ein Bild pro Folie, und sein experimentelles `--pptx-editable` wandelt ein PDF +über LibreOffice um. Hier wird jede Folie aus ihrem Layout gezeichnet, demselben, +das die PNG- und PDF-Exporte zeichnen: + +- jede Textzeile ist ein Textfeld an ihrer berechneten Position, in den Schriften, + mit denen die Folie gesetzt wurde – **in die Datei eingebettet**, sodass sie auf einem Rechner ohne Lato oder Inter + gleich aussieht (geprüft mit LibreOffice 25.8) – + mit Fett, Kursiv, Farben, Buchstabenabstand und anklickbaren Links; +- Hintergründe, Rahmen und abgerundete Kästen sind Formen; Verläufe sind Verlaufs- + füllungen im PPTX und Bilder im ODP (ein ODF-Verlauf hat zwei + Farben); +- Bilder werden nur einmal gespeichert, egal wie viele Folien sie zeigen; +- was eine Folie nicht ausdrücken kann – ein Element unter `opacity`, ein `filter`, ein + weichgezeichneter Schatten, eine Formel – ist ein Bild dieses Elements, mit seinem darüberliegenden Text. + +Von LibreOffice zurückgerendert, liegt eine Folie 0,04–2 % von den Pixeln des +PNG-Exports entfernt. `--pptx-raster` und `--odp-raster` behalten ein Bild pro Folie. + +### Vektor-PDF {#vector-pdf} + +Seit v0.4.0 wird das PDF als Vektoren gezeichnet: go-webengine malt die berechnete +Folie in das PDF statt in Pixel. Der Text ist Text, in den Schriften, mit denen die +Folie gesetzt wurde, als Teilmenge eingebettet mit einer `/ToUnicode`-Zuordnung – er lässt sich +auswählen, durchsuchen und kopieren. Hintergründe, Rahmen und Verläufe sind Pfade +und Schattierungen, ein Bild, das auf vielen Folien erscheint, wird einmal eingebettet, und Links sind +anklickbar. Der Benchmark-Foliensatz mit 100 Folien ist 758 KiB groß, gegenüber 837 KiB bei +marp-cli und 10 MiB beim Bild-PDF von v0.3.0, und er ist in 0,3 s geschrieben. + +Ein Element ohne Vektorform – unter `opacity` < 1, mit einem `filter` +oder einer Drehung, ein weichgezeichneter `box-shadow`, ein Verlauf mit ausblendender Deckkraft – +wird als Bitmap nur dieses Elements gezeichnet, mit unsichtbar darübergelegtem Text, damit er +auswählbar bleibt. Formeln sind weiterhin Bitmaps. `--pdf-raster` schreibt ein Bild +pro Seite, wie es v0.3.0 tat. + +## `slides.yaml` {#slidesyaml} + +Eine Konfigurationsdatei neben dem Foliensatz gilt für ihn, sodass sich ein Ordner voller Foliensätze +eine teilt: + +```yaml +kinds: kinds.yaml # a mapping of kinds, or file(s) holding one +themes: # theme CSS files or directories + - themes/ +data: # template data + company: Example Inc. +html: false # allow all raw HTML +lang: en +``` + +Kinds: Das eigene `kinds:` des Foliensatzes überschreibt das der Datei und das der Kommandozeile. +Daten: Das `data` der Datei und der Kommandozeile überschreibt das Front-Matter des Foliensatzes, +sodass ein Foliensatz mit verschiedenen Daten gebaut werden kann. + +## Geplant {#planned} + +Noch nicht gebaut: Formeln als Vektoren im PDF. Sobald sie kommen, sind sie hier ein +Befehl oder Flag. diff --git a/content/de/emoji.md b/content/de/emoji.md new file mode 100644 index 0000000..6fc66b4 --- /dev/null +++ b/content/de/emoji.md @@ -0,0 +1,80 @@ +--- +title: "Emoji" +weight: 36 +description: "Emoji-Shortcodes und Unicode-Emoji werden zu Twemoji-Bildern, ohne Netzwerk: auf der HTML-Seite, in Bildern, PDF, PowerPoint und OpenDocument." +tags: [emoji] +--- + +{{< callout type="warning" >}} +Emoji gibt es ab v0.4.0; v0.3.0 hatte keine. +{{< /callout >}} + +Schreiben Sie ein Emoji als Shortcode oder als das Zeichen selbst: + +```markdown +# Launch :rocket: 🎉 + +Shortcodes :smile: :+1: :heart_eyes: and Unicode 😄 👍🏽 👩‍💻 🇫🇷 1️⃣. +``` + +Jedes wird zu dem [Twemoji](https://github.com/jdecked/twemoji)-Bild, das Marp Core +zeichnet, mit dem Markup, das Marp Core schreibt: + +```html +<img class="emoji" draggable="false" alt="😄" src="data:image/svg+xml;base64,…" data-marp-twemoji=""/> +``` + +## Kein Netzwerk {#no-network} + +Das `src` von Marp Core ist eine URL auf einem CDN. Ein Foliensatz, der offline gezeigt, gedruckt +oder auf einem Rechner ohne Verbindung in ein PDF umgewandelt wird, zeigt deshalb kaputte Bilder. +go-slides legt das SVG selbst in einen `data:`-URI. Die Grafiken (4.009 Stück, +Twemoji 17.0.3, Emoji 17.0) sind aus dem reinen Go-Modul +[go-icons/twemoji](https://github.com/go-icons/twemoji) in das Programm eingebettet, 2 MB komprimiert, +und werden nur gelesen, wenn ein Foliensatz ein Emoji enthält. + +Die Bilder zeichnen die HTML-Seite, `--images`/`--image`, `--pdf`, `--pptx` +und `--odp` gleichermaßen: go-webengine rastert das SVG. Alle 4.009 +Grafiken lassen sich mit seinem SVG-Rasterizer parsen und zeichnen; ein Test prüft das. Seit +v0.5.0 ist dieser Rasterizer [go-gfx/oksvg](https://github.com/go-gfx/oksvg) +v0.2.0: `clipPath`, `fill-rule: evenodd` und Bogenbefehle werden jetzt so gezeichnet, wie +Chrome sie zeichnet, was ein Twemoji und 211 weitere verändert hat; SVG-Logos +sind nicht mehr verzerrt. + +Die Regel, die ein Emoji an den Text anpasst (`img[data-marp-twemoji]`, ein `em` +im Quadrat, an der Grundlinie ausgerichtet), wird dem Theme so hinzugefügt, wie Marp Core sie hinzufügt, +sodass auch ein eigenes Theme sie erhält. + +## Was umgewandelt wird {#what-is-converted} + +Die Regeln sind die von Marp Core; ein Test vergleicht sie mit dem HTML, das Marp Core 4.4.0 +für einen Foliensatz mit allen Fällen schreibt. + +| | | +| --- | --- | +| `:smile:`, `:+1:`, `:heart_eyes:` | die 1.903 Namen von markdown-it-emoji, in Text, Überschriften, Links, Tabellen, `header` und `footer`; nicht in Code, URLs oder im Alt-Text eines Bildes | +| `😄`, `👩‍💻`, `🇫🇷`, `1️⃣`, `👍🏽` | Unicode-Emoji, im Text **und im Code**: Marp Core schreibt das gerenderte Code-HTML um | +| `✔`, `©`, `™`, `▶` | bleiben Text: Zeichen, deren Standarddarstellung Text ist, sind nur mit U+FE0F ein Emoji (`✔️`); `❤` ist die Ausnahme | +| `☺︎` | bleibt Text: Ein U+FE0E hinter einem Emoji verlangt die schlichte Glyphe | +| `12:30:45`, `:not_a_name:` | bleiben unverändert | + +Die `id` einer Überschrift und die Gliederung verwenden das Emoji, nicht den Namen des Shortcodes: +`# Launch :rocket:` hat die ID `launch-%F0%9F%9A%80`, wie in Marp. + +Wo go-slides von Marp Core abweicht, wandelt es mehr um: `✌️ ☝️ ✍️ 🏋️` und einige +weitere Zeichen mit Text als Standarddarstellung, die der emoji-regex von Marp Core als Text belässt, +sowie die wenigen Sequenzen, die Twemoji zeichnet, Unicode aber nicht auflistet. + +## Abschalten {#turning-it-off} + +`--no-emoji` (im Code `slides.Config{NoEmoji: true}`) belässt Shortcodes und +Unicode-Emoji als den Text, als der sie geschrieben wurden; das entspricht +`emoji: false` von Marp Core. + +## Quellenangabe {#attribution} + +Die Grafiken stammen von Twemoji, Copyright 2020 Twitter, Inc. und weitere Mitwirkende, +lizenziert unter [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) und +weitergeführt vom Projekt [jdecked/twemoji](https://github.com/jdecked/twemoji). +Ein Foliensatz, der sie zeigt, sollte sie nennen, etwa in einer Fußzeile oder auf einer +Schlussfolie. Die Shortcode-Tabelle stammt von markdown-it-emoji (MIT). diff --git a/content/de/gallery.md b/content/de/gallery.md new file mode 100644 index 0000000..a5a328e --- /dev/null +++ b/content/de/gallery.md @@ -0,0 +1,24 @@ +--- +title: "Galerie" +weight: 12 +description: "Zwölf kleine Foliensätze, die jeweils eine Sache gut zeigen: Themes, Kinds, Vorlagen, HCL, Mathematik, Bilder, Code, Emoji, Übergänge und ein vollständiger Vortrag. Jeden davon können Sie im Playground öffnen." +tags: [gallery, examples] +--- + +Zwölf kleine Foliensätze aus dem Ordner +[`examples/`](https://github.com/go-slides/slides/tree/main/examples) von +go-slides, die jeweils eine Sache gut zeigen. Die Vorschaubilder zeigen die erste Folie +jedes Foliensatzes, von go-slides selbst beim Bau dieser Website gerendert; die CI baut jeden +Foliensatz zu HTML und PDF, damit keiner kaputtgeht. + +**Open in the playground** lädt den Foliensatz in Ihrem Browser, über einen Link, der +nichts an einen Server sendet. Ein Foliensatz, der neben `deck.md` weitere Dateien hat (eine Kinds-Datei, +eingebundene Teile, Bilder), passt nicht in einen Link: Laden Sie sein **.zip** herunter +und importieren Sie es über das Dateifenster des Playgrounds, oder entpacken Sie es und bauen Sie es mit +`slides build --allow-local-files deck.md`. + +Die Bilder sind Original-SVGs der go-slides-Autoren, freigegeben unter +[CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/); die Foliensätze, +die sie verwenden, weisen darauf hin. + +{{< gallery >}} diff --git a/content/de/getting-started.md b/content/de/getting-started.md new file mode 100644 index 0000000..ab33347 --- /dev/null +++ b/content/de/getting-started.md @@ -0,0 +1,126 @@ +--- +title: "Erste Schritte" +weight: 10 +description: "Ein erster Foliensatz, zu HTML gebaut und zurück zu Marp-Markdown expandiert." +tags: [start] +--- + +## Installation {#install} + +Go 1.27.2 oder neuer: + +```sh +go install github.com/go-slides/slides/cmd/slides@latest +``` + +`slides` ist ein einzelnes statisches Go-Binary ohne cgo: Sie müssen nichts weiter installieren, kein +Node, kein Chrome. Diese Seite beschreibt v0.9.0. + +## Ein erster Foliensatz {#a-first-deck} + +Jeder Marp-Foliensatz ist einer. Speichern Sie Folgendes als `talk.md`: + +```markdown +--- +marp: true +theme: default +paginate: true +footer: "{{ .talk }}" +data: + talk: Pure Go slides + topics: [Why, How, Next] +--- + +<!-- _kind: title --> +# {{ .talk }} + +{{ range .topics }} +--- + +# {{ . }} +{{ end }} +``` + +Das Front-Matter ist das von Marp, ergänzt um `data:`. Der Rumpf ist eine Go-Vorlage: Das +`range` schreibt pro Thema eine Folie, jeweils mit `---` eingeleitet, und die erste +Folie hat das eingebaute Kind (Folientyp) `title` (ohne Kopfzeile, ohne Fußzeile, ohne Seitenzahl). + +## Bauen {#build-it} + +```sh +slides talk.md # writes talk.html beside the deck +slides -o slides.html talk.md # or somewhere else; -o - for standard output +``` + +`slides` ohne Befehl ist `slides build`. Die Flags dürfen vor oder nach dem +Foliensatz stehen. Öffnen Sie das HTML in einem Browser; mit der Tastatur wechseln Sie zwischen den Folien. + +## Bilder, PDF, PPTX und ODP exportieren {#export-images-pdf-pptx-and-odp} + +```sh +slides talk.md --pdf # one page per slide, with an outline +slides talk.md --images png # talk.001.png, talk.002.png, … +slides talk.md --image jpeg # the first slide only +slides talk.md -o out/slides.pdf # the extension picks the format; out/ is created +slides talk.md --images jpeg --jpeg-quality 90 +slides talk.md --images png --image-scale 2 # 2560×1440 for 16:9 +slides talk.md --pdf --allow-local-files # images from under the deck's directory +slides talk.md --pptx # or -o talk.pptx, with notes +slides talk.md --odp # or -o talk.odp: the same, as OpenDocument +``` + +Es ist kein Browser beteiligt: go-webengine rendert die Folien, und go-pdfkit schreibt +das PDF. Lokale Bilder werden abgelehnt, solange `--allow-local-files` nicht angegeben ist. +Seit v0.5.0 sind die Folien eines PPTX oder ODP editierbar – Textfelder, Formen +und Bilder, eingebettete Schriften –, während sie bei marp-cli Bilder sind; `--pptx-raster` +behält Bilder bei ([Editierbares PPTX und ODP]({{< relref "cli#editable-pptx-and-odp" >}})). Seit v0.4.0 besteht das PDF aus Vektoren, sein Text ist +auswählbar (v0.3.0 schrieb ein Bild pro Seite). Siehe [Kommandozeile]({{< relref "cli#vector-pdf" >}}). + +## Expandieren {#expand-it} + +```sh +slides expand talk.md +``` + +gibt denselben Foliensatz als **reines Marp-Markdown** aus (Ausgabe von `main` bei +`4ae5c49`, 2026-10-18): + +```markdown +--- +marp: true +theme: default +paginate: true +footer: "Pure Go slides" +--- +<!-- +_class: "lead" +_footer: "" +_paginate: "false" +--> + + +# Pure Go slides + + +--- + +# Why + +--- + +# How + +--- + +# Next +``` + +Die vier Folien, die das `range` erzeugt hat, die Fußzeile mit ihrem Wert und das +Kind `title`, ersetzt durch die Spot-Direktiven, für die es steht. Alles, was Marp liest – marp-cli, die VS- +Code-Erweiterung –, kann damit weiterarbeiten; nichts, was Sie mit den Erweiterungen von go-slides geschrieben haben, sperrt Sie ein. + +## Weiter {#next} + +- [Marp-Kompatibilität]({{< relref "marp" >}}): was so gelesen wird, wie Marp es liest. +- [Vorlagen]({{< relref "templates" >}}) und [Kinds]({{< relref "kinds" >}}): die beiden Erweiterungen. +- [Kommandozeile]({{< relref "cli" >}}): jeder Befehl und jedes Flag. diff --git a/content/de/hcl.md b/content/de/hcl.md new file mode 100644 index 0000000..85e0c95 --- /dev/null +++ b/content/de/hcl.md @@ -0,0 +1,151 @@ +--- +title: "HCL" +weight: 45 +description: "HCL2 überall dort, wo ein Foliensatz YAML erwartet: ein HCL-Front-Matter, slides.hcl, Kinds- und Datendateien, mit Ausdrücken und Funktionen." +tags: [hcl, configuration] +--- + +{{< callout type="warning" >}} +HCL gibt es ab v0.3.0 ([go-slides/slides#13](https://github.com/go-slides/slides/pull/13)). +{{< /callout >}} + +Überall, wo `slides` YAML liest, liest es auch die native Syntax von +[HCL2](https://github.com/hashicorp/hcl/blob/main/hclsyntax/spec.md) – wegen +ihrer Ausdrücke und Funktionen. + +| Wo | YAML | HCL | +| --- | --- | --- | +| Front-Matter | `---` … `---` | `---hcl` … `---` | +| Konfiguration | `slides.yaml` | `slides.hcl`, wird zuerst gesucht | +| Kinds-Dateien | `--kinds kinds.yaml`, `kinds: kinds.yaml` | jede Datei namens `*.hcl` | +| Datendateien | `--data data.yaml` oder `.json` | jede Datei namens `*.hcl` | + +Direktivenkommentare (`<!-- class: invert -->`) bleiben wie in Marp YAML, und der +Rumpf des Foliensatzes bleibt eine [Go-Vorlage]({{< relref "templates" >}}). + +## Ein Front-Matter in HCL {#a-front-matter-in-hcl} + +```hcl +---hcl +theme = "gaia" +paginate = true +title = "Quarterly review" +data = { + company = "ACME" + quarter = 3 +} +header = format("%s — Q%d", data.company, data.quarter) +footer = "${upper(data.company)} · ${page()}/${total()}" +date = formatdate("D MMMM YYYY", now()) + +kind "chapter" { + extends = "section" + class = ["lead", "invert"] +} +--- + +# {{ .title }} +``` + +- **Attribute** sind die YAML-Schlüssel unter denselben Namen: die Direktiven von Marp + (`theme`, `paginate`, `header`, `footer`, `class`, `backgroundColor` …), + die Metadaten (`title`, `author` …), `template`, `delims`, `kinds` (Dateipfade) + und `data`, ein Objekt. +- **`kind "<name>" { … }`**-Blöcke definieren [Kinds (Folientypen)]({{< relref "kinds" >}}) + mit den lokalen Direktiven und `extends`. +- **Werte** erreichen die Direktiven in den Zeichenkettenformen, die YAML ihnen gibt – + `true` wird zu `"true"`, eine Liste von Klassen bleibt eine Liste –, sodass sich eine Direktive + gleich verhält, welche Syntax sie auch gesetzt hat. `null` lässt eine Direktive ungesetzt. +- **`data`** behält seine Typen: In Vorlagen ist eine ganze Zahl ein `int`, + andere Zahlen sind `float64`, und Listen und Objekte bleiben Listen und Maps, sodass + `{{ add .quarter 1 }}` funktioniert. +- Ein HCL-Wert ist endgültig: Go-Vorlagen laufen kein zweites Mal darüber. + +## Variablen {#variables} + +`data` sind die Vorlagendaten: das `data` des Front-Matters, überlagert von dem +aus `slides.hcl`, von `--data`-Dateien und von `--set`. `"${data.company}"` funktioniert +überall dort, wo auch `{{ .company }}` funktionieren würde. + +Attribute des Front-Matters dürfen auf `data.*` verweisen, aber **nicht aufeinander**: +`footer = title` ist ein Fehler. Spot-Direktiven (`_class` …) sind keine HCL- +Bezeichner; schreiben Sie sie in einen Kommentar. + +## Funktionen {#functions} + +| | | +| --- | --- | +| `page()`, `total()` | die Seitennummer und die Nummer der letzten Seite, wie `{{ page }}` und `{{ total }}`: eine Fußzeile paginiert | +| `now()`, `timestamp()` | die Zeit, RFC 3339, lokal und UTC; `formatdate` formatiert sie | +| `env(name)` | eine Umgebungsvariable, `""` wenn nicht gesetzt | +| `file(path)` | eine UTF-8-Textdatei von höchstens 1 MiB, relativ zum Foliensatz oder zur Konfigurationsdatei | +| Zahlen | `abs ceil floor log max min parseint pow signum` | +| Zeichenketten | `chomp format formatlist join lower regex regexall regexreplace replace split strlen strrev substr title trim trimprefix trimspace trimsuffix upper` | +| Collections | `chunklist coalesce coalescelist compact concat contains distinct element flatten keys length lookup merge range reverse slice sort values zipmap` | +| Kodierungen | `csvdecode jsondecode jsonencode` | +| Datumsangaben | `formatdate timeadd` | + +Alle Zeilen außer den ersten vier stammen aus der Standardbibliothek von +[go-cty](https://github.com/zclconf/go-cty). Keine davon greift auf das Netzwerk zu. + +## slides.hcl {#slideshcl} + +```hcl +kinds = "kinds.hcl" # or a list of files +themes = ["themes/acme.css"] +data = { company = "ACME" } # or file paths +html = false +lang = "en" + +kind "quote" { + extends = "blank" + footer = "${data.company}" +} +``` + +Ein unbekanntes Attribut ist ein Fehler, kein stilles Nichts. Eine Kinds-Datei enthält +`kind`-Blöcke; ihre übrigen Attribute werden ignoriert, sodass `slides.hcl` als +solche dienen kann. Die Attribute einer Datendatei sind die Schlüssel der Daten: + +```hcl +# data.hcl +company = "ACME" +year = 2026 +motto = trimspace(file("motto.txt")) +``` + +## Fehler {#errors} + +Fehler sind die Diagnosen von HCL, an der Position in der Foliensatz-Datei selbst: +`footer = data.compnay` in der vierten Zeile des Foliensatzes ergibt + +``` +deck.md:4:14: Unsupported attribute: This object does not have an attribute named "compnay". +``` + +## Marp und `slides expand` {#marp-and-slides-expand} + +Auch Marp liest `---hcl` als Beginn eines Front-Matters – seine Front-Matter- +Regel greift bei jeder ersten Zeile, die mit drei Strichen beginnt – und wendet davon nichts an, +da der Block keine YAML-Map ist: Der Foliensatz wird in Marp trotzdem gerendert, +nur ohne seine Direktiven. Ein Test legt dies gegen die eigene Ausgabe von Marp Core fest. + +`slides expand` schreibt das Front-Matter als YAML zurück, sodass der expandierte Foliensatz +reines Marp ist und so gerendert wird, wie `slides` das Original rendert: + +```yaml +--- +theme: "gaia" +paginate: true +title: "Quarterly review" +header: "ACME — Q3" +footer: "ACME · 1/2" +date: "19 October 2026" +--- +``` + +## Lizenzen {#licences} + +Die HCL-Unterstützung bindet [hashicorp/hcl](https://github.com/hashicorp/hcl) +(MPL-2.0, unverändert als Bibliothek verwendet) und +[zclconf/go-cty](https://github.com/zclconf/go-cty) (MIT) ein. diff --git a/content/de/kinds.md b/content/de/kinds.md new file mode 100644 index 0000000..8572f99 --- /dev/null +++ b/content/de/kinds.md @@ -0,0 +1,91 @@ +--- +title: "Kinds" +weight: 40 +description: "Eine Folie sagt, was sie ist: kind benennt eine Vorgabe lokaler Direktiven." +tags: [kinds] +--- + +Ein **Kind** (Folientyp) ist eine benannte Vorgabe lokaler Direktiven: Kopfzeile, Fußzeile, +Seitennummerierung, Klasse, Hintergrund. Eine Folie gibt an, was sie ist – `title`, +`section` –, statt zu wiederholen, wie sie aussieht, und das Erscheinungsbild eines Foliensatzes ändert sich +an einer einzigen Stelle. + +## Ein Kind verwenden {#using-a-kind} + +```markdown +<!-- kind: section --> +# Part two + +--- + +<!-- _kind: blank --> +![bg](photo.jpg) +``` + +- `kind:` wirkt wie eine lokale Direktive: auf diese Folie und die folgenden. +- `_kind:` wirkt wie eine Spot-Direktive: nur auf diese Folie. +- Beim Wechsel eines Kinds wird zuerst **zurückgesetzt**, was das vorherige Kind gesetzt hat, sodass eine Klasse oder eine + Fußzeile nie von einem Kind ins nächste durchsickert. +- Direktiven, die auf der Folie selbst stehen, **haben Vorrang** vor dem Kind. + +## Eingebaut {#built-in} + +| Kind | Setzt | +| --- | --- | +| `title` | `class: lead`, keine Seitennummer, keine Kopfzeile, keine Fußzeile | +| `section` | `class: lead` | +| `content` | nichts: die Standardwerte des Foliensatzes | +| `blank` | keine Seitennummer, keine Kopfzeile, keine Fußzeile | + +Jedes davon lässt sich neu definieren. + +## Kinds definieren {#defining-kinds} + +Im Front-Matter: + +```yaml +--- +marp: true +kinds: + chapter: + class: invert + backgroundColor: "#1e1b4b" + paginate: false + quote: + extends: blank + class: quote +--- +``` + +`extends` lässt ein Kind mit den Direktiven eines anderen beginnen. Ein Kind akzeptiert die +lokalen Direktiven und `extends`. + +## Kinds teilen {#sharing-kinds} + +Die Kinds eines Teams gehören in eine Datei, die jeder Foliensatz benennt: + +```yaml +# kinds.yaml +chapter: + class: invert + paginate: false +``` + +```yaml +--- +marp: true +kinds: kinds.yaml # or a list of files +--- +``` + +oder `--kinds kinds.yaml` auf der Kommandozeile, oder `kinds:` in `slides.yaml`. +Die Reihenfolge ist: die eingebauten Kinds, dann die Kommandozeile und die +Konfigurationsdatei, dann das eigene `kinds:` des Foliensatzes. + +`slides kinds talk.md` listet die Kinds auf, die ein Foliensatz verwenden kann, und was jedes davon setzt. + +## go-slides verlassen {#leaving-go-slides} + +`slides expand` schreibt jedes Kind als die Spot-Direktiven aus, für die es steht, +Folie für Folie, sodass das Ergebnis reines Marp-Markdown ist, das jedes Marp-Werkzeug +lesen kann. diff --git a/content/de/marp.md b/content/de/marp.md new file mode 100644 index 0000000..126a0ee --- /dev/null +++ b/content/de/marp.md @@ -0,0 +1,81 @@ +--- +title: "Marp-Kompatibilität" +linkTitle: "Marp" +weight: 20 +description: "Das Markdown, das slides liest, ist das von Marpit und Marp Core, kein Dialekt davon." +tags: [marp] +--- + +Ein für [Marp](https://marp.app) geschriebener Foliensatz ist ein go-slides-Foliensatz. Die folgenden +Regeln stammen von [Marpit](https://marpit.marp.app) und +[Marp Core](https://github.com/marp-team/marp-core); wo go-slides +abweicht, sagt diese Seite es. + +## Folien {#slides} + +Eine horizontale Linie – `---` allein auf einer Zeile, so wie Marpit trennt – beginnt eine +neue Folie, ebenso eine Überschrift der Ebene, die die Direktive `headingDivider` +nennt. Das Front-Matter zwischen den ersten beiden `---` enthält globale Direktiven +und die Daten des Foliensatzes. + +## Direktiven {#directives} + +| Art | Wo | Gilt für | +| --- | --- | --- | +| global | Front-Matter oder `<!-- theme: gaia -->` an beliebiger Stelle | den ganzen Foliensatz: `theme`, `style`, `headingDivider`, `lang` … | +| lokal | `<!-- paginate: true -->` | diese Folie und die folgenden | +| Spot | `<!-- _class: lead -->` (Unterstrich) | nur diese Folie | + +Die lokalen Direktiven sind die von Marpit: `paginate`, `header`, `footer`, `class`, +`backgroundColor`, `backgroundImage`, `backgroundPosition`, +`backgroundRepeat`, `backgroundSize`, `color`. Direktiven werden als +nachsichtiges YAML geparst, wie es Marp Core tut; `--strict-yaml` parst sie so, wie es +reines Marpit tut. + +## Bilder {#images} + +`![bg](image.jpg)` macht ein Bild zum Hintergrund der Folie, mit den Schlüsselwörtern +von Marp: `bg contain`, `bg cover`, `bg 50%`, `bg left`/`bg right` für einen geteilten +Hintergrund, mehrere `bg`-Bilder nebeneinander und die Bildfilter +(`blur`, `sepia`, `grayscale` …). Größenangaben für Inline-Bilder (`w:200px`, `h:`) werden +so gelesen, wie Marp sie liest. + +## Rund um die Folie {#around-the-slide} + +- **Kopf- und Fußzeile**: die Direktiven `header:` und `footer:`, in Markdown. +- **Seitennummerierung**: `paginate: true`. +- **Sprechernotizen**: Ein HTML-Kommentar, der keine Direktive ist, ist eine Notiz. + `slides notes` gibt sie aus. +- **Schrittweise Listen**: Eine mit `*` (oder `1)`) geschriebene Liste erscheint Eintrag für Eintrag, + wie in Marp. +- **`<style scoped>`**: CSS nur für eine Folie. +- **Rohes HTML**: standardmäßig die Allowlist von Marp Core; `--html` erlaubt alles. +- **Emoji**: `:shortcodes:` und Unicode-Emoji werden zu Twemoji-Bildern, wie in + Marp Core, jedoch eingebettet statt von einem CDN geladen: siehe + [Emoji]({{< relref "emoji" >}}). + +## Themes {#themes} + +`default`, `gaia` und `uncover` sind eingebaut und werden mit `theme:` gewählt. Ein +eigenes Theme ist eine CSS-Datei mit dem Marp-Kommentar `/* @theme name */`, +die Sie mit `--theme-set` angeben oder in `slides.yaml` unter `themes:` aufführen. + +## Wo go-slides abweicht {#where-go-slides-differs} + +- **Vorlagen.** `{{ }}` im Rumpf ist eine Aktion einer Go-Vorlage (siehe + [Vorlagen]({{< relref "templates" >}})). Ein Foliensatz, der `{{` außerhalb eines Codeblocks als Text + zeigt, braucht `--no-template` oder den Text maskiert, so wie Go-Vorlagen + ihn maskieren. Codeblöcke und Code-Spans bleiben unberührt. +- **Kinds (Folientypen).** `kind:` und `_kind:` sind Direktiven, die Marp nicht kennt (siehe + [Kinds]({{< relref "kinds" >}})). `slides expand` ersetzt sie durch die + Direktiven, für die sie stehen, sodass der expandierte Foliensatz reines Marp ist. +- **Ausgaben.** HTML, PNG, JPEG, PDF, PPTX und ODP, über Go-Bibliotheken statt über + einen Headless-Browser (zur bekannten Einschränkung siehe [Kommandozeile]({{< relref "cli#images-pdf-pptx-and-odp" >}})). + In v0.4.0 enthält ein PPTX wie bei marp-cli Bilder; seit + v0.5.0 sind seine Folien editierbare Textfelder und Formen, und `--pptx-raster` behält + die Bilder bei. +- **Einpassende Überschriften.** `# <!-- fit --> Title` wird unterstützt: Die Überschrift füllt + die Folienbreite in einer Zeile, auf der Seite und in jedem Export. +- **Mathematik und Code.** `$…$` und `$$…$$` folgen den Begrenzerregeln von Marp Core und + werden von go-tex/math gesetzt; Codeblöcke hebt go-rouge mit den Klassennamen von + highlight.js hervor (siehe [Mathematik und Code]({{< relref "math-and-code" >}})). diff --git a/content/de/math-and-code.md b/content/de/math-and-code.md new file mode 100644 index 0000000..6a55daa --- /dev/null +++ b/content/de/math-and-code.md @@ -0,0 +1,143 @@ +--- +title: "Mathematik und Code" +weight: 35 +description: "Formeln zwischen Dollarzeichen werden gesetzt, Codeblöcke hervorgehoben, auf der HTML-Seite, in Bildern und im PDF." +tags: [math, code] +--- + +{{< callout type="warning" >}} +Mathematik und Code gibt es ab v0.2.0. +{{< /callout >}} + +## Mathematik {#math} + +`$…$` ist eine Inline-Formel und `$$…$$` eine abgesetzte Formel: + +```markdown +The famous one is $E=mc^2$, and this is a display formula: + +$$ +\int_0^\infty e^{-x^2}\,dx = \frac{\sqrt{\pi}}{2} +$$ +``` + +- Die Begrenzerregeln sind die von Marp Core, gelesen aus marp-core 4.4.0: Ein `$` + mit folgendem Leerzeichen öffnet keine Formel, und eines mit folgender Ziffer + schließt keine, sodass Preise wie `$5 and $10` Text bleiben. +- [go-tex/math](https://github.com/go-tex/math) setzt jede Formel in ein + Inline-SVG in der Einheit `em`, das sich der Textgröße anpasst und auf der + Grundlinie sitzt. Dasselbe SVG dient auf der HTML-Seite, in Bildern und im PDF. +- Eine Formel, die sich nicht parsen lässt, bleibt wie geschrieben sichtbar; der Fehler steht in + ihrem `title`. + +## Code {#code} + +Codeblöcke werden von [go-rouge](https://github.com/go-rouge/rouge) eingefärbt, +das die Klassennamen von highlight.js schreibt, die die Themes von Marp formatieren: + +````markdown +```go +fmt.Println("hello") +``` +```` + +Eine Sprache, die go-rouge nicht kennt, wird als reiner Text geschrieben, wie es Marp Core tut. + +### Zeilennummern {#line-numbers} + +Ab v0.8.0 kann ein Block seine Zeilen nummerieren. Marp Core kennt keine Zeilennummern und +ignoriert, was hinter der Sprache eines Codeblocks steht; die Optionen stehen deshalb in geschweiften Klammern, +und ein Foliensatz, der sie verwendet, wird in Marp so gerendert, als gäbe es sie nicht: + +````markdown +```go {lineNumbers} +func main() { + fmt.Println("hello") +} +``` + +```go {lineNumbers=10} +// starts at 10 +``` +```` + +| Schreibweise | Bedeutung | +| --- | --- | +| `{lineNumbers}` | ab 1 nummerieren (Slidevs `{lines:true}` und Pandocs `{.numberLines}` werden ebenfalls gelesen) | +| `{lineNumbers=10}` | ab 10 nummerieren (auch `startLine=10`, Pandocs `startFrom="10"`) | +| `{lineNumbers=false}` | keine Nummern, was die Folie auch sagt | + +Für alle Blöcke einer Folie und der folgenden Folien setzen Sie die Direktive: +`<!-- lineNumbers: true -->` (`_lineNumbers` für eine einzelne Folie). Ein Kind (Folientyp) kann sie +einmal für eine ganze Gruppe von Folien setzen: + +```yaml +kinds: + listing: {lineNumbers: true} +``` + +Jede Nummer beginnt ihre Zeile innerhalb des Codekastens des Themes, durch eine Linie abgesetzt. +Sie bleibt auf der Grundlinie ihrer Zeile, auch wenn die Zeile höher ist, etwa +durch eine Markierung oder ein Emoji. Die Nummern sind in einer Farbe gedämpft, die aus der +Farbe des Codes und seinem Hintergrund gemischt ist, mit einem Kontrast von 4,5:1 oder mehr in den Themes +default, gaia und uncover, hell und dunkel. Sie sind auf der Seite **nicht auswählbar** +und werden von Screenreadern nicht vorgelesen. Im PDF werden sie gezeichnet, aber aus dem Text +ausgelassen, sodass ein Textextraktor nur den Code liest. In einer PowerPoint- oder +OpenDocument-Datei sind sie editierbarer Text am Anfang jeder Zeile. Ein Foliensatz, +der die Option nicht verwendet, wird wie zuvor gerendert, bis auf das Byte genau. Das Hervorheben +ausgewählter Zeilen wird noch nicht unterstützt. + +### Markierungen im Code {#code-callouts} + +Ab v0.8.0 kann eine Codezeile mit einer nummerierten Scheibe enden, die auf eine Notiz +unter dem Block verweist. Die Syntax ist die von +[Quarto](https://quarto.org/docs/authoring/code-annotation.html), die auch +die Kommentarform von Asciidoctor ist. Die Markierung ist ein Kommentar in der Sprache des Codes, +sodass der Code gültig bleibt, und die Notizen sind die geordnete Liste direkt +hinter dem Block: + +````markdown +```go +func main() { // <1> + x := load() // <2> + fmt.Println(x) +} +``` + +1. The entry point. +2. Reads the input. +```` + +| Schreibweise | Bedeutung | +| --- | --- | +| `// <1>`, `# <1>`, `-- <1>`, `; <1>`, `% <1>`, `' <1>` | ein Zeilenkommentar, der nur die Markierung enthält: beide werden durch die Scheibe ersetzt | +| `<!-- <1> -->`, `/* <1> */` | dasselbe für Sprachen mit umschließenden Kommentaren | +| `// <1> <2>` | zwei Scheiben in einer Zeile | +| `x = 1 # count <1>` | der Text des Kommentars bleibt, und die Scheibe folgt ihm | + +Die Markierungen werden nur dann zu Scheiben, wenn die Liste direkt hinter dem Block bei 1 beginnt +und für jede Nummer einen Eintrag hat. Andernfalls bleibt der Block wie geschrieben. Die +Scheiben sind Ringe in der Farbe des Codes, in den Themes default, gaia und uncover, +hell oder dunkel. Die Einträge der Liste werden mit denselben Scheiben nummeriert. + +Die Scheiben sind **nicht auswählbar**. Kopiert man den Code von der Seite, erhält man den Code +ohne seine Markierungen. Im PDF sind sie Vektoren, die aus dem Text ausgelassen werden, und in einer +PowerPoint- oder OpenDocument-Datei sind sie editierbare Formen. Markierungen lassen sich +mit Zeilennummern kombinieren. Marp Core kennt keine Markierungen (marp-core 4.4.0); in Marp erscheinen daher die +Kommentare und die nummerierte Liste wie geschrieben. Ein Foliensatz, der sie nicht verwendet, +wird wie zuvor gerendert, bis auf das Byte genau. + +Ab v0.9.0 finden sich eine Scheibe und ihre Notiz auf der HTML-Seite gegenseitig. Fährt man mit der Maus +über eine Scheibe oder erreicht sie mit der Tabulatortaste, färbt sich ihre Notiz ein, und eine Notiz bewirkt dasselbe für ihre +Scheibe. Das entspricht `code-annotations: hover` von Quarto, nur erledigt es hier allein das Stylesheet, +ohne Skript. Es funktioniert daher gleich in der Sandbox-Vorschau des +Playgrounds und in der Vorschau von VS Code, die beide Skripte einschränken. Jede Scheibe wird durch ihre Notiz beschrieben, +sodass ein Screenreader die Notiz vorliest, wenn er die Scheibe erreicht. Die Färbung blendet nur dann ein, wenn der Leser +keine reduzierte Bewegung verlangt hat. Bilder, PDF-, PowerPoint- und OpenDocument-Dateien +bleiben Byte für Byte unverändert. + +## Schriften {#fonts} + +Lato Regular, Bold, Black und Kursiv sowie Roboto Mono Regular, Bold und +Kursiv sind eingebettet, sodass die Überschriften von gaia ihre Strichstärke erhalten und kursiver Text +kursiv gerendert wird. diff --git a/content/de/performance.md b/content/de/performance.md new file mode 100644 index 0000000..be54c55 --- /dev/null +++ b/content/de/performance.md @@ -0,0 +1,88 @@ +--- +title: "Performance" +weight: 60 +description: "Wie schnell go-slides einen Foliensatz baut und wie viel Speicher es dafür braucht, gemessen gegen marp-cli auf demselben Rechner." +tags: [performance] +--- + +{{< callout type="info" >}} +Die PPTX-Werte unten gelten für v0.5.0, die Werte für das Vektor-PDF für v0.4.0. +Die erste Tabelle gilt für v0.3.0, +die vorherige Version, die Folien nebenläufig rendert und ihre Schriften +nur einmal parst ([go-slides/slides#11](https://github.com/go-slides/slides/pull/11)). +{{< /callout >}} + +go-slides und [marp-cli](https://github.com/marp-team/marp-cli) 4.5.1 wurden +als Subprozesse mit denselben Foliensätzen ausgeführt, auf einem Apple M4 Max (16 CPUs) mit +einem Load Average über eine Minute unter 8, fünf abwechselnd ausgeführte Durchläufe nach einem Warm-up. +Die Zeit ist der Median der Wanduhrzeit; der Speicher ist der Spitzenwert des gesamten Prozessbaums +(marp-cli steuert einen Chrome, dessen Prozesse mitzählen). + +| Foliensatz | Ausgabe | go-slides | marp-cli | Speicher go-slides | Speicher marp-cli | +| --- | --- | ---: | ---: | ---: | ---: | +| 100 Folien | HTML | 36 ms | 315 ms | 47 MiB | 140 MiB | +| 100 Folien | PDF | 1,14 s | 1,88 s | 242 MiB | 1.509 MiB | +| 100 Folien | PNG, jede Folie | 0,97 s | 5,44 s | 225 MiB | 1.354 MiB | +| 100 Folien | PPTX | 3,39 s | 8,03 s | 530 MiB | 1.373 MiB | +| gaia, 20 Folien | PDF | 283 ms | 1,57 s | 154 MiB | 1.315 MiB | +| Mathematik und Code, 20 Folien | PNG, jede Folie | 250 ms | 2,32 s | 168 MiB | 1.286 MiB | + +## Vektor-PDF {#vector-pdf} + +Ab v0.4.0 wird das PDF als Vektoren gezeichnet (siehe +[Kommandozeile]({{< relref "cli#vector-pdf" >}})). Gleicher Rechner, gleiche Methode, +2026-10-19: + +| Foliensatz | go-slides | marp-cli | v0.3.0 | Größe go-slides | Größe marp-cli | Größe v0.3.0 | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 100 Folien | 306 ms | 1.763 ms | 1.147 ms | 758 KiB | 837 KiB | 10,0 MiB | +| gaia, 20 Folien | 93 ms | 1.546 ms | 285 ms | 32 KiB | 66 KiB | 582 KiB | +| Mathematik und Code, 20 Folien | 140 ms | 1.643 ms | 294 ms | 124 KiB | 335 KiB | 638 KiB | + +Speicher für das PDF mit 100 Folien: 158 MiB gegenüber 1.512 MiB bei marp-cli; CPU-Zeit +0,9 s gegenüber 2,7 s. + +Die Werte von v0.4.0 für das PDF mit 100 Folien wurden am 2026-10-19 um 21:29 gemessen, +bei einem Load Average über eine Minute von 7,7, in drei Durchläufen: go-slides 306 ms, 758 +KiB und 158 MiB; marp-cli 1.838 ms, 837 KiB und 1.512 MiB. + +## Editierbares PPTX {#editable-pptx} + +Seit v0.5.0 besteht das PPTX aus Textfeldern, Formen und Bildern (siehe +[Kommandozeile]({{< relref "cli#editable-pptx-and-odp" >}})). Gemessen am +2026-10-19 um 22:51, bei einem Load Average über eine Minute von 7,1, in drei Durchläufen: + +| Foliensatz | go-slides | marp-cli | Größe go-slides | Größe marp-cli | Speicher go-slides | Speicher marp-cli | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 100 Folien | 348 ms | 7.795 ms | 760 KiB | 7,2 MiB | 204 MiB | 1.370 MiB | +| gaia, 20 Folien | 112 ms | 4.608 ms | 893 KiB | 4,8 MiB | | | +| Mathematik und Code, 20 Folien | 207 ms | 2.780 ms | 842 KiB | 1,5 MiB | | | + +Für den Foliensatz mit 100 Folien brauchte v0.4.0 4,7 s, 4,8 MiB und 540 MiB. Das PPTX +von marp-cli enthält Bilder. Den größten Teil der Größe eines editierbaren Foliensatzes machen die eingebetteten Schriften aus. + +## Warum es schnell ist {#why-it-is-fast} + +- **Kein Browser, der gestartet werden muss.** marp-cli startet für jeden Export nach PDF, Bild und + PPTX einen Chrome; go-slides rendert mit + [go-webengine](https://github.com/go-webengine/engine) im eigenen + Prozess. +- **Folien werden parallel gerendert**, standardmäßig vier gleichzeitig, und jedes Bild wird + geschrieben, sobald es an der Reihe ist: Ein langer Foliensatz liegt nie als + Bilder im Speicher. +- **Schriften werden nur einmal pro Prozess geparst**, nicht einmal pro Folie. + +## Was es nicht gewinnt {#what-it-does-not-win} + +- **PDF-Größe in v0.3.0.** Jede Folie war ein Bild im PDF: 10 MiB gegenüber + 837 KiB beim Foliensatz mit 100 Folien. Das Vektor-PDF oben ist kleiner als das von + marp-cli. +- **CPU-Zeit bei großen Foliensätzen.** Vier Worker verbrauchen mehr CPU, als die Wanduhrzeit + zeigt: in v0.3.0 3,2 s gegenüber 2,7 s bei marp-cli für das PDF mit 100 Folien + (0,9 s beim Vektor-PDF). +- **Der Speicher ist bei marp-cli eine Obergrenze**: Die Prozesse von Chrome teilen sich Seiten, + die die Summe in jedem davon mitzählt. Sein größter einzelner Prozess hat 220–320 MiB. + +Die Methode, jeder Foliensatz und jeder Modus sowie die Befehle zum Reproduzieren der Werte +stehen in +[BENCHMARKS.md](https://github.com/go-slides/slides/blob/main/BENCHMARKS.md). diff --git a/content/de/playground.md b/content/de/playground.md new file mode 100644 index 0000000..e63d922 --- /dev/null +++ b/content/de/playground.md @@ -0,0 +1,172 @@ +--- +title: "Playground" +weight: 15 +description: "Schreiben Sie einen Foliensatz im Browser und sehen Sie, wie die Folien folgen: go-slides, nach WebAssembly kompiliert, mit Vervollständigung, Hilfe und Beispielen." +tags: [playground, browser] +--- + +**[Playground öffnen →](https://go-slides.github.io/playground/)** + +Der Playground ist go-slides selbst, v0.9.0, nach WebAssembly kompiliert und +in Ihrem Browser ausgeführt. Tippen Sie links einen Foliensatz; rechts +folgen die Folien. Nichts wird an einen Server gesendet: weder der Foliensatz noch ein Link zum Teilen. + +## Was er bietet {#what-it-has} + +- **Live-Vorschau** jeder Folie, außerhalb des Threads der Seite gerendert (ein Web + Worker), sodass das Tippen nie darauf wartet. Klicken Sie auf eine Folie, um den Editor + zu ihrer Zeile springen zu lassen; bewegen Sie den Cursor, und die Vorschau scrollt zu ihrer Folie. +- **Bildschirmpräsentation**: die Präsentation selbst, mit Tastatur, + Bedienelementen auf dem Bildschirm und den Übergängen. +- **Referentenansicht**: **Present** (oder `p` in der Bildschirmpräsentation) öffnet sie in einem + zweiten Fenster: die aktuelle Folie, der nächste Schritt, die Sprechernotizen (die + `<!-- comments -->` des Foliensatzes), eine Uhr und ein Timer. Die Pfeiltasten, die Leertaste, + Pos1 und Ende funktionieren in beiden Fenstern, und jedes folgt dem anderen, auch + wenn Sie den Foliensatz bearbeiten. Öffnet sich das Fenster nicht, erlauben Sie Pop-ups für + die Website. Die Übersichtsansicht (`o`) gibt es nur auf der heruntergeladenen Seite. +- **Vervollständigung** beim Tippen: Direktivenschlüssel (Spot-Direktiven mit `_` in Kommentaren), + die Werte von `kind`, `theme`, `size`, `paginate` sowie Vorlagenfunktionen + und Datenfelder innerhalb von `{{ }}`. +- **Hilfe** zu dem, was unter dem Cursor steht: eine Direktive, ein Kind (Folientyp; was es setzt), + eine Vorlagenfunktion. Der Hilfebereich verlinkt auch auf diese Seiten. +- **Probleme** in ihrer Zeile: Fehler in Vorlagen, YAML und HCL werden im Editor hervorgehoben + und darunter aufgelistet, und die Vorschau behält den letzten Foliensatz, + der sich rendern ließ. +- **Beispiele**: ein Willkommens-Foliensatz, Vorlagen, die Folien erzeugen, Kinds, + ein HCL-Front-Matter, Themes und Spot-Direktiven sowie Mathematik, Bilder und + Hintergründe (`![bg]`, ein geteiltes `![bg left:40%]`, Filter). +- **Dateien**: Der Bereich links vom Editor ist das Projekt des Foliensatzes, so angeordnet, + wie der [Befehl]({{< relref "cli" >}}) und die + [VS-Code-Erweiterung]({{< relref "vscode" >}}) es lesen: `deck.md`, + `slides.yaml`, `themes/`, `kinds/`, `media/`. + - Klicken Sie auf eine Datei, um sie zu bearbeiten. Der Editor färbt sie nach ihrer Sprache ein + (Markdown, YAML, HCL, JSON, CSS) und markiert ihre Probleme. + - Rechtsklick (oder Umschalt+F10) für Neue Datei, Neuer Ordner, Umbenennen (F2), + Löschen (fragt vorher nach) und Neues Theme / Neues Kind. Ziehen Sie eine Datei auf einen Ordner, + um sie zu verschieben. Die Tastaturbedienung folgt dem WAI-ARIA-Tree-Muster, einschließlich + Type-ahead. + - **Project ▸ New theme** erzeugt `themes/my-gaia.css` (oder auf Basis von default, + uncover): ein `@import` des eingebauten Themes und die Regeln, die Sie ergänzen. Es + erscheint in der Theme-Liste. **New kind** und **New data file** beginnen ebenfalls mit + Vorlagen. Jedes wird in `slides.yaml` aufgeführt, sodass `slides build deck.md` + es findet. + - **Project ▸ Download project (.zip)** sichert alles. Entpackt, baut + `slides build deck.md` es. **Open project** nimmt eine `.zip`-Datei oder einen + Ordner wieder entgegen. Ein Archiv, das seinen Ordner verlässt (`..`, absolute Pfade), + mehr als 256 Dateien oder 8 MiB in einer Datei enthält oder sich auf mehr als das 100-Fache + entpackt, wird abgelehnt. + - Alles wird zusammen mit dem Entwurf in diesem Browser gespeichert. Ein Projekt fasst + bis zu 64 MiB, und die Zeile unter den Dateien zeigt, wie viel belegt ist. +- **Medien**: Ziehen Sie ein Bild, ein Video oder einen Ton auf den Editor oder fügen Sie es ein, und es + landet in `media/`, und der Foliensatz benennt es: `![](media/photo.png)`. Ziehen Sie + Dateien auf einen Ordner des Bereichs, um sie dort abzulegen. Der Foliensatz bleibt klein und + lesbar, und nichts wird hochgeladen. Die Sandbox der Vorschau lädt sie, + ohne geöffnet zu werden (die Seite reicht ihr die Bytes). **Save as ▸ HTML + page** legt sie in die Seite. Auch `https:`-Bilder funktionieren, aber der Browser + holt sie von ihrem Server, der dann Ihre Adresse sieht. Die + Vorschau sendet keinen Referrer. +- **Größenveränderbare Bereiche**: Der Dateibereich, der Editor, die Folienminiaturen, die + Vorschau und die Hilfe sind durch Leisten getrennt, die Sie ziehen können (der Mauszeiger + wird darüber zum Größenänderungs-Cursor). Der Bereich neben der Leiste erhält den Platz, + die anderen behalten ihre Größe. Doppelklicken Sie auf eine Leiste oder drücken Sie die Eingabetaste darauf, um den Bereich + zu schließen und wieder zu öffnen. Per Tastatur wechselt F6 zwischen + den Leisten, die Pfeiltasten verschieben eine um 16 px, Pos1 und Ende bringen den davorliegenden Bereich + auf seine kleinste und größte Größe, und Escape führt zurück in den Editor. + Der Reiter View schaltet Bereiche weiterhin ab. Die von Ihnen eingestellten Größen werden in diesem + Browser mit dem Entwurf gespeichert; die Standardgrößen (190, 168 und 280 px, wobei sich Editor + und Vorschau den Rest im Verhältnis 6 zu 5 teilen) werden nie gespeichert. +- **Format**: Der Reiter Format bearbeitet das Markdown für Sie, und jeder Druck + ist ein Schritt für Rückgängig. + - **Text**: Bold, Italic, Strikethrough und Inline code wirken auf die Auswahl + oder das Wort am Cursor und schalten um (Strg+B, Strg+I, Strg+Umschalt+X und + Strg+E; Cmd auf dem Mac). Eine Schaltfläche erscheint gedrückt, wenn der Cursor innerhalb + ihrer Auszeichnung steht. Markdown kennt kein Unterstreichen: Underline (Strg+U) umschließt den Text mit + `<u>…</u>`, einem Tag, das die Standard-HTML-Allowlist von Marp beibehält, sodass es auch bei ausgeschaltetem + rohem HTML funktioniert. + - **Paragraph**: Heading 1 bis 3 (Strg+Alt+1..3), Aufzählung (Strg+Umschalt+8), + Nummerierung (Strg+Umschalt+7) und ein Zitat wirken auf die ausgewählten Zeilen und schalten um. + Die horizontale Linie ist das Tag `<hr>`: `---`, `***` und `___` würden in Marp jeweils + eine neue Folie beginnen. + - **Table**: Ein Raster wählt Spalten und Zeilen. Am Cursor in einer Tabelle + fügen Sie eine Zeile oder Spalte hinzu oder entfernen sie und richten die Spalte aus (`:--`, `:-:`, + `--:`); die Tabelle wird nach jeder Änderung ausgerichtet neu geschrieben. + - **Slide text** links, zentriert oder rechts schreibt einen `<style scoped>`-Block in + die aktuelle Folie. Marp kennt keine Ausrichtungssyntax, und ein `style`-Attribut + passiert die Standard-HTML-Allowlist nicht, der Scoped-Block aber schon, sodass er bei ausgeschaltetem + rohem HTML funktioniert und in der Vorschau, auf der HTML-Seite und im PDF erscheint. + - **Link** (Strg+K) schreibt `[text](url)` mit markierter URL. +- **Hervorhebung** der Schlüssel des Front-Matters in beliebiger Tiefe, in YAML (auch + in `{a: 1}`-Maps) und in HCL. +- **Theme**-Auswahl: default, gaia, uncover, die Themes des Projekts + (`themes/*.css`) und die Themes, die der Foliensatz definiert (`/* @theme name */` in einem + `<style>`). Sie schreibt `theme:` in das Front-Matter, YAML oder HCL, sodass ein geteilter Link es beibehält, + und folgt dem Front-Matter, wenn Sie es bearbeiten. +- Die Vorschau **behält ihre Position** beim Tippen: Nur die Folien, die sich + geändert haben, werden ersetzt, und ein Themenwechsel öffnet die Vorschau + wieder an derselben Folie. +- **Link zum Teilen**: Der Foliensatz reist im Link selbst (in dessen `#`-Teil, + komprimiert), sodass das Öffnen des Links den Foliensatz öffnet. Links fassen bis zu + 64 KiB komprimiert, und nur den Foliensatz: Enthält das Projekt mehr + (Themes, Kinds, Medien), weist Share darauf hin und bietet die `.zip`-Datei an. +- **Save as**: + - die HTML-Seite, dieselbe wie von `slides deck.md` geschrieben; + - ein **PDF** (Vektor, Text auswählbar, mit Gliederung); + - ein editierbarer **PowerPoint**- oder **OpenDocument**-Foliensatz. + + Sie entstehen in Ihrem Browser, mit demselben Code wie `slides --pdf`, + `--pptx` und `--odp`: Das PDF des Benchmark-Foliensatzes mit 100 Folien ist + byteidentisch mit dem des Befehls. Der erste Export lädt die Export-Engine herunter + (etwa 21 MiB, einmalig; Ihr Browser behält sie bis zum nächsten Update), + mit einer Fortschrittszeile. Der Foliensatz mit 100 Folien braucht dann etwa 3 Sekunden. Ein + `https:`-Bild wird nur eingebunden, wenn sein Server es erlaubt (CORS); `data:`-Bilder + immer. +- **Ihr Entwurf bleibt erhalten**: + - Der Foliensatz wird beim Tippen in diesem Browser gespeichert (IndexedDB, nur für diese + Website) und kommt beim Neuladen zurück. Nichts wird irgendwohin gesendet. Die + Textdateien des Projekts (Themes, Kinds, Daten, `slides.yaml`) werden auf dieselbe Weise + gespeichert, auch wenn Sie den Tab direkt nach dem Tippen schließen; Medien werden + gespeichert, sobald sie abgelegt werden. + - Ein geteilter Link öffnet seinen Foliensatz und behält Ihren Entwurf: **Draft ▸ Restore my + draft** holt ihn zurück. + - Ein Beispiel, **Draft ▸ Reset to the example** (fragt vorher nach) und + ein anderer Tab löschen ihn ebenfalls nie: **Draft ▸ Restore replaced** holt ihn + zurück. + - **Draft ▸ Forget my drafts** löscht alles Gespeicherte. + +Jeder Export rendert das Projekt, das die Vorschau zeigt: seine `slides.yaml`, +Themes, Kinds, Datendateien und Medien, die Medien im Dokument. + +Bilder als Dateien (`--images` von slides) entstehen nicht im Browser: Verwenden Sie den +[Befehl]({{< relref "cli" >}}) oder die +[VS-Code-Erweiterung]({{< relref "vscode" >}}). + +## Sicherheit {#security} + +Der Foliensatz wird in einem iframe gezeigt, das ohne den Ursprung der Seite in einer Sandbox läuft: Er kann +weder den Playground noch dessen Speicher erreichen, kein Formular absenden und +nirgendwohin navigieren. Seine Seite trägt eine Content Security Policy, die nur ihre eigenen +Skripte ausführt. Rohes HTML im Foliensatz folgt der Allowlist von Marp, wie es `slides` +standardmäßig tut. Ein Rendern, das länger als 5 Sekunden läuft (eine Vorlage mit Endlosschleife), +wird abgebrochen. + +Das Referentenfenster behält diese Sandbox. Es wird vom Playground +selbst geöffnet und zeigt den Foliensatz in einem ebenso abgeschotteten iframe. Die beiden +iframes können keinen Speicher teilen, deshalb reicht der Playground die Foliennummer +in Nachrichten zwischen ihnen weiter. Kein Frame, der den Foliensatz ausführt, erhält den +Ursprung des Playgrounds. + +Die Medien des Projekts erreichen die Sandbox-Frames als Bytes in einer Nachricht, und +jeder Frame erzeugt daraus seine eigenen `blob:`-URLs, in seinem eigenen Ursprung. Eine `blob:`-URL, die vom Playground +erzeugt wurde, lädt dort nicht, und die Richtlinie der Frames +erhält lediglich `blob:` für Medien und Schriften hinzu. + +## Größe und Geschwindigkeit {#size-and-speed} + +Das Modul umfasst 8,8 MiB, wie GitHub Pages es ausliefert (gzip; Pages liefert +kein Brotli aus). Es wird einmal mit Fortschrittsbalken heruntergeladen und dann vom +Browser bis zum nächsten Deploy zwischengespeichert. Auf einem Apple M4 Max mit Chrome 155, gemessen +an der Live-Seite, ist der Editor 2,1 s nach dem Öffnen der Seite bereit, davon 1,5 s +für den Download, und die ersten Folien erscheinen nach 2,4 s. Eine Änderung am +Benchmark-Foliensatz mit 100 Folien erreicht die Vorschau in etwa 30 ms: go-slides +rendert nur die Folien neu, die sich geändert haben, und nur diese werden in der +Vorschau ersetzt. diff --git a/content/de/templates.md b/content/de/templates.md new file mode 100644 index 0000000..1a64df2 --- /dev/null +++ b/content/de/templates.md @@ -0,0 +1,77 @@ +--- +title: "Vorlagen" +weight: 30 +description: "Interpolation mit Go-text/template in einem Foliensatz: Werte, erzeugte Folien, Seitenzahlen, Includes." +tags: [templates] +--- + +Der Foliensatz ist ein Go-[`text/template`](https://pkg.go.dev/text/template), +das **ausgewertet wird, bevor** das Markdown geparst wird. Alles, was die Go-Template- +Sprache bietet – Aktionen, Pipelines, `if`, `range`, `with`, Variablen –, funktioniert +in einem Foliensatz. + +## Daten {#data} + +Der Punkt der Vorlage enthält die Schlüssel des Front-Matters, dann die Schlüssel seiner +`data:`-Map, dann die Daten, die auf der Kommandozeile oder in `slides.yaml` angegeben werden +– jede Stufe überschreibt die vorherige. + +```markdown +--- +marp: true +title: Quarterly review +data: + team: Platform +--- + +# {{ .title }} + +Presented by the {{ .team }} team. +``` + +Von der Kommandozeile: + +| Flag | Liefert | +| --- | --- | +| `--data file.yaml` | eine YAML- oder JSON-Datei mit Daten (wiederholbar) | +| `--set key=value` | einen Wert (wiederholbar) | + +## Folien erzeugen {#generating-slides} + +Der gesamte Rumpf ist eine einzige Vorlage, sodass ein `range`, das `---` schreibt, Folien schreibt: + +```markdown +{{ range .releases }} +--- + +# {{ .version }} + +{{ range .changes }}- {{ . }} +{{ end }} +{{ end }} +``` + +## Seite und Gesamtzahl {#page-and-total} + +`{{ page }}` und `{{ total }}` sind die Nummer der Folie und die Anzahl der +Folien. Sie sind erst bekannt, nachdem der Foliensatz aufgeteilt wurde, und werden deshalb +erst dann eingesetzt – ein erzeugter Foliensatz zählt die Folien, die er erzeugt hat. + +```markdown +<!-- footer: "{{ page }} / {{ total }}" --> +``` + +## Include {#include} + +`{{ include "parts/agenda.md" }}` fügt eine andere Datei ein, relativ zum +Foliensatz. + +## Code bleibt unberührt {#code-is-left-alone} + +Codeblöcke und Inline-Code-Spans werden beiseitegelegt, bevor die Vorlage +läuft, und unverändert wiederhergestellt, sodass eine Folie über Go-Vorlagen +`{{ .Name }}` zeigen kann, ohne es zu maskieren. + +## Abschalten {#turning-it-off} + +`--no-template` liest den Foliensatz als reines Marp: `{{` ist Text. diff --git a/content/de/vscode.md b/content/de/vscode.md new file mode 100644 index 0000000..f56f150 --- /dev/null +++ b/content/de/vscode.md @@ -0,0 +1,184 @@ +--- +title: "VS Code" +weight: 55 +description: "Die go-slides-Erweiterung für VS Code: Vorschau, Präsentieren, Probleme, Vervollständigung und Export, so wie Marp for VS Code es für Marp bietet." +tags: [vscode, editor] +--- + +{{< callout type="info" >}} +Die Erweiterung ist [go-slides/vscode](https://github.com/go-slides/vscode), +mit eigener Versionierung (v0.4.0). Sie rendert mit go-slides v0.9.0 (dem Paket +`editor`, den Quellzeilen für die Scroll-Synchronisierung sowie der Übersichts- und +Referentenansicht). Installiert wird sie über die an das GitHub-Release angehängte Datei +`go-slides-0.4.0.vsix`: Die Veröffentlichung im VS Code Marketplace und auf Open VSX ist +vorbereitet, aber noch nicht erfolgt. +{{< /callout >}} + +Die Erweiterung ist das Gegenstück von go-slides zu +[Marp for VS Code](https://github.com/marp-team/marp-vscode): Der Foliensatz erscheint in +der Markdown-Vorschau von VS Code als Folien, seine Fehler im Problems-Panel, und +Direktiven, Kinds (Folientypen) und Vorlagenfunktionen werden beim Tippen vervollständigt. + +## Installation {#install} + +Laden Sie `go-slides-<version>.vsix` von den +[Releases](https://github.com/go-slides/vscode/releases) herunter und führen Sie dann +*Extensions: Install from VSIX...* aus, oder: + +```sh +code --install-extension go-slides-0.4.0.vsix +``` + +Die Vorschau braucht nichts weiter. Für den Export benötigen Sie den Befehl `slides`: + +```sh +go install github.com/go-slides/slides/cmd/slides@latest +``` + +## Vorschau {#preview} + +Öffnen Sie einen Foliensatz und wählen Sie *Markdown: Open Preview to the Side*. Ein Foliensatz ist eine Markdown-Datei, +deren Front-Matter `slides: true` enthält: + +```markdown +--- +slides: true +paginate: true +data: + who: World +--- + +# Hello {{ .who }} + +--- + +<!-- _kind: title --> +# Page {{ page }} of {{ total }} +``` + +Dateien mit `marp: true` werden ebenfalls in der Vorschau gezeigt, es sei denn, Marp for VS Code ist installiert: +Beide Erweiterungen würden dasselbe Dokument rendern. Die Einstellung +`goSlides.preview.marpDocuments` entscheidet: `auto` (der Standard), `always` oder +`never`. + +Die Vorschau ist go-slides selbst, nach WebAssembly kompiliert und von der +Erweiterung geladen: kein Binary nötig, und sie funktioniert auf [vscode.dev](https://vscode.dev). Sie liest +aus dem Workspace, was der Foliensatz verwendet – `slides.yaml` oder `slides.hcl` neben +dem Foliensatz, die [Kinds]({{< relref "kinds" >}})-Dateien, die diese benennen, Dateien, die eine +[Vorlage]({{< relref "templates" >}}) einbindet, Theme-CSS – und das nur aus dem +Workspace-Ordner des Foliensatzes. + +## Scroll-Synchronisierung {#scroll-sync} + +Seit v0.2.0 der Erweiterung (go-slides v0.7.0) scrollen Editor und +Vorschau gemeinsam, in beide Richtungen, wie bei reinem Markdown: Die +Vorschau folgt der Folie und dem Block unter dem Cursor des Editors, und +Scrollen in der Vorschau bewegt den Editor. Die Einstellungen sind die von VS Code selbst, +`markdown.preview.scrollPreviewWithEditor` und +`markdown.preview.scrollEditorWithPreview`. + +Zeilen werden durch Vorlagen hindurch verfolgt. Eine Folie, die ein `{{ range }}` erzeugt, +folgt der Zeile ihrer Vorlage, und was ein `{{ include }}` hereinholt, +folgt der Zeile des Includes. Ein durch `headingDivider` aufgeteilter Foliensatz synchronisiert sich an seinen +Überschriften. + +## Präsentieren {#presenting} + +Seit v0.4.0 der Erweiterung (go-slides v0.9.0) lässt sich ein Foliensatz präsentieren, +ohne VS Code zu verlassen. Mit dem Foliensatz im Editor, über die Befehlspalette +oder das `…`-Menü des Editors: + +| Befehl | Öffnet | +|---|---| +| *go-slides: Open Overview* | ein Panel mit allen Folien in einem Raster. Eine gewählte Folie zeigt ihre Quelle im Editor; `Esc` schließt das Panel | +| *go-slides: Start Presenter View* | die Folien und daneben die Referentenansicht: den nächsten Schritt, die Notizen des Vortragenden und einen Timer | + +Die Panels liegen neben dem Editor und folgen einander: Wechseln Sie die Folie in einem, +wechseln die anderen mit. Sie folgen auch Ihren Änderungen am Foliensatz. Die Taste `p` +in den Folien öffnet die Referentenansicht. + +Um auf einem anderen Bildschirm zu präsentieren, verschieben Sie das Folien-Panel mit *View: Move +Editor into New Window* dorthin. + +Jedes Panel führt den Foliensatz in einem Sandbox-Frame aus (ohne `allow-same-origin`) unter einer +Richtlinie mit `default-src 'none'`: Der Frame lädt Bilder, Medien, Stile und Schriften +und sonst nichts und führt nur die Skripte aus, die go-slides in die Seite eingebaut hat, +benannt per Hash. Ein Skript, das der Foliensatz selbst mitbringt, läuft nicht. + +## Probleme, Vervollständigung, Hover {#problems-completion-hover} + +- **Probleme**: Fehler in Vorlagen, YAML und [HCL]({{< relref "hcl" >}}), in der + Zeile, in der sie auftreten, im Foliensatz oder in der Kinds-Datei, aus der sie stammen. +- **Vervollständigung**: Direktivenschlüssel im Front-Matter und in `<!-- -->`-Kommentaren + (mit `_` für eine Spot-Direktive), die Werte von `kind` und `_kind` (mit dem, was + das jeweilige Kind setzt), `theme`, `size`, `paginate`, `math`, `headingDivider`; innerhalb von + `{{ }}` die Vorlagenfunktionen und die Datenfelder. +- **Hover**: was eine Direktive bewirkt, was ein Kind setzt, die Argumente einer Vorlagenfunktion. + +## Export {#export} + +*go-slides: Export Slide Deck...* (auch in der Titelleiste des Editors) schreibt HTML, +Vektor-PDF, editierbares PPTX und ODP, PNG oder JPEG mit dem Befehl `slides`, genau wie +`slides -o deck.pdf deck.md` es täte; siehe [Kommandozeile]({{< relref "cli" >}}). +Dafür ist ein vertrauenswürdiger Workspace nötig. Wird `slides` nicht gefunden, bietet die Erweiterung an, +das obige `go install` in einem Terminal auszuführen. + +## Einstellungen {#settings} + +| Einstellung | Standard | | +|---|---|---| +| `goSlides.cliPath` | `""` | der Befehl `slides`; leer: `slides` im `PATH`. Eine Maschineneinstellung: Ein Workspace kann sie nicht setzen | +| `goSlides.exportType` | `pdf` | das Format, das der Exportdialog zuerst vorschlägt | +| `goSlides.exportAutoOpen` | `true` | die exportierte Datei öffnen | +| `goSlides.html` | `false` | alles rohe HTML statt der Allowlist von Marp erlauben; wird nur in einem vertrauenswürdigen Workspace beachtet | +| `goSlides.preview.marpDocuments` | `auto` | `marp: true`-Foliensätze in der Vorschau zeigen: `auto`, `always`, `never` | + +## Sicherheit {#security} + +Foliensatz und Workspace gelten als nicht vertrauenswürdig. Rohes HTML folgt der Allowlist von Marp, +es sei denn, `goSlides.html` ist in einem vertrauenswürdigen Workspace eingeschaltet (eine `slides.yaml` mit +`html: true` schaltet es in der Vorschau nicht ein), und die Vorschau von VS Code führt kein +Skript aus dem Foliensatz aus. Die Vorschau liest nur Dateien aus dem Workspace-Ordner des Foliensatzes, +folgt keinem symbolischen Link, und Vorlagen sehen keine Umgebungsvariable. +Der Export führt ein Programm aus, wartet daher auf die Workspace-Vertrauensstellung, und das Programm +wird nie vom Workspace bestimmt. + +## Performance {#performance} + +Mit dem 100-Folien-Benchmark-Foliensatz von go-slides, in VS Code 1.142 auf einem Apple M4 Max, +mit Marp for VS Code 3.6.1 in derselben Sitzung gemessen: + +| | go-slides | Marp for VS Code | +|---|---:|---:| +| Aktivierung (Laden des WebAssembly-Moduls) | 189 ms | — | +| erste Vorschau | 52 ms | 78 ms | +| Vorschau nach einer Änderung (Median) | **19 ms** | 23–24 ms | +| `.vsix` | 7,5 MB | 36,3 MB | + +Die Werte gelten für Erweiterung v0.3.0 (go-slides v0.8.0) mit einem Foliensatz von 100 Folien, +bei Eingabe eines Zeichens in Folie 50, aus abwechselnd ausgeführten Durchläufen bei einem Load Average über eine Minute unter 8. Die erste Vorschau ist schneller als die von Marp, und die Vorschau nach einer +Änderung ist es nun ebenfalls. + +### In v0.8.0 und Erweiterung v0.3.0 {#in-v080-and-extension-v030} + +go-slides v0.8.0 behält die gerenderten Folien jedes geöffneten Foliensatzes und rendert nach einer Änderung +nur die neu, deren Quelle oder geerbter Zustand sich geändert hat (Direktiven, Seiten- +nummer, Gesamtzahl, Überschrifts-IDs, Linkreferenzen, Quellzeilen); die Ausgabe ist +dieselbe wie bei einem vollständigen Rendern, Byte für Byte. Tippen in Folie 50 von 100 rendert eine +Folie neu: 2,5 ms nativ, 11,5 ms in WebAssembly. Die Vorschau von VS Code überführt dann +nur das DOM der geänderten Folie, und der Smoke-Test der Erweiterung tippt ein +Zeichen in Folie 50 zwischen zeitgemessenen Renderings, für beide Erweiterungen. Die +Referentenansicht der Erweiterung synchronisiert sich über das übergeordnete Fenster (`postMessage`). + +### In v0.7.1 und Erweiterung v0.2.2 {#in-v071-and-extension-v022} + +go-slides v0.7.1 hält geparste Themes, gepackte Stylesheets und hervorgehobenen Code +zwischen Renderings im Cache, markiert Scroll-Sync-Zeilen, ohne Byte für Byte vorzugehen, und verwendet +seine Writer wieder: Ein Rendern des Foliensatzes mit 100 Folien belegt 5,7 MB, gegenüber 52,7 MB +bei Erweiterung v0.2.0. Damit verzichtet Erweiterung v0.2.2 auf die Überschreibung `GOGC=400`. +Zum Vergleich: Erweiterung v0.2.1 brauchte 72 ms für die erste Vorschau und 44 ms +nach einer Änderung bei Standard-`GOGC` (36 ms mit `GOGC=400`), und v0.2.0 brauchte +47–51 ms. + +Erweiterung v0.2.2 maß 52 ms für die erste Vorschau und 24 ms nach einer Änderung, +auf Augenhöhe mit den 23 ms von Marp. diff --git a/content/es/_index.md b/content/es/_index.md new file mode 100644 index 0000000..0a54ac0 --- /dev/null +++ b/content/es/_index.md @@ -0,0 +1,62 @@ +--- +title: "Documentación de go-slides" +linkTitle: "Inicio" +type: docs +cascade: + type: docs +description: "Presentaciones compatibles con Marp en Go puro (CGO=0), con interpolación de plantillas de Go y kinds de diapositiva." +--- + +**`slides` convierte una presentación de Marp en una presentación lista para +proyectar, en Go puro.** Lee el Markdown que lee [Marp](https://marp.app): las +directivas de Marpit, `---` entre diapositivas, los fondos `![bg]`, las notas del +orador y los temas `default`, `gaia` y `uncover`. Y añade dos cosas que Marp no +tiene: + +- **[Plantillas de Go]({{< relref "templates" >}})** dentro de la presentación, + evaluadas antes de analizar el Markdown, de modo que una presentación puede + generar sus propias diapositivas; +- **[kinds de diapositiva]({{< relref "kinds" >}})**: una diapositiva declara lo + que es (`title`, `section`…) en lugar de repetir cómo se ve. + +{{< callout type="warning" >}} +**v0.9.0.** Estas páginas describen +[go-slides/slides v0.9.0](https://github.com/go-slides/slides/releases/tag/v0.9.0) +y se publican en `/docs/0.9/`. +{{< /callout >}} + +## Qué hace hoy {#what-it-does-today} + +| | | +| --- | --- | +| `slides build` (el comando por defecto) | la presentación como una sola página HTML: cada diapositiva es una `<section>` enmarcada en SVG que se ajusta a la ventana, el CSS del tema y un visor con teclado | +| `slides build --pdf`, `--images png`, `--image jpeg` | la presentación como PDF (una página por diapositiva, con índice) o como imágenes PNG/JPEG, sin navegador: [go-webengine](https://github.com/go-webengine) renderiza y [go-pdfkit](https://github.com/go-pdfkit) escribe el PDF, en vectores desde v0.4.0 y con el texto seleccionable: [PDF vectorial]({{< relref "cli#vector-pdf" >}}) | +| matemáticas y código | `$…$` y `$$…$$` compuestos por go-tex/math, bloques de código resaltados por go-rouge, en HTML, imágenes y PDF: [Matemáticas y código]({{< relref "math-and-code" >}}) | +| emoji | `:smile:` y 😄 se convierten en imágenes Twemoji incrustadas, de modo que no necesitan red y se dibujan en todas las salidas: [Emoji]({{< relref "emoji" >}}) (desde v0.4.0) | +| `slides build --pptx` | la presentación como archivo de PowerPoint con sus notas del orador y los metadatos de la presentación: una imagen por diapositiva en v0.4.0, como en marp-cli; **editable** desde v0.5.0 con cuadros de texto, formas, imágenes y fuentes incrustadas ([PPTX y ODP editables]({{< relref "cli#editable-pptx-and-odp" >}})) | +| `slides build --odp` | lo mismo como presentación OpenDocument (`.odp`) | +| títulos ajustados | `# <!-- fit --> Title` ocupa todo el ancho de la diapositiva en una línea, en la página y en todas las exportaciones | +| `slides expand` | la presentación como Markdown de Marp plano: plantillas evaluadas, kinds sustituidos por las directivas locales que representan | +| `slides notes` | las notas del orador | +| `slides kinds`, `slides themes` | lo que una presentación puede nombrar | +| `slides.yaml` | kinds, temas, datos, política de HTML e idioma compartidos por una carpeta de presentaciones | +| HCL | front matter `---hcl`, `slides.hcl`, archivos de kinds y de datos en HCL2, con expresiones y funciones: [HCL]({{< relref "hcl" >}}) | + +## Previsto {#planned} + +Todavía no está hecho, y se indica para que nadie tenga que adivinarlo: las +fórmulas como vectores en el PDF (ahora son mapas de bits). La diferencia con +marp-cli está en el camino: él renderiza imágenes y PDF controlando un Chrome +sin interfaz; aquí lo hacen bibliotecas de Go. + +## Páginas {#pages} + +- [Primeros pasos]({{< relref "getting-started" >}}) +- [Galería]({{< relref "gallery" >}}): doce presentaciones de ejemplo, para abrir en el playground +- [Compatibilidad con Marp]({{< relref "marp" >}}) +- [Plantillas]({{< relref "templates" >}}) +- [Kinds]({{< relref "kinds" >}}) +- [HCL]({{< relref "hcl" >}}) +- [Matemáticas y código]({{< relref "math-and-code" >}}) +- [Emoji]({{< relref "emoji" >}}) +- [Línea de comandos]({{< relref "cli" >}}) diff --git a/content/es/cli.md b/content/es/cli.md new file mode 100644 index 0000000..5ea1be1 --- /dev/null +++ b/content/es/cli.md @@ -0,0 +1,381 @@ +--- +title: "Línea de comandos" +weight: 50 +description: "Todos los comandos y opciones de slides, y el archivo de configuración slides.yaml." +tags: [cli] +--- + +{{< callout type="warning" >}} +Esta página describe v0.9.0. +{{< /callout >}} + +```text +slides [deck.md] [flags] +slides [command] [flags] deck.md +``` + +## Comandos {#commands} + +| Comando | Qué hace | +| --- | --- | +| `build` | escribe la presentación como HTML, PDF o imágenes (véase más abajo). **Es el comando por defecto**: `slides deck.md` equivale a `slides build deck.md` | +| `expand` | imprime la presentación como Markdown de Marp plano: plantillas expandidas, kinds sustituidos por las directivas que representan | +| `notes` | imprime las notas del orador | +| `kinds` | lista los kinds que la presentación puede usar y lo que fijan; `--json` (en v0.6.0) los escribe como `[{"name", "extends", "directives": [{"key", "value"}]}]`, con `extends` ya resuelto | +| `themes` | lista los temas | +| `version` | imprime la versión (también lo hace `slides --version`) | +| `completion` | imprime un script de autocompletado para el shell: `bash`, `zsh`, `fish` o `powershell` | + +`slides <command> --help` imprime las opciones de un comando. Las opciones +pueden ir antes o después de la presentación: `slides deck.md -o out.html` y +`slides -o out.html +deck.md` son equivalentes. Las opciones largas llevan dos guiones (`--html`); +`-o` es la única corta. `build` escribe `deck.html` junto a `deck.md`, salvo que +`-o` indique otra cosa. + +## Opciones {#flags} + +| Opción | Comandos | Qué hace | +| --- | --- | --- | +| `-o`, `--output file` | `build`, `expand`, `notes` | archivo de salida; `-` para la salida estándar | +| `-I`, `--input-dir dir` | `build` | convierte los archivos Markdown de `dir`, de forma recursiva (en v0.6.0); véase más abajo | +| `-P`, `--parallel n` | `build` | presentaciones construidas a la vez (en v0.6.0): 5 por defecto, como máximo el número de núcleos; con 1 se construyen una tras otra | +| `-w`, `--watch` | `build` | vuelve a construir cuando cambia una presentación, un tema, un archivo de kinds o de datos, o la configuración, y recarga las páginas abiertas (en v0.6.0); véase más abajo | +| `-s`, `--server dir` | `build` | sirve un directorio y convierte sus presentaciones bajo demanda (en v0.6.0); véase más abajo | +| `-p`, `--preview` | `build` | abre el resultado en el navegador por defecto (en v0.6.0): las páginas HTML con `--watch` (implícito), el servidor con `--server` | +| `--host addr` | `build` | la dirección en la que escucha `--server`, `127.0.0.1` por defecto (en v0.6.0) | +| `--template bespoke\|bare` | `build` | la plantilla de la página HTML (en v0.6.0): `bespoke`, la de por defecto, con el script de presentación, el controlador en pantalla y las transiciones, o `bare`, con las diapositivas apiladas (`--bare`) | +| `--bespoke.osc`, `--bespoke.progress`, `--bespoke.transition` | `build` | las opciones de la plantilla bespoke, con los valores por defecto de marp-cli (en v0.6.0): controlador activado, **barra de progreso desactivada** (antes se dibujaba siempre), transiciones activadas; `--bespoke.osc=false` desactiva una | +| `-c`, `--config-file file`, `--no-config-file` | `build` | el archivo de configuración de marp-cli, JSON o YAML (en v0.6.0); véase más abajo. `--config` es el archivo propio de `slides` | +| `--browser`, `--browser-path`, `--browser-protocol`, `--browser-timeout` | `build` | se aceptan y se ignoran con un aviso (en v0.6.0): no hay navegador | +| `--pptx-editable`, `--no-parallel` | `build` | las grafías de marp-cli para `--pptx` y `-P 1` (en v0.6.0) | +| `--config file` | todos | archivo de configuración; por defecto `slides.yaml` junto a la presentación, si existe | +| `--no-config` | todos | no lee ningún archivo de configuración | +| `--theme-set path` | todos | un archivo CSS de tema, o un directorio de ellos (repetible) | +| `--kinds file` | todos | un archivo YAML de kinds (repetible) | +| `--data file` | todos | un archivo YAML o JSON con datos para las plantillas (repetible) | +| `--set key=value` | todos | un valor para las plantillas (repetible) | +| `--no-template` | todos | no expande las plantillas de Go | +| `--strict-yaml` | todos | analiza las directivas como YAML estricto, como hace Marpit (Marp Core las analiza con más tolerancia) | +| `--no-emoji` | todos | deja los `:shortcodes:` y los emoji Unicode como texto en lugar de dibujarlos como imágenes (desde v0.4.0: [Emoji]({{< relref "emoji" >}})) | +| `--html` | `build` | permite todo el HTML sin procesar; por defecto, la lista de permitidos de Marp Core | +| `--lang code` | `build` | el `lang` de la página cuando la presentación no define ninguno | +| `--bare` | `build` | apila las diapositivas, sin el script de presentación | +| `--pdf` | `build` | escribe un PDF, una página por diapositiva, con índice: texto y formas en vectores (también se elige con `-o file.pdf`) | +| `--pdf-raster` | `build` | escribe el PDF con cada página como una imagen de la diapositiva, como hacía v0.3.0 | +| `--notes` | `build` | escribe las notas del orador en un archivo de texto, con las diapositivas separadas por `---` como hace marp-cli (en v0.6.0; también se elige con `-o file.txt`). El comando `notes` las imprime con otra disposición | +| `--pdf-notes` | `build` | añade las notas del orador de cada diapositiva a su página del PDF como anotación de nota, como hace marp-cli (en v0.6.0). Pide un PDF cuando nada más indica qué escribir | +| `--pdf-outlines`, `--pdf-outlines.pages`, `--pdf-outlines.headings` | `build` | los marcadores del PDF (en v0.6.0): «Page N» por diapositiva y los títulos anidados debajo. Activados por defecto aquí, a diferencia de marp-cli; `=false` desactiva uno. Véase más abajo | +| `--images png\|jpeg` | `build` | escribe una imagen por diapositiva: `deck.001.png`, … | +| `--image png\|jpeg` | `build` | escribe la primera diapositiva como imagen (también se elige con `-o file.png` o `-o file.jpg`) | +| `--pptx` | `build` | escribe un archivo de PowerPoint con sus notas del orador (también se elige con `-o file.pptx`): editable desde v0.5.0, una imagen por diapositiva en v0.4.0 | +| `--pptx-raster` | `build` | escribe un archivo de PowerPoint con una imagen a diapositiva completa por diapositiva, como hace marp-cli (desde v0.5.0); `--image-scale` vale 2 por defecto | +| `--odp` | `build` | escribe una presentación OpenDocument con sus notas del orador (también se elige con `-o file.odp`): editable desde v0.5.0 | +| `--odp-raster` | `build` | escribe una presentación OpenDocument con una imagen por diapositiva (desde v0.5.0) | +| `--jpeg-quality n` | `build` | calidad JPEG, de 1 a 100 (85 por defecto) | +| `--image-scale n` | `build` | píxeles del dispositivo por píxel CSS para las imágenes y el PDF (1 por defecto); con 2 se obtienen 2560×1440 en 16:9, con el texto y los vectores dibujados a esa densidad, no ampliados | +| `--allow-local-files` | `build` | permite que las imágenes y el PDF lean archivos locales, solo desde el directorio de la presentación; se rechaza por defecto, como en marp-cli | +| `--theme name\|file.css` | `build` | sustituye el tema de la presentación (en v0.6.0): un nombre de tema o un archivo CSS, que pasa a ser el tema sea cual sea el `@theme` que declare; un directorio se rechaza, use `--theme-set` | +| `--title`, `--description`, `--author`, `--keywords`, `--url`, `--og-image` | `build` | los metadatos de la página (en v0.6.0), por encima de las directivas `title`, `description`, `author`, `keywords`, `url` e `image` de la presentación; véase más abajo | + +## Varias presentaciones y un directorio {#several-decks-and-a-directory} + +En v0.6.0, `build` admite varias presentaciones, un directorio (las +presentaciones que contiene, buscadas de forma recursiva) o un patrón, y +construye hasta `--parallel` de ellas a la vez. `-` es la entrada estándar; a +diferencia de marp-cli, no se lee una tubería salvo que `-` lo indique. + +Un patrón se interpreta como lo hace el glob de marp-cli (`**` y `{a,b}` en +v0.7.0; escríbalo entre comillas para que el shell se lo deje a `slides`): `*`, +`?`, `[a-z]` y `{md,txt}` dentro de un nombre, `**` para cualquier número de +directorios, de modo que `'docs/**/*.md'` toma las presentaciones de todo el +árbol. Un comodín no alcanza los nombres que empiezan por un punto, no se busca +nada en `node_modules`, se distinguen mayúsculas y minúsculas (`*.md` no +encuentra `UPPER.MD`), y se convierte todo lo que el patrón encuentre, sea cual +sea su extensión. Un patrón que no encuentra nada es un error. + +- **`-I dir`** busca los archivos Markdown (`.md`, `.mdown`, `.markdown`, + `.markdn`, en minúsculas) dentro de `dir`, como hace marp-cli: no se mira en + `node_modules` ni en los directorios que empiezan por un punto. Con `-o out`, + cada presentación se escribe en su ruta bajo `dir`, replicada bajo `out`: + `dir/sub/b.md` da `out/sub/b.html` (`b.pdf`, `b.001.png`, `b.txt`, … según las + opciones). Sin `-o`, las salidas van junto a sus presentaciones. Indicar + presentaciones además de `-I` es un error, y también lo es `-o file` con varias + presentaciones (`-o -` está permitido). +- **`-P n`** construye `n` presentaciones a la vez. El renderizado de una + presentación ya usa los núcleos, así que `n` está limitado por su número; con + `-o -` las presentaciones se construyen una tras otra. Los mensajes de cada + presentación se mantienen juntos, y una presentación que falla no detiene a las + demás: el estado de salida es 1 y el mensaje lista los fallos. + +## Transiciones y la página de presentación {#transitions-and-the-presenting-page} + +En v0.6.0 funciona la directiva `transition` de marp-cli: `<!-- transition: +fade 1s -->`, un nombre y una duración opcional, para la diapositiva y las +siguientes, o `<!-- _transition: cover -->` para una sola. Los nombres +incorporados son los de marp-cli: `clockwise`, `counterclockwise`, `cover`, +`coverflow`, `cube`, `cylinder`, `diamond`, `drop`, `explode`, `fade`, +`fade-out`, `fall`, `flip`, `glow`, `implode`, `in-out`, `iris-in`, `iris-out`, +`melt`, `overlap`, `pivot`, `pull`, `push`, `reveal`, `rotate`, `slide`, `star`, +`swap`, `swipe`, `swoosh`, `wipe`, `wiper` y `zoom`; `none` la desactiva. La +página la reproduce con la **View Transition API** del navegador cuando el +espectador sale de la diapositiva que la tiene, hacia delante, y cuando vuelve a +ella; los navegadores sin esa API cambian de diapositiva como antes, y un +espectador que pide movimiento reducido obtiene un simple fundido. Un tema puede +aportar sus propios fotogramas clave, con el nombre `marp-transition-NAME` (o +`marp-outgoing-` y `marp-incoming-`, y `-backward`), que se usan antes que los +incorporados. + +La página incluye el **controlador en pantalla** (anterior, página, siguiente, +pantalla completa), que aparece cuando se mueve el puntero o una tecla y se +oculta pasado un momento (`--bespoke.osc=false` lo elimina) y, con +`--bespoke.progress`, una barra de progreso. **La barra de progreso está ahora +desactivada por defecto**, como en marp-cli; antes se dibujaba siempre. + +### Vista del presentador {#presenter-view} + +En v0.7.0 la página incluye la vista del presentador de marp-cli. Pulse `p`, o +el último botón del controlador, y se abre una ventana con la diapositiva +actual, el paso siguiente, sus notas del orador, la página, la hora y un +cronómetro (haga clic en el cronómetro para reiniciarlo; `+` y `-` cambian el +tamaño de las notas; la barra entre las diapositivas y las notas se puede +arrastrar). Las dos ventanas se siguen en ambos sentidos: las teclas de flecha, +un clic en el paso siguiente o en Anterior/Siguiente en cualquiera de las dos +mueve la otra, un fragmento cada vez. + +Funciona como la de marp-cli, según se lee en sus fuentes de la versión 4.5.1: la +ventana es `?view=presenter&sync=KEY` del mismo archivo, el paso siguiente es la +misma página como `?view=next` dentro de un marco, y las ventanas comparten su +posición mediante `localStorage` (`bespoke-marp-sync-KEY`), de modo que no +necesita servidor ni red, y no funciona en un navegador sin `localStorage` (el +botón lo indica). Las notas son texto: `&`, `<` y `>` se escapan, por lo que una +nota nunca puede ser marcado. Están en la página, ocultas, para que la vista del +presentador las tome, lo que las mantiene fuera de lo que un lector de pantalla +lee al público. Las páginas `--bare` no tienen nada de esto. La vista añade +unos 10 KB a una página. + +### Vista general {#overview-view} + +En v0.8.0, pulse `o` (o `Escape`: marp-cli alterna con ambas), el botón de +cuadrícula del controlador o el contador de páginas de la vista del presentador, +y todas las diapositivas se muestran enteras en una cuadrícula sobre la página. +Las teclas de flecha eligen una diapositiva (arriba y abajo, por filas), `Home` +y `End` van a los extremos, y las demás ventanas la siguen; `Enter`, `Space` o +un clic eligen una diapositiva y, sobre la página, cierran la vista general. +Sobre la vista del presentador, elegir deja la vista abierta (el presentador +escoge la diapositiva que se muestra y sigue mirando). El botón «Close +overview», `o` y `Escape` la cierran. Es la de marp-cli: la misma página otra +vez dentro de un marco en `?view=overview&sync=KEY`, que comparte la posición +mediante `localStorage`, sin red. Las diapositivas de la cuadrícula admiten el +foco y llevan la etiqueta «Slide N», y la vista general cerrada es inerte, de +modo que quien use el teclado o un lector de pantalla nunca cae en ella. Las dos +vistas juntas añaden unos 15 KB a una página. + +### Archivos de configuración {#configuration-files} + +marp-cli lee sus opciones de un archivo en el directorio de trabajo, y `slides` +también, cuando el archivo es de datos: `.marprc` (YAML; JSON también es YAML), +`.marprc.json`, `.marprc.yaml`, `.marprc.yml`, `.config/marprc*`, o la clave +`marp` de `package.json`; `--config-file` (`-c`) nombra cualquier archivo JSON o +YAML, `marp.config.json` para uno de ellos. Solo se busca en el directorio de +trabajo, no en sus padres, como hace la búsqueda de marp-cli. Un archivo de +JavaScript o TypeScript (`marp.config.js` ...) es un programa, que `slides` no +ejecuta: lo avisa y continúa. Las opciones son las de la documentación de +marp-cli que tienen sentido aquí: `title`, `description`, `author`, `keywords`, +`url`, `ogImage`, `lang`, `theme`, `themeSet`, `html`, `allowLocalFiles`, +`template`, `bespoke`, `inputDir`, `output`, `parallel`, `pdf`, `pptx`, `notes`, +`pdfNotes`, `pdfOutlines`, `images`, `image`, `imageScale`, `jpegQuality`, +`watch`, `server`, `preview`; las rutas son relativas al archivo. La línea de +comandos prevalece. `engine`, `options` y `browser*` necesitan el motor de Marp +y un navegador, y se ignoran con un aviso, igual que las opciones `--browser*`: +los scripts escritos para marp-cli siguen funcionando. (`slides.yaml` y +`slides.hcl`, con los kinds y los datos de las plantillas, son aparte y se leen +como antes.) + +## Observar, servir y previsualizar {#watch-serve-and-preview} + +En v0.6.0, con las rutas y el protocolo de recarga en vivo de marp-cli (leídos +de sus fuentes de la versión 4.5.1 y comparados con un `marp -s` en ejecución). + +**`slides --server dir`** (`-s`) sirve el directorio en +`http://127.0.0.1:8080/` (la variable de entorno `PORT` cambia el puerto; un +puerto ya en uso es un error que lo dice): + +| Petición | Respuesta | +| --- | --- | +| `/deck.md` | la presentación como HTML, convertida bajo demanda | +| `/deck.md?pdf`, `?pptx`, `?png`, `?jpg` (o `?jpeg`), `?notes` (o `?txt`) | la presentación convertida como pide la consulta; el PPTX es un adjunto | +| un directorio | su `index.md` o `PITCHME.md` como página; si no, un índice con un enlace al PDF y otro al PPTX de cada presentación | +| cualquier otro archivo | el archivo | +| una presentación que no se convierte | 503 con el error | + +Una página abre un WebSocket en `/.__marp-cli-watch-notifier__/<id>`, donde el id +es el SHA-256 de la ruta de la presentación; el servidor envía `ready` y luego +`reload` cuando cambia la presentación o cualquier cosa bajo el directorio. Un +directorio solicitado sin la barra final se redirige a la versión con barra +(marp-cli sirve su índice ahí, lo que rompe los enlaces relativos de la página). + +**`--watch`** (`-w`) construye las presentaciones y luego vuelve a construir la +que cambia y, cuando cambia un archivo de `--theme-set`, un archivo de kinds o de +datos o la configuración, todas. Las páginas que escribe abren un socket en el +loopback (puerto 37717, o el siguiente libre) y se recargan cuando se vuelve a +construir su presentación. Una presentación que no se construye se notifica y +se sigue observando. **`--preview`** (`-p`) abre el navegador por defecto en el +servidor, o en las páginas HTML recién construidas; no hay ventana propia, ya que +`slides` no tiene un navegador que controlar. + +El servidor es una puerta de entrada a la máquina, así que es más estricto que el +de marp-cli, que escucha en todas las interfaces y no comprueba nada: + +- escucha en **127.0.0.1**; `--host` lo cambia, con un aviso, y una dirección + comodín ya no permite comprobar la cabecera `Host`; +- la cabecera **`Host`** debe ser un nombre de loopback (`localhost`, + `127.0.0.1`, `[::1]`, `*.localhost`) o el host en el que escucha, lo que impide + el DNS rebinding; +- el **`Origin`** de un WebSocket debe ser el del propio servidor; los sockets del + modo watch aceptan además el origen `null` de una página abierta desde un + archivo; se rechaza una petición de otro sitio que no sea una navegación (un + script, una imagen, un `fetch`); +- una ruta se limpia, se resuelven sus enlaces simbólicos y el resultado debe + quedar dentro del directorio: `..` y un enlace que salga de él se rechazan, y + también `?pdf` de una presentación fuera de él; los nombres que empiezan por un + punto (`.git`, `.env`) no se sirven; +- solo `GET` y `HEAD`; pocas conversiones a la vez; como máximo 4096 directorios + observados, y ninguno en `node_modules` ni bajo un punto. + +## Metadatos de la página {#page-metadata} + +La línea de comandos prevalece sobre la presentación, como en marp-cli, donde las +opciones se añaden a la presentación como un último comentario de directivas. +Reglas leídas de marp-cli 4.5.1 y fijadas con pruebas contra su salida (en +v0.6.0): + +- Una presentación que no define `title` toma el texto de su primer título del + nivel más bajo, tal como está escrito (`# **Bold** title` da `**Bold** title`). + Sin ningún título no hay `<title>`. +- Las `keywords` se separan por comas, se recortan y se deduplican; una lista del + front matter se toma tal cual. Como marp-cli, la página incluye la etiqueta + `keywords` a partir de la segunda palabra clave, no para una sola. +- Una `url` no válida (no absoluta) se ignora con un aviso y se conserva el valor + anterior; una válida escribe además `<link rel="canonical">` y `og:url`. +- `author` y `description` vienen con sus gemelos de Open Graph; `og:image:alt` y + la tarjeta grande de Twitter necesitan un título y una imagen. + +## Imágenes, PDF, PPTX y ODP {#images-pdf-pptx-and-odp} + +`-o` con una extensión `.pdf`, `.pptx`, `.odp`, `.png` o `.jpg` elige el formato, +como hace marp-cli. Los directorios de salida se crean. Un PDF tiene una página +por diapositiva, del tamaño de la diapositiva, y un índice de las diapositivas y +sus títulos. + +### Notas e índice del PDF {#pdf-notes-and-outline} + +En v0.6.0 el PDF admite las dos opciones de marp-cli, y se comparan con los PDF +que escribe marp-cli 4.5.1 para la misma presentación: + +- `--pdf-notes` coloca las notas del orador de cada diapositiva en su página como + anotación de nota: un icono amarillo de nota abajo a la izquierda cuyo + contenido son las notas (varios comentarios unidos por una línea en blanco) y + cuyo título es el `author` de la presentación. `--pdf-notes` solo pide un PDF, + como en marp-cli. +- El índice contiene un marcador en negrita «Page N» por diapositiva y, debajo, + los títulos de la diapositiva anidados por nivel (`--pdf-outlines.pages` y + `--pdf-outlines.headings` eligen las dos partes). **El índice está activado por + defecto aquí y desactivado en marp-cli.** `slides` siempre ha escrito uno; + cuesta unos cientos de bytes y hace navegable una presentación larga, así que + se mantiene el valor por defecto. `--pdf-outlines=false`, o el + `--no-pdf-outlines` de marp-cli, lo desactiva, y `=false` en `.pages` o + `.headings` suprime esa parte. El marcador de un título salta a su diapositiva; + marp-cli además se desplaza hasta el título. + +El renderizado lo hace [go-webengine](https://github.com/go-webengine/engine) y +el PDF lo escribe [go-pdfkit](https://github.com/go-pdfkit/pdfkit); las fuentes +que piden los temas incorporados (Lato, Roboto Mono) provienen de +[go-opentype/fonts](https://github.com/go-opentype/fonts) y van incrustadas, de +modo que ningún renderizado toca la red. No hay Chrome. + +`--image-scale 2` renderiza a dos píxeles del dispositivo por píxel CSS. Las +imágenes locales se rechazan salvo que se indique `--allow-local-files`, e +incluso entonces solo se leen los archivos bajo el directorio de la +presentación: una presentación ajena no puede alcanzar `~/.ssh`. Los fondos +múltiples y divididos (`![bg left:40%]`) se renderizan como lo hacen las capas de +Marp. + +`--pptx` escribe la presentación con el `WritePresentation` de +[go-ooxml](https://github.com/go-ooxml), con las notas del orador y los metadatos +de la presentación. Desde v0.5.0 las diapositivas son editables (véase más +abajo); `--pptx-raster` escribe una imagen a diapositiva completa por diapositiva, +como hace marp-cli, con una escala que vale 2 por defecto y que `--image-scale` +sustituye. LibreOffice abre el resultado. Solo se probó LibreOffice; PowerPoint y +Keynote no. + +`--odp` escribe una presentación OpenDocument, mediante el `WritePresentation` de +go-odf, con sus notas del orador; `--odp-raster` escribe una imagen por +diapositiva. + +### PPTX y ODP editables {#editable-pptx-and-odp} + +Desde v0.5.0 las diapositivas de un PPTX o de un ODP son **editables**: el PPTX de +marp-cli es una imagen por diapositiva, y su `--pptx-editable` experimental +convierte un PDF mediante LibreOffice. Aquí cada diapositiva se dibuja a partir de +su disposición, la misma que dibujan las exportaciones a PNG y PDF: + +- cada línea de texto es un cuadro de texto en su posición calculada, con las + fuentes con las que se maquetó la diapositiva, **incrustadas en el archivo**, + de modo que se ve igual en una máquina sin Lato o Inter (comprobado con + LibreOffice 25.8), con negrita, cursiva, colores, espaciado entre letras y + enlaces en los que se puede hacer clic; +- los fondos, los bordes y los cuadros redondeados son formas; los degradados son + rellenos degradados en el PPTX e imágenes en el ODP (un degradado de ODF tiene + dos colores); +- las imágenes se almacenan una sola vez, por muchas diapositivas que las + muestren; +- lo que una diapositiva no puede expresar (un elemento bajo `opacity`, un + `filter`, una sombra difuminada, una fórmula) es una imagen de ese elemento, con + su texto conservado encima. + +Al renderizarla de nuevo con LibreOffice, una diapositiva queda entre el 0,04 y el +2 % de los píxeles de la exportación a PNG. `--pptx-raster` y `--odp-raster` +mantienen una imagen por diapositiva. + +### PDF vectorial {#vector-pdf} + +Desde v0.4.0 el PDF se dibuja en vectores: go-webengine pinta la diapositiva ya +maquetada en el PDF en lugar de en píxeles. El texto es texto, con las fuentes con +las que se maquetó la diapositiva, incrustadas en subconjunto con un mapa +`/ToUnicode`: se puede seleccionar, buscar y copiar. Los fondos, los bordes y los +degradados son trazados y sombreados, una imagen que aparece en muchas +diapositivas se incrusta una sola vez y los enlaces admiten clic. La presentación +de prueba de 100 diapositivas pesa 758 KiB, frente a 837 KiB de marp-cli y 10 MiB +del PDF de imágenes de v0.3.0, y se escribe en 0,3 s. + +Un elemento sin forma vectorial aquí (bajo `opacity` < 1, con un `filter` o una +rotación, un `box-shadow` difuminado, un degradado cuya opacidad se atenúa) se +dibuja como un mapa de bits solo de ese elemento, con su texto superpuesto de +forma invisible para que siga siendo seleccionable. Las fórmulas siguen siendo +mapas de bits. `--pdf-raster` escribe una imagen por página, como hacía v0.3.0. + +## `slides.yaml` {#slidesyaml} + +Un archivo de configuración junto a la presentación se aplica a ella, de modo que +una carpeta de presentaciones comparte uno solo: + +```yaml +kinds: kinds.yaml # a mapping of kinds, or file(s) holding one +themes: # theme CSS files or directories + - themes/ +data: # template data + company: Example Inc. +html: false # allow all raw HTML +lang: en +``` + +Kinds: los `kinds:` de la propia presentación prevalecen sobre los del archivo y +los de la línea de comandos. Datos: los `data` del archivo y de la línea de +comandos prevalecen sobre el front matter de la presentación, de modo que una +misma presentación se puede construir con datos distintos. + +## Previsto {#planned} + +Todavía no está hecho: las fórmulas como vectores en el PDF. Cuando lleguen, será +un comando u opción aquí. diff --git a/content/es/emoji.md b/content/es/emoji.md new file mode 100644 index 0000000..86abb0b --- /dev/null +++ b/content/es/emoji.md @@ -0,0 +1,82 @@ +--- +title: "Emoji" +weight: 36 +description: "Los códigos cortos y los emoji Unicode se convierten en imágenes Twemoji, sin red: en la página HTML, las imágenes, el PDF, PowerPoint y OpenDocument." +tags: [emoji] +--- + +{{< callout type="warning" >}} +Los emoji llegaron en v0.4.0; v0.3.0 no tenía ninguno. +{{< /callout >}} + +Escriba un emoji como código corto o como el propio carácter: + +```markdown +# Launch :rocket: 🎉 + +Shortcodes :smile: :+1: :heart_eyes: and Unicode 😄 👍🏽 👩‍💻 🇫🇷 1️⃣. +``` + +Cada uno se convierte en la imagen [Twemoji](https://github.com/jdecked/twemoji) +que dibuja Marp Core, con el marcado que escribe Marp Core: + +```html +<img class="emoji" draggable="false" alt="😄" src="data:image/svg+xml;base64,…" data-marp-twemoji=""/> +``` + +## Sin red {#no-network} + +El `src` de Marp Core es una URL de una CDN, así que una presentación mostrada +sin conexión, impresa o convertida a PDF en una máquina sin conexión muestra +imágenes rotas. go-slides coloca el propio SVG en un URI `data:`. Los gráficos +(4009, Twemoji 17.0.3, Emoji 17.0) van incrustados en el programa desde el +módulo en Go puro [go-icons/twemoji](https://github.com/go-icons/twemoji): 2 MB +comprimidos, que solo se leen cuando una presentación contiene algún emoji. + +Las imágenes las dibuja por igual la página HTML, `--images`/`--image`, `--pdf`, +`--pptx` y `--odp`: go-webengine rasteriza el SVG. Los 4009 gráficos se analizan +y se dibujan con su rasterizador de SVG; una prueba lo comprueba. Desde v0.5.0 +ese rasterizador es [go-gfx/oksvg](https://github.com/go-gfx/oksvg) v0.2.0: +`clipPath`, `fill-rule: evenodd` y los comandos de arco se dibujan ahora como los +dibuja Chrome, lo que cambió un Twemoji y otros 211, y los logotipos SVG ya no +salen deformados. + +La regla que ajusta el tamaño de un emoji al del texto (`img[data-marp-twemoji]`, +un cuadrado de un `em`, alineado con la línea base) se añade al tema igual que +lo hace Marp Core, de modo que un tema propio también la recibe. + +## Qué se convierte {#what-is-converted} + +Las reglas son las de Marp Core; una prueba las compara con el HTML que escribe +Marp Core 4.4.0 para una presentación con todos los casos. + +| | | +|---|---| +| `:smile:`, `:+1:`, `:heart_eyes:` | los 1903 nombres de markdown-it-emoji, en texto, títulos, enlaces, tablas, `header` y `footer`; no en código, URL ni en el texto alternativo de una imagen | +| `😄`, `👩‍💻`, `🇫🇷`, `1️⃣`, `👍🏽` | emoji Unicode, en el texto **y en el código**: Marp Core reescribe el HTML del código ya renderizado | +| `✔`, `©`, `™`, `▶` | se quedan como texto: los caracteres cuya presentación por defecto es de texto solo son emoji con U+FE0F (`✔️`); `❤` es la excepción | +| `☺︎` | se queda como texto: un U+FE0E tras un emoji pide el glifo plano | +| `12:30:45`, `:not_a_name:` | se quedan tal como están escritos | + +El `id` de un título y el índice usan el emoji, no el nombre del código corto: +`# Launch :rocket:` tiene el id `launch-%F0%9F%9A%80`, como en Marp. + +Donde go-slides difiere de Marp Core, convierte más: `✌️ ☝️ ✍️ 🏋️` y algunos +otros caracteres de presentación textual por defecto que el emoji-regex de Marp +Core deja como texto, además de las pocas secuencias que Twemoji dibuja y que +Unicode no lista. + +## Desactivarlo {#turning-it-off} + +`--no-emoji` (en código, `slides.Config{NoEmoji: true}`) deja los códigos cortos +y los emoji Unicode como el texto con el que se escribieron, que es el +`emoji: false` de Marp Core. + +## Atribución {#attribution} + +Los gráficos son Twemoji, Copyright 2020 Twitter, Inc y otros colaboradores, +con licencia [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) y +mantenidos por el proyecto [jdecked/twemoji](https://github.com/jdecked/twemoji). +Una presentación que los muestre debe acreditarlos, por ejemplo en un pie de +página o en una diapositiva final. La tabla de códigos cortos es la de +markdown-it-emoji (MIT). diff --git a/content/es/gallery.md b/content/es/gallery.md new file mode 100644 index 0000000..cf0dae4 --- /dev/null +++ b/content/es/gallery.md @@ -0,0 +1,26 @@ +--- +title: "Galería" +weight: 12 +description: "Doce presentaciones pequeñas, cada una con un solo tema bien mostrado: temas, kinds, plantillas, HCL, matemáticas, imágenes, código, emoji, transiciones y una charla completa. Ábralas en el playground." +tags: [gallery, examples] +--- + +Doce presentaciones pequeñas de la carpeta +[`examples/`](https://github.com/go-slides/slides/tree/main/examples) de +go-slides, cada una con un solo tema bien mostrado. Las miniaturas son la primera +diapositiva de cada presentación, renderizada por el propio go-slides al +construir este sitio; la CI construye cada presentación en HTML y PDF, así que +ninguna se rompe. + +**Open in the playground** carga la presentación en su navegador, mediante un +enlace que no envía nada a ningún servidor. Una presentación con más archivos +además de `deck.md` (un archivo de kinds, partes incluidas, imágenes) no puede +viajar en un enlace: descargue su **.zip** e impórtelo con el panel de archivos +del playground, o descomprímalo y constrúyalo con +`slides build --allow-local-files deck.md`. + +Las imágenes son SVG originales de los autores de go-slides, publicados bajo +[CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/), y las +presentaciones que las usan lo indican. + +{{< gallery >}} diff --git a/content/es/getting-started.md b/content/es/getting-started.md new file mode 100644 index 0000000..a8f17e8 --- /dev/null +++ b/content/es/getting-started.md @@ -0,0 +1,132 @@ +--- +title: "Primeros pasos" +weight: 10 +description: "Una primera presentación, construida como HTML y expandida de nuevo a Markdown de Marp." +tags: [start] +--- + +## Instalación {#install} + +Go 1.27.2 o posterior: + +```sh +go install github.com/go-slides/slides/cmd/slides@latest +``` + +`slides` es un único binario estático de Go, sin cgo: no hay nada más que +instalar, ni Node ni Chrome. Esta página describe v0.9.0. + +## Una primera presentación {#a-first-deck} + +Cualquier presentación de Marp sirve. Guarde esto como `talk.md`: + +```markdown +--- +marp: true +theme: default +paginate: true +footer: "{{ .talk }}" +data: + talk: Pure Go slides + topics: [Why, How, Next] +--- + +<!-- _kind: title --> +# {{ .talk }} + +{{ range .topics }} +--- + +# {{ . }} +{{ end }} +``` + +El front matter es el de Marp, más `data:`. El cuerpo es una plantilla de Go: +`range` escribe una diapositiva por tema, cada una empezando por `---`, y la +primera diapositiva es del kind (tipo de diapositiva) incorporado `title` (sin +encabezado, sin pie de página, sin número de página). + +## Constrúyala {#build-it} + +```sh +slides talk.md # writes talk.html beside the deck +slides -o slides.html talk.md # or somewhere else; -o - for standard output +``` + +`slides` sin ningún comando equivale a `slides build`. Las opciones pueden ir +antes o después de la presentación. Abra el HTML en un navegador; el teclado +permite moverse entre diapositivas. + +## Exportar imágenes, PDF, PPTX y ODP {#export-images-pdf-pptx-and-odp} + +```sh +slides talk.md --pdf # one page per slide, with an outline +slides talk.md --images png # talk.001.png, talk.002.png, … +slides talk.md --image jpeg # the first slide only +slides talk.md -o out/slides.pdf # the extension picks the format; out/ is created +slides talk.md --images jpeg --jpeg-quality 90 +slides talk.md --images png --image-scale 2 # 2560×1440 for 16:9 +slides talk.md --pdf --allow-local-files # images from under the deck's directory +slides talk.md --pptx # or -o talk.pptx, with notes +slides talk.md --odp # or -o talk.odp: the same, as OpenDocument +``` + +No interviene ningún navegador: go-webengine renderiza las diapositivas y +go-pdfkit escribe el PDF. Las imágenes locales se rechazan salvo que se indique +`--allow-local-files`. Desde v0.5.0 las diapositivas de un PPTX o un ODP son +editables (cuadros de texto, formas e imágenes, con las fuentes incrustadas), +mientras que las de marp-cli son imágenes; `--pptx-raster` conserva las imágenes +([PPTX y ODP editables]({{< relref "cli#editable-pptx-and-odp" >}})). Desde +v0.4.0 el PDF es vectorial y su texto se puede seleccionar (v0.3.0 escribía una +imagen por página). Véase [Línea de comandos]({{< relref "cli#vector-pdf" >}}). + +## Expandirla {#expand-it} + +```sh +slides expand talk.md +``` + +imprime la misma presentación como **Markdown de Marp plano** (salida de `main` +en `4ae5c49`, 2026-10-18): + +```markdown +--- +marp: true +theme: default +paginate: true +footer: "Pure Go slides" +--- +<!-- +_class: "lead" +_footer: "" +_paginate: "false" +--> + + +# Pure Go slides + + +--- + +# Why + +--- + +# How + +--- + +# Next +``` + +Las cuatro diapositivas que generó `range`, el pie de página con su valor y el +kind `title` sustituido por las directivas locales que representa. Cualquier +herramienta que lea Marp (marp-cli, la extensión de VS Code) puede continuar a +partir de ahí, de modo que nada de lo escrito con las ampliaciones de go-slides +queda atrapado. + +## Siguientes pasos {#next} + +- [Compatibilidad con Marp]({{< relref "marp" >}}): qué se lee tal como lo lee Marp. +- [Plantillas]({{< relref "templates" >}}) y [Kinds]({{< relref "kinds" >}}): las dos ampliaciones. +- [Línea de comandos]({{< relref "cli" >}}): todos los comandos y opciones. diff --git a/content/es/hcl.md b/content/es/hcl.md new file mode 100644 index 0000000..a0df057 --- /dev/null +++ b/content/es/hcl.md @@ -0,0 +1,158 @@ +--- +title: "HCL" +weight: 45 +description: "HCL2 allí donde una presentación admite YAML: un front matter en HCL, slides.hcl, archivos de kinds y de datos, con expresiones y funciones." +tags: [hcl, configuration] +--- + +{{< callout type="warning" >}} +HCL llegó en v0.3.0 ([go-slides/slides#13](https://github.com/go-slides/slides/pull/13)). +{{< /callout >}} + +Allí donde `slides` lee YAML, también lee la sintaxis nativa de +[HCL2](https://github.com/hashicorp/hcl/blob/main/hclsyntax/spec.md), por sus +expresiones y sus funciones. + +| Dónde | YAML | HCL | +| --- | --- | --- | +| front matter | `---` … `---` | `---hcl` … `---` | +| configuración | `slides.yaml` | `slides.hcl`, que se busca primero | +| archivos de kinds | `--kinds kinds.yaml`, `kinds: kinds.yaml` | cualquier archivo llamado `*.hcl` | +| archivos de datos | `--data data.yaml` o `.json` | cualquier archivo llamado `*.hcl` | + +Los comentarios de directivas (`<!-- class: invert -->`) siguen siendo YAML, como +en Marp, y el cuerpo de la presentación sigue siendo una +[plantilla de Go]({{< relref "templates" >}}). + +## Un front matter en HCL {#a-front-matter-in-hcl} + +```hcl +---hcl +theme = "gaia" +paginate = true +title = "Quarterly review" +data = { + company = "ACME" + quarter = 3 +} +header = format("%s — Q%d", data.company, data.quarter) +footer = "${upper(data.company)} · ${page()}/${total()}" +date = formatdate("D MMMM YYYY", now()) + +kind "chapter" { + extends = "section" + class = ["lead", "invert"] +} +--- + +# {{ .title }} +``` + +- Los **atributos** son las claves de YAML con los mismos nombres: las directivas + de Marp (`theme`, `paginate`, `header`, `footer`, `class`, `backgroundColor`…), + los metadatos (`title`, `author`…), `template`, `delims`, `kinds` (rutas de + archivos) y `data`, un objeto. +- Los bloques **`kind "<name>" { … }`** definen [kinds]({{< relref "kinds" >}}) (tipos + de diapositiva), con las directivas locales y `extends`. +- Los **valores** llegan a las directivas en las formas de cadena que les da + YAML (`true` es `"true"`, una lista de clases es una lista), de modo que una + directiva se comporta igual sea cual sea la sintaxis que la fije. `null` deja + una directiva sin definir. +- **`data`** conserva sus tipos: en las plantillas un número entero es un `int`, + los demás números son `float64`, y las listas y los objetos siguen siendo + listas y mapas, de modo que `{{ add .quarter 1 }}` funciona. +- Un valor HCL es definitivo: las plantillas de Go no se ejecutan sobre él por + segunda vez. + +## Variables {#variables} + +`data` son los datos de la plantilla: el propio `data` del front matter, +superpuesto al de `slides.hcl`, a los archivos de `--data` y a `--set`. +`"${data.company}"` funciona allí donde funcionaría `{{ .company }}`. + +Los atributos del front matter pueden hacer referencia a `data.*`, pero **no +entre sí**: `footer = title` es un error. Las directivas locales de una sola +diapositiva (`_class`…) no son identificadores de HCL; escríbalas en un +comentario. + +## Funciones {#functions} + +| | | +| --- | --- | +| `page()`, `total()` | el número de página y el número de la última página, como `{{ page }}` y `{{ total }}`: un pie de página pagina | +| `now()`, `timestamp()` | la hora, en RFC 3339, local y UTC; `formatdate` le da formato | +| `env(name)` | una variable de entorno, `""` si no está definida | +| `file(path)` | un archivo de texto UTF-8 de como máximo 1 MiB, relativo a la presentación o al archivo de configuración | +| números | `abs ceil floor log max min parseint pow signum` | +| cadenas | `chomp format formatlist join lower regex regexall regexreplace replace split strlen strrev substr title trim trimprefix trimspace trimsuffix upper` | +| colecciones | `chunklist coalesce coalescelist compact concat contains distinct element flatten keys length lookup merge range reverse slice sort values zipmap` | +| codificaciones | `csvdecode jsondecode jsonencode` | +| fechas | `formatdate timeadd` | + +Todas salvo las cuatro primeras filas son la biblioteca estándar de +[go-cty](https://github.com/zclconf/go-cty). Ninguna accede a la red. + +## slides.hcl {#slideshcl} + +```hcl +kinds = "kinds.hcl" # or a list of files +themes = ["themes/acme.css"] +data = { company = "ACME" } # or file paths +html = false +lang = "en" + +kind "quote" { + extends = "blank" + footer = "${data.company}" +} +``` + +Un atributo desconocido es un error, no una operación nula silenciosa. Un archivo +de kinds contiene bloques `kind`; sus demás atributos se ignoran, así que +`slides.hcl` puede servir como uno. Los atributos de un archivo de datos son las +claves de los datos: + +```hcl +# data.hcl +company = "ACME" +year = 2026 +motto = trimspace(file("motto.txt")) +``` + +## Errores {#errors} + +Los errores son los diagnósticos de HCL, situados en el propio archivo de la +presentación: `footer = data.compnay` en la cuarta línea de la presentación da + +``` +deck.md:4:14: Unsupported attribute: This object does not have an attribute named "compnay". +``` + +## Marp y `slides expand` {#marp-and-slides-expand} + +Marp también lee `---hcl` como el inicio de un front matter (su regla de front +matter se abre con cualquier primera línea que empiece por tres guiones) y, al no +ser el bloque un mapa YAML, no aplica nada de él: la presentación se sigue +renderizando en Marp, pero sin sus directivas. Una prueba lo fija contra la +propia salida de Marp Core. + +`slides expand` vuelve a escribir el front matter como YAML, de modo que la +presentación expandida es Marp plano y se renderiza como `slides` renderiza la +original: + +```yaml +--- +theme: "gaia" +paginate: true +title: "Quarterly review" +header: "ACME — Q3" +footer: "ACME · 1/2" +date: "19 October 2026" +--- +``` + +## Licencias {#licences} + +La compatibilidad con HCL enlaza [hashicorp/hcl](https://github.com/hashicorp/hcl) +(MPL-2.0, usado sin modificar como biblioteca) y +[zclconf/go-cty](https://github.com/zclconf/go-cty) (MIT). diff --git a/content/es/kinds.md b/content/es/kinds.md new file mode 100644 index 0000000..b04f373 --- /dev/null +++ b/content/es/kinds.md @@ -0,0 +1,94 @@ +--- +title: "Kinds" +weight: 40 +description: "Una diapositiva declara lo que es: kind nombra un conjunto predefinido de directivas locales." +tags: [kinds] +--- + +Un ***kind*** (tipo de diapositiva) es un conjunto predefinido y con nombre de +directivas locales: encabezado, pie de página, paginación, clase, fondo. Una +diapositiva declara lo que es (`title`, `section`) en lugar de repetir cómo se +ve, y el aspecto de una presentación se cambia en un único lugar. + +## Usar un kind {#using-a-kind} + +```markdown +<!-- kind: section --> +# Part two + +--- + +<!-- _kind: blank --> +![bg](photo.jpg) +``` + +- `kind:` se aplica como una directiva local: a esta diapositiva y a las + siguientes. +- `_kind:` se aplica como una directiva de una sola diapositiva: solo a esta. +- Cambiar de kind **borra** primero lo que fijó el kind anterior, de modo que una + clase o un pie de página nunca pasa de un kind al siguiente. +- Las directivas escritas en la propia diapositiva **prevalecen** sobre el kind. + +## Incorporados {#built-in} + +| Kind | Fija | +| --- | --- | +| `title` | `class: lead`, sin número de página, sin encabezado, sin pie de página | +| `section` | `class: lead` | +| `content` | nada: los valores por defecto de la presentación | +| `blank` | sin número de página, sin encabezado, sin pie de página | + +Cualquiera de ellos se puede redefinir. + +## Definir kinds {#defining-kinds} + +En el front matter: + +```yaml +--- +marp: true +kinds: + chapter: + class: invert + backgroundColor: "#1e1b4b" + paginate: false + quote: + extends: blank + class: quote +--- +``` + +`extends` parte de las directivas de otro kind. Un kind acepta las directivas +locales y `extends`. + +## Compartir kinds {#sharing-kinds} + +Los kinds de un equipo van en un único archivo que nombran todas las +presentaciones: + +```yaml +# kinds.yaml +chapter: + class: invert + paginate: false +``` + +```yaml +--- +marp: true +kinds: kinds.yaml # or a list of files +--- +``` + +o bien `--kinds kinds.yaml` en la línea de comandos, o `kinds:` en `slides.yaml`. +El orden es: los kinds incorporados, luego la línea de comandos y el archivo de +configuración y, por último, los `kinds:` de la propia presentación. + +`slides kinds talk.md` lista los kinds que puede usar una presentación y lo que +fija cada uno. + +## Salir de go-slides {#leaving-go-slides} + +`slides expand` escribe cada kind como las directivas locales de una sola +diapositiva que representa, diapositiva a diapositiva, de modo que el resultado +es Markdown de Marp plano que lee cualquier herramienta de Marp. diff --git a/content/es/marp.md b/content/es/marp.md new file mode 100644 index 0000000..4008ceb --- /dev/null +++ b/content/es/marp.md @@ -0,0 +1,87 @@ +--- +title: "Compatibilidad con Marp" +linkTitle: "Marp" +weight: 20 +description: "El Markdown que lee slides es el de Marpit y Marp Core, no un dialecto suyo." +tags: [marp] +--- + +Una presentación escrita para [Marp](https://marp.app) es una presentación de +go-slides. Las reglas siguientes son las de [Marpit](https://marpit.marp.app) y +de [Marp Core](https://github.com/marp-team/marp-core); donde go-slides difiere, +esta página lo indica. + +## Diapositivas {#slides} + +Una regla horizontal (`---` en una línea propia, como divide Marpit) inicia una +nueva diapositiva, y también lo hace un título del nivel que indique la directiva +`headingDivider`. El front matter, entre los dos primeros `---`, contiene las +directivas globales y los datos de la presentación. + +## Directivas {#directives} + +| Tipo | Dónde | Se aplica a | +| --- | --- | --- | +| global | el front matter, o `<!-- theme: gaia -->` en cualquier lugar | toda la presentación: `theme`, `style`, `headingDivider`, `lang`… | +| local | `<!-- paginate: true -->` | esta diapositiva y las siguientes | +| de una sola diapositiva (spot) | `<!-- _class: lead -->` (guion bajo) | solo esta diapositiva | + +Las directivas locales son las de Marpit: `paginate`, `header`, `footer`, +`class`, `backgroundColor`, `backgroundImage`, `backgroundPosition`, +`backgroundRepeat`, `backgroundSize`, `color`. Las directivas se analizan como +YAML tolerante, igual que hace Marp Core; `--strict-yaml` las analiza como lo +hace Marpit. + +## Imágenes {#images} + +`![bg](image.jpg)` convierte una imagen en el fondo de la diapositiva, con las +palabras clave de Marp: `bg contain`, `bg cover`, `bg 50%`, `bg left`/`bg right` +para un fondo dividido, varias imágenes `bg` una junto a otra, y los filtros de +imagen (`blur`, `sepia`, `grayscale`…). Los tamaños de las imágenes en línea +(`w:200px`, `h:`) se leen como los lee Marp. + +## Alrededor de la diapositiva {#around-the-slide} + +- **Encabezado y pie de página**: las directivas `header:` y `footer:`, en Markdown. +- **Paginación**: `paginate: true`. +- **Notas del orador**: un comentario HTML que no es una directiva es una nota. + `slides notes` las imprime. +- **Listas fragmentadas**: una lista escrita con `*` (o `1)`) aparece elemento a + elemento, como en Marp. +- **`<style scoped>`**: CSS para una sola diapositiva. +- **HTML sin procesar**: la lista de permitidos de Marp Core por defecto; `--html` + lo permite todo. +- **Emoji**: los `:shortcodes:` y los emoji Unicode se convierten en imágenes + Twemoji, como en Marp Core, pero incrustadas en lugar de cargadas desde una + CDN: véase [Emoji]({{< relref "emoji" >}}). + +## Temas {#themes} + +`default`, `gaia` y `uncover` vienen incorporados y se eligen con `theme:`. Un +tema propio es un archivo CSS con el comentario `/* @theme name */` de Marp, que +se indica con `--theme-set` o se lista bajo `themes:` en `slides.yaml`. + +## En qué difiere go-slides {#where-go-slides-differs} + +- **Plantillas.** `{{ }}` en el cuerpo es una acción de plantilla de Go (véase + [Plantillas]({{< relref "templates" >}})). Una presentación que muestre `{{` + como texto fuera de un bloque de código necesita `--no-template`, o el texto + escapado como lo escapan las plantillas de Go. Los bloques de código y los + fragmentos de código en línea se dejan tal cual. +- **Kinds** (tipos de diapositiva). `kind:` y `_kind:` son directivas que Marp no tiene (véase + [Kinds]({{< relref "kinds" >}})). `slides expand` las sustituye por las + directivas que representan, de modo que la presentación expandida es Marp + plano. +- **Salidas.** HTML, PNG, JPEG, PDF, PPTX y ODP, mediante bibliotecas de Go en + lugar de un navegador sin interfaz (véase + [Línea de comandos]({{< relref "cli#images-pdf-pptx-and-odp" >}}) para el + límite conocido). En v0.4.0, como en marp-cli, un PPTX contiene imágenes; desde + v0.5.0 sus diapositivas son cuadros de texto y formas editables, y + `--pptx-raster` conserva las imágenes. +- **Títulos ajustados.** `# <!-- fit --> Title` es compatible: el título ocupa + todo el ancho de la diapositiva en una línea, en la página y en todas las + exportaciones. +- **Matemáticas y código.** `$…$` y `$$…$$` siguen las reglas de delimitadores de + Marp Core y las compone go-tex/math; el código en bloques lo resalta go-rouge + con los nombres de clase de highlight.js (véase + [Matemáticas y código]({{< relref "math-and-code" >}})). diff --git a/content/es/math-and-code.md b/content/es/math-and-code.md new file mode 100644 index 0000000..8e073bd --- /dev/null +++ b/content/es/math-and-code.md @@ -0,0 +1,150 @@ +--- +title: "Matemáticas y código" +weight: 35 +description: "Las fórmulas entre signos de dólar se componen y el código en bloques se resalta, en la página HTML, las imágenes y el PDF." +tags: [math, code] +--- + +{{< callout type="warning" >}} +Las matemáticas y el código llegaron en v0.2.0. +{{< /callout >}} + +## Matemáticas {#math} + +`$…$` es una fórmula en línea y `$$…$$` una fórmula en bloque: + +```markdown +The famous one is $E=mc^2$, and this is a display formula: + +$$ +\int_0^\infty e^{-x^2}\,dx = \frac{\sqrt{\pi}}{2} +$$ +``` + +- Las reglas de delimitadores son las de Marp Core, leídas de marp-core 4.4.0: un + `$` seguido de un espacio no abre una fórmula, y uno seguido de un dígito no la + cierra, de modo que precios como `$5 and $10` se quedan como texto. +- [go-tex/math](https://github.com/go-tex/math) compone cada fórmula en un SVG en + línea con tamaño en `em`, de modo que sigue el tamaño del texto y se apoya en + su línea base. El mismo SVG se usa en la página HTML, en las imágenes y en el + PDF. +- Una fórmula que no se analiza correctamente queda visible tal como se escribió; + el error se conserva en su `title`. + +## Código {#code} + +Los bloques de código se colorean con [go-rouge](https://github.com/go-rouge/rouge), +que escribe los nombres de clase de highlight.js, los que usan los temas de Marp: + +````markdown +```go +fmt.Println("hello") +``` +```` + +Un lenguaje que go-rouge no conoce se escribe como texto plano, igual que hace +Marp Core. + +### Números de línea {#line-numbers} + +En v0.8.0 un bloque puede numerar sus líneas. Marp Core no tiene números de línea +e ignora lo que sigue al lenguaje de una valla, así que las opciones van entre +llaves y una presentación que las usa se renderiza en Marp como lo haría sin +ellas: + +````markdown +```go {lineNumbers} +func main() { + fmt.Println("hello") +} +``` + +```go {lineNumbers=10} +// starts at 10 +``` +```` + +| Se escribe | Significa | +| --- | --- | +| `{lineNumbers}` | numera desde 1 (también se leen el `{lines:true}` de Slidev y el `{.numberLines}` de Pandoc) | +| `{lineNumbers=10}` | numera desde 10 (también `startLine=10`, el `startFrom="10"` de Pandoc) | +| `{lineNumbers=false}` | sin números, diga lo que diga la diapositiva | + +Para todos los bloques de una diapositiva y de las siguientes, fije la directiva: +`<!-- lineNumbers: true -->` (`_lineNumbers` para una sola diapositiva). Un kind +(tipo de diapositiva) puede fijarla una vez para toda una familia de +diapositivas: + +```yaml +kinds: + listing: {lineNumbers: true} +``` + +Cada número empieza su línea dentro de la caja de código del tema, separado por +una línea. Se mantiene en la línea base de su línea aunque esta sea más alta, por +ejemplo con una llamada o un emoji. Los números se atenúan con un color mezcla +del color del código y el de su fondo, con un contraste de 4,5:1 o más en los +temas default, gaia y uncover, claros y oscuros. **No se pueden seleccionar** en +la página y los lectores de pantalla no los leen. En un PDF se dibujan pero se +dejan fuera del texto, de modo que un extractor de texto lee solo el código. En +un archivo de PowerPoint o de OpenDocument son texto editable al comienzo de cada +línea. Una presentación que no usa la opción se renderiza como antes, byte a +byte. Resaltar líneas escogidas todavía no es compatible. + +### Llamadas en el código {#code-callouts} + +En v0.8.0 una línea de código puede terminar con un disco numerado que remite a +una nota situada debajo del bloque. La sintaxis es la de +[Quarto](https://quarto.org/docs/authoring/code-annotation.html), que es también +la forma de comentario de Asciidoctor. El marcador es un comentario en el propio +lenguaje del código, así que el código sigue siendo válido, y las notas son la +lista ordenada que sigue justo después del bloque: + +````markdown +```go +func main() { // <1> + x := load() // <2> + fmt.Println(x) +} +``` + +1. The entry point. +2. Reads the input. +```` + +| Se escribe | Significa | +| --- | --- | +| `// <1>`, `# <1>`, `-- <1>`, `; <1>`, `% <1>`, `' <1>` | un comentario de línea que contiene solo el marcador: ambos se sustituyen por el disco | +| `<!-- <1> -->`, `/* <1> */` | lo mismo, para los lenguajes con comentarios delimitados | +| `// <1> <2>` | dos discos en una línea | +| `x = 1 # count <1>` | el texto del comentario se conserva y el disco lo sigue | + +Los marcadores se convierten en discos solo cuando la lista que sigue justo +después del bloque empieza en 1 y tiene un elemento por cada número. En caso +contrario, el bloque se queda como se escribió. Los discos son anillos del color +del código, en los temas default, gaia y uncover, claros u oscuros. Los +elementos de la lista se numeran con los mismos discos. + +Los discos **no se pueden seleccionar**. Al copiar el código de la página se +obtiene el código sin sus marcadores. En un PDF son vectores que quedan fuera del +texto, y en un archivo de PowerPoint o de OpenDocument son formas editables. Las +llamadas se combinan con los números de línea. Marp Core no tiene llamadas +(marp-core 4.4.0), así que en Marp los comentarios y la lista numerada se ven +tal como se escribieron. Una presentación que no las usa se renderiza como antes, +byte a byte. + +En v0.9.0, un disco y su nota se encuentran mutuamente en la página HTML. Pasar +el puntero por un disco, o llegar a él con Tab, tiñe su nota, y una nota hace lo +mismo con su disco. Es lo que hace el `code-annotations: hover` de Quarto, pero +aquí lo hace solo la hoja de estilos, sin ningún script. Por eso funciona igual +en la vista previa aislada del playground y en la vista previa de VS Code, que +restringen los scripts. Cada disco se describe con su nota, de modo que un lector +de pantalla lee la nota al llegar al disco. El tinte aparece gradualmente solo +cuando el lector no ha pedido movimiento reducido. Las imágenes, el PDF y los +archivos de PowerPoint y de OpenDocument no cambian, byte a byte. + +## Fuentes {#fonts} + +Lato Regular, Bold, Black e itálicas, y Roboto Mono Regular, Bold e itálicas van +incrustadas, de modo que los títulos de gaia tienen su grosor y el texto en +cursiva se renderiza en cursiva. diff --git a/content/es/performance.md b/content/es/performance.md new file mode 100644 index 0000000..a44b434 --- /dev/null +++ b/content/es/performance.md @@ -0,0 +1,91 @@ +--- +title: "Rendimiento" +weight: 60 +description: "Qué tan rápido construye go-slides una presentación y con cuánta memoria, medido frente a marp-cli en la misma máquina." +tags: [performance] +--- + +{{< callout type="info" >}} +Las cifras de PPTX de abajo corresponden a v0.5.0 y las del PDF vectorial a +v0.4.0. La primera tabla es de v0.3.0, la versión anterior, que renderiza las +diapositivas de forma concurrente y analiza sus fuentes una sola vez +([go-slides/slides#11](https://github.com/go-slides/slides/pull/11)). +{{< /callout >}} + +go-slides y [marp-cli](https://github.com/marp-team/marp-cli) 4.5.1 se ejecutaron +como subprocesos sobre las mismas presentaciones, en un Apple M4 Max (16 CPU) con +una carga media de 1 minuto inferior a 8, cinco rondas intercaladas tras un +calentamiento. El tiempo es la mediana del tiempo real; la memoria es el pico de +todo el árbol de procesos (marp-cli controla un Chrome, cuyos procesos cuentan). + +| Presentación | Salida | go-slides | marp-cli | memoria de go-slides | memoria de marp-cli | +| --- | --- | ---: | ---: | ---: | ---: | +| 100 diapositivas | HTML | 36 ms | 315 ms | 47 MiB | 140 MiB | +| 100 diapositivas | PDF | 1,14 s | 1,88 s | 242 MiB | 1509 MiB | +| 100 diapositivas | PNG, todas las diapositivas | 0,97 s | 5,44 s | 225 MiB | 1354 MiB | +| 100 diapositivas | PPTX | 3,39 s | 8,03 s | 530 MiB | 1373 MiB | +| gaia, 20 diapositivas | PDF | 283 ms | 1,57 s | 154 MiB | 1315 MiB | +| matemáticas y código, 20 diapositivas | PNG, todas las diapositivas | 250 ms | 2,32 s | 168 MiB | 1286 MiB | + +## PDF vectorial {#vector-pdf} + +En v0.4.0 el PDF se dibuja en vectores (véase +[Línea de comandos]({{< relref "cli#vector-pdf" >}})). Misma máquina y mismo +método, 2026-10-19: + +| Presentación | go-slides | marp-cli | v0.3.0 | tamaño de go-slides | tamaño de marp-cli | tamaño de v0.3.0 | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 100 diapositivas | 306 ms | 1763 ms | 1147 ms | 758 KiB | 837 KiB | 10,0 MiB | +| gaia, 20 diapositivas | 93 ms | 1546 ms | 285 ms | 32 KiB | 66 KiB | 582 KiB | +| matemáticas y código, 20 diapositivas | 140 ms | 1643 ms | 294 ms | 124 KiB | 335 KiB | 638 KiB | + +Memoria del PDF de 100 diapositivas: 158 MiB, frente a 1512 MiB de marp-cli; +tiempo de CPU de 0,9 s frente a 2,7 s. + +Las cifras de v0.4.0 para el PDF de 100 diapositivas se midieron el 2026-10-19 a +las 21:29, con una carga media de 1 minuto de 7,7, en tres rondas: go-slides +306 ms, 758 KiB y 158 MiB; marp-cli 1838 ms, 837 KiB y 1512 MiB. + +## PPTX editable {#editable-pptx} + +Desde v0.5.0 el PPTX se compone de cuadros de texto, formas e imágenes (véase +[Línea de comandos]({{< relref "cli#editable-pptx-and-odp" >}})). Medido el +2026-10-19 a las 22:51, con una carga media de 1 minuto de 7,1, en tres rondas: + +| Presentación | go-slides | marp-cli | tamaño de go-slides | tamaño de marp-cli | memoria de go-slides | memoria de marp-cli | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 100 diapositivas | 348 ms | 7795 ms | 760 KiB | 7,2 MiB | 204 MiB | 1370 MiB | +| gaia, 20 diapositivas | 112 ms | 4608 ms | 893 KiB | 4,8 MiB | | | +| matemáticas y código, 20 diapositivas | 207 ms | 2780 ms | 842 KiB | 1,5 MiB | | | + +Para la presentación de 100 diapositivas, v0.4.0 tardaba 4,7 s y ocupaba 4,8 MiB +y 540 MiB. El PPTX de marp-cli contiene imágenes. La mayor parte del tamaño de +una presentación editable son las fuentes incrustadas. + +## Por qué es rápido {#why-it-is-fast} + +- **No hay que arrancar ningún navegador.** marp-cli lanza Chrome en cada + exportación a PDF, imagen o PPTX; go-slides renderiza con + [go-webengine](https://github.com/go-webengine/engine) dentro de su propio + proceso. +- **Las diapositivas se renderizan en paralelo**, cuatro a la vez por defecto, y + cada imagen se escribe en cuanto le toca el turno: una presentación larga nunca + se mantiene en memoria como imágenes. +- **Las fuentes se analizan una sola vez** por proceso, no una vez por + diapositiva. + +## Dónde no gana {#what-it-does-not-win} + +- **Tamaño del PDF, en v0.3.0.** Cada diapositiva era una imagen en el PDF: 10 MiB + frente a 837 KiB en la presentación de 100 diapositivas. El PDF vectorial de + arriba es más pequeño que el de marp-cli. +- **Tiempo de CPU en presentaciones grandes.** Cuatro trabajadores gastan más CPU + de lo que muestra el tiempo real: en v0.3.0, 3,2 s frente a los 2,7 s de + marp-cli en el PDF de 100 diapositivas (0,9 s con el PDF vectorial). +- **La memoria de marp-cli es una cota superior**: los procesos de Chrome + comparten páginas que la suma cuenta en cada uno. Su mayor proceso individual + ocupa entre 220 y 320 MiB. + +El método, cada presentación y modo, y los comandos para reproducir las cifras +están en +[BENCHMARKS.md](https://github.com/go-slides/slides/blob/main/BENCHMARKS.md). diff --git a/content/es/playground.md b/content/es/playground.md new file mode 100644 index 0000000..73fd3ac --- /dev/null +++ b/content/es/playground.md @@ -0,0 +1,184 @@ +--- +title: "Playground" +weight: 15 +description: "Escriba una presentación en el navegador y vea cómo las diapositivas la siguen: go-slides compilado a WebAssembly, con autocompletado, ayuda y ejemplos." +tags: [playground, browser] +--- + +**[Abrir el playground →](https://go-slides.github.io/playground/)** + +El playground es el propio go-slides, v0.9.0, compilado a WebAssembly y +ejecutándose en su navegador. Escriba una presentación a la izquierda; las +diapositivas la siguen a la derecha. No se envía nada a ningún servidor: ni la +presentación, ni un enlace para compartir. + +## Qué ofrece {#what-it-has} + +- **Vista previa en vivo** de cada diapositiva, renderizada fuera del hilo de la + página (un Web Worker), de modo que escribir nunca espera por ella. Haga clic en + una diapositiva para llevar el editor a su línea; mueva el cursor y la vista + previa se desplaza a su diapositiva. +- **Pase de diapositivas**: la propia presentación, con el teclado, el + controlador en pantalla y las transiciones. +- **Vista del presentador**: **Present** (o `p` en el pase de diapositivas) la + abre en una segunda ventana: la diapositiva actual, el paso siguiente, las + notas del orador (los `<!-- comments -->` de la presentación), un reloj y un + cronómetro. Las teclas de flecha, Espacio, Inicio y Fin funcionan en cualquiera + de las dos ventanas, y cada una sigue a la otra, también cuando se edita la + presentación. Si la ventana no se abre, permita las ventanas emergentes para el + sitio. La vista general (`o`) solo está en la página descargada. +- **Autocompletado** mientras escribe: claves de directivas (las de una sola + diapositiva, con `_`, en los comentarios), los valores de `kind`, `theme`, + `size`, `paginate`, y las funciones de plantilla y los campos de datos dentro + de `{{ }}`. +- **Ayuda** sobre lo que hay bajo el cursor: una directiva, un kind (tipo de + diapositiva; qué fija), una función de plantilla. El panel de ayuda enlaza + también con estas páginas. +- **Problemas** en su línea: los errores de plantilla, de YAML y de HCL se + resaltan en el editor y se listan debajo, y la vista previa conserva la última + presentación que se renderizó. +- **Ejemplos**: una presentación de bienvenida, plantillas que generan + diapositivas, kinds de diapositiva, un front matter en HCL, temas y directivas + de una sola diapositiva, y matemáticas, imágenes y fondos (`![bg]`, un fondo + dividido `![bg left:40%]`, filtros). +- **Archivos**: el panel a la izquierda del editor es el proyecto de la + presentación, organizado como lo leen el [comando]({{< relref "cli" >}}) y la + [extensión de VS Code]({{< relref "vscode" >}}): `deck.md`, `slides.yaml`, + `themes/`, `kinds/`, `media/`. + - Haga clic en un archivo para editarlo. El editor lo colorea según su lenguaje + (Markdown, YAML, HCL, JSON, CSS) y marca sus problemas. + - Clic derecho (o Mayús+F10) para New File, New Folder, Rename (F2), Delete + (pregunta antes) y New theme / New kind. Arrastre un archivo a una carpeta + para moverlo. El teclado sigue el patrón de árbol de WAI-ARIA, incluida la + búsqueda al escribir. + - **Project ▸ New theme** crea `themes/my-gaia.css` (o a partir de default, + uncover): un `@import` del tema incorporado y las reglas que usted añada. Se + incorpora a la lista de temas. **New kind** y **New data file** también + parten de plantillas. Cada uno se lista en `slides.yaml`, de modo que + `slides build deck.md` lo encuentra. + - **Project ▸ Download project (.zip)** lo guarda todo. Una vez descomprimido, + `slides build deck.md` lo construye. **Open project** recupera un `.zip` o + una carpeta. Se rechaza un archivo comprimido que salga de su carpeta (`..`, + rutas absolutas), que contenga más de 256 archivos o más de 8 MiB en un + archivo, o que se descomprima a más de 100 veces su tamaño. + - Todo se conserva en este navegador junto con el borrador. Un proyecto admite + hasta 64 MiB, y la línea bajo los archivos indica cuánto se usa. +- **Medios**: suelte o pegue una imagen, un vídeo o un sonido en el editor y pasa + a `media/`, y la presentación lo nombra: `![](media/photo.png)`. Suelte + archivos sobre una carpeta del panel para ponerlos allí. La presentación sigue + siendo pequeña y legible, y no se sube nada. El entorno aislado de la vista + previa los carga sin abrirse (la página le entrega los bytes). **Save as ▸ HTML + page** los incluye en la página. Las imágenes `https:` también funcionan, pero + el navegador las obtiene de su servidor, que entonces ve su dirección. La vista + previa no envía referrer. +- **Paneles redimensionables**: el panel de archivos, el editor, las miniaturas + de las diapositivas, la vista previa y la ayuda están separados por barras que + se pueden arrastrar (el puntero se convierte en un cursor de redimensionado + sobre una de ellas). El panel junto a la barra se queda con el espacio y los + demás conservan su tamaño. Haga doble clic en una barra, o pulse Intro sobre + ella, para cerrar el panel y volver a abrirlo. Con el teclado, F6 pasa de una + barra a otra, las teclas de flecha mueven una barra 16 px, Inicio y Fin llevan + el panel anterior a su tamaño mínimo y máximo, y Escape vuelve al editor. La + pestaña View sigue pudiendo desactivar paneles. Los tamaños que fije se + conservan en este navegador, con el borrador; los tamaños por defecto (190, 168 + y 280 px, y el editor y la vista previa se reparten el resto en proporción 6 a + 5) nunca se almacenan. +- **Formato**: la pestaña Format edita el Markdown por usted, y cada pulsación es + un paso de Deshacer. + - **Texto**: Bold, Italic, Strikethrough e Inline code actúan sobre la + selección o la palabra bajo el cursor y se alternan (Ctrl+B, Ctrl+I, + Ctrl+Shift+X y Ctrl+E; Cmd en un Mac). Un botón aparece pulsado cuando el + cursor está dentro de sus marcas. Markdown no tiene subrayado: Underline + (Ctrl+U) envuelve el texto en `<u>…</u>`, una etiqueta que la lista de + permitidos de HTML por defecto de Marp conserva, así que funciona con el HTML + sin procesar desactivado. + - **Párrafo**: Heading 1 a 3 (Ctrl+Alt+1..3), viñetas (Ctrl+Shift+8), + numeración (Ctrl+Shift+7) y una cita actúan sobre las líneas seleccionadas y + se alternan. La regla horizontal es la etiqueta `<hr>`: `---`, `***` y `___` + iniciarían cada una una nueva diapositiva en Marp. + - **Tabla**: una cuadrícula elige las columnas y las filas. Con el cursor en una + tabla se añade o se elimina una fila o una columna y se alinea la columna + (`:--`, `:-:`, `--:`); la tabla se reescribe alineada tras cada edición. + - **Slide text** a la izquierda, al centro o a la derecha escribe un bloque + `<style scoped>` en la diapositiva actual. Marp no tiene sintaxis de + alineación, y un atributo `style` no pasa la lista de permitidos de HTML por + defecto, pero el bloque con ámbito sí, de modo que funciona con el HTML sin + procesar desactivado y se ve en la vista previa, la página HTML y el PDF. + - **Link** (Ctrl+K) escribe `[text](url)` con la url seleccionada. +- **Resaltado** de las claves del front matter a cualquier profundidad, en YAML + (también en mapas `{a: 1}`) y en HCL. +- Selector de **tema**: default, gaia, uncover, los temas del proyecto + (`themes/*.css`) y los temas que define la presentación (`/* @theme name */` en + un `<style>`). Escribe `theme:` en el front matter, YAML o HCL, de modo que un + enlace compartido lo conserva, y sigue al front matter cuando lo edita. +- La vista previa **mantiene su posición** mientras escribe: solo se sustituyen + las diapositivas que cambiaron, y un cambio de tema reabre la vista previa en + la misma diapositiva. +- **Enlace para compartir**: la presentación viaja en el propio enlace (en su + parte `#`, comprimida), así que abrir el enlace abre la presentación. Los + enlaces admiten hasta 64 KiB comprimidos, y solo la presentación: cuando el + proyecto contiene más (temas, kinds, medios), Share lo indica y ofrece el + `.zip`. +- **Save as**: + - la página HTML, la misma que escribe `slides deck.md`; + - un **PDF** (vectorial, con el texto seleccionable, con índice); + - una presentación **PowerPoint** u **OpenDocument** editable. + + Se generan en su navegador, con el mismo código que `slides --pdf`, `--pptx` y + `--odp`: el PDF de la presentación de prueba de 100 diapositivas es idéntico + byte a byte al del comando. La primera exportación descarga el motor de + exportación (unos 21 MiB, una sola vez; su navegador lo conserva hasta la + siguiente actualización), con una línea de progreso. La presentación de 100 + diapositivas tarda entonces unos 3 segundos. Una imagen `https:` solo se incluye + si su servidor lo permite (CORS); las imágenes `data:` siempre se incluyen. +- **Su borrador se conserva**: + - La presentación se guarda en este navegador mientras escribe (IndexedDB, + solo para este sitio) y reaparece al recargar. No se envía nada a ninguna + parte. Los archivos de texto del proyecto (temas, kinds, datos, + `slides.yaml`) se conservan del mismo modo, incluso si cierra la pestaña justo + después de escribir; los medios se almacenan en cuanto se sueltan. + - Un enlace compartido abre su presentación y conserva su borrador: **Draft ▸ + Restore my draft** lo recupera. + - Un ejemplo, **Draft ▸ Reset to the example** (que pregunta antes) y otra + pestaña tampoco lo borran nunca: **Draft ▸ Restore replaced** lo recupera. + - **Draft ▸ Forget my drafts** borra todo lo almacenado. + +Toda exportación renderiza el proyecto que muestra la vista previa: su +`slides.yaml`, temas, kinds, archivos de datos y medios, con los medios dentro +del documento. + +Las imágenes como archivos (`--images` de slides) no se generan en el navegador: +use el [comando]({{< relref "cli" >}}) o la +[extensión de VS Code]({{< relref "vscode" >}}). + +## Seguridad {#security} + +La presentación se muestra en un iframe aislado, sin el origen de la página: no +puede alcanzar ni el playground ni su almacenamiento, no puede enviar ningún +formulario y no navega a ninguna parte. Su página lleva una Content Security +Policy que ejecuta solo sus propios scripts. El HTML sin procesar de la +presentación sigue la lista de permitidos de Marp, como hace `slides` por +defecto. Un renderizado que dure más de 5 segundos (una plantilla que entra en +bucle) se detiene. + +La ventana del presentador mantiene ese aislamiento. La abre el propio +playground y muestra la presentación en un iframe aislado de la misma manera. Los +dos iframes no pueden compartir almacenamiento, así que el playground retransmite +el número de diapositiva entre ellos mediante mensajes. Ningún marco que ejecute +la presentación obtiene el origen del playground. + +Los medios del proyecto llegan a los marcos aislados como bytes en un mensaje, y +cada marco crea con ellos sus propias URL `blob:`, en su propio origen. Una URL +`blob:` creada por el playground no se carga allí, y la política de los marcos +solo añade `blob:` para medios y fuentes. + +## Tamaño y velocidad {#size-and-speed} + +El módulo pesa 8,8 MiB tal como lo sirve GitHub Pages (gzip; Pages no sirve +brotli). Se descarga una sola vez con una barra de progreso y luego el navegador +lo conserva en caché hasta el siguiente despliegue. En un Apple M4 Max con Chrome +155, medido sobre la página en línea, el editor está listo 2,1 s después de abrir +la página, 1,5 s de los cuales son la descarga, y las primeras diapositivas +aparecen a los 2,4 s. Un cambio en la presentación de prueba de 100 diapositivas +llega a la vista previa en unos 30 ms: go-slides renderiza solo las diapositivas +que cambiaron, y solo esas se sustituyen en la vista previa. diff --git a/content/es/templates.md b/content/es/templates.md new file mode 100644 index 0000000..bde4c75 --- /dev/null +++ b/content/es/templates.md @@ -0,0 +1,78 @@ +--- +title: "Plantillas" +weight: 30 +description: "Interpolación con text/template de Go en una presentación: valores, diapositivas generadas, números de página, inclusiones." +tags: [templates] +--- + +La presentación es una [`text/template`](https://pkg.go.dev/text/template) de Go, +evaluada **antes** de analizar el Markdown. Todo lo que ofrece el lenguaje de +plantillas de Go (acciones, tuberías, `if`, `range`, `with`, variables) +funciona en una presentación. + +## Datos {#data} + +El punto de la plantilla contiene las claves del front matter, luego las claves +de su mapa `data:`, y luego los datos indicados en la línea de comandos o en +`slides.yaml`, cada uno sobrescribiendo al anterior. + +```markdown +--- +marp: true +title: Quarterly review +data: + team: Platform +--- + +# {{ .title }} + +Presented by the {{ .team }} team. +``` + +Desde la línea de comandos: + +| Opción | Aporta | +| --- | --- | +| `--data file.yaml` | un archivo YAML o JSON de datos (repetible) | +| `--set key=value` | un valor (repetible) | + +## Generar diapositivas {#generating-slides} + +Todo el cuerpo es una plantilla, así que un `range` que escribe `---` escribe +diapositivas: + +```markdown +{{ range .releases }} +--- + +# {{ .version }} + +{{ range .changes }}- {{ . }} +{{ end }} +{{ end }} +``` + +## Página y total {#page-and-total} + +`{{ page }}` y `{{ total }}` son el número de la diapositiva y el número de +diapositivas. Solo se conocen una vez dividida la presentación, así que se +rellenan entonces: una presentación generada cuenta las diapositivas que generó. + +```markdown +<!-- footer: "{{ page }} / {{ total }}" --> +``` + +## Incluir {#include} + +`{{ include "parts/agenda.md" }}` inserta otro archivo, relativo a la +presentación. + +## El código se deja tal cual {#code-is-left-alone} + +Los bloques de código y los fragmentos de código en línea se apartan antes de +ejecutar la plantilla y se restituyen sin cambios, de modo que una diapositiva +sobre plantillas de Go puede mostrar `{{ .Name }}` sin escaparlo. + +## Desactivarlas {#turning-it-off} + +`--no-template` lee la presentación como Marp plano: `{{` es texto. diff --git a/content/es/vscode.md b/content/es/vscode.md new file mode 100644 index 0000000..8c609e8 --- /dev/null +++ b/content/es/vscode.md @@ -0,0 +1,199 @@ +--- +title: "VS Code" +weight: 55 +description: "La extensión de go-slides para VS Code: vista previa, presentación, problemas, autocompletado y exportación, como hace Marp for VS Code con Marp." +tags: [vscode, editor] +--- + +{{< callout type="info" >}} +La extensión es [go-slides/vscode](https://github.com/go-slides/vscode), con +versionado propio (v0.4.0). Renderiza con go-slides v0.9.0 (el paquete `editor`, +las líneas de origen de la sincronización del desplazamiento, y la vista general +y la vista del presentador). Se instala desde el `go-slides-0.4.0.vsix` adjunto a +su versión de GitHub: su publicación en VS Code Marketplace y en Open VSX está +preparada, pero todavía no está allí. +{{< /callout >}} + +La extensión es el equivalente de go-slides de +[Marp for VS Code](https://github.com/marp-team/marp-vscode): la presentación se +muestra como diapositivas en la vista previa de Markdown de VS Code, sus errores +en el panel Problemas, y las directivas, los kinds (tipos de diapositiva) y las +funciones de plantilla se autocompletan mientras se escribe. + +## Instalación {#install} + +Descargue `go-slides-<version>.vsix` de las +[versiones](https://github.com/go-slides/vscode/releases) y ejecute +*Extensions: Install from VSIX...*, o bien: + +```sh +code --install-extension go-slides-0.4.0.vsix +``` + +La vista previa no necesita nada más. Para exportar hace falta el comando +`slides`: + +```sh +go install github.com/go-slides/slides/cmd/slides@latest +``` + +## Vista previa {#preview} + +Abra una presentación y ejecute *Markdown: Open Preview to the Side*. Una +presentación es un archivo Markdown cuyo front matter dice `slides: true`: + +```markdown +--- +slides: true +paginate: true +data: + who: World +--- + +# Hello {{ .who }} + +--- + +<!-- _kind: title --> +# Page {{ page }} of {{ total }} +``` + +Los archivos con `marp: true` también se previsualizan, salvo que esté instalado +Marp for VS Code: ambas extensiones renderizarían el mismo documento. Lo decide +el ajuste `goSlides.preview.marpDocuments`: `auto` (el valor por defecto), +`always` o `never`. + +La vista previa es el propio go-slides, compilado a WebAssembly y cargado por la +extensión: sin binario, y funciona en [vscode.dev](https://vscode.dev). Lee del +espacio de trabajo lo que usa la presentación (`slides.yaml` o `slides.hcl` junto +a la presentación, los archivos de [kinds]({{< relref "kinds" >}}) que nombran, +los archivos que incluye una [plantilla]({{< relref "templates" >}}), el CSS de +los temas) y solo de la carpeta del espacio de trabajo de la presentación. + +## Sincronización del desplazamiento {#scroll-sync} + +Desde la v0.2.0 de la extensión (go-slides v0.7.0), el editor y la vista previa +se desplazan a la vez, en ambos sentidos, como ocurre con el Markdown normal: la +vista previa sigue la diapositiva y el bloque que hay bajo el cursor del editor, +y desplazar la vista previa mueve el editor. Los ajustes son los propios de VS +Code, `markdown.preview.scrollPreviewWithEditor` y +`markdown.preview.scrollEditorWithPreview`. + +Las líneas se rastrean a través de las plantillas. Una diapositiva que genera un +`{{ range }}` sigue la línea de su plantilla, y lo que aporta un `{{ include }}` +sigue la línea de la inclusión. Una presentación dividida por `headingDivider` se +sincroniza con sus títulos. + +## Presentar {#presenting} + +Desde la v0.4.0 de la extensión (go-slides v0.9.0), una presentación se puede +proyectar sin salir de VS Code. Con la presentación en el editor, desde la paleta +de comandos o el menú `…` del editor: + +| Comando | Abre | +|---|---| +| *go-slides: Open Overview* | un panel con todas las diapositivas en una cuadrícula. Una diapositiva elegida muestra su código fuente en el editor; `Esc` cierra el panel | +| *go-slides: Start Presenter View* | las diapositivas y, a su lado, la vista del presentador: el paso siguiente, las notas del orador y un cronómetro | + +Los paneles se sitúan junto al editor y se siguen entre sí: al cambiar de +diapositiva en uno, cambian también los demás. También siguen sus ediciones de la +presentación. La tecla `p` en las diapositivas abre la vista del presentador. + +Para presentar en otra pantalla, mueva allí el panel de las diapositivas con +*View: Move Editor into New Window*. + +Cada panel ejecuta la presentación en un marco aislado (sin `allow-same-origin`) +con una política `default-src 'none'`: el marco carga imágenes, medios, estilos y +fuentes y nada más, y ejecuta solo los scripts que go-slides incorporó a la +página, identificados por su hash. Un script escrito por la presentación no se +ejecuta. + +## Problemas, autocompletado, información al pasar el puntero {#problems-completion-hover} + +- **Problemas**: errores de plantilla, de YAML y de [HCL]({{< relref "hcl" >}}), + en la línea en que se producen, en la presentación o en el archivo de kinds de + donde proceden. +- **Autocompletado**: las claves de directivas en el front matter y en los + comentarios `<!-- -->` (con `_` para una directiva de una sola diapositiva), los + valores de `kind` y `_kind` (con lo que fija cada kind), `theme`, `size`, + `paginate`, `math`, `headingDivider`; dentro de `{{ }}`, las funciones de + plantilla y los campos de datos. +- **Información al pasar el puntero**: qué hace una directiva, qué fija un kind, + los argumentos de una función de plantilla. + +## Exportación {#export} + +*go-slides: Export Slide Deck...* (también en la barra de título del editor) +escribe HTML, PDF vectorial, PPTX y ODP editables, PNG o JPEG con el comando +`slides`, como lo haría `slides -o deck.pdf deck.md`; véase +[Línea de comandos]({{< relref "cli" >}}). Necesita un espacio de trabajo de +confianza. Si no se encuentra `slides`, la extensión ofrece ejecutar en un +terminal el `go install` de arriba. + +## Ajustes {#settings} + +| Ajuste | Valor por defecto | | +|---|---|---| +| `goSlides.cliPath` | `""` | el comando `slides`; vacío: `slides` en el `PATH`. Es un ajuste de la máquina: un espacio de trabajo no puede fijarlo | +| `goSlides.exportType` | `pdf` | el formato que propone primero el diálogo de exportación | +| `goSlides.exportAutoOpen` | `true` | abre el archivo exportado | +| `goSlides.html` | `false` | permite todo el HTML sin procesar en lugar de la lista de permitidos de Marp; se respeta solo en un espacio de trabajo de confianza | +| `goSlides.preview.marpDocuments` | `auto` | previsualiza las presentaciones con `marp: true`: `auto`, `always`, `never` | + +## Seguridad {#security} + +La presentación y el espacio de trabajo no son de confianza. El HTML sin procesar +sigue la lista de permitidos de Marp salvo que `goSlides.html` esté activado en un +espacio de trabajo de confianza (un `slides.yaml` con `html: true` no lo activa en +la vista previa), y la vista previa de VS Code no ejecuta ningún script de la +presentación. La vista previa solo lee archivos de la carpeta del espacio de +trabajo de la presentación, no sigue ningún enlace simbólico, y las plantillas no +ven ninguna variable de entorno. Exportar ejecuta un programa, así que espera a +que se confíe en el espacio de trabajo, y el espacio de trabajo nunca nombra el +programa. + +## Rendimiento {#performance} + +Con la presentación de prueba de 100 diapositivas de go-slides, en VS Code 1.142 +sobre un Apple M4 Max, con Marp for VS Code 3.6.1 medido en la misma sesión: + +| | go-slides | Marp for VS Code | +|---|---:|---:| +| activación (carga del módulo WebAssembly) | 189 ms | — | +| primera vista previa | 52 ms | 78 ms | +| vista previa tras una edición (mediana) | **19 ms** | 23–24 ms | +| `.vsix` | 7,5 MB | 36,3 MB | + +Las cifras corresponden a la extensión v0.3.0 (go-slides v0.8.0) con una +presentación de 100 diapositivas, escribiendo un carácter en la diapositiva 50, +en rondas intercaladas con una carga de 1 minuto inferior a 8. La primera vista +previa es más rápida que la de Marp, y la vista previa tras una edición también +lo es ya. + +### En v0.8.0 y la extensión v0.3.0 {#in-v080-and-extension-v030} + +go-slides v0.8.0 conserva las diapositivas renderizadas de cada presentación +abierta y, tras una edición, vuelve a renderizar solo aquellas cuyo origen o +estado heredado cambió (directivas, número de página, total, ids de títulos, +referencias de enlaces, líneas de origen); el resultado es idéntico al de un +renderizado completo, byte a byte. Escribir en la diapositiva 50 de 100 vuelve a +renderizar una diapositiva: 2,5 ms de forma nativa, 11,5 ms en WebAssembly. La +vista previa de VS Code transforma entonces solo el DOM de la diapositiva +modificada, y la prueba de humo de la extensión escribe un carácter en la +diapositiva 50 entre renderizados cronometrados, para ambas extensiones. La vista +del presentador de la extensión se sincroniza a través de la ventana padre +(`postMessage`). + +### En v0.7.1 y la extensión v0.2.2 {#in-v071-and-extension-v022} + +go-slides v0.7.1 almacena en caché entre renderizados los temas analizados, las +hojas de estilo empaquetadas y el código resaltado, marca las líneas de +sincronización del desplazamiento sin recorrer byte a byte, y reutiliza sus +escritores: un renderizado de la presentación de 100 diapositivas reserva 5,7 MB, +frente a los 52,7 MB de la extensión v0.2.0. Con ello, la extensión v0.2.2 +elimina la sobrescritura `GOGC=400`. A modo de comparación, la extensión v0.2.1 +tardaba 72 ms en la primera vista previa y 44 ms tras una edición con el `GOGC` +por defecto (36 ms con `GOGC=400`), y v0.2.0 tardaba entre 47 y 51 ms. + +La extensión v0.2.2 midió 52 ms en la primera vista previa y 24 ms tras una +edición, a la par con los 23 ms de Marp. diff --git a/content/fr/_index.md b/content/fr/_index.md new file mode 100644 index 0000000..3ca6db5 --- /dev/null +++ b/content/fr/_index.md @@ -0,0 +1,60 @@ +--- +title: "Documentation de go-slides" +linkTitle: "Accueil" +type: docs +cascade: + type: docs +description: "Des présentations compatibles Marp en pur Go (CGO=0), avec interpolation de gabarits Go et types de diapositives." +--- + +**`slides` fait d'une présentation Marp un diaporama, en pur Go.** Il lit le +Markdown que lit [Marp](https://marp.app) — les directives de Marpit, `---` entre +les diapositives, les arrière-plans `![bg]`, les notes de l'orateur, les thèmes `default`, `gaia` et +`uncover` — et y ajoute deux choses que Marp n'a pas : + +- des **[gabarits Go]({{< relref "templates" >}})** dans la présentation, évalués + avant l'analyse du Markdown, si bien qu'une présentation peut générer ses propres diapositives ; +- des **[types de diapositives (kinds)]({{< relref "kinds" >}})** : une diapositive dit ce qu'elle est + (`title`, `section`…) au lieu de répéter à quoi elle ressemble. + +{{< callout type="warning" >}} +**v0.9.0.** Ces pages décrivent +[go-slides/slides v0.9.0](https://github.com/go-slides/slides/releases/tag/v0.9.0) +et sont publiées sous `/docs/0.9/`. +{{< /callout >}} + +## Ce qu'il fait aujourd'hui {#what-it-does-today} + +| | | +| --- | --- | +| `slides build` (par défaut) | la présentation en une seule page HTML : chaque diapositive est une `<section>` dans un cadre SVG qui s'adapte à la fenêtre, avec le CSS du thème et une visionneuse pilotée au clavier | +| `slides build --pdf`, `--images png`, `--image jpeg` | la présentation en PDF (une page par diapositive, avec un plan) ou en images PNG/JPEG, sans navigateur : [go-webengine](https://github.com/go-webengine) fait le rendu, [go-pdfkit](https://github.com/go-pdfkit) écrit le PDF — en vectoriel depuis la v0.4.0, avec un texte sélectionnable : [PDF vectoriel]({{< relref "cli#vector-pdf" >}}) | +| mathématiques et code | `$…$` et `$$…$$` composés par go-tex/math, code délimité coloré par go-rouge, en HTML, en images et en PDF : [Mathématiques et code]({{< relref "math-and-code" >}}) | +| emoji | `:smile:` et 😄 deviennent des images Twemoji intégrées : elles se passent de réseau et s'affichent dans toutes les sorties : [Emoji]({{< relref "emoji" >}}) (depuis la v0.4.0) | +| `slides build --pptx` | la présentation en fichier PowerPoint, avec ses notes de l'orateur et ses métadonnées : une image par diapositive en v0.4.0, comme marp-cli ; **modifiable** depuis la v0.5.0 — zones de texte, formes, images, polices intégrées ([PPTX et ODP modifiables]({{< relref "cli#editable-pptx-and-odp" >}})) | +| `slides build --odp` | la même chose en présentation OpenDocument (`.odp`) | +| titres ajustés | `# <!-- fit --> Title` occupe toute la largeur de la diapositive sur une ligne, dans la page comme dans chaque export | +| `slides expand` | la présentation en Markdown Marp ordinaire : gabarits évalués, *kinds* remplacés par les directives ponctuelles qu'ils représentent | +| `slides notes` | les notes de l'orateur | +| `slides kinds`, `slides themes` | ce qu'une présentation peut nommer | +| `slides.yaml` | *kinds*, thèmes, données, politique HTML et langue communs à un dossier de présentations | +| HCL | front matter `---hcl`, `slides.hcl`, fichiers de *kinds* et de données en HCL2, avec expressions et fonctions : [HCL]({{< relref "hcl" >}}) | + +## Prévu {#planned} + +Pas encore réalisé, et indiqué ici pour que personne n'ait à deviner : les formules en vectoriel dans le +PDF (elles y sont en bitmap). La différence avec +marp-cli tient à la méthode : il produit images et PDF en pilotant un Chrome headless ; +ici, ce sont des bibliothèques Go qui s'en chargent. + +## Pages {#pages} + +- [Premiers pas]({{< relref "getting-started" >}}) +- [Galerie]({{< relref "gallery" >}}) : douze présentations d'exemple, à ouvrir dans le playground +- [Compatibilité Marp]({{< relref "marp" >}}) +- [Gabarits]({{< relref "templates" >}}) +- [Kinds]({{< relref "kinds" >}}) +- [HCL]({{< relref "hcl" >}}) +- [Mathématiques et code]({{< relref "math-and-code" >}}) +- [Emoji]({{< relref "emoji" >}}) +- [Ligne de commande]({{< relref "cli" >}}) diff --git a/content/fr/cli.md b/content/fr/cli.md new file mode 100644 index 0000000..66b3c4a --- /dev/null +++ b/content/fr/cli.md @@ -0,0 +1,355 @@ +--- +title: "Ligne de commande" +weight: 50 +description: "Toutes les commandes et options de slides, et le fichier de configuration slides.yaml." +tags: [cli] +--- + +{{< callout type="warning" >}} +Cette page décrit la v0.9.0. +{{< /callout >}} + +```text +slides [deck.md] [flags] +slides [command] [flags] deck.md +``` + +## Commandes {#commands} + +| Commande | Rôle | +| --- | --- | +| `build` | écrit la présentation en HTML, en PDF ou en images (voir plus bas). **Commande par défaut** : `slides deck.md` équivaut à `slides build deck.md` | +| `expand` | affiche la présentation en Markdown Marp ordinaire : gabarits développés, types de diapositives (kinds) remplacés par les directives qu'ils représentent | +| `notes` | affiche les notes de l'orateur | +| `kinds` | liste les *kinds* que la présentation peut utiliser et ce qu'ils définissent ; `--json` (depuis la v0.6.0) les écrit sous la forme `[{"name", "extends", "directives": [{"key", "value"}]}]`, `extends` étant résolu | +| `themes` | liste les thèmes | +| `version` | affiche la version (tout comme `slides --version`) | +| `completion` | affiche un script de complétion pour le shell : `bash`, `zsh`, `fish` ou `powershell` | + +`slides <command> --help` affiche les options d'une commande. Les options peuvent précéder +ou suivre la présentation : `slides deck.md -o out.html` et `slides -o out.html +deck.md` reviennent au même. Les options longues prennent deux tirets (`--html`) ; `-o` est la seule +option courte. `build` écrit `deck.html` à côté de `deck.md`, sauf si `-o` en décide +autrement. + +## Options {#flags} + +| Option | Commandes | Rôle | +| --- | --- | --- | +| `-o`, `--output file` | `build`, `expand`, `notes` | fichier de sortie ; `-` pour la sortie standard | +| `-I`, `--input-dir dir` | `build` | convertit récursivement les fichiers Markdown sous `dir` (depuis la v0.6.0) ; voir plus bas | +| `-P`, `--parallel n` | `build` | nombre de présentations générées simultanément (depuis la v0.6.0) : 5 par défaut, au plus le nombre de cœurs ; 1 les génère l'une après l'autre | +| `-w`, `--watch` | `build` | régénère quand une présentation, un thème, un fichier de *kinds* ou de données, ou la configuration change, et recharge les pages ouvertes (depuis la v0.6.0) ; voir plus bas | +| `-s`, `--server dir` | `build` | sert un répertoire en convertissant ses présentations à la demande (depuis la v0.6.0) ; voir plus bas | +| `-p`, `--preview` | `build` | ouvre le résultat dans le navigateur par défaut (depuis la v0.6.0) : les pages HTML avec `--watch` (implicite), le serveur avec `--server` | +| `--host addr` | `build` | l'adresse sur laquelle écoute `--server`, `127.0.0.1` par défaut (depuis la v0.6.0) | +| `--template bespoke\|bare` | `build` | le gabarit de la page HTML (depuis la v0.6.0) : `bespoke`, par défaut, avec le script de présentation, le contrôleur à l'écran et les transitions, ou `bare`, les diapositives empilées (`--bare`) | +| `--bespoke.osc`, `--bespoke.progress`, `--bespoke.transition` | `build` | les options du gabarit bespoke, avec les valeurs par défaut de marp-cli (depuis la v0.6.0) : contrôleur activé, **barre de progression désactivée** (elle était auparavant toujours affichée), transitions activées ; `--bespoke.osc=false` en désactive une | +| `-c`, `--config-file file`, `--no-config-file` | `build` | le fichier de configuration de marp-cli, en JSON ou en YAML (depuis la v0.6.0) ; voir plus bas. `--config` désigne le fichier propre à `slides` | +| `--browser`, `--browser-path`, `--browser-protocol`, `--browser-timeout` | `build` | acceptées et ignorées avec un avertissement (depuis la v0.6.0) : il n'y a pas de navigateur | +| `--pptx-editable`, `--no-parallel` | `build` | les équivalents marp-cli de `--pptx` et de `-P 1` (depuis la v0.6.0) | +| `--config file` | toutes | fichier de configuration ; par défaut, `slides.yaml` à côté de la présentation, s'il existe | +| `--no-config` | toutes | ne lit aucun fichier de configuration | +| `--theme-set path` | toutes | un fichier CSS de thème, ou un répertoire qui en contient (répétable) | +| `--kinds file` | toutes | un fichier YAML de *kinds* (répétable) | +| `--data file` | toutes | un fichier YAML ou JSON de données de gabarit (répétable) | +| `--set key=value` | toutes | une valeur de gabarit (répétable) | +| `--no-template` | toutes | ne développe pas les gabarits Go | +| `--strict-yaml` | toutes | analyse les directives en YAML strict, comme Marpit seul (Marp Core les analyse avec souplesse) | +| `--no-emoji` | toutes | laisse les `:shortcodes:` et les emoji Unicode sous forme de texte au lieu de les dessiner en images (depuis la v0.4.0 : [Emoji]({{< relref "emoji" >}})) | +| `--html` | `build` | autorise tout le HTML brut ; par défaut, la liste d'autorisation de Marp Core | +| `--lang code` | `build` | le `lang` de la page quand la présentation n'en définit pas | +| `--bare` | `build` | empile les diapositives, sans le script de présentation | +| `--pdf` | `build` | écrit un PDF, une page par diapositive, avec un plan — texte et formes en vectoriel (également choisi par `-o file.pdf`) | +| `--pdf-raster` | `build` | écrit le PDF avec, pour chaque page, une seule image de la diapositive, comme le faisait la v0.3.0 | +| `--notes` | `build` | écrit les notes de l'orateur dans un fichier texte, les diapositives séparées par `---`, comme marp-cli (depuis la v0.6.0 ; également choisi par `-o file.txt`). La commande `notes` les affiche dans une autre mise en forme | +| `--pdf-notes` | `build` | ajoute les notes de l'orateur de chaque diapositive à sa page PDF sous forme d'annotation de type note, comme marp-cli (depuis la v0.6.0). Demande un PDF quand rien d'autre n'indique quoi écrire | +| `--pdf-outlines`, `--pdf-outlines.pages`, `--pdf-outlines.headings` | `build` | les signets du PDF (depuis la v0.6.0) : « Page N » pour chaque diapositive et les titres imbriqués dessous. Activés par défaut ici, contrairement à marp-cli ; `=false` en désactive un. Voir plus bas | +| `--images png\|jpeg` | `build` | écrit une image par diapositive : `deck.001.png`, … | +| `--image png\|jpeg` | `build` | écrit la première diapositive sous forme d'image (également choisi par `-o file.png` ou `-o file.jpg`) | +| `--pptx` | `build` | écrit un fichier PowerPoint avec ses notes de l'orateur (également choisi par `-o file.pptx`) : modifiable depuis la v0.5.0, une image par diapositive en v0.4.0 | +| `--pptx-raster` | `build` | écrit un fichier PowerPoint composé d'une image pleine diapositive par diapositive, comme marp-cli (depuis la v0.5.0) ; `--image-scale` vaut 2 par défaut | +| `--odp` | `build` | écrit une présentation OpenDocument avec ses notes de l'orateur (également choisi par `-o file.odp`) : modifiable depuis la v0.5.0 | +| `--odp-raster` | `build` | écrit une présentation OpenDocument composée d'une image par diapositive (depuis la v0.5.0) | +| `--jpeg-quality n` | `build` | qualité JPEG, de 1 à 100 (85 par défaut) | +| `--image-scale n` | `build` | pixels physiques par pixel CSS pour les images et le PDF (1 par défaut) ; 2 donne 2560×1440 en 16:9, texte et vecteurs étant dessinés à cette densité, et non agrandis | +| `--allow-local-files` | `build` | permet aux images et au PDF de lire des fichiers locaux, uniquement sous le répertoire de la présentation ; refusé par défaut, comme avec marp-cli | +| `--theme name\|file.css` | `build` | remplace le thème de la présentation (depuis la v0.6.0) : un nom de thème, ou un fichier CSS, qui devient le thème quel que soit le `@theme` qu'il déclare ; un répertoire est refusé, utilisez `--theme-set` | +| `--title`, `--description`, `--author`, `--keywords`, `--url`, `--og-image` | `build` | les métadonnées de la page (depuis la v0.6.0), prioritaires sur les directives `title`, `description`, `author`, `keywords`, `url` et `image` de la présentation ; voir plus bas | + +## Plusieurs présentations, et un répertoire {#several-decks-and-a-directory} + +Depuis la v0.6.0, `build` accepte plusieurs présentations, un répertoire (les présentations qu'il contient, +trouvées récursivement) ou un motif, et en génère jusqu'à `--parallel` simultanément. +`-` désigne l'entrée standard ; contrairement à marp-cli, un tube n'est lu que si `-` le demande. + +Un motif est interprété comme le fait le glob de marp-cli (`**` et `{a,b}` depuis la v0.7.0 ; +mettez-le entre guillemets pour que le shell le laisse à `slides`) : `*`, `?`, `[a-z]` et +`{md,txt}` dans un nom, `**` pour un nombre quelconque de répertoires, si bien que +`'docs/**/*.md'` prend les présentations de toute l'arborescence. Un joker n'atteint pas un nom +qui commence par un point, rien n'est cherché dans `node_modules`, la casse compte +(`*.md` ne trouve pas `UPPER.MD`), et tout ce qu'un motif sélectionne est converti, +quelle que soit son extension. Un motif qui ne sélectionne rien est une erreur. + +- **`-I dir`** trouve les fichiers Markdown (`.md`, `.mdown`, `.markdown`, + `.markdn`, en minuscules) sous `dir`, comme marp-cli : `node_modules` et + les répertoires commençant par un point sont ignorés. Avec `-o out`, chaque présentation est écrite à son + chemin sous `dir`, reproduit sous `out` : `dir/sub/b.md` donne + `out/sub/b.html` (`b.pdf`, `b.001.png`, `b.txt`, … selon les options). + Sans `-o`, les sorties sont écrites à côté de leurs présentations. Nommer des présentations en plus de + `-I` est une erreur, de même que `-o file` pour plusieurs présentations (`-o -` est autorisé). +- **`-P n`** génère `n` présentations simultanément. Le rendu d'une présentation exploite déjà les + cœurs, donc `n` est plafonné à leur nombre ; avec `-o -`, les présentations sont générées l'une après + l'autre. Les messages d'une présentation restent groupés, et une présentation en échec n'arrête + pas les autres : le code de sortie vaut 1 et le message liste les échecs. + +## Transitions et page de présentation {#transitions-and-the-presenting-page} + +Depuis la v0.6.0, la directive `transition` de marp-cli fonctionne : `<!-- transition: +fade 1s -->`, un nom et une durée facultative, pour la diapositive et les suivantes, +ou `<!-- _transition: cover -->` pour une seule. Les noms intégrés sont ceux de marp-cli : +`clockwise`, `counterclockwise`, `cover`, `coverflow`, `cube`, `cylinder`, +`diamond`, `drop`, `explode`, `fade`, `fade-out`, `fall`, `flip`, `glow`, +`implode`, `in-out`, `iris-in`, `iris-out`, `melt`, `overlap`, `pivot`, `pull`, +`push`, `reveal`, `rotate`, `slide`, `star`, `swap`, `swipe`, `swoosh`, `wipe`, +`wiper` et `zoom` ; `none` la désactive. La page la joue avec la +**View Transition API** du navigateur quand le spectateur quitte vers l'avant la diapositive qui la porte, +et quand il y revient ; les navigateurs sans cette API changent de diapositive comme avant, +et un spectateur qui demande des mouvements réduits obtient un simple fondu. Un thème peut apporter +ses propres images clés, nommées `marp-transition-NAME` (ou `marp-outgoing-` et +`marp-incoming-`, et `-backward`), qui passent avant celles qui sont intégrées. + +La page comporte le **contrôleur à l'écran** — précédent, page, suivant, plein écran —, +qui apparaît quand le pointeur bouge ou qu'une touche est pressée et disparaît au bout d'un moment +(`--bespoke.osc=false` le supprime), ainsi que, avec `--bespoke.progress`, une barre de +progression. **La barre de progression est désormais désactivée par défaut**, comme dans marp-cli ; elle était auparavant +toujours affichée. + +### Mode présentateur {#presenter-view} + +Depuis la v0.7.0, la page dispose du mode présentateur de marp-cli. Appuyez sur `p`, ou sur le dernier +bouton du contrôleur : une fenêtre s'ouvre avec la diapositive courante, l'étape +suivante, vos notes de l'orateur, la page, l'heure et un chronomètre (cliquez sur le chronomètre +pour le remettre à zéro ; `+` et `-` redimensionnent les notes ; la barre entre les diapositives et les +notes peut être déplacée). Les deux fenêtres se suivent dans les deux sens : les +touches fléchées, un clic sur l'étape suivante ou sur Précédent/Suivant dans l'une font avancer +l'autre, un fragment à la fois. + +Il fonctionne comme celui de marp-cli, d'après ses sources 4.5.1 : la fenêtre est +`?view=presenter&sync=KEY` du même fichier, l'étape suivante est la même page en +`?view=next` dans un cadre, et les fenêtres partagent leur position par +`localStorage` (`bespoke-marp-sync-KEY`) : il ne demande ni serveur ni +réseau, et ne fonctionne pas dans un navigateur dépourvu de `localStorage` (le +bouton le signale). Les notes sont du texte : `&`, `<` et `>` sont échappés, si bien qu'une note +ne peut jamais être du balisage. Elles figurent dans la page, masquées, pour que le mode présentateur +les reprenne, ce qui les tient à l'écart de ce qu'un lecteur d'écran lit au public. +Les pages `--bare` n'ont rien de tout cela. Ce mode ajoute environ 10 Ko à une page. + +### Vue d'ensemble {#overview-view} + +Depuis la v0.8.0, appuyez sur `o` (ou `Escape` : marp-cli bascule avec les deux), sur le +bouton en grille du contrôleur ou sur le compteur de pages du mode présentateur : toutes les diapositives +s'affichent en entier dans une grille par-dessus la page. Les touches fléchées choisissent une diapositive (haut et bas +par rangée), `Home` et `End` vont aux extrémités, et les autres fenêtres suivent ; +`Enter`, `Space` ou un clic choisit une diapositive et, par-dessus la page, ferme la +vue d'ensemble. Par-dessus le mode présentateur, un choix la laisse ouverte (le présentateur choisit +la diapositive à montrer et continue à regarder). Le bouton « Close overview », `o` et +`Escape` la ferment. C'est celle de marp-cli : la page, de nouveau dans un cadre, à +`?view=overview&sync=KEY`, qui partage la position par `localStorage`, sans +réseau. Les diapositives de la grille peuvent recevoir le focus et portent le libellé « Slide N », et la +vue d'ensemble fermée est inerte : les personnes qui naviguent au clavier ou au lecteur d'écran n'y +tombent jamais. Les deux vues ajoutent ensemble environ 15 Ko à une page. + +### Fichiers de configuration {#configuration-files} + +marp-cli lit ses options dans un fichier du répertoire de travail, et `slides` aussi, +quand le fichier contient des données : `.marprc` (YAML, le JSON étant aussi du YAML), +`.marprc.json`, `.marprc.yaml`, `.marprc.yml`, `.config/marprc*`, ou la +clé `marp` de `package.json` ; `--config-file` (`-c`) désigne n'importe quel fichier JSON ou YAML, +`marp.config.json` par exemple. Seul le répertoire de travail est examiné, pas +ses parents, comme dans la recherche de marp-cli. Un fichier JavaScript ou TypeScript +(`marp.config.js` ...) est un programme, que `slides` n'exécute pas : il le signale et +continue. Les options sont celles de la documentation de marp-cli qui ont un sens +ici : `title`, `description`, `author`, `keywords`, `url`, `ogImage`, `lang`, +`theme`, `themeSet`, `html`, `allowLocalFiles`, `template`, `bespoke`, +`inputDir`, `output`, `parallel`, `pdf`, `pptx`, `notes`, `pdfNotes`, +`pdfOutlines`, `images`, `image`, `imageScale`, `jpegQuality`, `watch`, +`server`, `preview` ; les chemins sont relatifs au fichier. La ligne de commande l'emporte. +`engine`, `options` et `browser*` nécessitent le moteur de Marp et un navigateur ; ils sont +ignorés avec un avertissement, tout comme les options `--browser*` : les scripts écrits pour +marp-cli continuent de fonctionner. (`slides.yaml` et `slides.hcl`, avec les *kinds* et les +données de gabarit, sont distincts et lus comme avant.) + +## Surveillance, serveur et aperçu {#watch-serve-and-preview} + +Depuis la v0.6.0, avec les routes et le protocole de rechargement à chaud de marp-cli (lus dans ses +sources 4.5.1 et comparés à un `marp -s` en cours d'exécution). + +**`slides --server dir`** (`-s`) sert le répertoire sur `http://127.0.0.1:8080/` +(la variable d'environnement `PORT` change le port ; un port déjà utilisé est une +erreur, signalée comme telle) : + +| Requête | Réponse | +| --- | --- | +| `/deck.md` | la présentation en HTML, convertie à la demande | +| `/deck.md?pdf`, `?pptx`, `?png`, `?jpg` (ou `?jpeg`), `?notes` (ou `?txt`) | la présentation convertie selon la requête ; le PPTX est servi en pièce jointe | +| un répertoire | son `index.md` ou son `PITCHME.md` en tant que page, sinon un index avec un lien PDF et un lien PPTX pour chaque présentation | +| tout autre fichier | le fichier | +| une présentation dont la conversion échoue | 503, avec l'erreur | + +Une page ouvre un WebSocket vers `/.__marp-cli-watch-notifier__/<id>`, où l'id est +le SHA-256 du chemin de la présentation ; le serveur envoie `ready`, puis `reload` quand la +présentation, ou quoi que ce soit sous le répertoire, change. Un répertoire demandé sans +sa barre oblique finale est redirigé vers celle-ci (marp-cli y sert son index, ce qui +casse les liens relatifs de la page). + +**`--watch`** (`-w`) génère les présentations, puis régénère celle qui change +et, quand un fichier `--theme-set`, un fichier de *kinds* ou de données, ou la configuration +change, toutes. Les pages qu'il écrit ouvrent une socket sur l'interface de bouclage (port +37717, ou le suivant qui est libre) et se rechargent quand leur présentation est régénérée. Une présentation +dont la génération échoue est signalée et reste surveillée. **`--preview`** (`-p`) ouvre +le navigateur par défaut sur le serveur, ou sur les pages HTML tout juste générées ; il n'a pas de +fenêtre propre, puisque `slides` n'a aucun navigateur à piloter. + +Le serveur est une porte d'entrée sur la machine : il est donc plus strict que celui de marp-cli, qui +écoute sur toutes les interfaces et ne vérifie rien : + +- il écoute sur **127.0.0.1** ; `--host` change cela, avec un avertissement, et une + adresse générique ne permet plus de vérifier l'en-tête `Host` ; +- l'en-tête **`Host`** doit être un nom de bouclage (`localhost`, `127.0.0.1`, + `[::1]`, `*.localhost`) ou l'hôte sur lequel il écoute, ce qui bloque le DNS rebinding ; +- l'**`Origin`** d'un WebSocket doit être celle du serveur ; les sockets du mode surveillance + acceptent aussi l'origine `null` d'une page ouverte depuis un fichier ; une requête venant + d'un autre site qui n'est pas une navigation (un script, une image, un `fetch`) est + refusée ; +- un chemin est normalisé, ses liens symboliques sont résolus, et le résultat doit se trouver + dans le répertoire : `..` et un lien qui en sort sont refusés, de même que + `?pdf` sur une présentation extérieure ; les noms commençant par un point (`.git`, `.env`) ne sont pas + servis ; +- seulement `GET` et `HEAD` ; quelques conversions à la fois ; au plus 4096 répertoires + surveillés, et aucun dans `node_modules` ni sous un répertoire commençant par un point. + +## Métadonnées de la page {#page-metadata} + +La ligne de commande l'emporte sur la présentation, comme dans marp-cli, où les options sont +ajoutées à la présentation sous la forme d'un dernier commentaire de directive. Règles lues dans marp-cli +4.5.1 et vérifiées par des tests sur sa sortie (depuis la v0.6.0) : + +- Une présentation qui ne définit pas de `title` prend le texte de son premier titre du niveau le plus + élevé, tel qu'il est écrit (`# **Bold** title` donne `**Bold** title`). Sans + titre, il n'y a pas de `<title>`. +- Les `keywords` sont découpés aux virgules, débarrassés des espaces et dédoublonnés ; une liste dans le front matter + est reprise telle quelle. Comme marp-cli, la page porte la balise `keywords` à partir + du deuxième mot-clé, et non pour un seul. +- Une `url` invalide (non absolue) est ignorée avec un avertissement, et la valeur + précédente conservée ; une URL valide écrit aussi `<link rel="canonical">` et `og:url`. +- `author` et `description` sont accompagnés de leurs équivalents Open Graph ; `og:image:alt` + et la grande carte Twitter nécessitent à la fois un titre et une image. + +## Images, PDF, PPTX et ODP {#images-pdf-pptx-and-odp} + +`-o` avec une extension `.pdf`, `.pptx`, `.odp`, `.png` ou `.jpg` choisit le format, comme +marp-cli. Les répertoires de sortie sont créés. Un PDF comporte une page par diapositive, à la +taille de la diapositive, et un plan des diapositives et de leurs titres. + +### Notes et plan du PDF {#pdf-notes-and-outline} + +Depuis la v0.6.0, le PDF prend en charge les deux options de marp-cli, comparées avec +les PDF qu'écrit marp-cli 4.5.1 pour la même présentation : + +- `--pdf-notes` place les notes de l'orateur de chaque diapositive sur sa page sous forme d'annotation + de type note : une icône Note jaune en bas à gauche, dont le contenu est constitué des + notes (plusieurs commentaires séparés par une ligne vide) et dont le titre est l'`author` + de la présentation. `--pdf-notes` seul demande un PDF, comme dans marp-cli. +- Le plan contient un signet en gras « Page N » pour chaque diapositive et, en dessous, les + titres de la diapositive imbriqués par niveau (`--pdf-outlines.pages` et + `--pdf-outlines.headings` choisissent ces deux parties). **Le plan est activé par défaut + ici et désactivé dans marp-cli.** `slides` en a toujours écrit un ; il coûte quelques + centaines d'octets et rend une longue présentation navigable : ce choix par défaut est donc conservé. + `--pdf-outlines=false`, ou le `--no-pdf-outlines` de marp-cli, le désactive, et + `=false` sur `.pages` ou `.headings` supprime la partie correspondante. Un signet de titre renvoie + à sa diapositive ; marp-cli fait en plus défiler jusqu'au titre. + +Le rendu est assuré par [go-webengine](https://github.com/go-webengine/engine) +et le PDF est écrit par [go-pdfkit](https://github.com/go-pdfkit/pdfkit) ; +les polices demandées par les thèmes intégrés (Lato, Roboto Mono) proviennent de +[go-opentype/fonts](https://github.com/go-opentype/fonts) et sont intégrées : aucun +rendu n'accède au réseau. Il n'y a pas de Chrome. + +`--image-scale 2` fait le rendu à deux pixels physiques par pixel CSS. Les images locales sont +refusées sauf si `--allow-local-files` est indiqué, et même alors seuls les fichiers situés sous +le répertoire de la présentation sont lus : une présentation venue d'ailleurs ne peut pas atteindre `~/.ssh`. +Les arrière-plans multiples et partagés (`![bg left:40%]`) sont rendus comme les calques de Marp. + +`--pptx` écrit la présentation avec la fonction `WritePresentation` de [go-ooxml](https://github.com/go-ooxml), +avec les notes de l'orateur et les métadonnées de la présentation. Depuis +la v0.5.0, les diapositives sont modifiables (voir ci-dessous) ; `--pptx-raster` écrit une image +pleine diapositive par diapositive, comme marp-cli, à une échelle de 2 par défaut que +`--image-scale` remplace. LibreOffice ouvre le résultat. Seul LibreOffice a été +testé ; PowerPoint et Keynote ne l'ont pas été. + +`--odp` écrit une présentation OpenDocument, au moyen de la fonction +`WritePresentation` de go-odf, avec ses notes de l'orateur ; `--odp-raster` écrit une image +par diapositive. + +### PPTX et ODP modifiables {#editable-pptx-and-odp} + +Depuis la v0.5.0, les diapositives d'un PPTX ou d'un ODP sont **modifiables** — le PPTX de marp-cli +est une image par diapositive, et son option expérimentale `--pptx-editable` convertit +un PDF au moyen de LibreOffice. Ici, chaque diapositive est dessinée à partir de sa mise en page, celle-là même +que dessinent les exports PNG et PDF : + +- chaque ligne de texte est une zone de texte placée à sa position dans la mise en page, dans les polices + avec lesquelles la diapositive a été mise en page — **intégrées au fichier**, si bien qu'elle a le même + aspect sur une machine dépourvue de Lato ou d'Inter (vérifié avec LibreOffice 25.8) — + avec le gras, l'italique, les couleurs, l'espacement des lettres et les liens cliquables ; +- les arrière-plans, les bordures et les cadres arrondis sont des formes ; les dégradés sont des remplissages + en dégradé dans le PPTX et des images dans l'ODP (un dégradé ODF n'a que deux + couleurs) ; +- les images ne sont stockées qu'une fois, quel que soit le nombre de diapositives qui les affichent ; +- ce qu'une diapositive ne peut pas exprimer — un élément sous `opacity`, un `filter`, une + ombre floue, une formule — devient une image de cet élément, son texte étant conservé + par-dessus. + +Rendue à nouveau par LibreOffice, une diapositive ne s'écarte que de 0,04 à 2 % des pixels de l'export +PNG. `--pptx-raster` et `--odp-raster` conservent une image par diapositive. + +### PDF vectoriel {#vector-pdf} + +Depuis la v0.4.0, le PDF est dessiné en vectoriel : go-webengine peint la diapositive mise en page +dans le PDF au lieu de la peindre en pixels. Le texte reste du texte, dans les polices avec lesquelles la +diapositive a été mise en page, intégrées sous forme de sous-ensembles avec une table `/ToUnicode` : on peut le +sélectionner, y rechercher et le copier. Les arrière-plans, les bordures et les dégradés sont des tracés +et des ombrages, une image affichée sur plusieurs diapositives n'est intégrée qu'une fois, et les liens sont +cliquables. La présentation de référence de 100 diapositives pèse 758 Kio, contre 837 Kio pour +marp-cli et 10 Mio pour le PDF en images de la v0.3.0, et elle est écrite en 0,3 s. + +Un élément sans forme vectorielle ici — sous `opacity` < 1, avec un `filter` +ou une rotation, une `box-shadow` floue, un dégradé dont l'opacité s'estompe — est +dessiné comme un bitmap de ce seul élément, son texte étant posé dessus de façon invisible pour +rester sélectionnable. Les formules sont encore des bitmaps. `--pdf-raster` écrit une image +par page, comme le faisait la v0.3.0. + +## `slides.yaml` {#slidesyaml} + +Un fichier de configuration placé à côté de la présentation s'y applique : un dossier de présentations +peut ainsi en partager un seul : + +```yaml +kinds: kinds.yaml # a mapping of kinds, or file(s) holding one +themes: # theme CSS files or directories + - themes/ +data: # template data + company: Example Inc. +html: false # allow all raw HTML +lang: en +``` + +*Kinds* : les `kinds:` propres à la présentation remplacent ceux du fichier et de la ligne de commande. +Données : les `data` du fichier et de la ligne de commande remplacent celles du front +matter de la présentation, si bien qu'une même présentation peut être générée avec des données différentes. + +## Prévu {#planned} + +Pas encore réalisé : les formules en vectoriel dans le PDF. Quand elles arriveront, elles prendront la forme d'une +commande ou d'une option décrite ici. diff --git a/content/fr/emoji.md b/content/fr/emoji.md new file mode 100644 index 0000000..28c1945 --- /dev/null +++ b/content/fr/emoji.md @@ -0,0 +1,80 @@ +--- +title: "Emoji" +weight: 36 +description: "Les shortcodes d'emoji et les emoji Unicode deviennent des images Twemoji, sans réseau : dans la page HTML, les images, le PDF, PowerPoint et OpenDocument." +tags: [emoji] +--- + +{{< callout type="warning" >}} +Les emoji sont arrivés en v0.4.0 ; la v0.3.0 n'en avait pas. +{{< /callout >}} + +Écrivez un emoji sous forme de shortcode ou avec le caractère lui-même : + +```markdown +# Launch :rocket: 🎉 + +Shortcodes :smile: :+1: :heart_eyes: and Unicode 😄 👍🏽 👩‍💻 🇫🇷 1️⃣. +``` + +Chacun devient l'image [Twemoji](https://github.com/jdecked/twemoji) que dessine Marp Core, +avec le balisage qu'écrit Marp Core : + +```html +<img class="emoji" draggable="false" alt="😄" src="data:image/svg+xml;base64,…" data-marp-twemoji=""/> +``` + +## Sans réseau {#no-network} + +Dans Marp Core, `src` est une URL sur un CDN : une présentation affichée hors ligne, imprimée ou +convertie en PDF sur une machine sans connexion montre des images cassées. +go-slides place le SVG lui-même dans une URI `data:`. Les graphismes (4 009, issus de +Twemoji 17.0.3, Emoji 17.0) sont intégrés au programme depuis le module en pur Go +[go-icons/twemoji](https://github.com/go-icons/twemoji) : 2 Mo compressés, lus +seulement quand une présentation contient un emoji. + +Les images sont dessinées de la même façon par la page HTML, par `--images`/`--image`, par `--pdf`, par +`--pptx` et par `--odp` : go-webengine rastérise le SVG. Les 4 009 +graphismes s'analysent et se dessinent tous avec son rastériseur SVG ; un test le vérifie. Depuis +la v0.5.0, ce rastériseur est [go-gfx/oksvg](https://github.com/go-gfx/oksvg) +v0.2.0 : `clipPath`, `fill-rule: evenodd` et les commandes d'arc se dessinent désormais comme dans Chrome, +ce qui a modifié un Twemoji et 211 autres, et les logos SVG ne sont plus +déformés. + +La règle qui dimensionne un emoji selon le texte (`img[data-marp-twemoji]`, un carré d'un `em`, +aligné sur la ligne de base) est ajoutée au thème comme le fait Marp Core : +un thème personnel en bénéficie aussi. + +## Ce qui est converti {#what-is-converted} + +Les règles sont celles de Marp Core ; un test les compare au HTML qu'écrit Marp Core 4.4.0 +pour une présentation couvrant tous les cas. + +| | | +| --- | --- | +| `:smile:`, `:+1:`, `:heart_eyes:` | les 1 903 noms de markdown-it-emoji, dans le texte, les titres, les liens, les tableaux, `header` et `footer` ; pas dans le code, les URL ni le texte alternatif d'une image | +| `😄`, `👩‍💻`, `🇫🇷`, `1️⃣`, `👍🏽` | les emoji Unicode, dans le texte **et dans le code** : Marp Core réécrit le HTML du code rendu | +| `✔`, `©`, `™`, `▶` | restent du texte : les caractères dont la présentation par défaut est textuelle ne sont des emoji qu'avec U+FE0F (`✔️`) ; `❤` fait exception | +| `☺︎` | reste du texte : un U+FE0E après un emoji demande le glyphe simple | +| `12:30:45`, `:not_a_name:` | restent tels qu'ils sont écrits | + +L'`id` d'un titre et le plan utilisent l'emoji, pas le nom du shortcode : +`# Launch :rocket:` a pour id `launch-%F0%9F%9A%80`, comme dans Marp. + +Là où go-slides s'écarte de Marp Core, il convertit davantage : `✌️ ☝️ ✍️ 🏋️` et +quelques autres caractères textuels par défaut que l'emoji-regex de Marp Core laisse en texte, +ainsi que la poignée de séquences que dessine Twemoji sans qu'Unicode les recense. + +## Désactivation {#turning-it-off} + +`--no-emoji` (dans le code, `slides.Config{NoEmoji: true}`) laisse les shortcodes et les +emoji Unicode sous la forme textuelle où ils ont été écrits, ce qui correspond à +`emoji: false` dans Marp Core. + +## Attribution {#attribution} + +Les graphismes sont des Twemoji, Copyright 2020 Twitter, Inc and other contributors, +sous licence [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) et +poursuivis par le projet [jdecked/twemoji](https://github.com/jdecked/twemoji). +Une présentation qui les affiche doit les créditer, par exemple dans un pied de page ou sur une +diapositive de clôture. La table des shortcodes est celle de markdown-it-emoji (MIT). diff --git a/content/fr/gallery.md b/content/fr/gallery.md new file mode 100644 index 0000000..747564c --- /dev/null +++ b/content/fr/gallery.md @@ -0,0 +1,24 @@ +--- +title: "Galerie" +weight: 12 +description: "Douze petites présentations, chacune montrant bien une chose : thèmes, kinds, gabarits, HCL, mathématiques, images, code, emoji, transitions et un exposé complet. Ouvrez-les dans le playground." +tags: [gallery, examples] +--- + +Douze petites présentations tirées du dossier +[`examples/`](https://github.com/go-slides/slides/tree/main/examples) de +go-slides, chacune montrant bien une chose. Les vignettes sont la première diapositive de +chaque présentation, rendue par go-slides lui-même lors de la génération de ce site ; la CI génère chaque +présentation en HTML et en PDF, pour qu'aucune ne se casse. + +**Ouvrir dans le playground** charge la présentation dans votre navigateur, par un lien qui n'envoie +rien à aucun serveur. Une présentation qui comporte d'autres fichiers que `deck.md` (un fichier de *kinds*, +des parties incluses, des images) ne peut pas tenir dans un lien : téléchargez son **.zip**, puis +importez-le avec le panneau de fichiers du playground, ou décompressez-le et générez la présentation avec +`slides build --allow-local-files deck.md`. + +Les illustrations sont des SVG originaux des auteurs de go-slides, placés sous +[CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/), et les présentations +qui les utilisent le mentionnent. + +{{< gallery >}} diff --git a/content/fr/getting-started.md b/content/fr/getting-started.md new file mode 100644 index 0000000..4ee751a --- /dev/null +++ b/content/fr/getting-started.md @@ -0,0 +1,127 @@ +--- +title: "Premiers pas" +weight: 10 +description: "Une première présentation, générée en HTML puis développée en Markdown Marp." +tags: [start] +--- + +## Installation {#install} + +Go 1.27.2 ou ultérieur : + +```sh +go install github.com/go-slides/slides/cmd/slides@latest +``` + +`slides` est un binaire Go statique, sans cgo : rien d'autre à installer, ni +Node ni Chrome. Cette page décrit la v0.9.0. + +## Une première présentation {#a-first-deck} + +Toute présentation Marp en est une. Enregistrez ceci sous `talk.md` : + +```markdown +--- +marp: true +theme: default +paginate: true +footer: "{{ .talk }}" +data: + talk: Pure Go slides + topics: [Why, How, Next] +--- + +<!-- _kind: title --> +# {{ .talk }} + +{{ range .topics }} +--- + +# {{ . }} +{{ end }} +``` + +Le front matter est celui de Marp, plus `data:`. Le corps est un gabarit Go : le +`range` écrit une diapositive par sujet, chacune commençant par `---`, et la première +diapositive est du type de diapositive (kind) intégré `title` (ni en-tête, ni pied de page, ni numéro de page). + +## Génération {#build-it} + +```sh +slides talk.md # writes talk.html beside the deck +slides -o slides.html talk.md # or somewhere else; -o - for standard output +``` + +`slides` sans commande équivaut à `slides build`. Les options peuvent précéder ou suivre la +présentation. Ouvrez le HTML dans un navigateur ; le clavier permet de passer d'une diapositive à l'autre. + +## Export en images, PDF, PPTX et ODP {#export-images-pdf-pptx-and-odp} + +```sh +slides talk.md --pdf # one page per slide, with an outline +slides talk.md --images png # talk.001.png, talk.002.png, … +slides talk.md --image jpeg # the first slide only +slides talk.md -o out/slides.pdf # the extension picks the format; out/ is created +slides talk.md --images jpeg --jpeg-quality 90 +slides talk.md --images png --image-scale 2 # 2560×1440 for 16:9 +slides talk.md --pdf --allow-local-files # images from under the deck's directory +slides talk.md --pptx # or -o talk.pptx, with notes +slides talk.md --odp # or -o talk.odp: the same, as OpenDocument +``` + +Aucun navigateur n'intervient : go-webengine fait le rendu des diapositives et go-pdfkit écrit +le PDF. Les images locales sont refusées, sauf si `--allow-local-files` est indiqué. +Depuis la v0.5.0, les diapositives d'un PPTX ou d'un ODP sont modifiables — zones de texte, formes +et images, polices intégrées —, là où celles de marp-cli sont des images ; `--pptx-raster` +conserve les images ([PPTX et ODP modifiables]({{< relref "cli#editable-pptx-and-odp" >}})). Depuis la v0.4.0, le PDF est vectoriel et son texte +sélectionnable (la v0.3.0 écrivait une image par page). Voir [Ligne de commande]({{< relref "cli#vector-pdf" >}}). + +## Développer la présentation {#expand-it} + +```sh +slides expand talk.md +``` + +affiche la même présentation en **Markdown Marp ordinaire** (sortie de `main` au commit +`4ae5c49`, le 2026-10-18) : + +```markdown +--- +marp: true +theme: default +paginate: true +footer: "Pure Go slides" +--- +<!-- +_class: "lead" +_footer: "" +_paginate: "false" +--> + + +# Pure Go slides + + +--- + +# Why + +--- + +# How + +--- + +# Next +``` + +On y retrouve les quatre diapositives générées par le `range`, le pied de page avec sa valeur, et le +*kind* `title` remplacé par les directives ponctuelles qu'il représente. Tout outil qui lit Marp — marp-cli, l'extension VS +Code — peut prendre le relais : rien de ce qui est écrit avec les ajouts de go-slides +ne vous enferme. + +## Pour aller plus loin {#next} + +- [Compatibilité Marp]({{< relref "marp" >}}) : ce qui est lu comme Marp le lit. +- [Gabarits]({{< relref "templates" >}}) et [Kinds]({{< relref "kinds" >}}) : les deux ajouts. +- [Ligne de commande]({{< relref "cli" >}}) : toutes les commandes et toutes les options. diff --git a/content/fr/hcl.md b/content/fr/hcl.md new file mode 100644 index 0000000..a474363 --- /dev/null +++ b/content/fr/hcl.md @@ -0,0 +1,151 @@ +--- +title: "HCL" +weight: 45 +description: "Du HCL2 partout où une présentation accepte du YAML : front matter HCL, slides.hcl, fichiers de kinds et de données, avec expressions et fonctions." +tags: [hcl, configuration] +--- + +{{< callout type="warning" >}} +HCL est arrivé en v0.3.0 ([go-slides/slides#13](https://github.com/go-slides/slides/pull/13)). +{{< /callout >}} + +Partout où `slides` lit du YAML, il lit aussi la syntaxe native de +[HCL2](https://github.com/hashicorp/hcl/blob/main/hclsyntax/spec.md) — pour +ses expressions et ses fonctions. + +| Où | YAML | HCL | +| --- | --- | --- | +| front matter | `---` … `---` | `---hcl` … `---` | +| configuration | `slides.yaml` | `slides.hcl`, cherché en premier | +| fichiers de *kinds* | `--kinds kinds.yaml`, `kinds: kinds.yaml` | tout fichier nommé `*.hcl` | +| fichiers de données | `--data data.yaml` ou `.json` | tout fichier nommé `*.hcl` | + +Les commentaires de directive (`<!-- class: invert -->`) restent en YAML, comme dans Marp, et le +corps de la présentation reste un [gabarit Go]({{< relref "templates" >}}). + +## Un front matter en HCL {#a-front-matter-in-hcl} + +```hcl +---hcl +theme = "gaia" +paginate = true +title = "Quarterly review" +data = { + company = "ACME" + quarter = 3 +} +header = format("%s — Q%d", data.company, data.quarter) +footer = "${upper(data.company)} · ${page()}/${total()}" +date = formatdate("D MMMM YYYY", now()) + +kind "chapter" { + extends = "section" + class = ["lead", "invert"] +} +--- + +# {{ .title }} +``` + +- Les **attributs** sont les clés YAML, sous les mêmes noms : les directives de Marp + (`theme`, `paginate`, `header`, `footer`, `class`, `backgroundColor`…), + les métadonnées (`title`, `author`…), `template`, `delims`, `kinds` (chemins de + fichiers) et `data`, un objet. +- Les blocs **`kind "<name>" { … }`** définissent des [types de diapositives (kinds)]({{< relref "kinds" >}}), + avec les directives locales et `extends`. +- Les **valeurs** parviennent aux directives sous les formes textuelles que leur donne YAML — + `true` devient `"true"`, une liste de classes est une liste — si bien qu'une directive se comporte + de la même façon quelle que soit la syntaxe qui l'a définie. `null` laisse une directive non définie. +- **`data`** conserve ses types : dans les gabarits, un nombre entier est un `int`, + les autres nombres des `float64`, et listes et objets restent des listes et des maps : ainsi, + `{{ add .quarter 1 }}` fonctionne. +- Une valeur HCL est définitive : les gabarits Go ne repassent pas dessus. + +## Variables {#variables} + +`data` contient les données du gabarit : le `data` du front matter lui-même, surchargé par +celui de `slides.hcl`, par les fichiers `--data` et par `--set`. `"${data.company}"` fonctionne +partout où `{{ .company }}` fonctionnerait. + +Les attributs du front matter peuvent faire référence à `data.*`, mais **pas les uns aux autres** : +`footer = title` est une erreur. Les directives ponctuelles (`_class`…) ne sont pas des +identifiants HCL ; écrivez-les dans un commentaire. + +## Fonctions {#functions} + +| | | +| --- | --- | +| `page()`, `total()` | le numéro de page et celui de la dernière page, comme `{{ page }}` et `{{ total }}` : un pied de page peut ainsi paginer | +| `now()`, `timestamp()` | l'heure, au format RFC 3339, locale et UTC ; `formatdate` la met en forme | +| `env(name)` | une variable d'environnement, `""` si elle n'est pas définie | +| `file(path)` | un fichier texte UTF-8 d'au plus 1 Mio, relatif à la présentation ou au fichier de configuration | +| nombres | `abs ceil floor log max min parseint pow signum` | +| chaînes | `chomp format formatlist join lower regex regexall regexreplace replace split strlen strrev substr title trim trimprefix trimspace trimsuffix upper` | +| collections | `chunklist coalesce coalescelist compact concat contains distinct element flatten keys length lookup merge range reverse slice sort values zipmap` | +| encodages | `csvdecode jsondecode jsonencode` | +| dates | `formatdate timeadd` | + +Toutes, sauf celles des quatre premières lignes, viennent de la bibliothèque standard de +[go-cty](https://github.com/zclconf/go-cty). Aucune n'accède au réseau. + +## slides.hcl {#slideshcl} + +```hcl +kinds = "kinds.hcl" # or a list of files +themes = ["themes/acme.css"] +data = { company = "ACME" } # or file paths +html = false +lang = "en" + +kind "quote" { + extends = "blank" + footer = "${data.company}" +} +``` + +Un attribut inconnu est une erreur, et non une opération silencieusement ignorée. Un fichier de *kinds* contient +des blocs `kind` ; ses autres attributs sont ignorés, si bien que `slides.hcl` peut en tenir +lieu. Les attributs d'un fichier de données sont les clés des données : + +```hcl +# data.hcl +company = "ACME" +year = 2026 +motto = trimspace(file("motto.txt")) +``` + +## Erreurs {#errors} + +Les erreurs sont les diagnostics de HCL, positionnés dans le fichier de la présentation lui-même : +`footer = data.compnay` sur la quatrième ligne de la présentation donne + +``` +deck.md:4:14: Unsupported attribute: This object does not have an attribute named "compnay". +``` + +## Marp et `slides expand` {#marp-and-slides-expand} + +Marp lit lui aussi `---hcl` comme le début d'un front matter — sa règle de +front matter s'ouvre sur toute première ligne commençant par trois tirets — et, le bloc +n'étant pas un mapping YAML, n'en applique rien : la présentation s'affiche quand même dans Marp, +sans ses directives. Un test le vérifie par rapport à la sortie de Marp Core. + +`slides expand` réécrit le front matter en YAML : la présentation développée est donc +du Marp ordinaire et s'affiche comme `slides` affiche l'original : + +```yaml +--- +theme: "gaia" +paginate: true +title: "Quarterly review" +header: "ACME — Q3" +footer: "ACME · 1/2" +date: "19 October 2026" +--- +``` + +## Licences {#licences} + +La prise en charge de HCL lie [hashicorp/hcl](https://github.com/hashicorp/hcl) +(MPL-2.0, utilisé sans modification comme bibliothèque) et +[zclconf/go-cty](https://github.com/zclconf/go-cty) (MIT). diff --git a/content/fr/kinds.md b/content/fr/kinds.md new file mode 100644 index 0000000..9ba7c09 --- /dev/null +++ b/content/fr/kinds.md @@ -0,0 +1,91 @@ +--- +title: "Kinds" +weight: 40 +description: "Une diapositive dit ce qu'elle est : kind désigne un préréglage de directives locales." +tags: [kinds] +--- + +Un **type de diapositive (kind)** est un préréglage nommé de directives locales : en-tête, pied de page, +pagination, classe, arrière-plan. Une diapositive dit ce qu'elle est — `title`, +`section` — au lieu de répéter à quoi elle ressemble, et l'apparence d'une présentation se modifie +à un seul endroit. + +## Utiliser un kind {#using-a-kind} + +```markdown +<!-- kind: section --> +# Part two + +--- + +<!-- _kind: blank --> +![bg](photo.jpg) +``` + +- `kind:` s'applique comme une directive locale : à cette diapositive et aux suivantes. +- `_kind:` s'applique comme une directive ponctuelle : à cette diapositive seulement. +- Changer de *kind* **efface** d'abord ce que le précédent avait défini : une classe ou un + pied de page ne déborde donc jamais d'un *kind* sur le suivant. +- Les directives écrites sur la diapositive elle-même **l'emportent** sur le *kind*. + +## Kinds intégrés {#built-in} + +| Kind | Définit | +| --- | --- | +| `title` | `class: lead`, ni numéro de page, ni en-tête, ni pied de page | +| `section` | `class: lead` | +| `content` | rien : les valeurs par défaut de la présentation | +| `blank` | ni numéro de page, ni en-tête, ni pied de page | + +Chacun d'eux peut être redéfini. + +## Définir des kinds {#defining-kinds} + +Dans le front matter : + +```yaml +--- +marp: true +kinds: + chapter: + class: invert + backgroundColor: "#1e1b4b" + paginate: false + quote: + extends: blank + class: quote +--- +``` + +`extends` fait partir un *kind* des directives d'un autre. Un *kind* accepte les +directives locales et `extends`. + +## Partager des kinds {#sharing-kinds} + +Les *kinds* d'une équipe ont leur place dans un fichier unique que chaque présentation désigne : + +```yaml +# kinds.yaml +chapter: + class: invert + paginate: false +``` + +```yaml +--- +marp: true +kinds: kinds.yaml # or a list of files +--- +``` + +ou `--kinds kinds.yaml` en ligne de commande, ou `kinds:` dans `slides.yaml`. +L'ordre est le suivant : les *kinds* intégrés, puis la ligne de commande et le +fichier de configuration, puis les `kinds:` de la présentation elle-même. + +`slides kinds talk.md` liste les *kinds* qu'une présentation peut utiliser et ce que chacun définit. + +## Quitter go-slides {#leaving-go-slides} + +`slides expand` écrit chaque *kind* sous la forme des directives ponctuelles qu'il représente, +diapositive par diapositive : le résultat est du Markdown Marp ordinaire, que tout outil Marp +sait lire. diff --git a/content/fr/marp.md b/content/fr/marp.md new file mode 100644 index 0000000..2bfae32 --- /dev/null +++ b/content/fr/marp.md @@ -0,0 +1,81 @@ +--- +title: "Compatibilité Marp" +linkTitle: "Marp" +weight: 20 +description: "Le Markdown que lit slides est celui de Marpit et de Marp Core, pas un dialecte." +tags: [marp] +--- + +Une présentation écrite pour [Marp](https://marp.app) est une présentation go-slides. Les règles +ci-dessous sont celles de [Marpit](https://marpit.marp.app) et de +[Marp Core](https://github.com/marp-team/marp-core) ; quand go-slides +s'en écarte, cette page le signale. + +## Diapositives {#slides} + +Une règle horizontale — `---` seul sur sa ligne, comme le découpage de Marpit — commence une +nouvelle diapositive, de même qu'un titre du niveau indiqué par la directive `headingDivider`. +Le front matter, entre les deux premiers `---`, contient les directives globales +et les données de la présentation. + +## Directives {#directives} + +| Type | Où | S'applique à | +| --- | --- | --- | +| globale | front matter, ou `<!-- theme: gaia -->` n'importe où | toute la présentation : `theme`, `style`, `headingDivider`, `lang`… | +| locale | `<!-- paginate: true -->` | cette diapositive et les suivantes | +| ponctuelle | `<!-- _class: lead -->` (tiret bas) | cette diapositive seulement | + +Les directives locales sont celles de Marpit : `paginate`, `header`, `footer`, `class`, +`backgroundColor`, `backgroundImage`, `backgroundPosition`, +`backgroundRepeat`, `backgroundSize`, `color`. Les directives sont analysées comme du +YAML souple, comme le fait Marp Core ; `--strict-yaml` les analyse comme le fait +Marpit seul. + +## Images {#images} + +`![bg](image.jpg)` fait d'une image l'arrière-plan de la diapositive, avec les +mots-clés de Marp : `bg contain`, `bg cover`, `bg 50%`, `bg left`/`bg right` pour un arrière-plan +partagé, plusieurs images `bg` côte à côte, et les filtres d'image +(`blur`, `sepia`, `grayscale`…). Les tailles d'image en ligne (`w:200px`, `h:`) sont +lues comme Marp les lit. + +## Autour de la diapositive {#around-the-slide} + +- **En-tête et pied de page** : directives `header:` et `footer:`, en Markdown. +- **Pagination** : `paginate: true`. +- **Notes de l'orateur** : un commentaire HTML qui n'est pas une directive est une note. + `slides notes` les affiche. +- **Listes progressives** : une liste écrite avec `*` (ou `1)`) apparaît élément par élément, + comme dans Marp. +- **`<style scoped>`** : du CSS pour une seule diapositive. +- **HTML brut** : liste d'autorisation de Marp Core par défaut ; `--html` autorise tout. +- **Emoji** : les `:shortcodes:` et les emoji Unicode deviennent des images Twemoji, comme dans + Marp Core, mais intégrées au lieu d'être chargées depuis un CDN : voir + [Emoji]({{< relref "emoji" >}}). + +## Thèmes {#themes} + +`default`, `gaia` et `uncover` sont intégrés et se choisissent avec `theme:`. Un +thème personnel est un fichier CSS portant le commentaire `/* @theme name */` de Marp, +fourni avec `--theme-set` ou listé sous `themes:` dans `slides.yaml`. + +## Différences avec Marp {#where-go-slides-differs} + +- **Gabarits.** `{{ }}` dans le corps est une action de gabarit Go (voir + [Gabarits]({{< relref "templates" >}})). Une présentation qui affiche `{{` comme du texte + hors d'un bloc de code a besoin de `--no-template`, ou d'échapper ce texte comme les gabarits Go + l'échappent. Les blocs et les extraits de code ne sont pas touchés. +- **Kinds.** `kind:` et `_kind:` sont des directives que Marp n'a pas (voir + [Kinds]({{< relref "kinds" >}})). `slides expand` les remplace par les + directives qu'elles représentent : la présentation développée est du Marp ordinaire. +- **Sorties.** HTML, PNG, JPEG, PDF, PPTX et ODP, au moyen de bibliothèques Go plutôt + que d'un navigateur headless (voir [Ligne de commande]({{< relref "cli#images-pdf-pptx-and-odp" >}}) + pour la limite connue). En v0.4.0, comme avec marp-cli, un PPTX contient des images ; depuis + la v0.5.0, ses diapositives sont des zones de texte et des formes modifiables, `--pptx-raster` + conservant les images. +- **Titres ajustés.** `# <!-- fit --> Title` est pris en charge : le titre occupe toute + la largeur de la diapositive sur une ligne, dans la page comme dans chaque export. +- **Mathématiques et code.** `$…$` et `$$…$$` suivent les règles de délimitation de Marp Core et + sont composés par go-tex/math ; le code délimité est coloré par go-rouge avec + les noms de classes de highlight.js (voir [Mathématiques et code]({{< relref "math-and-code" >}})). diff --git a/content/fr/math-and-code.md b/content/fr/math-and-code.md new file mode 100644 index 0000000..78f6a79 --- /dev/null +++ b/content/fr/math-and-code.md @@ -0,0 +1,144 @@ +--- +title: "Mathématiques et code" +weight: 35 +description: "Les formules entre dollars sont composées et le code délimité est coloré, dans la page HTML, les images et le PDF." +tags: [math, code] +--- + +{{< callout type="warning" >}} +Les mathématiques et le code sont arrivés en v0.2.0. +{{< /callout >}} + +## Mathématiques {#math} + +`$…$` est une formule en ligne et `$$…$$` une formule centrée : + +```markdown +The famous one is $E=mc^2$, and this is a display formula: + +$$ +\int_0^\infty e^{-x^2}\,dx = \frac{\sqrt{\pi}}{2} +$$ +``` + +- Les règles de délimitation sont celles de Marp Core, lues dans marp-core 4.4.0 : un `$` + suivi d'une espace n'ouvre pas de formule, et un dollar suivi d'un chiffre + ne la ferme pas, si bien que des prix comme `$5 and $10` restent du texte. +- [go-tex/math](https://github.com/go-tex/math) compose chaque formule en + SVG en ligne dimensionné en `em` : elle suit la taille du texte et repose sur sa + ligne de base. Le même SVG sert dans la page HTML, dans les images et dans le PDF. +- Une formule qui ne s'analyse pas reste visible telle qu'elle est écrite ; l'erreur est conservée dans + son `title`. + +## Code {#code} + +Les blocs de code délimités sont colorés par [go-rouge](https://github.com/go-rouge/rouge), +qui écrit les noms de classes de highlight.js, ceux que stylent les thèmes de Marp : + +````markdown +```go +fmt.Println("hello") +``` +```` + +Un langage que go-rouge ne connaît pas est écrit en texte brut, comme le fait Marp Core. + +### Numéros de ligne {#line-numbers} + +Depuis la v0.8.0, un bloc peut numéroter ses lignes. Marp Core n'a pas de numéros de ligne et +ignore ce qui suit le langage d'une clôture : les options se placent donc là, entre accolades, +et une présentation qui les utilise s'affiche dans Marp comme elle s'afficherait sans elles : + +````markdown +```go {lineNumbers} +func main() { + fmt.Println("hello") +} +``` + +```go {lineNumbers=10} +// starts at 10 +``` +```` + +| Écriture | Signification | +| --- | --- | +| `{lineNumbers}` | numérotation à partir de 1 (le `{lines:true}` de Slidev et le `{.numberLines}` de Pandoc sont aussi reconnus) | +| `{lineNumbers=10}` | numérotation à partir de 10 (également `startLine=10`, ou le `startFrom="10"` de Pandoc) | +| `{lineNumbers=false}` | aucun numéro, quoi qu'indique la diapositive | + +Pour tous les blocs d'une diapositive et des suivantes, définissez la directive +`<!-- lineNumbers: true -->` (`_lineNumbers` pour une seule diapositive). Un type de diapositive (kind) peut la définir +une fois pour toute une famille de diapositives : + +```yaml +kinds: + listing: {lineNumbers: true} +``` + +Chaque numéro ouvre sa ligne à l'intérieur du cadre de code du thème, séparé par un filet. +Il reste sur la ligne de base de sa ligne même quand celle-ci est plus haute, par exemple +à cause d'une annotation ou d'un emoji. Les numéros sont atténués par une couleur mélangeant la +couleur du code et celle de son fond, avec un contraste d'au moins 4,5:1 dans les thèmes default, +gaia et uncover, clairs comme sombres. Ils ne sont **pas sélectionnables** dans la +page et ne sont pas lus par les lecteurs d'écran. Dans un PDF, ils sont dessinés mais exclus +du texte : un extracteur de texte ne lit que le code. Dans un fichier PowerPoint ou +OpenDocument, ce sont des textes modifiables en début de chaque ligne. Une présentation +qui n'utilise pas l'option s'affiche comme avant, à l'octet près. La mise en évidence +de lignes choisies n'est pas encore prise en charge. + +### Annotations de code {#code-callouts} + +Depuis la v0.8.0, une ligne de code peut se terminer par une pastille numérotée qui renvoie à une note +sous le bloc. La syntaxe est +[celle de Quarto](https://quarto.org/docs/authoring/code-annotation.html), qui est +aussi la forme en commentaire d'Asciidoctor. Le marqueur est un commentaire dans le langage même +du code, qui reste donc valide, et les notes forment la liste numérotée qui suit +immédiatement le bloc : + +````markdown +```go +func main() { // <1> + x := load() // <2> + fmt.Println(x) +} +``` + +1. The entry point. +2. Reads the input. +```` + +| Écriture | Signification | +| --- | --- | +| `// <1>`, `# <1>`, `-- <1>`, `; <1>`, `% <1>`, `' <1>` | un commentaire de ligne qui ne contient que le marqueur : les deux sont remplacés par la pastille | +| `<!-- <1> -->`, `/* <1> */` | la même chose, pour les langages à commentaires délimités | +| `// <1> <2>` | deux pastilles sur une ligne | +| `x = 1 # count <1>` | le texte du commentaire reste et la pastille le suit | + +Les marqueurs ne deviennent des pastilles que si la liste qui suit immédiatement le bloc commence à 1 +et comporte un élément pour chaque numéro. Sinon, le bloc reste tel qu'il est écrit. Les +pastilles sont des anneaux de la couleur du code, dans les thèmes default, gaia et uncover, +clairs ou sombres. Les éléments de la liste sont numérotés avec les mêmes pastilles. + +Les pastilles ne sont **pas sélectionnables**. Copier le code depuis la page donne le code +sans ses marqueurs. Dans un PDF, ce sont des vecteurs exclus du texte, et dans un fichier +PowerPoint ou OpenDocument, des formes modifiables. Les annotations se combinent +avec les numéros de ligne. Marp Core n'a pas d'annotations (marp-core 4.4.0) : dans Marp, les +commentaires et la liste numérotée s'affichent donc tels qu'ils sont écrits. Une présentation qui ne les utilise pas +s'affiche comme avant, à l'octet près. + +Depuis la v0.9.0, une pastille et sa note se répondent dans la page HTML. Survoler une +pastille, ou l'atteindre avec Tab, teinte sa note, et une note fait de même pour sa +pastille. C'est ce que fait `code-annotations: hover` dans Quarto, mais ici la feuille de style +s'en charge seule, sans script. Cela fonctionne donc de la même façon dans l'aperçu +isolé du playground et dans l'aperçu de VS Code, qui restreignent tous deux les +scripts. Chaque pastille est décrite par sa note : un lecteur d'écran lit la note +quand il atteint la pastille. La teinte n'apparaît en fondu que si le lecteur n'a pas demandé +de mouvements réduits. Les images, les PDF et les fichiers PowerPoint et OpenDocument sont +inchangés, à l'octet près. + +## Polices {#fonts} + +Lato Regular, Bold, Black et italiques ainsi que Roboto Mono Regular, Bold et +italiques sont intégrées : les titres de gaia ont leur graisse et le texte en italique +s'affiche bien en italique. diff --git a/content/fr/performance.md b/content/fr/performance.md new file mode 100644 index 0000000..a15320e --- /dev/null +++ b/content/fr/performance.md @@ -0,0 +1,88 @@ +--- +title: "Performances" +weight: 60 +description: "La vitesse à laquelle go-slides génère une présentation, et avec quelle mémoire, mesurées face à marp-cli sur la même machine." +tags: [performance] +--- + +{{< callout type="info" >}} +Les chiffres PPTX ci-dessous concernent la v0.5.0 et ceux du PDF vectoriel la v0.4.0. +Le premier tableau concerne la v0.3.0, +la version précédente, qui fait le rendu des diapositives en parallèle et n'analyse ses polices +qu'une fois ([go-slides/slides#11](https://github.com/go-slides/slides/pull/11)). +{{< /callout >}} + +go-slides et [marp-cli](https://github.com/marp-team/marp-cli) 4.5.1 ont été +lancés en sous-processus sur les mêmes présentations, sur un Apple M4 Max (16 CPU) avec une +charge moyenne sur 1 minute inférieure à 8, en cinq tours entrelacés après un échauffement. +Le temps est la médiane du temps écoulé ; la mémoire est le pic de toute l'arborescence de processus +(marp-cli pilote un Chrome, dont les processus sont comptés). + +| Présentation | Sortie | go-slides | marp-cli | mémoire go-slides | mémoire marp-cli | +| --- | --- | ---: | ---: | ---: | ---: | +| 100 diapositives | HTML | 36 ms | 315 ms | 47 Mio | 140 Mio | +| 100 diapositives | PDF | 1,14 s | 1,88 s | 242 Mio | 1 509 Mio | +| 100 diapositives | PNG, toutes les diapositives | 0,97 s | 5,44 s | 225 Mio | 1 354 Mio | +| 100 diapositives | PPTX | 3,39 s | 8,03 s | 530 Mio | 1 373 Mio | +| gaia, 20 diapositives | PDF | 283 ms | 1,57 s | 154 Mio | 1 315 Mio | +| mathématiques et code, 20 diapositives | PNG, toutes les diapositives | 250 ms | 2,32 s | 168 Mio | 1 286 Mio | + +## PDF vectoriel {#vector-pdf} + +Depuis la v0.4.0, le PDF est dessiné en vectoriel (voir +[Ligne de commande]({{< relref "cli#vector-pdf" >}})). Même machine et même méthode, +le 2026-10-19 : + +| Présentation | go-slides | marp-cli | v0.3.0 | taille go-slides | taille marp-cli | taille v0.3.0 | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 100 diapositives | 306 ms | 1 763 ms | 1 147 ms | 758 Kio | 837 Kio | 10,0 Mio | +| gaia, 20 diapositives | 93 ms | 1 546 ms | 285 ms | 32 Kio | 66 Kio | 582 Kio | +| mathématiques et code, 20 diapositives | 140 ms | 1 643 ms | 294 ms | 124 Kio | 335 Kio | 638 Kio | + +Mémoire pour le PDF de 100 diapositives : 158 Mio, contre 1 512 Mio pour marp-cli ; temps +CPU : 0,9 s contre 2,7 s. + +Les chiffres de la v0.4.0 pour le PDF de 100 diapositives ont été mesurés le 2026-10-19 à 21 h 29, +avec une charge moyenne sur 1 minute de 7,7, sur trois tours : go-slides 306 ms, 758 +Kio et 158 Mio ; marp-cli 1 838 ms, 837 Kio et 1 512 Mio. + +## PPTX modifiable {#editable-pptx} + +Depuis la v0.5.0, le PPTX est composé de zones de texte, de formes et d'images (voir +[Ligne de commande]({{< relref "cli#editable-pptx-and-odp" >}})). Mesuré le +2026-10-19 à 22 h 51, avec une charge moyenne sur 1 minute de 7,1, sur trois tours : + +| Présentation | go-slides | marp-cli | taille go-slides | taille marp-cli | mémoire go-slides | mémoire marp-cli | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 100 diapositives | 348 ms | 7 795 ms | 760 Kio | 7,2 Mio | 204 Mio | 1 370 Mio | +| gaia, 20 diapositives | 112 ms | 4 608 ms | 893 Kio | 4,8 Mio | | | +| mathématiques et code, 20 diapositives | 207 ms | 2 780 ms | 842 Kio | 1,5 Mio | | | + +Pour la présentation de 100 diapositives, la v0.4.0 demandait 4,7 s, 4,8 Mio et 540 Mio. Le +PPTX de marp-cli contient des images. L'essentiel de la taille d'une présentation modifiable tient aux polices intégrées. + +## Pourquoi c'est rapide {#why-it-is-fast} + +- **Aucun navigateur à démarrer.** marp-cli lance Chrome pour chaque export PDF, image et + PPTX ; go-slides fait le rendu avec + [go-webengine](https://github.com/go-webengine/engine) dans son propre + processus. +- **Les diapositives sont rendues en parallèle**, quatre à la fois par défaut, et chaque image est + écrite dès que son tour vient : une longue présentation n'est jamais conservée en mémoire sous forme + d'images. +- **Les polices sont analysées une seule fois** par processus, et non une fois par diapositive. + +## Là où il ne gagne pas {#what-it-does-not-win} + +- **La taille du PDF, en v0.3.0.** Chaque diapositive était une image dans le PDF : 10 Mio contre + 837 Kio pour la présentation de 100 diapositives. Le PDF vectoriel ci-dessus est plus petit que celui de + marp-cli. +- **Le temps CPU sur les grosses présentations.** Quatre workers consomment plus de CPU que ne le laisse voir + le temps écoulé : en v0.3.0, 3,2 s contre 2,7 s pour marp-cli sur le PDF de 100 diapositives + (0,9 s pour le PDF vectoriel). +- **La mémoire est une borne supérieure pour marp-cli** : les processus de Chrome partagent des pages + que la somme compte dans chacun d'eux. Son plus gros processus pris isolément occupe 220 à 320 Mio. + +La méthode, chaque présentation et chaque mode, ainsi que les commandes permettant de reproduire les chiffres, +se trouvent dans +[BENCHMARKS.md](https://github.com/go-slides/slides/blob/main/BENCHMARKS.md). diff --git a/content/fr/playground.md b/content/fr/playground.md new file mode 100644 index 0000000..05cd45b --- /dev/null +++ b/content/fr/playground.md @@ -0,0 +1,173 @@ +--- +title: "Playground" +weight: 15 +description: "Écrivez une présentation dans votre navigateur et regardez les diapositives suivre : go-slides compilé en WebAssembly, avec complétion, aide et exemples." +tags: [playground, browser] +--- + +**[Ouvrir le playground →](https://go-slides.github.io/playground/)** + +Le playground, c'est go-slides lui-même, en v0.9.0, compilé en WebAssembly et +exécuté dans votre navigateur. Tapez une présentation à gauche ; les diapositives suivent à +droite. Rien n'est envoyé à un serveur : ni la présentation, ni un lien de partage. + +## Ce qu'il propose {#what-it-has} + +- **Aperçu en direct** de chaque diapositive, rendu hors du thread de la page (dans un Web + Worker) : la saisie ne l'attend jamais. Cliquez sur une diapositive pour amener l'éditeur + à sa ligne ; déplacez le curseur et l'aperçu défile jusqu'à sa diapositive. +- **Diaporama** : la présentation elle-même, avec le clavier, le + contrôleur à l'écran et les transitions. +- **Mode présentateur** : **Present** (ou `p` dans le diaporama) l'ouvre dans une + seconde fenêtre : la diapositive courante, l'étape suivante, les notes de l'orateur (les + `<!-- comments -->` de la présentation), une horloge et un chronomètre. Les touches fléchées, Espace, + Début et Fin fonctionnent dans l'une ou l'autre fenêtre, et chacune suit l'autre, y compris + quand vous modifiez la présentation. Si la fenêtre ne s'ouvre pas, autorisez les fenêtres surgissantes pour + le site. La vue d'ensemble (`o`) n'existe que dans la page téléchargée. +- **Complétion** pendant la saisie : clés de directive (les ponctuelles, avec `_`, dans les commentaires), + les valeurs de `kind`, `theme`, `size`, `paginate`, ainsi que les fonctions de gabarit + et les champs de données dans `{{ }}`. +- **Aide** sur ce qui se trouve sous le curseur : une directive, un type de diapositive (kind) (ce qu'il définit), + une fonction de gabarit. Le volet d'aide renvoie aussi vers ces pages. +- **Problèmes** sur leur ligne : les erreurs de gabarit, de YAML et de HCL sont mises en évidence + dans l'éditeur et listées en dessous, et l'aperçu conserve la dernière présentation + rendue avec succès. +- **Exemples** : une présentation d'accueil, des gabarits qui génèrent des diapositives, des *kinds*, + un front matter HCL, des thèmes et des directives ponctuelles, ainsi que des mathématiques, des images et des + arrière-plans (`![bg]`, un `![bg left:40%]` partagé, des filtres). +- **Fichiers** : le panneau à gauche de l'éditeur est le projet de la présentation, organisé + comme le lisent la [commande]({{< relref "cli" >}}) et + l'[extension VS Code]({{< relref "vscode" >}}) : `deck.md`, + `slides.yaml`, `themes/`, `kinds/`, `media/`. + - Cliquez sur un fichier pour le modifier. L'éditeur le colore selon son langage + (Markdown, YAML, HCL, JSON, CSS) et signale ses problèmes. + - Clic droit (ou Maj+F10) pour New File, New Folder, Rename (F2), + Delete (avec confirmation) et New theme / New kind. Faites glisser un fichier sur un dossier + pour l'y déplacer. Le clavier suit le modèle d'arborescence WAI-ARIA, recherche par saisie + comprise. + - **Project ▸ New theme** crée `themes/my-gaia.css` (ou à partir de default, + uncover) : un `@import` du thème intégré et les règles que vous ajoutez. Il + rejoint la liste Theme. **New kind** et **New data file** partent eux aussi + de modèles. Chacun est listé dans `slides.yaml`, si bien que `slides build deck.md` + le trouve. + - **Project ▸ Download project (.zip)** enregistre le tout. Une fois décompressé, + `slides build deck.md` le génère. **Open project** reprend un `.zip` ou un + dossier. Une archive qui sort de son dossier (`..`, chemins absolus), + contient plus de 256 fichiers ou plus de 8 Mio dans un fichier, ou se décompresse à plus de 100 + fois sa taille est refusée. + - Tout est conservé dans ce navigateur avec le brouillon. Un projet peut contenir jusqu'à + 64 Mio, et la ligne sous les fichiers indique l'espace utilisé. +- **Médias** : déposez ou collez une image, une vidéo ou un son dans l'éditeur : il + va dans `media/`, et la présentation le désigne : `![](media/photo.png)`. Déposez + des fichiers sur un dossier du panneau pour les y placer. La présentation reste légère et + lisible, et rien n'est téléversé. Le bac à sable de l'aperçu les charge + sans être assoupli (la page lui transmet les octets). **Save as ▸ HTML + page** les intègre à la page. Les images `https:` fonctionnent aussi, mais le navigateur + les récupère sur leur serveur, qui voit alors votre adresse. L'aperçu + n'envoie pas de referrer. +- **Volets redimensionnables** : le panneau de fichiers, l'éditeur, les vignettes des diapositives, + l'aperçu et l'aide sont séparés par des barres que vous pouvez faire glisser (le pointeur + prend la forme d'un curseur de redimensionnement au-dessus). Le volet voisin de la barre prend la place, + les autres gardent leur taille. Double-cliquez sur une barre, ou appuyez sur Entrée dessus, pour + refermer le volet puis le rouvrir. Au clavier, F6 passe d'une barre à l'autre, + les touches fléchées déplacent une barre de 16 px, Début et Fin amènent le volet + qui la précède à sa taille minimale ou maximale, et Échap revient à l'éditeur. + L'onglet View permet toujours de masquer des volets. Les tailles que vous définissez sont conservées dans ce + navigateur, avec le brouillon ; les tailles par défaut (190, 168 et 280 px, l'éditeur + et l'aperçu se partageant le reste à raison de 6 pour 5) ne sont jamais enregistrées. +- **Mise en forme** : l'onglet Format modifie le Markdown à votre place, et chaque clic + correspond à une étape d'annulation. + - **Texte** : Bold, Italic, Strikethrough et Inline code agissent sur la sélection + ou sur le mot sous le curseur, et basculent (Ctrl+B, Ctrl+I, Ctrl+Maj+X et + Ctrl+E ; Cmd sur Mac). Un bouton apparaît enfoncé quand le curseur est à l'intérieur + de ses marques. Markdown n'a pas de soulignement : Underline (Ctrl+U) entoure le texte de + `<u>…</u>`, une balise que conserve la liste d'autorisation HTML par défaut de Marp : cela fonctionne donc avec + le HTML brut désactivé. + - **Paragraphe** : Heading 1 à 3 (Ctrl+Alt+1..3), puces (Ctrl+Maj+8), + numérotation (Ctrl+Maj+7) et citation agissent sur les lignes sélectionnées et basculent. + La règle horizontale est la balise `<hr>` : `---`, `***` et `___` commenceraient chacun + une nouvelle diapositive dans Marp. + - **Tableau** : une grille permet de choisir colonnes et lignes. Avec le curseur dans un tableau, vous + ajoutez ou supprimez une ligne ou une colonne et alignez la colonne (`:--`, `:-:`, + `--:`) ; le tableau est réécrit aligné après chaque modification. + - **Slide text** à gauche, au centre ou à droite écrit un bloc `<style scoped>` dans + la diapositive courante. Marp n'a pas de syntaxe d'alignement, et un attribut `style` + ne passe pas la liste d'autorisation HTML par défaut, mais le bloc scoped si : cela + fonctionne donc avec le HTML brut désactivé, et se voit dans l'aperçu, la page HTML et le PDF. + - **Link** (Ctrl+K) écrit `[text](url)` avec l'url sélectionnée. +- **Coloration** des clés du front matter à toutes les profondeurs, en YAML (y compris + dans les mappings `{a: 1}`) et en HCL. +- **Sélecteur de thème** : default, gaia, uncover, les thèmes du projet + (`themes/*.css`) et les thèmes que définit la présentation (`/* @theme name */` dans un + `<style>`). Il écrit `theme:` dans le front + matter, en YAML ou en HCL, si bien qu'un lien partagé le conserve, et il suit le front + matter quand vous le modifiez. +- L'aperçu **garde sa position** pendant la saisie : seules les diapositives qui + ont changé sont remplacées, et un changement de thème rouvre l'aperçu sur la + même diapositive. +- **Lien de partage** : la présentation voyage dans le lien lui-même (dans sa partie `#`, + compressée), si bien qu'ouvrir le lien ouvre la présentation. Les liens transportent jusqu'à + 64 Kio compressés, et la présentation seule : quand le projet contient davantage + (thèmes, *kinds*, médias), Share le signale et propose le `.zip`. +- **Save as** : + - la page HTML, identique à celle qu'écrit `slides deck.md` ; + - un **PDF** (vectoriel, avec texte sélectionnable et plan) ; + - une présentation **PowerPoint** ou **OpenDocument** modifiable. + + Ils sont produits dans votre navigateur, par le même code que `slides --pdf`, + `--pptx` et `--odp` : le PDF de la présentation de référence de 100 diapositives est + identique, octet pour octet, à celui de la commande. Le premier export télécharge le moteur + d'export (environ 21 Mio, une seule fois ; votre navigateur le conserve jusqu'à la prochaine mise à jour), + avec une ligne de progression. La présentation de 100 diapositives prend ensuite environ 3 secondes. Une + image `https:` n'est incluse que si son serveur l'autorise (CORS) ; les images `data:` + le sont toujours. +- **Votre brouillon est conservé** : + - La présentation est enregistrée dans ce navigateur pendant la saisie (IndexedDB, pour ce site + uniquement) et revient quand vous rechargez la page. Rien n'est envoyé nulle part. Les + fichiers texte du projet (thèmes, *kinds*, données, `slides.yaml`) sont conservés de la + même façon, même si vous fermez l'onglet juste après avoir tapé ; les médias sont + enregistrés au moment où ils sont déposés. + - Un lien partagé ouvre sa présentation et conserve votre brouillon : **Draft ▸ Restore my + draft** le fait revenir. + - Un exemple, **Draft ▸ Reset to the example** (qui demande confirmation) et + un autre onglet ne l'effacent pas non plus : **Draft ▸ Restore replaced** le + rétablit. + - **Draft ▸ Forget my drafts** efface tout ce qui est enregistré. + +Chaque export fait le rendu du projet qu'affiche l'aperçu : son `slides.yaml`, +ses thèmes, ses *kinds*, ses fichiers de données et ses médias, les médias étant intégrés au document. + +Les images en tant que fichiers (`--images` de slides) ne sont pas produites dans le navigateur : utilisez la +[commande]({{< relref "cli" >}}) ou +l'[extension VS Code]({{< relref "vscode" >}}). + +## Sécurité {#security} + +La présentation s'affiche dans une iframe isolée, privée de l'origine de la page : elle ne peut +atteindre ni le playground ni son stockage, ne soumet aucun formulaire et ne navigue +nulle part. Sa page porte une Content Security Policy qui n'exécute que ses propres +scripts. Le HTML brut de la présentation suit la liste d'autorisation de Marp, comme `slides` le fait par +défaut. Un rendu qui dure plus de 5 secondes (un gabarit qui boucle) +est interrompu. + +La fenêtre du présentateur conserve ce bac à sable. Elle est ouverte par le playground +lui-même et affiche la présentation dans une iframe isolée de la même façon. Les deux +iframes ne peuvent pas partager de stockage : le playground relaie donc le numéro de diapositive +de l'une à l'autre par messages. Aucun cadre qui exécute la présentation ne reçoit l'origine du +playground. + +Les médias du projet parviennent aux cadres isolés sous forme d'octets dans un message, et +chaque cadre en fait ses propres URL `blob:`, dans sa propre origine. Une URL `blob:` +créée par le playground ne s'y charge pas, et la politique des cadres +n'ajoute que `blob:` pour les médias et les polices. + +## Taille et vitesse {#size-and-speed} + +Le module pèse 8,8 Mio tel que GitHub Pages le sert (gzip ; Pages ne +sert pas de brotli). Il est téléchargé une fois avec une barre de progression, puis mis en cache par le +navigateur jusqu'au prochain déploiement. Sur un Apple M4 Max avec Chrome 155, mesuré +sur la page en ligne, l'éditeur est prêt 2,1 s après l'ouverture de la page, dont 1,5 s +de téléchargement, et les premières diapositives apparaissent à 2,4 s. Une modification de +la présentation de référence de 100 diapositives atteint l'aperçu en 30 ms environ : go-slides +ne rend que les diapositives qui ont changé, et seules celles-ci sont remplacées dans +l'aperçu. diff --git a/content/fr/templates.md b/content/fr/templates.md new file mode 100644 index 0000000..dcb19e0 --- /dev/null +++ b/content/fr/templates.md @@ -0,0 +1,77 @@ +--- +title: "Gabarits" +weight: 30 +description: "Interpolation de gabarits Go text/template dans une présentation : valeurs, diapositives générées, numéros de page, inclusions." +tags: [templates] +--- + +La présentation est un gabarit Go [`text/template`](https://pkg.go.dev/text/template), +évalué **avant** l'analyse du Markdown. Tout ce qu'offre le langage de gabarits +de Go — actions, pipelines, `if`, `range`, `with`, variables — fonctionne +dans une présentation. + +## Données {#data} + +Le point du gabarit contient les clés du front matter, puis les clés de son +mapping `data:`, puis les données fournies en ligne de commande ou dans `slides.yaml` +— chacune remplaçant la précédente. + +```markdown +--- +marp: true +title: Quarterly review +data: + team: Platform +--- + +# {{ .title }} + +Presented by the {{ .team }} team. +``` + +En ligne de commande : + +| Option | Fournit | +| --- | --- | +| `--data file.yaml` | un fichier de données YAML ou JSON (répétable) | +| `--set key=value` | une valeur (répétable) | + +## Générer des diapositives {#generating-slides} + +Le corps entier est un seul gabarit : un `range` qui écrit `---` écrit donc des diapositives : + +```markdown +{{ range .releases }} +--- + +# {{ .version }} + +{{ range .changes }}- {{ . }} +{{ end }} +{{ end }} +``` + +## Numéro de page et total {#page-and-total} + +`{{ page }}` et `{{ total }}` sont le numéro de la diapositive et le nombre de +diapositives. Ils ne sont connus qu'une fois la présentation découpée : ils sont donc renseignés +à ce moment-là — une présentation générée compte les diapositives qu'elle a générées. + +```markdown +<!-- footer: "{{ page }} / {{ total }}" --> +``` + +## Inclusion {#include} + +`{{ include "parts/agenda.md" }}` insère un autre fichier, relatif à la +présentation. + +## Le code n'est pas touché {#code-is-left-alone} + +Les blocs de code délimités et les extraits de code en ligne sont mis de côté avant l'exécution du gabarit +puis restitués tels quels : une diapositive consacrée aux gabarits Go peut ainsi montrer +`{{ .Name }}` sans l'échapper. + +## Désactivation {#turning-it-off} + +`--no-template` lit la présentation comme du Marp ordinaire : `{{` est du texte. diff --git a/content/fr/vscode.md b/content/fr/vscode.md new file mode 100644 index 0000000..6e21e54 --- /dev/null +++ b/content/fr/vscode.md @@ -0,0 +1,186 @@ +--- +title: "VS Code" +weight: 55 +description: "L'extension go-slides pour VS Code : aperçu, présentation, problèmes, complétion et export, comme Marp for VS Code le fait pour Marp." +tags: [vscode, editor] +--- + +{{< callout type="info" >}} +L'extension est [go-slides/vscode](https://github.com/go-slides/vscode), +versionnée séparément (v0.4.0). Elle fait le rendu avec go-slides v0.9.0 (le paquet +`editor`, les lignes sources de la synchronisation du défilement, la vue d'ensemble et le +mode présentateur). Elle s'installe à partir du fichier `go-slides-0.4.0.vsix` joint à +sa version GitHub : sa publication sur le VS Code Marketplace et sur Open VSX est +prête, mais elle n'y figure pas encore. +{{< /callout >}} + +L'extension est l'équivalent, pour go-slides, de +[Marp for VS Code](https://github.com/marp-team/marp-vscode) : la présentation s'affiche dans +l'aperçu Markdown de VS Code sous forme de diapositives, ses erreurs dans le panneau Problèmes, et +les directives, les types de diapositives (kinds) et les fonctions de gabarit se complètent pendant la saisie. + +## Installation {#install} + +Téléchargez `go-slides-<version>.vsix` depuis les +[versions publiées](https://github.com/go-slides/vscode/releases), puis lancez +*Extensions: Install from VSIX...*, ou : + +```sh +code --install-extension go-slides-0.4.0.vsix +``` + +L'aperçu n'a besoin de rien d'autre. L'export nécessite la commande `slides` : + +```sh +go install github.com/go-slides/slides/cmd/slides@latest +``` + +## Aperçu {#preview} + +Ouvrez une présentation, puis *Markdown: Open Preview to the Side*. Une présentation est un fichier Markdown +dont le front matter indique `slides: true` : + +```markdown +--- +slides: true +paginate: true +data: + who: World +--- + +# Hello {{ .who }} + +--- + +<!-- _kind: title --> +# Page {{ page }} of {{ total }} +``` + +Les fichiers avec `marp: true` sont eux aussi prévisualisés, sauf si Marp for VS Code est installé : +les deux extensions rendraient le même document. Le paramètre +`goSlides.preview.marpDocuments` en décide : `auto` (par défaut), `always` ou +`never`. + +L'aperçu, c'est go-slides lui-même, compilé en WebAssembly et chargé par +l'extension : aucun binaire, et il fonctionne sur [vscode.dev](https://vscode.dev). Il lit +dans l'espace de travail ce qu'utilise la présentation — `slides.yaml` ou `slides.hcl` à côté de +la présentation, les fichiers de [kinds]({{< relref "kinds" >}}) qu'ils désignent, les fichiers qu'un +[gabarit]({{< relref "templates" >}}) inclut, le CSS des thèmes — et uniquement dans le +dossier de l'espace de travail de la présentation. + +## Synchronisation du défilement {#scroll-sync} + +Depuis la v0.2.0 de l'extension (go-slides v0.7.0), l'éditeur et +l'aperçu défilent ensemble, dans les deux sens, comme pour du Markdown ordinaire : l'aperçu +suit la diapositive et le bloc sous le curseur de l'éditeur, et +faire défiler l'aperçu déplace l'éditeur. Les paramètres sont ceux de VS Code lui-même, +`markdown.preview.scrollPreviewWithEditor` et +`markdown.preview.scrollEditorWithPreview`. + +Les lignes sont suivies à travers les gabarits. Une diapositive générée par un `{{ range }}` +suit la ligne de son gabarit, et ce qu'apporte un `{{ include }}` +suit la ligne de l'inclusion. Une présentation découpée par `headingDivider` se synchronise sur ses +titres. + +## Présentation {#presenting} + +Depuis la v0.4.0 de l'extension (go-slides v0.9.0), une présentation peut être projetée +sans quitter VS Code. La présentation ouverte dans l'éditeur, depuis la palette de commandes +ou le menu `…` de l'éditeur : + +| Commande | Ouvre | +|---|---| +| *go-slides: Open Overview* | un panneau avec toutes les diapositives dans une grille. Une diapositive choisie affiche sa source dans l'éditeur ; `Esc` ferme le panneau | +| *go-slides: Start Presenter View* | les diapositives et, à côté, le mode présentateur : l'étape suivante, les notes de l'orateur et un chronomètre | + +Les panneaux se placent à côté de l'éditeur et se suivent : changez de diapositive dans l'un +et les autres changent avec lui. Ils suivent aussi vos modifications de la présentation. La touche `p` +dans les diapositives ouvre le mode présentateur. + +Pour projeter sur un autre écran, déplacez-y le panneau des diapositives avec *View: Move +Editor into New Window*. + +Chaque panneau exécute la présentation dans un cadre isolé (sans `allow-same-origin`) sous une +politique `default-src 'none'` : le cadre charge les images, les médias, les styles et les polices, +rien d'autre, et n'exécute que les scripts que go-slides a intégrés à la page, +désignés par leur empreinte. Un script écrit par la présentation ne s'exécute pas. + +## Problèmes, complétion, survol {#problems-completion-hover} + +- **Problèmes** : les erreurs de gabarit, de YAML et de [HCL]({{< relref "hcl" >}}), sur la + ligne où elles se trouvent, dans la présentation ou dans le fichier de *kinds* dont elles proviennent. +- **Complétion** : les clés de directive dans le front matter et dans les commentaires `<!-- -->` + (avec `_` pour une directive ponctuelle), les valeurs de `kind` et de `_kind` (avec ce que + définit chaque *kind*), `theme`, `size`, `paginate`, `math`, `headingDivider` ; dans + `{{ }}`, les fonctions de gabarit et les champs de données. +- **Survol** : ce que fait une directive, ce que définit un *kind*, les arguments d'une + fonction de gabarit. + +## Export {#export} + +*go-slides: Export Slide Deck...* (également dans la barre de titre de l'éditeur) écrit du HTML, +du PDF vectoriel, du PPTX et de l'ODP modifiables, du PNG ou du JPEG avec la commande `slides`, comme +le ferait `slides -o deck.pdf deck.md` ; voir [Ligne de commande]({{< relref "cli" >}}). +Il nécessite un espace de travail approuvé. Si `slides` est introuvable, l'extension propose de +lancer le `go install` ci-dessus dans un terminal. + +## Paramètres {#settings} + +| Paramètre | Défaut | | +|---|---|---| +| `goSlides.cliPath` | `""` | la commande `slides` ; vide : `slides` dans le `PATH`. Paramètre de machine : un espace de travail ne peut pas le définir | +| `goSlides.exportType` | `pdf` | le format que la boîte de dialogue d'export propose en premier | +| `goSlides.exportAutoOpen` | `true` | ouvrir le fichier exporté | +| `goSlides.html` | `false` | autoriser tout le HTML brut au lieu de la liste d'autorisation de Marp ; pris en compte uniquement dans un espace de travail approuvé | +| `goSlides.preview.marpDocuments` | `auto` | prévisualiser les présentations `marp: true` : `auto`, `always`, `never` | + +## Sécurité {#security} + +La présentation et l'espace de travail ne sont pas dignes de confiance. Le HTML brut suit la liste d'autorisation de Marp, +sauf si `goSlides.html` est activé dans un espace de travail approuvé (un `slides.yaml` avec +`html: true` ne l'active pas dans l'aperçu), et l'aperçu de VS Code n'exécute aucun +script de la présentation. L'aperçu ne lit que les fichiers du dossier de l'espace de travail de la présentation, +ne suit aucun lien symbolique, et les gabarits ne voient aucune variable d'environnement. +L'export exécute un programme : il attend donc que l'espace de travail soit approuvé, et le programme n'est +jamais désigné par l'espace de travail. + +## Performances {#performance} + +Sur la présentation de référence de 100 diapositives de go-slides, dans VS Code 1.142 sur un Apple M4 Max, +avec Marp for VS Code 3.6.1 mesuré dans la même session : + +| | go-slides | Marp for VS Code | +|---|---:|---:| +| activation (chargement du module WebAssembly) | 189 ms | — | +| premier aperçu | 52 ms | 78 ms | +| aperçu après une modification (médiane) | **19 ms** | 23–24 ms | +| `.vsix` | 7,5 Mo | 36,3 Mo | + +Les chiffres concernent l'extension v0.3.0 (go-slides v0.8.0) sur une présentation de 100 diapositives, +en tapant un caractère dans la diapositive 50, sur des tours entrelacés avec une charge moyenne sur 1 minute +inférieure à 8. Le premier aperçu est plus rapide que celui de Marp, et l'aperçu après une +modification l'est désormais aussi. + +### Dans la v0.8.0 et l'extension v0.3.0 {#in-v080-and-extension-v030} + +go-slides v0.8.0 conserve les diapositives rendues de chaque présentation ouverte et, après une modification, +ne refait le rendu que de celles dont la source ou l'état hérité a changé (directives, numéro de +page, total, ids des titres, références de liens, lignes sources) ; la sortie est +identique à celle d'un rendu complet, octet pour octet. Taper dans la diapositive 50 sur 100 refait le rendu d'une seule +diapositive : 2,5 ms en natif, 11,5 ms en WebAssembly. L'aperçu de VS Code ne transforme alors +que le DOM de la diapositive modifiée, et le test de fumée de l'extension tape un +caractère dans la diapositive 50 entre deux rendus chronométrés, pour les deux extensions. Le +mode présentateur de l'extension se synchronise via la fenêtre parente (`postMessage`). + +### Dans la v0.7.1 et l'extension v0.2.2 {#in-v071-and-extension-v022} + +go-slides v0.7.1 met en cache, d'un rendu à l'autre, les thèmes analysés, les feuilles de style compactées et le code coloré, +marque les lignes de synchronisation du défilement sans parcourir les octets un à un, et réutilise +ses writers : un rendu de la présentation de 100 diapositives alloue 5,7 Mo, contre 52,7 Mo +pour l'extension v0.2.0. Grâce à cela, l'extension v0.2.2 abandonne le forçage `GOGC=400`. +À titre de comparaison, l'extension v0.2.1 prenait 72 ms pour le premier aperçu et 44 ms +après une modification avec le `GOGC` par défaut (36 ms avec `GOGC=400`), et la v0.2.0 prenait +47 à 51 ms. + +L'extension v0.2.2 a mesuré 52 ms pour le premier aperçu et 24 ms après une modification, +à égalité avec les 23 ms de Marp. diff --git a/content/ja/_index.md b/content/ja/_index.md new file mode 100644 index 0000000..f56aa2a --- /dev/null +++ b/content/ja/_index.md @@ -0,0 +1,57 @@ +--- +title: "go-slides ドキュメント" +linkTitle: "ホーム" +type: docs +cascade: + type: docs +description: "純粋な Go(CGO=0)で書かれた Marp 互換のスライドデッキ。Go テンプレートによる埋め込みとスライドの kind に対応しています。" +--- + +**`slides` は Marp のスライドデッキを、純粋な Go だけでプレゼンテーションに変換します。** +[Marp](https://marp.app) が読むのと同じ Markdown(Marpit のディレクティブ、スライド間の `---`、`![bg]` による背景、スピーカーノート、`default`・`gaia`・ +`uncover` の各テーマ)を読み込み、さらに Marp にはない機能を 2 つ追加します。 + +- スライドデッキ内の **[Go テンプレート]({{< relref "templates" >}})**。Markdown の解析前に評価されるため、スライドデッキが自分自身のスライドを生成できます。 +- **[スライドの kind]({{< relref "kinds" >}})**(スライドの種類)。スライドは見た目を毎回繰り返し書く代わりに、自分が何であるか + (`title`、`section`…)を宣言します。 + +{{< callout type="warning" >}} +**v0.9.0。** このページ群は +[go-slides/slides v0.9.0](https://github.com/go-slides/slides/releases/tag/v0.9.0) +について説明しており、`/docs/0.9/` で公開されています。 +{{< /callout >}} + +## 現在できること {#what-it-does-today} + +| | | +| --- | --- | +| `slides build`(デフォルト) | スライドデッキを 1 つの HTML ページとして出力します。各スライドはウィンドウに合わせて拡大縮小される SVG 枠付きの `<section>` で、テーマの CSS とキーボード操作のビューアーが付きます | +| `slides build --pdf`、`--images png`、`--image jpeg` | スライドデッキを PDF(スライドごとに 1 ページ、アウトライン付き)または PNG/JPEG 画像として出力します。ブラウザーは不要です。[go-webengine](https://github.com/go-webengine) がレンダリングし、[go-pdfkit](https://github.com/go-pdfkit) が PDF を書き出します。v0.4.0 以降はベクター形式で、テキストを選択できます:[ベクター PDF]({{< relref "cli#vector-pdf" >}}) | +| 数式とコード | `$…$` と `$$…$$` は go-tex/math で組版され、フェンスで囲んだコードは go-rouge でハイライトされます。HTML、画像、PDF のいずれでも同様です:[数式とコード]({{< relref "math-and-code" >}}) | +| 絵文字 | `:smile:` や 😄 は Twemoji の画像に変換されて埋め込まれるため、ネットワークを必要とせず、すべての出力で描画されます:[絵文字]({{< relref "emoji" >}})(v0.4.0 以降) | +| `slides build --pptx` | スライドデッキを、スピーカーノートとメタデータ付きの PowerPoint ファイルとして出力します。v0.4.0 では marp-cli と同じくスライドごとに 1 枚の画像でしたが、v0.5.0 以降は **編集可能** です。テキストボックス、図形、画像を使い、フォントも埋め込まれます([編集可能な PPTX と ODP]({{< relref "cli#editable-pptx-and-odp" >}})) | +| `slides build --odp` | 同じ内容を OpenDocument プレゼンテーション(`.odp`)として出力します | +| 見出しのフィット | `# <!-- fit --> Title` は、ページでもすべてのエクスポートでも、見出しを 1 行でスライドの幅いっぱいに表示します | +| `slides expand` | スライドデッキをプレーンな Marp Markdown として出力します。テンプレートは評価され、kind はそれが表すスポットディレクティブに置き換えられます | +| `slides notes` | スピーカーノートを出力します | +| `slides kinds`、`slides themes` | スライドデッキで指定できる名前を一覧表示します | +| `slides.yaml` | フォルダー内のスライドデッキで共有する kind、テーマ、データ、HTML ポリシー、言語を定義します | +| HCL | `---hcl` のフロントマター、`slides.hcl`、kind とデータファイルを、式と関数が使える HCL2 で記述できます:[HCL]({{< relref "hcl" >}}) | + +## 予定 {#planned} + +まだ実装されておらず、推測しなくて済むようにここに記載しているもの:PDF 内の数式のベクター化(現在はビットマップです)。 +marp-cli との違いは処理の経路です。marp-cli はヘッドレス Chrome を操作して画像や PDF をレンダリングしますが、 +go-slides ではそれを Go のライブラリで行います。 + +## ページ一覧 {#pages} + +- [はじめに]({{< relref "getting-started" >}}) +- [ギャラリー]({{< relref "gallery" >}}):Playground で開ける 12 個のサンプルスライドデッキ +- [Marp との互換性]({{< relref "marp" >}}) +- [テンプレート]({{< relref "templates" >}}) +- [kind]({{< relref "kinds" >}}) +- [HCL]({{< relref "hcl" >}}) +- [数式とコード]({{< relref "math-and-code" >}}) +- [絵文字]({{< relref "emoji" >}}) +- [コマンドライン]({{< relref "cli" >}}) diff --git a/content/ja/cli.md b/content/ja/cli.md new file mode 100644 index 0000000..3494e93 --- /dev/null +++ b/content/ja/cli.md @@ -0,0 +1,348 @@ +--- +title: "コマンドライン" +weight: 50 +description: "slides のすべてのコマンドとフラグ、および設定ファイル slides.yaml。" +tags: [cli] +--- + +{{< callout type="warning" >}} +このページは v0.9.0 について説明しています。 +{{< /callout >}} + +```text +slides [deck.md] [flags] +slides [command] [flags] deck.md +``` + +## コマンド {#commands} + +| コマンド | 動作 | +| --- | --- | +| `build` | プレゼンテーションを HTML、または PDF や画像として書き出します(後述)。**デフォルトのコマンド** です:`slides deck.md` は `slides build deck.md` と同じです | +| `expand` | スライドデッキをプレーンな Marp Markdown として出力します。テンプレートは展開され、kind(スライドの種類)はそれが表すディレクティブに置き換えられます | +| `notes` | スピーカーノートを出力します | +| `kinds` | スライドデッキで使える kind と、それぞれが設定する内容を一覧表示します。`--json`(v0.6.0)を指定すると `[{"name", "extends", "directives": [{"key", "value"}]}]` の形式で書き出し、`extends` は解決済みになります | +| `themes` | テーマを一覧表示します | +| `version` | バージョンを出力します(`slides --version` でも同じです) | +| `completion` | シェル補完スクリプトを出力します:`bash`、`zsh`、`fish`、`powershell` | + +`slides <command> --help` で、各コマンドのフラグを表示できます。フラグはスライドデッキの前にも +後にも置けます:`slides deck.md -o out.html` と `slides -o out.html +deck.md` は同じです。長い形式のフラグはダッシュ 2 つ(`--html`)で指定します。短い形式は `-o` +だけです。`build` は、`-o` で別の指定をしない限り、`deck.md` と同じ場所に `deck.html` を書き出します。 + +## フラグ {#flags} + +| フラグ | コマンド | 動作 | +| --- | --- | --- | +| `-o`, `--output file` | `build`, `expand`, `notes` | 出力ファイル。`-` で標準出力 | +| `-I`, `--input-dir dir` | `build` | `dir` 以下の Markdown ファイルを再帰的に変換します(v0.6.0)。後述 | +| `-P`, `--parallel n` | `build` | 同時にビルドするスライドデッキの数(v0.6.0)。デフォルトは 5 で、最大でコア数までです。1 を指定すると 1 つずつ順にビルドします | +| `-w`, `--watch` | `build` | スライドデッキ、テーマ、kind やデータのファイル、または設定が変更されると再ビルドし、開いているページを再読み込みします(v0.6.0)。後述 | +| `-s`, `--server dir` | `build` | ディレクトリを配信し、リクエストに応じてその中のスライドデッキを変換します(v0.6.0)。後述 | +| `-p`, `--preview` | `build` | 結果をデフォルトのブラウザーで開きます(v0.6.0)。`--watch`(暗黙的に有効)では HTML ページを、`--server` ではサーバーを開きます | +| `--host addr` | `build` | `--server` が待ち受けるアドレス。デフォルトは `127.0.0.1`(v0.6.0) | +| `--template bespoke\|bare` | `build` | HTML ページのテンプレート(v0.6.0)。デフォルトの `bespoke` は発表用スクリプト、画面上のコントローラー、トランジション付き、`bare` はスライドを縦に並べたもの(`--bare`)です | +| `--bespoke.osc`, `--bespoke.progress`, `--bespoke.transition` | `build` | bespoke テンプレートのオプション。デフォルトは marp-cli と同じです(v0.6.0):コントローラーはオン、**プログレスバーはオフ**(以前は常に表示されていました)、トランジションはオン。`--bespoke.osc=false` のようにして個別にオフにします | +| `-c`, `--config-file file`, `--no-config-file` | `build` | marp-cli の設定ファイル(JSON または YAML、v0.6.0)。後述。`--config` は `slides` 独自の設定ファイルです | +| `--browser`, `--browser-path`, `--browser-protocol`, `--browser-timeout` | `build` | 受け付けますが、警告を出して無視します(v0.6.0)。ブラウザーは使わないためです | +| `--pptx-editable`, `--no-parallel` | `build` | marp-cli での `--pptx` と `-P 1` の書き方です(v0.6.0) | +| `--config file` | すべて | 設定ファイル。デフォルトでは、スライドデッキと同じ場所に `slides.yaml` があればそれを使います | +| `--no-config` | すべて | 設定ファイルを読み込みません | +| `--theme-set path` | すべて | テーマの CSS ファイル、またはそれを含むディレクトリ(複数回指定可) | +| `--kinds file` | すべて | kind を記述した YAML ファイル(複数回指定可) | +| `--data file` | すべて | テンプレートのデータを記述した YAML または JSON ファイル(複数回指定可) | +| `--set key=value` | すべて | テンプレートの値 1 つ(複数回指定可) | +| `--no-template` | すべて | Go テンプレートを展開しません | +| `--strict-yaml` | すべて | 素の Marpit と同じく、ディレクティブを厳密な YAML として解析します(Marp Core は緩く解析します) | +| `--no-emoji` | すべて | `:shortcodes:` と Unicode の絵文字を画像として描画せず、テキストのまま残します(v0.4.0 以降:[絵文字]({{< relref "emoji" >}})) | +| `--html` | `build` | 生の HTML をすべて許可します。デフォルトは Marp Core の許可リストです | +| `--lang code` | `build` | スライドデッキで指定がない場合の、ページの `lang` | +| `--bare` | `build` | 発表用スクリプトなしで、スライドを縦に並べます | +| `--pdf` | `build` | スライドごとに 1 ページの、アウトライン付き PDF を書き出します。テキストと図形はベクターです(`-o file.pdf` でも選択されます) | +| `--pdf-raster` | `build` | v0.3.0 と同じく、各ページをスライドの画像 1 枚にした PDF を書き出します | +| `--notes` | `build` | スピーカーノートをテキストファイルとして書き出します。marp-cli と同じく、スライド間は `---` で区切られます(v0.6.0。`-o file.txt` でも選択されます)。`notes` コマンドは別のレイアウトで出力します | +| `--pdf-notes` | `build` | marp-cli と同じく、各スライドのスピーカーノートを、その PDF ページにノート注釈として追加します(v0.6.0)。出力形式がほかに指定されていなければ PDF を書き出します | +| `--pdf-outlines`, `--pdf-outlines.pages`, `--pdf-outlines.headings` | `build` | PDF のしおり(v0.6.0)。スライドごとの「Page N」と、その下に入れ子になった見出しです。marp-cli と異なり、ここではデフォルトでオンです。`=false` で個別にオフにします。後述 | +| `--images png\|jpeg` | `build` | スライドごとに 1 枚の画像を書き出します:`deck.001.png`、… | +| `--image png\|jpeg` | `build` | 最初のスライドを画像として書き出します(`-o file.png` または `-o file.jpg` でも選択されます) | +| `--pptx` | `build` | スピーカーノート付きの PowerPoint ファイルを書き出します(`-o file.pptx` でも選択されます)。v0.5.0 以降は編集可能で、v0.4.0 ではスライドごとに 1 枚の画像でした | +| `--pptx-raster` | `build` | marp-cli と同じく、スライド全面の画像 1 枚ずつで構成した PowerPoint ファイルを書き出します(v0.5.0 以降)。`--image-scale` のデフォルトは 2 になります | +| `--odp` | `build` | スピーカーノート付きの OpenDocument プレゼンテーションを書き出します(`-o file.odp` でも選択されます)。v0.5.0 以降は編集可能です | +| `--odp-raster` | `build` | スライドごとに 1 枚の画像で構成した OpenDocument プレゼンテーションを書き出します(v0.5.0 以降) | +| `--jpeg-quality n` | `build` | JPEG の品質。1〜100(デフォルトは 85) | +| `--image-scale n` | `build` | 画像と PDF での、CSS ピクセルあたりのデバイスピクセル数(デフォルトは 1)。2 を指定すると 16:9 で 2560×1440 になり、テキストとベクターは拡大されるのではなく、その密度で描画されます | +| `--allow-local-files` | `build` | 画像と PDF がローカルファイルを読み込むことを許可します。ただし、スライドデッキのディレクトリ以下にあるファイルだけです。marp-cli と同じく、デフォルトでは拒否されます | +| `--theme name\|file.css` | `build` | スライドデッキのテーマを上書きします(v0.6.0)。テーマ名か CSS ファイルを指定します。CSS ファイルは、宣言している `@theme` にかかわらずそのテーマになります。ディレクトリは拒否されるので、`--theme-set` を使ってください | +| `--title`, `--description`, `--author`, `--keywords`, `--url`, `--og-image` | `build` | ページのメタデータ(v0.6.0)。スライドデッキの `title`、`description`、`author`、`keywords`、`url`、`image` ディレクティブより優先されます。後述 | + +## 複数のスライドデッキとディレクトリ {#several-decks-and-a-directory} + +v0.6.0 では、`build` に複数のスライドデッキ、ディレクトリ(その下のスライドデッキを +再帰的に検索)、またはパターンを渡せます。最大 `--parallel` 個を同時にビルドします。 +`-` は標準入力です。marp-cli と異なり、`-` を指定しない限りパイプは読み込みません。 + +パターンは marp-cli の glob と同じように解釈されます(`**` と `{a,b}` は v0.7.0 から。 +シェルに展開させず `slides` に渡すよう、クォートしてください)。名前の中では `*`、`?`、`[a-z]`、 +`{md,txt}` が使え、`**` は任意の数のディレクトリにマッチします。そのため +`'docs/**/*.md'` でツリー内のスライドデッキを指定できます。ワイルドカードはドットで始まる名前には +マッチせず、`node_modules` の中は検索されず、大文字と小文字は区別されます +(`*.md` では `UPPER.MD` は見つかりません)。パターンにマッチしたものは、拡張子にかかわらずすべて変換されます。 +何にもマッチしないパターンはエラーになります。 + +- **`-I dir`** は、marp-cli と同じく `dir` 以下の Markdown ファイル(小文字の `.md`、`.mdown`、`.markdown`、 + `.markdn`)を検索します。`node_modules` とドットで始まるディレクトリの中は検索しません。 + `-o out` を指定すると、各スライドデッキは `dir` 以下のパスを `out` 以下に写した場所に書き出されます: + `dir/sub/b.md` は `out/sub/b.html` になります(フラグに応じて `b.pdf`、`b.001.png`、`b.txt`、…)。 + `-o` を指定しない場合、出力は各スライドデッキの隣に置かれます。`-I` と一緒にスライドデッキを + 指定するとエラーになり、複数のスライドデッキに対する `-o file` もエラーです(`-o -` は使えます)。 +- **`-P n`** は `n` 個のスライドデッキを同時にビルドします。1 つのスライドデッキのレンダリングでもすでに + 複数のコアを使うため、`n` の上限はコア数です。`-o -` の場合は 1 つずつ順に + ビルドします。各スライドデッキのメッセージはまとめて出力され、失敗したスライドデッキがあってもほかのビルドは + 止まりません:終了ステータスは 1 になり、メッセージに失敗の一覧が表示されます。 + +## トランジションと発表用ページ {#transitions-and-the-presenting-page} + +v0.6.0 では、marp-cli の `transition` ディレクティブが使えます:`<!-- transition: +fade 1s -->` のように名前と省略可能な継続時間を書くと、そのスライドと以降のスライドに適用され、 +`<!-- _transition: cover -->` ならそのスライドだけに適用されます。組み込みの名前は marp-cli と同じです: +`clockwise`、`counterclockwise`、`cover`、`coverflow`、`cube`、`cylinder`、 +`diamond`、`drop`、`explode`、`fade`、`fade-out`、`fall`、`flip`、`glow`、 +`implode`、`in-out`、`iris-in`、`iris-out`、`melt`、`overlap`、`pivot`、`pull`、 +`push`、`reveal`、`rotate`、`slide`、`star`、`swap`、`swipe`、`swoosh`、`wipe`、 +`wiper`、`zoom`。`none` でオフになります。ページは、閲覧者がトランジションの設定されたスライドから +次へ進むときと、そのスライドに戻ってくるときに、ブラウザーの **View Transition API** を使って再生します。 +この API がないブラウザーでは従来どおりにスライドが切り替わり、 +視差効果を減らす設定(reduced motion)を求める閲覧者には単純なフェードが使われます。テーマは独自の +キーフレームを `marp-transition-NAME`(または `marp-outgoing-` と +`marp-incoming-`、および `-backward`)という名前で提供でき、それらは組み込みのものより優先して使われます。 + +ページには **画面上のコントローラー**(前へ、ページ番号、次へ、全画面表示)があります。 +これはポインターが動いたりキーが押されたりすると表示され、少しすると隠れます +(`--bespoke.osc=false` で削除できます)。また `--bespoke.progress` を指定すると、プログレス +バーが表示されます。**プログレスバーは現在デフォルトでオフです。** これは marp-cli と同じで、 +以前は常に表示されていました。 + +### 発表者ビュー {#presenter-view} + +v0.7.0 では、ページに marp-cli の発表者ビューがあります。`p` キーか、コントローラーの最後の +ボタンを押すと、現在のスライド、その次のステップ、スピーカーノート、ページ番号、時刻、タイマーを +表示するウィンドウが開きます(タイマーをクリックするとリセットされ、`+` と `-` でノートの文字サイズを変更でき、 +スライドとノートの間のバーはドラッグできます)。2 つのウィンドウは双方向に連動します: +どちらのウィンドウでも、矢印キー、次のステップのクリック、「前へ」/「次へ」のクリックで、 +もう一方もフラグメント単位で移動します。 + +動作は marp-cli と同じで、その 4.5.1 のソースから読み取ったものです:ウィンドウは同じファイルの +`?view=presenter&sync=KEY` で、次のステップは同じページを +`?view=next` としてフレームに表示したものです。ウィンドウ間の位置は +`localStorage`(`bespoke-marp-sync-KEY`)で共有されます。そのため、サーバーもネットワークも +不要ですが、`localStorage` がないブラウザーでは動作しません(ボタンにその旨が表示されます)。 +ノートはテキストとして扱われ、`&`、`<`、`>` はエスケープされるので、ノートがマークアップになることはありません。 +ノートは発表者ビューが取り出せるよう、非表示の状態でページに含まれています。これにより、スクリーンリーダーが +聴衆向けに読み上げる内容にはノートが含まれません。 +`--bare` のページにはこの機能はありません。発表者ビューによってページのサイズは約 10 KB 増えます。 + +### 一覧表示 {#overview-view} + +v0.8.0 では、`o` キー(または `Escape`。marp-cli はどちらでも切り替わります)、 +コントローラーのグリッドボタン、または発表者ビューのページカウンターを押すと、すべてのスライドが +ページの上にグリッド状に全体表示されます。矢印キーでスライドを選び(上下キーは行単位で移動)、 +`Home` と `End` で先頭と末尾に移動でき、ほかのウィンドウも連動します。 +`Enter`、`Space`、またはクリックでスライドを選択すると、ページ上の一覧表示の場合は一覧表示が閉じます。 +発表者ビュー上では、選択しても一覧表示は開いたままです(発表者は表示するスライドを選んだ後も、 +一覧を見続けられます)。「Close overview」ボタン、`o`、 +`Escape` で閉じます。これは marp-cli と同じ仕組みです:同じページを +`?view=overview&sync=KEY` でフレームに表示し、位置を `localStorage` で共有するので、 +ネットワークは不要です。グリッド内のスライドはフォーカス可能で「Slide N」というラベルが付き、 +閉じた一覧表示は inert になるので、キーボードやスクリーンリーダーの利用者が誤ってそこに入り込むことは +ありません。発表者ビューと一覧表示を合わせて、ページのサイズは約 15 KB 増えます。 + +### 設定ファイル {#configuration-files} + +marp-cli は作業ディレクトリにあるファイルからオプションを読み込みます。`slides` も、そのファイルが +データであれば同じように読み込みます:`.marprc`(YAML。JSON も YAML として読めます)、 +`.marprc.json`、`.marprc.yaml`、`.marprc.yml`、`.config/marprc*`、または +`package.json` の `marp` キーです。`--config-file`(`-c`)では任意の JSON または YAML +ファイル(たとえば `marp.config.json`)を指定できます。marp-cli の検索と同じく、検索するのは +作業ディレクトリだけで、親ディレクトリは検索しません。JavaScript や TypeScript のファイル +(`marp.config.js` ...)はプログラムなので `slides` は実行しません。その旨を表示して +処理を続けます。使えるオプションは、marp-cli のドキュメントにあるもののうち +go-slides で意味を持つものです:`title`、`description`、`author`、`keywords`、`url`、`ogImage`、`lang`、 +`theme`、`themeSet`、`html`、`allowLocalFiles`、`template`、`bespoke`、 +`inputDir`、`output`、`parallel`、`pdf`、`pptx`、`notes`、`pdfNotes`、 +`pdfOutlines`、`images`、`image`、`imageScale`、`jpegQuality`、`watch`、 +`server`、`preview`。パスはそのファイルからの相対パスです。コマンドラインの指定が優先されます。 +`engine`、`options`、`browser*` は Marp のエンジンとブラウザーを必要とするため、 +`--browser*` フラグと同じく警告を出して無視されます。そのため、marp-cli 向けに書いたスクリプトは +そのまま動作します(`slides.yaml` と `slides.hcl`、およびそこに書く kind と +テンプレートのデータは別物で、従来どおりに読み込まれます)。 + +## 監視、配信、プレビュー {#watch-serve-and-preview} + +v0.6.0 で導入され、marp-cli のルートとライブリロードのプロトコルに対応しています(その +4.5.1 のソースから読み取り、実行中の `marp -s` と比較しました)。 + +**`slides --server dir`**(`-s`)は、ディレクトリを `http://127.0.0.1:8080/` で配信します +(ポートは環境変数 `PORT` で変更できます。すでに使用中のポートを指定すると、 +その旨を示すエラーになります)。 + +| リクエスト | 応答 | +| --- | --- | +| `/deck.md` | リクエストに応じて変換された、スライドデッキの HTML | +| `/deck.md?pdf`、`?pptx`、`?png`、`?jpg`(または `?jpeg`)、`?notes`(または `?txt`) | クエリーで指定された形式に変換されたスライドデッキ。PPTX は添付ファイルとして返されます | +| ディレクトリ | その `index.md` または `PITCHME.md` をページとして返します。どちらもなければ、各スライドデッキの PDF と PPTX へのリンクを含む一覧ページを返します | +| そのほかのファイル | そのファイル | +| 変換できないスライドデッキ | エラー内容付きの 503 | + +ページは `/.__marp-cli-watch-notifier__/<id>` への WebSocket を開きます。id は +スライドデッキのパスの SHA-256 です。サーバーは `ready` を送り、スライドデッキまたはディレクトリ以下の +何かが変更されると `reload` を送ります。末尾のスラッシュなしでリクエストされたディレクトリは、 +スラッシュ付きの URL にリダイレクトされます(marp-cli はスラッシュなしでも一覧を返すため、 +ページの相対リンクが壊れます)。 + +**`--watch`**(`-w`)はスライドデッキをビルドした後、変更されたスライドデッキを再ビルドします。 +`--theme-set` のファイル、kind やデータのファイル、または設定が変更された場合は、すべてを再ビルドします。 +書き出されたページはループバック上のソケット(ポート +37717、または次の空きポート)を開き、自分のスライドデッキが再ビルドされると再読み込みされます。 +ビルドに失敗したスライドデッキはエラーが報告され、引き続き監視されます。**`--preview`**(`-p`)は、 +デフォルトのブラウザーでサーバー、または直前にビルドした HTML ページを開きます。`slides` には +操作するブラウザーがないため、独自のウィンドウは持ちません。 + +サーバーはマシンへの入り口になるので、すべてのインターフェースで待ち受けて何もチェックしない +marp-cli のサーバーよりも厳しくしています。 + +- **127.0.0.1** で待ち受けます。`--host` で変更できますが警告が出ます。また、ワイルドカードアドレスでは + `Host` ヘッダーをチェックできなくなります。 +- **`Host`** ヘッダーは、ループバックの名前(`localhost`、`127.0.0.1`、 + `[::1]`、`*.localhost`)か、待ち受けているホストでなければなりません。これにより DNS リバインディングを防ぎます。 +- WebSocket の **`Origin`** は、サーバー自身のものでなければなりません。監視モードのソケットは、 + ファイルから開いたページの `null` オリジンも受け付けます。ほかのサイトからのリクエストのうち、 + ナビゲーション以外のもの(スクリプト、画像、`fetch`)は拒否されます。 +- パスは正規化され、シンボリックリンクは解決され、その結果がディレクトリ内になければなりません: + `..` やディレクトリ外へのリンクは拒否され、外部にあるスライドデッキの `?pdf` も拒否されます。 + ドットで始まる名前(`.git`、`.env`)は配信されません。 +- 受け付けるのは `GET` と `HEAD` だけです。同時に行う変換は少数に限られ、監視するディレクトリは + 最大 4096 個で、`node_modules` の中やドットで始まるディレクトリ以下は監視しません。 + +## ページのメタデータ {#page-metadata} + +marp-cli と同じく、コマンドラインの指定がスライドデッキより優先されます。marp-cli では、フラグは +最後のディレクティブコメントとしてスライドデッキに追加されます。以下のルールは marp-cli +4.5.1 から読み取り、その出力と照らし合わせるテストで固定しています(v0.6.0)。 + +- `title` を指定していないスライドデッキでは、最も上位のレベルの最初の見出しのテキストが、 + 書いたとおりに使われます(`# **Bold** title` なら `**Bold** title`)。見出しがなければ + `<title>` はありません。 +- `keywords` はカンマで分割され、前後の空白が除かれ、重複が取り除かれます。フロントマターのリストは + そのまま使われます。marp-cli と同じく、ページに `keywords` タグが付くのはキーワードが 2 つ以上の + 場合で、1 つだけのときは付きません。 +- 無効な `url`(絶対 URL でないもの)は警告を出して無視され、前の値が保持されます。有効な + ものは `<link rel="canonical">` と `og:url` も書き出します。 +- `author` と `description` には、対応する Open Graph のタグも付きます。`og:image:alt` + と大きな Twitter カードには、タイトルと画像の両方が必要です。 + +## 画像、PDF、PPTX、ODP {#images-pdf-pptx-and-odp} + +marp-cli と同じく、`-o` に `.pdf`、`.pptx`、`.odp`、`.png`、`.jpg` の拡張子を付けると、 +それによって形式が選ばれます。出力ディレクトリは自動的に作成されます。PDF はスライドごとに +スライドと同じサイズのページが 1 枚あり、スライドとその見出しのアウトラインが付きます。 + +### PDF のノートとアウトライン {#pdf-notes-and-outline} + +v0.6.0 では、PDF は marp-cli の 2 つのオプションに対応しています。同じスライドデッキについて +marp-cli 4.5.1 が書き出す PDF と比較しています。 + +- `--pdf-notes` は、各スライドのスピーカーノートを、そのページにノート注釈として追加します。 + 左下に黄色いノートアイコンが表示され、その内容はノート(複数のコメントは空行で連結)、 + タイトルはスライドデッキの `author` です。marp-cli と同じく、`--pdf-notes` だけを指定すると + PDF を書き出します。 +- アウトラインには、スライドごとの太字のしおり「Page N」と、その下にレベル別に入れ子になった + スライドの見出しが含まれます(`--pdf-outlines.pages` と + `--pdf-outlines.headings` でそれぞれを選択します)。**アウトラインは、ここではデフォルトで + オン、marp-cli ではオフです。** `slides` は以前から常にアウトラインを書き出してきました。数百 + バイトしか増えず、長いスライドデッキでも移動しやすくなるので、このデフォルトを維持しています。 + `--pdf-outlines=false` または marp-cli の `--no-pdf-outlines` でオフにでき、 + `.pages` や `.headings` に `=false` を付けるとその部分だけを省きます。見出しのしおりは + そのスライドに移動します。marp-cli ではさらに見出しの位置までスクロールします。 + +レンダリングは [go-webengine](https://github.com/go-webengine/engine) が行い、 +PDF は [go-pdfkit](https://github.com/go-pdfkit/pdfkit) が書き出します。 +組み込みテーマが使うフォント(Lato、Roboto Mono)は +[go-opentype/fonts](https://github.com/go-opentype/fonts) から取得して埋め込まれるので、 +レンダリング中にネットワークにアクセスすることはありません。Chrome は使いません。 + +`--image-scale 2` は、CSS ピクセルあたり 2 デバイスピクセルでレンダリングします。ローカルの画像は +`--allow-local-files` を指定しない限り拒否され、指定した場合でも、スライドデッキのディレクトリ以下にある +ファイルしか読み込みません:外部から来たスライドデッキが `~/.ssh` にアクセスすることはできません。 +複数の背景や分割背景(`![bg left:40%]`)は、Marp のレイヤーと同じようにレンダリングされます。 + +`--pptx` は、[go-ooxml](https://github.com/go-ooxml) の +`WritePresentation` を使って、スピーカーノートとスライドデッキのメタデータ付きでスライドデッキを書き出します。 +v0.5.0 以降、スライドは編集可能です(後述)。`--pptx-raster` は、marp-cli と同じくスライドごとに +スライド全面の画像を 1 枚書き出します。倍率のデフォルトは 2 で、 +`--image-scale` で上書きできます。結果は LibreOffice で開けます。テストしたのは +LibreOffice だけで、PowerPoint と Keynote ではテストしていません。 + +`--odp` は、go-odf の +`WritePresentation` を使って、スピーカーノート付きの OpenDocument プレゼンテーションを書き出します。 +`--odp-raster` はスライドごとに 1 枚の画像を書き出します。 + +### 編集可能な PPTX と ODP {#editable-pptx-and-odp} + +v0.5.0 以降、PPTX や ODP のスライドは **編集可能** です。marp-cli の +PPTX はスライドごとに 1 枚の画像で、実験的な `--pptx-editable` は +PDF を LibreOffice で変換するものです。go-slides では、各スライドを PNG や PDF のエクスポートで +使うのと同じレイアウトから描画します。 + +- テキストの各行は、レイアウトされた位置に置かれたテキストボックスで、スライドのレイアウトに使われた + フォントで表示されます。フォントは **ファイルに埋め込まれる** ので、Lato や Inter がないマシンでも + 同じ見た目になります(LibreOffice 25.8 で確認)。太字、斜体、色、文字間隔、クリック可能なリンクも + 保持されます。 +- 背景、枠線、角丸のボックスは図形になります。グラデーションは、PPTX ではグラデーション塗りつぶし、 + ODP では画像になります(ODF のグラデーションは 2 色までのため)。 +- 画像は、何枚のスライドに表示されていても 1 回だけ格納されます。 +- スライドで表現できないもの(`opacity` の下にある要素、`filter`、 + ぼかしのある影、数式)は、その要素の画像になり、テキストはその上に + 保持されます。 + +LibreOffice でレンダリングし直すと、スライドは PNG エクスポートのピクセルとの差が 0.04〜2 % に収まります。 +`--pptx-raster` と `--odp-raster` では、スライドごとに 1 枚の画像のままです。 + +### ベクター PDF {#vector-pdf} + +v0.4.0 以降、PDF はベクターで描画されます:go-webengine は、レイアウトされた +スライドをピクセルではなく PDF に描画します。テキストはテキストのままで、スライドのレイアウトに使われた +フォントが `/ToUnicode` マップ付きでサブセット埋め込みされるため、選択、検索、コピーができます。 +背景、枠線、グラデーションはパスとシェーディングになり、多くのスライドに表示される画像は 1 回だけ +埋め込まれ、リンクはクリックできます。100 枚のベンチマーク用スライドデッキは 758 KiB で、 +marp-cli の 837 KiB、v0.3.0 の画像 PDF の 10 MiB と比べて小さく、0.3 s で書き出されます。 + +ここでベクターで表現できない要素(`opacity` < 1 の下にあるもの、`filter` +や回転があるもの、ぼかしのある `box-shadow`、不透明度がフェードするグラデーション)は、 +その要素だけをビットマップとして描画し、テキストは選択できるように、その上に透明なまま重ねます。 +数式は現在もビットマップです。`--pdf-raster` は、v0.3.0 と同じくページごとに 1 枚の画像を +書き出します。 + +## `slides.yaml` {#slidesyaml} + +スライドデッキと同じ場所にある設定ファイルはそのスライドデッキに適用されるので、フォルダー内の +スライドデッキで 1 つの設定ファイルを共有できます。 + +```yaml +kinds: kinds.yaml # a mapping of kinds, or file(s) holding one +themes: # theme CSS files or directories + - themes/ +data: # template data + company: Example Inc. +html: false # allow all raw HTML +lang: en +``` + +kind について:スライドデッキ自体の `kinds:` が、設定ファイルとコマンドラインの指定より優先されます。 +データについて:設定ファイルとコマンドラインの `data` が、スライドデッキのフロントマターより +優先されます。そのため、1 つのスライドデッキを異なるデータでビルドできます。 + +## 予定 {#planned} + +まだ実装されていないもの:PDF 内の数式のベクター化。実装されたら、ここにコマンドまたはフラグとして +記載します。 diff --git a/content/ja/emoji.md b/content/ja/emoji.md new file mode 100644 index 0000000..59994d3 --- /dev/null +++ b/content/ja/emoji.md @@ -0,0 +1,78 @@ +--- +title: "絵文字" +weight: 36 +description: "絵文字のショートコードと Unicode の絵文字は、ネットワークなしで Twemoji の画像になります。HTML ページ、画像、PDF、PowerPoint、OpenDocument のいずれでも同様です。" +tags: [emoji] +--- + +{{< callout type="warning" >}} +絵文字は v0.4.0 で追加されました。v0.3.0 では使えません。 +{{< /callout >}} + +絵文字はショートコードでも、文字そのものでも書けます。 + +```markdown +# Launch :rocket: 🎉 + +Shortcodes :smile: :+1: :heart_eyes: and Unicode 😄 👍🏽 👩‍💻 🇫🇷 1️⃣. +``` + +いずれも Marp Core が描画するのと同じ [Twemoji](https://github.com/jdecked/twemoji) の画像になり、 +Marp Core と同じマークアップで出力されます。 + +```html +<img class="emoji" draggable="false" alt="😄" src="data:image/svg+xml;base64,…" data-marp-twemoji=""/> +``` + +## ネットワーク不要 {#no-network} + +Marp Core の `src` は CDN 上の URL なので、オフラインで表示したり、印刷したり、 +ネットワークに接続していないマシンで PDF に変換したりすると、画像が壊れて表示されます。 +go-slides は SVG そのものを `data:` URI に埋め込みます。画像データ(Twemoji 17.0.3、Emoji 17.0 の 4,009 個)は、 +純粋な Go のモジュール [go-icons/twemoji](https://github.com/go-icons/twemoji) からプログラムに組み込まれています。 +圧縮時のサイズは 2 MB で、スライドデッキに絵文字が含まれる場合にだけ読み込まれます。 + +画像は HTML ページでも、`--images`/`--image`、`--pdf`、 +`--pptx`、`--odp` でも同じように描画されます。SVG のラスタライズは go-webengine が行います。4,009 個の +画像はすべて、その SVG ラスタライザーで解析・描画でき、テストでそれを確認しています。 +v0.5.0 以降、このラスタライザーは [go-gfx/oksvg](https://github.com/go-gfx/oksvg) +v0.2.0 です。`clipPath`、`fill-rule: evenodd`、円弧コマンドが Chrome と同じように描画されるようになり、 +その結果 Twemoji の 1 個とそのほか 211 個の描画が変わり、SVG のロゴも崩れなくなりました。 + +絵文字をテキストに合わせたサイズにするルール(`img[data-marp-twemoji]`、1 `em` 四方、 +ベースラインに揃える)は、Marp Core と同様にテーマへ追加されます。 +そのため、独自のテーマにも適用されます。 + +## 変換されるもの {#what-is-converted} + +ルールは Marp Core と同じです。あらゆるケースを含むスライドデッキについて、 +Marp Core 4.4.0 が出力する HTML と比較するテストがあります。 + +| | | +| --- | --- | +| `:smile:`、`:+1:`、`:heart_eyes:` | markdown-it-emoji の 1,903 個の名前。本文、見出し、リンク、表、`header`、`footer` で変換されます。コード、URL、画像の代替テキストでは変換されません | +| `😄`、`👩‍💻`、`🇫🇷`、`1️⃣`、`👍🏽` | Unicode の絵文字。本文に加えて **コード内でも** 変換されます。Marp Core はレンダリング後のコードの HTML を書き換えるためです | +| `✔`、`©`、`™`、`▶` | テキストのままです。デフォルトの表示がテキストである文字は、U+FE0F が付いた場合(`✔️`)にだけ絵文字になります。`❤` は例外です | +| `☺︎` | テキストのままです。絵文字の後の U+FE0E は、通常のグリフでの表示を求めます | +| `12:30:45`、`:not_a_name:` | 書いたとおりに残ります | + +見出しの `id` とアウトラインでは、ショートコードの名前ではなく絵文字が使われます。 +`# Launch :rocket:` の id は、Marp と同じく `launch-%F0%9F%9A%80` になります。 + +go-slides が Marp Core と異なる点では、go-slides のほうが多くを変換します。`✌️ ☝️ ✍️ 🏋️` など、 +Marp Core の emoji-regex がテキストのまま残す、デフォルト表示がテキストの文字をいくつか変換するほか、 +Twemoji は描画できるものの Unicode には登録されていない少数のシーケンスも変換します。 + +## 無効にする {#turning-it-off} + +`--no-emoji`(コードでは `slides.Config{NoEmoji: true}`)を指定すると、ショートコードと +Unicode の絵文字は書いたとおりのテキストのまま残ります。これは Marp Core の +`emoji: false` に相当します。 + +## クレジット表記 {#attribution} + +画像は Twemoji(Copyright 2020 Twitter, Inc and other contributors)で、 +[CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) の下でライセンスされており、 +[jdecked/twemoji](https://github.com/jdecked/twemoji) プロジェクトによって引き継がれています。 +これらを表示するスライドデッキでは、フッターや最後のスライドなどでクレジットを記載してください。 +ショートコードの対応表は markdown-it-emoji のもの(MIT)です。 diff --git a/content/ja/gallery.md b/content/ja/gallery.md new file mode 100644 index 0000000..b489afb --- /dev/null +++ b/content/ja/gallery.md @@ -0,0 +1,23 @@ +--- +title: "ギャラリー" +weight: 12 +description: "テーマ、kind、テンプレート、HCL、数式、画像、コード、絵文字、トランジション、そして完全な講演まで、それぞれ 1 つのことをうまく示す 12 個の小さなスライドデッキ。どれも Playground で開けます。" +tags: [gallery, examples] +--- + +go-slides の +[`examples/`](https://github.com/go-slides/slides/tree/main/examples) フォルダーにある +12 個の小さなスライドデッキで、それぞれが 1 つのことをうまく示しています。サムネイルは +各スライドデッキの最初のスライドで、このサイトのビルド時に go-slides 自身がレンダリングしています。CI はすべての +スライドデッキを HTML と PDF にビルドしているので、どれも壊れることはありません。 + +**Playground で開く** を押すと、サーバーに何も送信しないリンクで、スライドデッキがブラウザーに読み込まれます。 +`deck.md` 以外のファイル(kind のファイル、インクルードする部品、画像)を含むスライドデッキはリンクでは渡せません。 +その場合は **.zip** をダウンロードして Playground のファイルパネルからインポートするか、展開して +`slides build --allow-local-files deck.md` でビルドしてください。 + +画像は go-slides の作者によるオリジナルの SVG で、 +[CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/) で公開されており、 +それを使うスライドデッキにはその旨が記載されています。 + +{{< gallery >}} diff --git a/content/ja/getting-started.md b/content/ja/getting-started.md new file mode 100644 index 0000000..bac0f1f --- /dev/null +++ b/content/ja/getting-started.md @@ -0,0 +1,127 @@ +--- +title: "はじめに" +weight: 10 +description: "最初のスライドデッキを HTML にビルドし、Marp Markdown に展開し直します。" +tags: [start] +--- + +## インストール {#install} + +Go 1.27.2 以降が必要です。 + +```sh +go install github.com/go-slides/slides/cmd/slides@latest +``` + +`slides` は cgo を使わない単一の静的 Go バイナリです。ほかにインストールするものはなく、 +Node も Chrome も不要です。このページは v0.9.0 について説明しています。 + +## 最初のスライドデッキ {#a-first-deck} + +Marp のスライドデッキであれば、そのまま使えます。次の内容を `talk.md` として保存してください。 + +```markdown +--- +marp: true +theme: default +paginate: true +footer: "{{ .talk }}" +data: + talk: Pure Go slides + topics: [Why, How, Next] +--- + +<!-- _kind: title --> +# {{ .talk }} + +{{ range .topics }} +--- + +# {{ . }} +{{ end }} +``` + +フロントマターは Marp のものに `data:` を加えたものです。本文は Go テンプレートです。 +`range` はトピックごとに `---` で始まるスライドを 1 枚ずつ書き出し、最初の +スライドは組み込みの kind(スライドの種類)`title` です(ヘッダー、フッター、ページ番号なし)。 + +## ビルドする {#build-it} + +```sh +slides talk.md # writes talk.html beside the deck +slides -o slides.html talk.md # or somewhere else; -o - for standard output +``` + +コマンドを付けずに `slides` を実行すると `slides build` になります。フラグはスライドデッキの前にも後にも置けます。 +HTML をブラウザーで開き、キーボードでスライド間を移動します。 + +## 画像、PDF、PPTX、ODP にエクスポートする {#export-images-pdf-pptx-and-odp} + +```sh +slides talk.md --pdf # one page per slide, with an outline +slides talk.md --images png # talk.001.png, talk.002.png, … +slides talk.md --image jpeg # the first slide only +slides talk.md -o out/slides.pdf # the extension picks the format; out/ is created +slides talk.md --images jpeg --jpeg-quality 90 +slides talk.md --images png --image-scale 2 # 2560×1440 for 16:9 +slides talk.md --pdf --allow-local-files # images from under the deck's directory +slides talk.md --pptx # or -o talk.pptx, with notes +slides talk.md --odp # or -o talk.odp: the same, as OpenDocument +``` + +ブラウザーは一切使いません。go-webengine がスライドをレンダリングし、go-pdfkit が +PDF を書き出します。ローカルの画像は、`--allow-local-files` を指定しない限り読み込まれません。 +v0.5.0 以降、PPTX や ODP のスライドは編集可能です。marp-cli では画像になるところを、テキストボックス、図形、 +画像で構成し、フォントも埋め込みます。`--pptx-raster` +を指定すると画像のままにできます([編集可能な PPTX と ODP]({{< relref "cli#editable-pptx-and-odp" >}}))。v0.4.0 以降、PDF はベクター形式で、テキストを +選択できます(v0.3.0 ではページごとに 1 枚の画像を書き出していました)。詳しくは [コマンドライン]({{< relref "cli#vector-pdf" >}}) を参照してください。 + +## 展開する {#expand-it} + +```sh +slides expand talk.md +``` + +は、同じスライドデッキを **プレーンな Marp Markdown** として出力します(`main` の +`4ae5c49`、2026-10-18 時点の出力)。 + +```markdown +--- +marp: true +theme: default +paginate: true +footer: "Pure Go slides" +--- +<!-- +_class: "lead" +_footer: "" +_paginate: "false" +--> + + +# Pure Go slides + + +--- + +# Why + +--- + +# How + +--- + +# Next +``` + +`range` が生成した 4 枚のスライド、値が埋め込まれたフッター、そして +`title` kind はそれが表すスポットディレクティブに置き換えられています。marp-cli や VS +Code 拡張機能など、Marp を読めるものならこの先を引き継げるので、go-slides の +追加機能を使って書いたものが囲い込まれることはありません。 + +## 次のステップ {#next} + +- [Marp との互換性]({{< relref "marp" >}}):Marp と同じように読み込まれるもの。 +- [テンプレート]({{< relref "templates" >}})と[kind]({{< relref "kinds" >}}):2 つの追加機能。 +- [コマンドライン]({{< relref "cli" >}}):すべてのコマンドとフラグ。 diff --git a/content/ja/hcl.md b/content/ja/hcl.md new file mode 100644 index 0000000..b1c0e38 --- /dev/null +++ b/content/ja/hcl.md @@ -0,0 +1,151 @@ +--- +title: "HCL" +weight: 45 +description: "スライドデッキで YAML を使う場所なら、どこでも HCL2 が使えます。HCL のフロントマター、slides.hcl、kind とデータのファイルで、式と関数を利用できます。" +tags: [hcl, configuration] +--- + +{{< callout type="warning" >}} +HCL は v0.3.0 で追加されました([go-slides/slides#13](https://github.com/go-slides/slides/pull/13))。 +{{< /callout >}} + +`slides` は YAML を読むすべての場所で、 +[HCL2](https://github.com/hashicorp/hcl/blob/main/hclsyntax/spec.md) のネイティブ構文も読み込みます。 +HCL2 の式と関数を使うためです。 + +| 場所 | YAML | HCL | +| --- | --- | --- | +| フロントマター | `---` … `---` | `---hcl` … `---` | +| 設定 | `slides.yaml` | `slides.hcl`(こちらが先に検索されます) | +| kind のファイル | `--kinds kinds.yaml`、`kinds: kinds.yaml` | `*.hcl` という名前の任意のファイル | +| データファイル | `--data data.yaml` または `.json` | `*.hcl` という名前の任意のファイル | + +ディレクティブのコメント(`<!-- class: invert -->`)は Marp と同じく YAML のままで、 +スライドデッキの本文も [Go テンプレート]({{< relref "templates" >}}) のままです。 + +## HCL で書くフロントマター {#a-front-matter-in-hcl} + +```hcl +---hcl +theme = "gaia" +paginate = true +title = "Quarterly review" +data = { + company = "ACME" + quarter = 3 +} +header = format("%s — Q%d", data.company, data.quarter) +footer = "${upper(data.company)} · ${page()}/${total()}" +date = formatdate("D MMMM YYYY", now()) + +kind "chapter" { + extends = "section" + class = ["lead", "invert"] +} +--- + +# {{ .title }} +``` + +- **属性** は、同じ名前の YAML のキーに対応します。Marp のディレクティブ + (`theme`、`paginate`、`header`、`footer`、`class`、`backgroundColor`…)、 + メタデータ(`title`、`author`…)、`template`、`delims`、`kinds`(ファイルの + パス)、そしてオブジェクトである `data` です。 +- **`kind "<name>" { … }`** ブロックは [kind]({{< relref "kinds" >}})(スライドの種類)を定義します。 + 中にはローカルディレクティブと `extends` を書けます。 +- **値** は、YAML の場合と同じ文字列の形でディレクティブに渡されます。 + `true` は `"true"` になり、クラスのリストはリストのままです。そのため、どちらの構文で設定しても + ディレクティブは同じように動作します。`null` を指定すると、そのディレクティブは未設定のままになります。 +- **`data`** は型を保持します。テンプレート内では整数は `int`、 + それ以外の数値は `float64` になり、リストとオブジェクトはリストとマップのままです。そのため + `{{ add .quarter 1 }}` が動作します。 +- HCL の値は確定値です。Go テンプレートがその値に対してもう一度実行されることはありません。 + +## 変数 {#variables} + +`data` はテンプレートのデータです。フロントマター自体の `data` に、 +`slides.hcl` のもの、`--data` のファイル、`--set` の順に重ねられます。`{{ .company }}` が使える場所なら、 +どこでも `"${data.company}"` が使えます。 + +フロントマターの属性は `data.*` を参照できますが、**属性どうしは参照できません**。 +`footer = title` はエラーになります。スポットディレクティブ(`_class`…)は HCL の +識別子ではないので、コメントに書いてください。 + +## 関数 {#functions} + +| | | +| --- | --- | +| `page()`、`total()` | `{{ page }}` や `{{ total }}` と同じく、ページ番号と最終ページの番号です。フッターにページ番号を入れられます | +| `now()`、`timestamp()` | 現在時刻(RFC 3339、ローカル時刻と UTC)です。`formatdate` で書式化できます | +| `env(name)` | 環境変数です。未設定の場合は `""` になります | +| `file(path)` | 最大 1 MiB の UTF-8 テキストファイルです。パスはスライドデッキまたは設定ファイルからの相対パスです | +| 数値 | `abs ceil floor log max min parseint pow signum` | +| 文字列 | `chomp format formatlist join lower regex regexall regexreplace replace split strlen strrev substr title trim trimprefix trimspace trimsuffix upper` | +| コレクション | `chunklist coalesce coalescelist compact concat contains distinct element flatten keys length lookup merge range reverse slice sort values zipmap` | +| エンコーディング | `csvdecode jsondecode jsonencode` | +| 日付 | `formatdate timeadd` | + +最初の 4 行以外は、[go-cty](https://github.com/zclconf/go-cty) の +標準ライブラリです。ネットワークにアクセスする関数はありません。 + +## slides.hcl {#slideshcl} + +```hcl +kinds = "kinds.hcl" # or a list of files +themes = ["themes/acme.css"] +data = { company = "ACME" } # or file paths +html = false +lang = "en" + +kind "quote" { + extends = "blank" + footer = "${data.company}" +} +``` + +未知の属性は、黙って無視されるのではなくエラーになります。kind のファイルには +`kind` ブロックを書きます。それ以外の属性は無視されるので、`slides.hcl` を kind のファイルとしても +使えます。データファイルの属性は、そのままデータのキーになります。 + +```hcl +# data.hcl +company = "ACME" +year = 2026 +motto = trimspace(file("motto.txt")) +``` + +## エラー {#errors} + +エラーは HCL の診断メッセージで、スライドデッキのファイル内の位置が示されます。 +スライドデッキの 4 行目に `footer = data.compnay` と書くと、次のようになります。 + +``` +deck.md:4:14: Unsupported attribute: This object does not have an attribute named "compnay". +``` + +## Marp と `slides expand` {#marp-and-slides-expand} + +Marp も `---hcl` をフロントマターの開始として読みます(Marp のフロントマターの +ルールは、ダッシュ 3 つで始まる最初の行であれば何でも開始とみなすためです)。ただし、そのブロックは +YAML のマッピングではないので、内容は何も適用されません。スライドデッキは Marp でもレンダリングされますが、 +ディレクティブは反映されません。この挙動は、Marp Core 自身の出力と照らし合わせるテストで固定しています。 + +`slides expand` はフロントマターを YAML に書き戻すので、展開後のスライドデッキは +プレーンな Marp になり、`slides` が元のスライドデッキをレンダリングしたときと同じようにレンダリングされます。 + +```yaml +--- +theme: "gaia" +paginate: true +title: "Quarterly review" +header: "ACME — Q3" +footer: "ACME · 1/2" +date: "19 October 2026" +--- +``` + +## ライセンス {#licences} + +HCL のサポートでは、[hashicorp/hcl](https://github.com/hashicorp/hcl) +(MPL-2.0、ライブラリとして無改変で使用)と +[zclconf/go-cty](https://github.com/zclconf/go-cty)(MIT)をリンクしています。 diff --git a/content/ja/kinds.md b/content/ja/kinds.md new file mode 100644 index 0000000..1a4c8d0 --- /dev/null +++ b/content/ja/kinds.md @@ -0,0 +1,90 @@ +--- +title: "kind" +weight: 40 +description: "スライドは自分が何であるかを宣言します。kind はローカルディレクティブのプリセットに名前を付けたものです。" +tags: [kinds] +--- + +**kind**(スライドの種類)は、ローカルディレクティブ(ヘッダー、フッター、 +ページ番号、クラス、背景)のプリセットに名前を付けたものです。スライドは見た目を毎回繰り返し書く代わりに、 +自分が何であるか(`title`、`section`)を宣言します。こうすれば、スライドデッキの見た目を +1 か所で変更できます。 + +## kind を使う {#using-a-kind} + +```markdown +<!-- kind: section --> +# Part two + +--- + +<!-- _kind: blank --> +![bg](photo.jpg) +``` + +- `kind:` はローカルディレクティブと同じように、そのスライドと以降のスライドに適用されます。 +- `_kind:` はスポットディレクティブと同じように、そのスライドだけに適用されます。 +- kind を切り替えると、まず直前の kind が設定した内容が **クリア** されます。そのため、クラスや + フッターがある kind から次の kind に漏れ出すことはありません。 +- スライド自体に書いたディレクティブは、kind より **優先** されます。 + +## 組み込みの kind {#built-in} + +| kind | 設定内容 | +| --- | --- | +| `title` | `class: lead`、ページ番号なし、ヘッダーなし、フッターなし | +| `section` | `class: lead` | +| `content` | なし(スライドデッキ自体のデフォルト) | +| `blank` | ページ番号なし、ヘッダーなし、フッターなし | + +どれも再定義できます。 + +## kind を定義する {#defining-kinds} + +フロントマターに記述します。 + +```yaml +--- +marp: true +kinds: + chapter: + class: invert + backgroundColor: "#1e1b4b" + paginate: false + quote: + extends: blank + class: quote +--- +``` + +`extends` を使うと、別の kind のディレクティブを土台にして kind を定義できます。kind に書けるのは +ローカルディレクティブと `extends` です。 + +## kind を共有する {#sharing-kinds} + +チームで使う kind は、すべてのスライドデッキが参照する 1 つのファイルにまとめます。 + +```yaml +# kinds.yaml +chapter: + class: invert + paginate: false +``` + +```yaml +--- +marp: true +kinds: kinds.yaml # or a list of files +--- +``` + +または、コマンドラインで `--kinds kinds.yaml` を指定するか、`slides.yaml` に `kinds:` を書きます。 +適用順は、組み込みの kind、次にコマンドラインと設定ファイル、最後にスライドデッキ自体の `kinds:` です。 + +`slides kinds talk.md` は、スライドデッキで使える kind と、それぞれが設定する内容を一覧表示します。 + +## go-slides から離れる {#leaving-go-slides} + +`slides expand` は、すべての kind をそれが表すスポットディレクティブとして +スライドごとに書き出します。その結果はプレーンな Marp Markdown になり、どの Marp ツールでも +読み込めます。 diff --git a/content/ja/marp.md b/content/ja/marp.md new file mode 100644 index 0000000..1348523 --- /dev/null +++ b/content/ja/marp.md @@ -0,0 +1,80 @@ +--- +title: "Marp との互換性" +linkTitle: "Marp" +weight: 20 +description: "slides が読む Markdown は Marpit と Marp Core のものそのもので、その方言ではありません。" +tags: [marp] +--- + +[Marp](https://marp.app) 向けに書かれたスライドデッキは、そのまま go-slides のスライドデッキです。以下の +ルールは [Marpit](https://marpit.marp.app) と +[Marp Core](https://github.com/marp-team/marp-core) のものです。go-slides が +異なる点は、このページで明記します。 + +## スライド {#slides} + +水平線(Marpit が分割するのと同じく、1 行に単独で書いた `---`)で +新しいスライドが始まります。`headingDivider` ディレクティブで指定したレベルの見出しでも +新しいスライドが始まります。最初の 2 つの `---` の間にあるフロントマターには、グローバルディレクティブと +スライドデッキのデータを書きます。 + +## ディレクティブ {#directives} + +| 種類 | 書く場所 | 適用範囲 | +| --- | --- | --- | +| グローバル | フロントマター、または任意の場所の `<!-- theme: gaia -->` | スライドデッキ全体:`theme`、`style`、`headingDivider`、`lang`… | +| ローカル | `<!-- paginate: true -->` | そのスライドと以降のスライド | +| スポット | `<!-- _class: lead -->`(アンダースコア付き) | そのスライドのみ | + +ローカルディレクティブは Marpit のものです:`paginate`、`header`、`footer`、`class`、 +`backgroundColor`、`backgroundImage`、`backgroundPosition`、 +`backgroundRepeat`、`backgroundSize`、`color`。ディレクティブは Marp Core と同じく +緩い YAML として解析されます。`--strict-yaml` を指定すると、素の Marpit と同じように +解析されます。 + +## 画像 {#images} + +`![bg](image.jpg)` は画像をスライドの背景にします。Marp のキーワードが使えます: +`bg contain`、`bg cover`、`bg 50%`、分割背景の `bg left`/`bg right`、複数の `bg` 画像の横並び、 +画像フィルター(`blur`、`sepia`、`grayscale`…)。インライン画像のサイズ指定(`w:200px`、`h:`)も +Marp と同じように解釈されます。 + +## スライドの周辺要素 {#around-the-slide} + +- **ヘッダーとフッター**:Markdown で書く `header:` と `footer:` のディレクティブ。 +- **ページ番号**:`paginate: true`。 +- **スピーカーノート**:ディレクティブではない HTML コメントはノートになります。 + `slides notes` で出力できます。 +- **段階的に表示するリスト**:`*`(または `1)`)で書いたリストは、Marp と同じく + 項目が 1 つずつ表示されます。 +- **`<style scoped>`**:1 枚のスライドだけに適用される CSS。 +- **生の HTML**:デフォルトでは Marp Core の許可リストに従います。`--html` を指定するとすべて許可されます。 +- **絵文字**:`:shortcodes:` と Unicode の絵文字は、Marp Core と同じく Twemoji の画像になります。 + ただし、CDN から読み込むのではなく埋め込まれます。詳しくは + [絵文字]({{< relref "emoji" >}}) を参照してください。 + +## テーマ {#themes} + +`default`、`gaia`、`uncover` が組み込まれており、`theme:` で選択します。 +独自のテーマは、Marp の `/* @theme name */` コメントを含む CSS ファイルです。 +`--theme-set` で指定するか、`slides.yaml` の `themes:` に列挙します。 + +## go-slides が異なる点 {#where-go-slides-differs} + +- **テンプレート。** 本文中の `{{ }}` は Go テンプレートのアクションです( + [テンプレート]({{< relref "templates" >}}) を参照)。コードブロックの外で `{{` をテキストとして表示する + スライドデッキでは、`--no-template` を指定するか、Go テンプレートの方法でそのテキストをエスケープする必要があります。 + コードブロックとインラインコードはそのまま残ります。 +- **kind。** `kind:` と `_kind:` は Marp にはないディレクティブです( + [kind]({{< relref "kinds" >}})(スライドの種類)を参照)。`slides expand` はこれらを、それが表す + ディレクティブに置き換えるので、展開後のスライドデッキはプレーンな Marp になります。 +- **出力。** HTML、PNG、JPEG、PDF、PPTX、ODP を、ヘッドレスブラウザーではなく Go のライブラリで + 出力します(既知の制限については [コマンドライン]({{< relref "cli#images-pdf-pptx-and-odp" >}}) + を参照)。v0.4.0 では marp-cli と同じく PPTX の中身は画像でしたが、 + v0.5.0 以降のスライドは編集可能なテキストボックスと図形で構成されます。`--pptx-raster` を指定すると + 画像のままにできます。 +- **見出しのフィット。** `# <!-- fit --> Title` に対応しています。ページでもすべてのエクスポートでも、 + 見出しが 1 行でスライドの幅いっぱいに表示されます。 +- **数式とコード。** `$…$` と `$$…$$` は Marp Core の区切り文字のルールに従い、 + go-tex/math で組版されます。フェンスで囲んだコードは go-rouge によって + highlight.js のクラス名でハイライトされます([数式とコード]({{< relref "math-and-code" >}}) を参照)。 diff --git a/content/ja/math-and-code.md b/content/ja/math-and-code.md new file mode 100644 index 0000000..c183bde --- /dev/null +++ b/content/ja/math-and-code.md @@ -0,0 +1,138 @@ +--- +title: "数式とコード" +weight: 35 +description: "ドル記号で囲んだ数式は組版され、フェンスで囲んだコードはハイライトされます。HTML ページ、画像、PDF のいずれでも同様です。" +tags: [math, code] +--- + +{{< callout type="warning" >}} +数式とコードは v0.2.0 で追加されました。 +{{< /callout >}} + +## 数式 {#math} + +`$…$` はインライン数式、`$$…$$` はディスプレイ数式です。 + +```markdown +The famous one is $E=mc^2$, and this is a display formula: + +$$ +\int_0^\infty e^{-x^2}\,dx = \frac{\sqrt{\pi}}{2} +$$ +``` + +- 区切り文字のルールは、marp-core 4.4.0 から読み取った Marp Core のものです。空白が続く `$` + は数式を開始せず、数字が続くものは数式を閉じません。そのため、`$5 and $10` のような価格表記は + テキストのまま残ります。 +- [go-tex/math](https://github.com/go-tex/math) は各数式を、`em` 単位でサイズ指定された + インライン SVG に組版します。そのため、数式はテキストのサイズに追従し、テキストのベースラインに揃います。 + HTML ページ、画像、PDF のいずれでも同じ SVG が使われます。 +- 解析できない数式は書いたとおりに表示され、エラーはその `title` に保持されます。 + +## コード {#code} + +フェンスで囲んだコードブロックは [go-rouge](https://github.com/go-rouge/rouge) で色付けされます。 +go-rouge は、Marp のテーマがスタイルを定義している highlight.js のクラス名を出力します。 + +````markdown +```go +fmt.Println("hello") +``` +```` + +go-rouge が知らない言語は、Marp Core と同じくプレーンテキストとして出力されます。 + +### 行番号 {#line-numbers} + +v0.8.0 では、コードブロックに行番号を付けられます。Marp Core には行番号の機能がなく、 +フェンスの言語名の後ろに続く部分を無視します。そのため、オプションはそこに波かっこで書きます。 +オプションを使ったスライドデッキも、Marp ではオプションがない場合と同じようにレンダリングされます。 + +````markdown +```go {lineNumbers} +func main() { + fmt.Println("hello") +} +``` + +```go {lineNumbers=10} +// starts at 10 +``` +```` + +| 書き方 | 意味 | +| --- | --- | +| `{lineNumbers}` | 1 から番号を付けます(Slidev の `{lines:true}` と Pandoc の `{.numberLines}` も解釈されます) | +| `{lineNumbers=10}` | 10 から番号を付けます(`startLine=10`、Pandoc の `startFrom="10"` も可) | +| `{lineNumbers=false}` | スライドの設定にかかわらず、番号を付けません | + +あるスライドとそれ以降のスライドのすべてのコードブロックに番号を付けるには、ディレクティブ +`<!-- lineNumbers: true -->` を設定します(1 枚のスライドだけなら `_lineNumbers`)。kind(スライドの種類)を使えば、 +同じ系統のスライドにまとめて一度で設定できます。 + +```yaml +kinds: + listing: {lineNumbers: true} +``` + +各番号は、テーマのコードボックス内で行の先頭に置かれ、罫線で区切られます。 +吹き出しや絵文字などで行の高さが増えても、番号はその行のベースラインに揃ったままです。番号は、 +コードの文字色と背景色を混ぜた控えめな色で表示され、default・ +gaia・uncover の各テーマのライトとダークのいずれでも、コントラスト比は 4.5:1 以上です。ページ上では番号は **選択できず**、 +スクリーンリーダーにも読み上げられません。PDF では描画されますがテキストからは除外されるため、 +テキスト抽出ツールはコードだけを読み取ります。PowerPoint や +OpenDocument のファイルでは、各行の先頭にある編集可能なテキストになります。このオプションを使わない +スライドデッキは、以前とバイト単位で同じ出力になります。指定した行のハイライトにはまだ対応していません。 + +### コードの吹き出し {#code-callouts} + +v0.8.0 では、コードの行末に、ブロックの下にある注釈を参照する番号付きの丸印を付けられます。構文は +[Quarto のもの](https://quarto.org/docs/authoring/code-annotation.html) で、 +Asciidoctor のコメント形式でもあります。マーカーはそのコード自体の言語のコメントなので、 +コードは有効なままです。注釈は、ブロックの直後にある番号付きリストです。 + +````markdown +```go +func main() { // <1> + x := load() // <2> + fmt.Println(x) +} +``` + +1. The entry point. +2. Reads the input. +```` + +| 書き方 | 意味 | +| --- | --- | +| `// <1>`、`# <1>`、`-- <1>`、`; <1>`、`% <1>`、`' <1>` | マーカーだけを含む行コメント。コメントとマーカーの両方が丸印に置き換えられます | +| `<!-- <1> -->`、`/* <1> */` | 同上。囲み型のコメントを使う言語向けです | +| `// <1> <2>` | 1 行に 2 つの丸印 | +| `x = 1 # count <1>` | コメントのテキストは残り、その後ろに丸印が付きます | + +マーカーが丸印になるのは、ブロックの直後のリストが 1 から始まり、 +すべての番号に対応する項目がある場合だけです。そうでない場合、ブロックは書いたとおりに残ります。 +丸印は、default・gaia・uncover の各テーマのライトとダークのいずれでも、コードの文字色のリングで描画されます。 +リストの項目にも同じ丸印で番号が付きます。 + +丸印は **選択できません**。ページからコードをコピーすると、マーカーを除いたコードが得られます。 +PDF ではテキストから除外されたベクターとして描画され、 +PowerPoint や OpenDocument のファイルでは編集可能な図形になります。吹き出しは +行番号と組み合わせられます。Marp Core には吹き出しの機能がないため(marp-core 4.4.0)、Marp では +コメントと番号付きリストが書いたとおりに表示されます。吹き出しを使わないスライドデッキは、 +以前とバイト単位で同じ出力になります。 + +v0.9.0 では、HTML ページ上で丸印と注釈が互いに連動します。丸印にマウスを重ねるか、 +Tab キーでフォーカスすると対応する注釈が色付けされ、注釈の側でも同様に対応する丸印が色付けされます。 +これは Quarto の `code-annotations: hover` と同じ動作ですが、ここではスクリプトを使わず、 +スタイルシートだけで実現しています。そのため、どちらもスクリプトを制限している Playground のサンドボックス化された +プレビューでも、VS Code のプレビューでも同じように動作します。各丸印には注釈が説明として関連付けられているので、 +スクリーンリーダーは丸印に到達すると注釈を読み上げます。色付けのフェードインは、閲覧者が +視差効果を減らす設定(reduced motion)を求めていない場合にだけ行われます。画像、PDF、PowerPoint、OpenDocument の +ファイルは、バイト単位で変わりません。 + +## フォント {#fonts} + +Lato の Regular、Bold、Black と各イタリック、Roboto Mono の Regular、Bold と +各イタリックが埋め込まれています。そのため、gaia の見出しは本来の太さで表示され、イタリックのテキストも +イタリックで描画されます。 diff --git a/content/ja/performance.md b/content/ja/performance.md new file mode 100644 index 0000000..1938182 --- /dev/null +++ b/content/ja/performance.md @@ -0,0 +1,85 @@ +--- +title: "パフォーマンス" +weight: 60 +description: "go-slides がスライドデッキをどれだけ速く、どれだけのメモリでビルドするか。同じマシン上で marp-cli と比較して計測しました。" +tags: [performance] +--- + +{{< callout type="info" >}} +以下の PPTX の数値は v0.5.0、ベクター PDF の数値は v0.4.0 のものです。 +最初の表は v0.3.0(その前のリリース)のものです。v0.3.0 はスライドを並行してレンダリングし、 +フォントの解析を 1 回だけ行います([go-slides/slides#11](https://github.com/go-slides/slides/pull/11))。 +{{< /callout >}} + +go-slides と [marp-cli](https://github.com/marp-team/marp-cli) 4.5.1 を、 +同じスライドデッキに対してサブプロセスとして実行しました。マシンは Apple M4 Max(16 CPU)、 +1 分間のロードアベレージは 8 未満で、ウォームアップの後に交互に 5 ラウンド計測しています。 +時間は実時間の中央値、メモリはプロセスツリー全体のピークです +(marp-cli は Chrome を操作するため、Chrome のプロセスも含めています)。 + +| スライドデッキ | 出力 | go-slides | marp-cli | go-slides のメモリ | marp-cli のメモリ | +| --- | --- | ---: | ---: | ---: | ---: | +| 100 枚 | HTML | 36 ms | 315 ms | 47 MiB | 140 MiB | +| 100 枚 | PDF | 1.14 s | 1.88 s | 242 MiB | 1,509 MiB | +| 100 枚 | PNG(全スライド) | 0.97 s | 5.44 s | 225 MiB | 1,354 MiB | +| 100 枚 | PPTX | 3.39 s | 8.03 s | 530 MiB | 1,373 MiB | +| gaia、20 枚 | PDF | 283 ms | 1.57 s | 154 MiB | 1,315 MiB | +| 数式とコード、20 枚 | PNG(全スライド) | 250 ms | 2.32 s | 168 MiB | 1,286 MiB | + +## ベクター PDF {#vector-pdf} + +v0.4.0 では、PDF はベクターで描画されます( +[コマンドライン]({{< relref "cli#vector-pdf" >}}) を参照)。同じマシンと方法で、 +2026-10-19 に計測しました。 + +| スライドデッキ | go-slides | marp-cli | v0.3.0 | go-slides のサイズ | marp-cli のサイズ | v0.3.0 のサイズ | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 100 枚 | 306 ms | 1,763 ms | 1,147 ms | 758 KiB | 837 KiB | 10.0 MiB | +| gaia、20 枚 | 93 ms | 1,546 ms | 285 ms | 32 KiB | 66 KiB | 582 KiB | +| 数式とコード、20 枚 | 140 ms | 1,643 ms | 294 ms | 124 KiB | 335 KiB | 638 KiB | + +100 枚の PDF のメモリ使用量は 158 MiB で、marp-cli は 1,512 MiB です。CPU +時間は 0.9 s 対 2.7 s です。 + +100 枚の PDF についての v0.4.0 の数値は、2026-10-19 の 21:29 に、 +1 分間のロードアベレージ 7.7 の状態で 3 ラウンド計測したものです:go-slides は 306 ms、758 +KiB、158 MiB、marp-cli は 1,838 ms、837 KiB、1,512 MiB でした。 + +## 編集可能な PPTX {#editable-pptx} + +v0.5.0 以降、PPTX はテキストボックス、図形、画像で構成されます( +[コマンドライン]({{< relref "cli#editable-pptx-and-odp" >}}) を参照)。 +2026-10-19 の 22:51 に、1 分間のロードアベレージ 7.1 の状態で 3 ラウンド計測しました。 + +| スライドデッキ | go-slides | marp-cli | go-slides のサイズ | marp-cli のサイズ | go-slides のメモリ | marp-cli のメモリ | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 100 枚 | 348 ms | 7,795 ms | 760 KiB | 7.2 MiB | 204 MiB | 1,370 MiB | +| gaia、20 枚 | 112 ms | 4,608 ms | 893 KiB | 4.8 MiB | | | +| 数式とコード、20 枚 | 207 ms | 2,780 ms | 842 KiB | 1.5 MiB | | | + +100 枚のスライドデッキでは、v0.4.0 は 4.7 s、4.8 MiB、540 MiB でした。marp-cli の +PPTX の中身は画像です。編集可能なスライドデッキのサイズの大部分は、埋め込まれたフォントが占めています。 + +## 速い理由 {#why-it-is-fast} + +- **起動するブラウザーがない。** marp-cli は PDF、画像、 + PPTX へのエクスポートのたびに Chrome を起動します。go-slides は + [go-webengine](https://github.com/go-webengine/engine) を使って自身の + プロセス内でレンダリングします。 +- **スライドは並列にレンダリングされます。** デフォルトでは 4 枚ずつで、各画像は + 順番が来しだい書き出されます。そのため、長いスライドデッキが画像としてメモリに保持され続けることはありません。 +- **フォントの解析はプロセスごとに 1 回** だけで、スライドごとには行いません。 + +## 優位でない点 {#what-it-does-not-win} + +- **v0.3.0 での PDF のサイズ。** PDF の各スライドが画像だったため、100 枚のスライドデッキでは + 10 MiB 対 837 KiB でした。上記のベクター PDF は marp-cli のものより小さくなっています。 +- **大きなスライドデッキでの CPU 時間。** 4 つのワーカーは、実時間から見えるより多くの CPU を消費します。 + v0.3.0 の 100 枚の PDF では 3.2 s で、marp-cli の 2.7 s を上回りました + (ベクター PDF では 0.9 s)。 +- **marp-cli のメモリは上限値です。** Chrome のプロセスはページを共有しており、合計ではそれを + プロセスごとに重複して数えています。単一プロセスで最大のものは 220–320 MiB です。 + +計測方法、すべてのスライドデッキとモード、数値を再現するためのコマンドは +[BENCHMARKS.md](https://github.com/go-slides/slides/blob/main/BENCHMARKS.md) +にあります。 diff --git a/content/ja/playground.md b/content/ja/playground.md new file mode 100644 index 0000000..315a180 --- /dev/null +++ b/content/ja/playground.md @@ -0,0 +1,168 @@ +--- +title: "Playground" +weight: 15 +description: "ブラウザーでスライドデッキを書くと、スライドがすぐに追従します。WebAssembly にコンパイルされた go-slides で、補完、ヘルプ、サンプル付きです。" +tags: [playground, browser] +--- + +**[Playground を開く →](https://go-slides.github.io/playground/)** + +Playground は go-slides v0.9.0 そのものを WebAssembly にコンパイルし、 +ブラウザー内で動かしたものです。左側にスライドデッキを入力すると、右側にスライドが追従して表示されます。 +スライドデッキも共有リンクも、サーバーには何も送信されません。 + +## 主な機能 {#what-it-has} + +- 各スライドの **ライブプレビュー**。ページのスレッドとは別(Web + Worker)でレンダリングされるので、入力が待たされることはありません。スライドをクリックすると、エディターが + その行に移動します。キャレットを動かすと、プレビューがそのスライドまでスクロールします。 +- **スライドショー**:キーボード操作、画面上のコントローラー、トランジションを備えた、 + プレゼンテーションそのものです。 +- **発表者ビュー**:**Present**(スライドショーでは `p`)を押すと、2 つ目のウィンドウで開きます。 + 現在のスライド、次のステップ、スピーカーノート(スライドデッキの + `<!-- comments -->`)、時計、タイマーが表示されます。矢印キー、Space、 + Home、End はどちらのウィンドウでも使え、互いに連動します。スライドデッキを編集したときも + 連動します。ウィンドウが開かない場合は、このサイトのポップアップを許可してください。 + 一覧表示(`o`)は、ダウンロードしたページでのみ使えます。 +- 入力中の **補完**:ディレクティブのキー(コメント内ではスポットディレクティブの `_` 付きのものも)、 + `kind`、`theme`、`size`、`paginate` の値、そして `{{ }}` 内のテンプレート関数と + データのフィールド。 +- キャレット位置にあるものの **ヘルプ**:ディレクティブ、kind(スライドの種類。何を設定するか)、 + テンプレート関数。ヘルプペインからは、このドキュメントの各ページにもリンクしています。 +- 該当行に表示される **問題**:テンプレート、YAML、HCL のエラーはエディター内でハイライトされ、 + エディターの下に一覧表示されます。プレビューには、最後に正しくレンダリングできたスライドデッキが + 表示されたままになります。 +- **サンプル**:ウェルカム用のスライドデッキ、スライドを生成するテンプレート、スライドの kind、 + HCL のフロントマター、テーマとスポットディレクティブ、そして数式、画像、 + 背景(`![bg]`、分割背景の `![bg left:40%]`、フィルター)。 +- **ファイル**:エディターの左にあるパネルはスライドデッキのプロジェクトで、 + [コマンド]({{< relref "cli" >}}) や + [VS Code 拡張機能]({{< relref "vscode" >}}) が読み込むのと同じ構成になっています:`deck.md`、 + `slides.yaml`、`themes/`、`kinds/`、`media/`。 + - ファイルをクリックすると編集できます。エディターは言語 + (Markdown、YAML、HCL、JSON、CSS)に応じて色付けし、問題箇所に印を付けます。 + - 右クリック(または Shift+F10)で、New File、New Folder、Rename(F2)、 + Delete(確認あり)、New theme / New kind を使えます。ファイルをフォルダーにドラッグすると + 移動できます。キーボード操作は WAI-ARIA のツリーパターンに従い、先行入力による検索にも + 対応しています。 + - **Project ▸ New theme** は `themes/my-gaia.css`(または default、 + uncover をもとにしたもの)を作成します。内容は組み込みテーマの `@import` と、追加したルールです。 + 作成したテーマは Theme の一覧に加わります。**New kind** と **New data file** も + テンプレートから始まります。いずれも `slides.yaml` に登録されるので、`slides build deck.md` + で見つかります。 + - **Project ▸ Download project (.zip)** ですべてを保存できます。展開すれば、 + `slides build deck.md` でビルドできます。**Open project** では `.zip` または + フォルダーを読み込めます。フォルダーの外に出るアーカイブ(`..`、絶対パス)、 + 256 個を超えるファイルや 8 MiB を超えるファイルを含むアーカイブ、展開後のサイズが 100 + 倍を超えるアーカイブは拒否されます。 + - すべては下書きと一緒に、このブラウザー内に保存されます。プロジェクトの容量は + 最大 64 MiB で、ファイル一覧の下の行に使用量が表示されます。 +- **メディア**:画像、動画、音声をエディターにドロップまたは貼り付けると、 + `media/` に保存され、スライドデッキにその参照が書き込まれます:`![](media/photo.png)`。パネルのフォルダーに + ファイルをドロップすると、そのフォルダーに保存されます。スライドデッキは小さく読みやすいままで、 + 何もアップロードされません。プレビューのサンドボックスは、制限を緩めることなくメディアを読み込みます + (ページがバイト列を渡します)。**Save as ▸ HTML + page** では、メディアがページに埋め込まれます。`https:` の画像も使えますが、ブラウザーが + そのサーバーから取得するため、サーバー側にはあなたのアドレスが伝わります。 + プレビューはリファラーを送信しません。 +- **サイズ変更可能なペイン**:ファイルパネル、エディター、スライドのサムネイル、 + プレビュー、ヘルプは、ドラッグできるバーで区切られています(バーの上ではポインターが + サイズ変更カーソルになります)。バーの隣のペインが広さを取り、 + ほかのペインはサイズを保ちます。バーをダブルクリックするか、バー上で Enter を押すと、 + ペインを閉じたり元に戻したりできます。キーボードでは、F6 でバー間を移動し、 + 矢印キーで 16 px ずつ動かし、Home と End でバーの前のペインを最小・最大にし、Escape でエディターに戻ります。 + View タブではこれまでどおりペインを非表示にできます。設定したサイズは下書きと一緒に + このブラウザーに保存されます。デフォルトのサイズ(190、168、280 px で、残りをエディターと + プレビューが 6 対 5 で分け合う)は保存されません。 +- **書式**:Format タブで Markdown を編集でき、1 回押すごとに + 元に戻す(Undo)の 1 ステップになります。 + - **テキスト**:Bold、Italic、Strikethrough、Inline code は、選択範囲 + またはキャレット位置の単語に適用され、オン・オフが切り替わります(Ctrl+B、Ctrl+I、Ctrl+Shift+X、 + Ctrl+E。Mac では Cmd)。キャレットが書式の記号の内側にあると、ボタンが押された状態で表示されます。 + Markdown には下線がないため、Underline(Ctrl+U)はテキストを + `<u>…</u>` で囲みます。このタグは Marp のデフォルトの HTML 許可リストで残されるので、 + 生の HTML がオフでも機能します。 + - **段落**:見出し 1〜3(Ctrl+Alt+1..3)、箇条書き(Ctrl+Shift+8)、 + 番号付きリスト(Ctrl+Shift+7)、引用は、選択した行に適用され、オン・オフが切り替わります。 + 水平線は `<hr>` タグです:`---`、`***`、`___` は、Marp ではいずれも + 新しいスライドの開始になってしまうためです。 + - **表**:グリッドで列数と行数を選びます。表の中にキャレットがあるときは、 + 行や列を追加・削除したり、列の配置(`:--`、`:-:`、 + `--:`)を設定したりできます。編集のたびに、表は整形し直されます。 + - **スライドのテキスト** の左揃え、中央揃え、右揃えは、現在のスライドに `<style scoped>` ブロックを + 書き込みます。Marp には配置の構文がなく、`style` 属性はデフォルトの HTML 許可リストを + 通りませんが、scoped ブロックは通ります。そのため、生の HTML がオフでも機能し、 + プレビュー、HTML ページ、PDF に反映されます。 + - **リンク**(Ctrl+K)は、url を選択した状態で `[text](url)` を書き込みます。 +- フロントマターのキーを、どの深さでも **ハイライト** します。YAML( + `{a: 1}` 形式のマッピングも含む)と HCL に対応しています。 +- **テーマ** の選択:default、gaia、uncover、プロジェクトのテーマ + (`themes/*.css`)、スライドデッキ内で定義したテーマ(`<style>` 内の + `/* @theme name */`)から選べます。選択すると、YAML または HCL のフロントマターに `theme:` が + 書き込まれるので、共有リンクにも反映されます。フロントマターを編集すると、 + 選択もそれに追従します。 +- 入力中もプレビューは **表示位置を保ちます**:変更されたスライドだけが置き換えられ、 + テーマを変更してもプレビューは同じスライドで開き直されます。 +- **共有リンク**:スライドデッキはリンク自体(`#` 以降の部分に + 圧縮して)に含まれるので、リンクを開けばスライドデッキが開きます。リンクで運べるのは + 圧縮後 64 KiB までで、スライドデッキだけです。プロジェクトにほかのもの + (テーマ、kind、メディア)が含まれる場合は、Share がその旨を伝え、`.zip` を提案します。 +- **Save as**: + - HTML ページ(`slides deck.md` が書き出すものと同じ) + - **PDF**(ベクター、テキスト選択可、アウトライン付き) + - 編集可能な **PowerPoint** または **OpenDocument** のスライドデッキ + + これらは、`slides --pdf`、 + `--pptx`、`--odp` と同じコードによってブラウザー内で作成されます。100 枚のベンチマーク用スライドデッキの PDF は、 + コマンドで作成したものとバイト単位で同一です。最初のエクスポート時には、エクスポート + エンジン(約 21 MiB。一度だけで、次の更新までブラウザーに保持されます)が、 + 進捗表示付きでダウンロードされます。その後、100 枚のスライドデッキは約 3 秒で書き出せます。 + `https:` の画像は、そのサーバーが許可している場合(CORS)にだけ含まれます。`data:` の + 画像は常に含まれます。 +- **下書きは保持されます**: + - スライドデッキは入力に合わせてこのブラウザーに保存され(IndexedDB、このサイト専用)、 + 再読み込みしても戻ってきます。どこにも何も送信されません。 + プロジェクトのテキストファイル(テーマ、kind、データ、`slides.yaml`)も同じように保存され、 + 入力直後にタブを閉じても失われません。メディアはドロップした時点で保存されます。 + - 共有リンクを開くとそのスライドデッキが開きますが、あなたの下書きは保持されます:**Draft ▸ Restore my + draft** で元に戻せます。 + - サンプルを開いても、**Draft ▸ Reset to the example**(確認あり)を実行しても、 + 別のタブで作業しても、下書きは消えません:**Draft ▸ Restore replaced** で + 元に戻せます。 + - **Draft ▸ Forget my drafts** は、保存されているものをすべて消去します。 + +どのエクスポートも、プレビューに表示されているプロジェクト(`slides.yaml`、 +テーマ、kind、データファイル、メディア)をレンダリングし、メディアはドキュメント内に埋め込まれます。 + +ファイルとしての画像(slides の `--images`)はブラウザーでは作成できません。 +[コマンド]({{< relref "cli" >}}) または +[VS Code 拡張機能]({{< relref "vscode" >}}) を使ってください。 + +## セキュリティ {#security} + +スライドデッキは、ページのオリジンを持たないサンドボックス化された iframe に表示されます。 +iframe からは Playground にもそのストレージにもアクセスできず、フォームの送信もナビゲーションもできません。 +そのページには、自身のスクリプトだけを実行する Content Security Policy が設定されています。 +スライドデッキ内の生の HTML は、`slides` のデフォルトと同じく Marp の許可リストに従います。 +5 秒を超えて実行されるレンダリング(ループするテンプレートなど)は +停止されます。 + +発表者ウィンドウも同じサンドボックスを保ちます。このウィンドウは Playground 自身が開き、 +同じようにサンドボックス化された iframe にスライドデッキを表示します。2 つの +iframe はストレージを共有できないので、Playground がメッセージでスライド番号を +中継します。スライドデッキを実行するフレームが Playground の +オリジンを得ることはありません。 + +プロジェクトのメディアは、メッセージ内のバイト列としてサンドボックス化されたフレームに渡され、 +各フレームは自身のオリジンで、それぞれ独自の `blob:` URL を作成します。Playground が作成した `blob:` +URL はそこでは読み込まれず、フレームのポリシーに追加されるのは、メディアとフォント向けの `blob:` だけです。 + +## サイズと速度 {#size-and-speed} + +モジュールは、GitHub Pages が配信する状態で 8.8 MiB です(gzip。Pages は +brotli で配信しません)。進捗バー付きで一度だけダウンロードされ、その後は次のデプロイまで +ブラウザーにキャッシュされます。Apple M4 Max と Chrome 155 で公開中のページを +計測したところ、エディターはページを開いてから 2.1 s で使えるようになり、そのうち 1.5 s +はダウンロードで、最初のスライドは 2.4 s で表示されました。100 枚のベンチマーク用スライドデッキへの変更は、 +約 30 ms でプレビューに反映されます:go-slides は変更されたスライドだけを +レンダリングし、プレビューでもそのスライドだけが置き換えられます。 diff --git a/content/ja/templates.md b/content/ja/templates.md new file mode 100644 index 0000000..ddc8088 --- /dev/null +++ b/content/ja/templates.md @@ -0,0 +1,77 @@ +--- +title: "テンプレート" +weight: 30 +description: "スライドデッキでの Go text/template による埋め込み:値、スライドの生成、ページ番号、インクルード。" +tags: [templates] +--- + +スライドデッキは Go の [`text/template`](https://pkg.go.dev/text/template) であり、 +Markdown の解析 **前** に評価されます。Go のテンプレート言語が備えるもの +(アクション、パイプライン、`if`、`range`、`with`、変数)は、すべて +スライドデッキの中で使えます。 + +## データ {#data} + +テンプレートのドットには、フロントマターのキー、次にその +`data:` マッピングのキー、さらにコマンドラインまたは `slides.yaml` で与えたデータが、 +この順に入ります。後のものが前のものを上書きします。 + +```markdown +--- +marp: true +title: Quarterly review +data: + team: Platform +--- + +# {{ .title }} + +Presented by the {{ .team }} team. +``` + +コマンドラインからは次のように指定します。 + +| フラグ | 与えるもの | +| --- | --- | +| `--data file.yaml` | データを記述した YAML または JSON ファイル(複数回指定可) | +| `--set key=value` | 1 つの値(複数回指定可) | + +## スライドを生成する {#generating-slides} + +本文全体が 1 つのテンプレートなので、`---` を書き出す `range` はスライドを書き出すことになります。 + +```markdown +{{ range .releases }} +--- + +# {{ .version }} + +{{ range .changes }}- {{ . }} +{{ end }} +{{ end }} +``` + +## ページ番号と総数 {#page-and-total} + +`{{ page }}` と `{{ total }}` は、そのスライドの番号とスライドの総数です。 +これらはスライドデッキが分割されて初めて決まるため、その時点で埋め込まれます。 +生成されたスライドデッキでは、生成されたスライドも数えられます。 + +```markdown +<!-- footer: "{{ page }} / {{ total }}" --> +``` + +## インクルード {#include} + +`{{ include "parts/agenda.md" }}` は、スライドデッキからの相対パスで指定した別のファイルを +挿入します。 + +## コードはそのまま {#code-is-left-alone} + +フェンスで囲んだコードブロックとインラインコードは、テンプレートの実行前に取り分けられ、 +そのまま元に戻されます。そのため、Go テンプレートについてのスライドでも +`{{ .Name }}` をエスケープせずに表示できます。 + +## 無効にする {#turning-it-off} + +`--no-template` を指定すると、スライドデッキをプレーンな Marp として読み込みます。`{{` はただのテキストになります。 diff --git a/content/ja/vscode.md b/content/ja/vscode.md new file mode 100644 index 0000000..2124115 --- /dev/null +++ b/content/ja/vscode.md @@ -0,0 +1,186 @@ +--- +title: "VS Code" +weight: 55 +description: "VS Code 向けの go-slides 拡張機能。Marp for VS Code が Marp に対して行うのと同じく、プレビュー、発表、問題の表示、補完、エクスポートを提供します。" +tags: [vscode, editor] +--- + +{{< callout type="info" >}} +拡張機能は [go-slides/vscode](https://github.com/go-slides/vscode) で、 +独自にバージョン管理されています(v0.4.0)。レンダリングには go-slides v0.9.0( +`editor` パッケージ、スクロール同期用のソース行、一覧表示と +発表者ビュー)を使います。インストールには、GitHub リリースに添付された `go-slides-0.4.0.vsix` +を使います。VS Code Marketplace と Open VSX での公開は +準備中で、まだ公開されていません。 +{{< /callout >}} + +この拡張機能は、go-slides 版の +[Marp for VS Code](https://github.com/marp-team/marp-vscode) です。スライドデッキは +VS Code の Markdown プレビューにスライドとして表示され、エラーは「問題」パネルに表示され、 +ディレクティブ、kind(スライドの種類)、テンプレート関数は入力中に補完されます。 + +## インストール {#install} + +[リリース](https://github.com/go-slides/vscode/releases) から +`go-slides-<version>.vsix` をダウンロードし、 +*Extensions: Install from VSIX...* を実行するか、次のコマンドを実行します。 + +```sh +code --install-extension go-slides-0.4.0.vsix +``` + +プレビューにはほかに何も必要ありません。エクスポートには `slides` コマンドが必要です。 + +```sh +go install github.com/go-slides/slides/cmd/slides@latest +``` + +## プレビュー {#preview} + +スライドデッキを開き、*Markdown: Open Preview to the Side* を実行します。スライドデッキとは、 +フロントマターに `slides: true` と書かれた Markdown ファイルです。 + +```markdown +--- +slides: true +paginate: true +data: + who: World +--- + +# Hello {{ .who }} + +--- + +<!-- _kind: title --> +# Page {{ page }} of {{ total }} +``` + +`marp: true` のファイルもプレビューされます。ただし、Marp for VS Code がインストールされている場合は除きます。 +両方の拡張機能が同じドキュメントをレンダリングしてしまうためです。この挙動は設定 +`goSlides.preview.marpDocuments` で決まります:`auto`(デフォルト)、`always`、 +`never`。 + +プレビューは、WebAssembly にコンパイルされて拡張機能に読み込まれる go-slides そのものです。 +バイナリは不要で、[vscode.dev](https://vscode.dev) でも動作します。スライドデッキが使うもの +(スライドデッキと同じ場所の `slides.yaml` または `slides.hcl`、そこで指定された +[kind]({{< relref "kinds" >}}) のファイル、[テンプレート]({{< relref "templates" >}}) がインクルードするファイル、 +テーマの CSS)をワークスペースから読み込みます。読み込むのは、 +スライドデッキのワークスペースフォルダー内のものだけです。 + +## スクロール同期 {#scroll-sync} + +拡張機能の v0.2.0(go-slides v0.7.0)以降、エディターとプレビューは、 +通常の Markdown と同じく双方向に連動してスクロールします: +プレビューはエディターのカーソル位置にあるスライドとブロックに追従し、 +プレビューをスクロールするとエディターが移動します。設定は VS Code 自体のもの、 +`markdown.preview.scrollPreviewWithEditor` と +`markdown.preview.scrollEditorWithPreview` です。 + +行はテンプレートを通して追跡されます。`{{ range }}` が生成したスライドは +そのテンプレートの行に追従し、`{{ include }}` で取り込まれた内容は +インクルードした行に追従します。`headingDivider` で分割されたスライドデッキは、 +見出しの位置で同期します。 + +## 発表 {#presenting} + +拡張機能の v0.4.0(go-slides v0.9.0)以降、VS Code から離れずに +スライドデッキで発表できます。スライドデッキをエディターで開いた状態で、コマンドパレット +またはエディターの `…` メニューから実行します。 + +| コマンド | 開くもの | +|---|---| +| *go-slides: Open Overview* | すべてのスライドをグリッドに並べたパネル。スライドを選ぶとエディターにそのソースが表示されます。`Esc` でパネルを閉じます | +| *go-slides: Start Presenter View* | スライドと、その横の発表者ビュー:次のステップ、スピーカーノート、タイマー | + +パネルはエディターの横に配置され、互いに連動します:どれかでスライドを切り替えると、 +ほかのパネルも切り替わります。スライドデッキの編集にも追従します。スライドのパネルで `p` +キーを押すと、発表者ビューが開きます。 + +別の画面で発表するには、*View: Move +Editor into New Window* でスライドのパネルをその画面に移動します。 + +各パネルは、サンドボックス化されたフレーム(`allow-same-origin` なし)で、 +`default-src 'none'` のポリシーの下でスライドデッキを実行します:フレームが読み込むのは画像、メディア、スタイル、フォントだけで、 +実行するのは go-slides がページに組み込んだ、ハッシュで指定されたスクリプトだけです。 +スライドデッキ内に書かれたスクリプトは実行されません。 + +## 問題、補完、ホバー {#problems-completion-hover} + +- **問題**:テンプレート、YAML、[HCL]({{< relref "hcl" >}}) のエラーを、スライドデッキ内または + エラー元の kind のファイル内の、該当する行に表示します。 +- **補完**:フロントマターと `<!-- -->` コメント内のディレクティブのキー + (スポットディレクティブには `_` 付き)、`kind` と `_kind` の値(各 kind が設定する内容付き)、 + `theme`、`size`、`paginate`、`math`、`headingDivider`。 + `{{ }}` 内では、テンプレート関数とデータのフィールド。 +- **ホバー**:ディレクティブの働き、kind が設定する内容、テンプレート関数の + 引数を表示します。 + +## エクスポート {#export} + +*go-slides: Export Slide Deck...*(エディターのタイトルバーにもあります)は、 +`slides -o deck.pdf deck.md` と同じように `slides` コマンドを使って、HTML、 +ベクター PDF、編集可能な PPTX と ODP、PNG、JPEG を書き出します。[コマンドライン]({{< relref "cli" >}}) を参照してください。 +信頼されたワークスペースが必要です。`slides` が見つからない場合、拡張機能は上記の `go install` を +ターミナルで実行することを提案します。 + +## 設定 {#settings} + +| 設定 | デフォルト | | +|---|---|---| +| `goSlides.cliPath` | `""` | `slides` コマンド。空の場合は `PATH` 上の `slides`。マシン設定なので、ワークスペースからは設定できません | +| `goSlides.exportType` | `pdf` | エクスポートダイアログで最初に提案される形式 | +| `goSlides.exportAutoOpen` | `true` | エクスポートしたファイルを開きます | +| `goSlides.html` | `false` | Marp の許可リストではなく、生の HTML をすべて許可します。信頼されたワークスペースでのみ有効です | +| `goSlides.preview.marpDocuments` | `auto` | `marp: true` のスライドデッキをプレビューするかどうか:`auto`、`always`、`never` | + +## セキュリティ {#security} + +スライドデッキとワークスペースは信頼されていないものとして扱います。生の HTML は、 +信頼されたワークスペースで `goSlides.html` がオンになっていない限り、Marp の許可リストに従います +(`html: true` を含む `slides.yaml` があっても、プレビューでは有効になりません)。また、VS Code のプレビューは +スライドデッキのスクリプトを実行しません。プレビューが読み込むのはスライドデッキのワークスペースフォルダー内の +ファイルだけで、シンボリックリンクはたどらず、テンプレートから環境変数は見えません。 +エクスポートはプログラムを実行するので、ワークスペースが信頼されるまで待機します。また、そのプログラムが +ワークスペースによって指定されることはありません。 + +## パフォーマンス {#performance} + +go-slides の 100 枚のベンチマーク用スライドデッキについて、Apple M4 Max 上の VS Code 1.142 で、 +同じセッション内で Marp for VS Code 3.6.1 と比較して計測しました。 + +| | go-slides | Marp for VS Code | +|---|---:|---:| +| アクティベーション(WebAssembly モジュールの読み込み) | 189 ms | — | +| 最初のプレビュー | 52 ms | 78 ms | +| 編集後のプレビュー(中央値) | **19 ms** | 23–24 ms | +| `.vsix` | 7.5 MB | 36.3 MB | + +この数値は、拡張機能 v0.3.0(go-slides v0.8.0)で 100 枚のスライドデッキを使い、 +50 枚目のスライドに 1 文字入力したときのもので、1 分間のロードアベレージが 8 未満の状態で +交互に行ったラウンドから得ています。最初のプレビューは Marp より速く、編集後のプレビューも +現在は Marp より速くなっています。 + +### v0.8.0 と拡張機能 v0.3.0 {#in-v080-and-extension-v030} + +go-slides v0.8.0 は、開いている各スライドデッキのレンダリング済みスライドを保持し、編集後は、 +ソースまたは継承された状態(ディレクティブ、ページ番号、総数、見出しの id、リンク参照、ソース行)が +変わったスライドだけを再レンダリングします。出力は完全なレンダリングとバイト単位で +同じです。100 枚中 50 枚目のスライドで入力すると、再レンダリングされるのは 1 +枚だけで、ネイティブでは 2.5 ms、WebAssembly では 11.5 ms です。その後、VS Code のプレビューは +変更されたスライドの DOM だけを差分更新します。拡張機能のスモークテストでは、両方の拡張機能について、 +計測するレンダリングの合間に 50 枚目のスライドへ 1 文字を入力します。 +拡張機能の発表者ビューは、親ウィンドウ経由(`postMessage`)で同期します。 + +### v0.7.1 と拡張機能 v0.2.2 {#in-v071-and-extension-v022} + +go-slides v0.7.1 は、解析済みのテーマ、パック済みのスタイルシート、ハイライト済みのコードを +レンダリング間でキャッシュし、スクロール同期用の行をバイト単位で走査せずに記録し、 +ライターを再利用します:100 枚のスライドデッキの 1 回のレンダリングで割り当てるメモリは 5.7 MB で、 +拡張機能 v0.2.0 の 52.7 MB から減りました。これにより、拡張機能 v0.2.2 では `GOGC=400` の上書きをやめています。 +比較のために挙げると、拡張機能 v0.2.1 は、デフォルトの `GOGC` で最初のプレビューに 72 ms、 +編集後に 44 ms(`GOGC=400` では 36 ms)かかり、v0.2.0 は +47–51 ms でした。 + +拡張機能 v0.2.2 の計測値は、最初のプレビューが 52 ms、編集後が 24 ms で、 +Marp の 23 ms とほぼ同じでした。 diff --git a/content/ko/_index.md b/content/ko/_index.md new file mode 100644 index 0000000..53f97b4 --- /dev/null +++ b/content/ko/_index.md @@ -0,0 +1,60 @@ +--- +title: "go-slides 문서" +linkTitle: "홈" +type: docs +cascade: + type: docs +description: "순수 Go(CGO=0)로 만드는 Marp 호환 프레젠테이션. Go 템플릿 치환과 슬라이드 kind를 지원합니다." +--- + +**`slides`는 Marp 프레젠테이션을 순수 Go만으로 발표 자료로 만들어 줍니다.** +[Marp](https://marp.app)가 읽는 Markdown을 그대로 읽으며(Marpit 디렉티브, +슬라이드 사이의 `---`, `![bg]` 배경, 발표자 노트, `default`, `gaia`, +`uncover` 테마), Marp에 없는 두 가지 기능을 더합니다. + +- 프레젠테이션 안의 **[Go 템플릿]({{< relref "templates" >}})**: Markdown을 + 파싱하기 전에 평가되므로 프레젠테이션이 스스로 슬라이드를 생성할 수 있습니다. +- **[슬라이드 kind(슬라이드 유형)]({{< relref "kinds" >}})**: 슬라이드가 어떻게 + 보이는지를 반복하는 대신 자신이 무엇인지(`title`, `section`…)를 밝힙니다. + +{{< callout type="warning" >}} +**v0.9.0.** 이 문서는 +[go-slides/slides v0.9.0](https://github.com/go-slides/slides/releases/tag/v0.9.0)을 +설명하며 `/docs/0.9/` 아래에 게시됩니다. +{{< /callout >}} + +## 현재 지원하는 기능 {#what-it-does-today} + +| | | +| --- | --- | +| `slides build` (기본값) | 프레젠테이션 전체를 하나의 HTML 페이지로 만듭니다. 각 슬라이드는 창 크기에 맞춰 늘어나는 SVG 틀 안의 `<section>`이며, 테마 CSS와 키보드 뷰어가 함께 들어갑니다 | +| `slides build --pdf`, `--images png`, `--image jpeg` | 브라우저 없이 프레젠테이션을 PDF(슬라이드당 한 페이지, 개요 포함)나 PNG/JPEG 이미지로 만듭니다. 렌더링은 [go-webengine](https://github.com/go-webengine)이, PDF 작성은 [go-pdfkit](https://github.com/go-pdfkit)이 맡습니다. v0.4.0부터는 벡터로 작성되어 텍스트를 선택할 수 있습니다: [벡터 PDF]({{< relref "cli#vector-pdf" >}}) | +| 수식과 코드 | `$…$`와 `$$…$$`는 go-tex/math로 조판하고, 펜스 코드 블록은 go-rouge로 강조 표시합니다. HTML, 이미지, PDF 모두에 적용됩니다: [수식과 코드]({{< relref "math-and-code" >}}) | +| 이모지 | `:smile:`과 😄는 Twemoji 이미지로 바뀌어 내장되므로 네트워크 없이도 모든 출력에 그려집니다: [이모지]({{< relref "emoji" >}}) (v0.4.0부터) | +| `slides build --pptx` | 발표자 노트와 프레젠테이션 메타데이터를 담은 PowerPoint 파일로 만듭니다. v0.4.0에서는 marp-cli처럼 슬라이드당 그림 한 장이었고, v0.5.0부터는 텍스트 상자, 도형, 그림, 내장 글꼴로 이루어져 **편집할 수 있습니다** ([편집 가능한 PPTX와 ODP]({{< relref "cli#editable-pptx-and-odp" >}})) | +| `slides build --odp` | 같은 내용을 OpenDocument 프레젠테이션(`.odp`)으로 만듭니다 | +| 제목 맞춤 | `# <!-- fit --> Title`은 페이지와 모든 내보내기 결과에서 한 줄로 슬라이드 너비를 채웁니다 | +| `slides expand` | 프레젠테이션을 일반 Marp Markdown으로 펼칩니다. 템플릿은 평가되고, kind는 그에 해당하는 스폿 디렉티브로 바뀝니다 | +| `slides notes` | 발표자 노트를 출력합니다 | +| `slides kinds`, `slides themes` | 프레젠테이션에서 지정할 수 있는 이름을 보여 줍니다 | +| `slides.yaml` | 한 폴더의 프레젠테이션들이 공유하는 kind, 테마, 데이터, HTML 정책, 언어를 정합니다 | +| HCL | `---hcl` 프런트 매터, `slides.hcl`, HCL2로 쓴 kind와 데이터 파일을 표현식과 함수까지 지원합니다: [HCL]({{< relref "hcl" >}}) | + +## 계획된 기능 {#planned} + +아직 구현되지 않았으며, 추측할 필요가 없도록 적어 둡니다. PDF 안의 수식을 +벡터로 출력하는 기능입니다(현재는 비트맵입니다). marp-cli와의 차이는 방식에 +있습니다. marp-cli는 헤드리스 Chrome을 구동해 이미지와 PDF를 렌더링하지만, +여기서는 Go 라이브러리가 그 일을 합니다. + +## 페이지 {#pages} + +- [시작하기]({{< relref "getting-started" >}}) +- [갤러리]({{< relref "gallery" >}}): Playground에서 열어 볼 수 있는 예제 프레젠테이션 12개 +- [Marp 호환성]({{< relref "marp" >}}) +- [템플릿]({{< relref "templates" >}}) +- [Kind]({{< relref "kinds" >}}) +- [HCL]({{< relref "hcl" >}}) +- [수식과 코드]({{< relref "math-and-code" >}}) +- [이모지]({{< relref "emoji" >}}) +- [명령줄]({{< relref "cli" >}}) diff --git a/content/ko/cli.md b/content/ko/cli.md new file mode 100644 index 0000000..3be0414 --- /dev/null +++ b/content/ko/cli.md @@ -0,0 +1,362 @@ +--- +title: "명령줄" +weight: 50 +description: "slides의 모든 명령과 플래그, 그리고 slides.yaml 설정 파일." +tags: [cli] +--- + +{{< callout type="warning" >}} +이 페이지는 v0.9.0을 기준으로 설명합니다. +{{< /callout >}} + +```text +slides [deck.md] [flags] +slides [command] [flags] deck.md +``` + +## 명령 {#commands} + +| 명령 | 하는 일 | +| --- | --- | +| `build` | 프레젠테이션을 HTML, PDF, 이미지로 씁니다(아래 참고). **기본 명령**이므로 `slides deck.md`는 `slides build deck.md`와 같습니다 | +| `expand` | 프레젠테이션을 일반 Marp Markdown으로 출력합니다. 템플릿은 펼쳐지고, kind(슬라이드 유형)는 그에 해당하는 디렉티브로 바뀝니다 | +| `notes` | 발표자 노트를 출력합니다 | +| `kinds` | 프레젠테이션에서 쓸 수 있는 kind와 각각이 설정하는 내용을 나열합니다. `--json`(v0.6.0부터)을 쓰면 `extends`를 해석한 상태로 `[{"name", "extends", "directives": [{"key", "value"}]}]` 형식으로 씁니다 | +| `themes` | 테마를 나열합니다 | +| `version` | 버전을 출력합니다(`slides --version`도 같음) | +| `completion` | 셸 자동 완성 스크립트를 출력합니다: `bash`, `zsh`, `fish`, `powershell` | + +`slides <command> --help`는 명령의 플래그를 출력합니다. 플래그는 프레젠테이션 +앞에 와도, 뒤에 와도 됩니다. `slides deck.md -o out.html`과 `slides -o out.html +deck.md`는 같습니다. 긴 플래그는 대시 두 개(`--html`)로 쓰며, 짧은 플래그는 +`-o` 하나뿐입니다. `build`는 `-o`로 따로 지정하지 않으면 `deck.md` 옆에 +`deck.html`을 씁니다. + +## 플래그 {#flags} + +| 플래그 | 명령 | 하는 일 | +| --- | --- | --- | +| `-o`, `--output file` | `build`, `expand`, `notes` | 출력 파일. 표준 출력은 `-` | +| `-I`, `--input-dir dir` | `build` | `dir` 아래의 Markdown 파일을 재귀적으로 변환합니다(v0.6.0부터). 아래 참고 | +| `-P`, `--parallel n` | `build` | 동시에 빌드할 프레젠테이션 수(v0.6.0부터). 기본값 5, 최대 코어 수이며, 1이면 하나씩 차례로 빌드합니다 | +| `-w`, `--watch` | `build` | 프레젠테이션, 테마, kind 파일이나 데이터 파일, 설정이 바뀌면 다시 빌드하고 열린 페이지를 새로 고칩니다(v0.6.0부터). 아래 참고 | +| `-s`, `--server dir` | `build` | 디렉터리를 제공하며, 요청이 오면 그 안의 프레젠테이션을 변환합니다(v0.6.0부터). 아래 참고 | +| `-p`, `--preview` | `build` | 결과를 기본 브라우저로 엽니다(v0.6.0부터). `--watch`(자동 적용)와 함께면 HTML 페이지를, `--server`와 함께면 서버를 엽니다 | +| `--host addr` | `build` | `--server`가 수신할 주소. 기본값은 `127.0.0.1`입니다(v0.6.0부터) | +| `--template bespoke\|bare` | `build` | HTML 페이지 템플릿(v0.6.0부터). 기본값인 `bespoke`는 발표 스크립트, 화면 컨트롤러, 전환 효과를 포함하고, `bare`는 슬라이드를 쌓아 놓습니다(`--bare`) | +| `--bespoke.osc`, `--bespoke.progress`, `--bespoke.transition` | `build` | bespoke 템플릿의 옵션으로, 기본값은 marp-cli와 같습니다(v0.6.0부터). 컨트롤러 켜짐, **진행률 막대 꺼짐**(이전에는 항상 그려졌음), 전환 효과 켜짐. `--bespoke.osc=false`처럼 하나씩 끌 수 있습니다 | +| `-c`, `--config-file file`, `--no-config-file` | `build` | marp-cli의 설정 파일(JSON 또는 YAML, v0.6.0부터). 아래 참고. `--config`는 `slides` 자체의 설정 파일입니다 | +| `--browser`, `--browser-path`, `--browser-protocol`, `--browser-timeout` | `build` | 받아들이되 경고와 함께 무시합니다(v0.6.0부터). 브라우저가 없기 때문입니다 | +| `--pptx-editable`, `--no-parallel` | `build` | `--pptx`와 `-P 1`의 marp-cli식 표기(v0.6.0부터) | +| `--config file` | 모두 | 설정 파일. 기본값은 프레젠테이션 옆에 있는 `slides.yaml`(있을 경우) | +| `--no-config` | 모두 | 설정 파일을 읽지 않습니다 | +| `--theme-set path` | 모두 | 테마 CSS 파일 또는 그런 파일이 든 디렉터리(여러 번 지정 가능) | +| `--kinds file` | 모두 | kind를 정의한 YAML 파일(여러 번 지정 가능) | +| `--data file` | 모두 | 템플릿 데이터를 담은 YAML 또는 JSON 파일(여러 번 지정 가능) | +| `--set key=value` | 모두 | 템플릿 값 하나(여러 번 지정 가능) | +| `--no-template` | 모두 | Go 템플릿을 펼치지 않습니다 | +| `--strict-yaml` | 모두 | 순수 Marpit처럼 디렉티브를 엄격한 YAML로 파싱합니다(Marp Core는 느슨하게 파싱합니다) | +| `--no-emoji` | 모두 | `:shortcodes:`와 Unicode 이모지를 이미지로 그리지 않고 텍스트로 둡니다(v0.4.0부터: [이모지]({{< relref "emoji" >}})) | +| `--html` | `build` | 모든 원시 HTML을 허용합니다. 기본값은 Marp Core의 허용 목록입니다 | +| `--lang code` | `build` | 프레젠테이션이 지정하지 않았을 때 쓸 페이지의 `lang` | +| `--bare` | `build` | 발표 스크립트 없이 슬라이드를 쌓아 놓습니다 | +| `--pdf` | `build` | 슬라이드당 한 페이지, 개요가 포함된 PDF를 씁니다. 텍스트와 도형은 벡터입니다(`-o file.pdf`로도 선택됨) | +| `--pdf-raster` | `build` | v0.3.0처럼 각 페이지가 슬라이드 이미지 한 장인 PDF를 씁니다 | +| `--notes` | `build` | marp-cli처럼 슬라이드를 `---`로 구분한 텍스트 파일로 발표자 노트를 씁니다(v0.6.0부터, `-o file.txt`로도 선택됨). `notes` 명령은 다른 형식으로 출력합니다 | +| `--pdf-notes` | `build` | marp-cli처럼 각 슬라이드의 발표자 노트를 PDF 페이지에 노트 주석으로 추가합니다(v0.6.0부터). 출력 형식을 따로 지정하지 않으면 PDF를 만듭니다 | +| `--pdf-outlines`, `--pdf-outlines.pages`, `--pdf-outlines.headings` | `build` | PDF 책갈피(v0.6.0부터): 슬라이드마다 "Page N"과 그 아래에 중첩된 제목. marp-cli와 달리 여기서는 기본적으로 켜져 있으며, `=false`로 하나씩 끌 수 있습니다. 아래 참고 | +| `--images png\|jpeg` | `build` | 슬라이드마다 이미지 한 장을 씁니다: `deck.001.png`, … | +| `--image png\|jpeg` | `build` | 첫 슬라이드를 이미지로 씁니다(`-o file.png`나 `-o file.jpg`로도 선택됨) | +| `--pptx` | `build` | 발표자 노트를 포함한 PowerPoint 파일을 씁니다(`-o file.pptx`로도 선택됨). v0.5.0부터 편집 가능하며, v0.4.0에서는 슬라이드당 그림 한 장이었습니다 | +| `--pptx-raster` | `build` | marp-cli처럼 슬라이드마다 슬라이드 전체 크기 그림 한 장으로 이루어진 PowerPoint 파일을 씁니다(v0.5.0부터). `--image-scale` 기본값은 2입니다 | +| `--odp` | `build` | 발표자 노트를 포함한 OpenDocument 프레젠테이션을 씁니다(`-o file.odp`로도 선택됨). v0.5.0부터 편집 가능합니다 | +| `--odp-raster` | `build` | 슬라이드당 그림 한 장으로 이루어진 OpenDocument 프레젠테이션을 씁니다(v0.5.0부터) | +| `--jpeg-quality n` | `build` | JPEG 품질, 1~100(기본값 85) | +| `--image-scale n` | `build` | 이미지와 PDF에서 CSS 픽셀당 장치 픽셀 수(기본값 1). 2이면 16:9에서 2560×1440이 되며, 텍스트와 벡터는 확대되지 않고 그 밀도로 그려집니다 | +| `--allow-local-files` | `build` | 이미지와 PDF가 로컬 파일을 읽도록 허용하되, 프레젠테이션 디렉터리 아래의 파일만 허용합니다. marp-cli와 마찬가지로 기본값은 거부입니다 | +| `--theme name\|file.css` | `build` | 프레젠테이션의 테마를 재정의합니다(v0.6.0부터). 테마 이름이나 CSS 파일을 지정하며, CSS 파일은 어떤 `@theme`을 선언하든 그 테마가 됩니다. 디렉터리는 거부되므로 `--theme-set`을 쓰십시오 | +| `--title`, `--description`, `--author`, `--keywords`, `--url`, `--og-image` | `build` | 페이지 메타데이터(v0.6.0부터). 프레젠테이션의 `title`, `description`, `author`, `keywords`, `url`, `image` 디렉티브보다 우선합니다. 아래 참고 | + +## 여러 프레젠테이션과 디렉터리 {#several-decks-and-a-directory} + +v0.6.0부터 `build`는 여러 프레젠테이션, 디렉터리(그 아래에서 재귀적으로 찾은 +프레젠테이션), 패턴을 받을 수 있으며, 최대 `--parallel`개를 동시에 빌드합니다. +`-`는 표준 입력입니다. marp-cli와 달리 `-`로 지정하지 않으면 파이프를 읽지 +않습니다. + +패턴은 marp-cli의 glob과 같은 방식으로 해석됩니다(`**`와 `{a,b}`는 v0.7.0부터. +셸이 펼치지 않고 `slides`에 넘기도록 따옴표로 감싸십시오). 이름 안에서는 `*`, +`?`, `[a-z]`, `{md,txt}`를, 디렉터리 여러 단계에는 `**`를 쓰므로 +`'docs/**/*.md'`는 트리 안의 모든 프레젠테이션을 가리킵니다. 와일드카드는 점으로 +시작하는 이름과 일치하지 않고, `node_modules` 안은 찾지 않으며, 대소문자를 +구분하고(`*.md`는 `UPPER.MD`를 찾지 않음), 패턴과 일치하는 것은 확장자와 +관계없이 변환됩니다. 아무것도 일치하지 않는 패턴은 오류입니다. + +- **`-I dir`**는 marp-cli처럼 `dir` 아래의 Markdown 파일(소문자 `.md`, `.mdown`, + `.markdown`, `.markdn`)을 찾으며, `node_modules`와 점으로 시작하는 + 디렉터리는 찾지 않습니다. `-o out`을 지정하면 각 프레젠테이션은 `dir` 아래의 + 경로를 `out` 아래에 그대로 옮긴 위치에 쓰입니다. `dir/sub/b.md`는 + `out/sub/b.html`(플래그에 따라 `b.pdf`, `b.001.png`, `b.txt`, …)이 됩니다. + `-o`가 없으면 출력은 각 프레젠테이션 옆에 쓰입니다. `-I`와 함께 + 프레젠테이션을 지정하면 오류이며, 여러 프레젠테이션에 `-o file`을 지정해도 + 오류입니다(`-o -`는 허용). +- **`-P n`**은 프레젠테이션 `n`개를 동시에 빌드합니다. 프레젠테이션 하나의 + 렌더링도 이미 여러 코어를 쓰므로 `n`은 코어 수로 제한되며, `-o -`이면 하나씩 + 차례로 빌드합니다. 한 프레젠테이션의 메시지는 함께 묶여 출력되고, 실패한 + 프레젠테이션이 있어도 나머지는 멈추지 않습니다. 종료 상태는 1이며 메시지에 + 실패 목록이 나옵니다. + +## 전환 효과와 발표 페이지 {#transitions-and-the-presenting-page} + +v0.6.0부터 marp-cli의 `transition` 디렉티브가 동작합니다. `<!-- transition: +fade 1s -->`처럼 이름과 선택적 지속 시간을 쓰면 해당 슬라이드와 그 뒤의 +슬라이드에 적용되고, `<!-- _transition: cover -->`는 한 슬라이드에만 +적용됩니다. 기본 제공 이름은 marp-cli와 같습니다: +`clockwise`, `counterclockwise`, `cover`, `coverflow`, `cube`, `cylinder`, +`diamond`, `drop`, `explode`, `fade`, `fade-out`, `fall`, `flip`, `glow`, +`implode`, `in-out`, `iris-in`, `iris-out`, `melt`, `overlap`, `pivot`, `pull`, +`push`, `reveal`, `rotate`, `slide`, `star`, `swap`, `swipe`, `swoosh`, `wipe`, +`wiper`, `zoom`. `none`은 전환 효과를 끕니다. 페이지는 보는 사람이 전환 효과가 +지정된 슬라이드에서 앞으로 넘어갈 때와 그 슬라이드로 돌아올 때 브라우저의 +**View Transition API**로 효과를 재생합니다. 이 API가 없는 브라우저는 이전처럼 +슬라이드를 바꾸고, 동작 줄이기를 요청한 사용자에게는 단순한 페이드가 적용됩니다. +테마는 `marp-transition-NAME`(또는 `marp-outgoing-`과 `marp-incoming-`, 그리고 +`-backward`)이라는 이름으로 자체 키프레임을 제공할 수 있으며, 이 키프레임이 기본 +제공 키프레임보다 먼저 쓰입니다. + +페이지에는 이전, 페이지, 다음, 전체 화면 버튼으로 이루어진 **화면 컨트롤러**가 +있습니다. 포인터를 움직이거나 키를 누르면 나타났다가 잠시 뒤 사라지며 +(`--bespoke.osc=false`로 제거), `--bespoke.progress`를 쓰면 진행률 막대도 +표시됩니다. **이제 진행률 막대는 marp-cli처럼 기본적으로 꺼져 있습니다.** +이전에는 항상 그려졌습니다. + +### 발표자 보기 {#presenter-view} + +v0.7.0부터 페이지에 marp-cli의 발표자 보기가 있습니다. `p`를 누르거나 컨트롤러의 +마지막 버튼을 누르면 현재 슬라이드, 그다음 단계, 발표자 노트, 페이지, 시각, +타이머가 있는 창이 열립니다(타이머를 클릭하면 초기화되고, `+`와 `-`로 노트 크기를 +조절하며, 슬라이드와 노트 사이의 막대는 끌어서 움직일 수 있습니다). 두 창은 +양방향으로 연동됩니다. 어느 창에서든 화살표 키, 다음 단계 클릭, Previous/Next +클릭으로 다른 창도 한 단계(프래그먼트)씩 움직입니다. + +marp-cli 4.5.1 소스에서 확인한 것과 같은 방식으로 동작합니다. 창은 같은 파일의 +`?view=presenter&sync=KEY`이고, 다음 단계는 같은 페이지를 `?view=next`로 +프레임에 띄운 것이며, 두 창은 `localStorage`(`bespoke-marp-sync-KEY`)로 위치를 +공유합니다. 따라서 서버도 네트워크도 필요 없지만, `localStorage`가 없는 +브라우저에서는 동작하지 않습니다(버튼이 이를 알려 줍니다). 노트는 텍스트이며 +`&`, `<`, `>`가 이스케이프되므로 노트가 마크업이 될 수 없습니다. 노트는 발표자 +보기가 가져갈 수 있도록 페이지 안에 숨겨져 있어, 화면 낭독기가 청중에게 읽어 +주는 내용에는 포함되지 않습니다. `--bare` 페이지에는 이 기능이 없습니다. 발표자 +보기는 페이지 크기를 약 10 KB 늘립니다. + +### 개요 보기 {#overview-view} + +v0.8.0부터 `o`(또는 `Escape`. marp-cli는 두 키 모두로 전환합니다), 컨트롤러의 +격자 버튼, 발표자 보기의 페이지 카운터를 누르면 모든 슬라이드가 페이지 위의 +격자에 온전히 표시됩니다. 화살표 키로 슬라이드를 고르고(위아래는 행 단위), +`Home`과 `End`로 처음과 끝으로 가며, 다른 창도 따라 움직입니다. `Enter`, +`Space`, 클릭으로 슬라이드를 선택하면 페이지 위에서는 개요가 닫힙니다. 발표자 +보기 위에서는 선택해도 개요가 열린 채로 남습니다(발표자가 보여 줄 슬라이드를 +고른 뒤에도 계속 살펴볼 수 있도록). "Close overview" 버튼, `o`, `Escape`로 +닫습니다. 이 기능도 marp-cli와 같습니다. 같은 페이지를 다시 +`?view=overview&sync=KEY` 프레임에 띄우고 `localStorage`로 위치를 공유하며, +네트워크를 쓰지 않습니다. 격자 안의 슬라이드는 포커스를 받을 수 있고 "Slide N" +레이블이 붙으며, 닫힌 개요는 비활성(inert) 상태이므로 키보드나 화면 낭독기 +사용자가 그 안으로 들어가는 일은 없습니다. 두 보기를 합치면 페이지 크기가 약 +15 KB 늘어납니다. + +### 설정 파일 {#configuration-files} + +marp-cli는 작업 디렉터리의 파일에서 옵션을 읽으며, `slides`도 그 파일이 데이터일 +때 똑같이 읽습니다: `.marprc`(YAML. JSON도 YAML입니다), `.marprc.json`, +`.marprc.yaml`, `.marprc.yml`, `.config/marprc*`, 또는 `package.json`의 `marp` +키. `--config-file`(`-c`)로 임의의 JSON 또는 YAML 파일(예: `marp.config.json`)을 +지정할 수 있습니다. marp-cli의 검색과 마찬가지로 작업 디렉터리만 찾고 상위 +디렉터리는 찾지 않습니다. JavaScript나 TypeScript 파일(`marp.config.js` ...)은 +프로그램이므로 `slides`는 실행하지 않으며, 그 사실을 알리고 계속 진행합니다. +옵션은 marp-cli 문서에 있는 것 중 여기서 의미가 있는 것들입니다: +`title`, `description`, `author`, `keywords`, `url`, `ogImage`, `lang`, +`theme`, `themeSet`, `html`, `allowLocalFiles`, `template`, `bespoke`, +`inputDir`, `output`, `parallel`, `pdf`, `pptx`, `notes`, `pdfNotes`, +`pdfOutlines`, `images`, `image`, `imageScale`, `jpegQuality`, `watch`, +`server`, `preview`. 경로는 파일 기준 상대 경로이며, 명령줄이 우선합니다. +`engine`, `options`, `browser*`는 Marp 엔진과 브라우저가 필요하므로 +`--browser*` 플래그와 마찬가지로 경고와 함께 무시됩니다. 따라서 marp-cli용으로 +작성한 스크립트도 계속 동작합니다. (`slides.yaml`과 `slides.hcl`은 kind, +템플릿 데이터와 함께 별개이며 이전과 똑같이 읽습니다.) + +## 감시, 서버, 미리 보기 {#watch-serve-and-preview} + +v0.6.0부터 marp-cli의 경로와 실시간 새로 고침 프로토콜을 지원합니다(marp-cli +4.5.1 소스에서 확인하고 실행 중인 `marp -s`와 비교했습니다). + +**`slides --server dir`**(`-s`)는 디렉터리를 `http://127.0.0.1:8080/`에서 +제공합니다(`PORT` 환경 변수로 포트를 바꿀 수 있으며, 이미 사용 중인 포트는 그 +사실을 알리는 오류가 됩니다). + +| 요청 | 응답 | +| --- | --- | +| `/deck.md` | 요청 시 변환한 HTML 프레젠테이션 | +| `/deck.md?pdf`, `?pptx`, `?png`, `?jpg` (또는 `?jpeg`), `?notes` (또는 `?txt`) | 쿼리가 요청한 형식으로 변환한 프레젠테이션. PPTX는 첨부 파일로 제공됩니다 | +| 디렉터리 | 그 안의 `index.md`나 `PITCHME.md`를 페이지로, 없으면 프레젠테이션마다 PDF와 PPTX 링크가 있는 색인 | +| 그 밖의 파일 | 파일 그대로 | +| 변환되지 않는 프레젠테이션 | 오류와 함께 503 | + +페이지는 `/.__marp-cli-watch-notifier__/<id>`로 WebSocket을 엽니다. id는 +프레젠테이션 경로의 SHA-256입니다. 서버는 `ready`를 보낸 다음, 프레젠테이션이나 +디렉터리 아래의 무언가가 바뀌면 `reload`를 보냅니다. 끝의 슬래시 없이 요청한 +디렉터리는 슬래시가 붙은 경로로 리디렉션됩니다(marp-cli는 그 경로에서 색인을 +제공하므로 페이지의 상대 링크가 깨집니다). + +**`--watch`**(`-w`)는 프레젠테이션을 빌드한 뒤 바뀐 프레젠테이션을 다시 +빌드하고, `--theme-set` 파일, kind 파일이나 데이터 파일, 설정이 바뀌면 전체를 다시 +빌드합니다. 이렇게 쓴 페이지는 루프백에 소켓(포트 37717 또는 그다음 빈 포트)을 +열고, 해당 프레젠테이션이 다시 빌드되면 새로 고침합니다. 빌드에 실패한 +프레젠테이션은 보고된 뒤에도 계속 감시됩니다. **`--preview`**(`-p`)는 기본 +브라우저로 서버나 방금 빌드한 HTML 페이지를 엽니다. `slides`에는 구동할 +브라우저가 없으므로 자체 창은 없습니다. + +서버는 컴퓨터로 들어오는 통로이므로, 모든 인터페이스에서 수신하고 아무것도 +검사하지 않는 marp-cli보다 엄격합니다. + +- **127.0.0.1**에서 수신합니다. `--host`로 바꿀 수 있지만 경고가 나오며, + 와일드카드 주소에서는 더 이상 `Host` 헤더를 검사할 수 없습니다. +- **`Host`** 헤더는 루프백 이름(`localhost`, `127.0.0.1`, `[::1]`, + `*.localhost`)이거나 서버가 수신하는 호스트여야 하며, 이것이 DNS 리바인딩을 + 막습니다. +- WebSocket의 **`Origin`**은 서버 자신의 출처여야 합니다. 감시 모드의 소켓은 + 파일에서 연 페이지의 `null` 출처도 받습니다. 다른 사이트에서 온 요청 중 + 탐색이 아닌 것(스크립트, 이미지, `fetch`)은 거부됩니다. +- 경로는 정규화하고 심볼릭 링크를 해석하며, 그 결과가 디렉터리 안에 있어야 + 합니다. `..`와 디렉터리 밖을 가리키는 링크는 거부되며, 밖에 있는 + 프레젠테이션의 `?pdf`도 거부됩니다. 점으로 시작하는 이름(`.git`, `.env`)은 + 제공하지 않습니다. +- `GET`과 `HEAD`만 받습니다. 동시 변환 수는 몇 개로 제한되고, 감시하는 + 디렉터리는 최대 4096개이며 `node_modules` 안이나 점으로 시작하는 디렉터리 + 아래는 감시하지 않습니다. + +## 페이지 메타데이터 {#page-metadata} + +marp-cli와 마찬가지로 명령줄이 프레젠테이션보다 우선합니다. marp-cli에서는 +플래그가 마지막 디렉티브 주석 하나로 프레젠테이션 끝에 덧붙여집니다. 규칙은 +marp-cli 4.5.1에서 확인했으며 그 출력과 비교하는 테스트로 고정되어 있습니다 +(v0.6.0부터). + +- `title`을 지정하지 않은 프레젠테이션은 가장 낮은 수준의 첫 제목 텍스트를 쓴 + 그대로 가져옵니다(`# **Bold** title`이면 `**Bold** title`). 제목이 없으면 + `<title>`도 없습니다. +- `keywords`는 쉼표로 나누고 앞뒤 공백을 없앤 뒤 중복을 제거합니다. 프런트 매터의 + 목록은 그대로 받아들입니다. marp-cli와 마찬가지로 키워드가 두 개 이상일 때만 + 페이지에 `keywords` 태그가 들어가며, 하나뿐이면 들어가지 않습니다. +- 유효하지 않은(절대 경로가 아닌) `url`은 경고와 함께 무시되고 이전 값이 + 유지됩니다. 유효한 값은 `<link rel="canonical">`과 `og:url`도 씁니다. +- `author`와 `description`에는 대응하는 Open Graph 태그가 함께 붙습니다. + `og:image:alt`와 큰 Twitter 카드에는 제목과 이미지가 모두 필요합니다. + +## 이미지, PDF, PPTX, ODP {#images-pdf-pptx-and-odp} + +marp-cli와 마찬가지로 `-o`에 `.pdf`, `.pptx`, `.odp`, `.png`, `.jpg` 확장자를 +쓰면 그에 맞는 형식이 선택됩니다. 출력 디렉터리는 자동으로 만들어집니다. PDF는 +슬라이드 크기의 페이지가 슬라이드당 하나씩 있고, 슬라이드와 그 제목으로 이루어진 +개요가 들어갑니다. + +### PDF 노트와 개요 {#pdf-notes-and-outline} + +v0.6.0부터 PDF는 marp-cli의 두 옵션을 지원하며, 같은 프레젠테이션에 대해 +marp-cli 4.5.1이 쓴 PDF와 비교했습니다. + +- `--pdf-notes`는 각 슬라이드의 발표자 노트를 해당 페이지에 노트 주석으로 + 넣습니다. 왼쪽 아래에 노란 노트 아이콘이 표시되며, 내용은 노트(주석이 여러 + 개면 빈 줄로 연결)이고 제목은 프레젠테이션의 `author`입니다. marp-cli와 + 마찬가지로 `--pdf-notes`만 지정해도 PDF를 만듭니다. +- 개요에는 슬라이드마다 굵은 글씨의 "Page N" 책갈피가 있고, 그 아래에 슬라이드의 + 제목이 수준별로 중첩됩니다(`--pdf-outlines.pages`와 + `--pdf-outlines.headings`로 각각 선택). **개요는 여기서는 기본적으로 켜져 있고 + marp-cli에서는 꺼져 있습니다.** `slides`는 항상 개요를 써 왔습니다. 몇백 + 바이트밖에 들지 않고 긴 프레젠테이션을 쉽게 탐색할 수 있게 해 주므로 기본값을 + 유지합니다. `--pdf-outlines=false`나 marp-cli의 `--no-pdf-outlines`로 끄며, + `.pages`나 `.headings`에 `=false`를 쓰면 해당 부분만 뺍니다. 제목 책갈피는 + 해당 슬라이드로 이동합니다. marp-cli는 제목 위치까지 스크롤합니다. + +렌더링은 [go-webengine](https://github.com/go-webengine/engine)이 하고 PDF는 +[go-pdfkit](https://github.com/go-pdfkit/pdfkit)이 씁니다. 기본 제공 테마가 +요구하는 글꼴(Lato, Roboto Mono)은 +[go-opentype/fonts](https://github.com/go-opentype/fonts)에서 가져와 내장하므로, +렌더링 중 네트워크에 접근하는 일이 없습니다. Chrome은 쓰지 않습니다. + +`--image-scale 2`는 CSS 픽셀당 장치 픽셀 2개로 렌더링합니다. 로컬 이미지는 +`--allow-local-files`를 지정하지 않으면 거부되며, 지정하더라도 프레젠테이션 +디렉터리 아래의 파일만 읽습니다. 따라서 다른 곳에서 받은 프레젠테이션이 +`~/.ssh`에 접근할 수 없습니다. 여러 장의 배경과 화면을 나누는 배경 +(`![bg left:40%]`)은 Marp의 레이어와 똑같이 렌더링됩니다. + +`--pptx`는 [go-ooxml](https://github.com/go-ooxml)의 `WritePresentation`으로 +발표자 노트와 프레젠테이션 메타데이터를 포함해 씁니다. v0.5.0부터 슬라이드는 +편집할 수 있습니다(아래 참고). `--pptx-raster`는 marp-cli처럼 슬라이드마다 +슬라이드 전체 크기 그림 한 장을 쓰며, 배율 기본값은 2이고 `--image-scale`로 +바꿀 수 있습니다. 결과는 LibreOffice에서 열립니다. LibreOffice에서만 +테스트했으며 PowerPoint와 Keynote에서는 테스트하지 않았습니다. + +`--odp`는 go-odf의 `WritePresentation`으로 발표자 노트를 포함한 OpenDocument +프레젠테이션을 씁니다. `--odp-raster`는 슬라이드당 그림 한 장을 씁니다. + +### 편집 가능한 PPTX와 ODP {#editable-pptx-and-odp} + +v0.5.0부터 PPTX와 ODP의 슬라이드는 **편집할 수 있습니다**. marp-cli의 PPTX는 +슬라이드당 그림 한 장이며, 실험적 기능인 `--pptx-editable`은 PDF를 LibreOffice로 +변환합니다. 여기서는 각 슬라이드를 PNG와 PDF 내보내기가 그리는 것과 같은 +레이아웃에서 그립니다. + +- 텍스트의 각 줄은 레이아웃된 위치에 놓인 텍스트 상자이며, 슬라이드 레이아웃에 + 쓰인 글꼴을 씁니다. 글꼴은 **파일에 내장되므로** Lato나 Inter가 없는 + 컴퓨터에서도 똑같이 보이며(LibreOffice 25.8로 확인), 굵게, 기울임, 색, 자간, + 클릭 가능한 링크도 유지됩니다. +- 배경, 테두리, 둥근 상자는 도형입니다. 그레이디언트는 PPTX에서는 그레이디언트 + 채우기, ODP에서는 그림입니다(ODF 그레이디언트는 색이 두 개뿐입니다). +- 그림은 여러 슬라이드에 나오더라도 한 번만 저장됩니다. +- 슬라이드로 표현할 수 없는 것(`opacity`가 적용된 요소, `filter`, 흐린 그림자, + 수식)은 해당 요소의 그림이 되며, 그 위에 텍스트가 유지됩니다. + +LibreOffice로 다시 렌더링하면 슬라이드는 PNG 내보내기와 픽셀 기준 0.04–2 % +이내로 일치합니다. `--pptx-raster`와 `--odp-raster`는 슬라이드당 그림 한 장을 +유지합니다. + +### 벡터 PDF {#vector-pdf} + +v0.4.0부터 PDF는 벡터로 그려집니다. go-webengine이 레이아웃된 슬라이드를 픽셀 +대신 PDF에 직접 그립니다. 텍스트는 슬라이드 레이아웃에 쓰인 글꼴의 텍스트로, +`/ToUnicode` 맵과 함께 서브셋으로 내장되므로 선택, 검색, 복사할 수 있습니다. +배경, 테두리, 그레이디언트는 패스와 셰이딩이며, 여러 슬라이드에 나오는 이미지는 +한 번만 내장되고 링크는 클릭할 수 있습니다. 슬라이드 100장 벤치마크 +프레젠테이션은 758 KiB로, marp-cli의 837 KiB와 v0.3.0 이미지 PDF의 10 MiB보다 +작으며 0.3 s 만에 작성됩니다. + +여기서 벡터로 표현할 수 없는 요소(`opacity` < 1이 적용된 요소, `filter`나 +회전이 있는 요소, 흐린 `box-shadow`, 불투명도가 점점 옅어지는 그레이디언트)는 +해당 요소만 비트맵으로 그리고, 그 위에 텍스트를 보이지 않게 겹쳐 선택할 수 있게 +합니다. 수식은 아직 비트맵입니다. `--pdf-raster`는 v0.3.0처럼 페이지마다 이미지 +한 장을 씁니다. + +## `slides.yaml` {#slidesyaml} + +프레젠테이션 옆의 설정 파일이 그 프레젠테이션에 적용되므로, 한 폴더의 +프레젠테이션들이 설정 파일 하나를 공유합니다. + +```yaml +kinds: kinds.yaml # a mapping of kinds, or file(s) holding one +themes: # theme CSS files or directories + - themes/ +data: # template data + company: Example Inc. +html: false # allow all raw HTML +lang: en +``` + +kind: 프레젠테이션 자체의 `kinds:`가 설정 파일과 명령줄의 kind보다 우선합니다. +데이터: 설정 파일과 명령줄의 `data`가 프레젠테이션의 프런트 매터보다 우선하므로, +같은 프레젠테이션을 서로 다른 데이터로 빌드할 수 있습니다. + +## 계획된 기능 {#planned} + +아직 구현되지 않은 기능은 PDF 안의 수식을 벡터로 출력하는 것입니다. 구현되면 +이 페이지에 명령이나 플래그로 추가됩니다. diff --git a/content/ko/emoji.md b/content/ko/emoji.md new file mode 100644 index 0000000..11537e8 --- /dev/null +++ b/content/ko/emoji.md @@ -0,0 +1,80 @@ +--- +title: "이모지" +weight: 36 +description: "이모지 단축 코드와 Unicode 이모지가 네트워크 없이 Twemoji 이미지로 바뀝니다. HTML 페이지, 이미지, PDF, PowerPoint, OpenDocument 모두에 적용됩니다." +tags: [emoji] +--- + +{{< callout type="warning" >}} +이모지는 v0.4.0에 추가되었습니다. v0.3.0에는 없었습니다. +{{< /callout >}} + +이모지는 단축 코드로 쓰거나 문자 자체로 쓸 수 있습니다. + +```markdown +# Launch :rocket: 🎉 + +Shortcodes :smile: :+1: :heart_eyes: and Unicode 😄 👍🏽 👩‍💻 🇫🇷 1️⃣. +``` + +각 이모지는 Marp Core가 그리는 [Twemoji](https://github.com/jdecked/twemoji) +이미지가 되며, 마크업도 Marp Core가 쓰는 것과 같습니다. + +```html +<img class="emoji" draggable="false" alt="😄" src="data:image/svg+xml;base64,…" data-marp-twemoji=""/> +``` + +## 네트워크 불필요 {#no-network} + +Marp Core의 `src`는 CDN의 URL이므로, 오프라인에서 보여 주거나 인쇄하거나 +인터넷 연결이 없는 컴퓨터에서 PDF로 만든 프레젠테이션에는 깨진 이미지가 +나타납니다. go-slides는 SVG 자체를 `data:` URI에 넣습니다. 그래픽 4,009개 +(Twemoji 17.0.3, Emoji 17.0)는 순수 Go 모듈 +[go-icons/twemoji](https://github.com/go-icons/twemoji)를 통해 프로그램에 +내장되어 있으며, 압축 시 2 MB이고 프레젠테이션에 이모지가 있을 때만 읽습니다. + +이미지는 HTML 페이지, `--images`/`--image`, `--pdf`, `--pptx`, `--odp` 모두에서 +똑같이 그려지며, SVG 래스터화는 go-webengine이 맡습니다. 그래픽 4,009개 모두가 +이 SVG 래스터라이저에서 파싱되고 그려지는지 테스트로 확인합니다. v0.5.0부터 +래스터라이저는 [go-gfx/oksvg](https://github.com/go-gfx/oksvg) v0.2.0입니다. +이제 `clipPath`, `fill-rule: evenodd`, 원호 명령이 Chrome과 똑같이 그려지며, +이로 인해 Twemoji 하나와 그 밖의 211개가 달라졌고 SVG 로고도 더 이상 깨지지 +않습니다. + +이모지 크기를 텍스트에 맞추는 규칙(`img[data-marp-twemoji]`, 한 변이 `em` +하나인 정사각형, 기준선에 정렬)은 Marp Core와 같은 방식으로 테마에 추가되므로, +직접 만든 테마에도 적용됩니다. + +## 변환 대상 {#what-is-converted} + +규칙은 Marp Core의 것을 따르며, 모든 경우를 담은 프레젠테이션에 대해 Marp Core +4.4.0이 출력하는 HTML과 비교하는 테스트가 있습니다. + +| | | +| --- | --- | +| `:smile:`, `:+1:`, `:heart_eyes:` | markdown-it-emoji의 이름 1,903개. 본문, 제목, 링크, 표, `header`, `footer`에서 변환되며 코드, URL, 이미지 대체 텍스트에서는 변환되지 않습니다 | +| `😄`, `👩‍💻`, `🇫🇷`, `1️⃣`, `👍🏽` | Unicode 이모지. 본문 **그리고 코드에서도** 변환됩니다. Marp Core가 렌더링된 코드 HTML을 다시 쓰기 때문입니다 | +| `✔`, `©`, `™`, `▶` | 텍스트로 남습니다. 기본 표시 형태가 텍스트인 문자는 U+FE0F가 붙을 때만(`✔️`) 이모지가 됩니다. `❤`는 예외입니다 | +| `☺︎` | 텍스트로 남습니다. 이모지 뒤의 U+FE0E는 일반 글리프를 요청합니다 | +| `12:30:45`, `:not_a_name:` | 쓴 그대로 남습니다 | + +제목의 `id`와 개요에는 단축 코드 이름이 아니라 이모지가 쓰입니다. +`# Launch :rocket:`의 id는 Marp와 마찬가지로 `launch-%F0%9F%9A%80`입니다. + +go-slides가 Marp Core와 다른 점은 더 많이 변환한다는 것입니다. Marp Core의 +emoji-regex가 텍스트로 남겨 두는 `✌️ ☝️ ✍️ 🏋️` 등 기본 표시가 텍스트인 몇몇 +문자와, Unicode 목록에는 없지만 Twemoji가 그리는 몇 가지 시퀀스도 변환합니다. + +## 끄기 {#turning-it-off} + +`--no-emoji`(코드에서는 `slides.Config{NoEmoji: true}`)를 쓰면 단축 코드와 +Unicode 이모지가 쓴 그대로의 텍스트로 남습니다. Marp Core의 +`emoji: false`에 해당합니다. + +## 저작자 표시 {#attribution} + +그래픽은 Twemoji이며 Copyright 2020 Twitter, Inc and other contributors, +[CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) 라이선스로 배포되고 +[jdecked/twemoji](https://github.com/jdecked/twemoji) 프로젝트가 이어 가고 +있습니다. 이를 표시하는 프레젠테이션은 푸터나 마지막 슬라이드 등에 출처를 +밝혀야 합니다. 단축 코드 표는 markdown-it-emoji(MIT)의 것입니다. diff --git a/content/ko/gallery.md b/content/ko/gallery.md new file mode 100644 index 0000000..401f605 --- /dev/null +++ b/content/ko/gallery.md @@ -0,0 +1,25 @@ +--- +title: "갤러리" +weight: 12 +description: "각각 한 가지를 잘 보여 주는 작은 프레젠테이션 12개: 테마, kind, 템플릿, HCL, 수식, 이미지, 코드, 이모지, 전환 효과, 그리고 완성된 발표 하나. 어느 것이든 Playground에서 열 수 있습니다." +tags: [gallery, examples] +--- + +go-slides의 +[`examples/`](https://github.com/go-slides/slides/tree/main/examples) 폴더에 있는 +작은 프레젠테이션 12개로, 각각 한 가지를 잘 보여 줍니다. 미리 보기 이미지는 각 +프레젠테이션의 첫 슬라이드이며, 이 사이트를 빌드할 때 go-slides가 직접 +렌더링합니다. CI가 모든 프레젠테이션을 HTML과 PDF로 빌드하므로 어느 것도 깨지지 +않습니다. + +**Playground에서 열기**를 누르면 브라우저에서 프레젠테이션을 불러오며, 이 링크는 +어떤 서버에도 데이터를 보내지 않습니다. `deck.md` 외에 다른 파일(kind 파일, +포함된 부분, 이미지)이 있는 프레젠테이션은 링크에 담을 수 없습니다. 이 경우 +**.zip**을 내려받아 Playground의 파일 패널로 가져오거나, 압축을 풀고 +`slides build --allow-local-files deck.md`로 빌드하십시오. + +그림은 go-slides 작성자들이 직접 만든 SVG로 +[CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/)으로 공개되어 있으며, +이를 사용하는 프레젠테이션에는 그 사실이 적혀 있습니다. + +{{< gallery >}} diff --git a/content/ko/getting-started.md b/content/ko/getting-started.md new file mode 100644 index 0000000..983e2c7 --- /dev/null +++ b/content/ko/getting-started.md @@ -0,0 +1,129 @@ +--- +title: "시작하기" +weight: 10 +description: "첫 프레젠테이션을 HTML로 빌드하고 다시 Marp Markdown으로 펼쳐 봅니다." +tags: [start] +--- + +## 설치 {#install} + +Go 1.27.2 이상이 필요합니다. + +```sh +go install github.com/go-slides/slides/cmd/slides@latest +``` + +`slides`는 cgo를 쓰지 않는 정적 Go 바이너리 하나입니다. Node도 Chrome도 필요 +없고, 따로 설치할 것이 없습니다. 이 페이지는 v0.9.0을 기준으로 설명합니다. + +## 첫 프레젠테이션 {#a-first-deck} + +Marp 프레젠테이션이라면 무엇이든 그대로 쓸 수 있습니다. 다음 내용을 `talk.md`로 +저장하십시오. + +```markdown +--- +marp: true +theme: default +paginate: true +footer: "{{ .talk }}" +data: + talk: Pure Go slides + topics: [Why, How, Next] +--- + +<!-- _kind: title --> +# {{ .talk }} + +{{ range .topics }} +--- + +# {{ . }} +{{ end }} +``` + +프런트 매터는 Marp의 것에 `data:`가 더해진 것입니다. 본문은 Go 템플릿입니다. +`range`는 주제마다 `---`로 시작하는 슬라이드를 하나씩 만들고, 첫 슬라이드는 +기본 제공 kind(슬라이드 유형)인 `title`(헤더, 푸터, 페이지 번호 없음)입니다. + +## 빌드하기 {#build-it} + +```sh +slides talk.md # writes talk.html beside the deck +slides -o slides.html talk.md # or somewhere else; -o - for standard output +``` + +명령 없이 실행한 `slides`는 `slides build`와 같습니다. 플래그는 프레젠테이션 +앞에 와도, 뒤에 와도 됩니다. HTML을 브라우저에서 열면 키보드로 슬라이드를 +넘길 수 있습니다. + +## 이미지, PDF, PPTX, ODP로 내보내기 {#export-images-pdf-pptx-and-odp} + +```sh +slides talk.md --pdf # one page per slide, with an outline +slides talk.md --images png # talk.001.png, talk.002.png, … +slides talk.md --image jpeg # the first slide only +slides talk.md -o out/slides.pdf # the extension picks the format; out/ is created +slides talk.md --images jpeg --jpeg-quality 90 +slides talk.md --images png --image-scale 2 # 2560×1440 for 16:9 +slides talk.md --pdf --allow-local-files # images from under the deck's directory +slides talk.md --pptx # or -o talk.pptx, with notes +slides talk.md --odp # or -o talk.odp: the same, as OpenDocument +``` + +브라우저는 전혀 쓰이지 않습니다. 슬라이드는 go-webengine이 렌더링하고 PDF는 +go-pdfkit이 작성합니다. 로컬 이미지는 `--allow-local-files`를 지정하지 않으면 +거부됩니다. v0.5.0부터 PPTX와 ODP의 슬라이드는 텍스트 상자, 도형, 그림, 내장 +글꼴로 이루어져 편집할 수 있습니다(marp-cli는 그림입니다). 그림으로 유지하려면 +`--pptx-raster`를 쓰십시오([편집 가능한 PPTX와 ODP]({{< relref "cli#editable-pptx-and-odp" >}})). v0.4.0부터 PDF는 벡터이며 +텍스트를 선택할 수 있습니다(v0.3.0은 페이지마다 이미지 한 장을 썼습니다). [명령줄]({{< relref "cli#vector-pdf" >}})을 참고하십시오. + +## 펼치기 {#expand-it} + +```sh +slides expand talk.md +``` + +이 명령은 같은 프레젠테이션을 **일반 Marp Markdown**으로 출력합니다 +(`4ae5c49` 시점, 2026-10-18의 `main` 출력). + +```markdown +--- +marp: true +theme: default +paginate: true +footer: "Pure Go slides" +--- +<!-- +_class: "lead" +_footer: "" +_paginate: "false" +--> + + +# Pure Go slides + + +--- + +# Why + +--- + +# How + +--- + +# Next +``` + +`range`가 생성한 슬라이드 네 장, 값이 채워진 푸터, 그리고 `title` kind 대신 +그에 해당하는 스폿 디렉티브가 보입니다. marp-cli나 VS Code 확장처럼 Marp를 +읽는 도구라면 무엇이든 이어서 처리할 수 있으므로, go-slides의 추가 기능으로 +작성한 내용이 특정 도구에 묶이지 않습니다. + +## 다음 단계 {#next} + +- [Marp 호환성]({{< relref "marp" >}}): Marp와 똑같이 읽는 부분. +- [템플릿]({{< relref "templates" >}})과 [Kind]({{< relref "kinds" >}}): 두 가지 추가 기능. +- [명령줄]({{< relref "cli" >}}): 모든 명령과 플래그. diff --git a/content/ko/hcl.md b/content/ko/hcl.md new file mode 100644 index 0000000..054e216 --- /dev/null +++ b/content/ko/hcl.md @@ -0,0 +1,154 @@ +--- +title: "HCL" +weight: 45 +description: "프레젠테이션이 YAML을 받는 모든 곳에서 HCL2를 쓸 수 있습니다: HCL 프런트 매터, slides.hcl, kind와 데이터 파일, 표현식과 함수." +tags: [hcl, configuration] +--- + +{{< callout type="warning" >}} +HCL은 v0.3.0에 추가되었습니다([go-slides/slides#13](https://github.com/go-slides/slides/pull/13)). +{{< /callout >}} + +`slides`는 YAML을 읽는 모든 곳에서 +[HCL2](https://github.com/hashicorp/hcl/blob/main/hclsyntax/spec.md)의 네이티브 +문법도 읽습니다. 표현식과 함수를 쓸 수 있기 때문입니다. + +| 위치 | YAML | HCL | +| --- | --- | --- | +| 프런트 매터 | `---` … `---` | `---hcl` … `---` | +| 설정 | `slides.yaml` | `slides.hcl`, 먼저 찾음 | +| kind 파일 | `--kinds kinds.yaml`, `kinds: kinds.yaml` | 이름이 `*.hcl`인 파일 | +| 데이터 파일 | `--data data.yaml` 또는 `.json` | 이름이 `*.hcl`인 파일 | + +디렉티브 주석(`<!-- class: invert -->`)은 Marp와 마찬가지로 YAML로 남고, +프레젠테이션 본문은 그대로 [Go 템플릿]({{< relref "templates" >}})입니다. + +## HCL로 쓴 프런트 매터 {#a-front-matter-in-hcl} + +```hcl +---hcl +theme = "gaia" +paginate = true +title = "Quarterly review" +data = { + company = "ACME" + quarter = 3 +} +header = format("%s — Q%d", data.company, data.quarter) +footer = "${upper(data.company)} · ${page()}/${total()}" +date = formatdate("D MMMM YYYY", now()) + +kind "chapter" { + extends = "section" + class = ["lead", "invert"] +} +--- + +# {{ .title }} +``` + +- **속성**은 같은 이름의 YAML 키에 해당합니다. Marp 디렉티브(`theme`, + `paginate`, `header`, `footer`, `class`, `backgroundColor`…), 메타데이터 + (`title`, `author`…), `template`, `delims`, `kinds`(파일 경로), 그리고 + 객체인 `data`가 있습니다. +- **`kind "<name>" { … }`** 블록은 로컬 디렉티브와 `extends`로 + [kind(슬라이드 유형)]({{< relref "kinds" >}})를 정의합니다. +- **값**은 YAML이 주는 것과 같은 문자열 형태로 디렉티브에 전달됩니다. + `true`는 `"true"`가 되고 클래스 목록은 목록이 되므로, 어느 문법으로 설정했든 + 디렉티브는 똑같이 동작합니다. `null`은 디렉티브를 설정하지 않은 상태로 둡니다. +- **`data`**는 타입을 유지합니다. 템플릿에서 정수는 `int`, 그 밖의 수는 + `float64`가 되고, 목록과 객체는 목록과 맵으로 남으므로 + `{{ add .quarter 1 }}`이 동작합니다. +- HCL 값은 최종 값입니다. Go 템플릿이 그 위에서 다시 실행되지 않습니다. + +## 변수 {#variables} + +`data`는 템플릿 데이터입니다. 프런트 매터 자체의 `data` 위에 +`slides.hcl`의 데이터, `--data` 파일, `--set`이 차례로 덮어씁니다. +`{{ .company }}`를 쓸 수 있는 곳이면 어디서나 `"${data.company}"`를 쓸 수 +있습니다. + +프런트 매터 속성은 `data.*`를 참조할 수 있지만 **서로를 참조할 수는 없습니다**. +`footer = title`은 오류입니다. 스폿 디렉티브(`_class`…)는 HCL 식별자가 +아니므로 주석으로 쓰십시오. + +## 함수 {#functions} + +| | | +| --- | --- | +| `page()`, `total()` | `{{ page }}`, `{{ total }}`과 같은 페이지 번호와 마지막 페이지 번호. 푸터에 페이지를 매길 수 있습니다 | +| `now()`, `timestamp()` | 현재 시각(RFC 3339, 로컬 시각과 UTC). `formatdate`로 서식을 지정합니다 | +| `env(name)` | 환경 변수. 설정되지 않았으면 `""` | +| `file(path)` | 프레젠테이션이나 설정 파일 기준 상대 경로의 UTF-8 텍스트 파일(최대 1 MiB) | +| 숫자 | `abs ceil floor log max min parseint pow signum` | +| 문자열 | `chomp format formatlist join lower regex regexall regexreplace replace split strlen strrev substr title trim trimprefix trimspace trimsuffix upper` | +| 컬렉션 | `chunklist coalesce coalescelist compact concat contains distinct element flatten keys length lookup merge range reverse slice sort values zipmap` | +| 인코딩 | `csvdecode jsondecode jsonencode` | +| 날짜 | `formatdate timeadd` | + +처음 네 행을 제외한 나머지는 모두 [go-cty](https://github.com/zclconf/go-cty)의 +표준 라이브러리입니다. 어느 함수도 네트워크에 접근하지 않습니다. + +## slides.hcl {#slideshcl} + +```hcl +kinds = "kinds.hcl" # or a list of files +themes = ["themes/acme.css"] +data = { company = "ACME" } # or file paths +html = false +lang = "en" + +kind "quote" { + extends = "blank" + footer = "${data.company}" +} +``` + +알 수 없는 속성은 조용히 무시되지 않고 오류가 됩니다. kind 파일에는 `kind` +블록을 담으며, 그 밖의 속성은 무시되므로 `slides.hcl`을 kind 파일로도 쓸 수 +있습니다. 데이터 파일의 속성은 데이터의 키가 됩니다. + +```hcl +# data.hcl +company = "ACME" +year = 2026 +motto = trimspace(file("motto.txt")) +``` + +## 오류 {#errors} + +오류는 HCL의 진단 메시지이며, 위치는 프레젠테이션 파일 자체를 기준으로 +표시됩니다. 프레젠테이션 넷째 줄의 `footer = data.compnay`는 다음 오류를 +냅니다. + +``` +deck.md:4:14: Unsupported attribute: This object does not have an attribute named "compnay". +``` + +## Marp와 `slides expand` {#marp-and-slides-expand} + +Marp도 `---hcl`을 프런트 매터의 시작으로 읽지만(Marp의 프런트 매터 규칙은 +대시 세 개로 시작하는 첫 줄이면 무엇이든 받아들입니다), 이 블록은 YAML 매핑이 +아니므로 아무것도 적용하지 않습니다. 따라서 프레젠테이션은 Marp에서도 +렌더링되지만 디렉티브는 빠집니다. 이 동작은 Marp Core 자체의 출력과 비교하는 +테스트로 고정되어 있습니다. + +`slides expand`는 프런트 매터를 YAML로 다시 쓰므로, 펼친 프레젠테이션은 일반 +Marp이며 `slides`가 원본을 렌더링한 것과 똑같이 렌더링됩니다. + +```yaml +--- +theme: "gaia" +paginate: true +title: "Quarterly review" +header: "ACME — Q3" +footer: "ACME · 1/2" +date: "19 October 2026" +--- +``` + +## 라이선스 {#licences} + +HCL 지원에는 [hashicorp/hcl](https://github.com/hashicorp/hcl) +(MPL-2.0, 수정 없이 라이브러리로 사용)과 +[zclconf/go-cty](https://github.com/zclconf/go-cty)(MIT)가 링크됩니다. diff --git a/content/ko/kinds.md b/content/ko/kinds.md new file mode 100644 index 0000000..3b8554c --- /dev/null +++ b/content/ko/kinds.md @@ -0,0 +1,91 @@ +--- +title: "Kind" +weight: 40 +description: "슬라이드가 자신이 무엇인지 밝힙니다. kind는 로컬 디렉티브 프리셋에 붙인 이름입니다." +tags: [kinds] +--- + +**kind**(슬라이드 유형)는 헤더, 푸터, 페이지 번호, 클래스, 배경 같은 로컬 +디렉티브를 묶어 이름을 붙인 프리셋입니다. 슬라이드는 어떻게 보이는지를 +반복하는 대신 자신이 무엇인지(`title`, `section`)를 밝히고, 프레젠테이션의 +모양은 한곳에서 바꿉니다. + +## kind 사용하기 {#using-a-kind} + +```markdown +<!-- kind: section --> +# Part two + +--- + +<!-- _kind: blank --> +![bg](photo.jpg) +``` + +- `kind:`는 로컬 디렉티브처럼 적용됩니다. 즉 이 슬라이드와 그 뒤의 슬라이드에 적용됩니다. +- `_kind:`는 스폿 디렉티브처럼 이 슬라이드에만 적용됩니다. +- kind를 바꾸면 먼저 이전 kind가 설정한 내용을 **지우므로**, 클래스나 푸터가 + 한 kind에서 다음 kind로 새어 나가지 않습니다. +- 슬라이드에 직접 쓴 디렉티브가 kind보다 **우선합니다**. + +## 기본 제공 kind {#built-in} + +| Kind | 설정 내용 | +| --- | --- | +| `title` | `class: lead`, 페이지 번호·헤더·푸터 없음 | +| `section` | `class: lead` | +| `content` | 없음: 프레젠테이션 자체의 기본값 | +| `blank` | 페이지 번호·헤더·푸터 없음 | + +모두 다시 정의할 수 있습니다. + +## kind 정의하기 {#defining-kinds} + +프런트 매터에 정의합니다. + +```yaml +--- +marp: true +kinds: + chapter: + class: invert + backgroundColor: "#1e1b4b" + paginate: false + quote: + extends: blank + class: quote +--- +``` + +`extends`는 다른 kind의 디렉티브에서 출발해 kind를 만듭니다. kind에는 로컬 +디렉티브와 `extends`를 쓸 수 있습니다. + +## kind 공유하기 {#sharing-kinds} + +팀에서 쓰는 kind는 모든 프레젠테이션이 참조하는 파일 하나에 두는 것이 좋습니다. + +```yaml +# kinds.yaml +chapter: + class: invert + paginate: false +``` + +```yaml +--- +marp: true +kinds: kinds.yaml # or a list of files +--- +``` + +명령줄의 `--kinds kinds.yaml`이나 `slides.yaml`의 `kinds:`로도 지정할 수 +있습니다. 적용 순서는 기본 제공 kind, 명령줄과 설정 파일, 그리고 프레젠테이션 +자체의 `kinds:` 순입니다. + +`slides kinds talk.md`는 프레젠테이션에서 쓸 수 있는 kind와 각 kind가 설정하는 +내용을 나열합니다. + +## go-slides 없이 쓰기 {#leaving-go-slides} + +`slides expand`는 슬라이드마다 모든 kind를 그에 해당하는 스폿 디렉티브로 풀어 +쓰므로, 결과는 어떤 Marp 도구로도 읽을 수 있는 일반 Marp Markdown이 됩니다. diff --git a/content/ko/marp.md b/content/ko/marp.md new file mode 100644 index 0000000..f6e72b6 --- /dev/null +++ b/content/ko/marp.md @@ -0,0 +1,81 @@ +--- +title: "Marp 호환성" +linkTitle: "Marp" +weight: 20 +description: "slides가 읽는 Markdown은 Marpit과 Marp Core의 Markdown 그 자체이며, 별도의 방언이 아닙니다." +tags: [marp] +--- + +[Marp](https://marp.app)용으로 작성한 프레젠테이션은 그대로 go-slides +프레젠테이션입니다. 아래 규칙은 [Marpit](https://marpit.marp.app)과 +[Marp Core](https://github.com/marp-team/marp-core)의 규칙이며, go-slides가 +다른 부분은 이 페이지에 따로 밝힙니다. + +## 슬라이드 {#slides} + +수평선, 즉 Marpit이 나누는 방식대로 한 줄에 단독으로 쓴 `---`는 새 슬라이드를 +시작합니다. `headingDivider` 디렉티브가 지정한 수준의 제목도 마찬가지입니다. +처음 두 `---` 사이의 프런트 매터에는 전역 디렉티브와 프레젠테이션 데이터가 +들어갑니다. + +## 디렉티브 {#directives} + +| 종류 | 위치 | 적용 범위 | +| --- | --- | --- | +| 전역 | 프런트 매터, 또는 어디서나 `<!-- theme: gaia -->` | 프레젠테이션 전체: `theme`, `style`, `headingDivider`, `lang`… | +| 로컬 | `<!-- paginate: true -->` | 이 슬라이드와 그 뒤의 슬라이드 | +| 스폿 | `<!-- _class: lead -->` (밑줄) | 이 슬라이드만 | + +로컬 디렉티브는 Marpit의 것입니다: `paginate`, `header`, `footer`, `class`, +`backgroundColor`, `backgroundImage`, `backgroundPosition`, +`backgroundRepeat`, `backgroundSize`, `color`. 디렉티브는 Marp Core처럼 +느슨한 YAML로 파싱하며, `--strict-yaml`을 쓰면 순수 Marpit처럼 파싱합니다. + +## 이미지 {#images} + +`![bg](image.jpg)`는 이미지를 슬라이드 배경으로 만들며, Marp의 키워드를 그대로 +씁니다. `bg contain`, `bg cover`, `bg 50%`, 화면을 나누는 배경을 위한 +`bg left`/`bg right`, 나란히 놓는 여러 장의 `bg` 이미지, 그리고 이미지 필터 +(`blur`, `sepia`, `grayscale`…)를 지원합니다. 인라인 이미지 크기(`w:200px`, +`h:`)도 Marp와 똑같이 읽습니다. + +## 슬라이드 주변 요소 {#around-the-slide} + +- **헤더와 푸터**: Markdown으로 쓰는 `header:`와 `footer:` 디렉티브. +- **페이지 번호**: `paginate: true`. +- **발표자 노트**: 디렉티브가 아닌 HTML 주석은 노트가 됩니다. + `slides notes`로 출력할 수 있습니다. +- **단계별 목록**: `*`(또는 `1)`)로 쓴 목록은 Marp처럼 항목이 하나씩 + 나타납니다. +- **`<style scoped>`**: 한 슬라이드에만 적용되는 CSS. +- **원시 HTML**: 기본값은 Marp Core의 허용 목록이며, `--html`을 쓰면 모두 + 허용합니다. +- **이모지**: `:shortcodes:`와 Unicode 이모지는 Marp Core처럼 Twemoji 이미지가 + 되지만, CDN에서 불러오지 않고 내장됩니다. [이모지]({{< relref "emoji" >}})를 + 참고하십시오. + +## 테마 {#themes} + +`default`, `gaia`, `uncover`가 기본 제공되며 `theme:`로 선택합니다. 직접 만든 +테마는 Marp의 `/* @theme name */` 주석이 들어 있는 CSS 파일이며, +`--theme-set`으로 지정하거나 `slides.yaml`의 `themes:` 아래에 나열합니다. + +## go-slides가 다른 점 {#where-go-slides-differs} + +- **템플릿.** 본문의 `{{ }}`는 Go 템플릿 액션입니다 + ([템플릿]({{< relref "templates" >}}) 참고). 코드 블록 밖에서 `{{`를 + 텍스트로 보여 주려면 `--no-template`을 쓰거나 Go 템플릿 방식대로 이스케이프해야 + 합니다. 코드 블록과 인라인 코드는 건드리지 않습니다. +- **Kind.** `kind:`와 `_kind:`는 Marp에 없는 디렉티브입니다 + ([Kind]({{< relref "kinds" >}}) 참고). `slides expand`가 이를 해당하는 + 디렉티브로 바꾸므로, 펼친 프레젠테이션은 일반 Marp입니다. +- **출력.** HTML, PNG, JPEG, PDF, PPTX, ODP를 헤드리스 브라우저가 아닌 Go + 라이브러리로 만듭니다(알려진 제한 사항은 + [명령줄]({{< relref "cli#images-pdf-pptx-and-odp" >}}) 참고). v0.4.0에서는 + marp-cli처럼 PPTX에 그림이 들어갔지만, v0.5.0부터는 슬라이드가 편집 가능한 + 텍스트 상자와 도형이며 `--pptx-raster`를 쓰면 그림으로 유지됩니다. +- **제목 맞춤.** `# <!-- fit --> Title`을 지원합니다. 제목이 페이지와 모든 + 내보내기 결과에서 한 줄로 슬라이드 너비를 채웁니다. +- **수식과 코드.** `$…$`와 `$$…$$`는 Marp Core의 구분자 규칙을 따르며 + go-tex/math로 조판됩니다. 펜스 코드는 go-rouge가 highlight.js 클래스 이름으로 + 강조 표시합니다([수식과 코드]({{< relref "math-and-code" >}}) 참고). diff --git a/content/ko/math-and-code.md b/content/ko/math-and-code.md new file mode 100644 index 0000000..30f0725 --- /dev/null +++ b/content/ko/math-and-code.md @@ -0,0 +1,140 @@ +--- +title: "수식과 코드" +weight: 35 +description: "달러 기호 사이의 수식은 조판되고 펜스 코드는 강조 표시됩니다. HTML 페이지, 이미지, PDF 모두에 적용됩니다." +tags: [math, code] +--- + +{{< callout type="warning" >}} +수식과 코드는 v0.2.0에 추가되었습니다. +{{< /callout >}} + +## 수식 {#math} + +`$…$`는 인라인 수식이고 `$$…$$`는 디스플레이 수식입니다. + +```markdown +The famous one is $E=mc^2$, and this is a display formula: + +$$ +\int_0^\infty e^{-x^2}\,dx = \frac{\sqrt{\pi}}{2} +$$ +``` + +- 구분자 규칙은 marp-core 4.4.0에서 확인한 Marp Core의 규칙입니다. 뒤에 공백이 + 오는 `$`는 수식을 열지 않고, 뒤에 숫자가 오는 달러 기호는 수식을 닫지 않으므로 + `$5 and $10` 같은 가격은 텍스트로 남습니다. +- [go-tex/math](https://github.com/go-tex/math)는 각 수식을 `em` 단위로 크기가 + 정해진 인라인 SVG로 조판하므로, 수식이 텍스트 크기를 따르고 기준선 위에 + 놓입니다. HTML 페이지, 이미지, PDF에서 같은 SVG를 씁니다. +- 파싱할 수 없는 수식은 쓴 그대로 보이며, 오류는 그 `title`에 담깁니다. + +## 코드 {#code} + +펜스 코드 블록은 [go-rouge](https://github.com/go-rouge/rouge)가 색을 입힙니다. +go-rouge는 Marp 테마가 스타일을 지정하는 highlight.js 클래스 이름을 씁니다. + +````markdown +```go +fmt.Println("hello") +``` +```` + +go-rouge가 모르는 언어는 Marp Core와 마찬가지로 일반 텍스트로 출력합니다. + +### 줄 번호 {#line-numbers} + +v0.8.0부터 블록에 줄 번호를 붙일 수 있습니다. Marp Core에는 줄 번호가 없고 +펜스의 언어 뒤에 오는 내용을 무시하므로, 옵션은 그 자리에 중괄호로 씁니다. +이 옵션을 쓴 프레젠테이션도 Marp에서는 옵션이 없을 때와 똑같이 렌더링됩니다. + +````markdown +```go {lineNumbers} +func main() { + fmt.Println("hello") +} +``` + +```go {lineNumbers=10} +// starts at 10 +``` +```` + +| 표기 | 의미 | +| --- | --- | +| `{lineNumbers}` | 1부터 번호를 매깁니다(Slidev의 `{lines:true}`와 Pandoc의 `{.numberLines}`도 인식합니다) | +| `{lineNumbers=10}` | 10부터 번호를 매깁니다(`startLine=10`, Pandoc의 `startFrom="10"`도 가능) | +| `{lineNumbers=false}` | 슬라이드 설정과 관계없이 번호를 매기지 않습니다 | + +한 슬라이드와 그 뒤 슬라이드의 모든 블록에 적용하려면 디렉티브 +`<!-- lineNumbers: true -->`를 설정합니다(한 슬라이드만이면 `_lineNumbers`). +kind(슬라이드 유형)로 같은 계열의 슬라이드 전체에 한 번에 설정할 수도 있습니다. + +```yaml +kinds: + listing: {lineNumbers: true} +``` + +각 번호는 테마의 코드 상자 안에서 줄 앞에 놓이며 구분선으로 분리됩니다. +콜아웃이나 이모지 때문에 줄이 더 높아져도 번호는 그 줄의 기준선에 머뭅니다. +번호는 코드 색과 배경색을 섞은 흐린 색으로 표시되며, default, gaia, uncover +테마의 밝은 모드와 어두운 모드 모두에서 명암비가 4.5:1 이상입니다. 번호는 +페이지에서 **선택할 수 없고** 화면 낭독기도 읽지 않습니다. PDF에서는 그려지되 +텍스트에서는 제외되므로, 텍스트 추출기는 코드만 읽습니다. PowerPoint나 +OpenDocument 파일에서는 각 줄 앞의 편집 가능한 텍스트가 됩니다. 이 옵션을 +쓰지 않는 프레젠테이션은 바이트 단위까지 이전과 똑같이 렌더링됩니다. 특정 +줄을 강조하는 기능은 아직 지원하지 않습니다. + +### 코드 콜아웃 {#code-callouts} + +v0.8.0부터 코드 줄 끝에 블록 아래의 설명을 가리키는 번호 원을 붙일 수 있습니다. +문법은 [Quarto의 문법](https://quarto.org/docs/authoring/code-annotation.html)으로, +Asciidoctor의 주석 형식이기도 합니다. 표식은 해당 코드 언어의 주석이므로 코드는 +유효한 상태로 유지되며, 설명은 블록 바로 뒤에 오는 번호 목록입니다. + +````markdown +```go +func main() { // <1> + x := load() // <2> + fmt.Println(x) +} +``` + +1. The entry point. +2. Reads the input. +```` + +| 표기 | 의미 | +| --- | --- | +| `// <1>`, `# <1>`, `-- <1>`, `; <1>`, `% <1>`, `' <1>` | 표식만 담은 한 줄 주석: 주석과 표식이 모두 번호 원으로 바뀝니다 | +| `<!-- <1> -->`, `/* <1> */` | 감싸는 형식의 주석을 쓰는 언어에서도 마찬가지입니다 | +| `// <1> <2>` | 한 줄에 번호 원 두 개 | +| `x = 1 # count <1>` | 주석 텍스트는 남고 그 뒤에 번호 원이 붙습니다 | + +표식은 블록 바로 뒤의 목록이 1부터 시작하고 모든 번호에 해당하는 항목이 있을 +때만 번호 원이 됩니다. 그렇지 않으면 블록은 쓴 그대로 남습니다. 번호 원은 +default, gaia, uncover 테마의 밝은 모드와 어두운 모드 모두에서 코드 색의 고리로 +그려집니다. 목록 항목에도 같은 번호 원이 붙습니다. + +번호 원은 **선택할 수 없습니다**. 페이지에서 코드를 복사하면 표식이 없는 코드만 +복사됩니다. PDF에서는 텍스트에서 제외된 벡터이고, PowerPoint나 OpenDocument +파일에서는 편집 가능한 도형입니다. 콜아웃은 줄 번호와 함께 쓸 수 있습니다. +Marp Core에는 콜아웃이 없으므로(marp-core 4.4.0) Marp에서는 주석과 번호 목록이 +쓴 그대로 보입니다. 콜아웃을 쓰지 않는 프레젠테이션은 바이트 단위까지 이전과 +똑같이 렌더링됩니다. + +v0.9.0부터는 HTML 페이지에서 번호 원과 그 설명이 서로 연결됩니다. 번호 원에 +마우스를 올리거나 Tab 키로 이동하면 해당 설명에 색이 입혀지고, 설명에서도 +마찬가지로 번호 원이 강조됩니다. Quarto의 `code-annotations: hover`와 같은 +동작이지만, 여기서는 스크립트 없이 스타일시트만으로 처리합니다. 따라서 둘 다 +스크립트를 제한하는 Playground의 샌드박스 미리 보기와 VS Code 미리 보기에서도 +똑같이 동작합니다. 각 번호 원은 설명으로 기술되므로, 화면 낭독기는 번호 원에 +이르면 설명을 읽습니다. 색 변화는 사용자가 동작 줄이기를 요청하지 않은 경우에만 +서서히 나타납니다. 이미지, PDF, PowerPoint, OpenDocument 파일은 바이트 단위까지 +바뀌지 않습니다. + +## 글꼴 {#fonts} + +Lato Regular, Bold, Black과 이탤릭, 그리고 Roboto Mono Regular, Bold와 +이탤릭이 내장되어 있으므로 gaia의 제목은 제 굵기로 표시되고 이탤릭 텍스트는 +이탤릭으로 렌더링됩니다. diff --git a/content/ko/performance.md b/content/ko/performance.md new file mode 100644 index 0000000..ff59b81 --- /dev/null +++ b/content/ko/performance.md @@ -0,0 +1,89 @@ +--- +title: "성능" +weight: 60 +description: "go-slides가 프레젠테이션을 얼마나 빠르게, 얼마만큼의 메모리로 빌드하는지 같은 컴퓨터에서 marp-cli와 비교해 측정했습니다." +tags: [performance] +--- + +{{< callout type="info" >}} +아래 PPTX 수치는 v0.5.0, 벡터 PDF 수치는 v0.4.0 기준입니다. +첫 번째 표는 이전 릴리스인 v0.3.0 기준이며, 이 버전부터 슬라이드를 동시에 +렌더링하고 글꼴을 한 번만 파싱합니다 +([go-slides/slides#11](https://github.com/go-slides/slides/pull/11)). +{{< /callout >}} + +go-slides와 [marp-cli](https://github.com/marp-team/marp-cli) 4.5.1을 같은 +프레젠테이션에 대해 하위 프로세스로 실행했습니다. Apple M4 Max(CPU 16개)에서 +1분 평균 부하가 8 미만인 상태로, 예열 후 5회씩 번갈아 측정했습니다. 시간은 +경과 시간(wall time)의 중앙값이고, 메모리는 전체 프로세스 트리의 최대값입니다 +(marp-cli는 Chrome을 구동하므로 그 프로세스도 포함됩니다). + +| 프레젠테이션 | 출력 | go-slides | marp-cli | go-slides 메모리 | marp-cli 메모리 | +| --- | --- | ---: | ---: | ---: | ---: | +| 슬라이드 100장 | HTML | 36 ms | 315 ms | 47 MiB | 140 MiB | +| 슬라이드 100장 | PDF | 1.14 s | 1.88 s | 242 MiB | 1,509 MiB | +| 슬라이드 100장 | PNG, 전체 슬라이드 | 0.97 s | 5.44 s | 225 MiB | 1,354 MiB | +| 슬라이드 100장 | PPTX | 3.39 s | 8.03 s | 530 MiB | 1,373 MiB | +| gaia, 슬라이드 20장 | PDF | 283 ms | 1.57 s | 154 MiB | 1,315 MiB | +| 수식과 코드, 슬라이드 20장 | PNG, 전체 슬라이드 | 250 ms | 2.32 s | 168 MiB | 1,286 MiB | + +## 벡터 PDF {#vector-pdf} + +v0.4.0부터 PDF는 벡터로 그려집니다 +([명령줄]({{< relref "cli#vector-pdf" >}}) 참고). 같은 컴퓨터와 같은 방법으로 +2026-10-19에 측정했습니다. + +| 프레젠테이션 | go-slides | marp-cli | v0.3.0 | go-slides 크기 | marp-cli 크기 | v0.3.0 크기 | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 슬라이드 100장 | 306 ms | 1,763 ms | 1,147 ms | 758 KiB | 837 KiB | 10.0 MiB | +| gaia, 슬라이드 20장 | 93 ms | 1,546 ms | 285 ms | 32 KiB | 66 KiB | 582 KiB | +| 수식과 코드, 슬라이드 20장 | 140 ms | 1,643 ms | 294 ms | 124 KiB | 335 KiB | 638 KiB | + +슬라이드 100장 PDF의 메모리는 158 MiB로, marp-cli의 1,512 MiB와 비교됩니다. +CPU 시간은 0.9 s 대 2.7 s입니다. + +슬라이드 100장 PDF의 v0.4.0 수치는 2026-10-19 21:29에 1분 평균 부하 7.7에서 +3회 측정한 것입니다. go-slides는 306 ms, 758 KiB, 158 MiB였고 marp-cli는 +1,838 ms, 837 KiB, 1,512 MiB였습니다. + +## 편집 가능한 PPTX {#editable-pptx} + +v0.5.0부터 PPTX는 텍스트 상자, 도형, 그림으로 이루어집니다 +([명령줄]({{< relref "cli#editable-pptx-and-odp" >}}) 참고). 2026-10-19 22:51에 +1분 평균 부하 7.1에서 3회 측정했습니다. + +| 프레젠테이션 | go-slides | marp-cli | go-slides 크기 | marp-cli 크기 | go-slides 메모리 | marp-cli 메모리 | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 슬라이드 100장 | 348 ms | 7,795 ms | 760 KiB | 7.2 MiB | 204 MiB | 1,370 MiB | +| gaia, 슬라이드 20장 | 112 ms | 4,608 ms | 893 KiB | 4.8 MiB | | | +| 수식과 코드, 슬라이드 20장 | 207 ms | 2,780 ms | 842 KiB | 1.5 MiB | | | + +슬라이드 100장 프레젠테이션에서 v0.4.0은 4.7 s, 4.8 MiB, 540 MiB였습니다. +marp-cli의 PPTX에는 그림이 들어 있습니다. 편집 가능한 프레젠테이션 크기의 +대부분은 내장 글꼴입니다. + +## 빠른 이유 {#why-it-is-fast} + +- **시작할 브라우저가 없습니다.** marp-cli는 PDF, 이미지, PPTX를 내보낼 때마다 + Chrome을 실행하지만, go-slides는 자체 프로세스 안에서 + [go-webengine](https://github.com/go-webengine/engine)으로 렌더링합니다. +- **슬라이드를 병렬로 렌더링합니다.** 기본값은 한 번에 네 장이며, 각 이미지는 + 차례가 오는 즉시 기록되므로 긴 프레젠테이션도 이미지 형태로 메모리에 쌓아 두지 + 않습니다. +- **글꼴은 한 번만 파싱합니다.** 슬라이드마다가 아니라 프로세스당 한 번입니다. + +## 앞서지 못하는 부분 {#what-it-does-not-win} + +- **v0.3.0의 PDF 크기.** PDF 안의 각 슬라이드가 이미지였기 때문에 슬라이드 + 100장 프레젠테이션에서 10 MiB로, marp-cli의 837 KiB보다 컸습니다. 위의 벡터 + PDF는 marp-cli보다 작습니다. +- **큰 프레젠테이션의 CPU 시간.** 워커 네 개는 경과 시간이 보여 주는 것보다 더 + 많은 CPU를 씁니다. v0.3.0에서 슬라이드 100장 PDF는 3.2 s로, marp-cli의 + 2.7 s보다 많았습니다(벡터 PDF는 0.9 s). +- **marp-cli의 메모리는 상한값입니다.** Chrome 프로세스들은 페이지를 공유하는데, + 합계에서는 이를 프로세스마다 중복해 셉니다. 가장 큰 단일 프로세스는 + 220–320 MiB입니다. + +측정 방법, 모든 프레젠테이션과 모드, 수치를 재현하는 명령은 +[BENCHMARKS.md](https://github.com/go-slides/slides/blob/main/BENCHMARKS.md)에 +있습니다. diff --git a/content/ko/playground.md b/content/ko/playground.md new file mode 100644 index 0000000..d33c902 --- /dev/null +++ b/content/ko/playground.md @@ -0,0 +1,166 @@ +--- +title: "Playground" +weight: 15 +description: "브라우저에서 프레젠테이션을 작성하면 슬라이드가 바로 따라옵니다. WebAssembly로 컴파일한 go-slides에 자동 완성, 도움말, 예제를 갖췄습니다." +tags: [playground, browser] +--- + +**[Playground 열기 →](https://go-slides.github.io/playground/)** + +Playground는 WebAssembly로 컴파일되어 브라우저에서 실행되는 go-slides v0.9.0 +그 자체입니다. 왼쪽에 프레젠테이션을 입력하면 오른쪽에 슬라이드가 따라 +나타납니다. 프레젠테이션도, 공유 링크도 서버로 전송되지 않습니다. + +## 제공 기능 {#what-it-has} + +- 모든 슬라이드의 **실시간 미리 보기**: 페이지 스레드와 분리된 Web Worker에서 + 렌더링하므로 입력이 미리 보기 때문에 멈추지 않습니다. 슬라이드를 클릭하면 + 편집기가 해당 줄로 이동하고, 캐럿을 옮기면 미리 보기가 해당 슬라이드로 + 스크롤됩니다. +- **슬라이드 쇼**: 키보드, 화면 컨트롤러, 전환 효과를 갖춘 실제 발표 화면입니다. +- **발표자 보기**: **Present**(또는 슬라이드 쇼에서 `p`)를 누르면 두 번째 창에 + 열립니다. 현재 슬라이드, 다음 단계, 발표자 노트(프레젠테이션의 + `<!-- comments -->`), 시계, 타이머가 표시됩니다. 화살표 키, Space, Home, End는 + 어느 창에서나 동작하며, 프레젠테이션을 편집할 때도 두 창이 서로 연동됩니다. + 창이 열리지 않으면 이 사이트의 팝업을 허용하십시오. 개요 보기(`o`)는 내려받은 + 페이지에서만 쓸 수 있습니다. +- 입력 중 **자동 완성**: 디렉티브 키(주석 안에서는 `_`로 시작하는 스폿 + 디렉티브), `kind`, `theme`, `size`, `paginate`의 값, 그리고 `{{ }}` 안의 템플릿 + 함수와 데이터 필드. +- 캐럿 위치에 있는 요소의 **도움말**: 디렉티브, kind(슬라이드 유형, 설정하는 + 내용), 템플릿 함수. 도움말 창에서 이 문서로 연결되는 링크도 제공합니다. +- 해당 줄에 표시되는 **문제**: 템플릿, YAML, HCL 오류가 편집기에서 강조되고 + 그 아래에 나열되며, 미리 보기는 마지막으로 렌더링에 성공한 프레젠테이션을 + 유지합니다. +- **예제**: 환영 프레젠테이션, 슬라이드를 생성하는 템플릿, 슬라이드 kind, HCL + 프런트 매터, 테마와 스폿 디렉티브, 그리고 수식, 이미지, 배경(`![bg]`, 화면을 + 나누는 `![bg left:40%]`, 필터). +- **파일**: 편집기 왼쪽 패널은 프레젠테이션의 프로젝트이며, + [명령]({{< relref "cli" >}})과 + [VS Code 확장]({{< relref "vscode" >}})이 읽는 구조와 같습니다: `deck.md`, + `slides.yaml`, `themes/`, `kinds/`, `media/`. + - 파일을 클릭하면 편집할 수 있습니다. 편집기는 파일 언어(Markdown, YAML, HCL, + JSON, CSS)에 따라 색을 입히고 문제를 표시합니다. + - 마우스 오른쪽 버튼(또는 Shift+F10)으로 New File, New Folder, Rename(F2), + Delete(먼저 확인), New theme / New kind를 실행합니다. 파일을 폴더로 끌어다 + 놓으면 이동합니다. 키보드 조작은 미리 입력 검색을 포함해 WAI-ARIA 트리 + 패턴을 따릅니다. + - **Project ▸ New theme**은 `themes/my-gaia.css`(또는 default, uncover 기반)를 + 만듭니다. 기본 제공 테마의 `@import`와 직접 추가하는 규칙으로 이루어지며, + Theme 목록에 추가됩니다. **New kind**와 **New data file**도 템플릿에서 + 시작합니다. 각각 `slides.yaml`에 등록되므로 `slides build deck.md`가 찾을 + 수 있습니다. + - **Project ▸ Download project (.zip)**은 전체를 저장합니다. 압축을 풀면 + `slides build deck.md`로 빌드할 수 있습니다. **Open project**는 `.zip`이나 + 폴더를 다시 불러옵니다. 자기 폴더 밖으로 나가는 아카이브(`..`, 절대 경로), + 파일이 256개를 넘거나 한 파일이 8 MiB를 넘는 아카이브, 압축 해제 시 100배 + 넘게 커지는 아카이브는 거부됩니다. + - 모든 내용은 초안과 함께 이 브라우저에 보관됩니다. 프로젝트는 최대 64 MiB까지 + 담을 수 있으며, 파일 목록 아래 줄에 사용량이 표시됩니다. +- **미디어**: 이미지, 동영상, 소리를 편집기에 끌어다 놓거나 붙여 넣으면 + `media/`에 들어가고 프레젠테이션에서 `![](media/photo.png)`처럼 참조됩니다. + 패널의 폴더에 파일을 끌어다 놓으면 그 폴더에 들어갑니다. 프레젠테이션은 작고 + 읽기 쉬운 상태로 유지되며, 아무것도 업로드되지 않습니다. 미리 보기 샌드박스는 + 제한을 풀지 않고도 미디어를 불러옵니다(페이지가 바이트를 직접 넘겨줍니다). + **Save as ▸ HTML page**는 미디어를 페이지 안에 넣습니다. `https:` 이미지도 + 쓸 수 있지만 브라우저가 해당 서버에서 가져오므로 그 서버에 사용자의 주소가 + 노출됩니다. 미리 보기는 리퍼러를 보내지 않습니다. +- **크기 조절 가능한 창**: 파일 패널, 편집기, 슬라이드 썸네일, 미리 보기, + 도움말은 끌어서 움직일 수 있는 막대로 나뉩니다(막대 위에서는 포인터가 크기 + 조절 커서로 바뀝니다). 막대 옆의 창이 공간을 차지하고 나머지 창은 크기를 + 유지합니다. 막대를 더블클릭하거나 막대에서 Enter를 누르면 창을 접었다가 다시 + 펼칩니다. 키보드에서는 F6으로 막대 사이를 이동하고, 화살표 키로 막대를 16 px씩 + 옮기며, Home과 End로 막대 앞의 창을 최소·최대 크기로 만들고, Escape로 + 편집기로 돌아갑니다. View 탭으로 창을 끌 수도 있습니다. 직접 설정한 크기는 + 초안과 함께 이 브라우저에 보관되며, 기본 크기(190, 168, 280 px, 편집기와 미리 + 보기가 나머지를 6 대 5로 나눔)는 저장되지 않습니다. +- **서식**: Format 탭이 Markdown을 대신 편집해 주며, 한 번 누를 때마다 Undo 한 + 단계가 됩니다. + - **텍스트**: Bold, Italic, Strikethrough, Inline code는 선택 영역이나 캐럿 + 위치의 단어에 적용되며 켜고 끄는 방식입니다(Ctrl+B, Ctrl+I, Ctrl+Shift+X, + Ctrl+E, Mac에서는 Cmd). 캐럿이 해당 표식 안에 있으면 버튼이 눌린 상태로 + 표시됩니다. Markdown에는 밑줄이 없으므로 Underline(Ctrl+U)은 텍스트를 + `<u>…</u>`로 감쌉니다. 이 태그는 Marp의 기본 HTML 허용 목록에 포함되므로 + 원시 HTML을 꺼도 동작합니다. + - **문단**: Heading 1~3(Ctrl+Alt+1..3), 글머리 기호(Ctrl+Shift+8), 번호 매기기 + (Ctrl+Shift+7), 인용은 선택한 줄에 적용되며 켜고 끄는 방식입니다. 수평선은 + `<hr>` 태그입니다. `---`, `***`, `___`는 Marp에서 모두 새 슬라이드를 + 시작하기 때문입니다. + - **표**: 격자에서 열과 행 수를 고릅니다. 표 안에 캐럿이 있으면 행이나 열을 + 추가·삭제하고 열 정렬(`:--`, `:-:`, `--:`)을 지정할 수 있으며, 편집할 때마다 + 표가 정렬된 형태로 다시 작성됩니다. + - **슬라이드 텍스트** 왼쪽·가운데·오른쪽 정렬은 현재 슬라이드에 + `<style scoped>` 블록을 씁니다. Marp에는 정렬 문법이 없고 `style` 속성은 + 기본 HTML 허용 목록을 통과하지 못하지만, scoped 블록은 통과합니다. 따라서 + 원시 HTML을 꺼도 동작하며 미리 보기, HTML 페이지, PDF에 반영됩니다. + - **링크**(Ctrl+K)는 url이 선택된 상태로 `[text](url)`을 씁니다. +- 프런트 매터 키의 **구문 강조**: 깊이와 관계없이 YAML(`{a: 1}` 매핑 포함)과 + HCL 모두에서 강조합니다. +- **테마** 선택기: default, gaia, uncover, 프로젝트의 테마(`themes/*.css`), + 프레젠테이션이 정의한 테마(`<style>` 안의 `/* @theme name */`). 선택하면 + 프런트 매터(YAML 또는 HCL)에 `theme:`를 쓰므로 공유 링크에도 유지되며, + 프런트 매터를 편집하면 선택기도 따라 바뀝니다. +- 입력하는 동안 미리 보기가 **위치를 유지합니다**. 바뀐 슬라이드만 교체되며, + 테마를 바꾸면 미리 보기가 같은 슬라이드에서 다시 열립니다. +- **공유 링크**: 프레젠테이션이 링크 자체(`#` 부분에 압축된 형태)에 담기므로 + 링크를 열면 프레젠테이션이 열립니다. 링크에는 압축 후 64 KiB까지, + 프레젠테이션만 담을 수 있습니다. 프로젝트에 그 밖의 파일(테마, kind, 미디어)이 + 있으면 Share가 이를 알리고 `.zip`을 제안합니다. +- **다른 이름으로 저장**: + - `slides deck.md`가 쓰는 것과 같은 HTML 페이지 + - **PDF**(벡터, 텍스트 선택 가능, 개요 포함) + - 편집 가능한 **PowerPoint** 또는 **OpenDocument** 프레젠테이션 + + 모두 `slides --pdf`, `--pptx`, `--odp`와 같은 코드로 브라우저에서 만들어집니다. + 슬라이드 100장 벤치마크 프레젠테이션의 PDF는 명령으로 만든 것과 바이트 단위까지 + 같습니다. 처음 내보낼 때 진행률 표시와 함께 내보내기 엔진(약 21 MiB, 한 번만 + 받으며 다음 업데이트까지 브라우저가 보관)을 내려받습니다. 그 뒤 슬라이드 100장 + 프레젠테이션은 약 3초가 걸립니다. `https:` 이미지는 서버가 허용할 때(CORS)만 + 포함되며, `data:` 이미지는 항상 포함됩니다. +- **초안이 보관됩니다**: + - 프레젠테이션은 입력하는 동안 이 브라우저에 저장되며(IndexedDB, 이 사이트 + 전용), 새로 고침하면 다시 나타납니다. 어디에도 전송되지 않습니다. 프로젝트의 + 텍스트 파일(테마, kind, 데이터, `slides.yaml`)도 같은 방식으로 보관되며, + 입력 직후 탭을 닫아도 유지됩니다. 미디어는 끌어다 놓는 즉시 저장됩니다. + - 공유 링크를 열면 해당 프레젠테이션이 열리고 내 초안은 보관됩니다. + **Draft ▸ Restore my draft**로 되돌릴 수 있습니다. + - 예제를 열거나, **Draft ▸ Reset to the example**(먼저 확인)을 실행하거나, + 다른 탭을 써도 초안은 지워지지 않습니다. **Draft ▸ Restore replaced**로 다시 + 바꿀 수 있습니다. + - **Draft ▸ Forget my drafts**는 저장된 모든 내용을 지웁니다. + +모든 내보내기는 미리 보기에 표시된 프로젝트를 렌더링합니다. `slides.yaml`, +테마, kind, 데이터 파일, 미디어가 포함되며, 미디어는 문서 안에 들어갑니다. + +이미지 파일 출력(slides `--images`)은 브라우저에서 만들 수 없습니다. +[명령]({{< relref "cli" >}})이나 +[VS Code 확장]({{< relref "vscode" >}})을 사용하십시오. + +## 보안 {#security} + +프레젠테이션은 페이지의 출처(origin)를 갖지 않는 샌드박스 iframe에 표시됩니다. +따라서 Playground에도, 그 저장소에도 접근할 수 없고, 양식을 제출하거나 페이지를 +이동할 수도 없습니다. 이 페이지에는 자체 스크립트만 실행하는 Content Security +Policy가 적용됩니다. 프레젠테이션의 원시 HTML은 `slides`의 기본 동작과 마찬가지로 +Marp 허용 목록을 따릅니다. 5초 넘게 걸리는 렌더링(무한 반복하는 템플릿 등)은 +중단됩니다. + +발표자 창도 이 샌드박스를 유지합니다. 발표자 창은 Playground가 직접 열며, +같은 방식으로 샌드박스 처리된 iframe에 프레젠테이션을 표시합니다. 두 iframe은 +저장소를 공유할 수 없으므로 Playground가 메시지로 슬라이드 번호를 중계합니다. +프레젠테이션을 실행하는 어떤 프레임도 Playground의 출처를 얻지 못합니다. + +프로젝트의 미디어는 메시지에 담긴 바이트로 샌드박스 프레임에 전달되며, 각 +프레임은 자신의 출처에서 직접 `blob:` URL을 만듭니다. Playground가 만든 `blob:` +URL은 그 안에서 로드되지 않으며, 프레임 정책에는 미디어와 글꼴용 `blob:`만 +추가됩니다. + +## 크기와 속도 {#size-and-speed} + +모듈은 GitHub Pages가 제공하는 상태(gzip, Pages는 brotli를 제공하지 않음)로 +8.8 MiB입니다. 진행률 막대와 함께 한 번 내려받으며, 이후 다음 배포까지 브라우저가 +캐시합니다. Apple M4 Max의 Chrome 155에서 실제 페이지로 측정한 결과, 편집기는 +페이지를 연 뒤 2.1 s(그중 1.5 s는 내려받기)에 준비되고 첫 슬라이드는 2.4 s에 +나타납니다. 슬라이드 100장 벤치마크 프레젠테이션을 수정하면 약 30 ms 만에 미리 +보기에 반영됩니다. go-slides는 바뀐 슬라이드만 렌더링하고, 미리 보기에서도 그 +슬라이드만 교체합니다. diff --git a/content/ko/templates.md b/content/ko/templates.md new file mode 100644 index 0000000..827e418 --- /dev/null +++ b/content/ko/templates.md @@ -0,0 +1,78 @@ +--- +title: "템플릿" +weight: 30 +description: "프레젠테이션 안의 Go text/template 치환: 값, 생성된 슬라이드, 페이지 번호, 포함." +tags: [templates] +--- + +프레젠테이션은 Go [`text/template`](https://pkg.go.dev/text/template)이며, +Markdown을 파싱하기 **전에** 평가됩니다. 액션, 파이프라인, `if`, `range`, +`with`, 변수 등 Go 템플릿 언어의 모든 기능을 프레젠테이션에서 쓸 수 있습니다. + +## 데이터 {#data} + +템플릿의 점(dot)에는 프런트 매터의 키, 그다음 `data:` 매핑의 키, 그다음 +명령줄이나 `slides.yaml`에서 넘긴 데이터가 차례로 담기며, 뒤의 것이 앞의 것을 +덮어씁니다. + +```markdown +--- +marp: true +title: Quarterly review +data: + team: Platform +--- + +# {{ .title }} + +Presented by the {{ .team }} team. +``` + +명령줄에서는 다음과 같이 넘깁니다. + +| 플래그 | 넘기는 값 | +| --- | --- | +| `--data file.yaml` | 데이터를 담은 YAML 또는 JSON 파일(여러 번 지정 가능) | +| `--set key=value` | 값 하나(여러 번 지정 가능) | + +## 슬라이드 생성하기 {#generating-slides} + +본문 전체가 하나의 템플릿이므로, `---`를 출력하는 `range`는 슬라이드를 만들어 +냅니다. + +```markdown +{{ range .releases }} +--- + +# {{ .version }} + +{{ range .changes }}- {{ . }} +{{ end }} +{{ end }} +``` + +## 페이지 번호와 전체 수 {#page-and-total} + +`{{ page }}`와 `{{ total }}`은 슬라이드의 번호와 전체 슬라이드 수입니다. 이 값은 +프레젠테이션을 슬라이드로 나눈 뒤에야 알 수 있으므로 그때 채워집니다. 따라서 +생성된 프레젠테이션도 자신이 생성한 슬라이드를 정확히 셉니다. + +```markdown +<!-- footer: "{{ page }} / {{ total }}" --> +``` + +## 포함 {#include} + +`{{ include "parts/agenda.md" }}`는 프레젠테이션 기준 상대 경로의 다른 파일을 +삽입합니다. + +## 코드는 건드리지 않음 {#code-is-left-alone} + +펜스 코드 블록과 인라인 코드는 템플릿을 실행하기 전에 따로 빼 두었다가 그대로 +되돌려 놓습니다. 그래서 Go 템플릿을 다루는 슬라이드에서도 이스케이프 없이 +`{{ .Name }}`을 보여 줄 수 있습니다. + +## 끄기 {#turning-it-off} + +`--no-template`을 쓰면 프레젠테이션을 일반 Marp로 읽으며, `{{`는 텍스트로 +취급됩니다. diff --git a/content/ko/vscode.md b/content/ko/vscode.md new file mode 100644 index 0000000..ad6413e --- /dev/null +++ b/content/ko/vscode.md @@ -0,0 +1,184 @@ +--- +title: "VS Code" +weight: 55 +description: "VS Code용 go-slides 확장: Marp for VS Code가 Marp에 제공하는 것처럼 미리 보기, 발표, 문제 표시, 자동 완성, 내보내기를 제공합니다." +tags: [vscode, editor] +--- + +{{< callout type="info" >}} +이 확장은 [go-slides/vscode](https://github.com/go-slides/vscode)이며, 버전은 +따로 관리됩니다(v0.4.0). 렌더링에는 go-slides v0.9.0을 사용합니다(`editor` +패키지, 스크롤 동기화용 소스 줄, 개요와 발표자 보기). GitHub 릴리스에 첨부된 +`go-slides-0.4.0.vsix`로 설치합니다. VS Code Marketplace와 Open VSX 게시는 +준비되어 있지만 아직 올라가 있지 않습니다. +{{< /callout >}} + +이 확장은 [Marp for VS Code](https://github.com/marp-team/marp-vscode)에 +대응하는 go-slides용 확장입니다. 프레젠테이션은 VS Code의 Markdown 미리 보기에 +슬라이드로 표시되고, 오류는 문제 패널에 나타나며, 디렉티브, kind(슬라이드 +유형), 템플릿 함수는 입력하는 동안 자동 완성됩니다. + +## 설치 {#install} + +[릴리스](https://github.com/go-slides/vscode/releases)에서 +`go-slides-<version>.vsix`를 내려받은 다음 *Extensions: Install from VSIX...*를 +실행하거나, 다음 명령을 실행하십시오. + +```sh +code --install-extension go-slides-0.4.0.vsix +``` + +미리 보기에는 그 밖에 필요한 것이 없습니다. 내보내기에는 `slides` 명령이 +필요합니다. + +```sh +go install github.com/go-slides/slides/cmd/slides@latest +``` + +## 미리 보기 {#preview} + +프레젠테이션을 열고 *Markdown: Open Preview to the Side*를 실행합니다. +프레젠테이션은 프런트 매터에 `slides: true`가 있는 Markdown 파일입니다. + +```markdown +--- +slides: true +paginate: true +data: + who: World +--- + +# Hello {{ .who }} + +--- + +<!-- _kind: title --> +# Page {{ page }} of {{ total }} +``` + +`marp: true`가 있는 파일도 미리 보기를 제공합니다. 단, Marp for VS Code가 +설치되어 있으면 두 확장이 같은 문서를 렌더링하게 되므로 예외입니다. 설정 +`goSlides.preview.marpDocuments`로 정하며, 값은 `auto`(기본값), `always`, +`never`입니다. + +미리 보기는 WebAssembly로 컴파일되어 확장이 불러오는 go-slides 자체입니다. +바이너리가 필요 없으며 [vscode.dev](https://vscode.dev)에서도 동작합니다. +프레젠테이션이 사용하는 파일, 즉 프레젠테이션 옆의 `slides.yaml`이나 +`slides.hcl`, 거기서 지정한 [kind]({{< relref "kinds" >}}) 파일, +[템플릿]({{< relref "templates" >}})이 포함하는 파일, 테마 CSS를 작업 영역에서 +읽으며, 프레젠테이션이 속한 작업 영역 폴더에서만 읽습니다. + +## 스크롤 동기화 {#scroll-sync} + +확장 v0.2.0(go-slides v0.7.0)부터 편집기와 미리 보기가 일반 Markdown에서처럼 +양방향으로 함께 스크롤됩니다. 미리 보기는 편집기 커서가 있는 슬라이드와 블록을 +따라가고, 미리 보기를 스크롤하면 편집기가 움직입니다. 설정은 VS Code 자체의 +`markdown.preview.scrollPreviewWithEditor`와 +`markdown.preview.scrollEditorWithPreview`입니다. + +줄 위치는 템플릿을 거쳐서도 추적됩니다. `{{ range }}`가 생성한 슬라이드는 +템플릿의 해당 줄을 따라가고, `{{ include }}`로 가져온 내용은 include가 있는 +줄을 따라갑니다. `headingDivider`로 나뉜 프레젠테이션은 제목을 기준으로 +동기화됩니다. + +## 발표하기 {#presenting} + +확장 v0.4.0(go-slides v0.9.0)부터 VS Code를 떠나지 않고 프레젠테이션을 발표할 +수 있습니다. 편집기에 프레젠테이션을 연 상태에서 명령 팔레트나 편집기의 `…` +메뉴에서 다음 명령을 실행합니다. + +| 명령 | 여는 화면 | +|---|---| +| *go-slides: Open Overview* | 모든 슬라이드를 격자로 보여 주는 패널. 슬라이드를 고르면 편집기에 그 소스가 표시되며, `Esc`로 패널을 닫습니다 | +| *go-slides: Start Presenter View* | 슬라이드와 그 옆의 발표자 보기: 다음 단계, 발표자 노트, 타이머 | + +패널은 편집기 옆에 놓이며 서로 연동됩니다. 한 패널에서 슬라이드를 바꾸면 다른 +패널도 함께 바뀝니다. 프레젠테이션을 편집한 내용도 따라갑니다. 슬라이드에서 `p` +키를 누르면 발표자 보기가 열립니다. + +다른 화면에서 발표하려면 *View: Move Editor into New Window*로 슬라이드 패널을 +그쪽으로 옮기십시오. + +각 패널은 `default-src 'none'` 정책 아래 샌드박스 프레임(`allow-same-origin` +없음)에서 프레젠테이션을 실행합니다. 프레임은 이미지, 미디어, 스타일, 글꼴만 +불러오고, go-slides가 페이지에 넣은 해시로 지정된 스크립트만 실행합니다. +프레젠테이션에 작성된 스크립트는 실행되지 않습니다. + +## 문제 표시, 자동 완성, 호버 {#problems-completion-hover} + +- **문제 표시**: 템플릿, YAML, [HCL]({{< relref "hcl" >}}) 오류를 해당 줄에 + 표시합니다. 오류가 프레젠테이션에 있든, 그 출처인 kind 파일에 있든 마찬가지입니다. +- **자동 완성**: 프런트 매터와 `<!-- -->` 주석의 디렉티브 키(스폿 디렉티브는 + `_` 포함), `kind`와 `_kind`의 값(각 kind가 설정하는 내용과 함께), `theme`, + `size`, `paginate`, `math`, `headingDivider`, 그리고 `{{ }}` 안에서는 템플릿 + 함수와 데이터 필드를 완성합니다. +- **호버**: 디렉티브의 역할, kind가 설정하는 내용, 템플릿 함수의 인수를 + 보여 줍니다. + +## 내보내기 {#export} + +*go-slides: Export Slide Deck...*(편집기 제목 표시줄에도 있음)은 +`slides -o deck.pdf deck.md`처럼 `slides` 명령으로 HTML, 벡터 PDF, 편집 가능한 +PPTX와 ODP, PNG, JPEG를 만듭니다. [명령줄]({{< relref "cli" >}})을 +참고하십시오. 신뢰할 수 있는 작업 영역이 필요합니다. `slides`를 찾지 못하면 +확장이 위의 `go install`을 터미널에서 실행하도록 제안합니다. + +## 설정 {#settings} + +| 설정 | 기본값 | | +|---|---|---| +| `goSlides.cliPath` | `""` | `slides` 명령. 비어 있으면 `PATH`의 `slides`를 씁니다. 컴퓨터 설정이므로 작업 영역에서는 지정할 수 없습니다 | +| `goSlides.exportType` | `pdf` | 내보내기 대화 상자가 처음 제안하는 형식 | +| `goSlides.exportAutoOpen` | `true` | 내보낸 파일 열기 | +| `goSlides.html` | `false` | Marp 허용 목록 대신 모든 원시 HTML 허용. 신뢰할 수 있는 작업 영역에서만 적용됩니다 | +| `goSlides.preview.marpDocuments` | `auto` | `marp: true` 프레젠테이션 미리 보기: `auto`, `always`, `never` | + +## 보안 {#security} + +프레젠테이션과 작업 영역은 신뢰하지 않는 것으로 취급합니다. 원시 HTML은 +신뢰할 수 있는 작업 영역에서 `goSlides.html`을 켜지 않는 한 Marp 허용 목록을 +따르며(`html: true`가 있는 `slides.yaml`로는 미리 보기에서 켜지지 않습니다), +VS Code 미리 보기는 프레젠테이션의 스크립트를 실행하지 않습니다. 미리 보기는 +프레젠테이션이 속한 작업 영역 폴더의 파일만 읽고, 심볼릭 링크를 따라가지 않으며, +템플릿은 환경 변수를 볼 수 없습니다. 내보내기는 프로그램을 실행하므로 작업 영역 +신뢰를 기다리며, 실행할 프로그램을 작업 영역이 지정하는 일은 없습니다. + +## 성능 {#performance} + +go-slides의 슬라이드 100장짜리 벤치마크 프레젠테이션으로, Apple M4 Max의 +VS Code 1.142에서 같은 세션에 Marp for VS Code 3.6.1도 함께 측정했습니다. + +| | go-slides | Marp for VS Code | +|---|---:|---:| +| 활성화(WebAssembly 모듈 로드) | 189 ms | — | +| 첫 미리 보기 | 52 ms | 78 ms | +| 편집 후 미리 보기(중앙값) | **19 ms** | 23–24 ms | +| `.vsix` | 7.5 MB | 36.3 MB | + +수치는 확장 v0.3.0(go-slides v0.8.0) 기준이며, 슬라이드 100장 프레젠테이션의 +50번째 슬라이드에 문자 하나를 입력하는 작업을 1분 부하 8 미만에서 번갈아 +측정한 결과입니다. 첫 미리 보기는 Marp보다 빠르고, 이제 편집 후 미리 보기도 +Marp보다 빠릅니다. + +### v0.8.0과 확장 v0.3.0 {#in-v080-and-extension-v030} + +go-slides v0.8.0은 열려 있는 각 프레젠테이션의 렌더링된 슬라이드를 보관하고, +편집 후에는 소스나 상속된 상태(디렉티브, 페이지 번호, 전체 수, 제목 id, 링크 +참조, 소스 줄)가 바뀐 슬라이드만 다시 렌더링합니다. 결과는 전체 렌더링과 +바이트 단위까지 같습니다. 100장 중 50번째 슬라이드에 입력하면 슬라이드 한 장만 +다시 렌더링하며, 네이티브로 2.5 ms, WebAssembly로 11.5 ms가 걸립니다. 그러면 +VS Code 미리 보기는 바뀐 슬라이드의 DOM만 변형하며, 확장의 스모크 테스트는 두 +확장 모두에 대해 시간을 재는 렌더링 사이에 50번째 슬라이드에 문자 하나를 +입력합니다. 확장의 발표자 보기는 부모 창을 통해(`postMessage`) 동기화합니다. + +### v0.7.1과 확장 v0.2.2 {#in-v071-and-extension-v022} + +go-slides v0.7.1은 파싱한 테마, 압축한 스타일시트, 강조 표시한 코드를 렌더링 +사이에 캐시하고, 스크롤 동기화용 줄을 바이트 단위로 훑지 않고 표시하며, 작성기를 +재사용합니다. 슬라이드 100장 프레젠테이션 한 번 렌더링에 할당하는 메모리는 +5.7 MB로, 확장 v0.2.0의 52.7 MB보다 적습니다. 덕분에 확장 v0.2.2는 +`GOGC=400` 재정의를 없앴습니다. 비교하자면 확장 v0.2.1은 기본 `GOGC`에서 첫 미리 +보기 72 ms, 편집 후 44 ms(`GOGC=400`에서는 36 ms)였고, v0.2.0은 47–51 ms였습니다. + +확장 v0.2.2는 첫 미리 보기 52 ms, 편집 후 24 ms로 측정되어 Marp의 23 ms와 대등한 +수준이었습니다. diff --git a/content/zh/_index.md b/content/zh/_index.md new file mode 100644 index 0000000..78c6021 --- /dev/null +++ b/content/zh/_index.md @@ -0,0 +1,59 @@ +--- +title: "go-slides 文档" +linkTitle: "首页" +type: docs +cascade: + type: docs +description: "纯 Go(CGO=0)实现、兼容 Marp 的幻灯片演示文稿,支持 Go 模板插值和幻灯片 kind。" +--- + +**`slides` 用纯 Go 把 Marp 演示文稿变成一场演示。** 它读取的 Markdown 与 +[Marp](https://marp.app) 相同(Marpit 的指令、幻灯片之间的 `---`、`![bg]` +背景、演讲者备注,以及 `default`、`gaia` 和 `uncover` 主题),并增加了两项 +Marp 没有的功能: + +- 演示文稿中的 **[Go 模板]({{< relref "templates" >}})**:在解析 Markdown + 之前求值,因此演示文稿可以自己生成幻灯片; +- **[kind(幻灯片类型)]({{< relref "kinds" >}})**:幻灯片声明自己是什么 + (`title`、`section`…),而不必反复描述自己的外观。 + +{{< callout type="warning" >}} +**v0.9.0。** 本文档描述的是 +[go-slides/slides v0.9.0](https://github.com/go-slides/slides/releases/tag/v0.9.0), +发布于 `/docs/0.9/` 下。 +{{< /callout >}} + +## 现有功能 {#what-it-does-today} + +| | | +| --- | --- | +| `slides build`(默认命令) | 将演示文稿生成为一个 HTML 页面:每张幻灯片是一个以 SVG 为框架、随窗口缩放的 `<section>`,附带主题 CSS 和键盘浏览器 | +| `slides build --pdf`、`--images png`、`--image jpeg` | 将演示文稿导出为 PDF(每张幻灯片一页,带书签大纲)或 PNG/JPEG 图片,无需浏览器:由 [go-webengine](https://github.com/go-webengine) 渲染,[go-pdfkit](https://github.com/go-pdfkit) 写出 PDF;自 v0.4.0 起为矢量格式,文本可选中:[矢量 PDF]({{< relref "cli#vector-pdf" >}}) | +| 数学公式与代码 | `$…$` 和 `$$…$$` 由 go-tex/math 排版,围栏代码由 go-rouge 高亮,适用于 HTML、图片和 PDF:[数学公式与代码]({{< relref "math-and-code" >}}) | +| emoji | `:smile:` 和 😄 会变成内嵌的 Twemoji 图片,因此无需网络,并能在所有输出格式中绘制:[Emoji]({{< relref "emoji" >}})(自 v0.4.0 起) | +| `slides build --pptx` | 将演示文稿导出为 PowerPoint 文件,包含演讲者备注和演示文稿的元数据:v0.4.0 中每张幻灯片是一张图片,与 marp-cli 相同;自 v0.5.0 起**可编辑**:文本框、形状、图片,并内嵌字体([可编辑的 PPTX 和 ODP]({{< relref "cli#editable-pptx-and-odp" >}})) | +| `slides build --odp` | 同上,但导出为 OpenDocument 演示文稿(`.odp`) | +| 自适应标题 | `# <!-- fit --> Title` 在一行内撑满幻灯片宽度,在页面和所有导出格式中均有效 | +| `slides expand` | 将演示文稿输出为纯 Marp Markdown:模板已求值,kind 已替换为其所代表的单页指令 | +| `slides notes` | 演讲者备注 | +| `slides kinds`、`slides themes` | 演示文稿可以引用的名称 | +| `slides.yaml` | 为一个文件夹中的演示文稿共享 kind、主题、数据、HTML 策略和语言 | +| HCL | `---hcl` front matter、`slides.hcl`、HCL2 格式的 kind 和数据文件,支持表达式和函数:[HCL]({{< relref "hcl" >}}) | + +## 计划中 {#planned} + +尚未实现,列在这里以免大家猜测:PDF 中的公式矢量化(目前公式在 PDF 中是位图)。 +与 marp-cli 的区别在于实现途径:marp-cli 通过驱动无头 Chrome 来渲染图片和 PDF; +这里则由 Go 库完成。 + +## 页面 {#pages} + +- [快速上手]({{< relref "getting-started" >}}) +- [示例库]({{< relref "gallery" >}}):十二个示例演示文稿,可在 Playground 中打开 +- [Marp 兼容性]({{< relref "marp" >}}) +- [模板]({{< relref "templates" >}}) +- [Kind]({{< relref "kinds" >}}) +- [HCL]({{< relref "hcl" >}}) +- [数学公式与代码]({{< relref "math-and-code" >}}) +- [Emoji]({{< relref "emoji" >}}) +- [命令行]({{< relref "cli" >}}) diff --git a/content/zh/cli.md b/content/zh/cli.md new file mode 100644 index 0000000..6d14c98 --- /dev/null +++ b/content/zh/cli.md @@ -0,0 +1,311 @@ +--- +title: "命令行" +weight: 50 +description: "slides 的所有命令和参数,以及 slides.yaml 配置文件。" +tags: [cli] +--- + +{{< callout type="warning" >}} +本页描述的是 v0.9.0。 +{{< /callout >}} + +```text +slides [deck.md] [flags] +slides [command] [flags] deck.md +``` + +## 命令 {#commands} + +| 命令 | 作用 | +| --- | --- | +| `build` | 将演示文稿写出为 HTML,或 PDF、图片(见下文)。**默认命令**:`slides deck.md` 即 `slides build deck.md` | +| `expand` | 将演示文稿输出为纯 Marp Markdown:模板已展开,kind(幻灯片类型)已替换为其所代表的指令 | +| `notes` | 输出演讲者备注 | +| `kinds` | 列出演示文稿可用的 kind 及其设置的内容;`--json`(v0.6.0 起)将其写为 `[{"name", "extends", "directives": [{"key", "value"}]}]`,其中 `extends` 已解析 | +| `themes` | 列出主题 | +| `version` | 输出版本号(`slides --version` 亦可) | +| `completion` | 输出 shell 自动补全脚本:`bash`、`zsh`、`fish` 或 `powershell` | + +`slides <command> --help` 会列出某个命令的参数。参数可以写在演示文稿之前或之后: +`slides deck.md -o out.html` 与 `slides -o out.html +deck.md` 等价。长参数使用两个短横线(`--html`);`-o` 是唯一的短参数。 +除非 `-o` 另行指定,`build` 会在 `deck.md` 旁边写出 `deck.html`。 + +## 参数 {#flags} + +| 参数 | 适用命令 | 作用 | +| --- | --- | --- | +| `-o`, `--output file` | `build`、`expand`、`notes` | 输出文件;`-` 表示标准输出 | +| `-I`, `--input-dir dir` | `build` | 递归转换 `dir` 下的 Markdown 文件(v0.6.0 起);见下文 | +| `-P`, `--parallel n` | `build` | 同时构建的演示文稿数(v0.6.0 起):默认 5,最多为 CPU 核数;为 1 时逐个构建 | +| `-w`, `--watch` | `build` | 当演示文稿、主题、kind 或数据文件、配置发生变化时重新构建,并刷新已打开的页面(v0.6.0 起);见下文 | +| `-s`, `--server dir` | `build` | 托管一个目录,按请求转换其中的演示文稿(v0.6.0 起);见下文 | +| `-p`, `--preview` | `build` | 在默认浏览器中打开结果(v0.6.0 起):配合 `--watch`(隐含启用)打开 HTML 页面,配合 `--server` 打开服务器 | +| `--host addr` | `build` | `--server` 监听的地址,默认 `127.0.0.1`(v0.6.0 起) | +| `--template bespoke\|bare` | `build` | HTML 页面的模板(v0.6.0 起):默认的 `bespoke` 带有演示脚本、屏幕控制器和过渡效果;`bare` 则将幻灯片依次堆叠(`--bare`) | +| `--bespoke.osc`, `--bespoke.progress`, `--bespoke.transition` | `build` | bespoke 模板的选项,默认值与 marp-cli 相同(v0.6.0 起):控制器开启,**进度条关闭**(以前总是绘制),过渡效果开启;`--bespoke.osc=false` 可关闭其中一项 | +| `-c`, `--config-file file`, `--no-config-file` | `build` | marp-cli 的配置文件,JSON 或 YAML 格式(v0.6.0 起);见下文。`--config` 是 `slides` 自己的配置文件 | +| `--browser`, `--browser-path`, `--browser-protocol`, `--browser-timeout` | `build` | 接受但忽略,并给出警告(v0.6.0 起):这里没有浏览器 | +| `--pptx-editable`, `--no-parallel` | `build` | marp-cli 中 `--pptx` 和 `-P 1` 的写法(v0.6.0 起) | +| `--config file` | 全部 | 配置文件;默认为演示文稿旁边的 `slides.yaml`(如果存在) | +| `--no-config` | 全部 | 不读取配置文件 | +| `--theme-set path` | 全部 | 一个主题 CSS 文件,或包含主题文件的目录(可重复) | +| `--kinds file` | 全部 | 一个定义 kind 的 YAML 文件(可重复) | +| `--data file` | 全部 | 一个提供模板数据的 YAML 或 JSON 文件(可重复) | +| `--set key=value` | 全部 | 一个模板值(可重复) | +| `--no-template` | 全部 | 不展开 Go 模板 | +| `--strict-yaml` | 全部 | 按严格 YAML 解析指令,与原生 Marpit 一致(Marp Core 的解析较宽松) | +| `--no-emoji` | 全部 | 将 `:shortcodes:` 和 Unicode emoji 保留为文本,而不绘制为图片(自 v0.4.0 起:[Emoji]({{< relref "emoji" >}})) | +| `--html` | `build` | 允许所有原始 HTML;默认使用 Marp Core 的白名单 | +| `--lang code` | `build` | 演示文稿未设置时页面使用的 `lang` | +| `--bare` | `build` | 堆叠幻灯片,不带演示脚本 | +| `--pdf` | `build` | 写出 PDF,每张幻灯片一页,带书签大纲;文本和形状为矢量(也可由 `-o file.pdf` 选择) | +| `--pdf-raster` | `build` | 写出每页为一张幻灯片图片的 PDF,与 v0.3.0 相同 | +| `--notes` | `build` | 将演讲者备注写为文本文件,幻灯片之间以 `---` 分隔,与 marp-cli 相同(v0.6.0 起;也可由 `-o file.txt` 选择)。`notes` 命令以另一种格式输出备注 | +| `--pdf-notes` | `build` | 将每张幻灯片的演讲者备注作为注释批注加到对应的 PDF 页面上,与 marp-cli 相同(v0.6.0 起)。未指定其他输出格式时,会写出 PDF | +| `--pdf-outlines`, `--pdf-outlines.pages`, `--pdf-outlines.headings` | `build` | PDF 的书签(v0.6.0 起):每张幻灯片一个 “Page N”,其下嵌套该幻灯片的标题。与 marp-cli 不同,这里默认开启;`=false` 可关闭其中一项。见下文 | +| `--images png\|jpeg` | `build` | 每张幻灯片写出一张图片:`deck.001.png`、… | +| `--image png\|jpeg` | `build` | 将第一张幻灯片写为图片(也可由 `-o file.png` 或 `-o file.jpg` 选择) | +| `--pptx` | `build` | 写出带演讲者备注的 PowerPoint 文件(也可由 `-o file.pptx` 选择):自 v0.5.0 起可编辑,v0.4.0 中每张幻灯片是一张图片 | +| `--pptx-raster` | `build` | 写出每张幻灯片为一整张图片的 PowerPoint 文件,与 marp-cli 相同(自 v0.5.0 起);`--image-scale` 默认为 2 | +| `--odp` | `build` | 写出带演讲者备注的 OpenDocument 演示文稿(也可由 `-o file.odp` 选择):自 v0.5.0 起可编辑 | +| `--odp-raster` | `build` | 写出每张幻灯片为一张图片的 OpenDocument 演示文稿(自 v0.5.0 起) | +| `--jpeg-quality n` | `build` | JPEG 质量,1 到 100(默认 85) | +| `--image-scale n` | `build` | 图片和 PDF 中每个 CSS 像素对应的设备像素数(默认 1);为 2 时 16:9 幻灯片的尺寸为 2560×1440,文本和矢量图形按该密度绘制,而不是放大 | +| `--allow-local-files` | `build` | 允许图片和 PDF 读取本地文件,且仅限演示文稿所在目录之下;默认拒绝,与 marp-cli 相同 | +| `--theme name\|file.css` | `build` | 覆盖演示文稿的主题(v0.6.0 起):主题名,或一个 CSS 文件,无论其声明的 `@theme` 是什么,都会成为当前主题;不接受目录,目录请用 `--theme-set` | +| `--title`, `--description`, `--author`, `--keywords`, `--url`, `--og-image` | `build` | 页面的元数据(v0.6.0 起),优先于演示文稿的 `title`、`description`、`author`、`keywords`、`url` 和 `image` 指令;见下文 | + +## 多个演示文稿与目录 {#several-decks-and-a-directory} + +从 v0.6.0 起,`build` 可以接受多个演示文稿、一个目录(递归查找其中的演示文稿) +或一个通配模式,并同时构建最多 `--parallel` 个。`-` 表示标准输入;与 marp-cli +不同,除非用 `-` 明确指定,否则不会读取管道。 + +通配模式的解释方式与 marp-cli 的 glob 相同(`**` 和 `{a,b}` 自 v0.7.0 起支持; +请加引号,让 shell 把它原样交给 `slides`):名称中可使用 `*`、`?`、`[a-z]` 和 +`{md,txt}`,`**` 匹配任意层目录,因此 `'docs/**/*.md'` 会选取整棵目录树中的演示文稿。 +通配符不会匹配以点开头的名称,不会在 `node_modules` 中查找,区分大小写 +(`*.md` 找不到 `UPPER.MD`),并且模式匹配到的任何文件都会被转换,无论其扩展名是什么。 +没有匹配到任何文件的模式会报错。 + +- **`-I dir`** 与 marp-cli 一样,查找 `dir` 下的 Markdown 文件(`.md`、`.mdown`、`.markdown`、 + `.markdn`,小写):不会进入 `node_modules` 和以点开头的目录。配合 `-o out` 时, + 每个演示文稿按其在 `dir` 下的路径,镜像写到 `out` 下:`dir/sub/b.md` 生成 + `out/sub/b.html`(或 `b.pdf`、`b.001.png`、`b.txt`、…,取决于参数)。 + 不带 `-o` 时,输出文件写在各自的演示文稿旁边。同时指定演示文稿和 + `-I` 会报错,对多个演示文稿使用 `-o file` 也会报错(允许 `-o -`)。 +- **`-P n`** 同时构建 `n` 个演示文稿。单个演示文稿的渲染本身已经会利用多核, + 所以 `n` 的上限是 CPU 核数;使用 `-o -` 时逐个构建。同一个演示文稿的消息会集中在一起输出, + 一个演示文稿失败不会中断其他演示文稿:退出状态为 1,消息中会列出失败项。 + +## 过渡效果与演示页面 {#transitions-and-the-presenting-page} + +从 v0.6.0 起,marp-cli 的 `transition` 指令可用:`<!-- transition: +fade 1s -->`(名称加可选的时长)作用于当前幻灯片及其后的幻灯片, +`<!-- _transition: cover -->` 则只作用于一张。内置名称与 marp-cli 相同: +`clockwise`、`counterclockwise`、`cover`、`coverflow`、`cube`、`cylinder`、 +`diamond`、`drop`、`explode`、`fade`、`fade-out`、`fall`、`flip`、`glow`、 +`implode`、`in-out`、`iris-in`、`iris-out`、`melt`、`overlap`、`pivot`、`pull`、 +`push`、`reveal`、`rotate`、`slide`、`star`、`swap`、`swipe`、`swoosh`、`wipe`、 +`wiper` 和 `zoom`;`none` 表示关闭。当观众向前离开带有过渡效果的幻灯片, +以及返回该幻灯片时,页面会使用浏览器的 **View Transition API** 播放过渡; +不支持该 API 的浏览器照旧切换幻灯片,而要求减弱动态效果的观众会看到简单的淡入淡出。 +主题可以自带关键帧,命名为 `marp-transition-NAME`(或 `marp-outgoing-` 和 +`marp-incoming-`,以及 `-backward`),它们优先于内置关键帧。 + +页面带有**屏幕控制器**(上一张、页码、下一张、全屏),在指针移动或按键时显示, +片刻后隐藏(`--bespoke.osc=false` 可将其移除);使用 `--bespoke.progress` 时还会显示进度条。 +**进度条现在默认关闭**,与 marp-cli 相同;以前总是绘制。 + +### 演讲者视图 {#presenter-view} + +从 v0.7.0 起,页面提供 marp-cli 的演讲者视图。按 `p` 或控制器的最后一个按钮, +会打开一个窗口,显示当前幻灯片、下一步、你的演讲者备注、页码、时间和计时器 +(点击计时器可重置;`+` 和 `-` 调整备注字号;幻灯片与备注之间的分隔条可以拖动)。 +两个窗口双向同步:在任一窗口中按方向键、点击下一步或点击 Previous/Next, +另一个窗口都会跟着移动,每次一个片段。 + +它的工作方式与 marp-cli 相同(依据其 4.5.1 源码):该窗口是同一文件的 +`?view=presenter&sync=KEY`,下一步是在框架中以 `?view=next` 打开的同一页面, +窗口之间通过 `localStorage`(`bespoke-marp-sync-KEY`)共享位置,因此不需要服务器, +也不需要网络;在没有 `localStorage` 的浏览器中无法使用(按钮会给出提示)。 +备注是纯文本:`&`、`<` 和 `>` 会被转义,所以备注永远不会成为标记。 +备注隐藏在页面中,供演讲者视图读取,这样屏幕阅读器就不会把它们读给观众听。 +`--bare` 页面不包含这些功能。该视图会使页面增加约 10 KB。 + +### 概览视图 {#overview-view} + +从 v0.8.0 起,按 `o`(或 `Escape`:marp-cli 两者都可切换)、控制器的网格按钮, +或演讲者视图的页码计数器,所有幻灯片会以网格形式完整显示在页面上方。 +方向键选择幻灯片(上下键按行移动),`Home` 和 `End` 跳到首尾,其他窗口随之同步; +`Enter`、`Space` 或点击会选中幻灯片,在主页面上还会关闭概览。 +在演讲者视图上选择后概览保持打开(演讲者挑选要展示的幻灯片,并继续浏览)。 +“Close overview” 按钮、`o` 和 `Escape` 可关闭概览。它与 marp-cli 的实现相同: +同一页面再次在框架中以 `?view=overview&sync=KEY` 打开,通过 `localStorage` +共享位置,无需网络。网格中的幻灯片可获得焦点,标签为 “Slide N”; +概览关闭时处于 inert 状态,因此键盘用户和屏幕阅读器用户不会误入其中。 +两个视图合计使页面增加约 15 KB。 + +### 配置文件 {#configuration-files} + +marp-cli 从工作目录中的文件读取选项,`slides` 也一样,前提是该文件是数据文件: +`.marprc`(YAML;JSON 也是 YAML)、`.marprc.json`、`.marprc.yaml`、`.marprc.yml`、 +`.config/marprc*`,或 `package.json` 中的 `marp` 键;`--config-file`(`-c`) +可指定任意 JSON 或 YAML 文件,例如 `marp.config.json`。与 marp-cli 的查找方式一样, +只查找工作目录,不查找其上级目录。JavaScript 或 TypeScript 文件 +(`marp.config.js` ...)是程序,`slides` 不会运行它:会给出提示并继续。 +支持的选项是 marp-cli 文档中在这里有意义的那些:`title`、`description`、`author`、 +`keywords`、`url`、`ogImage`、`lang`、`theme`、`themeSet`、`html`、`allowLocalFiles`、 +`template`、`bespoke`、`inputDir`、`output`、`parallel`、`pdf`、`pptx`、`notes`、 +`pdfNotes`、`pdfOutlines`、`images`、`image`、`imageScale`、`jpegQuality`、`watch`、 +`server`、`preview`;路径相对于该文件。命令行优先。 +`engine`、`options` 和 `browser*` 需要 Marp 的引擎和浏览器,会被忽略并给出警告, +`--browser*` 参数也是如此:为 marp-cli 编写的脚本可以继续使用。 +(`slides.yaml` 和 `slides.hcl`,连同 kind 和模板数据,是独立的,读取方式不变。) + +## 监视、托管与预览 {#watch-serve-and-preview} + +自 v0.6.0 起,采用 marp-cli 的路由和实时刷新协议(依据其 4.5.1 源码, +并与运行中的 `marp -s` 对照过)。 + +**`slides --server dir`**(`-s`)在 `http://127.0.0.1:8080/` 上托管该目录 +(环境变量 `PORT` 可修改端口;端口已被占用时会报错并说明原因): + +| 请求 | 响应 | +| --- | --- | +| `/deck.md` | 按请求转换为 HTML 的演示文稿 | +| `/deck.md?pdf`、`?pptx`、`?png`、`?jpg`(或 `?jpeg`)、`?notes`(或 `?txt`) | 按查询参数转换后的演示文稿;PPTX 以附件形式返回 | +| 目录 | 其中的 `index.md` 或 `PITCHME.md` 作为页面返回,否则返回一个索引,为每个演示文稿提供 PDF 和 PPTX 链接 | +| 其他文件 | 文件本身 | +| 无法转换的演示文稿 | 503 及错误信息 | + +页面会打开一个连接到 `/.__marp-cli-watch-notifier__/<id>` 的 WebSocket,其中 id 是 +演示文稿路径的 SHA-256;服务器先发送 `ready`,在演示文稿或目录下任何内容变化时发送 `reload`。 +请求目录时如果缺少结尾的斜杠,会重定向到带斜杠的地址(marp-cli 会在不带斜杠的地址上返回索引, +这会破坏页面的相对链接)。 + +**`--watch`**(`-w`)先构建演示文稿,之后重新构建发生变化的那一个;当 `--theme-set` +文件、kind 或数据文件、配置发生变化时,则全部重新构建。它写出的页面会在回环地址上打开一个套接字 +(端口 37717,或下一个空闲端口),并在对应演示文稿重新构建时刷新。构建失败的演示文稿会报告错误, +但仍继续监视。**`--preview`**(`-p`)在默认浏览器中打开服务器,或刚刚构建的 HTML 页面; +它没有自己的窗口,因为 `slides` 没有可以驱动的浏览器。 + +服务器是进入本机的一个入口,所以它比 marp-cli 的更严格(marp-cli 的服务器监听所有网络接口, +且不做任何检查): + +- 它监听 **127.0.0.1**;`--host` 可以更改,但会给出警告,而且通配地址将无法再检查 `Host` 请求头; +- **`Host`** 请求头必须是回环名称(`localhost`、`127.0.0.1`、 + `[::1]`、`*.localhost`)或其监听的主机名,以防范 DNS 重绑定; +- WebSocket 的 **`Origin`** 必须是服务器自身;监视模式的套接字还接受从文件打开的页面的 + `null` 来源;来自其他站点、且不是页面导航的请求(脚本、图片、`fetch`)会被拒绝; +- 路径会被规范化,符号链接会被解析,结果必须位于该目录内:`..` 和指向目录外的链接会被拒绝, + 对目录外演示文稿的 `?pdf` 请求也是如此;以点开头的名称(`.git`、`.env`)不会被提供; +- 只接受 `GET` 和 `HEAD`;同时进行的转换数量有限;最多监视 4096 个目录, + 且不监视 `node_modules` 中或以点开头的目录。 + +## 页面元数据 {#page-metadata} + +与 marp-cli 一样,命令行优先于演示文稿;在 marp-cli 中,这些参数会作为最后一条指令注释追加到演示文稿末尾。 +以下规则依据 marp-cli 4.5.1 整理,并由针对其输出的测试固定下来(v0.6.0 起): + +- 未设置 `title` 的演示文稿,取其最低层级的第一个标题的原文 + (`# **Bold** title` 得到 `**Bold** title`)。没有标题时就没有 `<title>`。 +- `keywords` 按逗号拆分、去除首尾空白并去重;front matter 中的列表按原样使用。 + 与 marp-cli 一样,只有从第二个关键词起,页面才会带 `keywords` 标签,只有一个关键词时不带。 +- 无效的 `url`(非绝对地址)会被忽略并给出警告,保留之前的值; + 有效值还会写出 `<link rel="canonical">` 和 `og:url`。 +- `author` 和 `description` 会附带对应的 Open Graph 标签;`og:image:alt` + 和 Twitter 大图卡片需要同时有标题和图片。 + +## 图片、PDF、PPTX 和 ODP {#images-pdf-pptx-and-odp} + +与 marp-cli 一样,`-o` 配合 `.pdf`、`.pptx`、`.odp`、`.png` 或 `.jpg` 扩展名即可选择格式。 +输出目录会自动创建。PDF 每张幻灯片一页,页面尺寸与幻灯片相同,并带有由幻灯片及其标题组成的书签大纲。 + +### PDF 备注与书签大纲 {#pdf-notes-and-outline} + +从 v0.6.0 起,PDF 支持 marp-cli 的两个选项,并与 marp-cli 4.5.1 为同一演示文稿写出的 PDF 做过对比: + +- `--pdf-notes` 将每张幻灯片的演讲者备注作为注释批注放在对应页面上: + 左下角一个黄色的 Note 图标,内容为备注(多条注释之间以空行连接),标题为演示文稿的 + `author`。与 marp-cli 一样,单独使用 `--pdf-notes` 时会写出 PDF。 +- 书签大纲为每张幻灯片包含一个粗体书签 “Page N”,其下按层级嵌套该幻灯片的标题 + (`--pdf-outlines.pages` 和 `--pdf-outlines.headings` 分别控制这两部分)。 + **书签大纲在这里默认开启,在 marp-cli 中默认关闭。** `slides` 一直都会写出书签大纲; + 它只占几百字节,却能让长篇演示文稿便于导航,所以保留这一默认值。 + `--pdf-outlines=false` 或 marp-cli 的 `--no-pdf-outlines` 可将其关闭, + 在 `.pages` 或 `.headings` 上使用 `=false` 则去掉对应部分。标题书签跳转到其所在的幻灯片; + marp-cli 还会滚动到该标题。 + +渲染由 [go-webengine](https://github.com/go-webengine/engine) 完成, +PDF 由 [go-pdfkit](https://github.com/go-pdfkit/pdfkit) 写出; +内置主题所需的字体(Lato、Roboto Mono)来自 +[go-opentype/fonts](https://github.com/go-opentype/fonts) 并被内嵌, +因此任何渲染都不会访问网络。这里没有 Chrome。 + +`--image-scale 2` 以每个 CSS 像素两个设备像素进行渲染。除非指定 `--allow-local-files`, +否则会拒绝本地图片;即使指定了,也只读取演示文稿所在目录之下的文件: +来自别处的演示文稿无法访问 `~/.ssh`。多重背景和分栏背景(`![bg left:40%]`) +的渲染方式与 Marp 的图层相同。 + +`--pptx` 使用 [go-ooxml](https://github.com/go-ooxml) 的 +`WritePresentation` 写出演示文稿,包含演讲者备注和演示文稿的元数据。自 +v0.5.0 起幻灯片可编辑(见下文);`--pptx-raster` 则与 marp-cli 一样,每张幻灯片写出一整张图片, +缩放比例默认为 2,可由 `--image-scale` 覆盖。生成的文件可以用 LibreOffice 打开。 +只在 LibreOffice 中测试过,未在 PowerPoint 和 Keynote 中测试。 + +`--odp` 通过 go-odf 的 `WritePresentation` 写出 OpenDocument 演示文稿, +包含演讲者备注;`--odp-raster` 每张幻灯片写出一张图片。 + +### 可编辑的 PPTX 和 ODP {#editable-pptx-and-odp} + +自 v0.5.0 起,PPTX 或 ODP 中的幻灯片是**可编辑的**:marp-cli 的 PPTX 每张幻灯片是一张图片, +其实验性的 `--pptx-editable` 则是借助 LibreOffice 转换 PDF。这里每张幻灯片都依据其布局绘制, +与 PNG 和 PDF 导出所用的布局相同: + +- 每行文本都是一个位于其布局位置的文本框,使用幻灯片排版时所用的字体, + 且**字体内嵌在文件中**,因此在没有 Lato 或 Inter 的机器上看起来也一样 + (已用 LibreOffice 25.8 验证),并保留粗体、斜体、颜色、字间距和可点击的链接; +- 背景、边框和圆角框是形状;渐变在 PPTX 中是渐变填充,在 ODP 中是图片 + (ODF 的渐变只有两种颜色); +- 图片只存储一次,无论有多少张幻灯片显示它; +- 幻灯片无法表达的内容(受 `opacity` 影响的元素、`filter`、 + 模糊阴影、公式)会成为该元素的一张图片,其上保留文本。 + +经 LibreOffice 重新渲染后,幻灯片与 PNG 导出结果的像素差异在 0.04–2 % 之间。 +`--pptx-raster` 和 `--odp-raster` 仍然每张幻灯片一张图片。 + +### 矢量 PDF {#vector-pdf} + +自 v0.4.0 起,PDF 以矢量方式绘制:go-webengine 将排好版的幻灯片绘制到 PDF 中,而不是绘制成像素。 +文本就是文本,使用幻灯片排版时所用的字体,以子集方式内嵌并带有 `/ToUnicode` 映射, +因此可以选中、搜索和复制。背景、边框和渐变是路径和着色,在多张幻灯片上出现的图片只内嵌一次, +链接可以点击。100 张幻灯片的基准演示文稿大小为 758 KiB,marp-cli 为 837 KiB, +v0.3.0 的图片 PDF 为 10 MiB,写出耗时 0.3 s。 + +这里没有矢量形式的元素(`opacity` < 1、带有 `filter` 或旋转、模糊的 `box-shadow`、 +透明度渐变的渐变色)只会将该元素绘制为位图,其上覆盖不可见的文本,以保持可选中。 +公式目前仍是位图。`--pdf-raster` 与 v0.3.0 一样,每页写出一张图片。 + +## `slides.yaml` {#slidesyaml} + +演示文稿旁边的配置文件会作用于该演示文稿,因此一个文件夹中的演示文稿可以共享同一个配置文件: + +```yaml +kinds: kinds.yaml # a mapping of kinds, or file(s) holding one +themes: # theme CSS files or directories + - themes/ +data: # template data + company: Example Inc. +html: false # allow all raw HTML +lang: en +``` + +kind:演示文稿自身的 `kinds:` 优先于配置文件和命令行中的定义。 +数据:配置文件和命令行中的 `data` 优先于演示文稿的 front matter, +因此同一个演示文稿可以用不同的数据构建。 + +## 计划中 {#planned} + +尚未实现:PDF 中的公式矢量化。实现后,会在这里以命令或参数的形式提供。 diff --git a/content/zh/emoji.md b/content/zh/emoji.md new file mode 100644 index 0000000..8705934 --- /dev/null +++ b/content/zh/emoji.md @@ -0,0 +1,73 @@ +--- +title: "Emoji" +weight: 36 +description: "Emoji 短代码和 Unicode emoji 会变成 Twemoji 图片,无需网络:适用于 HTML 页面、图片、PDF、PowerPoint 和 OpenDocument。" +tags: [emoji] +--- + +{{< callout type="warning" >}} +Emoji 自 v0.4.0 起提供;v0.3.0 不支持。 +{{< /callout >}} + +emoji 可以写成短代码,也可以直接写字符本身: + +```markdown +# Launch :rocket: 🎉 + +Shortcodes :smile: :+1: :heart_eyes: and Unicode 😄 👍🏽 👩‍💻 🇫🇷 1️⃣. +``` + +每个 emoji 都会变成 Marp Core 所绘制的 [Twemoji](https://github.com/jdecked/twemoji) 图片, +标记与 Marp Core 写出的相同: + +```html +<img class="emoji" draggable="false" alt="😄" src="data:image/svg+xml;base64,…" data-marp-twemoji=""/> +``` + +## 无需网络 {#no-network} + +Marp Core 的 `src` 是 CDN 上的一个 URL,因此离线展示、打印,或在没有网络连接的机器上转换为 PDF 的演示文稿, +都会显示破损的图片。go-slides 则把 SVG 本身放进 `data:` URI。这些图形(共 4,009 个, +Twemoji 17.0.3,Emoji 17.0)来自纯 Go 模块 +[go-icons/twemoji](https://github.com/go-icons/twemoji),内嵌在程序中,压缩后 2 MB, +只有在演示文稿包含 emoji 时才会读取。 + +HTML 页面、`--images`/`--image`、`--pdf`、`--pptx` 和 `--odp` 都同样会绘制这些图片: +由 go-webengine 对 SVG 进行栅格化。全部 4,009 个图形都能被其 SVG 栅格化器解析和绘制; +有测试对此进行检查。自 v0.5.0 起,该栅格化器为 [go-gfx/oksvg](https://github.com/go-gfx/oksvg) +v0.2.0:`clipPath`、`fill-rule: evenodd` 和圆弧命令现在的绘制效果与 Chrome 一致, +这改变了一个 Twemoji 和另外 211 个图形,SVG 徽标也不再错乱。 + +让 emoji 与文字等大的规则(`img[data-marp-twemoji]`,一个 `em` 见方,与基线对齐) +会像 Marp Core 那样添加到主题中,因此你自己的主题也能用上。 + +## 转换范围 {#what-is-converted} + +规则与 Marp Core 相同;有测试将其与 Marp Core 4.4.0 为一个涵盖所有情况的演示文稿写出的 HTML 进行比较。 + +| | | +| --- | --- | +| `:smile:`、`:+1:`、`:heart_eyes:` | markdown-it-emoji 的 1,903 个名称,适用于正文、标题、链接、表格、`header` 和 `footer`;不适用于代码、URL 或图片的替代文本 | +| `😄`、`👩‍💻`、`🇫🇷`、`1️⃣`、`👍🏽` | Unicode emoji,适用于正文**以及代码**:Marp Core 会改写渲染后的代码 HTML | +| `✔`、`©`、`™`、`▶` | 保留为文本:默认以文本形式呈现的字符,只有带 U+FE0F(`✔️`)时才是 emoji;`❤` 是例外 | +| `☺︎` | 保留为文本:emoji 后的 U+FE0E 表示要求使用普通字形 | +| `12:30:45`、`:not_a_name:` | 保持原样 | + +标题的 `id` 和书签大纲使用的是 emoji 本身,而不是短代码的名称: +`# Launch :rocket:` 的 id 为 `launch-%F0%9F%9A%80`,与 Marp 相同。 + +go-slides 与 Marp Core 的不同之处在于它转换得更多:`✌️ ☝️ ✍️ 🏋️`,以及其他一些 +Marp Core 的 emoji-regex 保留为文本的默认文本字符,还有少数 Twemoji 能绘制但 Unicode 未列出的序列。 + +## 关闭 {#turning-it-off} + +`--no-emoji`(在代码中为 `slides.Config{NoEmoji: true}`)会将短代码和 +Unicode emoji 保留为书写时的文本,相当于 Marp Core 的 +`emoji: false`。 + +## 署名 {#attribution} + +这些图形来自 Twemoji,Copyright 2020 Twitter, Inc and other contributors, +采用 [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) 许可, +由 [jdecked/twemoji](https://github.com/jdecked/twemoji) 项目继续维护。 +展示这些图形的演示文稿应注明出处,例如在页脚或结尾幻灯片中。短代码表来自 markdown-it-emoji(MIT)。 diff --git a/content/zh/gallery.md b/content/zh/gallery.md new file mode 100644 index 0000000..727f2ca --- /dev/null +++ b/content/zh/gallery.md @@ -0,0 +1,22 @@ +--- +title: "示例库" +weight: 12 +description: "十二个小型演示文稿,各自展示一项功能:主题、kind、模板、HCL、数学公式、图片、代码、emoji、过渡效果,以及一场完整的演讲。每一个都可以在 Playground 中打开。" +tags: [gallery, examples] +--- + +十二个小型演示文稿,来自 go-slides 的 +[`examples/`](https://github.com/go-slides/slides/tree/main/examples) 文件夹, +每个都着重展示一项功能。缩略图是每个演示文稿的第一张幻灯片,在构建本站点时由 go-slides 自己渲染; +CI 会将每个演示文稿构建为 HTML 和 PDF,确保它们都不会损坏。 + +**在 Playground 中打开** 会在你的浏览器中加载演示文稿,所用的链接不会向任何服务器发送数据。 +除 `deck.md` 外还有其他文件(kind 文件、引入的片段、图片)的演示文稿无法通过链接传递: +请下载它的 **.zip**,再通过 Playground 的文件面板导入;或者解压后用 +`slides build --allow-local-files deck.md` 构建。 + +图片是 go-slides 作者原创的 SVG,以 +[CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/) 发布, +使用这些图片的演示文稿中也有说明。 + +{{< gallery >}} diff --git a/content/zh/getting-started.md b/content/zh/getting-started.md new file mode 100644 index 0000000..c0a6668 --- /dev/null +++ b/content/zh/getting-started.md @@ -0,0 +1,126 @@ +--- +title: "快速上手" +weight: 10 +description: "第一个演示文稿:构建为 HTML,再展开回 Marp Markdown。" +tags: [start] +--- + +## 安装 {#install} + +需要 Go 1.27.2 或更高版本: + +```sh +go install github.com/go-slides/slides/cmd/slides@latest +``` + +`slides` 是一个不使用 cgo 的静态 Go 二进制文件:无需安装其他任何东西, +不需要 Node,也不需要 Chrome。本页描述的是 v0.9.0。 + +## 第一个演示文稿 {#a-first-deck} + +任何 Marp 演示文稿都可以直接使用。将以下内容保存为 `talk.md`: + +```markdown +--- +marp: true +theme: default +paginate: true +footer: "{{ .talk }}" +data: + talk: Pure Go slides + topics: [Why, How, Next] +--- + +<!-- _kind: title --> +# {{ .talk }} + +{{ range .topics }} +--- + +# {{ . }} +{{ end }} +``` + +front matter 与 Marp 相同,只是多了 `data:`。正文是一个 Go 模板: +`range` 为每个主题生成一张幻灯片,每张都以 `---` 开头;第一张幻灯片使用内置的 kind(幻灯片类型) +`title`(没有页眉、页脚和页码)。 + +## 构建 {#build-it} + +```sh +slides talk.md # writes talk.html beside the deck +slides -o slides.html talk.md # or somewhere else; -o - for standard output +``` + +不带命令的 `slides` 即 `slides build`。参数可以写在演示文稿之前或之后。 +用浏览器打开生成的 HTML;用键盘即可在幻灯片之间切换。 + +## 导出图片、PDF、PPTX 和 ODP {#export-images-pdf-pptx-and-odp} + +```sh +slides talk.md --pdf # one page per slide, with an outline +slides talk.md --images png # talk.001.png, talk.002.png, … +slides talk.md --image jpeg # the first slide only +slides talk.md -o out/slides.pdf # the extension picks the format; out/ is created +slides talk.md --images jpeg --jpeg-quality 90 +slides talk.md --images png --image-scale 2 # 2560×1440 for 16:9 +slides talk.md --pdf --allow-local-files # images from under the deck's directory +slides talk.md --pptx # or -o talk.pptx, with notes +slides talk.md --odp # or -o talk.odp: the same, as OpenDocument +``` + +整个过程不涉及浏览器:go-webengine 渲染幻灯片,go-pdfkit 写出 PDF。 +除非指定 `--allow-local-files`,否则会拒绝本地图片。 +自 v0.5.0 起,PPTX 或 ODP 中的幻灯片是可编辑的(文本框、形状和图片,并内嵌字体), +而 marp-cli 生成的是图片;`--pptx-raster` +仍生成图片([可编辑的 PPTX 和 ODP]({{< relref "cli#editable-pptx-and-odp" >}}))。自 v0.4.0 起 PDF 为矢量格式,文本可选中 +(v0.3.0 每页写出一张图片)。参见[命令行]({{< relref "cli#vector-pdf" >}})。 + +## 展开 {#expand-it} + +```sh +slides expand talk.md +``` + +会将同一个演示文稿输出为**纯 Marp Markdown**(`main` 在 +`4ae5c49`,2026-10-18 的输出): + +```markdown +--- +marp: true +theme: default +paginate: true +footer: "Pure Go slides" +--- +<!-- +_class: "lead" +_footer: "" +_paginate: "false" +--> + + +# Pure Go slides + + +--- + +# Why + +--- + +# How + +--- + +# Next +``` + +可以看到 `range` 生成的四张幻灯片、填入了值的页脚,以及 +`title` 这个 kind 已被替换为它所代表的单页指令。任何能读取 Marp 的工具(marp-cli、VS +Code 扩展)都可以接着处理它,因此用 go-slides 的扩展功能编写的内容不会被锁定。 + +## 下一步 {#next} + +- [Marp 兼容性]({{< relref "marp" >}}):哪些内容的读取方式与 Marp 相同。 +- [模板]({{< relref "templates" >}})与 [Kind]({{< relref "kinds" >}}):两项扩展功能。 +- [命令行]({{< relref "cli" >}}):所有命令和参数。 diff --git a/content/zh/hcl.md b/content/zh/hcl.md new file mode 100644 index 0000000..a7960a4 --- /dev/null +++ b/content/zh/hcl.md @@ -0,0 +1,151 @@ +--- +title: "HCL" +weight: 45 +description: "凡是演示文稿接受 YAML 的地方都可以使用 HCL2:HCL front matter、slides.hcl、kind 文件和数据文件,支持表达式和函数。" +tags: [hcl, configuration] +--- + +{{< callout type="warning" >}} +HCL 自 v0.3.0 起提供([go-slides/slides#13](https://github.com/go-slides/slides/pull/13))。 +{{< /callout >}} + +凡是 `slides` 读取 YAML 的地方,它也能读取 +[HCL2](https://github.com/hashicorp/hcl/blob/main/hclsyntax/spec.md) 的原生语法, +以便使用其表达式和函数。 + +| 位置 | YAML | HCL | +| --- | --- | --- | +| front matter | `---` … `---` | `---hcl` … `---` | +| 配置文件 | `slides.yaml` | `slides.hcl`,优先查找 | +| kind 文件 | `--kinds kinds.yaml`、`kinds: kinds.yaml` | 任何名为 `*.hcl` 的文件 | +| 数据文件 | `--data data.yaml` 或 `.json` | 任何名为 `*.hcl` 的文件 | + +与 Marp 一样,指令注释(`<!-- class: invert -->`)仍然使用 YAML, +演示文稿正文仍然是 [Go 模板]({{< relref "templates" >}})。 + +## 用 HCL 编写 front matter {#a-front-matter-in-hcl} + +```hcl +---hcl +theme = "gaia" +paginate = true +title = "Quarterly review" +data = { + company = "ACME" + quarter = 3 +} +header = format("%s — Q%d", data.company, data.quarter) +footer = "${upper(data.company)} · ${page()}/${total()}" +date = formatdate("D MMMM YYYY", now()) + +kind "chapter" { + extends = "section" + class = ["lead", "invert"] +} +--- + +# {{ .title }} +``` + +- **属性** 与同名的 YAML 键对应:Marp 的指令 + (`theme`、`paginate`、`header`、`footer`、`class`、`backgroundColor`…)、 + 元数据(`title`、`author`…)、`template`、`delims`、`kinds`(文件路径) + 以及 `data`(一个对象)。 +- **`kind "<name>" { … }`** 块用于定义 [kind(幻灯片类型)]({{< relref "kinds" >}}), + 可包含局部指令和 `extends`。 +- **值** 以 YAML 给出的字符串形式传给指令: + `true` 即 `"true"`,class 列表仍是列表,因此无论用哪种语法设置,指令的行为都相同。 + `null` 表示不设置该指令。 +- **`data`** 保留其类型:在模板中,整数是 `int`, + 其他数字是 `float64`,列表和对象仍是列表和 map,因此 + `{{ add .quarter 1 }}` 可以正常工作。 +- HCL 的值是最终值:Go 模板不会再对其执行第二遍。 + +## 变量 {#variables} + +`data` 就是模板数据:front matter 自身的 `data`,依次被 +`slides.hcl` 中的、`--data` 文件中的以及 `--set` 中的数据覆盖。凡是能用 +`{{ .company }}` 的地方,都可以用 `"${data.company}"`。 + +front matter 的属性可以引用 `data.*`,但**不能相互引用**: +`footer = title` 会报错。单页指令(`_class`…)不是合法的 HCL +标识符;请把它们写在注释中。 + +## 函数 {#functions} + +| | | +| --- | --- | +| `page()`、`total()` | 页码和最后一页的页码,相当于 `{{ page }}` 和 `{{ total }}`:页脚可以显示页码 | +| `now()`、`timestamp()` | 当前时间,RFC 3339 格式,分别为本地时间和 UTC;可用 `formatdate` 格式化 | +| `env(name)` | 环境变量,未设置时为 `""` | +| `file(path)` | 一个不超过 1 MiB 的 UTF-8 文本文件,路径相对于演示文稿或配置文件 | +| 数字 | `abs ceil floor log max min parseint pow signum` | +| 字符串 | `chomp format formatlist join lower regex regexall regexreplace replace split strlen strrev substr title trim trimprefix trimspace trimsuffix upper` | +| 集合 | `chunklist coalesce coalescelist compact concat contains distinct element flatten keys length lookup merge range reverse slice sort values zipmap` | +| 编码 | `csvdecode jsondecode jsonencode` | +| 日期 | `formatdate timeadd` | + +除前四行外,其余均来自 [go-cty](https://github.com/zclconf/go-cty) 的标准库。 +所有函数都不会访问网络。 + +## slides.hcl {#slideshcl} + +```hcl +kinds = "kinds.hcl" # or a list of files +themes = ["themes/acme.css"] +data = { company = "ACME" } # or file paths +html = false +lang = "en" + +kind "quote" { + extends = "blank" + footer = "${data.company}" +} +``` + +未知属性会报错,而不是被静默忽略。kind 文件包含 +`kind` 块,其他属性会被忽略,因此 `slides.hcl` 本身也可以充当 kind 文件。 +数据文件的属性就是数据的键: + +```hcl +# data.hcl +company = "ACME" +year = 2026 +motto = trimspace(file("motto.txt")) +``` + +## 错误 {#errors} + +错误信息就是 HCL 的诊断信息,定位到演示文稿文件本身: +在演示文稿第四行写 `footer = data.compnay`,会得到 + +``` +deck.md:4:14: Unsupported attribute: This object does not have an attribute named "compnay". +``` + +## Marp 与 `slides expand` {#marp-and-slides-expand} + +Marp 同样会把 `---hcl` 当作 front matter 的开头(它的 front matter +规则会在任何以三个短横线开头的第一行开启),但由于该块不是 YAML 映射, +其中的内容一概不会应用:演示文稿在 Marp 中仍能渲染,只是没有这些指令。 +有测试针对 Marp Core 自身的输出固定了这一行为。 + +`slides expand` 会把 front matter 写回为 YAML,因此展开后的演示文稿是纯 Marp, +渲染结果与 `slides` 渲染原文件时相同: + +```yaml +--- +theme: "gaia" +paginate: true +title: "Quarterly review" +header: "ACME — Q3" +footer: "ACME · 1/2" +date: "19 October 2026" +--- +``` + +## 许可证 {#licences} + +HCL 支持链接了 [hashicorp/hcl](https://github.com/hashicorp/hcl) +(MPL-2.0,作为库原样使用,未作修改)和 +[zclconf/go-cty](https://github.com/zclconf/go-cty)(MIT)。 diff --git a/content/zh/kinds.md b/content/zh/kinds.md new file mode 100644 index 0000000..3652bbb --- /dev/null +++ b/content/zh/kinds.md @@ -0,0 +1,89 @@ +--- +title: "Kind" +weight: 40 +description: "幻灯片声明自己是什么:kind 是一组命名的局部指令预设。" +tags: [kinds] +--- + +**kind**(幻灯片类型)是一组命名的局部指令预设:页眉、页脚、 +页码、class、背景。幻灯片声明自己是什么(`title`、 +`section`),而不必反复描述自己的外观;要修改整个演示文稿的外观, +只需改一处。 + +## 使用 kind {#using-a-kind} + +```markdown +<!-- kind: section --> +# Part two + +--- + +<!-- _kind: blank --> +![bg](photo.jpg) +``` + +- `kind:` 的作用方式与局部指令相同:作用于当前幻灯片及其后的幻灯片。 +- `_kind:` 的作用方式与单页指令相同:只作用于当前幻灯片。 +- 切换 kind 时,会先**清除**上一个 kind 设置的内容,因此 class 或 + 页脚永远不会从一个 kind 泄漏到下一个。 +- 直接写在幻灯片上的指令**优先于** kind。 + +## 内置 kind {#built-in} + +| Kind | 设置内容 | +| --- | --- | +| `title` | `class: lead`,无页码、无页眉、无页脚 | +| `section` | `class: lead` | +| `content` | 不设置任何内容:使用演示文稿自身的默认值 | +| `blank` | 无页码、无页眉、无页脚 | + +它们都可以重新定义。 + +## 定义 kind {#defining-kinds} + +在 front matter 中: + +```yaml +--- +marp: true +kinds: + chapter: + class: invert + backgroundColor: "#1e1b4b" + paginate: false + quote: + extends: blank + class: quote +--- +``` + +`extends` 让一个 kind 以另一个 kind 的指令为起点。kind 可以包含 +局部指令和 `extends`。 + +## 共享 kind {#sharing-kinds} + +团队的 kind 应放在一个文件中,供每个演示文稿引用: + +```yaml +# kinds.yaml +chapter: + class: invert + paginate: false +``` + +```yaml +--- +marp: true +kinds: kinds.yaml # or a list of files +--- +``` + +也可以在命令行中使用 `--kinds kinds.yaml`,或在 `slides.yaml` 中使用 `kinds:`。 +优先顺序为:先是内置 kind,然后是命令行和配置文件,最后是演示文稿自身的 `kinds:`。 + +`slides kinds talk.md` 会列出演示文稿可用的 kind 以及每个 kind 设置的内容。 + +## 离开 go-slides {#leaving-go-slides} + +`slides expand` 会逐张幻灯片地把每个 kind 写成它所代表的单页指令, +因此结果是任何 Marp 工具都能读取的纯 Marp Markdown。 diff --git a/content/zh/marp.md b/content/zh/marp.md new file mode 100644 index 0000000..8e15253 --- /dev/null +++ b/content/zh/marp.md @@ -0,0 +1,76 @@ +--- +title: "Marp 兼容性" +linkTitle: "Marp" +weight: 20 +description: "slides 读取的 Markdown 就是 Marpit 和 Marp Core 的 Markdown,而非其方言。" +tags: [marp] +--- + +为 [Marp](https://marp.app) 编写的演示文稿就是 go-slides 演示文稿。下面的规则来自 +[Marpit](https://marpit.marp.app) 和 +[Marp Core](https://github.com/marp-team/marp-core);凡是 go-slides +有所不同的地方,本页都会注明。 + +## 幻灯片 {#slides} + +水平分隔线(单独一行的 `---`,与 Marpit 的分隔方式相同)开始一张新幻灯片, +`headingDivider` 指令所指定级别的标题也会开始新幻灯片。front matter 位于最前面的两个 +`---` 之间,包含全局指令和演示文稿的数据。 + +## 指令 {#directives} + +| 类型 | 位置 | 作用范围 | +| --- | --- | --- | +| 全局 | front matter,或任意位置的 `<!-- theme: gaia -->` | 整个演示文稿:`theme`、`style`、`headingDivider`、`lang`… | +| 局部 | `<!-- paginate: true -->` | 当前幻灯片及其后的幻灯片 | +| 单页 | `<!-- _class: lead -->`(下划线开头) | 仅当前幻灯片 | + +局部指令与 Marpit 相同:`paginate`、`header`、`footer`、`class`、 +`backgroundColor`、`backgroundImage`、`backgroundPosition`、 +`backgroundRepeat`、`backgroundSize`、`color`。与 Marp Core 一样, +指令按宽松 YAML 解析;`--strict-yaml` 则与原生 Marpit 一样严格解析。 + +## 图片 {#images} + +`![bg](image.jpg)` 将图片设为幻灯片背景,支持 Marp 的关键字: +`bg contain`、`bg cover`、`bg 50%`、用于分栏背景的 `bg left`/`bg right`、 +多张并排的 `bg` 图片,以及图片滤镜(`blur`、`sepia`、`grayscale`…)。 +行内图片尺寸(`w:200px`、`h:`)的解释方式与 Marp 相同。 + +## 幻灯片周边 {#around-the-slide} + +- **页眉和页脚**:`header:` 和 `footer:` 指令,内容为 Markdown。 +- **页码**:`paginate: true`。 +- **演讲者备注**:不是指令的 HTML 注释就是备注。 + `slides notes` 可输出备注。 +- **逐项显示列表**:用 `*`(或 `1)`)编写的列表会逐项出现, + 与 Marp 相同。 +- **`<style scoped>`**:只作用于一张幻灯片的 CSS。 +- **原始 HTML**:默认使用 Marp Core 的白名单;`--html` 允许所有 HTML。 +- **Emoji**:`:shortcodes:` 和 Unicode emoji 会变成 Twemoji 图片,与 + Marp Core 相同,但图片是内嵌的,而不是从 CDN 加载:参见 + [Emoji]({{< relref "emoji" >}})。 + +## 主题 {#themes} + +内置 `default`、`gaia` 和 `uncover` 主题,通过 `theme:` 选择。 +自定义主题是一个带有 Marp 的 `/* @theme name */` 注释的 CSS 文件, +通过 `--theme-set` 指定,或列在 `slides.yaml` 的 `themes:` 下。 + +## go-slides 的不同之处 {#where-go-slides-differs} + +- **模板。** 正文中的 `{{ }}` 是 Go 模板动作(参见 + [模板]({{< relref "templates" >}}))。如果演示文稿要在代码块之外把 `{{` 作为文本显示, + 需要使用 `--no-template`,或按 Go 模板的方式转义该文本。代码块和行内代码不受影响。 +- **Kind。** `kind:` 和 `_kind:` 是 Marp 没有的指令(参见 + [Kind]({{< relref "kinds" >}}))。`slides expand` 会将它们替换为其所代表的指令, + 因此展开后的演示文稿是纯 Marp。 +- **输出格式。** HTML、PNG、JPEG、PDF、PPTX 和 ODP,由 Go 库生成,而不是无头浏览器 + (已知限制参见[命令行]({{< relref "cli#images-pdf-pptx-and-odp" >}}))。 + 在 v0.4.0 中,PPTX 与 marp-cli 一样由图片组成;自 + v0.5.0 起,其幻灯片由可编辑的文本框和形状组成,`--pptx-raster` 则保留图片形式。 +- **自适应标题。** 支持 `# <!-- fit --> Title`:标题在一行内撑满幻灯片宽度, + 在页面和所有导出格式中均有效。 +- **数学公式与代码。** `$…$` 和 `$$…$$` 遵循 Marp Core 的定界符规则, + 由 go-tex/math 排版;围栏代码由 go-rouge 高亮,使用 + highlight.js 的类名(参见[数学公式与代码]({{< relref "math-and-code" >}}))。 diff --git a/content/zh/math-and-code.md b/content/zh/math-and-code.md new file mode 100644 index 0000000..512bbdc --- /dev/null +++ b/content/zh/math-and-code.md @@ -0,0 +1,130 @@ +--- +title: "数学公式与代码" +weight: 35 +description: "美元符号之间的公式会被排版,围栏代码会被高亮,适用于 HTML 页面、图片和 PDF。" +tags: [math, code] +--- + +{{< callout type="warning" >}} +数学公式与代码自 v0.2.0 起提供。 +{{< /callout >}} + +## 数学公式 {#math} + +`$…$` 是行内公式,`$$…$$` 是独立公式: + +```markdown +The famous one is $E=mc^2$, and this is a display formula: + +$$ +\int_0^\infty e^{-x^2}\,dx = \frac{\sqrt{\pi}}{2} +$$ +``` + +- 定界符规则与 Marp Core 相同(依据 marp-core 4.4.0):后面跟空格的 `$` + 不会开启公式,后面跟数字的美元符号不会结束公式,因此像 `$5 and $10` 这样的价格会保留为文本。 +- [go-tex/math](https://github.com/go-tex/math) 将每个公式排版为以 `em` 为单位的行内 SVG, + 因此它会随文字大小变化,并位于文字的基线上。HTML 页面、图片和 PDF 使用的是同一份 SVG。 +- 无法解析的公式会按原样显示;错误信息保存在其 `title` 中。 + +## 代码 {#code} + +围栏代码块由 [go-rouge](https://github.com/go-rouge/rouge) 着色, +它输出 highlight.js 的类名,也就是 Marp 主题所设置样式的那些类名: + +````markdown +```go +fmt.Println("hello") +``` +```` + +go-rouge 不认识的语言会按纯文本输出,与 Marp Core 相同。 + +### 行号 {#line-numbers} + +从 v0.8.0 起,代码块可以为各行编号。Marp Core 没有行号功能, +并且会忽略围栏中语言名之后的内容,所以这些选项写在那里的花括号中, +使用了它们的演示文稿在 Marp 中的渲染效果与不使用时相同: + +````markdown +```go {lineNumbers} +func main() { + fmt.Println("hello") +} +``` + +```go {lineNumbers=10} +// starts at 10 +``` +```` + +| 写法 | 含义 | +| --- | --- | +| `{lineNumbers}` | 从 1 开始编号(也支持 Slidev 的 `{lines:true}` 和 Pandoc 的 `{.numberLines}`) | +| `{lineNumbers=10}` | 从 10 开始编号(也可写作 `startLine=10`,或 Pandoc 的 `startFrom="10"`) | +| `{lineNumbers=false}` | 不编号,无论幻灯片如何设置 | + +要对一张幻灯片及其后幻灯片中的所有代码块生效,请设置指令: +`<!-- lineNumbers: true -->`(只对一张幻灯片生效则用 `_lineNumbers`)。 +kind(幻灯片类型)可以为一类幻灯片统一设置: + +```yaml +kinds: + listing: {lineNumbers: true} +``` + +每个行号位于主题代码框内对应行的开头,并以一条分隔线隔开。 +即使该行较高(例如带有代码标注或 emoji),行号也始终位于该行的基线上。 +行号使用由代码颜色和背景色混合而成的柔和颜色,在 default、 +gaia 和 uncover 主题的浅色和深色模式下,对比度均不低于 4.5:1。行号在页面中**不可选中**, +屏幕阅读器也不会朗读。在 PDF 中,行号会被绘制,但不计入文本,因此文本提取工具只会读到代码本身。 +在 PowerPoint 或 OpenDocument 文件中,行号是每行开头的可编辑文本。 +不使用该选项的演示文稿,渲染结果与以前逐字节相同。目前尚不支持高亮指定的行。 + +### 代码标注 {#code-callouts} + +从 v0.8.0 起,代码行末尾可以带一个编号圆标,指向代码块下方的一条说明。语法采用 +[Quarto](https://quarto.org/docs/authoring/code-annotation.html) 的写法, +这也是 Asciidoctor 的注释形式。标记是代码自身语言中的注释,因此代码仍然有效; +说明则是紧跟在代码块之后的有序列表: + +````markdown +```go +func main() { // <1> + x := load() // <2> + fmt.Println(x) +} +``` + +1. The entry point. +2. Reads the input. +```` + +| 写法 | 含义 | +| --- | --- | +| `// <1>`, `# <1>`, `-- <1>`, `; <1>`, `% <1>`, `' <1>` | 只包含标记的行注释:注释和标记一起被替换为圆标 | +| `<!-- <1> -->`, `/* <1> */` | 同上,适用于使用包围式注释的语言 | +| `// <1> <2>` | 同一行上的两个圆标 | +| `x = 1 # count <1>` | 注释文字保留,圆标跟在其后 | + +只有当紧跟在代码块之后的列表从 1 开始、并且每个编号都有对应的列表项时,标记才会变成圆标; +否则代码块按原样保留。在 default、gaia 和 uncover 主题的浅色或深色模式下, +圆标都是与代码同色的圆环。列表项也使用同样的圆标编号。 + +圆标**不可选中**。从页面复制代码时,得到的是不含标记的代码。在 PDF 中,圆标是不计入文本的矢量图形; +在 PowerPoint 或 OpenDocument 文件中,圆标是可编辑的形状。代码标注可以与行号同时使用。 +Marp Core 没有代码标注功能(marp-core 4.4.0),因此在 Marp 中,注释和编号列表会按原样显示。 +不使用代码标注的演示文稿,渲染结果与以前逐字节相同。 + +从 v0.9.0 起,在 HTML 页面中,圆标与其说明可以相互关联。将指针悬停在圆标上, +或用 Tab 键移到圆标上,其说明会被着色;说明对其圆标也是如此。这与 Quarto 的 +`code-annotations: hover` 效果相同,但这里完全由样式表实现,不需要脚本。 +因此,它在 Playground 的沙盒预览和 VS Code 的预览中(两者都限制脚本)效果相同。 +每个圆标都以其说明作为描述,因此屏幕阅读器读到圆标时会朗读对应的说明。 +只有在读者未要求减弱动态效果时,着色才会以渐显方式出现。图片、PDF、PowerPoint 和 +OpenDocument 文件保持不变,逐字节相同。 + +## 字体 {#fonts} + +内嵌了 Lato Regular、Bold、Black 及其斜体,以及 Roboto Mono Regular、Bold 及其斜体, +因此 gaia 的标题能获得应有的字重,斜体文本也能正确显示为斜体。 diff --git a/content/zh/performance.md b/content/zh/performance.md new file mode 100644 index 0000000..8e2dd98 --- /dev/null +++ b/content/zh/performance.md @@ -0,0 +1,84 @@ +--- +title: "性能" +weight: 60 +description: "go-slides 构建演示文稿有多快、占用多少内存,在同一台机器上与 marp-cli 对比测量。" +tags: [performance] +--- + +{{< callout type="info" >}} +下文的 PPTX 数据针对 v0.5.0,矢量 PDF 数据针对 v0.4.0。 +第一张表针对 v0.3.0, +即当时的上一个版本,该版本并行渲染幻灯片,并且只解析一次字体 +([go-slides/slides#11](https://github.com/go-slides/slides/pull/11))。 +{{< /callout >}} + +go-slides 和 [marp-cli](https://github.com/marp-team/marp-cli) 4.5.1 +以子进程方式在相同的演示文稿上运行,机器为 Apple M4 Max(16 个 CPU), +1 分钟平均负载低于 8,预热后交错进行五轮测试。 +时间为实际耗时(wall time)的中位数;内存为整个进程树的峰值 +(marp-cli 会驱动一个 Chrome,其进程也计算在内)。 + +| 演示文稿 | 输出 | go-slides | marp-cli | go-slides 内存 | marp-cli 内存 | +| --- | --- | ---: | ---: | ---: | ---: | +| 100 张幻灯片 | HTML | 36 ms | 315 ms | 47 MiB | 140 MiB | +| 100 张幻灯片 | PDF | 1.14 s | 1.88 s | 242 MiB | 1,509 MiB | +| 100 张幻灯片 | PNG,全部幻灯片 | 0.97 s | 5.44 s | 225 MiB | 1,354 MiB | +| 100 张幻灯片 | PPTX | 3.39 s | 8.03 s | 530 MiB | 1,373 MiB | +| gaia,20 张幻灯片 | PDF | 283 ms | 1.57 s | 154 MiB | 1,315 MiB | +| 数学公式与代码,20 张幻灯片 | PNG,全部幻灯片 | 250 ms | 2.32 s | 168 MiB | 1,286 MiB | + +## 矢量 PDF {#vector-pdf} + +从 v0.4.0 起,PDF 以矢量方式绘制(参见 +[命令行]({{< relref "cli#vector-pdf" >}}))。机器和方法相同, +测于 2026-10-19: + +| 演示文稿 | go-slides | marp-cli | v0.3.0 | go-slides 大小 | marp-cli 大小 | v0.3.0 大小 | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 100 张幻灯片 | 306 ms | 1,763 ms | 1,147 ms | 758 KiB | 837 KiB | 10.0 MiB | +| gaia,20 张幻灯片 | 93 ms | 1,546 ms | 285 ms | 32 KiB | 66 KiB | 582 KiB | +| 数学公式与代码,20 张幻灯片 | 140 ms | 1,643 ms | 294 ms | 124 KiB | 335 KiB | 638 KiB | + +100 张幻灯片 PDF 的内存占用:158 MiB,marp-cli 为 1,512 MiB;CPU +时间 0.9 s,marp-cli 为 2.7 s。 + +v0.4.0 的 100 张幻灯片 PDF 数据测于 2026-10-19 21:29, +1 分钟平均负载为 7.7,共三轮:go-slides 306 ms、758 +KiB、158 MiB;marp-cli 1,838 ms、837 KiB、1,512 MiB。 + +## 可编辑 PPTX {#editable-pptx} + +自 v0.5.0 起,PPTX 由文本框、形状和图片组成(参见 +[命令行]({{< relref "cli#editable-pptx-and-odp" >}}))。测于 +2026-10-19 22:51,1 分钟平均负载为 7.1,共三轮: + +| 演示文稿 | go-slides | marp-cli | go-slides 大小 | marp-cli 大小 | go-slides 内存 | marp-cli 内存 | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 100 张幻灯片 | 348 ms | 7,795 ms | 760 KiB | 7.2 MiB | 204 MiB | 1,370 MiB | +| gaia,20 张幻灯片 | 112 ms | 4,608 ms | 893 KiB | 4.8 MiB | | | +| 数学公式与代码,20 张幻灯片 | 207 ms | 2,780 ms | 842 KiB | 1.5 MiB | | | + +对于 100 张幻灯片的演示文稿,v0.4.0 耗时 4.7 s,大小 4.8 MiB,内存 540 MiB。marp-cli 的 +PPTX 由图片组成。可编辑演示文稿的体积大部分来自内嵌字体。 + +## 为什么快 {#why-it-is-fast} + +- **无需启动浏览器。** marp-cli 每次导出 PDF、图片和 + PPTX 时都要启动 Chrome;go-slides 则在自己的进程内用 + [go-webengine](https://github.com/go-webengine/engine) 渲染。 +- **幻灯片并行渲染**,默认同时渲染四张,每张图片一轮到就立即写出: + 长篇演示文稿永远不会以图片形式整体驻留在内存中。 +- **字体每个进程只解析一次**,而不是每张幻灯片解析一次。 + +## 不占优势的方面 {#what-it-does-not-win} + +- **v0.3.0 的 PDF 大小。** 当时 PDF 中每张幻灯片都是一张图片:100 张幻灯片的演示文稿为 10 MiB, + marp-cli 为 837 KiB。上文的矢量 PDF 已经比 marp-cli 的更小。 +- **大型演示文稿的 CPU 时间。** 四个工作线程消耗的 CPU 时间多于实际耗时所显示的: + 在 v0.3.0 中,100 张幻灯片的 PDF 为 3.2 s,marp-cli 为 2.7 s + (矢量 PDF 为 0.9 s)。 +- **marp-cli 的内存是一个上限值**:Chrome 的各进程共享内存页,而求和时每个进程都计入了这些页。 + 其中最大的单个进程为 220–320 MiB。 + +测试方法、每个演示文稿和模式,以及复现这些数据的命令,见 +[BENCHMARKS.md](https://github.com/go-slides/slides/blob/main/BENCHMARKS.md)。 diff --git a/content/zh/playground.md b/content/zh/playground.md new file mode 100644 index 0000000..97abba7 --- /dev/null +++ b/content/zh/playground.md @@ -0,0 +1,145 @@ +--- +title: "Playground" +weight: 15 +description: "在浏览器中编写演示文稿,幻灯片实时跟随:编译为 WebAssembly 的 go-slides,带自动补全、帮助和示例。" +tags: [playground, browser] +--- + +**[打开 Playground →](https://go-slides.github.io/playground/)** + +Playground 就是 go-slides 本身(v0.9.0),编译为 WebAssembly 后在你的浏览器中运行。 +在左侧输入演示文稿,右侧的幻灯片会随之更新。不会向服务器发送任何内容:演示文稿不会,分享链接也不会。 + +## 功能 {#what-it-has} + +- **实时预览** 每张幻灯片,渲染在页面主线程之外进行(Web + Worker),因此输入时永远不必等待。点击一张幻灯片,编辑器会跳到对应的行; + 移动光标,预览会滚动到对应的幻灯片。 +- **放映**:即演示本身,支持键盘、屏幕控制器和过渡效果。 +- **演讲者视图**:**Present**(或在放映中按 `p`)会在第二个窗口中打开它: + 当前幻灯片、下一步、演讲者备注(演示文稿中的 `<!-- comments -->`)、时钟和计时器。 + 方向键、Space、Home 和 End 在两个窗口中都可使用,两个窗口相互同步, + 编辑演示文稿时也是如此。如果窗口没有打开,请允许该站点弹出窗口。 + 概览视图(`o`)仅在下载的页面中提供。 +- **自动补全**,边输入边提示:指令键(注释中的单页指令 `_`)、 + `kind`(幻灯片类型)、`theme`、`size`、`paginate` 的取值,以及 `{{ }}` 内的模板函数和数据字段。 +- **帮助**,针对光标下的内容:指令、kind(及其设置的内容)、模板函数。 + 帮助面板中也有指向本文档各页的链接。 +- **问题** 显示在对应的行上:模板、YAML 和 HCL 错误会在编辑器中高亮, + 并在其下方列出,预览则保留最后一次成功渲染的演示文稿。 +- **示例**:欢迎演示文稿、生成幻灯片的模板、幻灯片 kind、 + HCL front matter、主题与单页指令,以及数学公式、图片和背景 + (`![bg]`、分栏的 `![bg left:40%]`、滤镜)。 +- **文件**:编辑器左侧的面板就是演示文稿的项目,其结构与 + [命令行工具]({{< relref "cli" >}})和 + [VS Code 扩展]({{< relref "vscode" >}})的读取方式一致:`deck.md`、 + `slides.yaml`、`themes/`、`kinds/`、`media/`。 + - 点击文件即可编辑。编辑器会按文件语言着色 + (Markdown、YAML、HCL、JSON、CSS),并标出其中的问题。 + - 右键(或 Shift+F10)可使用 New File、New Folder、Rename(F2)、 + Delete(会先确认)以及 New theme / New kind。把文件拖到文件夹上即可移动。 + 键盘操作遵循 WAI-ARIA 树形控件模式,包括输入时自动定位。 + - **Project ▸ New theme** 会创建 `themes/my-gaia.css`(也可基于 default、 + uncover):其中包含对内置主题的 `@import` 以及你添加的规则。 + 它会加入 Theme 列表。**New kind** 和 **New data file** 同样从模板开始。 + 每个新文件都会登记在 `slides.yaml` 中,因此 `slides build deck.md` + 能找到它。 + - **Project ▸ Download project (.zip)** 会保存全部内容。解压后, + 用 `slides build deck.md` 即可构建。**Open project** 可以重新打开一个 `.zip` 或 + 文件夹。会跳出其所在文件夹(`..`、绝对路径)、包含超过 256 个文件或单个文件超过 8 MiB, + 或解压后膨胀超过 100 倍的压缩包会被拒绝。 + - 所有内容都与草稿一起保存在当前浏览器中。一个项目最多可容纳 + 64 MiB,文件列表下方的一行会显示已用空间。 +- **媒体**:将图片、视频或音频拖放或粘贴到编辑器中,它会被放进 + `media/`,并在演示文稿中引用:`![](media/photo.png)`。把文件拖到面板中的某个文件夹上, + 即可放到该文件夹中。演示文稿保持精简易读,也不会上传任何内容。 + 预览的沙盒无需放宽限制即可加载这些媒体(由页面把字节数据交给它)。**Save as ▸ HTML + page** 会把它们放进页面中。`https:` 图片同样可用,但浏览器会从其服务器获取, + 服务器因此会看到你的地址。预览不发送 referrer。 +- **可调整大小的窗格**:文件面板、编辑器、幻灯片缩略图、预览和帮助之间以可拖动的分隔条隔开 + (指针移到分隔条上时会变为调整大小的光标)。分隔条旁边的窗格占用调整出的空间, + 其他窗格保持大小不变。双击分隔条,或在其上按 Enter,可以收起窗格,再次操作则恢复。 + 使用键盘时,F6 在分隔条之间切换,方向键每次移动 16 px,Home 和 End 将分隔条前面的窗格 + 调到最小和最大,Escape 返回编辑器。View 选项卡仍可关闭窗格。 + 你设置的尺寸会与草稿一起保存在当前浏览器中;默认尺寸(190、168 和 280 px, + 编辑器与预览按 6 比 5 分配剩余空间)从不保存。 +- **格式**:Format 选项卡会替你编辑 Markdown,每按一次就是一步撤销。 + - **文本**:Bold、Italic、Strikethrough 和 Inline code 作用于所选内容 + 或光标所在的单词,并可切换(Ctrl+B、Ctrl+I、Ctrl+Shift+X 和 + Ctrl+E;Mac 上用 Cmd)。光标位于相应标记内时,按钮显示为按下状态。 + Markdown 没有下划线:Underline(Ctrl+U)会用 `<u>…</u>` 包裹文本, + 这个标签在 Marp 默认的 HTML 白名单中,因此关闭原始 HTML 时也能生效。 + - **段落**:Heading 1 到 3(Ctrl+Alt+1..3)、项目符号(Ctrl+Shift+8)、 + 编号(Ctrl+Shift+7)和引用作用于所选的行,并可切换。 + 水平线使用 `<hr>` 标签:因为在 Marp 中,`---`、`***` 和 `___` 都会开始一张新幻灯片。 + - **表格**:通过网格选择列数和行数。光标位于表格中时,可以添加或删除行、列, + 并设置列的对齐方式(`:--`、`:-:`、 + `--:`);每次编辑后表格都会重新对齐。 + - **幻灯片文本** 左对齐、居中或右对齐,会在当前幻灯片中写入一个 `<style scoped>` 块。 + Marp 没有对齐语法,`style` 属性也无法通过默认的 HTML 白名单,但 scoped 块可以, + 因此关闭原始 HTML 时也能生效,并会显示在预览、HTML 页面和 PDF 中。 + - **链接**(Ctrl+K)会写出 `[text](url)`,并选中其中的 url。 +- **高亮** front matter 中任意层级的键,支持 YAML(包括 + `{a: 1}` 形式的映射)和 HCL。 +- **主题** 选择器:default、gaia、uncover、项目中的主题 + (`themes/*.css`),以及演示文稿自己定义的主题(`<style>` 中的 `/* @theme name */`)。 + 它会把 `theme:` 写入 front + matter(YAML 或 HCL),因此分享链接会保留主题;编辑 front matter 时,选择器也会随之更新。 +- 输入时预览会**保持位置**:只替换发生变化的幻灯片,切换主题后预览会重新定位到同一张幻灯片。 +- **分享链接**:演示文稿就包含在链接中(压缩后放在其 `#` 部分), + 因此打开链接即可打开演示文稿。链接最多可携带压缩后 + 64 KiB 的内容,且只包含演示文稿:当项目还有其他内容 + (主题、kind、媒体)时,Share 会给出提示并提供 `.zip`。 +- **另存为**: + - HTML 页面,与 `slides deck.md` 写出的相同; + - **PDF**(矢量,文本可选中,带书签大纲); + - 可编辑的 **PowerPoint** 或 **OpenDocument** 演示文稿。 + + 它们都在你的浏览器中生成,所用代码与 `slides --pdf`、 + `--pptx` 和 `--odp` 相同:100 张幻灯片基准演示文稿的 PDF 与命令行生成的 + 逐字节相同。第一次导出时会下载导出引擎(约 21 MiB,仅下载一次;浏览器会保留它直到下次更新), + 并显示进度。之后导出 100 张幻灯片的演示文稿约需 3 秒。 + `https:` 图片只有在其服务器允许时(CORS)才会被包含;`data:` + 图片则总会被包含。 +- **草稿会被保留**: + - 演示文稿会在输入时保存在当前浏览器中(IndexedDB,仅限本站点), + 重新加载页面后会恢复。不会发送到任何地方。项目中的文本文件 + (主题、kind、数据、`slides.yaml`)也以同样方式保存,即使你刚输入完就关闭标签页也不会丢失; + 媒体文件在拖入时即被保存。 + - 打开分享链接会打开其中的演示文稿,同时保留你的草稿:**Draft ▸ Restore my + draft** 可将草稿恢复。 + - 打开示例、**Draft ▸ Reset to the example**(会先确认)或另一个标签页也都不会抹掉草稿: + **Draft ▸ Restore replaced** 可将其换回。 + - **Draft ▸ Forget my drafts** 会删除所有已保存的内容。 + +每次导出渲染的都是预览所显示的项目:包括其 `slides.yaml`、 +主题、kind、数据文件和媒体,媒体会嵌入文档中。 + +以文件形式输出图片(slides `--images`)无法在浏览器中完成:请使用 +[命令行工具]({{< relref "cli" >}})或 +[VS Code 扩展]({{< relref "vscode" >}})。 + +## 安全 {#security} + +演示文稿显示在一个不具有页面来源(origin)的沙盒 iframe 中:它既无法访问 Playground, +也无法访问其存储,不能提交表单,也不能进行任何导航。其页面带有内容安全策略(Content Security Policy), +只运行它自己的脚本。演示文稿中的原始 HTML 遵循 Marp 的白名单,与 `slides` 的默认行为相同。 +运行超过 5 秒的渲染(例如陷入循环的模板)会被终止。 + +演讲者窗口同样处于该沙盒中。它由 Playground 本身打开, +并在以同样方式沙盒化的 iframe 中显示演示文稿。两个 +iframe 无法共享存储,因此由 Playground 通过消息在它们之间转发幻灯片序号。 +任何运行演示文稿的框架都不会获得 Playground 的来源。 + +项目中的媒体以消息中的字节数据形式传给沙盒框架, +每个框架在自己的来源中为其创建各自的 `blob:` URL。由 Playground 创建的 `blob:` +URL 在框架中无法加载,而框架的策略仅为媒体和字体额外放开了 `blob:`。 + +## 体积与速度 {#size-and-speed} + +按 GitHub Pages 提供的形式(gzip;Pages 不提供 +brotli),模块大小为 8.8 MiB。它只会下载一次并显示进度条,之后由浏览器缓存,直到下次部署。 +在 Apple M4 Max 和 Chrome 155 上针对线上页面测量,页面打开后 2.1 s 编辑器即可使用, +其中 1.5 s 用于下载,首批幻灯片在 2.4 s 时出现。修改 +100 张幻灯片的基准演示文稿后,约 30 ms 即可反映到预览中:go-slides +只渲染发生变化的幻灯片,预览中也只替换这些幻灯片。 diff --git a/content/zh/templates.md b/content/zh/templates.md new file mode 100644 index 0000000..14249c1 --- /dev/null +++ b/content/zh/templates.md @@ -0,0 +1,73 @@ +--- +title: "模板" +weight: 30 +description: "在演示文稿中使用 Go text/template 插值:变量值、生成幻灯片、页码、文件引入。" +tags: [templates] +--- + +演示文稿本身就是一个 Go [`text/template`](https://pkg.go.dev/text/template), +在解析 Markdown **之前**求值。Go 模板语言的所有功能(动作、管道、`if`、`range`、`with`、变量) +都可以在演示文稿中使用。 + +## 数据 {#data} + +模板的点(dot)依次包含 front matter 的键、其 `data:` 映射中的键, +以及通过命令行或 `slides.yaml` 提供的数据,后者覆盖前者。 + +```markdown +--- +marp: true +title: Quarterly review +data: + team: Platform +--- + +# {{ .title }} + +Presented by the {{ .team }} team. +``` + +通过命令行提供: + +| 参数 | 提供的内容 | +| --- | --- | +| `--data file.yaml` | 一个 YAML 或 JSON 数据文件(可重复) | +| `--set key=value` | 一个值(可重复) | + +## 生成幻灯片 {#generating-slides} + +整个正文是一个模板,因此写出 `---` 的 `range` 就能生成幻灯片: + +```markdown +{{ range .releases }} +--- + +# {{ .version }} + +{{ range .changes }}- {{ . }} +{{ end }} +{{ end }} +``` + +## 页码与总页数 {#page-and-total} + +`{{ page }}` 和 `{{ total }}` 分别是当前幻灯片的序号和幻灯片总数。 +只有在演示文稿被拆分之后才能知道它们的值,所以它们在那时才被填入: +生成的演示文稿统计的是它实际生成的幻灯片。 + +```markdown +<!-- footer: "{{ page }} / {{ total }}" --> +``` + +## 引入文件 {#include} + +`{{ include "parts/agenda.md" }}` 会插入另一个文件,路径相对于演示文稿。 + +## 代码不受影响 {#code-is-left-alone} + +围栏代码块和行内代码在模板执行之前会被暂存,之后原样还原, +因此一张讲解 Go 模板的幻灯片可以直接显示 `{{ .Name }}`,无需转义。 + +## 关闭 {#turning-it-off} + +`--no-template` 会将演示文稿当作纯 Marp 读取:`{{` 就是普通文本。 diff --git a/content/zh/vscode.md b/content/zh/vscode.md new file mode 100644 index 0000000..cf1d5b7 --- /dev/null +++ b/content/zh/vscode.md @@ -0,0 +1,172 @@ +--- +title: "VS Code" +weight: 55 +description: "VS Code 的 go-slides 扩展:预览、演示、问题提示、自动补全和导出,就像 Marp for VS Code 之于 Marp。" +tags: [vscode, editor] +--- + +{{< callout type="info" >}} +该扩展是 [go-slides/vscode](https://github.com/go-slides/vscode), +独立进行版本管理(v0.4.0)。它使用 go-slides v0.9.0 渲染(包括 +`editor` 包、滚动同步所用的源码行,以及概览视图和演讲者视图)。 +请从其 GitHub release 附带的 `go-slides-0.4.0.vsix` 安装: +在 VS Code Marketplace 和 Open VSX 上的发布已在准备中,但目前尚未上架。 +{{< /callout >}} + +该扩展是 go-slides 版的 +[Marp for VS Code](https://github.com/marp-team/marp-vscode):演示文稿会在 +VS Code 的 Markdown 预览中以幻灯片形式显示,错误会显示在“问题”面板中, +指令、kind(幻灯片类型)和模板函数会在输入时自动补全。 + +## 安装 {#install} + +从 [releases](https://github.com/go-slides/vscode/releases) 页面下载 +`go-slides-<version>.vsix`,然后运行 +*Extensions: Install from VSIX...*,或者: + +```sh +code --install-extension go-slides-0.4.0.vsix +``` + +预览不需要任何其他依赖。导出则需要 `slides` 命令: + +```sh +go install github.com/go-slides/slides/cmd/slides@latest +``` + +## 预览 {#preview} + +打开一个演示文稿,然后执行 *Markdown: Open Preview to the Side*。演示文稿就是 front matter 中写有 +`slides: true` 的 Markdown 文件: + +```markdown +--- +slides: true +paginate: true +data: + who: World +--- + +# Hello {{ .who }} + +--- + +<!-- _kind: title --> +# Page {{ page }} of {{ total }} +``` + +带有 `marp: true` 的文件也会被预览,除非已安装 Marp for VS Code: +因为那样两个扩展会渲染同一个文档。由设置项 +`goSlides.preview.marpDocuments` 决定:`auto`(默认)、`always` 或 +`never`。 + +预览就是 go-slides 本身,编译为 WebAssembly 并由扩展加载:不需要二进制文件, +在 [vscode.dev](https://vscode.dev) 上也能使用。它从工作区中读取演示文稿用到的内容 +(演示文稿旁边的 `slides.yaml` 或 `slides.hcl`、其中引用的 [kind]({{< relref "kinds" >}}) 文件、 +[模板]({{< relref "templates" >}})引入的文件、主题 CSS),且只从演示文稿所在的工作区文件夹中读取。 + +## 滚动同步 {#scroll-sync} + +从扩展 v0.2.0(go-slides v0.7.0)起,编辑器与预览会双向同步滚动, +与普通 Markdown 一样:预览跟随编辑器光标所在的幻灯片和块, +滚动预览也会移动编辑器。相关设置是 VS Code 自带的 +`markdown.preview.scrollPreviewWithEditor` 和 +`markdown.preview.scrollEditorWithPreview`。 + +行号映射会穿过模板。由 `{{ range }}` 生成的幻灯片跟随其模板所在的行, +由 `{{ include }}` 引入的内容跟随 include 所在的行。用 `headingDivider` 拆分的演示文稿 +按其标题同步。 + +## 演示 {#presenting} + +从扩展 v0.4.0(go-slides v0.9.0)起,无需离开 VS Code 即可进行演示。 +在编辑器中打开演示文稿后,从命令面板或编辑器的 `…` 菜单中选择: + +| 命令 | 打开的内容 | +|---|---| +| *go-slides: Open Overview* | 一个以网格显示所有幻灯片的面板。选中某张幻灯片后,编辑器会显示其源码;`Esc` 关闭面板 | +| *go-slides: Start Presenter View* | 幻灯片,以及旁边的演讲者视图:下一步、演讲者备注和计时器 | + +这些面板位于编辑器旁边,并相互同步:在其中一个面板中切换幻灯片,其他面板也会随之切换。 +它们也会跟随你对演示文稿的编辑。在幻灯片中按 `p` +键会打开演讲者视图。 + +要在另一块屏幕上演示,请用 *View: Move +Editor into New Window* 把幻灯片面板移过去。 + +每个面板都在一个沙盒框架(不带 `allow-same-origin`)中运行演示文稿,并采用 +`default-src 'none'` 策略:框架只加载图片、媒体、样式和字体, +且只运行 go-slides 内置于页面、按哈希值指定的脚本。演示文稿自己写的脚本不会运行。 + +## 问题提示、自动补全与悬停提示 {#problems-completion-hover} + +- **问题**:模板、YAML 和 [HCL]({{< relref "hcl" >}}) 错误,显示在出错的行上, + 位于演示文稿中或其来源的 kind 文件中。 +- **自动补全**:front matter 中和 `<!-- -->` 注释中的指令键 + (单页指令带 `_`),`kind` 和 `_kind` 的取值(附带每个 kind 设置的内容), + `theme`、`size`、`paginate`、`math`、`headingDivider`;在 + `{{ }}` 内则补全模板函数和数据字段。 +- **悬停提示**:指令的作用、kind 设置的内容、模板函数的参数。 + +## 导出 {#export} + +*go-slides: Export Slide Deck...*(也位于编辑器标题栏中)会调用 `slides` 命令写出 HTML、 +矢量 PDF、可编辑的 PPTX 和 ODP、PNG 或 JPEG,效果与 +`slides -o deck.pdf deck.md` 相同;参见[命令行]({{< relref "cli" >}})。 +导出需要受信任的工作区。如果找不到 `slides`,扩展会提议在终端中运行上面的 +`go install`。 + +## 设置 {#settings} + +| 设置项 | 默认值 | | +|---|---|---| +| `goSlides.cliPath` | `""` | `slides` 命令的路径;为空时使用 `PATH` 中的 `slides`。这是机器级设置:工作区无法设置它 | +| `goSlides.exportType` | `pdf` | 导出对话框默认推荐的格式 | +| `goSlides.exportAutoOpen` | `true` | 导出后打开文件 | +| `goSlides.html` | `false` | 允许所有原始 HTML,而不使用 Marp 的白名单;仅在受信任的工作区中生效 | +| `goSlides.preview.marpDocuments` | `auto` | 是否预览 `marp: true` 演示文稿:`auto`、`always`、`never` | + +## 安全 {#security} + +演示文稿和工作区都被视为不可信。原始 HTML 遵循 Marp 的白名单, +除非在受信任的工作区中开启了 `goSlides.html`(在 `slides.yaml` 中设置 +`html: true` 不会在预览中开启它),而且 VS Code 的预览不会运行演示文稿中的任何脚本。 +预览只读取演示文稿所在工作区文件夹中的文件,不跟随符号链接,模板也看不到任何环境变量。 +导出会运行一个程序,因此要等工作区被信任后才会进行,而且该程序永远不会由工作区指定。 + +## 性能 {#performance} + +在 go-slides 的 100 张幻灯片基准演示文稿上,使用 Apple M4 Max 上的 VS Code 1.142, +并在同一会话中测量 Marp for VS Code 3.6.1: + +| | go-slides | Marp for VS Code | +|---|---:|---:| +| 激活(加载 WebAssembly 模块) | 189 ms | — | +| 首次预览 | 52 ms | 78 ms | +| 编辑后的预览(中位数) | **19 ms** | 23–24 ms | +| `.vsix` | 7.5 MB | 36.3 MB | + +以上数据针对扩展 v0.3.0(go-slides v0.8.0),使用 100 张幻灯片的演示文稿, +在第 50 张幻灯片中输入一个字符,在 1 分钟负载低于 8 的条件下交错多轮测得。 +首次预览比 Marp 快,现在编辑后的预览也比 Marp 快。 + +### v0.8.0 与扩展 v0.3.0 {#in-v080-and-extension-v030} + +go-slides v0.8.0 会保留每个已打开演示文稿的渲染结果,编辑后只重新渲染 +源码或继承状态(指令、页码、总页数、标题 id、链接引用、源码行)发生变化的幻灯片; +输出与完整渲染逐字节相同。在 100 张幻灯片中的第 50 张中输入,只会重新渲染一张幻灯片: +原生 2.5 ms,WebAssembly 中 11.5 ms。随后 VS Code 的预览只对变化幻灯片的 DOM 做差量更新; +扩展的冒烟测试会对两个扩展都在两次计时渲染之间向第 50 张幻灯片输入一个字符。 +扩展的演讲者视图通过父窗口(`postMessage`)同步。 + +### v0.7.1 与扩展 v0.2.2 {#in-v071-and-extension-v022} + +go-slides v0.7.1 会在多次渲染之间缓存已解析的主题、打包好的样式表和已高亮的代码, +标记滚动同步行时不再逐字节处理,并复用其写入器:渲染 100 张幻灯片的演示文稿需分配 5.7 MB 内存, +扩展 v0.2.0 时为 52.7 MB。借此,扩展 v0.2.2 去掉了 `GOGC=400` 覆盖设置。 +作为对比,扩展 v0.2.1 在默认 `GOGC` 下首次预览耗时 72 ms,编辑后 44 ms +(使用 `GOGC=400` 时为 36 ms),而 v0.2.0 为 +47–51 ms。 + +扩展 v0.2.2 测得首次预览 52 ms、编辑后 24 ms, +与 Marp 的 23 ms 持平。 diff --git a/hugo.yaml b/hugo.yaml index 0c6f69e..837fa78 100644 --- a/hugo.yaml +++ b/hugo.yaml @@ -13,16 +13,76 @@ title: "go-slides" # related-sites menu, page subtitle and history, darkreaderLock). theme: hextra -# English is the default language and stays at the root of each version. -# Another language would live under its code, with content/<lang>/. +# English is the default language and stays at the root of each version +# (/docs/<version>/cli/); the others live under their code +# (/docs/<version>/fr/cli/), as in go-widgets/docs. defaultContentLanguage: en defaultContentLanguageInSubdir: false +# Each language has its own content directory, with the same file names, so +# that a page and its translation are paired; tools/i18n (run by the CI) fails +# when a page of content/en is missing in one of them, and checks that code, +# flags and anchors are what the English page has. A language is declared here +# only once its content exists. The flags are lipis/flag-icons' 4x3 flags (MIT, +# static/images/flags/LICENSE). Each language is listed under its own name. +# Chinese is Simplified (zh-Hans): the browser picks the right glyph variants +# of the Han characters from the page's lang. languages: en: label: "English" contentDir: content/en weight: 1 + params: + flag: images/flags/gb.svg + fr: + label: "Français" + contentDir: content/fr + weight: 2 + params: + flag: images/flags/fr.svg + dateFormat: "2 January 2006" + description: "Des présentations compatibles Marp en Go pur (CGO=0), avec des gabarits Go et des types de diapositives." + de: + label: "Deutsch" + contentDir: content/de + weight: 3 + params: + flag: images/flags/de.svg + dateFormat: "2. January 2006" + description: "Marp-kompatible Präsentationen in reinem Go (CGO=0), mit Go-Vorlagen und Folientypen." + es: + label: "Español" + contentDir: content/es + weight: 4 + params: + flag: images/flags/es.svg + dateFormat: "2 de January de 2006" + description: "Presentaciones compatibles con Marp en Go puro (CGO=0), con plantillas de Go y tipos de diapositiva." + zh: + label: "简体中文" + locale: zh-Hans + contentDir: content/zh + weight: 5 + params: + flag: images/flags/cn.svg + dateFormat: "2006年1月2日" + description: "用纯 Go(CGO=0)编写、兼容 Marp 的幻灯片,支持 Go 模板和幻灯片类型。" + ko: + label: "한국어" + contentDir: content/ko + weight: 6 + params: + flag: images/flags/kr.svg + dateFormat: "2006년 1월 2일" + description: "순수 Go(CGO=0)로 만든 Marp 호환 슬라이드. Go 템플릿과 슬라이드 유형을 지원합니다." + ja: + label: "日本語" + contentDir: content/ja + weight: 7 + params: + flag: images/flags/jp.svg + dateFormat: "2006年1月2日" + description: "純粋な Go(CGO=0)で書かれた Marp 互換のスライド。Go テンプレートとスライドの種類に対応します。" enableRobotsTXT: true # Git history dates every page: "Last updated on" at the bottom of each page. @@ -62,24 +122,30 @@ module: menu: main: - # Navbar, left to right: version, theme, search, sources. + # Navbar, left to right: version, language, theme, search, sources. - name: Version weight: 3 params: type: partial partial: custom/version-select.html - - name: Theme + - name: Language weight: 4 + params: + type: language-switch + label: true + icon: translate + - name: Theme + weight: 5 params: type: theme-toggle combo: true - name: Search - weight: 5 + weight: 6 params: type: search - name: GitHub url: https://github.com/go-slides/slides - weight: 6 + weight: 7 params: icon: github @@ -98,6 +164,10 @@ params: # then the release's tag, so that the gallery matches the pages. gallery: ref: "main" + # The language switch shows the translate icon, then the language's flag. + # It appears once a second language is declared (languages: above). + languageSwitch: + icon: translate versions: # The version selector shows "main" with the development icon. development: "main" diff --git a/i18n/de.yaml b/i18n/de.yaml new file mode 100644 index 0000000..e50916a --- /dev/null +++ b/i18n/de.yaml @@ -0,0 +1,15 @@ +# Zeichenketten der Website, zusätzlich zu denen von Hextra (themes/hextra/i18n/de.yaml). +copyright: "© 2026 die Autoren von go-slides · BSD-3-Clause" +version: "Version" +outdated: "Sie lesen die Dokumentation einer veralteten Version; ihr Inhalt ist möglicherweise nicht mehr korrekt." +outdatedLink: "Zur neuesten Version." + +# Die Galerie-Seite (layouts/_shortcodes/gallery.html). +gallerySlides: + one: "{{ .Count }} Folie" + other: "{{ .Count }} Folien" +galleryAlt: "Erste Folie von „{{ .title }}“" +galleryOpen: "Im Playground öffnen" +galleryZip: ".zip herunterladen" +gallerySource: "Quelle auf GitHub" +galleryNote: "Dieser Foliensatz enthält neben <code>deck.md</code> weitere Dateien ({{ .files }}) und passt deshalb nicht in einen Link: Entpacken Sie ihn und führen Sie <code>slides build --allow-local-files deck.md</code> aus, oder importieren Sie die .zip-Datei mit <em>Import</em> im Playground." diff --git a/i18n/en.yaml b/i18n/en.yaml index 1a82b41..0092df2 100644 --- a/i18n/en.yaml +++ b/i18n/en.yaml @@ -3,3 +3,13 @@ copyright: "© 2026 the go-slides authors · BSD-3-Clause" version: "Version" outdated: "You are reading the documentation of an outdated version, whose text may no longer be accurate." outdatedLink: "Go to the latest version." + +# The gallery page (layouts/_shortcodes/gallery.html). +gallerySlides: + one: "{{ .Count }} slide" + other: "{{ .Count }} slides" +galleryAlt: "The first slide of “{{ .title }}”" +galleryOpen: "Open in the playground" +galleryZip: "Download the .zip" +gallerySource: "Source on GitHub" +galleryNote: "This deck has files besides <code>deck.md</code> ({{ .files }}), so it cannot travel in a link: unzip it and run <code>slides build --allow-local-files deck.md</code>, or import the .zip with the playground's <em>Import</em>." diff --git a/i18n/es.yaml b/i18n/es.yaml new file mode 100644 index 0000000..d17e2af --- /dev/null +++ b/i18n/es.yaml @@ -0,0 +1,15 @@ +# Cadenas del sitio, además de las de Hextra (themes/hextra/i18n/es.yaml). +copyright: "© 2026 los autores de go-slides · BSD-3-Clause" +version: "Versión" +outdated: "Está leyendo la documentación de una versión obsoleta; es posible que su contenido ya no sea exacto." +outdatedLink: "Ir a la última versión." + +# La página Galería (layouts/_shortcodes/gallery.html). +gallerySlides: + one: "{{ .Count }} diapositiva" + other: "{{ .Count }} diapositivas" +galleryAlt: "Primera diapositiva de «{{ .title }}»" +galleryOpen: "Abrir en el playground" +galleryZip: "Descargar el .zip" +gallerySource: "Código fuente en GitHub" +galleryNote: "Esta presentación tiene archivos además de <code>deck.md</code> ({{ .files }}), por lo que no cabe en un enlace: descomprímala y ejecute <code>slides build --allow-local-files deck.md</code>, o importe el .zip con <em>Import</em> en el playground." diff --git a/i18n/fr.yaml b/i18n/fr.yaml new file mode 100644 index 0000000..3e1e122 --- /dev/null +++ b/i18n/fr.yaml @@ -0,0 +1,15 @@ +# Chaînes du site, en plus de celles de Hextra (themes/hextra/i18n/fr.yaml). +copyright: "© 2026 les auteurs de go-slides · BSD-3-Clause" +version: "Version" +outdated: "Vous lisez la documentation d’une version obsolète : son contenu peut ne plus être exact." +outdatedLink: "Aller à la dernière version." + +# La page Galerie (layouts/_shortcodes/gallery.html). +gallerySlides: + one: "{{ .Count }} diapositive" + other: "{{ .Count }} diapositives" +galleryAlt: "Première diapositive de « {{ .title }} »" +galleryOpen: "Ouvrir dans le playground" +galleryZip: "Télécharger le .zip" +gallerySource: "Source sur GitHub" +galleryNote: "Cette présentation contient des fichiers en plus de <code>deck.md</code> ({{ .files }}) : elle ne peut pas tenir dans un lien. Décompressez-la et lancez <code>slides build --allow-local-files deck.md</code>, ou importez le .zip avec <em>Import</em> dans le playground." diff --git a/i18n/ja.yaml b/i18n/ja.yaml new file mode 100644 index 0000000..c3c2c9c --- /dev/null +++ b/i18n/ja.yaml @@ -0,0 +1,15 @@ +# サイトの文字列。Hextra 自体の文字列(themes/hextra/i18n/ja.yaml)に加えて使われます。 +copyright: "© 2026 go-slides の作者 · BSD-3-Clause" +version: "バージョン" +outdated: "古いバージョンのドキュメントを表示しています。内容が現在と異なる可能性があります。" +outdatedLink: "最新バージョンへ移動" + +# ギャラリーのページ(layouts/_shortcodes/gallery.html) +gallerySlides: + one: "{{ .Count }} 枚のスライド" + other: "{{ .Count }} 枚のスライド" +galleryAlt: "「{{ .title }}」の最初のスライド" +galleryOpen: "Playground で開く" +galleryZip: ".zip をダウンロード" +gallerySource: "GitHub でソースを見る" +galleryNote: "このスライドデッキには <code>deck.md</code> 以外のファイル({{ .files }})があり、リンクに収まりません。展開して <code>slides build --allow-local-files deck.md</code> を実行するか、Playground の <em>Import</em> で .zip を読み込んでください。" diff --git a/i18n/ko.yaml b/i18n/ko.yaml new file mode 100644 index 0000000..1f2c4a8 --- /dev/null +++ b/i18n/ko.yaml @@ -0,0 +1,15 @@ +# 사이트 문자열. Hextra 자체 문자열(themes/hextra/i18n/ko.yaml)에 더해 쓰입니다. +copyright: "© 2026 go-slides 작성자 · BSD-3-Clause" +version: "버전" +outdated: "오래된 버전의 문서를 보고 계십니다. 내용이 더 이상 정확하지 않을 수 있습니다." +outdatedLink: "최신 버전으로 이동" + +# 갤러리 페이지 (layouts/_shortcodes/gallery.html) +gallerySlides: + one: "슬라이드 {{ .Count }}장" + other: "슬라이드 {{ .Count }}장" +galleryAlt: "“{{ .title }}”의 첫 번째 슬라이드" +galleryOpen: "Playground에서 열기" +galleryZip: ".zip 다운로드" +gallerySource: "GitHub에서 소스 보기" +galleryNote: "이 프레젠테이션에는 <code>deck.md</code> 외에도 파일({{ .files }})이 있어 링크에 담을 수 없습니다. 압축을 풀고 <code>slides build --allow-local-files deck.md</code>를 실행하거나, Playground의 <em>Import</em>로 .zip을 가져오세요." diff --git a/i18n/zh.yaml b/i18n/zh.yaml new file mode 100644 index 0000000..417f187 --- /dev/null +++ b/i18n/zh.yaml @@ -0,0 +1,64 @@ +# 站点字符串。Hextra 自带的简体中文字符串在 themes/hextra/i18n/zh-cn.yaml, +# 但语言键是 zh,主题找不到它,所以这里整份复制,再加上本站的字符串。 + +# Accessibility labels (screen reader only) +closeBanner: "关闭横幅" +imageGallery: "图片库" +menu: "菜单" +mermaidDiagram: "图表" +nextImage: "下一张图片" +pdfViewer: "PDF 查看器" +permalinkLabel: "此章节的永久链接" +playbackTime: "播放时间" +previousImage: "上一张图片" +searchResults: "搜索结果" +skipToContent: "跳至内容" +tableOfContents: "目录" +terminalRecording: "终端录像" +togglePageContextMenu: "切换页面上下文菜单" +toggleSection: "切换章节" + +# Accessibility live-region/status text +resultsFound: "找到 %d 个结果" + +# User-facing UI text +archives: "归档" +backToTop: "返回顶部" +changeLanguage: "切换语言" +changeTheme: "切换主题" +copy: "复制" +copied: "已复制!" +copyAsMarkdown: "复制为 Markdown" +copyPage: "复制页面" +copyCode: "复制代码" +dark: "深色" +editThisPage: "在 GitHub 上编辑此页 →" +lastUpdated: "最后更新于" +light: "浅色" +next: "下一页" +noResultsFound: "无结果" +onThisPage: "此页上" +more: "更多" +poweredBy: "由 Hextra 驱动" +previous: "上一页" +readMore: "更多 →" +searchPlaceholder: "搜索文档..." +system: "跟随系统" +tags: "标签" +viewAsMarkdown: "以 Markdown 查看" + +# go-slides +copyright: "© 2026 go-slides 作者 · BSD-3-Clause" +version: "版本" +outdated: "您正在阅读的是旧版本的文档,其内容可能已不再准确。" +outdatedLink: "前往最新版本。" + +# 示例库页面 (layouts/_shortcodes/gallery.html) +gallerySlides: + one: "{{ .Count }} 张幻灯片" + other: "{{ .Count }} 张幻灯片" +galleryAlt: "“{{ .title }}”的第一张幻灯片" +galleryOpen: "在 Playground 中打开" +galleryZip: "下载 .zip" +gallerySource: "在 GitHub 上查看源码" +galleryNote: "除 <code>deck.md</code> 外,这份演示文稿还有其他文件({{ .files }}),无法放进链接:请解压后运行 <code>slides build --allow-local-files deck.md</code>,或在 Playground 中用 <em>Import</em> 导入 .zip。" diff --git a/layouts/_shortcodes/gallery.html b/layouts/_shortcodes/gallery.html index 3237225..62b82a8 100644 --- a/layouts/_shortcodes/gallery.html +++ b/layouts/_shortcodes/gallery.html @@ -1,6 +1,8 @@ {{- /* The gallery of example decks, from data/gallery.json and static/gallery/, - which tools/gallery writes before every build (CI and local). + which tools/gallery writes before every build (CI and local). The page's + words are the language's (i18n/<lang>.yaml); the decks' titles and + descriptions are the English ones of their front matter, as are the decks. */ -}} {{- $items := hugo.Data.gallery -}} {{- if not $items -}} @@ -10,25 +12,24 @@ {{- range $items }} <article class="gallery-card" id="{{ .name }}"> <a class="gallery-thumb" href="{{ .thumbnail | relURL }}"> - <img src="{{ .thumbnail | relURL }}" alt="The first slide of “{{ .title }}”" width="640" height="360" loading="lazy"> + <img src="{{ .thumbnail | relURL }}" alt="{{ i18n "galleryAlt" (dict "title" .title) }}" width="640" height="360" loading="lazy"> </a> <h3><a href="#{{ .name }}">{{ .title }}</a></h3> - <p class="gallery-meta">{{ .slides }} slides</p> + <p class="gallery-meta">{{ i18n "gallerySlides" (int .slides) }}</p> <p>{{ .description }}</p> <p class="gallery-actions"> {{- if .playground }} - <a class="gallery-open" href="{{ .playground | safeURL }}">Open in the playground</a> + <a class="gallery-open" href="{{ .playground | safeURL }}">{{ i18n "galleryOpen" }}</a> {{- end }} {{- if .zip }} - <a class="gallery-open" href="{{ .zip | relURL }}">Download the .zip</a> + <a class="gallery-open" href="{{ .zip | relURL }}">{{ i18n "galleryZip" }}</a> {{- end }} - <a href="{{ .repo }}">Source on GitHub</a> + <a href="{{ .repo }}">{{ i18n "gallerySource" }}</a> </p> {{- if .zip }} - <p class="gallery-note">This deck has files besides <code>deck.md</code> - ({{ range $i, $f := .files }}{{ if $i }}, {{ end }}<code>{{ $f }}</code>{{ end }}), - so it cannot travel in a link: unzip it and run <code>slides build --allow-local-files deck.md</code>, - or import the .zip with the playground's <em>Import</em>.</p> + {{- $files := slice -}} + {{- range .files }}{{ $files = $files | append (printf "<code>%s</code>" .) }}{{ end }} + <p class="gallery-note">{{ i18n "galleryNote" (dict "files" (delimit $files ", " | safeHTML)) | safeHTML }}</p> {{- end }} <details> <summary>deck.md</summary> diff --git a/layouts/baseof.html b/layouts/baseof.html new file mode 100644 index 0000000..8e64e65 --- /dev/null +++ b/layouts/baseof.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +{{- /* Hextra's baseof.html, with one change: lang is the language's locale when + it has one (zh-Hans), not its key (zh). A browser picks the glyph + variants of the Han characters shared by Chinese and Japanese from lang, + and the right ones are the Simplified Chinese ones under zh-Hans. */ -}} +{{- $lang := partial "utils/hugo-compat/language-locale.html" .Site.Language | default .Site.Language.Lang -}} +<html lang="{{ $lang }}" dir="{{ partial "utils/hugo-compat/language-direction.html" .Site.Language | default `ltr` }}"> + {{- partial "head.html" . -}} + <body> + <a href="#content" class="hx:sr-only hx:focus-visible:not-sr-only hx:focus-visible:fixed hx:focus-visible:z-50 hx:focus-visible:top-2 hx:focus-visible:left-2 hx:focus-visible:bg-primary-500 hx:focus-visible:text-white hx:focus-visible:px-4 hx:focus-visible:py-2 hx:focus-visible:rounded-md hx:focus-visible:text-sm hx:focus-visible:font-medium"> + {{- (T "skipToContent") | default "Skip to content" -}} + </a> + {{- partial "banner.html" . -}} + {{- partial "navbar.html" . -}} + {{- block "main" . }}{{ end -}} + {{- if or (eq .Site.Params.footer.enable nil) (.Site.Params.footer.enable) }} + {{ partial "footer.html" . }} + {{ end }} + {{ partial "components/search-dialog.html" . }} + {{ partial "scripts.html" . }} + </body> +</html> diff --git a/static/images/flags/LICENSE b/static/images/flags/LICENSE new file mode 100644 index 0000000..ee959dc --- /dev/null +++ b/static/images/flags/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Panayiotis Lipiridis + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/static/images/flags/cn.svg b/static/images/flags/cn.svg new file mode 100644 index 0000000..10d3489 --- /dev/null +++ b/static/images/flags/cn.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="flag-icons-cn" viewBox="0 0 640 480"> + <defs> + <path id="cn-a" fill="#ff0" d="M-.6.8 0-1 .6.8-1-.3h2z"/> + </defs> + <path fill="#ee1c25" d="M0 0h640v480H0z"/> + <use xlink:href="#cn-a" width="30" height="20" transform="matrix(71.9991 0 0 72 120 120)"/> + <use xlink:href="#cn-a" width="30" height="20" transform="matrix(-12.33562 -20.5871 20.58684 -12.33577 240.3 48)"/> + <use xlink:href="#cn-a" width="30" height="20" transform="matrix(-3.38573 -23.75998 23.75968 -3.38578 288 95.8)"/> + <use xlink:href="#cn-a" width="30" height="20" transform="matrix(6.5991 -23.0749 23.0746 6.59919 288 168)"/> + <use xlink:href="#cn-a" width="30" height="20" transform="matrix(14.9991 -18.73557 18.73533 14.99929 240 216)"/> +</svg> diff --git a/static/images/flags/de.svg b/static/images/flags/de.svg new file mode 100644 index 0000000..71aa2d2 --- /dev/null +++ b/static/images/flags/de.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-de" viewBox="0 0 640 480"> + <path fill="#fc0" d="M0 320h640v160H0z"/> + <path fill="#000001" d="M0 0h640v160H0z"/> + <path fill="red" d="M0 160h640v160H0z"/> +</svg> diff --git a/static/images/flags/es.svg b/static/images/flags/es.svg new file mode 100644 index 0000000..a296ebf --- /dev/null +++ b/static/images/flags/es.svg @@ -0,0 +1,544 @@ +<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-es" viewBox="0 0 640 480"> + <path fill="#AA151B" d="M0 0h640v480H0z"/> + <path fill="#F1BF00" d="M0 120h640v240H0z"/> + <path fill="#ad1519" d="m127.3 213.3-.8-.1-1-1-.7-.4-.6-.8s-.7-1.1-.4-2q.5-1.2 1.4-1.5l1.5-.5 1-.4 1.3-.3.5-.3 1-.2 1-.2 1.6.1h4.8c.4 0 1.2.3 1.4.4l2 .7c.5.1 1.6.3 2.2.6q.8.5 1.1 1l.5 1v1.1l-.5.8-.6 1-.8.6s-.5.5-1 .4-4.8-.8-7.6-.8-7.3.9-7.3.9"/> + <path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".3" d="m127.3 213.3-.8-.1-1-1-.7-.4-.6-.8s-.7-1.1-.4-2q.5-1.2 1.4-1.5l1.5-.5 1-.4 1.3-.3.5-.3 1-.2 1-.2 1.6.1h4.8c.4 0 1.2.3 1.4.4l2 .7c.5.1 1.6.3 2.2.6q.8.5 1.1 1l.5 1v1.1l-.5.8-.6 1-.8.6s-.5.5-1 .4-4.8-.8-7.6-.8-7.3.9-7.3.9z"/> + <path fill="#c8b100" d="M133.3 207q.1-2.1 1.3-2.3 1.3.1 1.4 2.4c0 1.3-.6 2.4-1.4 2.4s-1.3-1.1-1.3-2.5"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M133.3 207q.1-2.1 1.3-2.3 1.3.1 1.4 2.4c0 1.3-.6 2.4-1.4 2.4s-1.3-1.1-1.3-2.5z"/> + <path fill="#c8b100" d="M134 207q0-2 .7-2.1c.3 0 .6 1 .6 2.1q0 2-.6 2.2c-.4 0-.6-1-.6-2.2"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M134 207q0-2 .7-2.1c.3 0 .6 1 .6 2.1q0 2-.6 2.2c-.4 0-.6-1-.6-2.2z"/> + <path fill="#c8b100" d="M133.8 204.5q.1-.7.8-.8 1 0 1 .8-.1.8-1 .9c-.9.1-.8-.4-.8-.9"/> + <path fill="#c8b100" d="M135.3 204.2v.6h-1.4v-.6h.5V203h-.7v-.6h.7v-.5h.5v.5h.6v.6h-.6v1.2z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M135.3 204.2v.6h-1.4v-.6h.5V203h-.7v-.6h.7v-.5h.5v.5h.6v.6h-.6v1.2h.4"/> + <path fill="#c8b100" d="M135.9 204.2v.6h-2.5v-.6h1V203h-.7v-.6h.7v-.5h.5v.5h.6v.6h-.6v1.2z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M135.9 204.2v.6h-2.5v-.6h1V203h-.7v-.6h.7v-.5h.5v.5h.6v.6h-.6v1.2h1"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M134.9 203.7q.6.2.6.8-.1.8-.8.9-1 0-1-.9 0-.6.7-.8"/> + <path fill="#c8b100" d="M134.7 213.2H130v-1.1l-.3-1.2-.2-1.5c-1.3-1.7-2.5-2.8-2.9-2.5q.1-.5.5-.7c1.1-.7 3.5 1 5.2 3.6l.5.7h3.8l.4-.7c1.8-2.7 4.1-4.3 5.2-3.6q.4.2.5.7c-.4-.3-1.6.8-2.9 2.5l-.2 1.5-.2 1.2-.1 1.1z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M134.7 213.2H130v-1.1l-.3-1.2-.2-1.5c-1.3-1.7-2.5-2.8-2.9-2.5q.1-.5.5-.7c1.1-.7 3.5 1 5.2 3.6l.5.7h3.8l.4-.7c1.8-2.7 4.1-4.3 5.2-3.6q.4.2.5.7c-.4-.3-1.6.8-2.9 2.5l-.2 1.5-.2 1.2-.1 1.1z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M126.8 206.8c1-.5 3 1.1 4.6 3.6m11-3.6c-.8-.5-2.8 1.1-4.5 3.6"/> + <path fill="#c8b100" d="m127.8 215.3-.5-1a27 27 0 0 1 14.7 0l-.5.8-.3.8a23 23 0 0 0-6.6-.8c-2.6 0-5.2.3-6.5.8z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m127.8 215.3-.5-1a27 27 0 0 1 14.7 0l-.5.8-.3.8a23 23 0 0 0-6.6-.8c-2.6 0-5.2.3-6.5.8l-.3-.6"/> + <path fill="#c8b100" d="M134.6 217.7c2.4 0 5-.4 5.9-.6q1-.3 1-.8 0-.3-.4-.4c-1.4-.5-4-.8-6.5-.8s-5 .3-6.4.8q-.3 0-.4.3 0 .6 1 .9c1 .2 3.5.6 5.8.6"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M134.6 217.7c2.4 0 5-.4 5.9-.6q1-.3 1-.8 0-.3-.4-.4c-1.4-.5-4-.8-6.5-.8s-5 .3-6.4.8q-.3 0-.4.3 0 .6 1 .9c1 .2 3.5.6 5.8.6z"/> + <path fill="#c8b100" d="m142.1 213.2-.5-.5s-.6.3-1.3.2c-.6 0-.9-1-.9-1s-.7.7-1.3.7-1-.6-1-.6-.7.5-1.3.4-1.2-.8-1.2-.8-.6.8-1.2.8c-.6.1-1-.5-1-.5s-.4.6-1.1.7-1.4-.6-1.4-.6-.5.7-1 1c-.5 0-1.2-.4-1.2-.4l-.2.5-.3.1.2.5a27 27 0 0 1 7.2-.9q4.5 0 7.4 1z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m142.1 213.2-.5-.5s-.6.3-1.3.2c-.6 0-.9-1-.9-1s-.7.7-1.3.7-1-.6-1-.6-.7.5-1.3.4-1.2-.8-1.2-.8-.6.8-1.2.8c-.6.1-1-.5-1-.5s-.4.6-1.1.7-1.4-.6-1.4-.6-.5.7-1 1c-.5 0-1.2-.4-1.2-.4l-.2.5-.3.1.2.5a27 27 0 0 1 7.2-.9q4.5 0 7.4 1z"/> + <path fill="#c8b100" d="M134.7 210.7h.2v.4q0 1 1 1a1 1 0 0 0 1-.7l.2-.3v.4q.3.8 1.1.8 1 0 1-1v-.1l.4-.4.2.5-.1.4a1 1 0 0 0 1 1q.6 0 .9-.5l.2-.2v.3q0 .5.4.7l1-.4.7-.7v.4s-.5.8-1 1q-.3.4-.8.3-.5 0-.7-.6-.3.3-.7.2-1 0-1.4-.8-.5.5-1.1.5a2 2 0 0 1-1.2-.6l-1 .4a2 2 0 0 1-1.3-.6 1.6 1.6 0 0 1-2.4.2 2 2 0 0 1-1.2.6l-1.1-.5q-.4.8-1.4.8l-.7-.2q-.2.5-.7.6t-.9-.2l-1-1 .1-.5.8.7q.8.5.9.4.4-.1.4-.7v-.3l.2.2q.3.5.9.5a1 1 0 0 0 1-1v-.9l.4.4v.1q.1 1 1 1t1.1-.9v-.3l.2.3q.3.6 1 .7 1 0 1.1-1v-.3h.3"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M134.7 210.7h.2v.4q0 1 1 1a1 1 0 0 0 1-.7l.2-.3v.4q.3.8 1.1.8 1 0 1-1v-.1l.4-.4.2.5-.1.4a1 1 0 0 0 1 1q.6 0 .9-.5l.2-.2v.3q0 .5.4.7l1-.4.7-.7v.4s-.5.8-1 1q-.3.4-.8.3-.5 0-.7-.6-.3.3-.7.2-1 0-1.4-.8-.5.5-1.1.5a2 2 0 0 1-1.2-.6l-1 .4a2 2 0 0 1-1.3-.6 1.6 1.6 0 0 1-2.4.2 2 2 0 0 1-1.2.6l-1.1-.5q-.4.8-1.4.8l-.7-.2q-.2.5-.7.6t-.9-.2l-1-1 .1-.5.8.7q.8.5.9.4.4-.1.4-.7v-.3l.2.2q.3.5.9.5a1 1 0 0 0 1-1v-.9l.4.4v.1q.1 1 1 1t1.1-.9v-.3l.2.3q.3.6 1 .7 1 0 1.1-1v-.3h.3z"/> + <path fill="#c8b100" d="M134.6 213.3q-4.5 0-7.3 1l-.3-.2.1-.3a27 27 0 0 1 7.5-1q4.6 0 7.6 1l.1.3-.3.2a27 27 0 0 0-7.4-1"/> + <path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".3" d="M134.6 213.3q-4.5 0-7.3 1l-.3-.2.1-.3a27 27 0 0 1 7.5-1q4.6 0 7.6 1l.1.3-.3.2a27 27 0 0 0-7.4-1z"/> + <path fill="#fff" d="M131.8 214.4q0-.4.5-.4a.4.4 0 0 1 .4.4q0 .3-.4.4a.4.4 0 0 1-.5-.4"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M131.8 214.4q0-.4.5-.4a.4.4 0 0 1 .4.4q0 .3-.4.4a.4.4 0 0 1-.5-.4z"/> + <path fill="#ad1519" d="M134.7 214.5h-1q-.2 0-.3-.3l.3-.3h2l.2.3-.3.3h-1"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M134.7 214.5h-1q-.2 0-.3-.3l.3-.3h2l.2.3-.3.3h-1"/> + <path fill="#058e6e" d="M130 214.9h-.7q-.2 0-.3-.2l.2-.3.7-.1.7-.1q.3 0 .4.2a.3.3 0 0 1-.3.4z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M130 214.9h-.7q-.2 0-.3-.2l.2-.3.7-.1.7-.1q.3 0 .4.2a.3.3 0 0 1-.3.4h-.7"/> + <path fill="#ad1519" d="m127.3 215.3.3-.4h.7l-.4.6z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m127.3 215.3.3-.4h.7l-.4.6-.6-.2"/> + <path fill="#fff" d="M136.6 214.4q0-.4.4-.4a.4.4 0 0 1 .5.4.4.4 0 0 1-.5.4.4.4 0 0 1-.4-.4"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M136.6 214.4q0-.4.4-.4a.4.4 0 0 1 .5.4.4.4 0 0 1-.5.4.4.4 0 0 1-.4-.4z"/> + <path fill="#058e6e" d="M139.3 214.9h.6a.3.3 0 0 0 .4-.2l-.3-.3-.6-.1-.7-.1q-.3 0-.4.2 0 .3.3.4z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M139.3 214.9h.6a.3.3 0 0 0 .4-.2l-.3-.3-.6-.1-.7-.1q-.3 0-.4.2 0 .3.3.4h.7"/> + <path fill="#ad1519" d="m142 215.4-.3-.5h-.7l.3.6z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m142 215.4-.3-.5h-.7l.3.6.6-.1"/> + <path fill="#ad1519" d="M134.6 217.1a25 25 0 0 1-6-.6 26 26 0 0 1 12.1 0q-2.5.6-6 .6"/> + <path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".3" d="M134.6 217.1a25 25 0 0 1-6-.6 26 26 0 0 1 12.1 0q-2.5.6-6 .6z"/> + <path fill="#c8b100" d="m142 212-.1-.3q-.3 0-.4.2 0 .4.2.4z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m142 212-.1-.3q-.3 0-.4.2 0 .4.2.4z"/> + <path fill="#c8b100" d="M137.3 211.2q0-.3-.2-.4l-.2.3q0 .3.2.4l.3-.3"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M137.3 211.2q0-.3-.2-.4l-.2.3q0 .3.2.4l.3-.3z"/> + <path fill="#c8b100" d="m132 211.2.1-.4q.3 0 .3.3t-.2.4z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m132 211.2.1-.4q.3 0 .3.3t-.2.4z"/> + <path fill="#c8b100" d="m127.3 212 .1-.3q.3 0 .4.2 0 .4-.2.4z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m127.3 212 .1-.3q.3 0 .4.2 0 .4-.2.4z"/> + <path fill="#c8b100" d="m134.6 208.5-.8.5.6 1.3.2.1.2-.1.7-1.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m134.6 208.5-.8.5.6 1.3.2.1.2-.1.7-1.3-.9-.5"/> + <path fill="#c8b100" d="m132.8 210.5.4.5 1.3-.4.1-.2-.1-.2-1.3-.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m132.8 210.5.4.5 1.3-.4.1-.2-.1-.2-1.3-.3-.4.6"/> + <path fill="#c8b100" d="m136.4 210.5-.3.5-1.3-.4-.2-.2.2-.2 1.3-.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m136.4 210.5-.3.5-1.3-.4-.2-.2.2-.2 1.3-.3.3.6"/> + <path fill="#c8b100" d="m129.3 209-.7.7.9 1 .2.1.1-.1.3-1.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m129.3 209-.7.7.9 1 .2.1.1-.1.3-1.3-.8-.3"/> + <path fill="#c8b100" d="m128 211.2.4.5 1.2-.6v-.2l-.1-.2-1.3-.1-.3.6"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m128 211.2.4.5 1.2-.6v-.2l-.1-.2-1.3-.1-.3.6"/> + <path fill="#c8b100" d="m131.5 210.5-.3.6H130l-.2-.2.1-.3 1.2-.6z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m131.5 210.5-.3.6H130l-.2-.2.1-.3 1.2-.6.5.5"/> + <path fill="#c8b100" d="M126.6 211.4v.6l-1.4.2-.2-.1v-.2l1-.9z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M126.6 211.4v.6l-1.4.2-.2-.1v-.2l1-.9.6.4"/> + <path fill="#c8b100" d="M129.2 210.9q0-.5.5-.5t.5.5l-.5.4z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M129.2 210.9q0-.5.5-.5t.5.5l-.5.4z"/> + <path fill="#c8b100" d="m140 209 .7.7-.9 1-.2.1-.1-.1-.3-1.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m140 209 .7.7-.9 1-.2.1-.1-.1-.3-1.3.8-.3"/> + <path fill="#c8b100" d="m141.4 211.2-.5.5-1.2-.6v-.2l.1-.2 1.3-.1z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m141.4 211.2-.5.5-1.2-.6v-.2l.1-.2 1.3-.1.3.6"/> + <path fill="#c8b100" d="m137.8 210.5.3.6h1.3l.2-.2-.1-.3-1.2-.6z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m137.8 210.5.3.6h1.3l.2-.2-.1-.3-1.2-.6-.5.5"/> + <path fill="#c8b100" d="m142.5 211.4.1.6 1.3.2.2-.1v-.2l-1-.9z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m142.5 211.4.1.6 1.3.2.2-.1v-.2l-1-.9-.6.4"/> + <path fill="#c8b100" d="m134.2 210.4.4-.4q.5 0 .5.4l-.5.5z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m134.2 210.4.4-.4q.5 0 .5.4l-.5.5z"/> + <path fill="#c8b100" d="M139.1 210.9q.1-.5.5-.5l.5.5-.5.4z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M139.1 210.9q.1-.5.5-.5l.5.5-.5.4z"/> + <path fill="#c8b100" d="m124.8 212.2-.6-.7c-.2-.2-.7-.3-.7-.3q0-.2.6-.3l.4.2v-.2s.3 0 .4.3v1"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m124.8 212.2-.6-.7c-.2-.2-.7-.3-.7-.3q0-.2.6-.3l.4.2v-.2s.3 0 .4.3v1z"/> + <path fill="#c8b100" d="M124.8 212q.3-.3.5 0t.2.5l-.5-.1q-.3-.2-.2-.5"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M124.8 212q.3-.3.5 0t.2.5l-.5-.1q-.3-.2-.2-.5z"/> + <path fill="#c8b100" d="m144.3 212.2.6-.7.7-.3q0-.2-.6-.3l-.4.2v-.2s-.3 0-.4.3v.7z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m144.3 212.2.6-.7.7-.3q0-.2-.6-.3l-.4.2v-.2s-.3 0-.4.3v.7z"/> + <path fill="#c8b100" d="M144.3 212q-.1-.3-.5 0-.3.2-.1.5l.5-.1q.3-.2.1-.5"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M144.3 212q-.1-.3-.5 0-.3.2-.1.5l.5-.1q.3-.2.1-.5z"/> + <path fill="#c8b100" d="M124 223h21.4v-5.5H124z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M124 223h21.4v-5.5H124z"/> + <path fill="#c8b100" d="M126.2 226.8h16.9a1.4 1.4 0 0 1-1-1.2q.1-1 1-1.3h-16.9q1 .3 1 1.3a1.3 1.3 0 0 1-1 1.2"/> + <path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="M126.2 226.8h16.9a1.4 1.4 0 0 1-1-1.2q.1-1 1-1.3h-16.9q1 .3 1 1.3a1.3 1.3 0 0 1-1 1.2z"/> + <path fill="#c8b100" d="M126.6 226.8h16q1 0 1 .7t-1 .8h-16q-.9-.1-1-.8 0-1 1-.8"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M126.6 226.8h16q1 0 1 .7t-1 .8h-16q-.9-.1-1-.8 0-1 1-.8z"/> + <path fill="#c8b100" d="M126.6 223h16q1 .1 1 .7t-1 .6h-16q-.9 0-1-.6.1-.5 1-.6"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M126.6 223h16q1 .1 1 .7t-1 .6h-16q-.9 0-1-.6.1-.5 1-.6z"/> + <path fill="#005bbf" d="M149.6 317.4q-2.2 0-3.7-.8a8 8 0 0 0-3.8-.8q-2.1 0-3.7.8a8 8 0 0 1-3.8.8q-2.3 0-3.7-.8a8 8 0 0 0-3.7-.8 8 8 0 0 0-3.7.8 8 8 0 0 1-3.8.8v2.4q2.3 0 3.8-.9a8 8 0 0 1 3.7-.8q2.2 0 3.7.8c1.5.8 2.2.9 3.7.9a8 8 0 0 0 3.8-.9q1.6-.8 3.7-.8 2.3 0 3.8.8c1.5.8 2.2.9 3.7.9z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M149.6 317.4q-2.2 0-3.7-.8a8 8 0 0 0-3.8-.8q-2.1 0-3.7.8a8 8 0 0 1-3.8.8q-2.3 0-3.7-.8a8 8 0 0 0-3.7-.8 8 8 0 0 0-3.7.8 8 8 0 0 1-3.8.8v2.4q2.3 0 3.8-.9a8 8 0 0 1 3.7-.8q2.2 0 3.7.8c1.5.8 2.2.9 3.7.9a8 8 0 0 0 3.8-.9q1.6-.8 3.7-.8 2.3 0 3.8.8c1.5.8 2.2.9 3.7.9z"/> + <path fill="#ccc" d="M149.6 319.8a8 8 0 0 1-3.7-.9 8 8 0 0 0-3.8-.8q-2.1 0-3.7.8c-1.6.8-2.3.9-3.8.9s-2.8-.4-3.7-.9a8 8 0 0 0-3.7-.8 8 8 0 0 0-3.7.8q-1.5.8-3.8.9v2.3q2.3 0 3.8-.9a8 8 0 0 1 3.7-.7q2.2 0 3.7.7a8 8 0 0 0 7.5 0 9 9 0 0 1 7.5.1 8 8 0 0 0 3.7.8z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M149.6 319.8a8 8 0 0 1-3.7-.9 8 8 0 0 0-3.8-.8q-2.1 0-3.7.8c-1.6.8-2.3.9-3.8.9s-2.8-.4-3.7-.9a8 8 0 0 0-3.7-.8 8 8 0 0 0-3.7.8q-1.5.8-3.8.9v2.3q2.3 0 3.8-.9a8 8 0 0 1 3.7-.7q2.2 0 3.7.7a8 8 0 0 0 7.5 0 9 9 0 0 1 7.5.1 8 8 0 0 0 3.7.8v-2.3"/> + <path fill="#005bbf" d="M149.6 322a7 7 0 0 1-3.7-.8 8 8 0 0 0-3.8-.7q-2.1 0-3.7.7-1.5.9-3.8.9c-2.3 0-2.8-.4-3.7-.9a8 8 0 0 0-3.7-.8 8 8 0 0 0-3.7.8q-1.5.8-3.8.9v2.3q2.3 0 3.8-.9a10 10 0 0 1 7.4 0 7 7 0 0 0 3.7.9 8 8 0 0 0 3.8-.8q1.6-.8 3.7-.8 2.3 0 3.8.8c1.5.8 2.2.8 3.7.8z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M149.6 322a7 7 0 0 1-3.7-.8 8 8 0 0 0-3.8-.7q-2.1 0-3.7.7-1.5.9-3.8.9c-2.3 0-2.8-.4-3.7-.9a8 8 0 0 0-3.7-.8 8 8 0 0 0-3.7.8q-1.5.8-3.8.9v2.3q2.3 0 3.8-.9a10 10 0 0 1 7.4 0 7 7 0 0 0 3.7.9 8 8 0 0 0 3.8-.8q1.6-.8 3.7-.8 2.3 0 3.8.8c1.5.8 2.2.8 3.7.8V322"/> + <path fill="#ccc" d="M149.6 326.7a8 8 0 0 1-3.7-.8q-1.6-.8-3.7-.8a8 8 0 0 0-3.8.8q-1.5.8-3.8.8a7 7 0 0 1-3.7-.9 8 8 0 0 0-3.7-.7q-2.2 0-3.7.8c-1.5.8-2.3.8-3.8.8v-2.3a8 8 0 0 0 3.8-.9 10 10 0 0 1 7.4 0 8 8 0 0 0 3.7.9 8 8 0 0 0 3.8-.8q1.5-.8 3.8-.8t3.7.8c1.5.8 2.3.8 3.7.8z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M149.6 326.7a8 8 0 0 1-3.7-.8q-1.6-.8-3.7-.8a8 8 0 0 0-3.8.8q-1.5.8-3.8.8a7 7 0 0 1-3.7-.9 8 8 0 0 0-3.7-.7q-2.2 0-3.7.8c-1.5.8-2.3.8-3.8.8v-2.3a8 8 0 0 0 3.8-.9 10 10 0 0 1 7.4 0 8 8 0 0 0 3.7.9 8 8 0 0 0 3.8-.8q1.5-.8 3.8-.8t3.7.8c1.5.8 2.3.8 3.7.8v2.3"/> + <path fill="#005bbf" d="M149.6 329a8 8 0 0 1-3.7-.8q-1.6-.8-3.7-.8a8 8 0 0 0-3.8.8q-1.5.8-3.8.8a7 7 0 0 1-3.7-.9 8 8 0 0 0-3.7-.7q-2.2 0-3.7.8c-1.5.8-2.3.8-3.8.8v-2.3a8 8 0 0 0 3.8-.8q1.5-.8 3.7-.8t3.7.7a8 8 0 0 0 7.5 0q1.5-.6 3.8-.7t3.7.8c1.5.8 2.2.8 3.7.8z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M149.6 329a8 8 0 0 1-3.7-.8q-1.6-.8-3.7-.8a8 8 0 0 0-3.8.8q-1.5.8-3.8.8a7 7 0 0 1-3.7-.9 8 8 0 0 0-3.7-.7q-2.2 0-3.7.8c-1.5.8-2.3.8-3.8.8v-2.3a8 8 0 0 0 3.8-.8q1.5-.8 3.7-.8t3.7.7a8 8 0 0 0 7.5 0q1.5-.6 3.8-.7t3.7.8c1.5.8 2.2.8 3.7.8z"/> + <path fill="#c8b100" d="m126.2 308 .2.5c0 1.5-1.3 2.6-2.7 2.6h22a3 3 0 0 1-2.7-2.6v-.5z"/> + <path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="m126.2 308 .2.5c0 1.5-1.3 2.6-2.7 2.6h22a3 3 0 0 1-2.7-2.6v-.5z"/> + <path fill="#c8b100" d="M126.6 306.5h16q1 0 1 .8 0 .6-1 .7h-16q-.9 0-1-.8.1-.6 1-.7"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M126.6 306.5h16q1 0 1 .8 0 .6-1 .7h-16q-.9 0-1-.8.1-.6 1-.7z"/> + <path fill="#c8b100" d="M123.7 316.7h22V311h-22z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M123.7 316.7h22V311h-22z"/> + <path fill="#ad1519" d="M122 286.7c-2.2 1.2-3.7 2.5-3.4 3.2q.2.8 1.8 1.6c1.5 1.1 2.5 3 1.7 4a5.5 5.5 0 0 0-.1-8.8"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M122 286.7c-2.2 1.2-3.7 2.5-3.4 3.2q.2.8 1.8 1.6c1.5 1.1 2.5 3 1.7 4a5.5 5.5 0 0 0-.1-8.8z"/> + <path fill="#ccc" d="M126.8 305.6h15.6V229h-15.6v76.5z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M138 229.2v76.3m1.7-76.3v76.3m-12.9 0h15.6v-76.4h-15.6v76.5z"/> + <path fill="#ad1519" d="M158.4 257.7a50 50 0 0 0-23.3-2c-9.4 1.6-16.5 5.3-15.9 8.4v.2l-3.5-8.2c-.6-3.3 7.2-7.5 17.6-9.2a43 43 0 0 1 9.2-.7c6.6 0 12.4.8 15.8 2.1v9.4"/> + <path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="M158.4 257.7a50 50 0 0 0-23.3-2c-9.4 1.6-16.5 5.3-15.9 8.4v.2l-3.5-8.2c-.6-3.3 7.2-7.5 17.6-9.2a43 43 0 0 1 9.2-.7c6.6 0 12.4.8 15.8 2.1v9.4"/> + <path fill="#ad1519" d="M126.8 267.3c-4.3-.3-7.3-1.4-7.6-3.2q-.3-2.2 3.8-4.5l3.8.3z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M126.8 267.3c-4.3-.3-7.3-1.4-7.6-3.2q-.3-2.2 3.8-4.5l3.8.3v7.4"/> + <path fill="#ad1519" d="M142.5 261.5q4.1.6 5.7 1.9l.1.2c.5 1-1.9 3-5.9 5.4v-7.5"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M142.5 261.5q4.1.6 5.7 1.9l.1.2c.5 1-1.9 3-5.9 5.4v-7.5"/> + <path fill="#ad1519" d="M117.1 282c-.4-1.2 3.8-3.6 9.8-5.8l7.8-3.2c8.3-3.7 14.4-7.9 13.6-9.4v-.2c.4.4 1 8 1 8 .8 1.3-4.8 5.5-12.4 9.1-2.5 1.2-7.6 3-10 4-4.4 1.4-8.7 4.3-8.3 5.3l-1.5-7.7"/> + <path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="M117.1 282c-.4-1.2 3.8-3.6 9.8-5.8l7.8-3.2c8.3-3.7 14.4-7.9 13.6-9.4v-.2c.4.4 1 8 1 8 .8 1.3-4.8 5.5-12.4 9.1-2.5 1.2-7.6 3-10 4-4.4 1.4-8.7 4.3-8.3 5.3l-1.5-7.7z"/> + <path fill="#c8b100" d="M125.8 254c1.9-.6 3.1-1.5 2.5-3q-.6-1.3-2.8-.6l-2.6 1 2.3 5.8.8-.3.8-.3zm-1.2-2.7.7-.3q1-.3 1.4.8.4.8-.5 1.5l-.6.3zm7.3-2.5-.9.3h-.8l1.3 6.1 4.3-.8-.2-.4v-.4l-2.5.6zm8.4 5.2q1.2-3.3 2.7-6.4h-1l-1.8 4.6-2.4-4.3-1 .1h-1l3.5 6zm8.8-4.7.4-.9a3 3 0 0 0-1.7-.6q-2.6 0-2.8 1.7c-.2 2.1 3.2 2 3 3.4q-.2 1-1.4.8-1.3 0-1.4-1.2h-.3l-.4 1.1a4 4 0 0 0 1.8.6q2.7.2 3.2-1.7c.2-2-3.3-2.1-3.1-3.4q0-.8 1.3-.7 1 0 1.2.9z"/> + <path fill="#ad1519" d="M277.9 211.6s-.7.8-1.3.9c-.5 0-1.1-.5-1.1-.5s-.5.5-1 .6-1.4-.6-1.4-.6l-1 1-1.1-.3c-.1-.1-.3.4-.7.6h-.4l-.6-.4-.7-.7-.5-.3-.4-1v-.5q0-1 2.2-1.7a4 4 0 0 1 2 0q.9-.8 3-.8c2.1 0 2.4.3 3 .7a6 6 0 0 1 2.9-.7q2.1 0 3 .8.8-.3 2 0 2.2.6 2.2 1.7v.5l-.4 1-.6.3-.6.7-.6.3s-.3.2-.4 0q-.6-.3-.7-.5c-.1-.2-.6.4-1 .2s-1-1-1-1-.9.8-1.4.7c-.6-.1-1-.6-1-.6s-.7.6-1.2.5-1.2-.9-1.2-.9"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M277.9 211.6s-.7.8-1.3.9c-.5 0-1.1-.5-1.1-.5s-.5.5-1 .6-1.4-.6-1.4-.6l-1 1-1.1-.3c-.1-.1-.3.4-.7.6h-.4l-.6-.4-.7-.7-.5-.3-.4-1v-.5q0-1 2.2-1.7a4 4 0 0 1 2 0q.9-.8 3-.8c2.1 0 2.4.3 3 .7a6 6 0 0 1 2.9-.7q2.1 0 3 .8.8-.3 2 0 2.2.6 2.2 1.7v.5l-.4 1-.6.3-.6.7-.6.3s-.3.2-.4 0q-.6-.3-.7-.5c-.1-.2-.6.4-1 .2s-1-1-1-1-.9.8-1.4.7c-.6-.1-1-.6-1-.6s-.7.6-1.2.5-1.2-.9-1.2-.9z"/> + <path fill="#c8b100" d="M276.5 207.6c0-1 .6-2 1.3-2s1.3 1 1.3 2-.5 1.8-1.3 1.8q-1.2-.1-1.3-1.9"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M276.5 207.6c0-1 .6-2 1.3-2s1.3 1 1.3 2-.5 1.8-1.3 1.8q-1.2-.1-1.3-1.9z"/> + <path fill="#c8b100" d="M277.3 207.6q0-1.7.5-1.8.6.2.7 1.8c.1 1.6-.3 1.7-.6 1.7q-.6-.2-.6-1.8"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M277.3 207.6q0-1.7.5-1.8.6.2.7 1.8c.1 1.6-.3 1.7-.6 1.7q-.6-.2-.6-1.8z"/> + <path fill="#c8b100" d="m271 215.3-.5-1a27 27 0 0 1 14.8 0l-.6.8-.3.8a23 23 0 0 0-6.6-.8c-2.6 0-5.2.3-6.6.8z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m271 215.3-.5-1a27 27 0 0 1 14.8 0l-.6.8-.3.8a23 23 0 0 0-6.6-.8c-2.6 0-5.2.3-6.6.8l-.2-.6"/> + <path fill="#c8b100" d="M277.8 217.7c2.4 0 5-.4 5.9-.6q1-.3 1-.8 0-.3-.4-.4a24 24 0 0 0-6.5-.8c-2.5 0-5 .3-6.4.8q-.3 0-.4.3 0 .6 1 .9c1 .2 3.5.6 5.8.6"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M277.8 217.7c2.4 0 5-.4 5.9-.6q1-.3 1-.8 0-.3-.4-.4a24 24 0 0 0-6.5-.8c-2.5 0-5 .3-6.4.8q-.3 0-.4.3 0 .6 1 .9c1 .2 3.5.6 5.8.6z"/> + <path fill="#fff" d="M283.5 208.4q0-.3.4-.4.5 0 .5.4t-.5.4a.4.4 0 0 1-.4-.4"/> + <path fill="none" stroke="#000" stroke-width=".2" d="M283.5 208.4q0-.3.4-.4.5 0 .5.4t-.5.4a.4.4 0 0 1-.4-.4zm-.2-1.4a.4.4 0 0 1 .4-.4q.4 0 .4.4t-.4.4a.4.4 0 0 1-.4-.4zm-1.1-1q0-.3.4-.3t.4.4-.4.4a.4.4 0 0 1-.4-.5zm-1.4-.4q0-.3.4-.4.5 0 .5.4t-.4.4q-.5-.1-.5-.4zm-1.4 0q0-.3.5-.3t.4.4q0 .3-.4.4a.4.4 0 0 1-.5-.4z"/> + <path fill="none" stroke="#000" stroke-linecap="round" stroke-width=".3" d="m287.8 211.2.2-1a2.7 2.7 0 0 0-2.7-2.8q-.8 0-1.3.3"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m283 209.2.2-.8q-.2-1.8-2.5-2-1 0-1.6.4"/> + <path fill="none" stroke="#000" stroke-width=".2" d="M288.2 210q0-.5.4-.5t.4.4-.4.4q-.5 0-.4-.4zm-.2-1.6q0-.3.4-.4a.4.4 0 0 1 .5.4q0 .3-.4.4-.5 0-.5-.4zm-1-1.1a.4.4 0 0 1 .5-.4q.3 0 .4.4a.4.4 0 0 1-.4.4.4.4 0 0 1-.5-.4zm-1.3-.7q0-.4.5-.4t.4.4q0 .5-.4.5a.4.4 0 0 1-.5-.5zm-1.4.1q0-.3.5-.4.6 0 .4.4t-.4.4q-.4-.1-.5-.4z"/> + <path fill="#c8b100" d="m285.3 213.2-.5-.5s-.6.3-1.3.2c-.6 0-.9-1-.9-1s-.7.7-1.3.7-1-.6-1-.6-.7.5-1.3.4-1.2-.8-1.2-.8-.6.8-1.2.8c-.6.1-1-.5-1-.5s-.3.6-1.1.7-1.4-.6-1.4-.6-.4.7-1 1c-.5 0-1.2-.4-1.2-.4l-.1.5-.3.1.1.5a27 27 0 0 1 7.3-.9q4.3 0 7.3 1l.2-.6"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m285.3 213.2-.5-.5s-.6.3-1.3.2c-.6 0-.9-1-.9-1s-.7.7-1.3.7-1-.6-1-.6-.7.5-1.3.4-1.2-.8-1.2-.8-.6.8-1.2.8c-.6.1-1-.5-1-.5s-.3.6-1.1.7-1.4-.6-1.4-.6-.4.7-1 1c-.5 0-1.2-.4-1.2-.4l-.1.5-.3.1.1.5a27 27 0 0 1 7.3-.9q4.3 0 7.3 1l.2-.6z"/> + <path fill="#fff" d="M271.3 208.4q0-.3.4-.4.5 0 .4.4a.4.4 0 0 1-.4.4.4.4 0 0 1-.4-.4"/> + <path fill="none" stroke="#000" stroke-width=".2" d="M271.3 208.4q0-.3.4-.4.5 0 .4.4a.4.4 0 0 1-.4.4.4.4 0 0 1-.4-.4zm.2-1.4q0-.4.4-.4t.5.4q0 .5-.5.4a.4.4 0 0 1-.4-.4zm1-1q.1-.3.5-.3t.5.4q0 .3-.5.4a.4.4 0 0 1-.4-.5zm1.4-.4q0-.3.5-.4.6 0 .4.4t-.4.4q-.4-.1-.5-.4zm1.4 0q0-.3.5-.3.3 0 .4.4 0 .3-.4.4a.4.4 0 0 1-.5-.4z"/> + <path fill="none" stroke="#000" stroke-linecap="round" stroke-width=".3" d="m267.8 211.2-.2-1a2.7 2.7 0 0 1 2.7-2.8q.8 0 1.4.3"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m272.7 209.2-.3-.8c0-1 1.2-2 2.6-2a3 3 0 0 1 1.5.4"/> + <path fill="none" stroke="#000" stroke-width=".2" d="M266.6 210q0-.5.4-.5t.4.4a.4.4 0 0 1-.4.4q-.3 0-.4-.4zm.1-1.6q.1-.3.5-.4.5 0 .4.4t-.4.4q-.4-.1-.4-.4zm1-1.1q0-.4.5-.4a.4.4 0 0 1 .4.4.4.4 0 0 1-.4.4.4.4 0 0 1-.5-.4zm1.3-.7q0-.4.5-.4.3 0 .4.4 0 .5-.4.5a.4.4 0 0 1-.5-.5zm1.4.1q0-.3.5-.4a.4.4 0 0 1 .4.4.4.4 0 0 1-.4.4q-.5 0-.5-.4z"/> + <path fill="#c8b100" d="M277.9 210.7h.2v.4q.1 1 1 1a1 1 0 0 0 1-.7l.2-.3v.4q.3.8 1.1.8 1 0 1-1v-.1l.4-.4.2.5-.1.4a1 1 0 0 0 1 1q.6 0 .9-.5l.2-.2v.3q0 .5.4.7l1-.4c1-.5.7-.7.7-.7v.4s-.5.8-1 1q-.3.4-.8.3-.5 0-.7-.6l-.7.2q-1 0-1.4-.8l-1.1.5q-.8 0-1.2-.6l-1 .4q-.9 0-1.4-.6-.4.6-1.2.6-.6 0-1-.4a2 2 0 0 1-1.3.6q-.6 0-1.1-.5-.4.8-1.4.8-.4 0-.7-.2-.2.5-.7.6t-.9-.2l-1-1 .1-.5.8.7q.8.5.9.4.4-.1.4-.7v-.3l.2.2q.3.5.9.5a1 1 0 0 0 1-1v-.9l.4.4v.1q.1 1 1 1t1.1-.9v-.3l.2.3q.4.6 1 .7 1 0 1.1-1v-.3h.2"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M277.9 210.7h.2v.4q.1 1 1 1a1 1 0 0 0 1-.7l.2-.3v.4q.3.8 1.1.8 1 0 1-1v-.1l.4-.4.2.5-.1.4a1 1 0 0 0 1 1q.6 0 .9-.5l.2-.2v.3q0 .5.4.7l1-.4c1-.5.7-.7.7-.7v.4s-.5.8-1 1q-.3.4-.8.3-.5 0-.7-.6l-.7.2q-1 0-1.4-.8l-1.1.5q-.8 0-1.2-.6l-1 .4q-.9 0-1.4-.6-.4.6-1.2.6-.6 0-1-.4a2 2 0 0 1-1.3.6q-.6 0-1.1-.5-.4.8-1.4.8-.4 0-.7-.2-.2.5-.7.6t-.9-.2l-1-1 .1-.5.8.7q.8.5.9.4.4-.1.4-.7v-.3l.2.2q.3.5.9.5a1 1 0 0 0 1-1v-.9l.4.4v.1q.1 1 1 1t1.1-.9v-.3l.2.3q.4.6 1 .7 1 0 1.1-1v-.3h.2z"/> + <path fill="#c8b100" d="M277.8 213.3q-4.5 0-7.3 1l-.3-.2.1-.3q3-1 7.5-1t7.6 1l.1.3-.3.2a27 27 0 0 0-7.4-1"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M277.8 213.3q-4.5 0-7.3 1l-.3-.2.1-.3q3-1 7.5-1t7.6 1l.1.3-.3.2a27 27 0 0 0-7.4-1z"/> + <path fill="#fff" d="M275 214.4q0-.4.5-.4a.4.4 0 0 1 .4.4.4.4 0 0 1-.4.4q-.5 0-.5-.4"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M275 214.4q0-.4.5-.4a.4.4 0 0 1 .4.4.4.4 0 0 1-.4.4q-.5 0-.5-.4z"/> + <path fill="#ad1519" d="M277.9 214.5h-1q-.2 0-.3-.3l.3-.3h2l.2.3-.3.3h-1"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M277.9 214.5h-1q-.2 0-.3-.3l.3-.3h2l.2.3-.3.3h-1"/> + <path fill="#058e6e" d="M273.2 214.9h-.6a.3.3 0 0 1-.4-.2l.3-.3.6-.1.7-.1q.3 0 .4.2a.3.3 0 0 1-.3.4z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M273.2 214.9h-.6a.3.3 0 0 1-.4-.2l.3-.3.6-.1.7-.1q.3 0 .4.2a.3.3 0 0 1-.3.4h-.7"/> + <path fill="#ad1519" d="m270.5 215.3.3-.4h.7l-.4.6z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m270.5 215.3.3-.4h.7l-.4.6-.6-.2"/> + <path fill="#fff" d="M279.8 214.4q0-.4.4-.4t.5.4q0 .3-.5.4a.4.4 0 0 1-.4-.4"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M279.8 214.4q0-.4.4-.4t.5.4q0 .3-.5.4a.4.4 0 0 1-.4-.4z"/> + <path fill="#058e6e" d="M282.5 214.9h.7l.3-.2-.2-.3-.7-.1-.7-.1q-.3 0-.4.2 0 .3.3.4z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M282.5 214.9h.7l.3-.2-.2-.3-.7-.1-.7-.1q-.3 0-.4.2 0 .3.3.4h.7"/> + <path fill="#ad1519" d="m285.1 215.4-.2-.5h-.7l.3.6z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m285.1 215.4-.2-.5h-.7l.3.6.6-.1"/> + <path fill="#ad1519" d="M277.8 217.1a25 25 0 0 1-6-.6 25 25 0 0 1 6-.7q3.6 0 6.1.7-2.5.6-6 .6"/> + <path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".3" d="M277.8 217.1a25 25 0 0 1-6-.6 25 25 0 0 1 6-.7q3.6 0 6.1.7-2.5.6-6 .6z"/> + <path fill="#c8b100" d="m285.2 212-.1-.3q-.3 0-.4.2l.1.4q.3 0 .4-.3"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m285.2 212-.1-.3q-.3 0-.4.2l.1.4q.3 0 .4-.3z"/> + <path fill="#c8b100" d="M280.6 211.2q0-.3-.3-.4l-.2.3q0 .3.2.4z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M280.6 211.2q0-.3-.3-.4l-.2.3q0 .3.2.4z"/> + <path fill="#c8b100" d="M275.2 211.2q0-.3.2-.4l.3.3-.2.4q-.3 0-.3-.3"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M275.2 211.2q0-.3.2-.4l.3.3-.2.4q-.3 0-.3-.3z"/> + <path fill="#c8b100" d="m270.5 212 .1-.3q.3 0 .4.2l-.1.4q-.3 0-.4-.3"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m270.5 212 .1-.3q.3 0 .4.2l-.1.4q-.3 0-.4-.3z"/> + <path fill="#c8b100" d="m277.8 208.5-.8.5.6 1.3.2.1.3-.1.6-1.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m277.8 208.5-.8.5.6 1.3.2.1.3-.1.6-1.3-.9-.5"/> + <path fill="#c8b100" d="m276 210.5.4.5 1.3-.4.1-.2-.1-.2-1.3-.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m276 210.5.4.5 1.3-.4.1-.2-.1-.2-1.3-.3-.4.6"/> + <path fill="#c8b100" d="m279.6 210.5-.3.5-1.3-.4-.1-.2v-.2l1.4-.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m279.6 210.5-.3.5-1.3-.4-.1-.2v-.2l1.4-.3.4.6"/> + <path fill="#c8b100" d="m272.5 209-.7.7.9 1 .2.1.2-.1.2-1.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m272.5 209-.7.7.9 1 .2.1.2-.1.2-1.3-.8-.3"/> + <path fill="#c8b100" d="m271.1 211.2.5.5 1.2-.6v-.2l-.1-.2-1.3-.1z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m271.1 211.2.5.5 1.2-.6v-.2l-.1-.2-1.3-.1-.3.6"/> + <path fill="#c8b100" d="m274.7 210.5-.3.6h-1.3l-.2-.2.1-.3 1.2-.6z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m274.7 210.5-.3.6h-1.3l-.2-.2.1-.3 1.2-.6.5.5"/> + <path fill="#c8b100" d="M269.8 211.4v.6l-1.4.2-.2-.1v-.2l1-.9z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M269.8 211.4v.6l-1.4.2-.2-.1v-.2l1-.9.6.4"/> + <path fill="#c8b100" d="M272.4 210.9q0-.5.5-.5l.5.5-.5.4z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M272.4 210.9q0-.5.5-.5l.5.5-.5.4z"/> + <path fill="#c8b100" d="m283.2 209 .7.7-.9 1-.2.1-.1-.1-.3-1.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m283.2 209 .7.7-.9 1-.2.1-.1-.1-.3-1.3.8-.3"/> + <path fill="#c8b100" d="m284.6 211.2-.5.5-1.2-.6v-.2l.1-.2 1.3-.1z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m284.6 211.2-.5.5-1.2-.6v-.2l.1-.2 1.3-.1.3.6"/> + <path fill="#c8b100" d="m281 210.5.3.6h1.3l.2-.2-.1-.3-1.2-.6z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m281 210.5.3.6h1.3l.2-.2-.1-.3-1.2-.6-.5.5"/> + <path fill="#c8b100" d="M285.7 211.4v.6l1.4.2.2-.1v-.2l-1-.9z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M285.7 211.4v.6l1.4.2.2-.1v-.2l-1-.9-.6.4"/> + <path fill="#c8b100" d="M277.4 210.4q0-.3.5-.4.4 0 .4.4t-.4.5z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M277.4 210.4q0-.3.5-.4.4 0 .4.4t-.4.5z"/> + <path fill="#c8b100" d="M282.3 210.9q.1-.5.5-.5t.5.5q0 .6-.5.4z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M282.3 210.9q.1-.5.5-.5t.5.5q0 .6-.5.4z"/> + <path fill="#c8b100" d="M277 205.4q.1-.8.8-.8 1 0 1 .8c0 .8-.5.8-1 .8a1 1 0 0 1-.8-.8"/> + <path fill="#c8b100" d="M278.5 205.1v.6H277v-.6h.4v-1.3h-.5v-.5h.5v-.6h.6v.6h.6v.6h-.6v1.2h.4"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M278.5 205.1v.6H277v-.6h.4v-1.3h-.5v-.5h.5v-.6h.6v.6h.6v.6h-.6v1.2h.4z"/> + <path fill="#c8b100" d="M279 205.1v.6h-2.4v-.6h1v-1.3h-.7v-.5h.6v-.6h.6v.6h.6v.6h-.6v1.2h1"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M278.1 204.6q.6.1.6.8t-.9.8a1 1 0 0 1-.8-.8q0-.6.6-.8"/> + <path fill="#c8b100" d="m268 212.2-.6-.7-.7-.3q0-.2.6-.3.3 0 .4.2v-.2s.3 0 .4.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m268 212.2-.6-.7-.7-.3q0-.2.6-.3.3 0 .4.2v-.2s.3 0 .4.3z"/> + <path fill="#c8b100" d="M268 212q.3-.3.5 0 .4.2.1.5l-.5-.1q-.2-.2 0-.5"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M268 212q.3-.3.5 0 .4.2.1.5l-.5-.1q-.2-.2 0-.5z"/> + <path fill="#c8b100" d="m287.5 212.2.6-.7c.2-.2.7-.3.7-.3q0-.2-.6-.3l-.4.2v-.2s-.3 0-.4.3v.7z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m287.5 212.2.6-.7c.2-.2.7-.3.7-.3q0-.2-.6-.3l-.4.2v-.2s-.3 0-.4.3v.7z"/> + <path fill="#c8b100" d="M287.5 212q-.2-.3-.5 0t-.1.5l.5-.1q.3-.2.1-.5"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M287.5 212q-.2-.3-.5 0t-.1.5l.5-.1q.3-.2.1-.5z"/> + <path fill="#c8b100" d="M267.2 223h21.4v-5.5h-21.4z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M267.2 223h21.4v-5.5h-21.4z"/> + <path fill="#c8b100" d="M286.3 226.8h-16.9q1-.4 1-1.2 0-1-1-1.3h17-.1q-1 .3-1 1.3 0 .8 1 1.2"/> + <path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="M286.3 226.8h-16.9q1-.4 1-1.2 0-1-1-1.3h17-.1q-1 .3-1 1.3 0 .8 1 1.2z"/> + <path fill="#c8b100" d="M269.9 226.8h16q1 0 1 .7t-1 .8h-16q-1-.1-1-.8.1-1 1-.8"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M269.9 226.8h16q1 0 1 .7t-1 .8h-16q-1-.1-1-.8.1-1 1-.8z"/> + <path fill="#c8b100" d="M269.9 223h16q1 .1 1 .7t-1 .6h-16q-1 0-1-.6t1-.6"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M269.9 223h16q1 .1 1 .7t-1 .6h-16q-1 0-1-.6t1-.6z"/> + <path fill="#005bbf" d="M263 317.4q2.1 0 3.7-.8a8 8 0 0 1 3.7-.8q2.2 0 3.8.8c1.6.8 2.3.8 3.7.8q2.3 0 3.8-.8a8 8 0 0 1 3.6-.8 8 8 0 0 1 3.7.8q1.6.8 3.8.8v2.4a8 8 0 0 1-3.8-.9 8 8 0 0 0-3.7-.8q-2.2 0-3.6.8-1.5.8-3.8.9a8 8 0 0 1-3.7-.9 8 8 0 0 0-3.8-.8 8 8 0 0 0-3.7.8q-1.5.8-3.8.9v-2.4"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M263 317.4q2.1 0 3.7-.8a8 8 0 0 1 3.7-.8q2.2 0 3.8.8c1.6.8 2.3.8 3.7.8q2.3 0 3.8-.8a8 8 0 0 1 3.6-.8 8 8 0 0 1 3.7.8q1.6.8 3.8.8v2.4a8 8 0 0 1-3.8-.9 8 8 0 0 0-3.7-.8q-2.2 0-3.6.8-1.5.8-3.8.9a8 8 0 0 1-3.7-.9 8 8 0 0 0-3.8-.8 8 8 0 0 0-3.7.8q-1.5.8-3.8.9v-2.4z"/> + <path fill="#ccc" d="M263 319.8q2.1 0 3.7-.9c1.6-.9 2.3-.8 3.7-.8q2.2 0 3.8.8c1.6.8 2.3.9 3.7.9a8 8 0 0 0 3.8-.9 8 8 0 0 1 3.6-.8q2.3 0 3.7.8 1.6.8 3.8.9v2.3a8 8 0 0 1-3.8-.9 8 8 0 0 0-3.7-.7q-2.2 0-3.6.7-1.5.8-3.8.9a7 7 0 0 1-3.7-.9q-1.5-.6-3.8-.7a8 8 0 0 0-3.7.7 8 8 0 0 1-3.8.9v-2.3"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M263 319.8q2.1 0 3.7-.9c1.6-.9 2.3-.8 3.7-.8q2.2 0 3.8.8c1.6.8 2.3.9 3.7.9a8 8 0 0 0 3.8-.9 8 8 0 0 1 3.6-.8q2.3 0 3.7.8 1.6.8 3.8.9v2.3a8 8 0 0 1-3.8-.9 8 8 0 0 0-3.7-.7q-2.2 0-3.6.7-1.5.8-3.8.9a7 7 0 0 1-3.7-.9q-1.5-.6-3.8-.7a8 8 0 0 0-3.7.7 8 8 0 0 1-3.8.9v-2.3"/> + <path fill="#005bbf" d="M263 322q2.1 0 3.7-.8 1.6-.6 3.7-.7 2.2 0 3.8.7c1.6.7 2.3.9 3.7.9a8 8 0 0 0 3.8-.9 8 8 0 0 1 3.6-.8 8 8 0 0 1 3.7.8q1.6.8 3.8.9v2.3a8 8 0 0 1-3.8-.9 8 8 0 0 0-3.7-.7q-2.2 0-3.6.7-1.5.9-3.8.9t-3.7-.8a8 8 0 0 0-3.8-.8 8 8 0 0 0-3.7.8q-1.5.8-3.8.8V322"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M263 322q2.1 0 3.7-.8 1.6-.6 3.7-.7 2.2 0 3.8.7c1.6.7 2.3.9 3.7.9a8 8 0 0 0 3.8-.9 8 8 0 0 1 3.6-.8 8 8 0 0 1 3.7.8q1.6.8 3.8.9v2.3a8 8 0 0 1-3.8-.9 8 8 0 0 0-3.7-.7q-2.2 0-3.6.7-1.5.9-3.8.9t-3.7-.8a8 8 0 0 0-3.8-.8 8 8 0 0 0-3.7.8q-1.5.8-3.8.8V322"/> + <path fill="#ccc" d="M263 326.7a8 8 0 0 0 3.7-.8q1.6-.8 3.7-.8 2.2 0 3.8.8c1.6.8 2.3.8 3.7.8q2.3 0 3.8-.9a8 8 0 0 1 3.6-.7q2.3 0 3.7.8a8 8 0 0 0 3.8.8v-2.3a8 8 0 0 1-3.8-.9 8 8 0 0 0-3.7-.7q-2.2 0-3.6.7-1.5.8-3.8.9t-3.7-.8a8 8 0 0 0-3.8-.8 8 8 0 0 0-3.7.8q-1.5.8-3.8.8v2.3"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M263 326.7a8 8 0 0 0 3.7-.8q1.6-.8 3.7-.8 2.2 0 3.8.8c1.6.8 2.3.8 3.7.8q2.3 0 3.8-.9a8 8 0 0 1 3.6-.7q2.3 0 3.7.8a8 8 0 0 0 3.8.8v-2.3a8 8 0 0 1-3.8-.9 8 8 0 0 0-3.7-.7q-2.2 0-3.6.7-1.5.8-3.8.9t-3.7-.8a8 8 0 0 0-3.8-.8 8 8 0 0 0-3.7.8q-1.5.8-3.8.8v2.3"/> + <path fill="#005bbf" d="M263 329a8 8 0 0 0 3.7-.8q1.6-.8 3.7-.8 2.2 0 3.8.8c1.6.8 2.3.8 3.7.8a8 8 0 0 0 3.8-.9 8 8 0 0 1 3.6-.7q2.3 0 3.7.8 1.6.8 3.8.8v-2.3a8 8 0 0 1-3.8-.8 8 8 0 0 0-3.7-.8 8 8 0 0 0-3.6.7 8 8 0 0 1-3.8.9q-2.2 0-3.7-.8t-3.8-.8q-2.1 0-3.7.8c-1.6.8-2.3.8-3.8.8v2.3"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M263 329a8 8 0 0 0 3.7-.8q1.6-.8 3.7-.8 2.2 0 3.8.8c1.6.8 2.3.8 3.7.8a8 8 0 0 0 3.8-.9 8 8 0 0 1 3.6-.7q2.3 0 3.7.8 1.6.8 3.8.8v-2.3a8 8 0 0 1-3.8-.8 8 8 0 0 0-3.7-.8 8 8 0 0 0-3.6.7 8 8 0 0 1-3.8.9q-2.2 0-3.7-.8t-3.8-.8q-2.1 0-3.7.8c-1.6.8-2.3.8-3.8.8v2.3z"/> + <path fill="#c8b100" d="m286.3 308-.1.5c0 1.5 1.2 2.6 2.7 2.6h-22c1.5 0 2.7-1.2 2.7-2.6l-.1-.5z"/> + <path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="m286.3 308-.1.5c0 1.5 1.2 2.6 2.7 2.6h-22c1.5 0 2.7-1.2 2.7-2.6l-.1-.5z"/> + <path fill="#c8b100" d="M269.9 306.5h16q1 0 1 .8 0 .6-1 .7h-16q-1 0-1-.8.1-.6 1-.7"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M269.9 306.5h16q1 0 1 .8 0 .6-1 .7h-16q-1 0-1-.8.1-.6 1-.7z"/> + <path fill="#c8b100" d="M266.9 316.7h22V311h-22z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M266.9 316.7h22V311h-22z"/> + <path fill="#ad1519" d="M290.6 286.7c2.1 1.2 3.6 2.5 3.4 3.2q-.2.8-1.8 1.6c-1.6 1.1-2.5 3-1.8 4a5.5 5.5 0 0 1 .2-8.8"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M290.6 286.7c2.1 1.2 3.6 2.5 3.4 3.2q-.2.8-1.8 1.6c-1.6 1.1-2.5 3-1.8 4a5.5 5.5 0 0 1 .2-8.8z"/> + <path fill="#ccc" d="M270.1 305.6h15.6V229h-15.6v76.5z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M281.4 229.1v76.3m1.8-76.3v76.3m-13 .2h15.5V229h-15.6v76.5z"/> + <path fill="#ad1519" d="M254.2 257.7a50 50 0 0 1 23.3-2c9.3 1.6 16.4 5.3 15.9 8.4v.2l3.5-8.2c.6-3.3-7.3-7.5-17.6-9.2a54 54 0 0 0-9.2-.7c-6.7 0-12.4.8-15.9 2.1z"/> + <path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="M254.2 257.7a50 50 0 0 1 23.3-2c9.3 1.6 16.4 5.3 15.9 8.4v.2l3.5-8.2c.6-3.3-7.3-7.5-17.6-9.2a54 54 0 0 0-9.2-.7c-6.7 0-12.4.8-15.9 2.1v9.4"/> + <path fill="#ad1519" d="M285.7 267.3c4.4-.3 7.3-1.4 7.7-3.2q.2-2.2-3.8-4.5l-3.9.3z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M285.7 267.3c4.4-.3 7.3-1.4 7.7-3.2q.2-2.2-3.8-4.5l-3.9.3v7.4"/> + <path fill="#ad1519" d="M270 261.5a13 13 0 0 0-5.7 1.9v.2c-.5 1 1.8 3 5.8 5.4v-7.5"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M270 261.5a13 13 0 0 0-5.7 1.9v.2c-.5 1 1.8 3 5.8 5.4v-7.5"/> + <path fill="#ad1519" d="M295.4 282c.4-1.2-3.8-3.6-9.7-5.8-2.8-1-5-2-7.8-3.2-8.3-3.7-14.4-7.9-13.6-9.4v-.2c-.4.4-1 8-1 8-.8 1.3 4.8 5.5 12.4 9.1 2.4 1.2 7.6 3 10 4 4.3 1.4 8.7 4.3 8.3 5.3l1.4-7.7"/> + <path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="M295.4 282c.4-1.2-3.8-3.6-9.7-5.8-2.8-1-5-2-7.8-3.2-8.3-3.7-14.4-7.9-13.6-9.4v-.2c-.4.4-1 8-1 8-.8 1.3 4.8 5.5 12.4 9.1 2.4 1.2 7.6 3 10 4 4.3 1.4 8.7 4.3 8.3 5.3l1.4-7.7z"/> + <path fill="#c8b100" d="m263.9 254.4 2.1-6.6h-.5l-.5.1-1.4 4.8q-1.6-2-2.7-4.1l-1 .2h-1l4 5.7h.5zm6-6.6h-1.8v6.2h4.2v-.7h-2.6zm6.8 1 2 .3v-.7l-5.8-.5v.8h2l-.4 5.6h1.6l.5-5.4m2.4 6 .8.2.8.2.7-2.9.6 1.2.8 2.1 1 .2q.6 0 1 .3l-.3-.7-1.3-2.9q1.6 0 2.1-1.2.3-.8-.7-1.5-.8-.4-1.7-.5l-2.4-.5zm3-5.2c.7.2 1.5.3 1.5 1v.5q-.5 1.3-2 .9zm8 7-.2 2 .8.5.9.5.5-7-.7-.3-6.1 3.8.5.3.4.2 1.7-1.2 2.3 1.3zm-1.7-1.5 2-1.4-.2 2.3z"/> + <path fill="none" stroke="#000" stroke-width=".1" d="M182.2 192.4c0-1 1-2 2-2s2.2 1 2.2 2c0 1.1-1 2-2.1 2a2 2 0 0 1-2.1-2z"/> + <path fill="#ad1519" stroke="#000" stroke-width=".3" d="M205.7 175.4c6.3 0 12 1 15.7 2.4a32 32 0 0 0 14.6 2.3c2.7 0 6.5.8 10.3 2.4a27 27 0 0 1 7.4 4.7l-1.5 1.4-.4 3.8-4.1 4.7-2 1.8-5 3.9-2.5.2-.7 2.1-31.6-3.7-31.7 3.7-.8-2.1-2.5-.2-4.9-4-2-1.7-4.1-4.7-.5-3.8-1.5-1.4a28 28 0 0 1 7.5-4.7 26 26 0 0 1 10.2-2.4q3 .3 6.6-.2a30 30 0 0 0 8-2q5.6-2.4 15.5-2.5z"/> + <path fill="#c8b100" stroke="#000" stroke-width=".4" d="M206.2 217.1c-11.8 0-22.4-1.4-29.9-3.6a1 1 0 0 1-.8-1.2q0-.8.8-1.2a109 109 0 0 1 29.9-3.6c11.7 0 22.3 1.4 29.8 3.6a1.3 1.3 0 0 1 0 2.4c-7.5 2.2-18 3.6-29.8 3.6"/> + <path fill="#ad1519" d="M206.1 215.6c-10.6 0-20.2-1.2-27.5-3.1 7.3-2 16.9-3 27.5-3.1a115 115 0 0 1 27.6 3c-7.3 2-17 3.2-27.6 3.2"/> + <path fill="none" stroke="#000" stroke-width=".1" d="M206.9 215.7v-6.3m-1.7 6.3v-6.3"/> + <path fill="none" stroke="#000" stroke-width=".2" d="M203.6 215.7v-6.3m-1.6 6.3v-6.3"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M200.6 215.7v-6.3m-2.8 5.9v-5.7m1.3 5.8v-6m-3.8 5.6v-5.2m1.3 5.4v-5.6"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M192 214.8V210m1 4.7V210m1.2 5v-5m-3.4 4.7v-4.5"/> + <path fill="none" stroke="#000" stroke-width=".5" d="M189.7 214.5v-4.2m-1.2 4.1v-4"/> + <path fill="none" stroke="#000" stroke-width=".6" d="M186 214v-3m1.3 3.2v-3.5m-2.5 3.1V211"/> + <path fill="none" stroke="#000" stroke-width=".7" d="M183.7 213.6v-2.3m-1.3 2v-1.8m-1.2 1.6v-1.3"/> + <path fill="none" stroke="#000" stroke-width=".9" d="M179.8 212.8v-.7"/> + <path fill="none" stroke="#000" stroke-width=".1" d="M213.7 215.3v-5.8m-2.9 6v-6.1m-2.1 6.2v-6.3"/> + <path fill="#c8b100" stroke="#000" stroke-width=".4" d="M206 207.4a108 108 0 0 0-30 3.9c.6-.3.5-1-.3-3-1-2.5-2.4-2.4-2.4-2.4 8.3-2.5 20-4 32.8-4a123 123 0 0 1 33 4s-1.5-.1-2.5 2.3q-1.2 2.8-.2 3c-7.5-2.2-18.4-3.7-30.3-3.7"/> + <path fill="#c8b100" stroke="#000" stroke-width=".4" d="M206.1 201.9c-12.9 0-24.5 1.5-32.8 4a1 1 0 0 1-1.3-.6 1 1 0 0 1 .7-1.3 121 121 0 0 1 33.4-4.2c13.2 0 25.2 1.7 33.5 4.2q1 .4.7 1.3t-1.3.6c-8.4-2.5-20-4-32.9-4"/> + <path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="M206.1 215.6c-10.6 0-20.2-1.2-27.5-3.1 7.3-2 16.9-3 27.5-3.1a115 115 0 0 1 27.6 3c-7.3 2-17 3.2-27.6 3.2z"/> + <path fill="#fff" stroke="#000" stroke-width=".4" d="M197 204.8q0-.9 1-1 .9.1 1 1 0 1.2-1 1a1 1 0 0 1-1-1"/> + <path fill="#ad1519" stroke="#000" stroke-width=".4" d="M206.1 205.6H203a1 1 0 0 1 0-2h6.4q.9.1 1 1 0 1.2-1 1h-3.2"/> + <path fill="#058e6e" stroke="#000" stroke-width=".4" d="m190.3 206.5-2.3.2q-.9 0-1.2-.8a1 1 0 0 1 1-1.1l2.2-.3 2.4-.3q.9 0 1.1.9 0 .9-.9 1l-2.3.4"/> + <path fill="#fff" stroke="#000" stroke-width=".4" d="M181 206.7q.1-1 1.1-1t1 1-1 1a1 1 0 0 1-1-1"/> + <path fill="#ad1519" stroke="#000" stroke-width=".4" d="m174 208.5 1.2-1.6 3.3.4-2.6 2-1.8-.8"/> + <path fill="#058e6e" stroke="#000" stroke-width=".4" d="m222 206.5 2.3.2q.9 0 1.1-.8a1 1 0 0 0-.9-1.1l-2.2-.3-2.4-.3a1 1 0 0 0-1.1.9q0 .9.9 1l2.3.4"/> + <path fill="#fff" stroke="#000" stroke-width=".4" d="M213.3 204.8q0-.9 1-1c1-.1 1 .5 1 1s-.4 1-1 1a1 1 0 0 1-1-1m15.8 1.9q.1-1 1-1t1.1 1q0 .9-1 1a1 1 0 0 1-1-1"/> + <path fill="#ad1519" stroke="#000" stroke-width=".4" d="m238.2 208.5-1.1-1.6-3.3.4 2.6 2 1.8-.8"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M177.3 212.8c7.4-2.1 17.6-3.4 28.8-3.4 11.3 0 21.4 1.3 28.9 3.4"/> + <path fill="#c8b100" d="m182.3 183.8 1.4 1 2-3.2a7 7 0 0 1-3.6-7.2c.2-4.1 5.2-7.6 11.7-7.6q5 .1 8.5 2.4l.2-1.8a17 17 0 0 0-8.7-2.1c-7.4 0-13.2 4.1-13.5 9.1a9 9 0 0 0 3 7.6z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="m182.3 183.8 1.4 1 2-3.2a7 7 0 0 1-3.6-7.2c.2-4.1 5.2-7.6 11.7-7.6q5 .1 8.5 2.4l.2-1.8a17 17 0 0 0-8.7-2.1c-7.4 0-13.2 4.1-13.5 9.1a9 9 0 0 0 3 7.6l-1 1.8"/> + <path fill="#c8b100" d="M182.4 183.8a9 9 0 0 1-4-7.3q.2-5 5.3-8a9 9 0 0 0-3.4 6.8 9 9 0 0 0 3 6.7z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M182.4 183.8a9 9 0 0 1-4-7.3q.2-5 5.3-8a9 9 0 0 0-3.4 6.8 9 9 0 0 0 3 6.7l-.9 1.8"/> + <path fill="#c8b100" d="M160.1 187.1a9 9 0 0 1-2.3-5.9q0-2 1-3.8c2-4.2 8.4-7.2 16-7.2q3 0 5.9.6l-1 1.4a26 26 0 0 0-4.9-.4c-7 0-12.8 2.7-14.5 6.3a7 7 0 0 0-.7 3.1 7 7 0 0 0 2.7 5.6l-2.6 4.1-1.3-1z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M160.1 187.1a9 9 0 0 1-2.3-5.9q0-2 1-3.8c2-4.2 8.4-7.2 16-7.2q3 0 5.9.6l-1 1.4a26 26 0 0 0-4.9-.4c-7 0-12.8 2.7-14.5 6.3a7 7 0 0 0-.7 3.1 7 7 0 0 0 2.7 5.6l-2.6 4.1-1.3-1z"/> + <path fill="#c8b100" d="M162.7 173.3a11 11 0 0 0-4 4.1 9 9 0 0 0-.9 3.8q.1 3.5 2.3 5.9l-1.5 2.5a10 10 0 0 1-2.3-6.5c0-4 2.5-7.5 6.4-9.8"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M162.7 173.3a11 11 0 0 0-4 4.1 9 9 0 0 0-.9 3.8q.1 3.5 2.3 5.9l-1.5 2.5a10 10 0 0 1-2.3-6.5c0-4 2.5-7.5 6.4-9.8z"/> + <path fill="#c8b100" d="M206 164.4c1.7 0 3.2 1.1 3.5 2.6q.4 2.1.4 4.5v1.1c.1 3.3.6 6.3 1.3 8.1l-5.2 5-5.2-5c.7-1.8 1.2-4.8 1.3-8.1v-1.1q0-2.4.4-4.5c.3-1.5 1.8-2.6 3.5-2.6"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M206 164.4c1.7 0 3.2 1.1 3.5 2.6q.4 2.1.4 4.5v1.1c.1 3.3.6 6.3 1.3 8.1l-5.2 5-5.2-5c.7-1.8 1.2-4.8 1.3-8.1v-1.1q0-2.4.4-4.5c.3-1.5 1.8-2.6 3.5-2.6z"/> + <path fill="#c8b100" d="M206 166q1.6.1 1.8 1.4.3 1.8.4 4.2v1q.2 5 1.2 7.7l-3.4 3.2-3.4-3.2q1-2.7 1.2-7.7v-1l.4-4.2a2 2 0 0 1 1.8-1.4"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M206 166q1.6.1 1.8 1.4.3 1.8.4 4.2v1q.2 5 1.2 7.7l-3.4 3.2-3.4-3.2q1-2.7 1.2-7.7v-1l.4-4.2a2 2 0 0 1 1.8-1.4z"/> + <path fill="#c8b100" d="m229.7 183.8-1.3 1-2-3.2a7 7 0 0 0 3.6-6.3v-.9c-.2-4.1-5.3-7.6-11.7-7.6a15 15 0 0 0-8.5 2.4l-.2-1.8a17 17 0 0 1 8.7-2.1c7.4 0 13.2 4.1 13.4 9.1a9 9 0 0 1-3 7.6z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="m229.7 183.8-1.3 1-2-3.2a7 7 0 0 0 3.6-6.3v-.9c-.2-4.1-5.3-7.6-11.7-7.6a15 15 0 0 0-8.5 2.4l-.2-1.8a17 17 0 0 1 8.7-2.1c7.4 0 13.2 4.1 13.4 9.1a9 9 0 0 1-3 7.6l1 1.8"/> + <path fill="#c8b100" d="M229.6 183.8a9 9 0 0 0 4.1-7.3c0-3.2-2.1-6.1-5.3-8a9 9 0 0 1 3.4 6.8 9 9 0 0 1-3.2 6.7z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M229.6 183.8a9 9 0 0 0 4.1-7.3c0-3.2-2.1-6.1-5.3-8a9 9 0 0 1 3.4 6.8 9 9 0 0 1-3.2 6.7l1 1.8"/> + <path fill="#c8b100" d="M252 187.1a9 9 0 0 0 2.2-5.9 9 9 0 0 0-.9-3.8c-2-4.2-8.4-7.2-16-7.2a29 29 0 0 0-6 .6l1 1.4a25 25 0 0 1 5-.4c7 0 12.8 2.7 14.4 6.3q.7 1.5.7 3.1a7 7 0 0 1-2.6 5.6l2.5 4.1 1.3-1z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M252 187.1a9 9 0 0 0 2.2-5.9 9 9 0 0 0-.9-3.8c-2-4.2-8.4-7.2-16-7.2a29 29 0 0 0-6 .6l1 1.4a25 25 0 0 1 5-.4c7 0 12.8 2.7 14.4 6.3q.7 1.5.7 3.1a7 7 0 0 1-2.6 5.6l2.5 4.1 1.3-1z"/> + <path fill="#c8b100" d="M249.3 173.3a11 11 0 0 1 4 4.1 9 9 0 0 1 .9 3.8 9 9 0 0 1-2.3 5.9l1.6 2.5a10 10 0 0 0 2.3-6.5c0-4-2.6-7.5-6.5-9.8"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M249.3 173.3a11 11 0 0 1 4 4.1 9 9 0 0 1 .9 3.8 9 9 0 0 1-2.3 5.9l1.6 2.5a10 10 0 0 0 2.3-6.5c0-4-2.6-7.5-6.5-9.8z"/> + <path fill="#fff" d="M204.2 181.4q.1-1.7 1.8-1.8c1.7-.1 1.9.8 1.9 1.8s-.9 1.7-1.9 1.7a2 2 0 0 1-1.8-1.7"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M204.2 181.4q.1-1.7 1.8-1.8c1.7-.1 1.9.8 1.9 1.8s-.9 1.7-1.9 1.7a2 2 0 0 1-1.8-1.7z"/> + <path fill="#fff" stroke="#000" stroke-width=".4" d="M204.2 178q.1-1.7 1.8-1.8c1.7-.1 1.9.8 1.9 1.8s-.9 1.7-1.9 1.7a2 2 0 0 1-1.8-1.7m.4-3.7q.1-1.2 1.4-1.3 1.4.1 1.5 1.3-.1 1.3-1.5 1.4c-1.4.1-1.4-.6-1.4-1.4m.4-3.3q0-.9 1-1c1-.1 1 .5 1 1s-.4 1-1 1a1 1 0 0 1-1-1m.2-2.8q.1-.8.8-.8t.9.8q0 .7-.9.8a1 1 0 0 1-.8-.8"/> + <path fill="#c8b100" stroke="#000" stroke-width=".4" d="m206.2 191.8 1.2.2a4.6 4.6 0 0 0 4.5 6 5 5 0 0 0 4.4-3c.1 0 .5-1.7.7-1.7s.1 1.8.2 1.7c.3 2.3 2.4 3.8 4.7 3.8a4.6 4.6 0 0 0 4.7-5l1.5-1.5.7 2a4 4 0 0 0-.4 1.9 4.4 4.4 0 0 0 4.5 4.2q2.5 0 3.8-1.9l.9-1.2v1.5c0 1.5.6 2.8 2 3 0 0 1.7.1 4-1.6 2.1-1.7 3.3-3.1 3.3-3.1l.2 1.7s-1.8 2.8-3.8 4c-1 .6-2.7 1.3-4 1q-2.1-.5-3-2.6a7 7 0 0 1-3.3 1 7 7 0 0 1-6.1-3.7 7 7 0 0 1-10.4-.3 7 7 0 0 1-4.6 1.8 7 7 0 0 1-5.7-3 7 7 0 0 1-5.7 3 7 7 0 0 1-4.7-1.8 7 7 0 0 1-10.4.3 7 7 0 0 1-6 3.7 7 7 0 0 1-3.4-1q-.8 2.1-3 2.7c-1.2.2-2.9-.5-4-1.1-2-1.2-3.8-4-3.8-4l.2-1.7s1.2 1.4 3.4 3.1 3.9 1.6 3.9 1.6c1.4-.2 2-1.5 2-3v-1.5l1 1.2a5 5 0 0 0 3.7 2c2.5 0 4.5-2 4.5-4.3a4 4 0 0 0-.4-2l.8-1.9 1.5 1.5v.6c0 2.4 2 4.4 4.6 4.4 2.4 0 4.4-1.5 4.7-3.8 0 0 0-1.6.2-1.7s.6 1.7.7 1.6a5 5 0 0 0 4.5 3.1 4.6 4.6 0 0 0 4.5-6l1.2-.2"/> + <path fill="#fff" stroke="#000" stroke-width=".4" d="M238.6 197.7q.4-1.4-.6-1.8-.9-.2-1.5 1.1-.4 1.4.6 1.8.9.2 1.5-1.1m-20.5-4c0-.8-.3-1.6-1-1.6q-.8 0-1.2 1.4 0 1.3.9 1.6 1-.1 1.3-1.4m-23.9 0c0-.8.4-1.6 1-1.6q1 0 1.2 1.4 0 1.3-.9 1.6-1-.1-1.2-1.4m-20.6 4q-.3-1.4.6-1.8 1-.2 1.5 1.1t-.5 1.8q-1 .2-1.6-1.1"/> + <path fill="#c8b100" stroke="#000" stroke-width=".4" d="M182.7 184a5 5 0 0 1 2.2 2.9s0-.3.6-.6 1-.3 1-.3l-.1 1.3-.3 2.2-.7 1.6a2 2 0 0 0-1.5-.4 2 2 0 0 0-1.2.9s-.7-.6-1.2-1.3l-1.1-2-.7-1.1s.5-.2 1.1 0q.9.1.8.2a5 5 0 0 1 1-3.4m.4 9.8-.6-1q0-.7.3-1.2s-.9-.5-1.8-.7c-.7-.2-2-.2-2.3-.2h-1l.2.5.5.7a5 5 0 0 0-3 2 5 5 0 0 0 3.5 1l-.2.8v.6l1-.4c.3-.1 1.5-.5 2-1 .8-.4 1.5-1.1 1.5-1.1m2.7-.5.2-1.1-.6-1 1.4-1.3 2-.9 1.1-.4v.6l-.2.8a5 5 0 0 1 3.4 1 5 5 0 0 1-2.9 2l.7 1.2h-1c-.4 0-1.6 0-2.3-.2l-1.8-.7"/> + <path fill="#ad1519" stroke="#000" stroke-width=".4" d="M182.2 192.4c0-1 1-2 2-2s2.2 1 2.2 2c0 1.1-1 2-2.1 2a2 2 0 0 1-2.1-2"/> + <path fill="#c8b100" stroke="#000" stroke-width=".4" d="M206.1 180.8a6 6 0 0 1 1.9 3.7s.2-.3.9-.5q1.1-.3 1.2-.2l-.5 1.4-.8 2.4-1 1.7a2 2 0 0 0-1.7-.7q-1 0-1.6.7-.2-.1-1-1.7l-.8-2.4-.5-1.4 1.2.2q1 .4.9.5.2-2.2 1.8-3.7"/> + <path fill="#c8b100" stroke="#000" stroke-width=".4" d="M204.6 191.8a2 2 0 0 1-.5-1.2q0-.8.4-1.3s-.8-.7-1.8-1c-.7-.4-2-.7-2.5-.7l-1.2-.2.2.6.4.9a6 6 0 0 0-3.7 1.7q1.6 1.5 3.7 1.6l-.4 1-.2.6 1.2-.2c.4-.1 1.8-.4 2.5-.7 1-.4 1.9-1 1.9-1m3 0a2 2 0 0 0 .1-2.6s.9-.7 1.8-1a8 8 0 0 1 2.5-.7l1.2-.3-.1.7-.4.9q2.2.2 3.6 1.7a6 6 0 0 1-3.6 1.6l.5 1.6-1.2-.2-2.5-.7c-1-.4-1.8-1-1.8-1m22-8a5 5 0 0 0-2.2 3l-.7-.6-1-.3.2 1.3c0 .3 0 1.3.3 2.2q.4 1.6.6 1.6a2 2 0 0 1 1.5-.4q.9.2 1.3.9l1.1-1.3q1-1.4 1.1-2l.7-1.1s-.4-.2-1 0l-1 .2a5 5 0 0 0-1-3.4m-.3 9.8q.5-.4.6-1l-.2-1.2s.8-.5 1.7-.7c.7-.2 2-.2 2.3-.2h1.1l-.3.5-.4.7a5 5 0 0 1 2.9 2 5 5 0 0 1-3.5 1l.2.8v.6l-1-.4c-.3-.1-1.4-.5-2-1-.8-.4-1.4-1.1-1.4-1.1m-2.8-.5-.2-1.1q0-.7.6-1s-.6-.8-1.4-1.3c-.6-.4-1.7-.8-2-.9l-1-.4v.6l.2.8a5 5 0 0 0-3.5 1q1 1.4 3 2l-.5.7-.3.5h1c.4 0 1.7 0 2.3-.2l1.8-.7"/> + <path fill="#ad1519" stroke="#000" stroke-width=".4" d="M226 192.4c0-1 1-2 2-2s2.1 1 2.1 2a2 2 0 0 1-2 2 2 2 0 0 1-2.1-2m23.2 4.4c-.4-.5-1.4-.4-2.2.2q-1.2 1.2-.5 2.2 1 .8 2.3-.3c.7-.6 1-1.6.5-2"/> + <path fill="#c8b100" stroke="#000" stroke-width=".4" d="m246.3 198 .7-1c.7-.6 1.8-.7 2.3-.2l.1.2s1-2 2.3-2.6 3.4-.5 3.4-.5a3 3 0 0 0-2.9-2.8 3 3 0 0 0-2.4 1l-.2-1s-1.3.3-1.9 1.8 0 3.6 0 3.6-.3-.9-.7-1.5a8 8 0 0 0-2.4-1.6l-1.3-.7-.1.5v.8a8 8 0 0 0-3.7.5 5 5 0 0 0 2.5 2.2l-.8.7-.4.5 1.3.2 2.5.2 1.7-.2m-80.3 0q0-.6-.7-1c-.7-.7-1.7-.8-2.2-.3l-.2.3s-1-2-2.3-2.7-3.3-.5-3.3-.5a3 3 0 0 1 2.8-2.8q1.6 0 2.4 1l.2-1s1.3.3 2 1.8c.5 1.5-.1 3.6-.1 3.6s.3-.9.8-1.5a8 8 0 0 1 2.4-1.6l1.3-.7v1.3a8 8 0 0 1 3.7.5 5 5 0 0 1-2.5 2.2l.8.7.4.5-1.2.2-2.6.2-1.7-.2"/> + <path fill="#ad1519" stroke="#000" stroke-width=".4" d="M163 196.8q1-.7 2.4.3 1.2 1 .4 2c-.5.6-1.5.5-2.2-.2s-1-1.6-.5-2m41-6.3q.2-1.8 2-2c1.8-.2 2.1.9 2.1 2 0 1-1 2-2 2a2 2 0 0 1-2.1-2"/> + <path fill="#005bbf" stroke="#000" stroke-width=".3" d="M201.8 160.6c0-2.2 1.9-4 4.3-4s4.2 1.8 4.2 4-1.9 4-4.3 4a4 4 0 0 1-4.2-4"/> + <path fill="#c8b100" stroke="#000" stroke-width=".3" d="M205 149.3v2.2h-2.4v2.2h2.3v6.3H202l-.2.6q0 .9.3 1.6h7.9q.3-.7.3-1.6l-.2-.6h-2.8v-6.3h2.3v-2.2h-2.3v-2.2z"/> + <path fill="#ccc" d="M206.5 330.6a82 82 0 0 1-35.5-8.2 23 23 0 0 1-12.8-20.4v-32h96.4v32a23 23 0 0 1-12.8 20.4 81 81 0 0 1-35.3 8.2"/> + <path fill="none" stroke="#000" stroke-width=".5" d="M206.5 330.6a82 82 0 0 1-35.5-8.2 23 23 0 0 1-12.8-20.4v-32h96.4v32a23 23 0 0 1-12.8 20.4 81 81 0 0 1-35.3 8.2z"/> + <path fill="#ccc" d="M206.3 270h48.3v-53.5h-48.3z"/> + <path fill="none" stroke="#000" stroke-width=".5" d="M206.3 270h48.3v-53.5h-48.3z"/> + <path fill="#ad1519" d="M206.3 302c0 12.6-10.7 22.9-24 22.9s-24.2-10.3-24.2-23v-32h48.2v32"/> + <path fill="#c8b100" stroke="#000" stroke-width=".5" d="M168.6 320.9c1.5.8 3.6 2 5.8 2.6l-.1-54.7h-5.7z"/> + <path fill="#c8b100" stroke="#000" stroke-linejoin="round" stroke-width=".5" d="M158 301.6a24 24 0 0 0 5.5 15v-47.5h-5.4z"/> + <path fill="#c7b500" stroke="#000" stroke-width=".5" d="M179.4 324.7a27 27 0 0 0 5.6 0v-55.9h-5.6v56z"/> + <path fill="#c8b100" stroke="#000" stroke-width=".5" d="M190 323.5a19 19 0 0 0 5.8-2.5v-52.2H190z"/> + <path fill="#ad1519" d="M158.1 270h48.2v-53.5H158z"/> + <path fill="none" stroke="#000" stroke-width=".5" d="M158.1 270h48.2v-53.5H158z"/> + <path fill="#c8b100" stroke="#000" stroke-width=".5" d="M201 316c2.4-2 4.6-6.8 5.4-12.2l.1-35H201l.1 47.3z"/> + <path fill="none" stroke="#000" stroke-width=".5" d="M206.3 302c0 12.6-10.7 22.9-24 22.9s-24.2-10.3-24.2-23v-32h48.2v32"/> + <path fill="#ad1519" d="M254.6 270v32c0 12.6-10.8 22.9-24.1 22.9s-24.2-10.3-24.2-23v-32h48.3"/> + <path fill="none" stroke="#000" stroke-width=".5" d="M254.6 270v32c0 12.6-10.8 22.9-24.1 22.9s-24.2-10.3-24.2-23v-32h48.3"/> + <path fill="#c8b100" d="m215.1 294.1.1.5q-.1 1-1.1 1a1 1 0 0 1-1.1-1v-.5h-1.5a2.5 2.5 0 0 0 1.8 2.9v3.9h1.6V297a3 3 0 0 0 1.7-1.6h4.4v-1.2zm21.8 0v1.2h-4l-.3.6 4.6 5.2-1.2 1-4.6-5.3-.2.1v8.7h-1.6V297h-.2l-4.8 5.2-1.2-1 4.7-5.3-.2-.4h-4V294h13zm2.6 0v1.2h4.4q.5 1.2 1.7 1.6v3.9h1.6V297a3 3 0 0 0 1.8-2.4v-.5h-1.6l.1.5q-.1 1-1 1-1.1 0-1.2-1l.1-.5zm-6.7 22.1a16 16 0 0 0 3.7-1l.8 1.4a18 18 0 0 1-4.3 1.2 3 3 0 0 1-2.6 2 3 3 0 0 1-2.5-2 18 18 0 0 1-4.6-1.2l.8-1.4q2 .8 4 1a3 3 0 0 1 1.5-1.3v-6.7h1.6v6.7q1.1.3 1.6 1.4zm-11-2.2-.8 1.4a17 17 0 0 1-3.6-3.1q-1.4.3-2.5-.5a2.4 2.4 0 0 1-.3-3.5l.1-.1a15 15 0 0 1-1.3-4.8h1.7a13 13 0 0 0 1 4q.8 0 1.4.2l4.1-4.5 1.3 1-4.1 4.5q.8 1.5-.1 2.8a15 15 0 0 0 3.1 2.6m-6-4.8q.6-.7 1.5 0c.9.7.5 1 .1 1.4a1 1 0 0 1-1.6.1 1 1 0 0 1 0-1.5m-2.2-4.5-1.6-.3-.3-4.3 1.7-.6v2.5q0 1.4.2 2.7m1.4-5.3 1.7.4v2.2c0-.8.3 2.1.3 2.1l-1.7.6-.3-2.7zm5.6 13.7a16 16 0 0 0 4.8 2.6l.4-1.6a14 14 0 0 1-4-2zm-.8 1.4a17 17 0 0 0 4.8 2.6l-1.2 1.1a19 19 0 0 1-4-2zm2.2-9.4 1.6.7 3-3.3-1-1.4zm-1.3-1-1-1.4 3-3.3 1.6.7zm18.1 9.9.8 1.4a17 17 0 0 0 3.6-3.1q1.4.3 2.5-.5a2.4 2.4 0 0 0 .3-3.5l-.1-.1a15 15 0 0 0 1.3-4.8h-1.7a13 13 0 0 1-1 4l-1.4.2-4.1-4.5-1.3 1 4.1 4.5a2.4 2.4 0 0 0 .1 2.8 15 15 0 0 1-3.1 2.6m6-4.8a1 1 0 0 0-1.5 0 1 1 0 0 0-.1 1.4 1 1 0 0 0 1.6.1 1 1 0 0 0 0-1.5m2.2-4.5 1.6-.3.3-4.3-1.7-.6v2.5q0 1.5-.2 2.8zm-1.4-5.3-1.7.4v2.2c0-.8-.3 2.1-.3 2.1l1.7.6.3-2.7zm-5.6 13.7a16 16 0 0 1-4.8 2.6l-.4-1.6a14 14 0 0 0 4-2zm.8 1.4a17 17 0 0 1-4.8 2.6l1.2 1.1a19 19 0 0 0 4-2zm-2.2-9.4-1.6.7-2.9-3.3 1-1.4zm1.3-1 1-1.4-3-3.3-1.6.7zm-20.1-8.7.5 1.6h4.5l.5-1.6zm21.1 0-.5 1.6h-4.5l-.5-1.6zm-11.6 21.9q.1-1 1.1-1a1 1 0 0 1 1.1 1q-.1 1-1 1a1 1 0 0 1-1.2-1m1.9-7.8 1.7-.4v-4.3l-1.7-.5zm-1.6 0-1.7-.4v-4.3l1.7-.5z"/> + <path fill="#c8b100" d="M211.5 294.2q.4-1.5 1.8-2V287h1.6v5.3q1.3.5 1.7 1.6h4.4v.3h-6a1 1 0 0 0-1-.6q-.6 0-1 .6zm12.2 0v-.3h4.1l.2-.3-5-5.7 1.2-1 5 5.6.2-.1V285h1.6v7.3h.3l4.9-5.5 1.2 1-4.9 5.5.3.6h4v.3zm21.6 0a1 1 0 0 1 1-.6q.7 0 1 .6h1.6q-.4-1.5-1.8-2V287h-1.6v5.3q-1.2.4-1.7 1.6h-4.4v.3zm-30.2-15 6 6.8 1.3-1-6.1-6.7.3-.6h4.4V276h-4.4a3 3 0 0 0-2.5-1.7 2.6 2.6 0 0 0-2.7 2.5 3 3 0 0 0 1.8 2.4v5.2h1.6v-5.2zm32 0v5.3h-1.7v-5.2l-.4-.2-6 6.8-1.3-1 6.2-6.9-.1-.3h-4.5V276h4.5a3 3 0 0 1 2.4-1.7 2.6 2.6 0 0 1 2.7 2.5 2.5 2.5 0 0 1-1.9 2.4zm-16.1 0v3.3h-1.7v-3.2a3 3 0 0 1-1.7-1.6h-4V276h4a3 3 0 0 1 2.5-1.7q2 .1 2.5 1.7h4v1.6h-4a3 3 0 0 1-1.6 1.6m-17.8 4-1.7.4v4.3l1.7.5zm1.6 0 1.7.4v4.3l-1.7.5zm30.6 0-1.7.4v4.3l1.7.5zm1.6 0 1.7.4v4.3l-1.7.5zm-25.5.8 1.6-.7 2.9 3.3-1 1.4zm-1.3 1-1 1.4 3 3.3 1.6-.7zm18.5-1.1-1.6-.7-3 3.3 1 1.4zm1.2 1 1 1.4-3 3.3-1.5-.7zm-20.3 9 .5-1.6h4.5l.5 1.6zm-6.7-17q0-1 1.2-1a1 1 0 0 1 1 1q0 1-1 1a1 1 0 0 1-1.2-1m12.1.8-.5 1.6H220l-.5-1.6zm0-1.6-.5-1.6H220l-.5 1.6zm15.7 17.8-.5-1.6h-4.5l-.5 1.6zm4.4-17q.1-1 1.1-1a1 1 0 0 1 1.1 1q-.1 1-1 1a1 1 0 0 1-1.2-1m-16.1 0q.1-1 1.1-1a1 1 0 0 1 1.1 1q-.1 1-1.1 1a1 1 0 0 1-1.1-1m6.2.8.5 1.6h4.6l.5-1.6zm0-1.6.5-1.6h4.6l.5 1.6zm-5.9 5-1.7.5v4.3l1.7.5V281m1.7 0 1.6.5v4.3l-1.6.5z"/> + <path fill="none" stroke="#c8b100" stroke-width=".3" d="M232.7 316.3a16 16 0 0 0 3.7-1.1l.8 1.4a18 18 0 0 1-4.3 1.2 3 3 0 0 1-2.6 2 3 3 0 0 1-2.5-2 18 18 0 0 1-4.6-1.2l.8-1.4q2 .8 4 1a3 3 0 0 1 1.5-1.3v-6.7h1.6v6.7q1.1.3 1.6 1.4zm-4.7-20.4-.2-.5h-4V294h4l.2-.4-5-5.6 1.2-1 5 5.5h.2V285h1.7v7.3h.2l4.9-5.5 1.2 1-4.9 5.5.3.6h4v1.5h-4q0 .3-.3.5l4.7 5.3-1.3 1-4.6-5.3-.2.1v8.7h-1.6V297l-.2-.1-4.8 5.3-1.2-1 4.7-5.3m-12.8-16.7 6 6.8 1.3-1-6.1-6.7.3-.6h4.4V276h-4.4a3 3 0 0 0-2.5-1.7 2.6 2.6 0 0 0-2.6 2.5 3 3 0 0 0 1.7 2.4v5.2h1.6v-5.2zm6.5 34.8-.8 1.4a17 17 0 0 1-3.6-3.1q-1.4.3-2.5-.5a2.4 2.4 0 0 1-.3-3.5l.1-.1a15 15 0 0 1-1.2-4.8h1.6a13 13 0 0 0 1 4q.8 0 1.4.2l4.1-4.5 1.3 1-4.1 4.5q.8 1.5-.1 2.8a15 15 0 0 0 3.1 2.6zm-8.4-13.1V297a3 3 0 0 1-1.8-2.4q.2-1.6 1.8-2.4V287h1.6v5.3q1.3.4 1.7 1.6h4.4v1.5h-4.4a3 3 0 0 1-1.6 1.6v3.9h-1.7m2.3 8.3q.7-.7 1.6 0c.9.7.5 1 .1 1.4a1 1 0 0 1-1.6.1 1 1 0 0 1 0-1.5zm-2-4.5-1.7-.3-.3-4.3 1.7-.6v2.5q0 1.4.3 2.7zm1.4-5.3 1.6.4v2.2c0-.8.3 2.1.3 2.1l-1.7.6-.3-2.7v-2.6zm5.5 13.7a16 16 0 0 0 4.8 2.6l.4-1.6a14 14 0 0 1-4-2l-1.2 1m-.8 1.4a17 17 0 0 0 4.8 2.6l-1.2 1.1a19 19 0 0 1-4-2l.4-1.7"/> + <path fill="none" stroke="#c8b100" stroke-width=".3" d="m221.9 305.1 1.6.7 3-3.3-1-1.4-3.6 4m-1.3-1-1-1.4 3-3.3 1.6.7-3.6 4m-7.6-9.5q.1-1 1-1 1.1.1 1.2 1-.1 1-1.1 1.1a1 1 0 0 1-1.1-1zm25.7 19.4.8 1.4a17 17 0 0 0 3.6-3.1q1.4.3 2.6-.5a2.4 2.4 0 0 0 .2-3.5l-.1-.1a15 15 0 0 0 1.3-4.8h-1.7a13 13 0 0 1-1 4l-1.4.2-4.1-4.5-1.3 1 4.1 4.5a2.4 2.4 0 0 0 .1 2.8 15 15 0 0 1-3 2.6zm8.4-13.1V297a3 3 0 0 0 1.8-2.4q0-1.6-1.8-2.4V287h-1.6v5.3q-1.2.4-1.7 1.6h-4.4v1.5h4.4q.5 1.2 1.7 1.6v3.9zm-2.3 8.3a1 1 0 0 0-1.6 0 1 1 0 0 0-.1 1.4 1 1 0 0 0 1.6.1 1 1 0 0 0 0-1.5zm2-4.5 1.7-.3.3-4.3-1.7-.6v2.5q0 1.4-.2 2.7zm-1.3-5.3-1.7.4v2.2c0-.8-.3 2.1-.3 2.1l1.7.6.3-2.7v-2.6m1.6-20.1v5.2h-1.6v-5.2l-.4-.2-6 6.8-1.2-1 6-7v-.2h-4.5V276h4.4a3 3 0 0 1 2.5-1.7 2.6 2.6 0 0 1 2.6 2.5 3 3 0 0 1-1.8 2.4zm-16 0v3.2h-1.7v-3.2a3 3 0 0 1-1.7-1.6h-4V276h4q.7-1.6 2.5-1.7c1.8-.1 2.2.7 2.5 1.7h4v1.6h-4a3 3 0 0 1-1.6 1.6zm8.8 33.8a16 16 0 0 1-4.8 2.6l-.4-1.6a14 14 0 0 0 4-2l1.2 1m.8 1.4a17 17 0 0 1-4.8 2.6l1.2 1.1a19 19 0 0 0 4-2l-.4-1.7m-27.4-31.4-1.7.5v4.3l1.7.5v-5.2m1.7 0 1.6.4v4.3l-1.6.5V283m30.5 0-1.7.5v4.3l1.7.5V283"/> + <path fill="none" stroke="#c8b100" stroke-width=".3" d="m247.1 283.1 1.7.5v4.3l-1.7.5V283m-8.6 22-1.6.7-2.9-3.3 1-1.4 3.5 4m1.3-1 1-1.4-3-3.3-1.6.7 3.6 4m-18.2-20 1.6-.7 3 3.3-1 1.4-3.6-4m-1.3 1-1 1.4 3 3.3 1.6-.7-3.6-4m18.5-1.1-1.6-.7-3 3.3 1 1.4 3.6-4m1.2 1 1 1.4-3 3.2-1.5-.6 3.5-4m-20.3 9 .5-1.6h4.5l.5 1.6h-5.5m0 1.5.5 1.6h4.5l.5-1.6h-5.5M213 277q0-1 1.2-1 1 0 1 1c0 1-.4 1-1 1a1 1 0 0 1-1.2-1zm12.1.8-.5 1.6h-4.5l-.5-1.6h5.5m0-1.6-.5-1.6h-4.5l-.5 1.6h5.5m20.1 18.5q.1-.9 1.1-1 1 .1 1.1 1-.1 1-1 1.1a1 1 0 0 1-1.2-1zm-4.4-.7-.5-1.6h-4.5l-.5 1.6h5.5m0 1.5-.5 1.6h-4.5l-.5-1.6h5.5m-11.6 21.9q.1-1 1.1-1t1.1 1c.1 1-.5 1-1 1a1 1 0 0 1-1.2-1zm1.9-7.8 1.7-.4v-4.3l-1.7-.5v5.2m-1.6 0-1.7-.4v-4.3l1.7-.5v5.2m15.7-32.6q.1-1 1.1-1a1 1 0 0 1 1.1 1q-.1 1-1 1a1 1 0 0 1-1.2-1zm-16.1 0q.1-1 1.1-1a1 1 0 0 1 1.1 1q-.1 1-1 1a1 1 0 0 1-1.2-1zm6.2.8.5 1.6h4.6l.5-1.6h-5.5m0-1.6.4-1.6h4.6l.5 1.6h-5.5m-6 5-1.6.5v4.3l1.6.5V281m1.7 0 1.6.5v4.3l-1.6.5V281"/> + <path fill="#058e6e" d="M227.7 294.7a2.6 2.6 0 0 1 2.6-2.5 2.6 2.6 0 0 1 2.6 2.5 2.6 2.6 0 0 1-2.6 2.4c-1.4 0-2.6-1-2.6-2.4"/> + <path fill="#db4446" d="M230.9 229.7v-.6l.1-.3-2.3-.1a6 6 0 0 1-2.3-1.2q-1-1-1.6-1.2c-1.3-.2-2.3.4-2.3.4s1 .4 1.7 1.3 1.5 1.3 1.8 1.4c.6.2 2.6 0 3.1.1z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M230.9 229.7v-.6l.1-.3-2.3-.1a6 6 0 0 1-2.3-1.2q-1-1-1.6-1.2c-1.3-.2-2.3.4-2.3.4s1 .4 1.7 1.3 1.5 1.3 1.8 1.4c.6.2 2.6 0 3.1.1z"/> + <path fill="#ed72aa" stroke="#000" stroke-width=".4" d="M238.1 227.5v1.4c.2.6-.1 1.2 0 1.5q0 .5.3.9l.2.9-.7-.5-.6-.4v1l.6 1.1 1 1.3c.2.5.1 1.4.1 1.4s-.4-.7-.8-.8l-1.2-.7s.7.8.7 1.5l-.3 1.6c-.1.2-.3-.7-.8-1.1l-1-.9s.4 1.2.4 2v2.3l-.9-1-1-.7c0-.2.5.6.6 1.1s.3 2.3 1.8 4.5c1 1.3 2.3 3.6 5.3 2.9s1.9-4.8 1.3-6.7a17 17 0 0 1-1-4.6c0-.8.6-2.9.5-3.3a8 8 0 0 1 .2-3.1l.9-2.3.4-1.3.1-1.3.7 1.3.1 1.5s.1-1 1-1.6 1.8-1.1 2-1.4l.3-.5c-.1 0 0 1.8-.6 2.6l-1.7 2s.7-.3 1.2-.3h.9s-.6.4-1.4 1.6c-.8 1-.5 1.2-1 2.1s-1 1-1.7 1.5c-1 .8-.5 4.2-.4 4.7.2.5 2 4.5 2 5.5s.2 3.2-1.5 4.6c-1.1 1-3 1-3.4 1.2-.4.3-1.2 1.1-1.2 2.8s.6 2 1 2.4c.6.5 1.2.2 1.3.6l.5.7q.3.3.2.8c0 .3-.8 1.1-1.1 1.7l-.8 2.4c0 .2-.1 1 .1 1.3 0 0 .9 1 .3 1.2-.4.2-.8-.2-1-.2l-.9.5q-.3-.1-.4-.8l-.1-.7q-.3 0-.4.5c0 .2 0 .8-.3.8l-.8-.5c-.2 0-.8-.2-.8-.4q.1-.6.7-1 .8-.1.5-.5t-.7 0c-.3.4-.8 0-.7-.2v-.8c0-.2-.4-.5.1-.8s.8.2 1.4.1q.8 0 1-.6.3-.6-.2-1.4l-.9-.8-.3-.9v2.2l-.7-.8c-.3-.3-.6-1.3-.6-1.3v1.3l.2.8c-.1.1-.8-.7-1-.8l-1-1-.4-1.4v-1.5l.4-1h-1.4c-.7 0-1.2-.3-1.5.2q-.4.8.2 2.8.5 1.8.4 2.1l-.7.8h-.9l-1.2-.3h-1.3l-1.1-.3c-.3.1-.8.3-.6.7q.1.8-.5.7l-.9-.2q-.8 0-.8-.4t.4-.7q.3-.5 0-.5h-.6q-.3.4-.8.4-.3-.1-.4-1c-.1-.9-.7-1.2 0-1.1.5 0 1.3.4 1.4 0q.2-.4-.2-.7c-.4-.3-.8-.4-.3-.7l.7-.5c.1-.2.4-.8.7-.6.6.2 0 .7.6 1.3s1 1 2 .8q1.4 0 1.3-.5l-.1-1v-1s-.4.3-.5.6l-.4.8v-2l-.2-.8-.3.9-.1 1s-.7-.5-.5-1.5l.1-2c.2-.3.7-1.5 2-1.6h2.6l2-.3s-2.8-1.4-3.5-1.9a10 10 0 0 1-2-2l-.6-1.6s-.5 0-1 .3l-1.2 1-.7 1 .1-1.2v-.8s-.4 1.2-1 1.7l-1.4 1v-.8l.2-1s-.4.8-1.1 1c-.7 0-1.8 0-1.9.4q.2.8 0 1.4c0 .3-.4.5-.4.5l-.8-.4-.7.2c-.1.1-.3-.4-.2-.7s.7-.6.5-.8l-.8.2c-.3.1-.8.3-.8-.2q.2-.6 0-1 0-.4.2-.6l1.2-.1q0-.4-.8-.6-1-.2-.5-.8.3-.2.5-.6c.1-.2.2-.7.7-.5.5.3.4.8 1 1a4 4 0 0 0 2-.2l1.5-1 1.5-1-1-.8q-.5-.7-1-1l-1.8-.6-1.7-.5.8-.3q.4-.5.8-.6h.3-1.4c-.3-.1-1-.6-1.3-.6l-.8.1s.8-.4 1.4-.5l1-.1s-.9-.3-1.1-.6l-.6-1q-.2-.3-.6-.5l-1 .3q-.6 0-.6-.6l-.1-.5c-.2-.3-.6-.8-.2-1h1.4q-.2-.4-.8-.8c-.4-.2-1-.5-.7-.8l.8-.5c.2-.3.3-1 .7-.7.4.2.8 1.2 1.1 1.1s.3-.8.3-1c0-.4 0-1 .2-.9l1 .5q.8-.1 1 .2 0 .5-.6 1-.4.6-.3 1.4.4 1 1.2 1.4t1.7.9c.5.3 1.7 1.2 2.1 1.3l.8.4s.5-.2 1.1-.2 2.1 0 2.6-.2 1.3-.6 1-1c-.1-.6-1.3-1-1.2-1.4q0-.5 1.2-.4c.8 0 1.8.1 2-1q.5-1.4-.8-1.8c-1-.2-1.8-.2-2-1q-.4-1-.2-1.1.3-.3 1.4-.4 1.3 0 1.9-.2l.6-.9c.3-.2 1.4-.4 1.4-.4s1.4.7 2.7 1.7l2.2 2.1"/> + <path d="m228.1 226.8-.2-.6v-.3s.8 0 .7.3q0 .2-.3.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m228.1 226.8-.2-.6v-.3s.8 0 .7.3q0 .2-.3.3z"/> + <path d="M232 225.4v-.4s.7 0 1 .3c.5.4.9 1 .9 1l-.8-.4h-.5l-.3-.1v-.3z"/> + <path fill="none" stroke="#000" stroke-width=".1" d="M232 225.4v-.4s.7 0 1 .3c.5.4.9 1 .9 1l-.8-.4h-.5l-.3-.1v-.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m237.3 231.3-.4-.7-.3-.4"/> + <path fill="#db4446" d="M217.4 226.6s.5.4.8.4h.8s.2-.5.1-.8c-.2-1.2-1.2-1.4-1.2-1.4s.3.7.1 1z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M217.4 226.6s.5.4.8.4h.8s.2-.5.1-.8c-.2-1.2-1.2-1.4-1.2-1.4s.3.7.1 1z"/> + <path fill="#db4446" d="M215.2 227.6s-.4-.7-1.3-.6-1.4.8-1.4.8h1.2c.3.3.4 1 .4 1l.7-.6z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M215.2 227.6s-.4-.7-1.3-.6-1.4.8-1.4.8h1.2c.3.3.4 1 .4 1l.7-.6z"/> + <path fill="#db4446" d="M214.2 230.6s-.8.1-1.2.6-.3 1.3-.3 1.3.4-.5.9-.5l1 .2-.1-.8z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M214.2 230.6s-.8.1-1.2.6-.3 1.3-.3 1.3.4-.5.9-.5l1 .2-.1-.8z"/> + <path d="m228.2 230.5.3-.5.3.5z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m228.2 230.5.3-.5.3.5h-.7"/> + <path d="m229 230.5.3-.5.4.5z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m229 230.5.3-.5.4.5h-.8"/> + <path d="m228.6 227.3.8.3-.7.4-.1-.6"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m228.6 227.3.8.3-.7.4-.1-.6"/> + <path d="m229.5 227.6.7.2-.5.4z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m229.5 227.6.7.2-.5.4-.2-.6"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M224.2 233.7s-.7.2-1 .6q-.4.9-.3 1c.1.1.6-.5 1.5-.3l1.2.3 1.3-.3s-.7.8-.7 1.3l.2 1.1c0 .7-.6 1.6-.6 1.6l1-.3 1.7-.8.9-1s-.2 1 0 1.4l.2 1.6.8-.6q.5-.2.9-.7l.3-1s0 .8.4 1.3l.6 1.6s.3-.8.6-1.1l.7-1-.1-.9.4.8m-11 .6s.5-.8 1-1l1.1-.8.9-.4m1 5 1.3-.8 1-1"/> + <path fill="#db4446" d="M216.6 240.4s-.4-.5-1.1-.3c-.7 0-1.2.9-1.2.9s.6-.2 1-.1.6.4.6.4l.4-.4z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M216.6 240.4s-.4-.5-1.1-.3c-.7 0-1.2.9-1.2.9s.6-.2 1-.1.6.4.6.4l.4-.4z"/> + <path fill="#db4446" d="M215.8 243.2s-.6 0-1.1.3c-.5.4-.5 1.2-.5 1.2s.4-.4.8-.3l.9.2v-.6c.2-.4-.1-.8-.1-.8"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M215.8 243.2s-.6 0-1.1.3c-.5.4-.5 1.2-.5 1.2s.4-.4.8-.3l.9.2v-.6c.2-.4-.1-.8-.1-.8z"/> + <path fill="#db4446" d="M217.2 245.8s0 .8.3 1.3c.4.5 1.1.5 1.1.5l-.3-.7.3-.8q.1-.1-.7-.3z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M217.2 245.8s0 .8.3 1.3c.4.5 1.1.5 1.1.5l-.3-.7.3-.8q.1-.1-.7-.3zm16 1.3s2 1.2 1.9 2.2c0 1-1 2.3-1 2.3"/> + <path fill="#db4446" d="M224.2 252.6s-.4-.6-1.1-.6-1.4.7-1.4.7.8-.1 1 .2l.5.6.5-.3z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M224.2 252.6s-.4-.6-1.1-.6-1.4.7-1.4.7.8-.1 1 .2l.5.6.5-.3z"/> + <path fill="#db4446" d="M222.2 255.3s-1-.1-1.4.3-.4 1.3-.4 1.3.6-.6 1-.5q.9.1 1 .3v-.7l-.3-.7"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M222.2 255.3s-1-.1-1.4.3-.4 1.3-.4 1.3.6-.6 1-.5q.9.1 1 .3v-.7l-.3-.7z"/> + <path fill="#db4446" d="M224 258.1s-.3.7 0 1.1 1 .8 1 .8-.3-.4-.2-.8c.1-.3.7-.8.7-.8l-1.4-.2"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M224 258.1s-.3.7 0 1.1 1 .8 1 .8-.3-.4-.2-.8c.1-.3.7-.8.7-.8l-1.4-.2z"/> + <path fill="#db4446" d="M236 259.3s-.8-.2-1.2 0c-.5.3-.8 1.4-.8 1.4s.7-.6 1.2-.5q.9.1 1 .3v-.8z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M236 259.3s-.8-.2-1.2 0c-.5.3-.8 1.4-.8 1.4s.7-.6 1.2-.5q.9.1 1 .3v-.8z"/> + <path fill="#db4446" d="M236.4 262.2s-.6.6-.4 1.1l.6 1s0-.7.2-1l1-.3-.7-.5z"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M236.4 262.2s-.6.6-.4 1.1l.6 1s0-.7.2-1l1-.3-.7-.5z"/> + <path fill="#db4446" d="M239.4 263s-.3.8.2 1.3l1 .5q-.2-.3-.2-1.1.3-.7.5-.7l-.8-.2-.7.3"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M239.4 263s-.3.8.2 1.3l1 .5q-.2-.3-.2-1.1.3-.7.5-.7l-.8-.2-.7.3z"/> + <path fill="#ffd691" stroke="#000" stroke-width=".5" d="M208.8 316.4q3 1 3 3.8c0 2.3-2.2 4-5 4-3 0-5.3-1.7-5.3-4 0-1.7 1-3.6 3-3.8l-.2-.4-.7-.7h1.2l.8.5.5-.7.6-.5.6.6.3.5.7-.4.8-.3s0 .4-.2.7l-.1.7"/> + <path fill="#058e6e" stroke="#000" stroke-width=".5" d="M206.3 326.7s-3.8-2.6-5.5-3c-2-.4-4.5 0-5.5 0 0 0 1.2.8 1.8 1.4.5.5 2.3 1.5 3.3 1.8 3 .8 6-.2 6-.2m1 .2s2.4-2.5 5-2.9c3-.4 5 .3 6.2.6l-1.5.8c-.5.3-2 1.5-4 1.6-2 0-4.4-.3-4.8-.2l-.9.1"/> + <path fill="#ad1519" stroke="#000" stroke-width=".5" d="M206.7 323.8a5 5 0 0 1 0-7.1 5 5 0 0 1 1.5 3.5 5 5 0 0 1-1.5 3.6"/> + <path fill="#058e6e" stroke="#000" stroke-width=".5" d="M205.7 329s.6-1.5.6-2.7l-.1-2.1h.8s.3 1.1.3 2l-.1 2.4-.7.1-.8.3"/> + <path fill="#fff" d="M254 190.7q.1-.9 1-1 1 .1 1.1 1-.1 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M254 190.7q.1-.9 1-1 1 .1 1.1 1-.1 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M255.4 188.2q.1-1 1.1-1t1 1c0 1-.4 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M255.4 188.2q.1-1 1.1-1t1 1c0 1-.4 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M256.4 185.2q.1-.9 1-1 1 .1 1.1 1c.1.9-.5 1-1 1a1 1 0 0 1-1.1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M256.4 185.2q.1-.9 1-1 1 .1 1.1 1c.1.9-.5 1-1 1a1 1 0 0 1-1.1-1z"/> + <path fill="#fff" d="M256.5 182q.1-.9 1-1 1 .1 1.1 1-.1 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M256.5 182q.1-.9 1-1 1 .1 1.1 1-.1 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M255.7 179q.1-1 1-1 1.1 0 1.2 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M255.7 179q.1-1 1-1 1.1 0 1.2 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M254.1 176.1q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M254.1 176.1q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M252 173.8q0-1 1-1c1 0 1 .4 1 1s-.4 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M252 173.8q0-1 1-1c1 0 1 .4 1 1s-.4 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M249.4 171.8q.1-.9 1.1-1a1 1 0 0 1 0 2q-1 0-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M249.4 171.8q.1-.9 1.1-1a1 1 0 0 1 0 2q-1 0-1-1z"/> + <path fill="#fff" d="M246.5 170.3q0-1 1-1c1 0 1 .4 1 1s-.4 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M246.5 170.3q0-1 1-1c1 0 1 .4 1 1s-.4 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M243.3 169.1q.1-.9 1.1-1a1 1 0 0 1 0 2 1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M243.3 169.1q.1-.9 1.1-1a1 1 0 0 1 0 2 1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M239.9 168.5q0-.9 1-1c1-.1 1 .5 1 1s-.4 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M239.9 168.5q0-.9 1-1c1-.1 1 .5 1 1s-.4 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M236.6 168.3q0-.9 1-1c1-.1 1 .5 1 1s-.4 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M236.6 168.3q0-.9 1-1c1-.1 1 .5 1 1s-.4 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M233.3 168.5q.1-1 1-1t1.1 1c.1 1-.4 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M233.3 168.5q.1-1 1-1t1.1 1c.1 1-.4 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M230.1 168.5q.1-1 1-1t1.1 1c.1 1-.5 1-1 1a1 1 0 0 1-1.1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M230.1 168.5q.1-1 1-1t1.1 1c.1 1-.5 1-1 1a1 1 0 0 1-1.1-1z"/> + <path fill="#fff" stroke="#000" stroke-width=".4" d="M231.7 171.2q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1m.6 3.1q0-1 1-1c1 0 1 .4 1 1q0 .9-1 1a1 1 0 0 1-1-1m0 3q.2-.9 1.1-1a1 1 0 0 1 0 2 1 1 0 0 1-1-1m-1 2.8q.1-.9 1-1 1 .1 1.1 1 0 1-1 1a1 1 0 0 1-1-1m-1.9 2.6q.1-.9 1-1 1.1.1 1.2 1 0 1.2-1.1 1-1 0-1-1"/> + <path fill="#fff" d="M227.6 166.5q.1-.9 1.1-1a1 1 0 0 1 0 2 1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M227.6 166.5q.1-.9 1.1-1a1 1 0 0 1 0 2 1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M224.8 165q0-1 1-1c1 0 1 .4 1 1s-.4 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M224.8 165q0-1 1-1c1 0 1 .4 1 1s-.4 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M221.6 164q.1-1 1-1t1.1 1q-.1.9-1 1-1-.1-1.1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M221.6 164q.1-1 1-1t1.1 1q-.1.9-1 1-1-.1-1.1-1z"/> + <path fill="#fff" d="M218.3 163.4q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1.1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M218.3 163.4q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1.1-1z"/> + <path fill="#fff" d="M215 163.5q.1-1 1.1-1t1 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M215 163.5q.1-1 1.1-1t1 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M211.7 164q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M211.7 164q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M208.6 165.1q.1-.9 1-1 1 .1 1.1 1c.1.9-.5 1-1 1a1 1 0 0 1-1.1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M208.6 165.1q.1-.9 1-1 1 .1 1.1 1c.1.9-.5 1-1 1a1 1 0 0 1-1.1-1z"/> + <path fill="#fff" d="M156 190.7q0-.9 1-1c1-.1 1 .5 1 1q0 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M156 190.7q0-.9 1-1c1-.1 1 .5 1 1q0 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M154.5 188.2q.1-1 1-1t1 1c0 1-.4 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M154.5 188.2q.1-1 1-1t1 1c0 1-.4 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M153.5 185.2q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M153.5 185.2q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M153.4 182q.1-.9 1-1 1 .1 1.1 1-.1 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M153.4 182q.1-.9 1-1 1 .1 1.1 1-.1 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M154.2 179q.1-1 1-1t1 1c0 1-.4 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M154.2 179q.1-1 1-1t1 1c0 1-.4 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M155.8 176.1q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M155.8 176.1q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M158 173.8q0-1 1-1c1 0 1 .4 1 1s-.4 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M158 173.8q0-1 1-1c1 0 1 .4 1 1s-.4 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M160.5 171.8q0-.9 1-1c1-.1 1 .5 1 1s-.4 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M160.5 171.8q0-.9 1-1c1-.1 1 .5 1 1s-.4 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M163.5 170.3q0-1 1-1c1 0 1 .4 1 1s-.4 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M163.5 170.3q0-1 1-1c1 0 1 .4 1 1s-.4 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M166.6 169.1q.1-.9 1-1a1 1 0 0 1 0 2 1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M166.6 169.1q.1-.9 1-1a1 1 0 0 1 0 2 1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M170 168.5q.1-.9 1.1-1a1 1 0 0 1 0 2q-1 0-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M170 168.5q.1-.9 1.1-1a1 1 0 0 1 0 2q-1 0-1-1z"/> + <path fill="#fff" d="M173.4 168.3q0-.9 1-1c1-.1 1 .5 1 1s-.4 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M173.4 168.3q0-.9 1-1c1-.1 1 .5 1 1s-.4 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M176.6 168.5q.1-1 1-1t1.1 1c.1 1-.5 1-1 1a1 1 0 0 1-1.1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M176.6 168.5q.1-1 1-1t1.1 1c.1 1-.5 1-1 1a1 1 0 0 1-1.1-1z"/> + <path fill="#fff" d="M179.8 168.5q.1-1 1-1 1.1 0 1.2 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M179.8 168.5q.1-1 1-1 1.1 0 1.2 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" stroke="#000" stroke-width=".4" d="M178.2 171.2q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1m-.7 3.1q0-1 1-1c1 0 1 .4 1 1q0 .9-1 1a1 1 0 0 1-1-1m-.2 3q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1m.9 2.8q.1-.9 1-1 1 .1 1.1 1-.1 1-1 1a1 1 0 0 1-1.1-1m1.8 2.6q.1-.9 1-1a1 1 0 0 1 0 2 1 1 0 0 1-1-1"/> + <path fill="#fff" d="M182.3 166.5q.1-.9 1-1a1 1 0 0 1 0 2 1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M182.3 166.5q.1-.9 1-1a1 1 0 0 1 0 2 1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M185.2 165q0-1 1-1c1 0 1 .4 1 1s-.4 1-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M185.2 165q0-1 1-1c1 0 1 .4 1 1s-.4 1-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M188.3 164q.1-1 1-1t1.1 1q0 .9-1 1c-1 .1-1-.5-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M188.3 164q.1-1 1-1t1.1 1q0 .9-1 1c-1 .1-1-.5-1-1z"/> + <path fill="#fff" d="M191.6 163.4q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M191.6 163.4q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M194.9 163.5q0-1 1-1c1 0 1 .4 1 1q0 .9-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M194.9 163.5q0-1 1-1c1 0 1 .4 1 1q0 .9-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M198.2 164q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M198.2 164q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1z"/> + <path fill="#fff" d="M201.3 165.1q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1"/> + <path fill="none" stroke="#000" stroke-width=".4" d="M201.3 165.1q.1-.9 1-1 1 .1 1.1 1 0 1.2-1 1a1 1 0 0 1-1-1z"/> + <path fill="#c8b100" stroke="#000" stroke-width=".4" d="M174.7 228.9h-1v-1h-1.5v3.6h1.6v2.5h-3.4v7h1.8v14.3h-3.5v7.3h27.2v-7.3h-3.5V241h1.8v-7h-3.4v-2.5h1.6V228h-1.6v.9h-.8v-1h-1.6v1h-1.1v-1h-1.6v3.6h1.6v2.5H184v-7.8h1.7v-3.5H184v.9h-1v-1h-1.5v1h-.9v-1H179v3.6h1.7v7.8h-3.3v-2.5h1.6V228h-1.6v.9h-.9v-1h-1.8zm-6 33.7H196m-27.3-1.8H196m-27.3-1.8H196m-27.3-1.7H196m-27.3-2H196m-23.8-1.6h20.2m-20.2-1.8h20.2m-20.2-2h20.2m-20.2-1.7h20.2m-20.2-1.8h20.2m-20.2-1.8h20.2m-20.2-1.7h20.2m-22-1.8h23.8m-23.8-1.8h23.8m-23.8-1.8h23.8m-23.8-1.8h23.8m-20.4-1.7h17m-10.2-1.8h3.4m-3.4-1.8h3.4m-3.4-1.8h3.4m-3.4-1.7h3.4m-5.1-2.2h6.8m-12 7.5h3.6m-5-2.2h6.6m-6.7 32.6v-1.8m0-1.8v-1.7m-1.8 1.7v1.8m3.4 0V259m1.7 3.6v-1.8m0-1.8v-1.7m0-2v-1.6m0-1.8v-2m-1.7 7.4v-2m-3.4 2v-2m7 0v2m1.5-2v-1.6m-5.1-1.8v1.8m3.5-1.8v1.8m3.3-1.8v1.8M179 252v-2m1.7-1.7v1.7m0-5.3v1.8m-1.7-3.6v1.8m1.7-3.5v1.7m-3.3-1.7v1.7m-3.5-1.7v1.7m-1.6-3.5v1.8m3.3-1.8v1.8m3.4-1.8v1.8m1.7-3.6v1.8m-3.3-1.8v1.8m-3.5-1.8v1.8m-1.6-3.6v1.8m6.7-1.8v1.8m-3.4-5.3v1.8m15.3-1.8h-3.5m5-2.2h-6.6m6.7 32.6v-1.8m0-1.8v-1.7m1.8 1.7v1.8m-3.4 0V259m-1.7 3.6v-1.8m0-1.8v-1.7m0-2v-1.6m0-1.8v-2m1.7 7.4v-2m3.4 2v-2m-7 0v2m-1.5-2v-1.6m5.1-1.8v1.8m-3.5-1.8v1.8m-3.3-1.8v1.8m1.7-1.8v-2m-1.7-1.7v1.7m0-5.3v1.8m1.7-3.6v1.8m-1.7-3.5v1.7m3.3-1.7v1.7m3.5-1.7v1.7m1.6-3.5v1.8m-3.3-1.8v1.8m-3.4-1.8v1.8m-1.7-3.6v1.8m3.3-1.8v1.8m3.5-1.8v1.8m1.6-3.6v1.8m-6.7-1.8v1.8m3.4-5.3v1.8m-7 18v-2m0-5.4v-1.8m0 5.4v-1.8m0-5.3v-1.8m0-1.8v-1.7m0-3.6v-1.8m0-1.7v-1.8m-8.3 4.6h3.5m3.3-5.3h3.4m3.3 5.3h3.5"/> + <path fill="#c8b100" stroke="#000" stroke-width=".4" d="M186.8 262.6v-4.7c0-.8-.4-3.5-4.6-3.5-4 0-4.4 2.7-4.4 3.5v4.7z"/> + <path fill="#c8b100" stroke="#000" stroke-width=".4" d="m179.3 258.2-2.2-.3c0-.9.2-2.2.9-2.6l2 1.5c-.3.2-.7 1-.7 1.4zm6 0 2.2-.3c0-.9-.2-2.2-.9-2.6l-2 1.5c.3.2.7 1 .7 1.4zm-2.2-2.3 1-2-2-.4-1.7.4 1.1 2zm-4.2-5.5v-4.9c0-1.3-1-2.4-2.5-2.4s-2.4 1-2.4 2.4v4.9zm6.8 0v-4.9c0-1.3 1-2.4 2.5-2.4s2.4 1 2.4 2.4v4.9zm-1.7-12 .4-4.4h-4.2l.2 4.4zm3.3 0-.4-4.4h4.4l-.5 4.4zm-10 0 .2-4.4h-4.2l.5 4.4z"/> + <path fill="#0039f0" d="M185.3 262.6v-4c0-.7-.5-2.7-3.1-2.7-2.4 0-2.9 2-2.9 2.7v4zm-6.9-12.7v-4.2c0-1-.6-2.2-2-2.2s-2 1.1-2 2.2v4.3h4zm7.8 0v-4.2c0-1 .7-2.2 2-2.2s2 1.1 2 2.2v4.3h-4z"/> + <path fill="#ad1519" d="M190.8 269.8c0-9.7 7-17.6 15.6-17.6s15.6 7.9 15.6 17.6-7 17.5-15.6 17.5-15.6-7.8-15.6-17.5"/> + <path fill="none" stroke="#000" stroke-width=".6" d="M190.8 269.8c0-9.7 7-17.6 15.6-17.6s15.6 7.9 15.6 17.6-7 17.5-15.6 17.5-15.6-7.8-15.6-17.5z"/> + <path fill="#005bbf" d="M195.4 269.7c0-7 5-12.8 11-12.8s11 5.7 11 12.8-5 13-11 13-11-5.8-11-13"/> + <path fill="none" stroke="#000" stroke-width=".6" d="M195.4 269.7c0-7 5-12.8 11-12.8s11 5.7 11 12.8-5 13-11 13-11-5.8-11-13z"/> + <path fill="#c8b100" d="M201.2 260.9s-1.3 1.4-1.3 2.7a6 6 0 0 0 .6 2.4q-.4-.8-1.4-.8-1.3.1-1.4 1.3l.2.8.5.9q.2-.5 1-.5 1.1 0 1 1v.2h-1.2v1h1l-.8 1.5 1-.4.8.9.8-.9 1 .4-.7-1.5h1v-1h-1.1v-.3a1 1 0 0 1 1-1q.6 0 1 .6l.4-1 .2-.7a1.4 1.4 0 0 0-1.4-1.3q-1 0-1.4.9s.6-1.2.6-2.5-1.4-2.7-1.4-2.7"/> + <path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".3" d="M201.2 260.9s-1.3 1.4-1.3 2.7a6 6 0 0 0 .6 2.4q-.4-.8-1.4-.8-1.3.1-1.4 1.3l.2.8.5.9q.2-.5 1-.5 1.1 0 1 1v.2h-1.2v1h1l-.8 1.5 1-.4.8.9.8-.9 1 .4-.7-1.5h1v-1h-1.1v-.3a1 1 0 0 1 1-1q.6 0 1 .6l.4-1 .2-.7a1.4 1.4 0 0 0-1.4-1.3q-1 0-1.4.9s.6-1.2.6-2.5-1.4-2.7-1.4-2.7z"/> + <path fill="#c8b100" d="M199.2 269.9h4.1v-1h-4.1z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M199.2 269.9h4.1v-1h-4.1z"/> + <path fill="#c8b100" d="M211.4 260.9s-1.3 1.4-1.3 2.7.6 2.4.6 2.4q-.3-.8-1.4-.8-1.3.1-1.4 1.3l.2.8.5.9q.3-.5 1-.5a1 1 0 0 1 1 1v.2h-1.2v1h1l-.8 1.5 1-.4.8.9.8-.9 1 .4-.7-1.5h1v-1h-1.1v-.3a1 1 0 0 1 1-1q.7 0 1 .6l.4-1 .2-.7a1.4 1.4 0 0 0-1.4-1.3q-1 0-1.4.9s.6-1.2.6-2.5-1.4-2.7-1.4-2.7"/> + <path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".3" d="M211.4 260.9s-1.3 1.4-1.3 2.7.6 2.4.6 2.4q-.3-.8-1.4-.8-1.3.1-1.4 1.3l.2.8.5.9q.3-.5 1-.5a1 1 0 0 1 1 1v.2h-1.2v1h1l-.8 1.5 1-.4.8.9.8-.9 1 .4-.7-1.5h1v-1h-1.1v-.3a1 1 0 0 1 1-1q.7 0 1 .6l.4-1 .2-.7a1.4 1.4 0 0 0-1.4-1.3q-1 0-1.4.9s.6-1.2.6-2.5-1.4-2.7-1.4-2.7z"/> + <path fill="#c8b100" d="M209.4 269.9h4.1v-1h-4.1z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M209.4 269.9h4.1v-1h-4.1z"/> + <path fill="#c8b100" d="M206.3 269.6s-1.3 1.5-1.3 2.8.6 2.4.6 2.4q-.3-.8-1.4-.9-1.3.1-1.4 1.4l.2.7.5 1q.2-.6 1-.6a1 1 0 0 1 1 1v.3h-1.2v1h1l-.8 1.5 1-.4.8.9.8-1 1 .5-.7-1.5h1v-1h-1.1v-.3a1 1 0 0 1 1-1q.6 0 .9.6l.5-1 .2-.7a1.4 1.4 0 0 0-1.4-1.4q-1 0-1.4 1s.6-1.2.6-2.5-1.4-2.7-1.4-2.7"/> + <path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".3" d="M206.3 269.6s-1.3 1.5-1.3 2.8.6 2.4.6 2.4q-.3-.8-1.4-.9-1.3.1-1.4 1.4l.2.7.5 1q.2-.6 1-.6a1 1 0 0 1 1 1v.3h-1.2v1h1l-.8 1.5 1-.4.8.9.8-1 1 .5-.7-1.5h1v-1h-1.1v-.3a1 1 0 0 1 1-1q.6 0 .9.6l.5-1 .2-.7a1.4 1.4 0 0 0-1.4-1.4q-1 0-1.4 1s.6-1.2.6-2.5-1.4-2.7-1.4-2.7z"/> + <path fill="#c8b100" d="M204.3 278.6h4.1v-1h-4.1z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M204.3 278.6h4.1v-1h-4.1z"/> + <path fill="#c8b100" d="M237.6 223.4h-.3l-.3.4q-.4.3-.8 0l-.1-.4h-.5q-.4-.3-.1-.7v-.5h-.3l-.1.2q-.3.4-.7.2v-.2h-.3c-.5.2-.7-1-.7-1.2l-.2.2s.2.7.1 1.2l-.3 1.2a9 9 0 0 1 2.9 1.6 9 9 0 0 1 2.2 2.3l1.2-.5c.6-.2 1.3-.2 1.3-.2l.2-.2c-.3 0-1.5.1-1.5-.4v-.2h-.2q-.3-.3 0-.7l.2-.1v-.3h-.3l-.2.1q-.4.5-.8 0l-.1-.4h-.5q-.4-.3 0-.8l.2-.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M237.6 223.4h-.3l-.3.4q-.4.3-.8 0l-.1-.4h-.5q-.4-.3-.1-.7v-.5h-.3l-.1.2q-.3.4-.7.2v-.2h-.3c-.5.2-.7-1-.7-1.2l-.2.2s.2.7.1 1.2l-.3 1.2a9 9 0 0 1 2.9 1.6 9 9 0 0 1 2.2 2.3l1.2-.5c.6-.2 1.3-.2 1.3-.2l.2-.2c-.3 0-1.5.1-1.5-.4v-.2h-.2q-.3-.3 0-.7l.2-.1v-.3h-.3l-.2.1q-.4.5-.8 0l-.1-.4h-.5q-.4-.3 0-.8l.2-.3z"/> + <path d="M235.4 224h.2v.3h-.1q-.1-.1 0-.2"/> + <path fill="none" stroke="#000" stroke-width=".1" d="M235.4 224h.2v.3h-.1q-.1-.1 0-.2z"/> + <path d="m236.3 224.8-.3-.2v-.2h.1l.4.3.3.2v.2h-.2z"/> + <path fill="none" stroke="#000" stroke-width=".1" d="m236.3 224.8-.3-.2v-.2h.1l.4.3.3.2v.2h-.2l-.3-.3"/> + <path d="m234.6 223.7-.2-.2s-.1 0 0-.1l.3.1.3.1v.2h-.1z"/> + <path fill="none" stroke="#000" stroke-width=".1" d="m234.6 223.7-.2-.2s-.1 0 0-.1l.3.1.3.1v.2h-.1l-.3-.1"/> + <path d="M233.7 223h.2v.2h-.2s-.1-.1 0-.2"/> + <path fill="none" stroke="#000" stroke-width=".1" d="M233.7 223h.2v.2h-.2s-.1-.1 0-.2z"/> + <path d="M237.3 225.5v-.2h-.3l.1.2z"/> + <path fill="none" stroke="#000" stroke-width=".1" d="M237.3 225.5v-.2h-.3l.1.2z"/> + <path d="m237.9 226.2.2.2h.1v-.2l-.2-.2-.2-.2h-.1v.2z"/> + <path fill="none" stroke="#000" stroke-width=".1" d="m237.9 226.2.2.2h.1v-.2l-.2-.2-.2-.2h-.1v.2l.2.2"/> + <path d="M238.8 227v-.3h-.3v.2h.3"/> + <path fill="none" stroke="#000" stroke-width=".1" d="M238.8 227v-.3h-.3v.2h.3z"/> + <path fill="#c8b100" d="M236.2 221.1h-.6l-.1.9v.1h.2l.7-.5-.3-.5"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M236.2 221.1h-.6l-.1.9v.1h.2l.7-.5-.3-.5"/> + <path fill="#c8b100" d="M234.6 221.6v.5l.9.1h.1v-.2l-.5-.7z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M234.6 221.6v.5l.9.1h.1v-.2l-.5-.7-.5.3"/> + <path fill="#c8b100" d="m236.4 222.6-.4.3-.6-.7v-.1h1.1z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m236.4 222.6-.4.3-.6-.7v-.1h1.1v.5"/> + <path fill="#c8b100" d="M235.3 222h.4v.3h-.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M235.3 222h.4v.3h-.3z"/> + <path fill="#c8b100" d="m233.2 221.1-.2-.7-.4-.4s.4-.2.8.1 0 .9 0 .9z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m233.2 221.1-.2-.7-.4-.4s.4-.2.8.1 0 .9 0 .9z"/> + <path fill="#c8b100" d="m234.2 221.4-.4.4-.6-.6v-.2h1z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m234.2 221.4-.4.4-.6-.6v-.2h1v.4"/> + <path fill="#c8b100" d="m233.1 221 .3-.1v.3q0 .3-.2.2z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m233.1 221 .3-.1v.3q0 .3-.2.2z"/> + <path fill="#c8b100" d="M238.3 222.5h-.5l-.3.7v.2h.2l.8-.4z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M238.3 222.5h-.5l-.3.7v.2h.2l.8-.4-.2-.5"/> + <path fill="#c8b100" d="M236.7 222.8v.5l.8.2h.1v-.2l-.4-.7z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M236.7 222.8v.5l.8.2h.1v-.2l-.4-.7-.5.2"/> + <path fill="#c8b100" d="m238.4 224-.5.2-.4-.7v-.2h.1l.9.2z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m238.4 224-.5.2-.4-.7v-.2h.1l.9.2-.1.5"/> + <path fill="#c8b100" d="M237.3 223.2h.4v.4h-.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M237.3 223.2h.4v.4h-.3z"/> + <path fill="#c8b100" d="m240.2 224.3.1.5-.8.3h-.2v-.2l.4-.8z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m240.2 224.3.1.5-.8.3h-.2v-.2l.4-.8.5.2"/> + <path fill="#c8b100" d="m240 225.8-.5.1-.3-.8v-.1h.2l.8.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m240 225.8-.5.1-.3-.8v-.1h.2l.8.3-.1.5"/> + <path fill="#c8b100" d="m238.6 224.3-.2.5.9.3h.1v-.1l-.3-.8z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m238.6 224.3-.2.5.9.3h.1v-.1l-.3-.8-.5.1"/> + <path fill="#c8b100" d="M239.5 225.2v-.3h-.4v.3z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M239.5 225.2v-.3h-.4v.3z"/> + <path fill="#c8b100" d="M240.8 227h.8l.5.3s.1-.4-.3-.7c-.3-.3-.8.2-.8.2z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M240.8 227h.8l.5.3s.1-.4-.3-.7c-.3-.3-.8.2-.8.2z"/> + <path fill="#c8b100" d="m240.3 226.1-.3.5.8.5v-.1h.2l-.1-1z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="m240.3 226.1-.3.5.8.5v-.1h.2l-.1-1-.6.1"/> + <path fill="#c8b100" d="M241 227s.1-.1 0-.2h-.3q-.3 0-.1.2z"/> + <path fill="none" stroke="#000" stroke-width=".3" d="M241 227s.1-.1 0-.2h-.3q-.3 0-.1.2zm38-21.9v.6h-2.4v-.6h1v-1.3h-.7v-.5h.6v-.6h.6v.6h.6v.6h-.6v1.2h1"/> + <path fill="none" d="M134.4 217.1v-1.2m-.4 1.2v-1.2m-.2 1.2v-1.2m-.3 1.2v-1.2"/> + <path fill="none" stroke="#000" stroke-width=".1" d="M133.2 217.1v-1.2m-.5 1.1v-1m.2 1v-1m-.7 1v-1m.2 1v-1m-.9 1v-1m.2 1v-1m.3 1v-1m-.7 1v-1m-.3.9v-.8m-.1.8v-.8m-.5.7v-.6m.2.6v-.6m-.4.5v-.5m-.2.5v-.4m-.3.3v-.3m-.3.3v-.2"/> + <path fill="none" stroke="#000" stroke-width=".2" d="M129.2 216.6v-.2"/> + <path fill="none" d="M135.7 217v-1m-.5 1v-1m-.4 1.2V216m143 1.1V216m-.4 1.1V216m-.3 1.1V216m-.3 1.2V216"/> + <path fill="none" stroke="#000" stroke-width=".1" d="M276.6 217.1V216m-.6 1v-1m.3 1v-1m-.8 1v-1m.3 1v-1m-.9 1v-1m.2 1v-1m.2 1v-1m-.6 1v-1m-.3.9v-.8m-.2.8v-.8m-.4.7v-.6m.2.6v-.6m-.5.6v-.6m-.2.5v-.4m-.3.4v-.4m-.2.3v-.2"/> + <path fill="none" stroke="#000" stroke-width=".2" d="M272.6 216.6v-.2"/> + <path fill="none" d="M279.1 217v-1m-.6 1v-1m-.4 1.1V216"/> +</svg> diff --git a/static/images/flags/fr.svg b/static/images/flags/fr.svg new file mode 100644 index 0000000..4110e59 --- /dev/null +++ b/static/images/flags/fr.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-fr" viewBox="0 0 640 480"> + <path fill="#fff" d="M0 0h640v480H0z"/> + <path fill="#000091" d="M0 0h213.3v480H0z"/> + <path fill="#e1000f" d="M426.7 0H640v480H426.7z"/> +</svg> diff --git a/static/images/flags/gb.svg b/static/images/flags/gb.svg new file mode 100644 index 0000000..7991383 --- /dev/null +++ b/static/images/flags/gb.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-gb" viewBox="0 0 640 480"> + <path fill="#012169" d="M0 0h640v480H0z"/> + <path fill="#FFF" d="m75 0 244 181L562 0h78v62L400 241l240 178v61h-80L320 301 81 480H0v-60l239-178L0 64V0z"/> + <path fill="#C8102E" d="m424 281 216 159v40L369 281zm-184 20 6 35L54 480H0zM640 0v3L391 191l2-44L590 0zM0 0l239 176h-60L0 42z"/> + <path fill="#FFF" d="M241 0v480h160V0zM0 160v160h640V160z"/> + <path fill="#C8102E" d="M0 193v96h640v-96zM273 0v480h96V0z"/> +</svg> diff --git a/static/images/flags/jp.svg b/static/images/flags/jp.svg new file mode 100644 index 0000000..cc1c181 --- /dev/null +++ b/static/images/flags/jp.svg @@ -0,0 +1,11 @@ +<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-jp" viewBox="0 0 640 480"> + <defs> + <clipPath id="jp-a"> + <path fill-opacity=".7" d="M-88 32h640v480H-88z"/> + </clipPath> + </defs> + <g fill-rule="evenodd" stroke-width="1pt" clip-path="url(#jp-a)" transform="translate(88 -32)"> + <path fill="#fff" d="M-128 32h720v480h-720z"/> + <circle cx="523.1" cy="344.1" r="194.9" fill="#bc002d" transform="translate(-168.4 8.6)scale(.76554)"/> + </g> +</svg> diff --git a/static/images/flags/kr.svg b/static/images/flags/kr.svg new file mode 100644 index 0000000..6947eab --- /dev/null +++ b/static/images/flags/kr.svg @@ -0,0 +1,24 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="flag-icons-kr" viewBox="0 0 640 480"> + <defs> + <clipPath id="kr-a"> + <path fill-opacity=".7" d="M-95.8-.4h682.7v512H-95.8z"/> + </clipPath> + </defs> + <g fill-rule="evenodd" clip-path="url(#kr-a)" transform="translate(89.8 .4)scale(.9375)"> + <path fill="#fff" d="M-95.8-.4H587v512H-95.8Z"/> + <g transform="rotate(-56.3 361.6 -101.3)scale(10.66667)"> + <g id="kr-c"> + <path id="kr-b" fill="#000001" d="M-6-26H6v2H-6Zm0 3H6v2H-6Zm0 3H6v2H-6Z"/> + <use xlink:href="#kr-b" width="100%" height="100%" y="44"/> + </g> + <path stroke="#fff" d="M0 17v10"/> + <path fill="#cd2e3a" d="M0-12a12 12 0 0 1 0 24Z"/> + <path fill="#0047a0" d="M0-12a12 12 0 0 0 0 24A6 6 0 0 0 0 0Z"/> + <circle cy="-6" r="6" fill="#cd2e3a"/> + </g> + <g transform="rotate(-123.7 191.2 62.2)scale(10.66667)"> + <use xlink:href="#kr-c" width="100%" height="100%"/> + <path stroke="#fff" d="M0-23.5v3M0 17v3.5m0 3v3"/> + </g> + </g> +</svg> diff --git a/tools/i18n/go.mod b/tools/i18n/go.mod new file mode 100644 index 0000000..34112ee --- /dev/null +++ b/tools/i18n/go.mod @@ -0,0 +1,3 @@ +module github.com/go-slides/docs/tools/i18n + +go 1.27.2 diff --git a/tools/i18n/main.go b/tools/i18n/main.go new file mode 100644 index 0000000..ed776ce --- /dev/null +++ b/tools/i18n/main.go @@ -0,0 +1,550 @@ +// Command i18n checks that the documentation's translations pair up with the +// English pages, and gives translated headings the English anchors. +// +// Every page of content/en/ must exist, under the same name, in each other +// language; no page may exist only in a translation. A translation must keep +// what is not prose exactly as the English page has it: +// +// - the headings: same number, same levels, in the same order, each with the +// English heading's anchor as an explicit {#id}, so that links into a page +// (a relref with a fragment, a bookmark) reach the same place in every +// language; +// - fenced code blocks, character for character; +// - inline code spans, as a multiset (whitespace runs count as one space); +// - Hugo shortcodes, in order; +// - Markdown link targets, as a multiset; +// - a title and a description in the front matter. +// +// Usage, from the documentation repository's root: +// +// go run ./tools/i18n # check; exit status 1 if a page is missing in a language +// go run ./tools/i18n -strict # ... and if a translation differs where it must not +// go run ./tools/i18n -fix # add the missing {#id} to translated headings, then check +// go run ./tools/i18n -built public # after hugo: the built pages' heading ids +// +// The languages are those of hugo.yaml (languages:), so that declaring one +// there without content fails the build here first. +package main + +import ( + "flag" + "fmt" + "io/fs" + "os" + "path/filepath" + "regexp" + "slices" + "sort" + "strings" + "unicode" +) + +func main() { + root := flag.String("root", ".", "the documentation repository's root") + fix := flag.Bool("fix", false, "add the English anchors to translated headings") + strict := flag.Bool("strict", false, "fail on every difference, not only on a missing page") + built := flag.String("built", "", "a built site (hugo's public/): check that every page's headings have the English page's ids") + flag.Parse() + if *built != "" { + langs, err := Languages(filepath.Join(*root, "hugo.yaml")) + if err != nil { + fmt.Fprintln(os.Stderr, "i18n:", err) + os.Exit(1) + } + problems, n, err := CheckBuilt(*built, langs) + if err != nil { + fmt.Fprintln(os.Stderr, "i18n:", err) + os.Exit(1) + } + for _, p := range problems { + if *strict { + fmt.Println(p) + } else { + fmt.Println("::warning::" + p) + } + } + if len(problems) > 0 && *strict { + fmt.Printf("i18n: %d problem(s) in the built site\n", len(problems)) + os.Exit(1) + } + fmt.Printf("i18n: %d translated pages of the built site compared with the English headings' ids (%d warning(s))\n", n, len(problems)) + return + } + langs, err := Languages(filepath.Join(*root, "hugo.yaml")) + if err != nil { + fmt.Fprintln(os.Stderr, "i18n:", err) + os.Exit(1) + } + problems, err := Run(*root, langs, *fix) + if err != nil { + fmt.Fprintln(os.Stderr, "i18n:", err) + os.Exit(1) + } + // A page missing in a language, or only in a translation, fails. A + // translation that differs from its English page where it must not is + // reported as a warning (an annotation in GitHub Actions), unless -strict: + // an English edit then does not turn every pull request red until the + // translations follow, and a broken link into a page is caught by the link + // check anyway. + failures := 0 + for _, p := range problems { + if *strict || Missing(p) { + fmt.Println(p) + failures++ + } else { + fmt.Println("::warning::" + p) + } + } + if failures > 0 { + fmt.Printf("i18n: %d problem(s)\n", failures) + os.Exit(1) + } + fmt.Printf("i18n: %d languages, every page of content/en has its translation (%d warning(s))\n", len(langs), len(problems)) +} + +// Missing reports whether a problem is a page that exists in only one of the +// two languages. +func Missing(problem string) bool { + return strings.Contains(problem, " is missing (") || strings.Contains(problem, " has no English page") +} + +var langRE = regexp.MustCompile(`(?m)^ ([a-z]{2}(?:-[A-Za-z]+)?):\s*$`) + +// Languages returns the codes of hugo.yaml's languages, other than en, in +// file order: the two-space-indented keys under "languages:". +func Languages(hugoYAML string) ([]string, error) { + b, err := os.ReadFile(hugoYAML) + if err != nil { + return nil, err + } + text := string(b) + i := strings.Index(text, "\nlanguages:\n") + if i < 0 { + return nil, fmt.Errorf("%s declares no languages:", hugoYAML) + } + text = text[i+len("\nlanguages:\n"):] + // the block ends at the next unindented line + if j := regexp.MustCompile(`(?m)^\S`).FindStringIndex(text); j != nil { + text = text[:j[0]] + } + var out []string + for _, m := range langRE.FindAllStringSubmatch(text, -1) { + if m[1] != "en" { + out = append(out, m[1]) + } + } + if len(out) == 0 { + return nil, fmt.Errorf("%s declares no language besides en", hugoYAML) + } + return out, nil +} + +// Run checks every language, and with fix rewrites translated headings. +func Run(root string, langs []string, fix bool) ([]string, error) { + en := filepath.Join(root, "content", "en") + pages, err := pagesOf(en) + if err != nil { + return nil, err + } + var problems []string + for _, lang := range langs { + dir := filepath.Join(root, "content", lang) + have, err := pagesOf(dir) + if err != nil { + problems = append(problems, fmt.Sprintf("%s: %v", lang, err)) + continue + } + for _, p := range pages { + if !slices.Contains(have, p) { + problems = append(problems, fmt.Sprintf("content/%s/%s is missing (content/en/%[2]s exists)", lang, p)) + } + } + for _, p := range have { + if !slices.Contains(pages, p) { + problems = append(problems, fmt.Sprintf("content/%s/%s has no English page", lang, p)) + } + } + for _, p := range pages { + if !slices.Contains(have, p) { + continue + } + src, err := os.ReadFile(filepath.Join(en, p)) + if err != nil { + return nil, err + } + tr, err := os.ReadFile(filepath.Join(dir, p)) + if err != nil { + return nil, err + } + if fix { + fixed, err := AddAnchors(string(src), string(tr)) + if err == nil && fixed != string(tr) { + if err := os.WriteFile(filepath.Join(dir, p), []byte(fixed), 0o644); err != nil { + return nil, err + } + tr = []byte(fixed) + } + } + for _, msg := range Compare(string(src), string(tr)) { + problems = append(problems, fmt.Sprintf("content/%s/%s: %s", lang, p, msg)) + } + } + } + return problems, nil +} + +var headingIDRE = regexp.MustCompile(`<h[1-6][^>]*?\sid=(?:"([^"]+)"|([^\s>"]+))`) + +// headingIDs lists the ids of the headings of a built page, in order. +func headingIDs(html []byte) []string { + var out []string + for _, m := range headingIDRE.FindAllSubmatch(html, -1) { + id := string(m[1]) + if id == "" { + id = string(m[2]) + } + out = append(out, id) + } + return out +} + +// CheckBuilt compares, in a built site, the ids of each translated page's +// headings with those of the English page at the same path (the languages +// live in public/<lang>/..., English at the root). It returns the problems and +// how many translated pages it compared. +func CheckBuilt(public string, langs []string) ([]string, int, error) { + var problems []string + n := 0 + err := filepath.WalkDir(public, func(p string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + rel, _ := filepath.Rel(public, p) + first := strings.SplitN(filepath.ToSlash(rel), "/", 2)[0] + if d.IsDir() && slices.Contains(langs, first) { + return filepath.SkipDir + } + if d.IsDir() || d.Name() != "index.html" { + return nil + } + en, err := os.ReadFile(p) + if err != nil { + return err + } + want := headingIDs(en) + for _, lang := range langs { + tp := filepath.Join(public, lang, rel) + tr, err := os.ReadFile(tp) + if err != nil { + continue // no translation of this generated page (a redirect, a tag page) + } + n++ + got := headingIDs(tr) + if !slices.Equal(want, got) { + problems = append(problems, fmt.Sprintf("%s/%s: heading ids %q, the English page's are %q", lang, filepath.ToSlash(rel), first4(got), first4(want))) + } + } + return nil + }) + return problems, n, err +} + +func first4(s []string) []string { + if len(s) > 4 { + return append(slices.Clone(s[:4]), "…") + } + return s +} + +// pagesOf lists the Markdown files under dir, slash-separated and sorted. +func pagesOf(dir string) ([]string, error) { + var out []string + err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if !d.IsDir() && strings.HasSuffix(p, ".md") { + rel, _ := filepath.Rel(dir, p) + out = append(out, filepath.ToSlash(rel)) + } + return nil + }) + sort.Strings(out) + return out, err +} + +// Page is what a Markdown page is made of, as far as this check cares. +type Page struct { + Front string + Headings []Heading + Fences []string + Spans []string + Shortcodes []string + Targets []string +} + +// Heading is an ATX heading: its level, its text as written and its explicit +// id, if it has one. +type Heading struct { + Level int + Text string + ID string + Line int // index in the page's lines +} + +var ( + headingRE = regexp.MustCompile(`^(#{1,6})\s+(.*?)\s*$`) + idRE = regexp.MustCompile(`\s*\{#([A-Za-z0-9_:.-]+)\}\s*$`) + fenceRE = regexp.MustCompile("^(\\s*)(`{3,}|~{3,})(.*)$") + shortcodeRE = regexp.MustCompile(`\{\{[<%].*?[>%]\}\}`) + targetRE = regexp.MustCompile(`\]\(([^)\s]+)(?:\s+"[^"]*")?\)`) +) + +// Parse reads a page. +func Parse(src string) Page { + var p Page + lines := strings.Split(src, "\n") + i := 0 + if len(lines) > 0 && strings.TrimRight(lines[0], " \r") == "---" { + for j := 1; j < len(lines); j++ { + if strings.TrimRight(lines[j], " \r") == "---" { + p.Front = strings.Join(lines[1:j], "\n") + i = j + 1 + break + } + } + } + var prose []string + for ; i < len(lines); i++ { + line := lines[i] + if m := fenceRE.FindStringSubmatch(line); m != nil { + indent, fence, info := m[1], m[2], strings.TrimSpace(m[3]) + var body []string + j := i + 1 + for ; j < len(lines); j++ { + if c := fenceRE.FindStringSubmatch(lines[j]); c != nil && c[2][0] == fence[0] && len(c[2]) >= len(fence) && strings.TrimSpace(c[3]) == "" { + break + } + body = append(body, strings.TrimPrefix(lines[j], indent)) + } + p.Fences = append(p.Fences, info+"\n"+strings.Join(body, "\n")) + i = j + continue + } + if m := headingRE.FindStringSubmatch(line); m != nil { + h := Heading{Level: len(m[1]), Text: m[2], Line: i} + if id := idRE.FindStringSubmatch(h.Text); id != nil { + h.ID = id[1] + h.Text = idRE.ReplaceAllString(h.Text, "") + } + p.Headings = append(p.Headings, h) + } + prose = append(prose, line) + } + text := strings.Join(prose, "\n") + p.Shortcodes = shortcodeRE.FindAllString(text, -1) + for _, m := range targetRE.FindAllStringSubmatch(text, -1) { + p.Targets = append(p.Targets, m[1]) + } + p.Spans = codeSpans(shortcodeRE.ReplaceAllString(text, "")) + return p +} + +// codeSpans returns the inline code spans of text: a run of n backticks opens +// one and the next run of exactly n closes it. Whitespace runs inside a span +// count as one space. +func codeSpans(text string) []string { + var out []string + for i := 0; i < len(text); { + if text[i] != '`' { + i++ + continue + } + j := i + for j < len(text) && text[j] == '`' { + j++ + } + n := j - i + k := j + closed := false + for k < len(text) { + if text[k] != '`' { + k++ + continue + } + e := k + for e < len(text) && text[e] == '`' { + e++ + } + if e-k == n { + out = append(out, strings.Join(strings.Fields(text[j:k]), " ")) + i = e + closed = true + break + } + k = e + } + if !closed { + i = j + } + } + return out +} + +// Slug is Hugo's default anchor for a heading (goldmark's "github" style): +// the text without its Markdown, lowercased, punctuation dropped, spaces and +// hyphens kept as hyphens. Duplicate slugs on a page are numbered by the caller. +func Slug(heading string) string { + t := plain(heading) + var b strings.Builder + for _, r := range strings.ToLower(t) { + switch { + case unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '-': + b.WriteRune(r) + case unicode.IsSpace(r): + b.WriteByte('-') + } + } + return b.String() +} + +var ( + linkTextRE = regexp.MustCompile(`\[([^\]]*)\]\([^)]*\)`) + htmlTagRE = regexp.MustCompile(`<[^>]+>`) +) + +// plain removes the Markdown of an inline text: code backticks, emphasis, +// links (keeping their text) and HTML tags. +func plain(s string) string { + s = shortcodeRE.ReplaceAllString(s, "") + s = linkTextRE.ReplaceAllString(s, "$1") + s = htmlTagRE.ReplaceAllString(s, "") + return strings.NewReplacer("`", "", "**", "", "__", "", "*", "").Replace(s) +} + +// IDs are the anchors of a page's headings: the explicit one, or the slug, +// with -1, -2 ... on a repeat, as Hugo numbers them. +func IDs(hs []Heading) []string { + seen := map[string]int{} + out := make([]string, len(hs)) + for i, h := range hs { + id := h.ID + if id == "" { + id = Slug(h.Text) + } + base := id + if n := seen[base]; n > 0 { + id = fmt.Sprintf("%s-%d", base, n) + } + seen[base]++ + out[i] = id + } + return out +} + +// Compare lists how a translation differs from its English page, where it +// must not. +func Compare(en, tr string) []string { + a, b := Parse(en), Parse(tr) + var out []string + for _, key := range []string{"title", "description"} { + if !regexp.MustCompile(`(?m)^` + key + `:\s*\S`).MatchString(b.Front) { + out = append(out, "the front matter has no "+key) + } + } + if len(a.Headings) != len(b.Headings) { + out = append(out, fmt.Sprintf("%d headings, the English page has %d", len(b.Headings), len(a.Headings))) + } else { + ids := IDs(a.Headings) + for i, h := range b.Headings { + if h.Level != a.Headings[i].Level { + out = append(out, fmt.Sprintf("heading %q is level %d, the English one is level %d", h.Text, h.Level, a.Headings[i].Level)) + } + if h.ID != ids[i] { + out = append(out, fmt.Sprintf("heading %q needs {#%s} (has %q)", h.Text, ids[i], h.ID)) + } + } + } + if !slices.Equal(a.Fences, b.Fences) { + out = append(out, diff("fenced code blocks", a.Fences, b.Fences)) + } + if x, y := sorted(a.Spans), sorted(b.Spans); !slices.Equal(x, y) { + out = append(out, diff("inline code spans", x, y)) + } + if !slices.Equal(a.Shortcodes, b.Shortcodes) { + out = append(out, diff("shortcodes", a.Shortcodes, b.Shortcodes)) + } + if x, y := sorted(a.Targets), sorted(b.Targets); !slices.Equal(x, y) { + out = append(out, diff("link targets", x, y)) + } + return out +} + +func sorted(s []string) []string { + c := slices.Clone(s) + sort.Strings(c) + return c +} + +// diff describes how got differs from want as multisets: what is missing and +// what is extra, a few of each. +func diff(what string, want, got []string) string { + count := func(s []string) map[string]int { + m := map[string]int{} + for _, x := range s { + m[x]++ + } + return m + } + w, g := count(want), count(got) + var missing, extra []string + for k, n := range w { + if g[k] < n { + missing = append(missing, short(k)) + } + } + for k, n := range g { + if w[k] < n { + extra = append(extra, short(k)) + } + } + sort.Strings(missing) + sort.Strings(extra) + if len(missing) == 0 && len(extra) == 0 { + return what + " are in another order than in the English page" + } + return fmt.Sprintf("%s differ: missing %q, extra %q", what, first(missing, 3), first(extra, 3)) +} + +func first(s []string, n int) []string { + if len(s) > n { + return s[:n] + } + return s +} + +func short(s string) string { + s = strings.ReplaceAll(s, "\n", "⏎") + if r := []rune(s); len(r) > 60 { + return string(r[:60]) + "…" + } + return s +} + +// AddAnchors gives each heading of a translation the anchor of the English +// heading at the same position, when it has no explicit one. It changes +// nothing, and reports an error, when the two pages' headings do not pair up. +func AddAnchors(en, tr string) (string, error) { + a, b := Parse(en), Parse(tr) + if len(a.Headings) != len(b.Headings) { + return tr, fmt.Errorf("%d headings against %d", len(b.Headings), len(a.Headings)) + } + ids := IDs(a.Headings) + lines := strings.Split(tr, "\n") + for i, h := range b.Headings { + if h.ID == "" { + lines[h.Line] = strings.TrimRight(lines[h.Line], " \r") + " {#" + ids[i] + "}" + } + } + return strings.Join(lines, "\n"), nil +} diff --git a/tools/i18n/main_test.go b/tools/i18n/main_test.go new file mode 100644 index 0000000..beac283 --- /dev/null +++ b/tools/i18n/main_test.go @@ -0,0 +1,185 @@ +package main + +import ( + "os" + "path/filepath" + "reflect" + "strings" + "testing" +) + +func TestSlug(t *testing.T) { + for in, want := range map[string]string{ + "Images, PDF, PPTX and ODP": "images-pdf-pptx-and-odp", + "`slides.yaml`": "slidesyaml", + "What it does today": "what-it-does-today", + "A [link](https://x.test) in **bold**": "a-link-in-bold", + "Vector PDF": "vector-pdf", + "HCL: expressions & more": "hcl-expressions--more", + } { + if got := Slug(in); got != want { + t.Errorf("Slug(%q) = %q, want %q", in, got, want) + } + } +} + +func TestIDsNumberRepeats(t *testing.T) { + hs := []Heading{{Text: "Notes"}, {Text: "Notes"}, {Text: "Other", ID: "custom"}, {Text: "Notes"}} + if got, want := IDs(hs), []string{"notes", "notes-1", "custom", "notes-2"}; !reflect.DeepEqual(got, want) { + t.Errorf("got %v, want %v", got, want) + } +} + +const english = "---\ntitle: Hello\ndescription: A page.\nweight: 1\n---\n\n# Hello\n\nRun `slides build` or ``a `b` c``,\nsee [docs]({{< relref \"cli#flags\" >}}) and [x](https://x.test).\n\n## Flags\n\n```sh\nslides --pdf # a comment\n```\n\n- in a list:\n\n ```go\n fmt.Println(\"hi\")\n ```\n" + +func translated(mod func(string) string) string { + tr := "---\ntitle: Bonjour\ndescription: Une page.\nweight: 1\n---\n\n# Bonjour {#hello}\n\nLancez `slides build` ou ``a `b` c``,\nvoyez [la doc]({{< relref \"cli#flags\" >}}) et [x](https://x.test).\n\n## Options {#flags}\n\n```sh\nslides --pdf # a comment\n```\n\n- dans une liste :\n\n ```go\n fmt.Println(\"hi\")\n ```\n" + if mod != nil { + tr = mod(tr) + } + return tr +} + +func TestCompareAcceptsAFaithfulTranslation(t *testing.T) { + if got := Compare(english, translated(nil)); len(got) != 0 { + t.Errorf("unexpected problems: %v", got) + } +} + +func TestCompareFindsWhatChanged(t *testing.T) { + for name, c := range map[string]struct { + mod func(string) string + want string + }{ + "code span": {func(s string) string { return strings.Replace(s, "`slides build`", "`slides construire`", 1) }, "inline code spans differ"}, + "fence": {func(s string) string { return strings.Replace(s, "# a comment", "# un commentaire", 1) }, "fenced code blocks differ"}, + "indented": {func(s string) string { return strings.Replace(s, `fmt.Println("hi")`, `fmt.Println("salut")`, 1) }, "fenced code blocks differ"}, + "shortcode": {func(s string) string { return strings.Replace(s, "cli#flags", "cli#options", 1) }, "shortcodes differ"}, + "target": {func(s string) string { return strings.Replace(s, "https://x.test", "https://y.test", 1) }, "link targets differ"}, + "heading": {func(s string) string { return strings.Replace(s, "\n## Options {#flags}\n", "\n", 1) }, "1 headings, the English page has 2"}, + "level": {func(s string) string { return strings.Replace(s, "## Options", "### Options", 1) }, "is level 3"}, + "anchor": {func(s string) string { return strings.Replace(s, "{#flags}", "", 1) }, "needs {#flags}"}, + "wrong anchor": {func(s string) string { return strings.Replace(s, "{#flags}", "{#options}", 1) }, "needs {#flags}"}, + "description": {func(s string) string { return strings.Replace(s, "description: Une page.\n", "", 1) }, "no description"}, + } { + t.Run(name, func(t *testing.T) { + got := strings.Join(Compare(english, translated(c.mod)), "\n") + if !strings.Contains(got, c.want) { + t.Errorf("want %q in:\n%s", c.want, got) + } + }) + } +} + +func TestSpansSurviveRewrapping(t *testing.T) { + a := Parse("x `one\ntwo` y") + b := Parse("x `one two` y") + if !reflect.DeepEqual(a.Spans, b.Spans) { + t.Errorf("%v != %v", a.Spans, b.Spans) + } +} + +func TestAddAnchors(t *testing.T) { + bare := strings.ReplaceAll(translated(nil), " {#hello}", "") + bare = strings.ReplaceAll(bare, " {#flags}", "") + got, err := AddAnchors(english, bare) + if err != nil { + t.Fatal(err) + } + if got != translated(nil) { + t.Errorf("got:\n%s", got) + } + if _, err := AddAnchors(english, "# one heading only\n"); err == nil { + t.Error("headings that do not pair up were anchored") + } +} + +func tree(t *testing.T, files map[string]string) string { + t.Helper() + root := t.TempDir() + for p, s := range files { + p = filepath.Join(root, filepath.FromSlash(p)) + if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(p, []byte(s), 0o644); err != nil { + t.Fatal(err) + } + } + return root +} + +const hugo = "title: x\nlanguages:\n en:\n label: English\n fr:\n label: Français\n zh:\n label: 简体中文\n locale: zh-Hans\nenableRobotsTXT: true\n" + +func TestLanguages(t *testing.T) { + root := tree(t, map[string]string{"hugo.yaml": hugo}) + got, err := Languages(filepath.Join(root, "hugo.yaml")) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, []string{"fr", "zh"}) { + t.Errorf("got %v", got) + } + root = tree(t, map[string]string{"hugo.yaml": "title: x\n"}) + if _, err := Languages(filepath.Join(root, "hugo.yaml")); err == nil { + t.Error("a site without languages was accepted") + } +} + +func TestRunFailsOnAMissingPageAndAnOrphan(t *testing.T) { + root := tree(t, map[string]string{ + "content/en/a.md": english, + "content/en/sub/b.md": english, + "content/fr/a.md": translated(nil), + "content/fr/orphan.md": translated(nil), + "content/zh/a.md": translated(nil), + "content/zh/sub/b.md": translated(nil), + }) + problems, err := Run(root, []string{"fr", "zh"}, false) + if err != nil { + t.Fatal(err) + } + all := strings.Join(problems, "\n") + for _, want := range []string{"content/fr/sub/b.md is missing", "content/fr/orphan.md has no English page"} { + if !strings.Contains(all, want) { + t.Errorf("missing %q in:\n%s", want, all) + } + } + if strings.Contains(all, "content/zh") { + t.Errorf("zh is complete, yet:\n%s", all) + } +} + +func TestRunFixAddsAnchors(t *testing.T) { + bare := strings.ReplaceAll(strings.ReplaceAll(translated(nil), " {#hello}", ""), " {#flags}", "") + root := tree(t, map[string]string{"content/en/a.md": english, "content/fr/a.md": bare}) + if problems, _ := Run(root, []string{"fr"}, false); len(problems) == 0 { + t.Fatal("a translation without anchors passed") + } + if problems, err := Run(root, []string{"fr"}, true); err != nil || len(problems) != 0 { + t.Fatalf("after -fix: %v %v", problems, err) + } +} + +func TestCheckBuilt(t *testing.T) { + page := func(ids ...string) string { + var b strings.Builder + for _, id := range ids { + b.WriteString("<h2 id=" + id + ">x</h2><h3 class=y id=\"" + id + "-2\">y</h3>") + } + return b.String() + } + root := tree(t, map[string]string{ + "index.html": page("a"), + "cli/index.html": page("flags", "pdf"), + "fr/index.html": page("a"), + "fr/cli/index.html": page("options", "pdf"), + }) + problems, n, err := CheckBuilt(root, []string{"fr"}) + if err != nil || n != 2 { + t.Fatalf("n=%d err=%v", n, err) + } + if len(problems) != 1 || !strings.Contains(problems[0], "fr/cli/index.html") { + t.Errorf("problems: %v", problems) + } +}