clone

a browser-first diagram editor with live svg previews, local drafts and encrypted, accountless sharing

migrate GoAT to dedicated WebAssembly Worker

Dami committed cc0879d
commit
cc0879d014965f55093ecc7c630154ddd120f9e8
parent
0ef3936c78ab77acd3eb4c3b131eace690db15c2
author
Dami <dami.etoile@gmail.com>
82 files changed, +9140 -21
Mdiagramzip-render/README.md6+-
Adiagramzip-render/goat-wasm/README.md20+
Adiagramzip-render/goat-wasm/go.mod7+
Adiagramzip-render/goat-wasm/go.sum16+
Adiagramzip-render/goat-wasm/goat.wasmbin
Adiagramzip-render/goat-wasm/main.go66+++
Adiagramzip-render/goat-wasm/provenance.md21+
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/.dir-locals.el19+
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/.gitignore11+
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/CHANGELOG.md40++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/LICENSE22+
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/README.md295+++++++++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/README.md.tmpl209++++++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/ascii/README.md125+++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/ascii/README.md.tmpl79+++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/ascii/a_drawable.go314++++++++++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/ascii/a_line.go535++++++++++++++++++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/ascii/a_roundedcorner.go26+
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/ascii/ascii.go327++++++++++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/clean.sh4+
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/git-add-generated-files.sh7+
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/internal/files.go80+++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/internal/runstate.go34+
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/local-markdown.sh5+
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/markdown_to_html.sh95++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/pre-push.sh127+++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/proof-markdown.sh57++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/runeset.go51++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/abstractcanvas.go264++++++++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/config.go320++++++++++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/csshelp.go75+++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/drawable.go54++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/index.go95++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/iter.go60++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/style.go76+++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/svg-text.go243+++++++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/svg.go287+++++++++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/utf8/README.md106++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/utf8/README.md.tmpl46++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/utf8/joints.go110++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/utf8/u_drawable.go144+++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/utf8/u_line.go203++++++++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/utf8/u_svg.go59++
Adiagramzip-render/goat-wasm/vendor/github.com/blampe/goat/utf8/utf8.go111++++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/.gitattributes1+
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/.gitignore5+
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/.golangci.yml16+
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/LICENSE.md22+
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/README.md64++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/binary.go792++++++++++++++++++++++++++++++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/binary_unix.go91+++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/buffer/buffer.go12+
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/buffer/lexer.go163++++++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/buffer/reader.go44++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/buffer/streamlexer.go223++++++++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/buffer/writer.go65++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/common.go546+++++++++++++++++++++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/css/README.md170++++++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/css/hash.go75+++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/css/lex.go698++++++++++++++++++++++++++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/css/parse.go493+++++++++++++++++++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/css/util.go47++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/error.go47++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/input.go186+++++++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/position.go95++++
Adiagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/util.go216++++++++
Adiagramzip-render/goat-wasm/vendor/modules.txt12+
Mdiagramzip-render/scripts/deploy-compatibility-units.mjs2+-
Mdiagramzip-render/scripts/deploy-worker-units.mjs3+-
Mdiagramzip-render/scripts/smoke-units.mjs5+
Mdiagramzip-render/src/adapters/edge.ts2+
Adiagramzip-render/src/adapters/edge/goat.ts69+++
Mdiagramzip-render/src/adapters/edge/types.ts2+-
Mdiagramzip-render/src/catalog.ts8+
Adiagramzip-render/src/units/goat.ts12+
Adiagramzip-render/src/vendor/goat-wasm.d.ts8+
Mdiagramzip-render/test/catalog.test.ts6+
Adiagramzip-render/test/goat.test.ts66+++
Ddiagramzip-render/wrangler.compatibility/goat.jsonc1-
Adiagramzip-render/wrangler.units/goat.jsonc10+
Mdiagramzip/src/renderer-units.js2+-
Mdocs/refactor-engines.md31+-
M diagramzip-render/README.md +3 -3
A diagramzip-render/goat-wasm/README.md +20 -0
A diagramzip-render/goat-wasm/go.mod +7 -0
A diagramzip-render/goat-wasm/go.sum +16 -0
A diagramzip-render/goat-wasm/goat.wasm +0 -0

Binary file.

A diagramzip-render/goat-wasm/main.go +66 -0
@@ -0,0 +1,66 @@
1+package main2+3+import (4+ "bytes"5+ "strings"6+ "unsafe"7+8+ "github.com/blampe/goat/ascii"9+ "github.com/blampe/goat/svg"10+ "github.com/blampe/goat/utf8"11+)12+13+var output []byte14+var retainedInputs [][]byte15+16+// beginRender releases input buffers from the previous request while keeping17+// the current request alive until render returns. The host calls this before18+// each sequence of alloc calls.19+//go:export beginRender20+func beginRender() {21+ retainedInputs = retainedInputs[:0]22+}23+24+// alloc reserves a buffer for a UTF-8 source string.25+//go:export alloc26+func alloc(size uint32) uint32 {27+ buf := make([]byte, size)28+ retainedInputs = append(retainedInputs, buf)29+ if len(buf) == 0 {30+ return 031+ }32+ return uint32(uintptr(unsafe.Pointer(&buf[0])))33+}34+35+// render converts the source at sourcePtr into SVG. The color strings are36+// passed separately so the Worker can validate them before entering Wasm.37+//go:export render38+func render(sourcePtr, sourceLen, utf8Flag, lightPtr, lightLen, darkPtr, darkLen uint32) uint32 {39+ source := string(unsafe.Slice((*byte)(unsafe.Pointer(uintptr(sourcePtr))), sourceLen))40+ light := string(unsafe.Slice((*byte)(unsafe.Pointer(uintptr(lightPtr))), lightLen))41+ dark := string(unsafe.Slice((*byte)(unsafe.Pointer(uintptr(darkPtr))), darkLen))42+43+ var config svg.Config44+ var canvas svg.AbstractCanvas45+ if utf8Flag != 0 {46+ config, _ = svg.NewConfig(utf8.ReservedSet, svg.MarkBindingMap{})47+ canvas = utf8.NewCanvas(&config, strings.NewReader(source))48+ } else {49+ config, _ = svg.NewConfig(ascii.ReservedSet, svg.MarkBindingMap{})50+ canvas = ascii.NewCanvas(&config, strings.NewReader(source))51+ }52+53+ var rendered bytes.Buffer54+ svg.WriteCanvas(&config, canvas, true, svg.ColorsOnlyCssFileContent(light, dark), nil, &rendered)55+ output = append(output[:0], rendered.Bytes()...)56+ if len(output) == 0 {57+ return 058+ }59+ return uint32(uintptr(unsafe.Pointer(&output[0])))60+}61+62+// outputLen returns the byte length of the most recent render result.63+//go:export outputLen64+func outputLen() uint32 { return uint32(len(output)) }65+66+func main() {}
A diagramzip-render/goat-wasm/provenance.md +21 -0
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/.dir-locals.el +19 -0
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/.gitignore +11 -0
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/CHANGELOG.md +40 -0
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/LICENSE +22 -0
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/README.md +295 -0
@@ -0,0 +1,295 @@
1+<!-- XX ? Move this file down to .github/, because its contents are GitHub-specific.2+ -->3+<!-- https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-collapsed-sections#creating-a-collapsed-section -->4+5+# GoAT: Go-based Text-Art to SVG refinement6+7+<!-- Github-specific status icon8+9+ Github requires explicitly binding a repository owner's username in the text of a README,10+ implying that a badge in README.md cannot both reflect the status of a test Action after a11+ contributor pushes to his pull request branch, and12+ the run of that same test Action after the upstream owner merges the pull request13+ into his 'main' branch. See:14+15+ https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/adding-a-workflow-status-badge16+17+ GitHub Community forum shows numerous similar issues with badges:18+ https://github.com/orgs/community/discussions/categories/actions?discussions_q=is%3Aopen+category%3AActions++badge.svg19+20+ X ?? Don't use badges. Instead, pay attention to Action result status.21+ XX But how to attest of self-test success to end users?22+ -->23+<!-- ![XXX missing local SVG](https://github.com/{ {.Github_Repository_Owner}}/goat/actions/workflows/test.yml/badge.svg) -->24+25+## What **GoAT** Can Do For You26+27+- From a chunky text-art input, render smooth, multi-colored SVG with embedded links.28+29+- Express your ideas in either of two input formats, subsets of well-known conventions:30+ 1. [ASCII](./ascii/)-based, as popularized by [MarkDeep](http://casual-effects.com/markdeep/)31+ 2. [Unicode UTF-8](./utf8/) [BOX](https://www.unicode.org/charts/PDF/U2500.pdf) characters32+33+- Style graphics globally within your diagram, by specifying CSS files on34+the command line.35+36+- Style individual text strings, by referring to37+individual CSS rules from markup bracketing your text.38+39+## You Will Also Need40+41+#### A fixed-pitch font with 2:1 height:width ratio as presented by your editor and terminal emulator42+Most fixed-pitch or "monospace" Unicode fonts maintain a 2:1 aspect ratio for43+characters in the ASCII range,44+45+ > On Ubuntu, satisfactory fonts are scarce -- perhaps the best is "DejaVu Sans Mono".46+47+#### Editor support48+49+Graphical- or rectangle-oriented text editing capability,50+provided either by special modes within your text editor, or a specialized graphical editor.51+Your best option may depend upon whether you prefer to create diagrams with52+graphics expressed in53+ [ASCII](./ascii/) or [UTF-8](./utf8/)54+ -- see the READMEs for some hints.55+56+## Installation57+58+```59+$ go install github.com/blampe/goat/cmd/goat@latest60+```61+62+## "Hello, world", two ways63+### Markdeep-style ASCII input64+65+```66+ .------------.67+o-->|Hello, world +--*68+ '------------'69+70+```71+![XXX missing local SVG](./ascii/_README/hello-world.svg)72+73+74+### UTF-8 BOX input75+```76+ ╭────────────┐77+○──►│Hello, world├─●78+ └────────────╯79+80+```81+![XXX missing local SVG](./utf8/_README/hello-world.svg)82+83+84+## Styling with CSS85+### Global styling86+Any number of `.css` files may be specified on the command line,87+ for inclusion within the output file's (single) `<svg>` element.88+To style all the various SVG drawing elements emitted by GoAT,89+ a CSS file might contain these rules:90+```91+/* Each of the CSS rules below begins with a CSS selector assumed by one of92+ * the SVG marking elements emitted by GoAT, so the properties inside93+ * will be globally effective.94+ * The sample of CSS properties below was chosen for obviousness in demonstration95+ * rather than suitability for any production use in. */96+97+/* CSS selector consists of the SVG element tag only. */98+text {99+ fill: inherit;100+}101+102+/* Selector consists of GoAT's choice of CSS class name, only, because GoAT draws "lines"103+ * by stringing together various SVG elements depending upon topology or geometry.104+ * Class name chosen is "path" to avoid any suggestion that no curves are possible. */105+.path {106+ stroke: #00B;107+}108+@media (prefers-color-scheme: dark) {109+ .path {110+ stroke: #ED8;111+ }112+}113+114+/* Selector consists of GoAT's choice of CSS class name only, because115+ * the name of the SVG element actually used is too general to suggest116+ * a diagramatic arrowhead. */117+.arrowhead {118+ stroke-width: 2; /* intrude into left and right neighbors */119+ stroke: #29F;120+ fill: none;121+}122+123+/* Selectors consist of the SVG element tag, qualified by GoAT's assignment of124+ * class "filled"/"hollow" based on recognition of a character code in the diagram source text. */125+circle.filled {126+ fill: #F60;127+ stroke: none;128+}129+circle.hollow {130+ stroke: #F6F;131+ stroke-width: 2;132+}133+134+```135+and produce this SVG image136+137+![XXX missing local SVG](./utf8/_README/hello-world.motley.svg)138+139+### Dark-mode support140+Note the `prefers-color-scheme: dark` above.141+This switches all paths (in the sense of "lines") between yellow and blue,142+because yellow is peculiarly hard to distinguish against a light background,143+and its hue-complement blue against a dark background.144+145+To see GoAT use the `@media` switch to dynamically change the drawing color,146+ toggle between dark and light either the GitHub UI or your browser's color scheme.147+148+## Fine-grained CSS rule specification, by diagram markup149+150+GoAT supports styling of `<text>` elements at the granularity of individual words.151+A word, or left-to-right sequence of words, is selected for alternate styling152+ by bracketing it within a pair of characters that you the user choose153+ to reserve for the purpose.154+155+For each such pair, GoAT generates an anchor element `<a>...</a>` to enclose156+ the printing `<text>` elements.157+The anchor element specifies a list of CSS classes, and their effects cascade158+ down to the wrapped `<text>` elements.159+160+Returning to our two ASCII and UTF-8 "Hello, world" examples:161+162+### ASCII163+The command line164+```165+ go run ./cmd/goat/ -io ./ascii/_README/hello-world.styled.txt embed:palette/bold.css embed:style/ascii.css166+```167+when executed at the top of the cloned GoAT source tree, combines a text file containing168+```169+ .---------------.170+` o-->| Hello,‗world‗ +--* `171+ '---------------'172+173+```174+with the CSS content175+```176+.bold {177+ goat-anchor-marks: "··";178+ font-weight: bold;179+}180+.italic {181+ goat-anchor-marks: "``";182+ font-style: italic;183+}184+.underline {185+ goat-anchor-marks: "‗‗"; /* -- resort to UTF-8 */186+ text-decoration: underline;187+}188+189+```190+to produce this SVG image191+192+![XXX missing local SVG](./ascii/_README/hello-world.styled.svg)193+194+For further details, see195+[ascii/README.md](./ascii/README.md)196+197+### UTF-8 BOX characters198+The command line199+```200+ go run ./cmd/goat/ -io ./utf8/_README/hello-world.styled.txt -utf8 embed:palette/bold.css embed:style/utf8.css201+```202+combines a text file containing203+```204+ ╭──────────────┐205+/ ○──►│ Hello,_world_├──● /206+ └──────────────╯207+208+```209+with the CSS content210+```211+.bold {212+ goat-anchor-marks: "**";213+ font-weight: bold;214+}215+.italic {216+ goat-anchor-marks: "//";217+ font-style: italic;218+}219+.underline {220+ goat-anchor-marks: "__";221+ text-decoration: underline;222+}223+.code {224+ goat-anchor-marks: "``";225+ stroke: inherit;226+}227+228+```229+produces this SVG image230+231+![XXX missing local SVG](./utf8/_README/hello-world.styled.svg)232+233+For further details, see234+[utf8/README.md](./utf8/README.md)235+236+237+## CSS property "goat-anchor-href"238+239+GoAT can add web links to text strings within your diagram.240+By appending to the GoAT command line above also a path to a CSS file containing this:241+```242+.underline_href {243+ goat-anchor-marks: "__";244+ goat-anchor-href: "https://www.worldometers.info/";245+}246+247+```248+we get an SVG that looks like this, with a clickable web URL (the link below is however disabled);249+250+![XXX missing local SVG](./utf8/_README/hello-world.href.svg)251+252+The above example exposes an environmental constraint:253+ GitHub-flavored Markdown disallows254+"inline" [\<svg\> elements](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg)255+ so the above SVG is256+["embedded"](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML)257+into the HTML output of the Markdown processor, with the help of an HTML258+[\<img\>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img),259+element, and web browsers disable outgoing links from anchor elements within "embedded" SVG files.260+261+### Comparable Projects262+- [asciitosvg](https://github.com/asciitosvg263+- [Svgbob](https://github.com/ivanceras/svgbob264+265+### Project Tenets266+1. Defaults suitable for simple drawings are provided; all can be overridden by the user.267+2. Incoming data validated promptly; errors fatal immediately; diagnostic messages268+ point to the first incorrect byte.269+3. Dark mode and light mode are co-equal peers.270+4. Utility and ease of integration into existing projects are paramount.271+5. TXT and SVG intelligibility are co-equal in priority.272+6. Column-alignment of TXT not to be sacrificed -- only width-8 characters allowed.273+7. Per-platform support limited to a single widely-available fixed-pitch TXT font.274+8. Compatibility with MarkDeep desired, but not required.275+ory hierarchy [.dir-locals.el](../.dir-locals.el)276+277+### Diagram Input sources278+#### Non-graphical279+- GRAPH::EASY280+- [D2](https://d2lang.com/blog/ascii/)281+- [Diagon](https://diagon.arthursonzogni.com)282+- MermaidJS283+- [Pikchr](https://pikchr.org/)284+#### Graphical editors285+- [AsciiFlow](https://github.com/lewish/asciiflow)286+- [Cascii](https://github.com/casparwylie/cascii-core)287+- Textik288+- specialized sub-modes of general text editors (Emacs, Vim)289+#### Emacs-specific hints290+To preserve your freedom to use some other body font outside of utf8 drawings,291+ by limiting DejaVu Sans Mono (or other "pure-monospace" font) to only utf8 drawings,292+ consider the following techniques for automatically selecting suitable fonts:293+ 1. font-lock custom faces294+ 2. GoAT-compatible face selected for all text-mode files within295+ a directory, with a [`.dir-locals.el`](./.dir-locals.el) file.
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/README.md.tmpl +209 -0
@@ -0,0 +1,209 @@
1+<!-- XX ? Move this file down to .github/, because its contents are GitHub-specific.2+ -->3+<!-- https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-collapsed-sections#creating-a-collapsed-section -->4+5+# GoAT: Go-based Text-Art to SVG refinement6+7+<!-- Github-specific status icon8+9+ Github requires explicitly binding a repository owner's username in the text of a README,10+ implying that a badge in README.md cannot both reflect the status of a test Action after a11+ contributor pushes to his pull request branch, and12+ the run of that same test Action after the upstream owner merges the pull request13+ into his 'main' branch. See:14+15+ https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/adding-a-workflow-status-badge16+17+ GitHub Community forum shows numerous similar issues with badges:18+ https://github.com/orgs/community/discussions/categories/actions?discussions_q=is%3Aopen+category%3AActions++badge.svg19+20+ X ?? Don't use badges. Instead, pay attention to Action result status.21+ XX But how to attest of self-test success to end users?22+ -->23+<!-- ![XXX missing local SVG](https://github.com/{ {.Github_Repository_Owner}}/goat/actions/workflows/test.yml/badge.svg) -->24+25+## What **GoAT** Can Do For You26+27+- From a chunky text-art input, render smooth, multi-colored SVG with embedded links.28+29+- Express your ideas in either of two input formats, subsets of well-known conventions:30+ 1. [ASCII](./ascii/)-based, as popularized by [MarkDeep](http://casual-effects.com/markdeep/)31+ 2. [Unicode UTF-8](./utf8/) [BOX](https://www.unicode.org/charts/PDF/U2500.pdf) characters32+33+- Style graphics globally within your diagram, by specifying CSS files on34+the command line.35+36+- Style individual text strings, by referring to37+individual CSS rules from markup bracketing your text.38+39+## You Will Also Need40+41+#### A fixed-pitch font with 2:1 height:width ratio as presented by your editor and terminal emulator42+Most fixed-pitch or "monospace" Unicode fonts maintain a 2:1 aspect ratio for43+characters in the ASCII range,44+45+ > On Ubuntu, satisfactory fonts are scarce -- perhaps the best is "DejaVu Sans Mono".46+47+#### Editor support48+49+Graphical- or rectangle-oriented text editing capability,50+provided either by special modes within your text editor, or a specialized graphical editor.51+Your best option may depend upon whether you prefer to create diagrams with52+graphics expressed in53+ [ASCII](./ascii/) or [UTF-8](./utf8/)54+ -- see the READMEs for some hints.55+56+## Installation57+58+```59+$ go install github.com/blampe/goat/cmd/goat@latest60+```61+62+## "Hello, world", two ways63+### Markdeep-style ASCII input64+65+```66+{{include "./ascii/_README/hello-world.txt"}}67+```68+![XXX missing local SVG](./ascii/_README/hello-world.svg)69+70+71+### UTF-8 BOX input72+```73+{{include "./utf8/_README/hello-world.txt"}}74+```75+![XXX missing local SVG](./utf8/_README/hello-world.svg)76+77+78+## Styling with CSS79+### Global styling80+Any number of `.css` files may be specified on the command line,81+ for inclusion within the output file's (single) `<svg>` element.82+To style all the various SVG drawing elements emitted by GoAT,83+ a CSS file might contain these rules:84+```85+{{include "./css/examples/css-legend.css"}}86+```87+and produce this SVG image88+89+![XXX missing local SVG](./utf8/_README/hello-world.motley.svg)90+91+### Dark-mode support92+Note the `prefers-color-scheme: dark` above.93+This switches all paths (in the sense of "lines") between yellow and blue,94+because yellow is peculiarly hard to distinguish against a light background,95+and its hue-complement blue against a dark background.96+97+To see GoAT use the `@media` switch to dynamically change the drawing color,98+ toggle between dark and light either the GitHub UI or your browser's color scheme.99+100+## Fine-grained CSS rule specification, by diagram markup101+102+GoAT supports styling of `<text>` elements at the granularity of individual words.103+A word, or left-to-right sequence of words, is selected for alternate styling104+ by bracketing it within a pair of characters that you the user choose105+ to reserve for the purpose.106+107+For each such pair, GoAT generates an anchor element `<a>...</a>` to enclose108+ the printing `<text>` elements.109+The anchor element specifies a list of CSS classes, and their effects cascade110+ down to the wrapped `<text>` elements.111+112+Returning to our two ASCII and UTF-8 "Hello, world" examples:113+114+### ASCII115+The command line116+```117+ go run ./cmd/goat/ -io ./ascii/_README/hello-world.styled.txt embed:palette/bold.css embed:style/ascii.css118+```119+when executed at the top of the cloned GoAT source tree, combines a text file containing120+```121+{{include "./ascii/_README/hello-world.styled.txt"}}122+```123+with the CSS content124+```125+{{include "./css/style/ascii.css"}}126+```127+to produce this SVG image128+129+![XXX missing local SVG](./ascii/_README/hello-world.styled.svg)130+131+For further details, see132+[ascii/README.md](./ascii/README.md)133+134+### UTF-8 BOX characters135+The command line136+```137+ go run ./cmd/goat/ -io ./utf8/_README/hello-world.styled.txt -utf8 embed:palette/bold.css embed:style/utf8.css138+```139+combines a text file containing140+```141+{{include "./utf8/_README/hello-world.styled.txt"}}142+```143+with the CSS content144+```145+{{include "./css/style/utf8.css"}}146+```147+produces this SVG image148+149+![XXX missing local SVG](./utf8/_README/hello-world.styled.svg)150+151+For further details, see152+[utf8/README.md](./utf8/README.md)153+154+155+## CSS property "goat-anchor-href"156+157+GoAT can add web links to text strings within your diagram.158+By appending to the GoAT command line above also a path to a CSS file containing this:159+```160+{{include "./utf8/_README/hello-world.href.css"}}161+```162+we get an SVG that looks like this, with a clickable web URL (the link below is however disabled);163+164+![XXX missing local SVG](./utf8/_README/hello-world.href.svg)165+166+The above example exposes an environmental constraint:167+ GitHub-flavored Markdown disallows168+"inline" [\<svg\> elements](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg)169+ so the above SVG is170+["embedded"](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Images_in_HTML)171+into the HTML output of the Markdown processor, with the help of an HTML172+[\<img\>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img),173+element, and web browsers disable outgoing links from anchor elements within "embedded" SVG files.174+175+### Comparable Projects176+- [asciitosvg](https://github.com/asciitosvg177+- [Svgbob](https://github.com/ivanceras/svgbob178+179+### Project Tenets180+1. Defaults suitable for simple drawings are provided; all can be overridden by the user.181+2. Incoming data validated promptly; errors fatal immediately; diagnostic messages182+ point to the first incorrect byte.183+3. Dark mode and light mode are co-equal peers.184+4. Utility and ease of integration into existing projects are paramount.185+5. TXT and SVG intelligibility are co-equal in priority.186+6. Column-alignment of TXT not to be sacrificed -- only width-8 characters allowed.187+7. Per-platform support limited to a single widely-available fixed-pitch TXT font.188+8. Compatibility with MarkDeep desired, but not required.189+ory hierarchy [.dir-locals.el](../.dir-locals.el)190+191+### Diagram Input sources192+#### Non-graphical193+- GRAPH::EASY194+- [D2](https://d2lang.com/blog/ascii/)195+- [Diagon](https://diagon.arthursonzogni.com)196+- MermaidJS197+- [Pikchr](https://pikchr.org/)198+#### Graphical editors199+- [AsciiFlow](https://github.com/lewish/asciiflow)200+- [Cascii](https://github.com/casparwylie/cascii-core)201+- Textik202+- specialized sub-modes of general text editors (Emacs, Vim)203+#### Emacs-specific hints204+To preserve your freedom to use some other body font outside of utf8 drawings,205+ by limiting DejaVu Sans Mono (or other "pure-monospace" font) to only utf8 drawings,206+ consider the following techniques for automatically selecting suitable fonts:207+ 1. font-lock custom faces208+ 2. GoAT-compatible face selected for all text-mode files within209+ a directory, with a [`.dir-locals.el`](./.dir-locals.el) file.
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/ascii/README.md +125 -0
@@ -0,0 +1,125 @@
1+# ASCII input mode2+3+<!-- https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-collapsed-sections#creating-a-collapsed-section -->4+5+GoAT implements a subset, and some extensions, of the ASCII diagram generation function of the browser-side Javascript in [Markdeep](http://casual-effects.com/markdeep/).6+7+If ASCII-mode is selected on the command line (the default),8+ all characters that affect drawing lie within the ASCII set.9+Certain Unicode graphical characters e.g. MIDDLE DOT may however be useful, and10+in at least some on-screen fonts, conform to the constant pixel-dimension standard of the ASCII range.11+12+CJK characters on the other hand are typically wider than 2:1.13+Non-standard width characters are not in general composable on the left-right axis within a plain-text14+drawing, because the remainder of the line of text to their right is pushed out of alignment15+with rows above and below.16+17+18+## Example Graphics19+20+Here are some snippets of21+GoAT-formatted text22+and the SVG each can generate.23+The SVG you see below was linked to by24+inline Markdown image references25+([howto](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#images),26+[spec](https://github.github.com/gfm/#images)) from27+GoAT's [README.md](README.md), then finally rendered to HTML ```<img>``` elements by Github's Markdown processor28+29+30+### Trees -- mid-range color value31+Setting a foreground color in the middle of the possible range of value or luminance32+is one way to work around a limitation of certain browsers e.g. Safari.33+Safari does not support inheritance of34+the [`color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)35+CSS property by36+`goat`'s output `<svg>` element37+from within an enclosing `<img>` element such as are generated by Markdown.38+39+40+![XXX missing local SVG](./_README/trees.mid-blue.svg)41+42+### Overlaps43+<!-- XX If GFM could merely "#include" other input files upon HTML generation,44+ following the usual mode of included images, could templating perhaps be dropped?45+46+ E.g.47+ [CANNOT #include ](./examples/overlaps.txt)48+ -->49+```50+51+ .-. .-. .-. .-. .-. .-.52+ | | | | | | | | | | | |53+ .---------. .--+---+--. .--+---+--. .--| |--. .--+ +--. .------|--.54+ | | | | | | | | | | | | | | | | | |55+ '---------' '--+---+--' '--+---+--' '--| |--' '--+ +--' '--|------'56+ | | | | | | | | | | | |57+ '-' '-' '-' '-' '-' '-'58+59+```60+---61+![XXX missing local SVG](./examples/overlaps.svg)62+63+### Line Decorations64+```65+ ________ o * * .--------------.66+ *---+--. | | o o | ^ \ / | .----------. |67+ | | '--* -+- | | v / \ / | | <------. | |68+ | '-----> .---(---' --->*<--- / .+->*<--o----' | | | | |69+ <--' ^ ^ | | | | | ^ \ | '--------' | |70+ \/ *-----' o |<----->| '-----' |__| v '------------' |71+ /\ *---------------'72+73+```74+---75+![XXX missing local SVG](./examples/line-decorations.svg)76+77+### Dot Grids78+```79+80+ o o o o o * * * * * * * o o * o o o * * * o o o · * · · · · · ·81+ o o o o o * * * * * o o o o * o o o o * * * * * o * * · * * · · · · · ·82+ o o o o o * * * * * o * o o o o o o o o * * * * * o o o o o · o · · o · · * * ·83+ o o o o o * * * * * o * o o o o o o o * * * * o * o o · · · · o · · * ·84+ o o o o o * * * * * * * * * o o o o * * * o * o · · · · · · · *85+86+87+```88+Note that '·' above is not ASCII, but rather Unicode, the MIDDLE DOT character, encoded with UTF-8.89+![XXX missing local SVG](./examples/dot-grids.svg)90+91+### "Complicated"92+```93++-------------------+ ^ .---.94+| ‗A Box‗ |__.--.__ __.--> | .-. | |95+| | '--' v | * |<--- | |96++-------------------+ '-' | |97+ `Round` *---(-. |98+ .-----------------. .-------. .----------. .-------. | | |99+ | Mixed Rounded | | | / Diagonals \ | | | | | |100+ | & Square Corners | '--. .--' / \ |---+---| '-)-' .--------.101+ '--+------------+-' .--. | '-------+--------' | | | | / Search /102+ | | | | '---. | '-------' | '-+------'103+ |<---------->| | | | v Interior | ^104+ ' <---' '----' .-----------. ---. .--- v |105+ .------------------. Diag line | .-------. +---. \ / . |106+ | if (a > b) +---. .--->| | | | |`Curved line` \ / / \ |107+ | obj->fcn() | \ / | '-------' |<--' + / \ |108+ '------------------' '--' '--+--------' .--. .--. | .-. +Done?+-'109+ .---+-----. | ^ |\ | | /| .--+ | | \ /110+ | | | Join \|/ | | `Curved` | \| |/ | | \ | \ /111+ | | +----> o --o-- '-' Vertical '--' '--' '-- '--' + .---.112+ <--+---+-----' | /|\ | | 3 |113+ v not:line 'quotes' .-' '---'114+ .-. .---+--------. / A || B ·bold· | ^115+ | | | Not a dot | <---+---<-- A dash--is not a line v |116+ '-' '---------+--' / Nor/is this. ---117+118+```119+---120+![XXX missing local SVG](./examples/complicated.svg)121+122+### "Complicated" redux, styled in motley123+![XXX missing local SVG](./_README/complicated.svg)124+125+### More [examples](./examples)
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/ascii/README.md.tmpl +79 -0
@@ -0,0 +1,79 @@
1+# ASCII input mode2+3+<!-- https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-collapsed-sections#creating-a-collapsed-section -->4+5+GoAT implements a subset, and some extensions, of the ASCII diagram generation function of the browser-side Javascript in [Markdeep](http://casual-effects.com/markdeep/).6+7+If ASCII-mode is selected on the command line (the default),8+ all characters that affect drawing lie within the ASCII set.9+Certain Unicode graphical characters e.g. MIDDLE DOT may however be useful, and10+in at least some on-screen fonts, conform to the constant pixel-dimension standard of the ASCII range.11+12+CJK characters on the other hand are typically wider than 2:1.13+Non-standard width characters are not in general composable on the left-right axis within a plain-text14+drawing, because the remainder of the line of text to their right is pushed out of alignment15+with rows above and below.16+17+18+## Example Graphics19+20+Here are some snippets of21+GoAT-formatted text22+and the SVG each can generate.23+The SVG you see below was linked to by24+inline Markdown image references25+([howto](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#images),26+[spec](https://github.github.com/gfm/#images)) from27+GoAT's [README.md](README.md), then finally rendered to HTML ```<img>``` elements by Github's Markdown processor28+29+30+### Trees -- mid-range color value31+Setting a foreground color in the middle of the possible range of value or luminance32+is one way to work around a limitation of certain browsers e.g. Safari.33+Safari does not support inheritance of34+the [`color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)35+CSS property by36+`goat`'s output `<svg>` element37+from within an enclosing `<img>` element such as are generated by Markdown.38+39+40+![XXX missing local SVG](./_README/trees.mid-blue.svg)41+42+### Overlaps43+<!-- XX If GFM could merely "#include" other input files upon HTML generation,44+ following the usual mode of included images, could templating perhaps be dropped?45+46+ E.g.47+ [CANNOT #include ]({{.examples_DIR}}/overlaps.txt)48+ -->49+```50+{{.examples_overlaps_txt}}51+```52+---53+![XXX missing local SVG]({{.examples_DIR}}/overlaps.svg)54+55+### Line Decorations56+```57+{{.examples_line_decorations_txt}}58+```59+---60+![XXX missing local SVG]({{.examples_DIR}}/line-decorations.svg)61+62+### Dot Grids63+```64+{{.examples_dot_grids_txt}}65+```66+Note that '·' above is not ASCII, but rather Unicode, the MIDDLE DOT character, encoded with UTF-8.67+![XXX missing local SVG]({{.examples_DIR}}/dot-grids.svg)68+69+### "Complicated"70+```71+{{.examples_complicated_txt}}72+```73+---74+![XXX missing local SVG]({{.examples_DIR}}/complicated.svg)75+76+### "Complicated" redux, styled in motley77+![XXX missing local SVG](./_README/complicated.svg)78+79+### More [examples](./examples)
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/ascii/a_drawable.go +314 -0
@@ -0,0 +1,314 @@
1+package ascii2+3+import (4+ "io"5+ // "log"6+7+8+ "github.com/blampe/goat/internal"9+ "github.com/blampe/goat/svg"10+)11+12+// WriteSVGBody writes the entire content of a Canvas out to a stream in SVG format.13+// XX Produces a complete <g ...>...</g> expression -- rename accordingly?14+func (c *Canvas) WriteSVGBody(dst io.Writer, config *svg.Config) {15+16+ internal.MustFPrintf(dst, " <g id='%s'>\n", "lines")17+ for _, l := range c.lines() {18+ l.Draw(dst)19+ }20+21+ internal.MustFPrintf(dst, " </g>\n <g id='%s'>\n", "triangles")22+ for _, tI := range c.triangles() {23+ tI.Draw(dst)24+ }25+26+ internal.MustFPrintf(dst, " </g>\n <g id='%s'>\n", "roundedCorners")27+ for _, rc := range c.roundedCorners() {28+ const cornerRadius = svg.CellHeight29+ drawArc(dst, rc, cornerRadius)30+ }31+32+ internal.MustFPrintf(dst, " </g>\n <g id='%s'>\n", "circles")33+ for _, ci := range c.circles() {34+ const circleRadius = 635+ ci.Draw(dst, circleRadius)36+ }37+38+ internal.MustFPrintf(dst, " </g>\n <g id='%s'>\n", "bridges")39+ for _, bI := range c.bridges() {40+ bI.Draw(dst)41+ }42+43+ internal.MustFPrintf(dst, " </g>\n <g id='%s'>\n", "text")44+ svg.Writetext(dst, config, c)45+46+ internal.MustFPrintf(dst, "%s", "</g>\n")47+}48+49+// triangles detects intended triangles -- typically at the end of an intended line --50+// and returns a representational slice composed of the two types Triangle and line.51+func (c *Canvas) triangles() (triangles []svg.Drawable) {52+ o := svg.O_NONE53+54+ for _, idx := range svg.UpDownMinor(c.Width, c.Height) {55+ needsNudging := false56+ start := idx57+58+ r := c.RuneAt(idx)59+60+ if !isTriangle(r) {61+ continue62+ }63+64+ // Identify orientation and nudge the triangle to touch any65+ // adjacent walls.66+ switch r {67+ case '^':68+ o = svg.O_N69+ // ^ and ^70+ // / \71+ if c.RuneAt(start.SWest()) == '/' {72+ o = svg.O_NE73+ } else if c.RuneAt(start.SEast()) == '\\' {74+ o = svg.O_NW75+ }76+ case 'v':77+ if verticalRunes.Contains(c.RuneAt(start.North())) {78+ // |79+ // v80+ o = svg.O_S81+ } else if c.RuneAt(start.NEast()) == '/' {82+ // /83+ // v84+ o = svg.O_SW85+ } else if c.RuneAt(start.NWest()) == '\\' {86+ // \87+ // v88+ o = svg.O_SE89+ } else {90+ // Conclusion: Meant as a text string 'v', not a triangle91+ //panic("Not sufficient to fix all 'v' troubles.")92+ // continue XX Already committed to non-text output for this string?93+ o = svg.O_S94+ }95+ case '<':96+ o = svg.O_W97+ case '>':98+ o = svg.O_E99+ }100+101+ // Determine if we need to snap the triangle to something and, if so,102+ // draw a tail if we need to.103+ switch o {104+ case svg.O_N:105+ r := c.RuneAt(start.North())106+ if r == '-' || isJoint(r) && !isDot(r) {107+ needsNudging = true108+ triangles = append(triangles, newHalfStep(start, svg.O_N))109+ }110+ case svg.O_NW:111+ r := c.RuneAt(start.NWest())112+ // Need to draw a tail.113+ if r == '-' || isJoint(r) && !isDot(r) {114+ needsNudging = true115+ triangles = append(116+ triangles,117+ line{118+ Start: start.NWest(),119+ Stop: start,120+ Orientation: svg.O_SE,121+ },122+ )123+ }124+ case svg.O_NE:125+ r := c.RuneAt(start.NEast())126+ if r == '-' || isJoint(r) && !isDot(r) {127+ needsNudging = true128+ triangles = append(129+ triangles,130+ line{131+ Start: start,132+ Stop: start.NEast(),133+ Orientation: svg.O_NE,134+ },135+ )136+ }137+ case svg.O_S:138+ r := c.RuneAt(start.South())139+ if r == '-' || isJoint(r) && !isDot(r) {140+ needsNudging = true141+ triangles = append(triangles, newHalfStep(start, svg.O_S))142+ }143+ case svg.O_SE:144+ r := c.RuneAt(start.SEast())145+ if r == '-' || isJoint(r) && !isDot(r) {146+ needsNudging = true147+ triangles = append(148+ triangles,149+ line{150+ Start: start,151+ Stop: start.SEast(),152+ Orientation: svg.O_SE,153+ },154+ )155+ }156+ case svg.O_SW:157+ r := c.RuneAt(start.SWest())158+ if r == '-' || isJoint(r) && !isDot(r) {159+ needsNudging = true160+ triangles = append(161+ triangles,162+ line{163+ Start: start.SWest(),164+ Stop: start,165+ Orientation: svg.O_NE,166+ },167+ )168+ }169+ case svg.O_W:170+ r := c.RuneAt(start.West())171+ if isDot(r) {172+ needsNudging = true173+ }174+ case svg.O_E:175+ r := c.RuneAt(start.East())176+ if isDot(r) {177+ needsNudging = true178+ }179+ }180+181+ triangles = append(182+ triangles,183+ svg.Triangle{184+ Start: start,185+ Orientation: o,186+ NeedsNudging: needsNudging,187+ },188+ )189+ }190+ return191+}192+193+// circles returns a slice of all 'o' and '*' characters not considered text.194+func (c *Canvas) circles() (circles []svg.Circle) {195+ for _, idx := range svg.UpDownMinor(c.Width, c.Height) {196+ // TODO INCOMING197+ if c.RuneAt(idx) == 'o' {198+ circles = append(circles, svg.Circle{Start: idx})199+ } else if c.RuneAt(idx) == '*' {200+ circles = append(circles, svg.Circle{Start: idx, Bold: true})201+ }202+ }203+ return204+}205+206+// roundedCorners returns a slice of all curvy corners in the diagram.207+func (c *Canvas) roundedCorners() (corners []svg.RoundedCorner) {208+ for _, idx := range svg.LeftRightMinor(c.Width, c.Height) {209+ if o := c.isroundedCorner(idx); o != svg.O_NONE {210+ corners = append(211+ corners,212+ svg.RoundedCorner{Start: idx, Orientation: o},213+ )214+ }215+ }216+ return217+}218+219+// For . and ' characters this will return a non-svg.O_NONE orientation if the220+// contents of adjacent characters satisfy the rules to allow a rounded corner,221+// in particular that aa circular arc can be drawn to connect a vertical edge222+// with a horizontal edge.223+func (c *Canvas) isroundedCorner(i svg.XyIndex) svg.Orientation {224+ r := c.RuneAt(i)225+226+ if !isJoint(r) {227+ return svg.O_NONE228+ }229+230+ left := i.West()231+ right := i.East()232+ lowerLeft := i.SWest()233+ lowerRight := i.SEast()234+ upperLeft := i.NWest()235+ upperRight := i.NEast()236+237+ opensUp := r == '\'' || r == '+'238+ opensDown := r == '.' || r == '+'239+240+ dashRight := c.RuneAt(right) == '-' || c.RuneAt(right) == '+' || c.RuneAt(right) == '_' || c.RuneAt(upperRight) == '_'241+ dashLeft := c.RuneAt(left) == '-' || c.RuneAt(left) == '+' || c.RuneAt(left) == '_' || c.RuneAt(upperLeft) == '_'242+243+ isVerticalSegment := func(i svg.XyIndex) bool {244+ r := c.RuneAt(i)245+ return verticalRunes.Contains(r) || r == '+' || r == ')' || r == '(' || isDot(r)246+ }247+248+ // .- or .-249+ // | +250+ if opensDown && dashRight && isVerticalSegment(lowerLeft) {251+ return svg.O_NW252+ }253+254+ // -. or -. or -. or _. or -.255+ // | + ) ) o256+ if opensDown && dashLeft && isVerticalSegment(lowerRight) {257+ return svg.O_NE258+ }259+260+ // | or + or | or + or + or_ )261+ // -' -' +' +' ++ '262+ if opensUp && dashLeft && isVerticalSegment(upperRight) {263+ return svg.O_SE264+ }265+266+ // | or +267+ // '- '-268+ if opensUp && dashRight && isVerticalSegment(upperLeft) {269+ return svg.O_SW270+ }271+272+ return svg.O_NONE273+}274+275+// bridges returns a slice of all bridges, "-)-" or "-(-", composed as a sequence of276+// either type bridge or type line.277+func (c *Canvas) bridges() (bridges []svg.Drawable) {278+ for _, idx := range svg.LeftRightMinor(c.Width, c.Height) {279+ if o := c.isBridge(idx); o != svg.O_NONE {280+ bridges = append(281+ bridges,282+ newHalfStep(idx.North(), svg.O_S),283+ newHalfStep(idx.South(), svg.O_N),284+ svg.Bridge{285+ Start: idx,286+ Orientation: o,287+ },288+ )289+ }290+ }291+ return292+}293+294+// -)- or -(- or295+func (c *Canvas) isBridge(i svg.XyIndex) svg.Orientation {296+ r := c.RuneAt(i)297+298+ left := c.RuneAt(i.West())299+ right := c.RuneAt(i.East())300+301+ if left != '-' || right != '-' {302+ return svg.O_NONE303+ }304+305+ if r == '(' {306+ return svg.O_W307+ }308+309+ if r == ')' {310+ return svg.O_E311+ }312+313+ return svg.O_NONE314+}
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/ascii/a_line.go +535 -0
@@ -0,0 +1,535 @@
1+package ascii2+3+import (4+ "io"5+6+ "github.com/blampe/goat"7+ "github.com/blampe/goat/svg"8+)9+10+// line represents a straight segment between two points 'start' and 'stop', where11+// 'start' is either lesser in X (north-east, east, south-east), or12+// equal in X and lesser in Y (south).13+type line struct {14+ // X These do not necessarily point to 'segment' characters e.g. '-' or '|' -- they15+ // commonly point to adjoining junction ('passThrough') characters e.g. '+'.16+ Start svg.XyIndex17+ Stop svg.XyIndex18+ startRune rune19+ stopRune rune20+21+ // dashed bool22+ NeedsNudgingDown bool // used for horizontal lines defined with '_'23+24+ // all of these are used to ~extend~ lines so as to complete connection at a corner of a box25+ NeedsNudgingLeft bool // X may be combined with NeedsTinyNudgingLeft26+ NeedsNudgingRight bool // X may be combined with NeedsTinyNudgingRight27+ NeedsTinyNudgingLeft bool28+ NeedsTinyNudgingRight bool29+30+ // This is a line segment all by itself.31+ // ^^^^^^^^^^^^^ XX What does this mean?32+ // No terminators, at neither end? AXXt one end only?33+ // Centers the segment around the midline.34+ Lonely bool35+36+ // N or S. Only useful for half steps - chops off this half of the line.37+ Chop svg.Orientation38+39+ // X-major, Y-minor. Therefore, always one of the compass points NE, E, SE, S.40+ Orientation svg.Orientation41+42+ State lineState // Value is Unstarted or Started43+}44+45+const (46+ W = svg.CellWidth47+ H = svg.CellHeight48+)49+50+type lineState int51+52+const (53+ unstarted lineState = iota54+ started55+)56+57+func (l *line) started() bool {58+ return l.State == started59+}60+61+func (c *Canvas) SetStart(l *line, i svg.XyIndex) {62+ if l.State == unstarted {63+ l.Start = i64+ l.startRune = c.RuneAt(i)65+ l.State = started66+ }67+}68+69+func (c *Canvas) SetStop(l *line, i svg.XyIndex) {70+ if l.State == started {71+ l.Stop = i72+ l.stopRune = c.RuneAt(i)73+ }74+}75+76+func (l *line) GoesSomewhere() bool {77+ return l.Start != l.Stop78+}79+80+func (l *line) horizontal() bool {81+ return l.Orientation == svg.O_E || l.Orientation == svg.O_W82+}83+84+func (l *line) vertical() bool {85+ return l.Orientation == svg.O_N || l.Orientation == svg.O_S86+}87+88+func (l *line) diagonal() bool {89+ return l.Orientation == svg.O_NE || l.Orientation == svg.O_SE || l.Orientation == svg.O_SW || l.Orientation == svg.O_NW90+}91+92+93+// lines returns a slice of all line Drawables,94+// in all possible directional orientations, that it can recognize in Canvas.Data[].95+func (c *Canvas) lines() (lines []line) {96+ horizontalMidlines := c.getlinesForSegment('-')97+ diagUplines := c.getlinesForSegment('/')98+ for i, l := range diagUplines {99+ // /_100+ if c.RuneAt(l.Start.East()) == '_' {101+ diagUplines[i].NeedsTinyNudgingLeft = true102+ }103+104+ // _105+ // /106+ if c.RuneAt(l.Stop.North()) == '_' {107+ diagUplines[i].NeedsTinyNudgingRight = true108+ }109+110+ // _111+ // /112+ if !l.Lonely && c.RuneAt(l.Stop.NEast()) == '_' {113+ diagUplines[i].NeedsTinyNudgingRight = true114+ }115+116+ // _/117+ if !l.Lonely && c.RuneAt(l.Start.West()) == '_' {118+ diagUplines[i].NeedsTinyNudgingLeft = true119+ }120+121+ // \122+ // /123+ if !l.Lonely && c.RuneAt(l.Stop.North()) == '\\' {124+ diagUplines[i].NeedsTinyNudgingRight = true125+ }126+127+ // /128+ // \129+ if !l.Lonely && c.RuneAt(l.Start.South()) == '\\' {130+ diagUplines[i].NeedsTinyNudgingLeft = true131+ }132+ }133+134+ diagDownlines := c.getlinesForSegment('\\')135+ for i, l := range diagDownlines {136+ // _\137+ if c.RuneAt(l.Stop.West()) == '_' {138+ diagDownlines[i].NeedsTinyNudgingRight = true139+ }140+141+ // _142+ // \143+ if c.RuneAt(l.Start.North()) == '_' {144+ diagDownlines[i].NeedsTinyNudgingLeft = true145+ }146+147+ // _148+ // \149+ if !l.Lonely && c.RuneAt(l.Start.NWest()) == '_' {150+ diagDownlines[i].NeedsTinyNudgingLeft = true151+ }152+153+ // \_154+ if !l.Lonely && c.RuneAt(l.Stop.East()) == '_' {155+ diagDownlines[i].NeedsTinyNudgingRight = true156+ }157+158+ // \159+ // /160+ if !l.Lonely && c.RuneAt(l.Stop.South()) == '/' {161+ diagDownlines[i].NeedsTinyNudgingRight = true162+ }163+164+ // /165+ // \166+ if !l.Lonely && c.RuneAt(l.Start.North()) == '/' {167+ diagDownlines[i].NeedsTinyNudgingLeft = true168+ }169+ }170+171+ horizontalBaselines := c.getlinesForSegment('_')172+ for i, l := range horizontalBaselines {173+ // TODO: make this nudge an orientation174+ horizontalBaselines[i].NeedsNudgingDown = true175+176+ // _177+ // _| | XX example wrong?178+ if c.RuneAt(l.Stop.SEast()) == '|' || c.RuneAt(l.Stop.NEast()) == '|' {179+ horizontalBaselines[i].NeedsNudgingRight = true180+ }181+182+ // _183+ // | _| XX example wrong?184+ if c.RuneAt(l.Start.SWest()) == '|' || c.RuneAt(l.Start.NWest()) == '|' {185+ horizontalBaselines[i].NeedsNudgingLeft = true186+ }187+188+ // _189+ // _/ \ X example appears right190+ if c.RuneAt(l.Stop.East()) == '/' || c.RuneAt(l.Stop.SEast()) == '\\' {191+ horizontalBaselines[i].NeedsTinyNudgingRight = true192+ }193+194+ // _195+ // \_ / X example appears right196+ if c.RuneAt(l.Start.West()) == '\\' || c.RuneAt(l.Start.SWest()) == '/' {197+ horizontalBaselines[i].NeedsTinyNudgingLeft = true198+ }199+200+ // _\201+ if c.RuneAt(l.Stop.East()) == '\\' {202+ horizontalBaselines[i].NeedsNudgingRight = true203+ horizontalBaselines[i].NeedsTinyNudgingRight = true204+ }205+206+ //207+ // /_208+ if c.RuneAt(l.Start.West()) == '/' {209+ horizontalBaselines[i].NeedsNudgingLeft = true210+ horizontalBaselines[i].NeedsTinyNudgingLeft = true211+ }212+ // _213+ // /214+ if c.RuneAt(l.Stop.South()) == '/' {215+ horizontalBaselines[i].NeedsTinyNudgingRight = true216+ }217+218+ // _219+ // \220+ if c.RuneAt(l.Start.South()) == '\\' {221+ horizontalBaselines[i].NeedsTinyNudgingLeft = true222+ }223+224+ // _225+ // '226+ if c.RuneAt(l.Start.SWest()) == '\'' {227+ horizontalBaselines[i].NeedsNudgingLeft = true228+ }229+230+ // _231+ // '232+ if c.RuneAt(l.Stop.SEast()) == '\'' {233+ horizontalBaselines[i].NeedsNudgingRight = true234+ }235+ }236+237+ verticallines := c.getlinesForSegment('|')238+239+ lines = append(lines, horizontalMidlines...)240+ lines = append(lines, horizontalBaselines...)241+ lines = append(lines, verticallines...)242+ lines = append(lines, diagUplines...)243+ lines = append(lines, diagDownlines...)244+ lines = append(lines, c.HalfSteps()...) // vertical, only245+246+ return247+}248+249+func newHalfStep(i svg.XyIndex, chop svg.Orientation) line {250+ return line{251+ Start: i,252+ Stop: i.South(),253+ Lonely: true,254+ Chop: chop,255+ Orientation: svg.O_S,256+ }257+}258+259+func (c *Canvas) HalfSteps() (lines []line) {260+ for _, idx := range svg.UpDownMinor(c.Width, c.Height) {261+ if o := c.partOfHalfStep(idx); o != svg.O_NONE {262+ lines = append(263+ lines,264+ newHalfStep(idx, o),265+ )266+ }267+ }268+ return269+}270+271+func (c *Canvas) getlinesForSegment(segment rune) []line {272+ var iter svg.CanvasIterator273+ var orientation svg.Orientation274+ passThroughs := goat.CopySet(jointRunes)275+276+ switch segment {277+ case '-':278+ iter = svg.LeftRightMinor279+ orientation = svg.O_E280+ passThroughs.ExtendSet( '<', '>', '(', ')')281+ case '_':282+ iter = svg.LeftRightMinor283+ orientation = svg.O_E284+ passThroughs.ExtendSet( '|')285+ case '|': // VERTICAL LINE286+ iter = svg.UpDownMinor287+ orientation = svg.O_S288+ passThroughs.UnionSet(verticalArrowheadRunes)289+ case '/':290+ iter = svg.DiagUp291+ orientation = svg.O_NE292+ passThroughs.UnionSet(arrowheadRunes)293+ passThroughs.ExtendSet( 'o', '*', '|')294+ case '\\':295+ iter = svg.DiagDown296+ orientation = svg.O_SE297+ passThroughs.UnionSet(arrowheadRunes)298+ passThroughs.ExtendSet( 'o', '*', '|')299+ default:300+ return nil301+ }302+303+ return c.getlines(segment, iter, orientation, passThroughs)304+}305+306+// segment: the primary character expected along a continuing line307+// ci: the order that the loop below traverse locations on the canvas.308+// o: the orientation for this line.309+// passThroughs: characters that will produce a mark that the line segment310+// is allowed to be drawn either through or, in the case of 'o', "underneath" --311+// without terminating the line.312+func (c *Canvas) getlines(313+ segment rune,314+ ci svg.CanvasIterator,315+ o svg.Orientation,316+ passThroughs goat.RuneSet,317+) (lines []line) {318+ // Helper to throw the current line we're tracking on to the slice and319+ // start a new one.320+ snip := func(cl line) line {321+ // Only collect lines that actually go somewhere or are isolated322+ // segments; otherwise, discard what's been collected so far within 'cl'.323+ if cl.GoesSomewhere() {324+ lines = append(lines, cl)325+ }326+327+ return line{Orientation: o}328+ }329+330+ currentline := line{Orientation: o}331+ lastSeenRune := ' '332+333+ // X Purpose of the '+1' overscan is to reset lastSeenRune to ' ' upon wrapping the minor axis.334+ for _, idx := range ci(c.Width+1, c.Height+1) {335+ r := c.RuneAt(idx)336+337+ isSegment := r == segment338+ isPassThrough := passThroughs.Contains(r)339+ isroundedCorner := c.isroundedCorner(idx)340+ isDot := isDot(r)341+ isTriangle := isTriangle(r)342+343+ justPassedThrough := passThroughs.Contains(lastSeenRune)344+345+ shouldKeep := (isSegment || isPassThrough) && isroundedCorner == svg.O_NONE346+347+ // This is an edge case where we have a rounded corner... that's also a348+ // joint... attached to orthogonal line, e.g.:349+ //350+ // '+--351+ // |352+ //353+ // TODO: This also depends on the orientation of the corner and our354+ // line.355+ // NW / NE line can't go with EW/NS lines, vertical is OK though.356+ if isroundedCorner != svg.O_NONE && o != svg.O_E && (c.partOfVerticalline(idx) || c.partOfDiagonalline(idx)) {357+ shouldKeep = true358+ }359+360+ // Don't connect | to > for diagonal lines or )) for horizontal lines.361+ if isPassThrough && justPassedThrough && o != svg.O_S {362+ currentline = snip(currentline)363+ }364+365+ // Don't connect o to o, + to o, etc. This character is a new pass-through366+ // so we still want to respect shouldKeep; we just don't want to draw367+ // the existing line through this cell.368+ if justPassedThrough && (isDot || isTriangle) {369+ currentline = snip(currentline)370+ }371+372+ if o == svg.O_S && (r == '.' || lastSeenRune == '\'') {373+ currentline = snip(currentline)374+ }375+376+ switch currentline.State {377+ case unstarted:378+ if shouldKeep {379+ c.SetStart(&currentline, idx)380+ c.SetStop(&currentline, idx)381+ }382+ case started:383+ if !shouldKeep {384+ // Snip the existing line, don't add the current cell to it385+ // *unless* its a line segment all by itself. If it is, keep a386+ // record that it's an individual segment because we need to387+ // adjust later in the / and \ cases.388+ if !currentline.GoesSomewhere() && lastSeenRune == segment {389+ if !c.partOfroundedCorner(currentline.Start) {390+ c.SetStop(&currentline, idx)391+ currentline.Lonely = true392+ }393+ }394+ currentline = snip(currentline)395+ } else if isPassThrough {396+ // Snip the existing line but include the current pass-through397+ // character because we may be continuing the line.398+ c.SetStop(&currentline, idx)399+ currentline = snip(currentline)400+ c.SetStart(&currentline, idx)401+ c.SetStop(&currentline, idx)402+ } else if shouldKeep {403+ // Keep the line going and extend it by one cell.404+ c.SetStop(&currentline, idx)405+ }406+ }407+408+ lastSeenRune = r409+ }410+ return411+}412+413+414+// Draw a straight line as an SVG path.415+func (l line) Draw(out io.Writer) {416+ start := l.Start.AsPixel()417+ stop := l.Stop.AsPixel()418+419+ // For cases when a vertical line hits a perpendicular like this:420+ //421+ // | |422+ // | or v423+ // --- ---424+ //425+ // We need to nudge the vertical line half a vertical cell in the426+ // appropriate direction in order to meet up cleanly with the midline of427+ // the cell next to it.428+429+ // A diagonal segment all by itself needs to be shifted slightly to line430+ // up with _ baselines:431+ // _432+ // \_433+ //434+ // TODO make this a method on line to return accurate pixel435+ if l.Lonely {436+ switch l.Orientation {437+ case svg.O_NE:438+ start.X -= W/2439+ stop.X -= W/2440+ start.Y += H/2441+ stop.Y += H/2442+ case svg.O_SE:443+ start.X -= W/2444+ stop.X -= W/2445+ start.Y -= H/2446+ stop.Y -= H/2447+ case svg.O_S:448+ start.Y -= H/2449+ stop.Y -= H/2450+ }451+452+ // Half steps453+ switch l.Chop {454+ case svg.O_N:455+ stop.Y -= H/2456+ case svg.O_S:457+ start.Y += H/2458+ }459+ }460+461+ if l.NeedsNudgingDown {462+ stop.Y += H/2463+ if l.horizontal() {464+ start.Y += H/2465+ }466+ }467+468+ if l.NeedsNudgingLeft {469+ start.X -= W470+ }471+472+ if l.NeedsNudgingRight {473+ stop.X += W474+ }475+476+ if l.NeedsTinyNudgingLeft {477+ start.X -= W/2478+ if l.Orientation == svg.O_NE {479+ start.Y += H/2480+ } else if l.Orientation == svg.O_SE {481+ start.Y -= H/2482+ }483+ }484+485+ if l.NeedsTinyNudgingRight {486+ stop.X += W/2487+ if l.Orientation == svg.O_NE {488+ stop.Y -= H/2489+ } else if l.Orientation == svg.O_SE {490+ stop.Y += H/2491+ }492+ }493+494+ // If either end is a hollow circle, back off drawing to the edge of the circle,495+ // rather extending as usual to center of the cell.496+ const (497+ ORTHO = 6498+ DIAG_X = 3 // XX By eye, '3' is a bit too much'; '2' is not enough.499+ DIAG_Y = 5500+ )501+ if (l.startRune == 'o') { // XX ? Easily generalized to needs of BOX chars?502+ switch l.Orientation {503+ case svg.O_NE:504+ start.X += DIAG_X505+ start.Y -= DIAG_Y506+ case svg.O_E:507+ start.X += ORTHO508+ case svg.O_SE:509+ start.X += DIAG_X510+ start.Y += DIAG_Y511+ case svg.O_S:512+ start.Y += ORTHO513+ default:514+ panic("impossible orientation")515+ }516+ }517+ // X 'stopRune' case differs from 'startRune' only by inversion of the arithmetic signs.518+ if (l.stopRune == 'o') { // XX ? Easily generalized to needs of BOX chars?519+ switch l.Orientation {520+ case svg.O_NE:521+ stop.X -= DIAG_X522+ stop.Y += DIAG_Y523+ case svg.O_E:524+ stop.X -= ORTHO525+ case svg.O_SE:526+ stop.X -= DIAG_X527+ stop.Y -= DIAG_Y528+ case svg.O_S:529+ stop.Y -= ORTHO530+ default:531+ panic("impossible orientation")532+ }533+ }534+ svg.WritePolyline(out, start, stop)535+}
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/ascii/a_roundedcorner.go +26 -0
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/ascii/ascii.go +327 -0
@@ -0,0 +1,327 @@
1+/*2+ Format ASCII─art into SVG image files.3+*/4+package ascii5+6+import (7+ "io"8+9+ "github.com/blampe/goat"10+ "github.com/blampe/goat/svg"11+)12+13+// X Differs from utf8.Canvas by the methods bound to it (see below).14+type Canvas struct {15+ svg.CanvasCommon16+}17+18+func (ac *Canvas) GetCommon() *svg.CanvasCommon {19+ return &ac.CanvasCommon20+}21+22+func NewCanvas(config *svg.Config, in io.Reader) svg.AbstractCanvas {23+ c := Canvas{24+ CanvasCommon: svg.NewCanvasCommon(config, in),25+ }26+ // Fill the 'TextRunes' map, with runes removed from 'data', according to c.ShouldMoveToTextRunes()27+ svg.MoveToText(&c)28+ return &c29+}30+31+var verticalRunes = goat.MakeRuneSet(32+ '|', // VERTICAL LINE33+)34+var horizontalRunes = goat.MakeRuneSet(35+ '-', // HYPHEN36+)37+38+// XX A/K/A "triangles"39+var verticalArrowheadRunes = goat.MakeRuneSet(40+ 'v',41+ '^',42+)43+var horizontalArrowheadRunes = goat.MakeRuneSet(44+ '<',45+ '>',46+)47+var arrowheadRunes = goat.UnionSets(48+ verticalArrowheadRunes,49+ horizontalArrowheadRunes,50+)51+52+// Characters where more than one line segment can come together.53+var jointRunes = goat.MakeRuneSet(54+ '.', // possible ... top corner of a 90 degree angle, or curve55+ '\'', // possible ... bottom corner of a 90 degree angle, or curve56+ '+',57+ '*',58+ 'o',59+ )60+61+// XX 'Reserved' is not a faithful abstraction; the functional meaning is more62+// like "possibly graphical, depending on neighbors".63+// X All but ' ' below might be well called "pathRunes".64+var ReservedSet = goat.UnionSets(65+ jointRunes, goat.MakeRuneSet(66+ '-',67+ '_',68+ '|',69+ 'v',70+ '^',71+ '>',72+ '<',73+ '/',74+ '\\',75+ ')',76+ '(',77+ ' ', // X SPACE is "reserved"78+ ))79+80+// "wide" implies that characters to left and right must not be text, if the "wide" character is81+// to be rendered as graphics82+var wideSVGSet = goat.MakeRuneSet(83+ // double-wide84+ 'o',85+ '*',86+87+ // "single-wide"88+ 'v', // X Input containing " over " needs to be considered text.89+// '>', // Uncommenting would get 'o<' and '>o' wrong. But o> and >o -- never desired to be text?90+// '<', // Uncom...91+ '^',92+ ')',93+ '(',94+ '.', // Dropping this would cause " v. " to be considered graphics.95+)96+97+98+// XX rename 'isSpot()'?99+func isDot(r rune) bool {100+ return r == 'o' || r == '*'101+}102+103+func isJoint(r rune) bool {104+ return jointRunes.Contains(r)105+}106+107+func isTriangle(r rune) bool {108+ return arrowheadRunes.Contains(r)109+}110+111+func (c *Canvas) ShouldMoveToTextRunes(i svg.XyIndex) bool {112+ i_r := c.RuneAt(i)113+ // character := string(i_r); _ = character // for debug114+115+ if _, found := ReservedSet[i_r]; !found {116+ return true117+ }118+119+ // X After this point, deal with problematic cases of 'reserved' characters that120+ // nevertheless must be treated as ordinary text.121+122+ // This is a reserved character with an incoming line (e.g., "|") above or below it,123+ // so call it non-text.124+ if c.haslineAboveOrBelow(i) {125+ return false126+ }127+128+ // Returns true if index 'i' of c.Data[] is to be treated as reserved.129+ // X Characters like 'o' and 'v' need more context (e.g., are other text characters130+ // nearby) to determine whether they're part of a diagram.131+ isReserved := func(i svg.XyIndex) (found bool) {132+ i_r, inData := c.Data[i]133+ if !inData {134+ // lies off left or right end of line, treat as reserved135+ return true136+ }137+ _, found = ReservedSet[i_r]138+ return139+ }140+141+ left := i.West()142+ right := i.East()143+144+ // XX ? Generalize these two tests to regard arbitrarily long rows of reserved chars as145+ // text, if even a single non-reserved character is embedded in the row.146+ // X For test cases, see regression.txt.147+ if !isReserved(left) {148+ return true149+ }150+ if !isReserved(right) {151+ return true152+ }153+154+ crowded := func (l, r svg.XyIndex) bool {155+ return svg.InSet(wideSVGSet, c.Data, l) &&156+ svg.InSet(wideSVGSet, c.Data, r)157+ }158+ if crowded(left, i) || crowded(i, right) {159+ return true160+ }161+162+ // If 'i' has anything other than a space to either left or right, treat as non-text.163+ if !(c.RuneAt(left) == ' ' && c.RuneAt(right) == ' ') {164+ return false165+ }166+167+ // circles surrounded by whitespace shouldn't be shown as text.168+ if i_r == 'o' || i_r == '*' {169+ return false170+ }171+172+ // 'i' is surrounded by whitespace or text on one side or the other, at two cell's distance.173+ // XX Cause of superfluous space-containing <text> elements (two!) at each end of strings?174+ if !isReserved(left.West()) || !isReserved(right.East()) {175+ return true176+ }177+178+ return false179+}180+181+182+// Returns true if it looks like this character belongs to anything besides a183+// horizontal line. This is the context we use to determine if a reserved184+// character is text or not.185+func (c *Canvas) haslineAboveOrBelow(i svg.XyIndex) bool {186+ i_r := c.RuneAt(i)187+188+ switch i_r {189+ case '*', 'o', '+', 'v', '^':190+ return c.partOfDiagonalline(i) || c.partOfVerticalline(i)191+ case '|':192+ return c.partOfVerticalline(i) || c.partOfroundedCorner(i)193+ case '/', '\\':194+ return c.partOfDiagonalline(i)195+ case '-':196+ return c.partOfroundedCorner(i)197+ case '(', ')':198+ return c.partOfVerticalline(i)199+ }200+201+ return false202+}203+204+// Returns true if a "|" segment passes through this index.205+func (c *Canvas) partOfVerticalline(i svg.XyIndex) bool {206+ this := c.RuneAt(i)207+ north := c.RuneAt(i.North())208+ south := c.RuneAt(i.South())209+210+ jointAboveMe := verticalRunes.Contains(this) && isJoint(north)211+212+ if verticalRunes.Contains(north) || jointAboveMe {213+ return true214+ }215+216+ jointBelowMe := verticalRunes.Contains(this) && isJoint(south)217+218+ if verticalRunes.Contains(south) || jointBelowMe {219+ return true220+ }221+222+ return false223+}224+225+// Return true if a "--" segment passes through this index.226+func (c *Canvas) partOfHorizontalline(i svg.XyIndex) bool {227+ return horizontalRunes.Contains(c.RuneAt(i.East())) ||228+ horizontalRunes.Contains(c.RuneAt(i.West()))229+}230+231+func (c *Canvas) partOfDiagonalline(i svg.XyIndex) bool {232+ r := c.RuneAt(i)233+234+ n := c.RuneAt(i.North())235+ s := c.RuneAt(i.South())236+ nw := c.RuneAt(i.NWest())237+ se := c.RuneAt(i.SEast())238+ ne := c.RuneAt(i.NEast())239+ sw := c.RuneAt(i.SWest())240+241+ switch r {242+ // Diagonal segments can be connected to joint or other segments.243+ case '/':244+ return ne == r || sw == r || isJoint(ne) || isJoint(sw) || n == '\\' || s == '\\'245+ case '\\':246+ return nw == r || se == r || isJoint(nw) || isJoint(se) || n == '/' || s == '/'247+248+ // For everything else just check if we have segments next to us.249+ default:250+ return nw == '\\' || ne == '/' || sw == '/' || se == '\\'251+ }252+}253+254+// For "-" and "|" characters returns true if they could be part of a rounded255+// corner.256+func (c *Canvas) partOfroundedCorner(i svg.XyIndex) bool {257+ r := c.RuneAt(i)258+259+ switch r {260+ case '-':261+ dotNext := c.RuneAt(i.West()) == '.' || c.RuneAt(i.East()) == '.'262+ hyphenNext := c.RuneAt(i.West()) == '\'' || c.RuneAt(i.East()) == '\''263+ return dotNext || hyphenNext264+265+ case '|':266+ dotAbove := c.RuneAt(i.NWest()) == '.' || c.RuneAt(i.NEast()) == '.'267+ hyphenBelow := c.RuneAt(i.SWest()) == '\'' || c.RuneAt(i.SEast()) == '\''268+ return dotAbove || hyphenBelow269+ }270+271+ return false272+}273+274+// Should the output for this XyIndex be a half-height line segment, and if so,275+// a top-half 'O_N' or a bottom-half 'O_S'?276+// TODO: Have this take care of all the vertical line nudging.277+func (c *Canvas) partOfHalfStep(i svg.XyIndex) svg.Orientation {278+ r := c.RuneAt(i)279+ if r != '\'' && r != '.' && r != '|' {280+ return svg.O_NONE281+ }282+283+ if c.isroundedCorner(i) != svg.O_NONE {284+ return svg.O_NONE285+ }286+287+ w := c.RuneAt(i.West())288+ e := c.RuneAt(i.East())289+ n := c.RuneAt(i.North())290+ s := c.RuneAt(i.South())291+ nw := c.RuneAt(i.NWest())292+ ne := c.RuneAt(i.NEast())293+294+ switch r {295+ case '\'':296+ // _ _297+ // '- -'298+ if (nw == '_' && e == '-') || (w == '-' && ne == '_') {299+ return svg.O_N300+ }301+ case '.':302+ // _.- -._303+ if (w == '-' && e == '_') || (w == '_' && e == '-') {304+ return svg.O_S305+ }306+ case '|':307+ //// _ _308+ //// | |309+ if n != '|' && (ne == '_' || nw == '_') {310+ return svg.O_N311+ }312+313+ if n == '-' {314+ return svg.O_N315+ }316+317+ //// _| |_318+ if s != '|' && (w == '_' || e == '_') {319+ return svg.O_S320+ }321+322+ if s == '-' {323+ return svg.O_S324+ }325+ }326+ return svg.O_NONE327+}
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/clean.sh +4 -0
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/git-add-generated-files.sh +7 -0
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/internal/files.go +80 -0
@@ -0,0 +1,80 @@
1+package internal2+3+// General library functions useful to non-server, exit-on-error CLI programs.4+5+import (6+ "bytes"7+ "errors"8+ "fmt"9+ "io"10+ "log"11+ "os"12+)13+14+type NamedReadSeeker interface {15+ io.ReadSeeker16+17+ // bytes.Reader lacks such a field18+ Name() string19+}20+21+type NamedBytesReader struct {22+ *bytes.Reader23+24+ // bytes.Reader lacks such a field25+ name string26+}27+28+func NewNamedBytesReader(b []byte, name string) NamedBytesReader {29+ return NamedBytesReader{30+ Reader: bytes.NewReader(b),31+ name: name,32+ }33+}34+35+func (nr NamedBytesReader) Name() string {36+ return nr.name37+}38+39+func MustOpen(filename string) *os.File {40+ if _, err := os.Stat(filename); os.IsNotExist(err) {41+ log.Fatalf("input file not found: %s", filename)42+ }43+ in, err := os.Open(filename)44+ if err != nil {45+ log.Fatal(err)46+ }47+ return in48+}49+50+func MustCreate(filename string) *os.File {51+ out, err := os.Create(filename)52+ if err != nil {53+ log.Fatal(err)54+ }55+ return out56+}57+58+func fileError(err error, fr NamedReadSeeker) {59+ where := fmt.Sprintf("on file name %s", fr.Name())60+ log.Output(2, where + err.Error())61+ log.Fatalln("exiting")62+}63+64+func ReadFileAll(fr NamedReadSeeker) []byte { // XX called only from parseargs.go65+ bS, err := io.ReadAll(fr)66+ if err != nil {67+ fileError(err, fr)68+ }69+ if len(bS) == 0 {70+ fileError(errors.New("attempt to read file of zero length"), fr)71+ }72+ return bS73+}74+75+func MustFPrintf(out io.Writer, format string, args ...interface{}) {76+ _, err := fmt.Fprintf(out, format, args...)77+ if err != nil {78+ log.Fatal(err)79+ }80+}
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/internal/runstate.go +34 -0
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/local-markdown.sh +5 -0
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/markdown_to_html.sh +95 -0
@@ -0,0 +1,95 @@
1+#! /bin/sh2+#3+# For local test of markdown generation, use a standalone Markdown-to-HTML processor.4+#5+# Plan for support of Goat diagrams embedded in Markdown locally,6+# generated for production e.g. upload to a non-GH server7+#8+# WITHOUT help from `go doc`:9+# 1. Go filter preprocesses .md file.10+# 2. Filter extracts and renders Goat diagrams to out-of-line** SVG files.11+# 3. Filter replaces inline ASCII Goat diagram with relative link to SVG.12+#13+# ** SVG content unlike PNG et al. is of course natively ASCII, but GFM apparently14+# nevertheless will not accept it inline, embedded.15+# c.f. https://github.github.com/gfm/#images16+#17+# WITH help from `go doc`:18+# ??? X X https://pkg.go.dev:19+# 1. grabs GH README.md, renders to HTML, discarding SVG.20+# 2. Runs `go doc` and appends its output.21+# ^^^^^^^ XX So generating README.md from package22+# would produce duplication!23+# references:24+# no GitHub activity in many years from creator:25+# github.com/robertkrimen/godocdown/26+#27+# XX Fixed template -- no support for layering in new content e.g. diagrams.28+# github.com/davecheney/godoc2md29+# fork with later fixes, now *deleted* from GitHub UI, but `git pull` still works!30+# github.com/conusion/godoc2md31+32+set -e33+#set -x34+35+# X Deliberately "off" colors, as a reminder that this is no more than a proof rendering.36+svg_color_light_scheme="#210"37+svg_color_dark_scheme="#FFE"38+39+# X Alternatives to 'marked':40+# - https://github.com/yuin/goldmark41+# X X https://github.com/abhinav/goldmark-toc42+# => Respin this very script as a Go CLI app, incorporating above libraries.43+# - https://github.com/remarkjs/remark44+# XX Coded in JS.45+# - https://github.com/remarkjs/remark-toc46+# - https://github.com/gomarkdown/markdown47+# XX No Table-Of-Contents support found.48+# - CLI: https://github.com/gomarkdown/mdtohtml49+50+# See https://github.github.com/gfm/#introduction51+52+# The <!doctype html> below is as advised by:53+# https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Quirks_mode_and_standards_mode54+#55+# The @media query from SVG may be verified in Firefox by switching between Themes56+# "Light" and "Dark" in Firefox's "Add-ons Manager".57+MARKED_PREAMBLE='<!doctype html>58+<!-- CSS values specific to local "marked" CLI processor, not to Github -->59+<style type="text/css">60+ body {61+ background-color: '$svg_color_dark_scheme';62+ color: '$svg_color_light_scheme';63+ font-family: sans;64+ }65+ a {66+ fill: '$svg_color_light_scheme'; /* for cascade downward to <text> */67+ }68+ @media (prefers-color-scheme: dark) {69+ body {70+ background-color: '$svg_color_light_scheme';71+ color: '$svg_color_dark_scheme';72+ }73+ a {74+ fill: '$svg_color_dark_scheme'; /* for cascade downward to <text> */75+ }76+ }77+ h4 {78+ /* Tighten grouping of "func Foo()" et al. with following <code> */79+ margin-block-end: 0.8em;80+ }81+ code {82+ font-size: 11pt;83+ }84+</style>85+'86+87+echo "${MARKED_PREAMBLE}"88+#89+#marked --gfm "$@"90+91+# Run './cmd/goldmark' instead of 'marked'92+# https://github.com/yuin/goldmark93+MOD_FILE=$(go env GOMOD)94+MODULE_ROOT=${MOD_FILE%/*}95+go run "$MODULE_ROOT"/cmd/goldmark <"$1"
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/pre-push.sh +127 -0
@@ -0,0 +1,127 @@
1+#! /bin/sh2+#3+# Run all tests, and all pre-compilation build steps.4+# Certain output files should be committed to the SCM archive.5+#6+# Recall that an end-user eventually installing with 'go get ...' will7+# trigger a compilation from source within the local environment,8+# without help from this file.9+10+set -e11+set -x12+13+usage () {14+ set +x15+ printf "%s\n\n" "$*"16+ printf "usage: %s [-w]\n" "${0##*/}"17+ printf "\t%s\n" ""18+ printf "\t%s\n" "$*"19+ printf "\tTo debug source code for a single failing test case, follow this example:\n"20+# printf "\t\tdlv debug ./cmd/goat --redirect stdin:./utf8/examples/s.txt --redirect stdout:./utf8/examples/s.svg -- -utf8 -sds '#FFF'"21+ printf "\t\tdlv debug ./cmd/goat -- -utf8 -sds '#FFF' -i ./utf8/examples/s.txt -o ./utf8/examples/s.svg\n"22+ exit 123+}24+25+TEST_ARGS=''26+while getopts h:w flag27+do28+ case $flag in29+ h) usage "";;30+ w) ./clean.sh; TEST_ARGS=${TEST_ARGS}" -write";;31+ \?) usage "unrecognized option flag";;32+ esac33+done34+35+DELTA_DIR_NAME="_examples_new" # XX Presently hard-coded into Go source36+37+for dir in ascii utf838+do39+ (40+ # SVG examples/ regeneration.41+ #42+ # If the command fails due to expected changes in SVG output, rerun43+ # this script with "TEST_ARGS=-write".44+ # Results are used as "golden" standard for GitHub-side regression tests,45+ # so in the normal case let there be no arguments here other than control46+ # of logging, conflict with test runs in "test.yml".47+48+ if [ -d $dir/$DELTA_DIR_NAME ]49+ then50+ printf "\n Pre-existing visual diffs found in directory ./%s/%s/\n\n" $dir $DELTA_DIR_NAME51+ fi52+53+ go test ./$dir/. -run . -v ${TEST_ARGS}54+ )55+done56+57+(58+ # A reduced-quality fallback hack for lack of support in browsers other than Firefox for59+ # inheritance of CSS property 'color-scheme' from <img> elements downward to nested60+ # <svg> elements.61+ #62+ # See third, last, row of table here:63+ # https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme#browser_compatibility64+ github_blue_color="#2F81F7"65+ go run ./cmd/goat \66+ -i ./ascii/examples/trees.txt \67+ -svg-color-dark-scheme=${github_blue_color} \68+ -svg-color-light-scheme=${github_blue_color} \69+ >./ascii/_README/trees.mid-blue.svg70+)71+72+go run ./cmd/goat \73+ -i ./ascii/examples/complicated.txt \74+ -o ./ascii/_README/complicated.motley.svg \75+ embed:style/ascii.css embed:examples/css-legend.css76+77+# # X Effective, but does not persist. Required for `go run -cover ...`78+# go env GOCOVERDIR $PWD79+80+# From:81+# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-readmes?page=2#about-readmes82+#83+# If you put your README file in your repository's hidden .github, root, or docs directory,84+# GitHub will recognize and automatically surface your README to repository visitors.85+# ...86+# When your README is viewed on GitHub, any content beyond 500 KiB will be truncated.87+#88+# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-readmes?page=2#relative-links-and-image-paths-in-markdown-files89+#90+# GitHub will automatically transform your relative link or image path based on91+# whatever branch you're currently on, so that the link or path always works. The92+# path of the link will be relative to the current file. Links starting with /93+# will be relative to the repository root. You can use all relative link operands,94+# such as ./ and ../.95+96+# XX Incorporate these commands into the respective README.md.tmpl files,97+# with ./cmd/tmpl-expand "shelling out" to run `goat`?98+99+COLORS="embed:palette/bold.css"100+101+# X For ./README.md:102+go run ./cmd/goat/ -io ./ascii/_README/hello-world.txt103+go run ./cmd/goat/ -utf8 -io ./utf8/_README/hello-world.txt104+go run ./cmd/goat/ -utf8 -i ./utf8/_README/hello-world.txt -o ./utf8/_README/hello-world.motley.svg \105+ ./css/examples/css-legend.css106+go run ./cmd/goat/ -io ./ascii/_README/hello-world.styled.txt $COLORS embed:style/ascii.css107+go run ./cmd/goat/ -utf8 -io ./utf8/_README/hello-world.styled.txt $COLORS embed:style/utf8.css108+#go run ./cmd/goat/ -io ./ascii/_README/hello-world.href.txt ./ascii/_README/hello-world.href.css109+go run ./cmd/goat/ -utf8 -i ./utf8/_README/hello-world.styled.txt -o ./utf8/_README/hello-world.href.svg \110+ $COLORS embed:style/utf8.css \111+ ./utf8/_README/hello-world.href.css112+113+# X For ./ascii/README.md114+go run ./cmd/goat/ -i ./ascii/examples/complicated.txt -o ./ascii/_README/complicated.svg \115+ $COLORS embed:style/ascii.css \116+ embed:examples/css-legend.css117+118+# X For ./utf8/README.md119+go run ./cmd/goat/ -utf8 -i ./utf8/_README/dataflow.utf8.txt -o ./utf8/_README/dataflow.bold.svg \120+ $COLORS embed:style/utf8.css \121+ ./utf8/_README/dataflow.css122+go run ./cmd/goat/ -utf8 -i ./utf8/_README/dataflow.utf8.txt -o ./utf8/_README/dataflow.earth.svg \123+ -sls '#222' -sds '#DDD' embed:palette/earth.css embed:style/utf8.css \124+ ./utf8/_README/dataflow.css125+126+# Execution is not necessary for push to GitHub -- for "proofing" of README appearance, only127+./proof-markdown.sh
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/proof-markdown.sh +57 -0
@@ -0,0 +1,57 @@
1+#! /bin/sh2+#3+# On local dev box, "proof" README files for eventual appearance on GitHub.4+5+set -e6+set -x7+8+PACKAGE_PATH=$(go list .)9+UPSTREAM_OWNER=${PACKAGE_PATH#*/}10+UPSTREAM_OWNER=${UPSTREAM_OWNER%/*}11+12+GITHUB_REPOSITORY_OWNER=$USER13+CURRENT_BRANCH_NAME=$(git-branch --show-current)14+# If the current branch name contains the GitHub username of the owner of the upstream repo,15+# assume the intention is to prepare and push a pull request.16+# X Used for "proofing" deployment to pkg.go.dev, from contributor's account. Anything else?17+if [ $(expr "$CURRENT_BRANCH_NAME" : ".*$UPSTREAM_OWNER") != 0 ]18+then19+ GITHUB_REPOSITORY_OWNER=$UPSTREAM_OWNER20+fi21+22+examples_DIR="./examples" # may be empty, or non-existent23+tmpl_expand () {24+ go run "$TOPDIR"/cmd/tmpl-expand \25+ examples_DIR="$examples_DIR" Github_Repository_Owner=${GITHUB_REPOSITORY_OWNER} \26+ "$@"27+}28+29+# Generate README.md files for pushing to GitHub and,30+# for ease of local proofing only, generate GFM-ish .html31+32+# for dir in ascii utf833+# do34+# $(git-ls-files "$dir/_README/hello-world.txt")35+# $(git-ls-files "$dir/_README/hello-world.styled.txt")36+# done37+38+TOPDIR=$(pwd)39+prefix=README40+41+# printf "Attempting to expand $PWD/%s\n" ${prefix}.md.tmpl42+# tmpl_expand <${prefix}.md.tmpl >${prefix}.md43+#44+# "$TOPDIR"/markdown_to_html.sh ${prefix}.md >_${prefix}.html45+46+for dir in ascii utf8 .47+do48+ (49+ cd $dir50+51+ # X git-ls-files will output paths to files in examples_DIR relative to $dir52+ printf "Attempting to expand $PWD/%s\n" ${prefix}.md.tmpl53+ tmpl_expand <${prefix}.md.tmpl >${prefix}.md $(git-ls-files "$examples_DIR*.txt")54+55+ "$TOPDIR"/markdown_to_html.sh ${prefix}.md >_${prefix}.html56+ )57+done
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/runeset.go +51 -0
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/abstractcanvas.go +264 -0
@@ -0,0 +1,264 @@
1+package svg2+3+import (4+ "bufio"5+ "bytes"6+ "errors"7+ "fmt"8+ "io"9+ "log" // Tests may want to suppress log output.10+ "os"11+ "path/filepath"12+ "strings"13+14+ "github.com/blampe/goat/internal"15+)16+17+// X Uncomment and populate this if more than one character should be reported as illegal.18+//var illegalSet = MakeRuneSet(19+// ' ', // TAB20+//)21+22+// provide callbacks to specific Ascii or Unicode handlers.23+type AbstractCanvas interface {24+ GetCommon() *CanvasCommon25+ WriteSVGBody(io.Writer, *Config)26+ ShouldMoveToTextRunes(XyIndex) bool27+}28+29+// CanvasCommon represents the input diagram, after the first stage of parsing.30+type CanvasCommon struct {31+ // units of cells32+ Width, Height int33+34+ Data map[XyIndex]rune35+ TextRunes map[XyIndex]rune36+}37+38+// 'ac.CanvasCommon().text' will contain begin and end marks for text styling39+//40+// XX Output can be to any io.Writer, therefore no corresponding filename is41+// guaranteed accessible for logging of errors.42+func WriteCanvas(config *Config, ac AbstractCanvas,43+ includeDefaultCSS bool, colorsOnlyBytes string,44+ cssInclude []internal.NamedReadSeeker, // XX pass 'defaultCSS' in this way -- from ALL callers?45+ dst io.Writer) {46+47+ mustPrintS := func(s string) {48+ internal.MustFPrintf(dst, `%s`, s)49+ }50+ mustPrintS(ac.GetCommon().OpenSvgElement())51+52+ // Include this first, so individual properties can be overridden.53+ if includeDefaultCSS {54+ mustPrintS(55+ newStyleElement(56+ "source-independent defaults: shared by ASCII and UTF-8",57+ defaultCSS + colorsOnlyBytes))58+ }59+60+ for _, cssR := range cssInclude {61+ // XX Generalize the read-in to indent four columns, for ease of eyeballing62+ // the eventual SVG output file.63+ bs, err := io.ReadAll(cssR)64+ title := cssR.Name()65+ if err != nil {66+ err = fmt.Errorf("Error in %s: '%v'", title, err)67+ log.Fatal(err)68+ }69+ mustPrintS(70+ newStyleElement(title, string(bs)))71+ }72+73+ mustPrintS(OpenGElement())74+75+ ac.WriteSVGBody(dst, config)76+77+ mustPrintS(CloseGElement())78+ mustPrintS(CloseSvgElement())79+}80+81+// text returns a slice of all text characters not belonging to part of the diagram.82+// Must be stably sorted, to satisfy regression tests.83+func (c *CanvasCommon) text() (textRunes []text) {84+ for _, idx := range LeftRightMinor(c.Width, c.Height) {85+ r, found := c.TextRunes[idx]86+ if !found {87+ continue88+ }89+ if r == 0 {90+ log.Fatalf("found rune with value 0x%x", r)91+ }92+ textRunes = append(textRunes,93+ text{94+ Start: idx,95+ r: r,96+ })97+ }98+ return99+}100+101+// text corresponds to any rune not reserved for diagrams, or a102+// rune ordinarily reserved but in this case stripped of its special meaning103+// because surrounded by alphanumerics.104+type text struct {105+ Start XyIndex // XX non-intuitive name: start and end are the same cell106+ r rune107+}108+109+func (t text) String() string {110+ return fmt.Sprintf(`xy index: %#v, character: %q`, t.Start, string(t.r))111+}112+113+// Looks only at c.Data[], ignores c.TextRunes[].114+// Returns the rune for ASCII Space i.e. ' ', in the event that map lookup fails.115+// XX Name 'dataRuneAt()' would be more descriptive, but maybe too bulky.116+func (c *CanvasCommon) RuneAt(i XyIndex) rune {117+ if val, ok := c.Data[i]; ok {118+ return val119+ }120+ return ' '121+}122+123+// NewCanvasCommon creates a fully-populated CanvasCommon according to GoAT-formatted text read from124+// an io.Reader, consuming all bytes available.125+func NewCanvasCommon(config *Config, in io.Reader) (c CanvasCommon) {126+ scanner := bufio.NewScanner(in)127+ split := bufio.ScanLines128+ if config.LineFilter != nil {129+ split = func(data []byte, atEOF bool) (advance int, token []byte, err error) {130+ advance, token, err = bufio.ScanLines(data, atEOF)131+ if token == nil || err != nil || atEOF {132+ return133+ }134+ if ! config.LineFilter.Match(token) {135+ token = nil136+ }137+ return138+ }139+ }140+ // Set the split function for the scanning operation.141+ scanner.Split(split)142+143+ c, err := newCanvasCommon(scanner)144+ if err != nil {145+ fatalIoReaderError(err, in)146+ }147+148+ // XX ? Separate and promote to caller this phase of processing and149+ // the particular data structure it creates?150+ c.TextRunes = make(map[XyIndex]rune)151+ return152+}153+154+// XX Refactor? Move to files.go -- more general package?155+func fatalIoReaderError(err error, in io.Reader) {156+ fileName := filenameFromReader(in)157+ if len(fileName) == 0 {158+ fileName = "input diagram not read from a named file"159+ }160+ err = fmt.Errorf("Error in %s: '%v'",161+ fileName, err)162+ log.Fatal(err)163+}164+func filenameFromReader(in io.Reader) string {165+ file, isFile := in.(*os.File)166+ if isFile {167+ return file.Name()168+ }169+ return ""170+}171+172+// Create and populate a 'data' map.173+func newCanvasCommon(scanner *bufio.Scanner) (CanvasCommon, error) {174+ data := make(map[XyIndex]rune)175+ width := 0176+ height := 0177+178+ for scanner.Scan() {179+ lineStr := scanner.Text()180+181+ w := 0182+ // X Type of second value assigned from "for ... range" operator over a string is "rune".183+ // https://go.dev/ref/spec#For_statements184+ // But yet, counterintuitively, type of lineStr[_index_] is 'byte'.185+ // https://go.dev/ref/spec#String_types186+ for _, r := range lineStr {187+ //if r > 255 {188+ // fmt.Printf("linestr=\"%s\"\n", lineStr)189+ // fmt.Printf("r == 0x%x\n", r)190+ //}191+ if r == ' ' {192+ return CanvasCommon{}, fmt.Errorf("Found TAB at row %d, column %d",193+ height+1, w)194+ }195+ i := XyIndex{w, height}196+ data[i] = r197+ w++198+ }199+200+ if w > width {201+ width = w202+ }203+ height++204+ }205+ if height == 0 {206+ // Return an error, for fuller error diagnostics to CLI user.207+ return CanvasCommon{}, errors.New("input appears to be empty!")208+ }209+ return CanvasCommon{210+ Data: data,211+ Width: width,212+ Height: height,213+ TextRunes: nil, // XX214+ }, nil215+}216+217+// Move contents of every cell that appears, according to a tricky set of rules,218+// to be "text", into a separate map: from data[] to textRunes[].219+// So data[] and textRunes[] are an exact partitioning of the220+// incoming grid-aligned runes.221+func MoveToText(ac AbstractCanvas) {222+ cc := ac.GetCommon()223+ for _, i := range LeftRightMinor(cc.Width, cc.Height) {224+ if ac.ShouldMoveToTextRunes(i) {225+ cc.TextRunes[i] = cc.RuneAt(i) // cc.RuneAt() Reads from cc.Data[]226+ }227+ }228+ for i := range cc.TextRunes {229+ delete(cc.Data, i)230+ }231+}232+233+func CanvasString(ac AbstractCanvas) string {234+ var buffer bytes.Buffer235+ cc := ac.GetCommon()236+237+ for h := 0; h < cc.Height; h++ {238+ for w := 0; w < cc.Width; w++ {239+ idx := XyIndex{w, h}240+241+ // Search 'text' map; if nothing there try the 'data' map.242+ r, ok := cc.TextRunes[idx]243+ if !ok {244+ r = cc.RuneAt(idx)245+ }246+247+ _, err := buffer.WriteRune(r)248+ if err != nil {249+ continue250+ }251+ }252+253+ err := buffer.WriteByte('\n')254+ if err != nil {255+ continue256+ }257+ }258+259+ return buffer.String()260+}261+262+func ToSVGFilename(txtFilename string) string {263+ return strings.TrimSuffix(txtFilename, filepath.Ext(txtFilename)) + ".svg"264+}
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/config.go +320 -0
@@ -0,0 +1,320 @@
1+package svg2+3+import (4+ "errors"5+ "fmt"6+ "io"7+ "log"8+ "os"9+ "regexp"10+ "strings"11+12+ "github.com/tdewolff/parse/v2"13+ "github.com/tdewolff/parse/v2/css"14+15+ "github.com/blampe/goat"16+ "github.com/blampe/goat/internal"17+)18+19+type (20+ Config struct {21+ LineFilter *regexp.Regexp22+23+ beginMap,24+ endMap map[rune]*markBinding25+ }26+)27+28+const DUMP_CSS = false // XX Also suppresses some early panic calls. Bad?29+func printfStderr(format string, args ...any) {30+ fmt.Fprintf(os.Stderr,31+ //"%s: "+format, XX why is this wrong?32+ "%24s: %v\n",33+ internal.Where(2), args)34+ // "%24s %16s() %v\n", XX Who(2) always fails -- why?35+ // internal.Where(2), internal.Who(2), args)36+}37+38+// Extend MarkBindingMap according to any GoAT-specific properties found in 'cssBytes'.39+func ParseCss(bindings MarkBindingMap, cssBytes []byte) error {40+ parser := css.NewParser(parse.NewInputBytes(cssBytes), false)41+42+ // Running accumulation of contents of current CSS RuleSet.43+ // To be appended to returned slice 'bindings' iff at least one44+ // GoAT pseudo-CSS-property is found.45+ var markBinding markBinding46+47+ // Iterate over the top level of the CSS structure.48+ for tokenCount := 0; ; tokenCount++ {49+ // X Conform to CSS convention: GoAT-specific properties are read50+ // case-insensitively from CSS files51+ grammarType, tokenType, tokenData := parser.Next()52+ _ = tokenType53+54+ switch grammarType {55+ case css.ErrorGrammar:56+ err := parser.Err()57+ if err == io.EOF {58+ return nil59+ }60+ // X Interleave error descriptions with echoing of normal output.61+ errData := []byte("ERROR(")62+ for _, val := range parser.Values() {63+ errData = append(errData, val.Data...)64+ }65+ errData = append(errData, ")"...)66+ return fmt.Errorf("%v: %v %s\n", err, grammarType, string(errData))67+ }68+69+ if DUMP_CSS {70+ // if tokenCount == 0 {71+ // printfStderr(`%16v %16v %v72+ //`, "grammarType", "tokenType", "string(tokenData)")73+ // }74+ printfStderr(`%#v %#v "%#v"75+`,76+ grammarType, tokenType, string(tokenData))77+ }78+79+ dumpValues := func(grammarType css.GrammarType) {80+ if !DUMP_CSS {81+ return82+ }83+ printfStderr(84+ ` %s Values():85+`,86+ grammarType.String())87+ }88+89+ switch grammarType {90+ case css.BeginRulesetGrammar:91+ dumpValues(grammarType)92+ // Enter a sub-loop at this point, for any CSS .class or #id block93+ // that requires examination.94+ markBinding = beginRuleSet(parser)95+ case css.EndRulesetGrammar:96+ if markBinding.markpair != zeroMarkArr {97+ if len(markBinding._idName) > 0 {98+ return fmt.Errorf("Ruleset containing %q "+99+ "was specified by an element tag (%q) -- NOT supported.",100+ goat_anchor_marks, markBinding._idName)101+ }102+103+ mustInsertBinding(bindings, markBinding)104+ } else {105+ if len(markBinding.HRef) > 0 {106+ return fmt.Errorf("Ruleset for classes %v contained %q, but no %q.",107+ markBinding.ClassNames, goat_anchor_href, goat_anchor_marks)108+ }109+ // otherwise discard the markBinding110+ }111+ case css.DeclarationGrammar:112+ // a single declaration within some CSS declaration block113+ // https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Syntax/Introduction#css_rulesets114+ dumpValues(grammarType)115+ cssTokens := parser.Values()116+ // 'tokenData' here is the CSS property name117+ switch string(tokenData) {118+ default:119+ continue // assumed to be an ordinary property120+ case goat_anchor_marks:121+ markStr := lexDeclaration(cssTokens)122+ markBinding.markpair = validPair(markStr)123+ //case goat_anchor_substitutes:124+ // substStr := lexDeclaration(cssTokens)125+ // markBinding.subst = validPair(substStr)126+ case goat_anchor_href:127+ // XX ? Feature wanted: Rebase local links from directory of TXT source to128+ // that of the SVG output?129+ markBinding.HRef = lexDeclaration(cssTokens)130+ }131+ // Fatal error if a GoAT-specific property is found132+ // inside a RuleSet whose CSS selector list is not names of classes, with or without a prepended element tag.133+ if len(markBinding.ClassNames) == 0 {134+ // XX At this point, markBinding.markpair[2] is not yet initialized.135+ return fmt.Errorf(136+ `GoAT-specific properties found, but no .CLASSNAME selectors for the RuleSet:137+ %s`,138+ markBinding.String())139+ }140+ }141+ }142+}143+144+func validPair(str string) (_ markArr) {145+ runes := []rune(str) // string to slice conversion146+ switch len(runes) {147+ case 0:148+ return149+ case 2:150+ // usual case151+ return markArr(runes)152+ default:153+ log.Fatalf(154+ "invalid mark pair length): %d", len(runes))155+ }156+ return157+}158+159+// Most declarations will be parsed into only one value; an example of160+// the exceptional case would be "var(--red)"161+func lexDeclaration(cssTokens []css.Token) (tokenStr string) {162+ if len(cssTokens) > 1 {163+ log.Fatalln("could this case arise???")164+ }165+ tokenStr = strings.Trim(string(cssTokens[0].Data), "\"")166+ if DUMP_CSS {167+ printfStderr(168+ ` %16v: %s169+`,170+ cssTokens[0].TokenType, tokenStr)171+ }172+ return173+}174+175+func beginRuleSet(parser *css.Parser) (kb markBinding) {176+ var isClass bool177+178+ // Consume the CSS selector list.179+ // https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/Selector_list180+ // lastTokenStr := ""181+ for _, token := range parser.Values() {182+ tokenStr := string(token.Data)183+ if DUMP_CSS {184+ printfStderr(185+ ` %16v: %s186+`,187+ token.TokenType, tokenStr)188+ }189+ switch token.TokenType {190+ case css.HashToken:191+ // Introduces an HTML element ID string.192+ // X The parser does not split out the initial '#' from the following identifier193+ kb._idName = tokenStr[1:]194+ case css.DelimToken:195+ // The initial '.' of a class rule hits this.196+ if tokenStr == "." {197+ isClass = true198+ }199+ case css.IdentToken: // X hits for both a class name, and an SVG element tag, which is not needed.200+ if isClass {201+ kb.ClassNames = append(kb.ClassNames, tokenStr)202+ }203+ default:204+ // some other selector syntax205+ }206+ //lastTokenStr = tokenStr207+ }208+ return209+}210+211+func (ma markArr) String() string {212+ return fmt.Sprintf("%s%s", string(ma[0]), string(ma[1]))213+}214+215+// General model for merging content of CSS files is that CSS definitions are216+// concatenated within the SVG -- no attempt to detect duplications.217+func mustInsertBinding(bindings MarkBindingMap, kb markBinding) {218+ seniorValue, exists := bindings[kb.markpair]219+ if exists {220+ if len(seniorValue.HRef) > 0 && len(kb.HRef) > 0 {221+ log.Fatalf(`222+Attempt to overwrite HRef:223+ new: %+v224+ old: %+v225+`,226+ seniorValue, kb)227+ }228+ seniorValue.HRef = kb.HRef229+230+ // Allow colliding markBindings, with contents merged, following231+ // existing practice of CSS rules.232+ seniorValue.ClassNames = append(seniorValue.ClassNames, kb.ClassNames...)233+ return234+ }235+ bindings[kb.markpair] = &kb // escape to heap236+}237+238+func appendAttr(l, r string) string {239+ if r == "" {240+ return l241+ } else if l == "" {242+ return r243+ } else {244+ return l + `;245+ ` + r246+ }247+}248+249+func NewConfig(reservedSet goat.RuneSet,250+ parsedCss MarkBindingMap, // heavyweight object251+) (Config, error) {252+253+ // XX Could be pushed into CSS parsing loop:254+ // ++ fewer loops, but ...255+ // -- parser would become sensitive to 'reservedSet'256+ err := vetMarkBindingMap(reservedSet, parsedCss)257+ if err != nil {258+ return Config{}, err259+ }260+261+ conf := Config{262+ // Copies of arg 'parsedCss', with possible edits.263+ beginMap: make(map[rune]*markBinding),264+ endMap: make(map[rune]*markBinding),265+ }266+ for markpair, kb := range parsedCss {267+ _ = markpair268+ // Verify that kb.markpair[0] and kb.markpair[1] are both unique269+ // across conf.MarkBindingMap, to avoid silently "orphaning" markBinding definitions.270+ allocIfUnique := func(beMap map[rune]*markBinding, be rune) error {271+ senior, nonUnique := beMap[be]272+ if nonUnique {273+ return fmt.Errorf("Mark already in use %s\nby markBinding:\n\t%+v",274+ string(be), senior)275+ }276+ beMap[be] = kb277+ return nil278+ }279+ // Afford the user identical begin and end marks.280+ errBegin := allocIfUnique(conf.beginMap, kb.markpair[0])281+ if errBegin != nil {282+ return Config{}, errors.Join(errors.New("Begin "), errBegin)283+ }284+ errEnd := allocIfUnique(conf.endMap, kb.markpair[1])285+ if errEnd != nil {286+ return Config{}, errors.Join(errors.New("End "), errEnd)287+ }288+ }289+ return conf, nil290+}291+292+func vetMarkBindingMap(reservedSet goat.RuneSet, parsedCss MarkBindingMap) (err error) {293+ for markRunes, kb := range parsedCss {294+ if markRunes == zeroMarkArr {295+ return errors.New(fmt.Sprintf(296+ "invalid markpair (%s)", markRunes.String()))297+ }298+ err = vetMark(reservedSet, kb, markRunes[0])299+ if err != nil {300+ return err301+ }302+ err = vetMark(reservedSet, kb, markRunes[1])303+ if err != nil {304+ return err305+ }306+ kb.markpair = markArr(markRunes)307+ }308+ return309+}310+311+func vetMark(reservedSet goat.RuneSet, candidateMarkBinding *markBinding, r rune) error {312+ _, found := reservedSet[r]313+ if found {314+ return errors.New(fmt.Sprintf(315+ `reserved rune '%c' (0x%x) `+316+ `cannot be used as a markpairclass mark by markpairclass: %s`,317+ r, r, formatMarkBinding(candidateMarkBinding)))318+ }319+ return nil320+}
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/csshelp.go +75 -0
@@ -0,0 +1,75 @@
1+package svg2+3+import (4+ "fmt"5+)6+7+// Embed arg 'source' in the neo-attribute 'goat-source', which the browser merely8+// ignores, except that it is displayed in the web debugger, for easy reference by the developer.9+func newStyleElement(sourceOrigin, css string) string {10+ return fmt.Sprintf(11+` <style type="text/css" source-text-origin="%s">12+%s </style>13+`,14+ sourceOrigin,15+ css)16+}17+18+// See:19+// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme20+// https://developer.mozilla.org/en-US/docs/Web/SVG/Element/style21+// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute22+//23+// X Note that all elements are drawn by SVG not HTML, and this guidance about24+// the CSS "color:" property is not valid:25+// https://developer.mozilla.org/en-US/docs/Web/CSS/color26+// Rather, the authority is:27+// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute28+// Or29+// https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/text30+// containing this:31+// /* Note that the color of the text is set with the *32+// * fill property, the color property is for HTML only */33+//34+// XX Could this be moved into "embed:defaultCSS", to be loaded unless CLI requests otherwise?35+const defaultCSS = ` svg {36+ color-scheme: light dark; /* this becomes necessary if not inherited from parent elements */37+ font-family: monospace;38+ font-size: 15px;39+ }40+ text {41+ stroke: none;42+ text-anchor: middle;43+ }44+ .path {45+ fill: none;46+ }47+ circle.filled {48+ fill: inherit;49+ }50+ circle.hollow {51+ fill: none;52+ }53+`54+55+// generate sharable CSS, for use in simple diagrams56+func ColorsOnlyCssFileContent(57+ svgColorLightScheme, svgColorDarkScheme string) string {58+59+ return fmt.Sprintf(60+` svg {61+ color: %s; /* set value of 'currentColor' */62+ }63+ @media (prefers-color-scheme: dark) {64+ svg {65+ color: %s; /* set value of 'currentColor' */66+ }67+ }68+ svg {69+ fill: currentColor;70+ stroke: currentColor;71+ }72+`,73+ svgColorLightScheme,74+ svgColorDarkScheme)75+}
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/drawable.go +54 -0
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/index.go +95 -0
@@ -0,0 +1,95 @@
1+package svg2+3+import (4+ "github.com/blampe/goat"5+)6+7+// XyIndex represents a position within an ASCII diagram, and8+// of the visual center of a corresponding rectangle within the output SVG,9+// where both have a height:width ratio of 2:1.10+type XyIndex struct {11+ // units of cells12+ X, Y int13+}14+15+16+// Arg 'canvasMap' is typically either Canvas.data or Canvas.text17+func InSet(set goat.RuneSet, canvasMap map[XyIndex]rune, i XyIndex) bool {18+ r, inMap := canvasMap[i]19+ if !inMap {20+ return false // r == rune(0)21+ }22+ return set.Contains(r)23+}24+25+26+// Type "pixel' represents the CSS-pixel coordinates of the apparent visual center of27+// an 8x16 cell pointed to by an XyIndex.28+type Pixel struct {29+ // units of CSS "pixels"30+ X, Y int31+}32+33+func (a *Pixel) Delta(b Pixel) {34+ a.X += b.X35+ a.Y += b.Y36+}37+38+func (a Pixel) Sum(b Pixel) Pixel {39+ return Pixel{40+ a.X + b.X,41+ a.Y + b.Y,42+ }43+}44+45+const (46+ CellWidth = 847+ CellHeight = 1648+ W = CellWidth49+ H = CellHeight50+)51+52+53+func (i *XyIndex) AsPixel() Pixel {54+ // TODO define constants rather than hard-wire width and height of cell55+ return Pixel{56+ X: i.X * CellWidth,57+ Y: i.Y * CellHeight}58+}59+60+func (i *XyIndex) AsPixelXY() (int, int) {61+ p := i.AsPixel()62+ return p.X, p.Y63+}64+65+func (i *XyIndex) East() XyIndex {66+ return XyIndex{i.X + 1, i.Y}67+}68+69+func (i *XyIndex) West() XyIndex {70+ return XyIndex{i.X - 1, i.Y}71+}72+73+func (i *XyIndex) North() XyIndex {74+ return XyIndex{i.X, i.Y - 1}75+}76+77+func (i *XyIndex) South() XyIndex {78+ return XyIndex{i.X, i.Y + 1}79+}80+81+func (i *XyIndex) NWest() XyIndex {82+ return XyIndex{i.X - 1, i.Y - 1}83+}84+85+func (i *XyIndex) NEast() XyIndex {86+ return XyIndex{i.X + 1, i.Y - 1}87+}88+89+func (i *XyIndex) SWest() XyIndex {90+ return XyIndex{i.X - 1, i.Y + 1}91+}92+93+func (i *XyIndex) SEast() XyIndex {94+ return XyIndex{i.X + 1, i.Y + 1}95+}
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/iter.go +60 -0
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/style.go +76 -0
@@ -0,0 +1,76 @@
1+package svg2+3+import (4+ "fmt"5+)6+7+type (8+ markArr [2]rune9+10+ // "Binds" a <g> or <a> element, itself wrapping a series of <text> elements, to a11+ // CSS class containing a property "goat-anchor-marks".12+ // https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Syntax/Introduction13+ markBinding struct {14+ goatAnchorClassStyleRuleProperties15+16+ // CSS class definitions -- one for each CSS style-rule with17+ // property '"goat-anchor-substitutes"' matching 'markpair'18+ ClassNames []string19+20+ // Used only for CSS correctness-checking: should never appear in21+ // selector list of a stylerule that contains GoAT-defined properties.22+ // XX ? No need to retain -- move out to a local var in the parsing loop?23+ _idName string24+ }25+26+ // XX XX Create a variant of this that identifies text ranges to be styled not by marks,27+ // but by match of the diagram text to a pattern (globbing, or regexp?)28+ goatAnchorClassStyleRuleProperties struct {29+ // For <text> elements, two characters long: Begin and end of a30+ // left-to-right, top-to-bottom range within the input UTF-8 diagram31+ // rectangle to which a markBinding should be applied.32+ markpair markArr // XX unify with name of CSS property "goat-anchor-marks"33+34+ // Replacements for a markpair, if not zeroes, in which case no substitution is made.35+ //subst markArr // XX unify with "goat-anchor-substitutes"36+37+ // If non-nil, an outgoing link to be emitted -- cannot be gotten indirectly38+ // through a CSS class.39+ //40+ // The 'href' attribute will be bound to an enclosing Text "<a>" element41+ // X Cannot be used in GitHub README.md Markdown files.42+ HRef string // XX unify with "goat-anchor-href"43+ }44+45+ // Reason for indirection to the referent "*markBinding": Other maps,46+ // instantiated later, will want to refer to the same struct objects.47+ MarkBindingMap map[markArr]*markBinding48+)49+50+const (51+ goat_anchor_marks = "goat-anchor-marks"52+53+ // XX Need at least one example of beneficial use.54+ //goat_anchor_substitutes = "goat-anchor-substitutes"55+56+ goat_anchor_href = "goat-anchor-href"57+)58+59+func (mb markBinding) String() string {60+ return fmt.Sprintf(`61+ markpair: %q`+62+ // `subst: %q` +63+ `64+ HRef: %q65+ ClassNames: %q66+ _idName: %q67+`,68+ mb.markpair.String(),69+ //mb.subst.String(),70+ mb.HRef,71+ mb.ClassNames,72+ mb._idName,73+ )74+}75+76+var zeroMarkArr markArr = markArr{}
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/svg-text.go +243 -0
@@ -0,0 +1,243 @@
1+package svg2+3+import (4+ "errors"5+ "fmt"6+ "io"7+ "log"8+9+ "github.com/blampe/goat/internal"10+)11+12+type textDrawer struct {13+ config *Config14+15+ // Support nesting of SVG elements to name additional classes:16+ // Goal: Report input errors promptly.17+ wrapperStack []wrapper18+}19+20+type wrapper struct {21+ *markBinding22+ text23+}24+25+func Writetext(out io.Writer, config *Config, ac AbstractCanvas) {26+ tD := textDrawer{27+ config: config,28+ }29+ cc := ac.GetCommon()30+ for _, textObj := range cc.text() {31+ err := tD.Draw(out, textObj)32+ if err != nil {33+ // XX Dump the filename as well.34+ log.Fatalf(`35+%s36+In Config %+v`,37+ err.Error(),38+ config,39+ )40+ }41+ }42+ // diagnose bad remaining wrapperStack contents here43+ // XX share error state dumping code with Draw() below.44+ // XX XX End users want helpful diagnostics.45+ // XX Passing an 'error' return up would require changing 3-4 levels of callers.46+ // X Alternative: set a field in Config 'exitStatus', to be checked by47+ // interested callers.48+ if len(tD.wrapperStack) > 0 {49+ wrapper := tD.wrapperStack[0]50+ log.Fatalf(`51+len(tD.wrapperStack)==%d, should be empty.52+53+tD.wrapperStack[0] = {54+ markBinding: %s55+ text: %s56+}57+`,58+ len(tD.wrapperStack),59+ wrapper.markBinding.String(),60+ wrapper.text.String(),61+ )62+ }63+}64+65+// Output is nested inside <svg>...</svg>.66+// Draw a single text character as an SVG <text> element. (In HTML, there is no <text> element.)67+68+// XX ? Refactor this, to emit a single <text> element for consecutive characters, using the SVG feature:69+// https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/letter-spacing70+// Rationale:71+// 1. Eliminates rasterization artifacts where underlines collide.72+// 2. Makes SVG code easier to read.73+func (tD *textDrawer) Draw(out io.Writer, t text) error {74+ //character := string(t.r); _ = character // for debug75+ textIndex := t.Start76+ p := textIndex.AsPixel()77+78+ // X Whether t.r is actually a beginning, or rather and ending mark is not known at this point.79+ endMarkBinding, foundEndMark := tD.config.endMap[t.r]80+ beginMarkBinding, foundBeginMark := tD.config.beginMap[t.r]81+82+ handleBeginMark := func() {83+ tD.wrapperStack = append(tD.wrapperStack,84+ wrapper{beginMarkBinding, t})85+86+ var attrs string87+ if len(beginMarkBinding.ClassNames) > 0 {88+ attrs += fmt.Sprintf(" class='%s", beginMarkBinding.ClassNames[0])89+ for _, classname := range beginMarkBinding.ClassNames[1:] {90+ attrs += fmt.Sprintf(" %s", classname)91+ }92+ attrs += fmt.Sprintf("'")93+ }94+ elemString := "g"95+ if len(beginMarkBinding.HRef) > 0 {96+ // XX Nesting of <a> is forbidden by the spec -- how check input for violations?97+ // X Observed in browser: empty string "href=''" produces linking to the page itself.98+ // Therefore, drop the href attribute entirely -- apparently functional equivalent99+ // of a <g> element.100+ attrs += fmt.Sprintf(" href='%s'", beginMarkBinding.HRef)101+ elemString = "a"102+ }103+ internal.MustFPrintf(out, ` <%s%s>104+`,105+ elemString, attrs)106+ //subst := beginMarkBinding.subst[0]107+ //108+ //if subst != 0 {109+ // finalDraw(out, p, subst)110+ //}111+ }112+113+ if foundEndMark {114+ // error cases115+ if len(tD.wrapperStack) == 0 {116+ if foundBeginMark {117+ handleBeginMark()118+ return nil119+ }120+ return errors.New(fmt.Sprintf(121+ // toggling case?122+ "\n\tEnd mark %s for\n" +123+ "\t\t markpairclass %+v found at\n" +124+ "\t\tIndex %+v, but no matching start mark on stack",125+ string(t.r), endMarkBinding, t.Start))126+ }127+128+ // Verify that t.r is the specific end mark expected in accordance with129+ // nesting order of the input.130+ tosWrapper := tD.wrapperStack[len(tD.wrapperStack)-1]131+ tosMarkBinding := tosWrapper.markBinding132+ if t.r != tosMarkBinding.markpair[1] {133+ // normal toggling case -- same mark for begin and end?134+ if foundBeginMark {135+ handleBeginMark()136+ return nil137+ }138+ tosMarkBindingEndMark := tosMarkBinding.markpair[1]139+ return errors.New(fmt.Sprintf(140+ "\tAt line %d, column %d scanned unexpected end mark" +141+ "\n\t%s (0x%x) for markpairclass" +142+ "\n\t\t%s" +143+ "\n\texpected end mark" +144+ "\n\t%s (0x%x) for markpairclass" +145+ "\n\t\t%s",146+ textIndex.Y, textIndex.X,147+ string(t.r), t.r,148+ formatMarkBinding(endMarkBinding),149+ string(tosMarkBindingEndMark), tosMarkBindingEndMark,150+ formatMarkBinding(tosMarkBinding)))151+ }152+ //// Special case: do not show a styled SPACE (possibly underlined)153+ //// as the replacement for an end Mark character.154+ //subst := endMarkBinding.subst[1]155+ //if subst != 0 {156+ // finalDraw(out, p, subst)157+ //}158+159+ elemString := "g"160+ if len(endMarkBinding.HRef) > 0 {161+ elemString = "a"162+ }163+ internal.MustFPrintf(out, ` </%s>164+`,165+ elemString)166+ // pop the stack167+ tD.wrapperStack = tD.wrapperStack[:len(tD.wrapperStack)-1]168+ return nil169+ }170+ if foundBeginMark {171+ handleBeginMark()172+ } else {173+ finalDraw(out, p, t.r)174+ }175+ return nil176+}177+178+179+func finalDraw(out io.Writer, p Pixel, r rune) {180+ if r == 0 {181+ log.Panicf("NULL rune received")182+ }183+ c := string(r)184+ if len(c) <= 0 {185+ log.Panicf("rune %#v yielded empty string!", r)186+ }187+ if c == " " {188+ // <text> elements containing only a SPC character can always be safely dropped, yes?189+ // XX Could this case be eliminated earlier in processing?190+ return191+ }192+ opacity := 0193+194+ // Markdeep special-cases these characters and treats them like a195+ // checkerboard.196+ switch c {197+ case "▉":198+ opacity = -9999199+ case "▓":200+ opacity = 64201+ case "▒":202+ opacity = 128203+ case "░":204+ opacity = 191205+ }206+207+ if opacity != 0 {208+ fill := "currentColor"209+ if opacity > 0 {210+ fill = fmt.Sprintf("rgb(%d,%d,%d)", opacity, opacity, opacity)211+ }212+ internal.MustFPrintf(out,213+ `<rect x="%d" y="%d" width="%d" height="%d" fill="%s"></rect>214+`,215+ p.X-W/2, p.Y-H/2,216+ W, H,217+ fill)218+ return219+ }220+221+ // Escape for XML222+ switch c {223+ case "&":224+ c = "&amp;"225+ case ">":226+ c = "&gt;"227+ case "<":228+ c = "&lt;"229+ }230+231+ // usual case232+233+ // Text elements <text> get an inline Y-offset of +4 – visually necessary for Y-alignment234+ // with Dots to left or right.235+ // The value +4 is in theory font-specific, but for common fonts it corresponds to the offset236+ // from the center of the 8x16 cell and the "baseline" typical of Roman fonts, which237+ // aligns with for example the bottom of the "bowl" of a lower-case 'g'.238+ // https://svgwg.org/svg2-draft/text.html#FontsGlyphs239+ centerToBaseline := 4240+ internal.MustFPrintf(out, ` <text x="%d" y="%d">%s</text>241+`,242+ p.X, p.Y+centerToBaseline, c)243+}
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/svg/svg.go +287 -0
@@ -0,0 +1,287 @@
1+package svg2+3+import (4+ "fmt"5+ "io"6+7+ "github.com/blampe/goat/internal"8+)9+10+func (cc *CanvasCommon) OpenSvgElement() string {11+ return fmt.Sprintf(12+`<svg xmlns="http://www.w3.org/2000/svg" version="1.1"13+ width="%d" height="%d"14+ viewBox="0 0 %d %d">15+`,16+ cc.widthScreen(), cc.heightScreen(),17+ cc.widthScreen(), cc.heightScreen(),18+ )19+}20+21+func (c *CanvasCommon) heightScreen() int {22+ // " + 8 + 2", because any less results in clipping of any edge at the bottom of the drawing23+ // XX Necessary because fragilely tuned to 'edgeToCenterY', below.24+ return c.Height*CellHeight + 8 + 225+}26+27+func (c *CanvasCommon) widthScreen() int {28+ // XX "c.Width + 1", fragilely tuned to 'edgeToCenterX', below.29+ return (c.Width + 1) * CellWidth30+}31+32+func CloseSvgElement() string {33+ return `</svg>34+`35+}36+37+// We desire that pixel coordinate {0,0} should lie at the *center* of the 8x1638+// "cell" at top-left corner of the enclosing SVG element, and that a39+// visually-pleasing margin separate that cell from the visible top-left40+// corner; the 'translate()' below accomplishes that.41+//42+// X The former 16-pixel Y-axis translation of the transform was more than necessary – there43+// is an always-blank band at the top of SVG image.44+func OpenGElement() string {45+ edgeToCenterX, edgeToCenterY :=46+ CellWidth, CellHeight*3/447+48+ return fmt.Sprintf(`49+<g transform='translate(%d,%d)'>50+`,51+ edgeToCenterX, edgeToCenterY)52+}53+54+func CloseGElement() string {55+ return `</g>56+`57+}58+59+func WritePolyline(out io.Writer, start, stop Pixel) {60+ internal.MustFPrintf(out, ` <polyline class="path" points="%d,%d %d,%d"/>61+`,62+ start.X, start.Y,63+ stop.X, stop.Y,64+ )65+}66+67+// Draw a solid triangle as an SVG polygon element.68+func (t Triangle) Draw(out io.Writer) {69+ // https://www.w3.org/TR/SVG/shapes.html#PolygonElement70+71+ /*72+ +-----+-----+73+ | /|\ |74+ | / | \ |75+ x +- / -+- \ -+76+ | / | \ |77+ |/ | \|78+ +-----+-----+79+ y80+ */81+82+ x, y := float32(t.Start.AsPixel().X), float32(t.Start.AsPixel().Y)83+ r := 0.084+85+ // Coordinate values below are effective verbatim only for O_E, an isosceles86+ // triangle "pointing" rightward; rotation to the desired final direction87+ // is a post-process.88+ // If regarded as an arrow-point, it will be 12px long and89+ // 0.35*16*2=11.2px in width.90+ x0 := x + W91+ y0 := y92+ x1 := x - W/293+ y1 := y - 0.35*CellHeight94+ x2 := x - W/295+ y2 := y + 0.35*CellHeight96+97+ // 't.NeedsNudging' in all cases below means "put the tip of the arrowhead98+ // on the boundary of the next cell".99+ // For a horizontal arrowhead this means retreating so as not to intrude100+ // into the next cell; for other orientations it necessitates advancing. XX correct?101+ switch t.Orientation {102+ case O_N:103+ r = 270104+ if t.NeedsNudging {105+ x0 += W106+ x1 += W107+ x2 += W108+ }109+ case O_NE:110+ r = 300111+ x0 += W/2112+ x1 += W/2113+ x2 += W/2114+ if t.NeedsNudging {115+ x0 += 6116+ x1 += 6117+ x2 += 6118+ }119+ case O_NW:120+ r = 240121+ x0 += W/2122+ x1 += W/2123+ x2 += W/2124+ if t.NeedsNudging {125+ x0 += 6126+ x1 += 6127+ x2 += 6128+ }129+ case O_W:130+ r = 180131+ if t.NeedsNudging {132+ x0 -= W133+ x1 -= W134+ x2 -= W135+ }136+ case O_E:137+ r = 0138+ if t.NeedsNudging {139+ x0 -= W140+ x1 -= W141+ x2 -= W142+ }143+ case O_S:144+ r = 90145+ if t.NeedsNudging {146+ x0 += W147+ x1 += W148+ x2 += W149+ }150+ case O_SW:151+ r = 120152+ x0 += W/2153+ x1 += W/2154+ x2 += W/2155+ if t.NeedsNudging {156+ x0 += 6157+ x1 += 6158+ x2 += 6159+ }160+ case O_SE:161+ r = 60162+ x0 += W/2163+ x1 += W/2164+ x2 += W/2165+ if t.NeedsNudging {166+ x0 += 6167+ x1 += 6168+ x2 += 6169+ }170+ }171+172+ // <polygon> inherits both 'fill' and 'stroke' attributes from parents.173+ internal.MustFPrintf(out, PolygonPrintFmt,174+ x0, y0,175+ x1, y1,176+ x2, y2,177+ r,178+ x, y)179+}180+181+const PolygonPrintFmt = ` <polygon points="%g,%g %g,%g %g,%g" transform="rotate(%g, %g, %g)" class="arrowhead"></polygon>182+`183+184+// Draw a solid circle as an SVG circle element.185+func (ci *Circle) Draw(out io.Writer, circleRadius int) {186+ var class string187+ if ci.Bold { // bad name?188+ class = "filled"189+ } else {190+ class = "hollow"191+ }192+ pixel := ci.Start.AsPixel()193+ internal.MustFPrintf(out,194+ ` <circle cx="%d" cy="%d" r="%d" class="%s"></circle>195+`,196+ pixel.X,197+ pixel.Y,198+ circleRadius,199+ class,200+ )201+}202+203+func formatMarkBinding(s *markBinding) string {204+ return fmt.Sprintf("%+v", s)205+}206+207+// Draw a rounded corner as an SVG "elliptical arc" element, here merely a circular arc208+// across one of the four axis-aligned quadrants.209+//210+// ASCII:211+// Span a _pair_ of left-right adjacent 8x16 cells, one of which contains an ASCII space, that212+// being the one at the corner of the connected line segments.213+// .-.214+// | | a circle215+// '-'216+// UTF-8:217+// Corner lies entirely within the _single_ cell containing 'startPixel'218+// ╭╮219+// ╰╯ output will be a true oval -- but no circle is possible220+//221+func (rc *RoundedCorner) DrawCentered(out io.Writer, centerPixel Pixel, radius int) {222+ // https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands223+224+ x, y := centerPixel.X, centerPixel.Y225+ var startX, startY, sweepFlag, endX, endY int226+227+ switch rc.Orientation {228+ case O_NW:229+ startX = x230+ startY = y - radius231+ sweepFlag = 0 // counter-clockwise232+ endX = x - radius233+ endY = y234+ case O_SW:235+ startX = x - radius236+ startY = y237+ sweepFlag = 0 // counter-clockwise238+ endX = x239+ endY = y + radius240+ case O_NE:241+ startX = x242+ startY = y - radius243+ sweepFlag = 1 // clockwise244+ endX = x + radius245+ endY = y246+ case O_SE:247+ startX = x + radius248+ startY = y249+ sweepFlag = 1 // clockwise250+ endX = x251+ endY = y + radius252+ }253+254+ // X Assumes inherited "fill: none"255+ internal.MustFPrintf(out,256+ ` <path class="path" d="M %d,%d A %d,%d %d %d,%d %d,%d"></path>257+`,258+ startX,259+ startY,260+ radius, // x-radius261+ radius, // y-radius262+ 0, // x-axis-rotation263+ 0, // large-arc-flag264+ sweepFlag,265+ endX, // absolute end position, as implied by SVG command 'A'266+ endY, // absolute end position, as implied by SVG command 'A'267+ )268+}269+270+// Draw a bridge as an SVG elliptical arc element.271+func (b Bridge) Draw(out io.Writer) {272+ x, y := b.Start.AsPixelXY()273+ sweepFlag := 1274+275+ if b.Orientation == O_W {276+ sweepFlag = 0277+ }278+279+ // X Assumes inherited "fill: none"280+ internal.MustFPrintf(out,281+ ` <path class="path" d="M %d,%d A 9,9 0 0,%d %d,%d"></path>282+`,283+ x, y-H/2,284+ sweepFlag,285+ x, y+H/2,286+ )287+}
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/utf8/README.md +106 -0
@@ -0,0 +1,106 @@
1+# UTF-8 Box-drawing mode2+3+You will also need and editor capable of manipulating the Unicode4+ BOX characters (code points 0x25xx).5+6+Ordinary text editors with "rectangle-mode" support may be suitable.7+More ergonomic are:8+ - [Cascii](https://github.com/casparwylie/cascii-core)9+ - [AsciiFlow](https://github.com/lewish/asciiflow)10+ - For Emacs users, ```picture-mode``` or [uniline.el](https://github.com/tbanel/uniline)11+12+## Font Compatibility13+GoAT requires curating one's choice of fonts, to avoid introducing characters of non-standard width or height14+ into the rectangular grid.15+Use of utf8-mode generally tightens the curation requirement, because many popular body fonts, while16+ though nominally monospace, actually select Unicode box-drawing glyphs of non-standard dimension.17+18+Known suitable font, available on Debian distros: "DejaVu Sans Mono".19+20+## Example Graphics21+A highly abstract view of GoAT's internal data flow, using UTF-8 text:22+```23+ ╭─────────────╮24+ │SVG graphics │ ╭───────────────╮25+┌ ┐ ╭─────────╮ ┌ ┐ │ generation │ │ concatenation │26+`annotated` │ │ / recovered / │ ╭──────────┤ ├ ─ ─ ─ ─ ┐ │27+ `diagram,` │ diagram │ / source- / │ │/generic/ ●─────────────────▶ │28+`ASCII or`●─▶ parsing ●─▶/independent/╶─┬───▶├──────────┤ ╭─────────╮ │ │ │29+╷₁`UTF-8`₁ │ │ / structure / │ │ │/source-/ │ ╭─▶ ASCII ●─╮ │ │30+└ ┘ ╰─────────╯ └ ┘ │ │ │/specific/├╴× ├─────────┤ ×─▶ │ │31+ │ │ │ │ ╰─▶UTF8-BOX ●─╯ │ SVG │ ┌ ┐32+ ╭───────────────╮ │ ╰──┴──────────╯ ╰─────────╯ │ elements│ │ `CSS-styled`33+ │ extraction of │ │ ╭───────────────┬────────────╮ │ ├─▶`₃SVG₃file`34+ │ CSS class- │ ╰─▶ text positions │ │ │ │ └ ┘35+┌ ┐ │ names and │ ├╴ ─ ─ ─ ─ ─ ─ ─┤ *<text>* ├──▶ │36+₂ CSS files₂○─────┬──▶ *goat-anchor* ├────▶ anchor ╷ generation │ ├ ─ ─ ─ ─ ┤ │37+└ ┘ │ │ properties │ │ class names ╵ │ │ │38+ │ ╰───────────────╯ ╰───────────────┴────────────╯ │ CSS │ │39+ ╰───────────────────────────────────────────────────────▶ │40+ ╰─────────┴─────╯41+42+```43+Processed to SVG, with garishly colorful CSS styling from44+[`embed:palette/bold.css`](./css/palette/bold.css):45+46+![XXX missing local SVG](./_README/dataflow.bold.svg)47+48+Processed to SVG, with unobtrusive colors from49+[`embed:palette/earth.css`](./css/palette/earth.css):50+51+![XXX missing local SVG](./_README/dataflow.earth.svg)52+53+### Input from AsciiFlow54+```55+56+ ┌─────┬─────────┬───────┐57+ │tight│ │ │58+ │ ▼ │tightly├───────►59+ │ │ │60+ │ ‹Lorem ipsum› │tightly│◄─────────┐61+ │ ·Lorem ipsum· │ ├──────╮ X │62+ │ │ │ ▲X │ │63+ │ │ │ ▲│ ╰───┘64+ ├───┬───────────┴───────┼─┼┤65+ ├───┼─┐ abc ·lmn ‹x›yz· │ ▼│66+ └───┴─┴─────────────────┘ ▼67+68+69+ ASCII Unicode BOX characters70+71+ X X72+ ^ ▲ X73+ | │ ▲74+ X <--*--> X X ◄──┼──► X X ◄─┼─► X75+ | │ ▼76+ v ▼ X77+ X X78+79+Not recognized by AsciiFlow as a line-terminating drawing character:80+the BLACK CIRCLE, Unicode code point 0x25CF:81+82+ X83+ ● X84+ │ ●85+ X ●──┼──● X X ●─┼─● X86+ │ ●87+ ● X88+ X89+90+```91+---92+![XXX missing local asciiflow.svg](./examples/asciiflow.svg)93+### Input from Uniline.el94+```95+Unicode BOX characters generatable by uniline.el, but neither of AsciiFlow nor Cascii:96+97+ ╭╮ ╭─╮98+ ╰╯ │X│99+ ╰─╯100+ ╭╌╌╮101+ ┊ ┊102+ ╰╌╌╯103+104+```105+---106+![XXX missing local asciiflow.svg](./examples/uniline.svg)
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/utf8/README.md.tmpl +46 -0
@@ -0,0 +1,46 @@
1+# UTF-8 Box-drawing mode2+3+You will also need and editor capable of manipulating the Unicode4+ BOX characters (code points 0x25xx).5+6+Ordinary text editors with "rectangle-mode" support may be suitable.7+More ergonomic are:8+ - [Cascii](https://github.com/casparwylie/cascii-core)9+ - [AsciiFlow](https://github.com/lewish/asciiflow)10+ - For Emacs users, ```picture-mode``` or [uniline.el](https://github.com/tbanel/uniline)11+12+## Font Compatibility13+GoAT requires curating one's choice of fonts, to avoid introducing characters of non-standard width or height14+ into the rectangular grid.15+Use of utf8-mode generally tightens the curation requirement, because many popular body fonts, while16+ though nominally monospace, actually select Unicode box-drawing glyphs of non-standard dimension.17+18+Known suitable font, available on Debian distros: "DejaVu Sans Mono".19+20+## Example Graphics21+A highly abstract view of GoAT's internal data flow, using UTF-8 text:22+```23+{{include "./_README/dataflow.utf8.txt"}}24+```25+Processed to SVG, with garishly colorful CSS styling from26+[`embed:palette/bold.css`](./css/palette/bold.css):27+28+![XXX missing local SVG](./_README/dataflow.bold.svg)29+30+Processed to SVG, with unobtrusive colors from31+[`embed:palette/earth.css`](./css/palette/earth.css):32+33+![XXX missing local SVG](./_README/dataflow.earth.svg)34+35+### Input from AsciiFlow36+```37+{{.examples_asciiflow_txt}}38+```39+---40+![XXX missing local asciiflow.svg]({{.examples_DIR}}/asciiflow.svg)41+### Input from Uniline.el42+```43+{{include "./examples/uniline.txt"}}44+```45+---46+![XXX missing local asciiflow.svg]({{.examples_DIR}}/uniline.svg)
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/utf8/joints.go +110 -0
@@ -0,0 +1,110 @@
1+package utf82+3+import (4+ "github.com/blampe/goat"5+ "github.com/blampe/goat/svg"6+)7+8+// X Fatal error on unsupported BOX characters, rather than treating9+// simply as text?10+11+// X Should "dangling" BOX DRAWINGS ends be supported in SVG output?12+// => Principle of 'least surprise': allow, as normal case.13+14+// What about 'BOX DRAWINGS HEAVY *'? => no rounded corners available15+16+var squareCornerRunes = goat.MakeRuneSet(17+ // Orientation18+ // | abutment with Line's19+ // | | |20+ '┌', // svg.O_NW: O_E start O_S start21+ '└', // svg.O_SW: O_E start O_S stop22+ '┐', // svg.O_NE: O_E stop O_S start23+ '┘', // svg.O_SE: O_E stop O_S stop24+)25+26+// BOX DRAWINGS LIGHT ARC characters27+var roundedCornerRunes = goat.MakeRuneSet(28+ // Orientation29+ // | abutment with Line's30+ // | | |31+ '╭', // svg.O_NW: O_E start O_S start32+ '╰', // svg.O_SW: O_E start O_S stop33+ '╮', // svg.O_NE: O_E stop O_S start34+ '╯', // svg.O_SE: O_E stop O_S stop35+)36+37+var roundedCornerCenters = map[svg.Orientation]svg.Pixel{38+ svg.O_NW: { X: cornerRadius, Y: cornerRadius}, // '╭'39+ svg.O_SW: { X: cornerRadius, Y:-cornerRadius}, // '╰'40+ svg.O_NE: { X:-cornerRadius, Y: cornerRadius}, // '╮'41+ svg.O_SE: { X:-cornerRadius, Y:-cornerRadius}, // '╯'42+}43+func (c *Canvas) CenterPixel(rc svg.RoundedCorner) svg.Pixel {44+ cellCenter := rc.Start.AsPixel()45+ return cellCenter.Sum(roundedCornerCenters[rc.Orientation])46+}47+48+var boxJointRunes = goat.UnionSets(49+ squareCornerRunes,50+ roundedCornerRunes,51+ goat.MakeRuneSet(52+ '┬',53+ '┴',54+ '┤',55+ '├',56+ '┼',57+ ),58+)59+60+// Meaning is "draw a Line, possibly extending into the adjacent cell61+// lying on side 'Orientation'."62+var connects = make(map[svg.Orientation]goat.RuneSet)63+64+func init() {65+ connects[svg.O_E] = goat.MakeRuneSet(66+ '╶',67+ '─', // BOX DRAWINGS LIGHT HORIZONTAL68+ '╭', // BOX DRAWINGS LIGHT ARC69+ '╰',70+ '┌',71+ '└',72+ '┬',73+ '┴',74+ '├',75+ '┼')76+ connects[svg.O_W] = goat.MakeRuneSet(77+ '╴',78+ '─',79+ '╮',80+ '╯',81+ '┐',82+ '┘',83+ '┬',84+ '┴',85+ '┤',86+ '┼')87+88+ connects[svg.O_S] = goat.MakeRuneSet(89+ '╷',90+ '│', // BOX DRAWINGS LIGHT VERTICAL91+ '╮',92+ '╭',93+ '┌',94+ '┐',95+ '┬',96+ '┤',97+ '├',98+ '┼')99+ connects[svg.O_N] = goat.MakeRuneSet(100+ '╵',101+ '│',102+ '╯',103+ '╰',104+ '└',105+ '┘',106+ '┴',107+ '┤',108+ '├',109+ '┼')110+}
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/utf8/u_drawable.go +144 -0
@@ -0,0 +1,144 @@
1+package utf82+3+import (4+ "io"5+6+ "github.com/blampe/goat/internal"7+ "github.com/blampe/goat/svg"8+)9+10+const (11+ W = svg.CellWidth // XX DRY with hard-coded constants in a_line.go et al.12+ H = svg.CellHeight13+14+ CIRCLERADIUS = W / 215+ cornerRadius = W / 216+)17+18+// WriteSVGBody writes the entire content of a Canvas out to a stream in SVG format.19+// XX Produces a complete <g ...>...</g> expression -- rename accordingly?20+func (c *Canvas) WriteSVGBody(dst io.Writer, config *svg.Config) {21+ // XX Refactor for readability.22+ wb := func(fmt string, s ...interface{}) {23+ internal.MustFPrintf(dst, fmt, s...)24+ }25+26+ wb(" <g id='%s'>\n", "lines-vertical")27+ for _, lv := range c.getlines(svg.UpDownMinor, svg.O_S) {28+ c.DrawLine(lv, dst)29+ }30+ wb(" </g>\n")31+32+ wb(" <g id='%s'>\n", "lines-horizontal")33+ for _, lh := range c.getlines(svg.LeftRightMinor, svg.O_E) {34+ c.DrawLine(lh, dst)35+ }36+ wb(" </g>\n")37+38+ // XX unify with '/ascii'39+ wb(" <g id='%s'>\n", "triangles")40+ for _, t := range c.triangles() {41+ t.Draw(dst)42+ }43+ wb(" </g>\n")44+45+ // Unicode's tightly-rounded "BOX LIGHT" corners, as46+ // parallel to Ascii-mode's widely-rounded.47+ // XX Not as easily discoverable as wanted:48+ // An svg.RoundedCorner struct contains two fields, Start and Orientation49+ wb(" <g id='%s'>\n", "roundedCorners")50+ for _, rc := range c.roundedCorners() {51+ rc.DrawCentered(dst, c.CenterPixel(rc), cornerRadius)52+ }53+ wb(" </g>\n")54+55+ // XX unify with '/ascii'56+ wb(" <g id='%s'>\n", "circles")57+ for _, ci := range c.circles() {58+ ci.Draw(dst, CIRCLERADIUS)59+ }60+ wb(" </g>\n")61+62+ wb(" <g id='%s'>\n", "text")63+ svg.Writetext(dst, config, c)64+ wb(" </g>\n")65+}66+67+func (c *Canvas) triangles() (triangles []smallTriangle) {68+ for _, idx := range svg.UpDownMinor(c.Width, c.Height) {69+ r := c.RuneAt(idx)70+71+ o := svg.O_NONE72+ // Identify orientation and nudge the triangle to touch any73+ // adjacent walls.74+ switch r {75+ case '▲':76+ o = svg.O_N77+ case '▼':78+ o = svg.O_S79+ case '◀':80+ fallthrough81+ case '◄':82+ o = svg.O_W83+ case '▶':84+ fallthrough85+ case '►':86+ o = svg.O_E87+ default:88+ continue89+ }90+ start := idx91+ triangles = append(92+ triangles,93+ smallTriangle{94+ Start: start,95+ Orientation: o,96+ //NeedsNudging: needsNudging,97+ },98+ )99+ }100+ return101+}102+103+func (c *Canvas) circles() (circles []svg.Circle) {104+ for _, idx := range svg.UpDownMinor(c.Width, c.Height) {105+ // TODO INCOMING106+ if c.RuneAt(idx) == '○' {107+ circles = append(circles, svg.Circle{Start: idx})108+ } else if c.RuneAt(idx) == '●' {109+ circles = append(circles, svg.Circle{Start: idx, Bold: true})110+ }111+ }112+ return113+}114+115+// XX Exact copy of ascii.roundedCorners(), except that Canvas are different types => DRY?116+// roundedCorners returns a slice of all curvy corners in the diagram.117+func (c *Canvas) roundedCorners() (corners []svg.RoundedCorner) {118+ for _, idx := range svg.LeftRightMinor(c.Width, c.Height) {119+ if o := c.isroundedCorner(idx); o != svg.O_NONE { // XX specific to 'utf8'120+ // XX demultiplex into an array by Orientation of slices, right here?121+ corners = append(122+ corners,123+ svg.RoundedCorner{Start: idx, Orientation: o},124+ )125+ }126+ }127+ return128+}129+130+// cf: 'var roundedCornerRunes'131+func (c *Canvas) isroundedCorner(i svg.XyIndex) svg.Orientation {132+ r := c.RuneAt(i)133+ switch r {134+ case '╭':135+ return svg.O_NW136+ case '╰':137+ return svg.O_SW138+ case '╮':139+ return svg.O_NE140+ case '╯':141+ return svg.O_SE142+ }143+ return svg.O_NONE144+}
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/utf8/u_line.go +203 -0
@@ -0,0 +1,203 @@
1+package utf82+3+import (4+ "io"5+6+ "github.com/blampe/goat/svg"7+)8+9+// Principle: Treat the two axes completely independently.10+// Therefore, input "┼─┬" produces three line structs.11+type line struct { // XX take local12+ Started bool // XX could be local variable of constructor loop?13+14+ // These point to 'segment' characters only in the case of "bare end" --15+ // more commonly they point to adjoining junction characters.16+ Start, Stop svg.XyIndex17+18+ // Always one of the compass points O_E, O_S.19+ Orientation svg.Orientation20+}21+22+func (c *Canvas) SetStart(l *line, i svg.XyIndex) {23+ if l.Started {24+ panic("Already started")25+ }26+ l.Start = i27+ l.Started = true28+}29+30+func (c *Canvas) SetStop(l *line, i svg.XyIndex) {31+ if !l.Started {32+ panic("not started")33+ }34+ l.Stop = i35+}36+37+func reverse(o svg.Orientation) svg.Orientation {38+ switch o {39+ case svg.O_E:40+ return svg.O_W41+ case svg.O_S:42+ return svg.O_N43+ }44+ panic("unexpected svg.Orientation")45+}46+47+// Recall that spatial precision of output line{} is whole character cells.48+//49+// In the baseline case, an SVG line is to be drawn from the center of line.Start to center of line.End .50+// Adjustments to abut well with neighbors happen later.51+func (c *Canvas) getlines(52+ ci svg.CanvasIterator, // the order that the loop below traverses cells on the canvas.53+ minor_axis svg.Orientation, // either O_E or O_S54+) (lines []line) {55+56+ reverse := reverse(minor_axis) // either O_W or O_N57+58+ // Write 'currentLine' onto the output.59+ // line may be of apparently zero-length i.e. contained within a single cell.60+ outputLine := func(l line) line {61+ lines = append(lines, l)62+63+ // start a new one.64+ // XX Writing the orientation into the line struct is65+ // redundant (now, with separated slices).66+ return line{Orientation: minor_axis}67+ }68+69+ currentline := line{Orientation: minor_axis}70+ for _, idx := range ci(c.Width+1, c.Height+1) {71+ r := c.RuneAt(idx)72+73+ if !currentline.Started {74+ if connects[reverse].Contains(r) {75+ // Half-cell-long segment76+ c.SetStart(&currentline, idx)77+ c.SetStop(&currentline, idx)78+ continue79+ }80+ if connects[minor_axis].Contains(r) {81+ c.SetStart(&currentline, idx)82+ c.SetStop(&currentline, idx)83+ }84+ continue85+ }86+87+ if connects[minor_axis].Contains(r) {88+ // Keep the line going and extend it by one cell.89+ c.SetStop(&currentline, idx)90+ } else {91+ if connects[reverse].Contains(r) {92+ // Terminate the line at a BOX T-intersection, or a triangle93+ c.SetStop(&currentline, idx)94+ } else {95+ // terminate at a dead end96+ // Stop is as set in previous iteration97+ }98+ currentline = outputLine(currentline)99+ }100+ }101+ return102+}103+104+// Draw a straight line as an SVG path.105+//106+// Cases:107+// 1. Unterminated lines composed of '─' or '│' faithfully -- extend to108+// limit of end cells, just as for interior '─' or '│'.109+//110+// 2. If a terminal circle or square, possibly open, lies at either end,111+// extend so as to abut there.112+//113+// 3. Isolated joints e.g. '┬' or '┼': Decompose into horizontal and vertical,114+// each independent of the other.115+116+func (c *Canvas) DrawLine(l line, out io.Writer) {117+ startPix := c.startingPixel(l)118+ stopPix := c.stoppingPixel(l)119+120+ if startPix.X == stopPix.X && startPix.Y == stopPix.Y {121+ return122+ }123+124+ svg.WritePolyline(out, startPix, stopPix)125+}126+127+func (c *Canvas) startingPixel(l line) svg.Pixel {128+ // initial values of these are at centers of cells -- possibly adjusted later129+ startPix := l.Start.AsPixel()130+ startRune := c.RuneAt(l.Start)131+132+ switch l.Orientation {133+ case svg.O_E:134+ if startRune == '╭' || startRune == '╰' {135+ startPix.X += cornerRadius136+ } else if connects[reverse(l.Orientation)].Contains(startRune) {137+ westRune := c.RuneAt(l.Start.West())138+ startTriangleBase := leftArrowheadRunes.Contains(westRune)139+140+ startPix.X -= W / 2141+ if startTriangleBase {142+ startPix.X -= W / 4143+ }144+ }145+ case svg.O_S:146+ if startRune == '╭' || startRune == '╮' {147+ startPix.Y += cornerRadius148+ } else if connects[reverse(l.Orientation)].Contains(startRune) {149+ // If either end abuts a circle, extend drawing to the edge of the circle,150+ // rather extending as usual to center of the cell.151+ northRune := c.RuneAt(l.Start.North())152+ startTriangleBase := northRune == '▲'153+154+ startPix.Y -= H / 2155+ if isDot(northRune) {156+ startPix.Y -= H / 4157+ } else if startTriangleBase {158+ startPix.Y -= H / 1159+ }160+ }161+ }162+ return startPix163+}164+165+func (c *Canvas) stoppingPixel(l line) svg.Pixel {166+ // initial values of these are at centers of cells -- possibly adjusted later167+ stopPix := l.Stop.AsPixel()168+ stopRune := c.RuneAt(l.Stop)169+170+ switch l.Orientation {171+ case svg.O_E:172+ if stopRune == '╮' || stopRune == '╯' {173+ stopPix.X -= cornerRadius174+ } else if connects[l.Orientation].Contains(stopRune) {175+ eastRune := c.RuneAt(l.Stop.East())176+ stopTriangleBase := rightArrowheadRunes.Contains(eastRune)177+178+ // extend to edge of cell179+ stopPix.X += W / 2180+ if stopTriangleBase {181+ stopPix.X += W / 4182+ }183+ }184+ case svg.O_S:185+ if stopRune == '╯' || stopRune == '╰' {186+ stopPix.Y -= cornerRadius187+ } else if connects[l.Orientation].Contains(stopRune) {188+ // If either end abuts a circle, extend drawing to the edge of the circle,189+ // rather extending as usual to center of the cell.190+ southRune := c.RuneAt(l.Stop.South())191+ stopTriangleBase := southRune == '▼'192+193+ // draw from center to "forward" edge of cell194+ stopPix.Y += H / 2195+ if isDot(southRune) {196+ stopPix.Y += H / 4197+ } else if stopTriangleBase {198+ stopPix.Y += H / 1199+ }200+ }201+ }202+ return stopPix203+}
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/utf8/u_svg.go +59 -0
A diagramzip-render/goat-wasm/vendor/github.com/blampe/goat/utf8/utf8.go +111 -0
@@ -0,0 +1,111 @@
1+/*2+ Format Unicode BOX─art into SVG image files.3+*/4+package utf85+6+import (7+ "io"8+9+ "github.com/blampe/goat"10+ "github.com/blampe/goat/svg"11+)12+13+// X Differs from ascii.Canvas by the methods bound to it (see below).14+type Canvas struct {15+ // ? make explicitly a local member, requiring dot-qualification?16+ // XX would require wrapper accessor methods for .Width, .Height, RuneAt().17+ svg.CanvasCommon18+}19+20+func (ac *Canvas) GetCommon() *svg.CanvasCommon {21+ return &ac.CanvasCommon22+}23+24+// Copy-paste of ascii.NewCanvas() -- difference is in data types utf8.Canvas vs ascii.Canvas25+// XX simplify, by pushing call to svg.NewCanvasCommon() up to client?26+func NewCanvas(config *svg.Config, in io.Reader) svg.AbstractCanvas {27+ c := Canvas{28+ CanvasCommon: svg.NewCanvasCommon(config, in),29+ }30+ // Fill the 'TextRunes' map, with runes removed from 'data', according to c.ShouldMoveToTextRunes()31+ svg.MoveToText(&c)32+ return &c33+}34+35+// XX XX Generalize to include '┆'36+var verticalRunes = goat.MakeRuneSet(37+ '│', // BOX DRAWINGS LIGHT VERTICAL38+ '╷',39+ '╵',40+)41+// XX XX Generalize to include '╌'42+var horizontalRunes = goat.MakeRuneSet(43+ '─', // BOX DRAWINGS LIGHT HORIZONTAL44+ '╶',45+ '╴',46+)47+48+// A/K/A "triangles"49+var verticalArrowheadRunes = goat.MakeRuneSet(50+ '▼', // ▼51+ '▲', // ▲52+)53+var leftArrowheadRunes = goat.MakeRuneSet(54+ '◀', // ◀55+ '◄', // ◄56+)57+var rightArrowheadRunes = goat.MakeRuneSet(58+ '▶', // ▶59+ '►', // ►60+)61+var horizontalArrowheadRunes = goat.UnionSets(62+ leftArrowheadRunes,63+ rightArrowheadRunes,64+)65+var arrowheadRunes = goat.UnionSets(66+ verticalArrowheadRunes,67+ horizontalArrowheadRunes,68+)69+70+// X Parameterize the output SVG <circle> with CSS to create the variants.71+var dotRunes = goat.MakeRuneSet(72+ '●', // ● BLACK CIRCLE 0x25CF73+ '○', // ○ WHITE CIRCLE 0x25CB74+ //'◌', // ◌ X requires <circle> with reference to a CSS class for the dot patterning75+ //'◠', // ◠ XX draw with <path>, containing a 180-degree arc.76+ //'◡', // ◡77+)78+79+// X Parameterize the output SVG <rect> with CSS to create the variants.80+var squareRunes = goat.MakeRuneSet(81+ //'◼', // ◼82+ //'◻', // ◻83+ //'⬚', // ⬚84+ //'▢', // ▢85+ //'◾', // ◾86+ //'▫', // ▫87+)88+var boxEdgeRunes = goat.UnionSets(89+ verticalRunes,90+ horizontalRunes,91+)92+var ReservedSet = goat.UnionSets(93+ boxJointRunes, boxEdgeRunes, arrowheadRunes, dotRunes,94+ goat.MakeRuneSet(95+ ' ', // X SPACE is "reserved"96+ ))97+98+// XX rename 'isCircle()'?99+func isDot(r rune) bool {100+ return dotRunes.Contains(r)101+}102+103+func (c *Canvas) ShouldMoveToTextRunes(i svg.XyIndex) bool {104+ i_r := c.RuneAt(i)105+ // character := string(i_r); _ = character // for debug106+107+ if _, found := ReservedSet[i_r]; !found {108+ return true109+ }110+ return false111+}
A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/.gitattributes +1 -0
A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/.gitignore +5 -0
A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/.golangci.yml +16 -0
A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/LICENSE.md +22 -0
A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/README.md +64 -0
@@ -0,0 +1,64 @@
1+# Parse [![API reference](https://img.shields.io/badge/godoc-reference-5272B4)](https://pkg.go.dev/github.com/tdewolff/parse/v2?tab=doc) [![Go Report Card](https://goreportcard.com/badge/github.com/tdewolff/parse)](https://goreportcard.com/report/github.com/tdewolff/parse) [![Coverage Status](https://coveralls.io/repos/github/tdewolff/parse/badge.svg?branch=master)](https://coveralls.io/github/tdewolff/parse?branch=master) [![Donate](https://img.shields.io/badge/patreon-donate-DFB317)](https://www.patreon.com/tdewolff)2+3+This package contains several lexers and parsers written in [Go][1]. All subpackages are built to be streaming, high performance and to be in accordance with the official (latest) specifications.4+5+The lexers are implemented using `buffer.Lexer` in https://github.com/tdewolff/parse/buffer and the parsers work on top of the lexers. Some subpackages have hashes defined (using [Hasher](https://github.com/tdewolff/hasher)) that speed up common byte-slice comparisons.6+7+## Buffer8+### Reader9+Reader is a wrapper around a `[]byte` that implements the `io.Reader` interface. It is comparable to `bytes.Reader` but has slightly different semantics (and a slightly smaller memory footprint).10+11+### Writer12+Writer is a buffer that implements the `io.Writer` interface and expands the buffer as needed. The reset functionality allows for better memory reuse. After calling `Reset`, it will overwrite the current buffer and thus reduce allocations.13+14+### Lexer15+Lexer is a read buffer specifically designed for building lexers. It keeps track of two positions: a start and end position. The start position is the beginning of the current token being parsed, the end position is being moved forward until a valid token is found. Calling `Shift` will collapse the positions to the end and return the parsed `[]byte`.16+17+Moving the end position can go through `Move(int)` which also accepts negative integers. One can also use `Pos() int` to try and parse a token, and if it fails rewind with `Rewind(int)`, passing the previously saved position.18+19+`Peek(int) byte` will peek forward (relative to the end position) and return the byte at that location. `PeekRune(int) (rune, int)` returns UTF-8 runes and its length at the given **byte** position. Upon an error `Peek` will return `0`, the **user must peek at every character** and not skip any, otherwise it may skip a `0` and panic on out-of-bounds indexing.20+21+`Lexeme() []byte` will return the currently selected bytes, `Skip()` will collapse the selection. `Shift() []byte` is a combination of `Lexeme() []byte` and `Skip()`.22+23+When the passed `io.Reader` returned an error, `Err() error` will return that error even if not at the end of the buffer.24+25+### StreamLexer26+StreamLexer behaves like Lexer but uses a buffer pool to read in chunks from `io.Reader`, retaining old buffers in memory that are still in use, and re-using old buffers otherwise. Calling `Free(n int)` frees up `n` bytes from the internal buffer(s). It holds an array of buffers to accommodate for keeping everything in-memory. Calling `ShiftLen() int` returns the number of bytes that have been shifted since the previous call to `ShiftLen`, which can be used to specify how many bytes need to be freed up from the buffer. If you don't need to keep returned byte slices around, call `Free(ShiftLen())` after every `Shift` call.27+28+## Strconv29+This package contains string conversion function much like the standard library's `strconv` package, but it is specifically tailored for the performance needs within the `minify` package.30+31+For example, the floating-point to string conversion function is approximately twice as fast as the standard library, but it is not as precise.32+33+## CSS34+This package is a CSS3 lexer and parser. Both follow the specification at [CSS Syntax Module Level 3](http://www.w3.org/TR/css-syntax-3/). The lexer takes an io.Reader and converts it into tokens until the EOF. The parser returns a parse tree of the full io.Reader input stream, but the low-level `Next` function can be used for stream parsing to returns grammar units until the EOF.35+36+[See README here](https://github.com/tdewolff/parse/tree/master/css).37+38+## HTML39+This package is an HTML5 lexer. It follows the specification at [The HTML syntax](http://www.w3.org/TR/html5/syntax.html). The lexer takes an io.Reader and converts it into tokens until the EOF.40+41+[See README here](https://github.com/tdewolff/parse/tree/master/html).42+43+## JS44+This package is a JS lexer (ECMA-262, edition 6.0). It follows the specification at [ECMAScript Language Specification](http://www.ecma-international.org/ecma-262/6.0/). The lexer takes an io.Reader and converts it into tokens until the EOF.45+46+[See README here](https://github.com/tdewolff/parse/tree/master/js).47+48+## JSON49+This package is a JSON parser (ECMA-404). It follows the specification at [JSON](http://json.org/). The parser takes an io.Reader and converts it into tokens until the EOF.50+51+[See README here](https://github.com/tdewolff/parse/tree/master/json).52+53+## SVG54+This package contains common hashes for SVG1.1 tags and attributes.55+56+## XML57+This package is an XML1.0 lexer. It follows the specification at [Extensible Markup Language (XML) 1.0 (Fifth Edition)](http://www.w3.org/TR/xml/). The lexer takes an io.Reader and converts it into tokens until the EOF.58+59+[See README here](https://github.com/tdewolff/parse/tree/master/xml).60+61+## License62+Released under the [MIT license](LICENSE.md).63+64+[1]: http://golang.org/ "Go Language"
A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/binary.go +792 -0
@@ -0,0 +1,792 @@
1+package parse2+3+import (4+ "encoding/binary"5+ "errors"6+ "fmt"7+ "io"8+ "math"9+ "os"10+)11+12+const PageSize = 409613+14+// BinaryReader is a binary big endian file format reader.15+type BinaryReader struct {16+ Endianness binary.ByteOrder17+ buf []byte18+ pos uint3219+ eof bool20+}21+22+// NewBinaryReader returns a big endian binary file format reader.23+func NewBinaryReader(buf []byte) *BinaryReader {24+ if math.MaxUint32 < uint(len(buf)) {25+ return &BinaryReader{binary.BigEndian, nil, 0, true}26+ }27+ return &BinaryReader{binary.BigEndian, buf, 0, false}28+}29+30+// NewBinaryReaderLE returns a little endian binary file format reader.31+func NewBinaryReaderLE(buf []byte) *BinaryReader {32+ r := NewBinaryReader(buf)33+ r.Endianness = binary.LittleEndian34+ return r35+}36+37+// Seek set the reader position in the buffer.38+func (r *BinaryReader) Seek(pos uint32) error {39+ if uint32(len(r.buf)) < pos {40+ r.eof = true41+ return io.EOF42+ }43+ r.pos = pos44+ r.eof = false45+ return nil46+}47+48+// Pos returns the reader's position.49+func (r *BinaryReader) Pos() uint32 {50+ return r.pos51+}52+53+// Len returns the remaining length of the buffer.54+func (r *BinaryReader) Len() uint32 {55+ return uint32(len(r.buf)) - r.pos56+}57+58+// EOF returns true if we reached the end-of-file.59+func (r *BinaryReader) EOF() bool {60+ return r.eof61+}62+63+// Read complies with io.Reader.64+func (r *BinaryReader) Read(b []byte) (int, error) {65+ n := copy(b, r.buf[r.pos:])66+ r.pos += uint32(n)67+ if r.pos == uint32(len(r.buf)) {68+ r.eof = true69+ return n, io.EOF70+ }71+ return n, nil72+}73+74+// ReadBytes reads n bytes.75+func (r *BinaryReader) ReadBytes(n uint32) []byte {76+ if r.eof || uint32(len(r.buf))-r.pos < n {77+ r.eof = true78+ return nil79+ }80+ buf := r.buf[r.pos : r.pos+n : r.pos+n]81+ r.pos += n82+ return buf83+}84+85+// ReadString reads a string of length n.86+func (r *BinaryReader) ReadString(n uint32) string {87+ return string(r.ReadBytes(n))88+}89+90+// ReadByte reads a single byte.91+func (r *BinaryReader) ReadByte() byte {92+ b := r.ReadBytes(1)93+ if b == nil {94+ return 095+ }96+ return b[0]97+}98+99+// ReadUint8 reads a uint8.100+func (r *BinaryReader) ReadUint8() uint8 {101+ return r.ReadByte()102+}103+104+// ReadUint16 reads a uint16.105+func (r *BinaryReader) ReadUint16() uint16 {106+ b := r.ReadBytes(2)107+ if b == nil {108+ return 0109+ }110+ return r.Endianness.Uint16(b)111+}112+113+// ReadUint32 reads a uint32.114+func (r *BinaryReader) ReadUint32() uint32 {115+ b := r.ReadBytes(4)116+ if b == nil {117+ return 0118+ }119+ return r.Endianness.Uint32(b)120+}121+122+// ReadUint64 reads a uint64.123+func (r *BinaryReader) ReadUint64() uint64 {124+ b := r.ReadBytes(8)125+ if b == nil {126+ return 0127+ }128+ return r.Endianness.Uint64(b)129+}130+131+// ReadInt8 reads a int8.132+func (r *BinaryReader) ReadInt8() int8 {133+ return int8(r.ReadByte())134+}135+136+// ReadInt16 reads a int16.137+func (r *BinaryReader) ReadInt16() int16 {138+ return int16(r.ReadUint16())139+}140+141+// ReadInt32 reads a int32.142+func (r *BinaryReader) ReadInt32() int32 {143+ return int32(r.ReadUint32())144+}145+146+// ReadInt64 reads a int64.147+func (r *BinaryReader) ReadInt64() int64 {148+ return int64(r.ReadUint64())149+}150+151+type BinaryFileReader struct {152+ f *os.File153+ size uint64154+ offset uint64155+156+ Endianness binary.ByteOrder157+ buf []byte158+ pos int159+}160+161+func NewBinaryFileReader(f *os.File, chunk int) (*BinaryFileReader, error) {162+ var buf []byte163+ var size uint64164+ if chunk == 0 {165+ var err error166+ if buf, err = io.ReadAll(f); err != nil {167+ return nil, err168+ }169+ } else {170+ buf = make([]byte, 0, chunk)171+ }172+ if info, err := f.Stat(); err != nil {173+ return nil, err174+ } else {175+ size = uint64(info.Size())176+ }177+ return &BinaryFileReader{178+ f: f,179+ size: size,180+ Endianness: binary.BigEndian,181+ buf: buf,182+ }, nil183+}184+185+func (r *BinaryFileReader) buffer(pos, length uint64) error {186+ if pos < r.offset || r.offset+uint64(len(r.buf)) < pos+length {187+ if math.MaxInt64 < pos {188+ return fmt.Errorf("seek position too large")189+ } else if _, err := r.f.Seek(int64(pos), 0); err != nil {190+ return err191+ } else if n, err := r.f.Read(r.buf[:cap(r.buf)]); err != nil {192+ return err193+ } else {194+ r.offset = pos195+ r.buf = r.buf[:n]196+ r.pos = 0197+ }198+ }199+ return nil200+}201+202+// Seek set the reader position in the buffer.203+func (r *BinaryFileReader) Seek(pos uint64) error {204+ if r.size <= pos {205+ return io.EOF206+ } else if err := r.buffer(pos, 0); err != nil {207+ return err208+ }209+ r.pos = int(pos - r.offset)210+ return nil211+}212+213+// Pos returns the reader's position.214+func (r *BinaryFileReader) Pos() uint64 {215+ return r.offset + uint64(r.pos)216+}217+218+// Len returns the remaining length of the buffer.219+func (r *BinaryFileReader) Len() uint64 {220+ return r.size - r.Pos()221+}222+223+// Offset returns the offset of the buffer.224+func (r *BinaryFileReader) Offset() uint64 {225+ return r.offset226+}227+228+// BufferLen returns the length of the buffer.229+func (r *BinaryFileReader) BufferLen() int {230+ return len(r.buf)231+}232+233+// Read complies with io.Reader.234+func (r *BinaryFileReader) Read(b []byte) (int, error) {235+ if len(b) <= cap(r.buf) {236+ if err := r.buffer(r.offset+uint64(r.pos), uint64(len(b))); err != nil {237+ return 0, err238+ }239+ n := copy(b, r.buf[r.pos:])240+ r.pos += n241+ return n, nil242+ }243+244+ // read directly from file245+ if _, err := r.f.Seek(int64(r.offset)+int64(r.pos), 0); err != nil {246+ return 0, err247+ }248+ n, err := r.f.Read(b)249+ r.offset += uint64(r.pos + n)250+ r.pos = 0251+ r.buf = r.buf[:0]252+ return n, err253+}254+255+// ReadBytes reads n bytes.256+func (r *BinaryFileReader) ReadBytes(n int) []byte {257+ if n < len(r.buf)-r.pos {258+ b := r.buf[r.pos : r.pos+n]259+ r.pos += n260+ return b261+ }262+263+ b := make([]byte, n)264+ if _, err := r.Read(b); err != nil {265+ return nil266+ }267+ return b268+}269+270+// ReadString reads a string of length n.271+func (r *BinaryFileReader) ReadString(n int) string {272+ return string(r.ReadBytes(n))273+}274+275+// ReadByte reads a single byte.276+func (r *BinaryFileReader) ReadByte() byte {277+ b := r.ReadBytes(1)278+ if b == nil {279+ return 0280+ }281+ return b[0]282+}283+284+// ReadUint8 reads a uint8.285+func (r *BinaryFileReader) ReadUint8() uint8 {286+ return r.ReadByte()287+}288+289+// ReadUint16 reads a uint16.290+func (r *BinaryFileReader) ReadUint16() uint16 {291+ b := r.ReadBytes(2)292+ if b == nil {293+ return 0294+ }295+ return r.Endianness.Uint16(b)296+}297+298+// ReadUint32 reads a uint32.299+func (r *BinaryFileReader) ReadUint32() uint32 {300+ b := r.ReadBytes(4)301+ if b == nil {302+ return 0303+ }304+ return r.Endianness.Uint32(b)305+}306+307+// ReadUint64 reads a uint64.308+func (r *BinaryFileReader) ReadUint64() uint64 {309+ b := r.ReadBytes(8)310+ if b == nil {311+ return 0312+ }313+ return r.Endianness.Uint64(b)314+}315+316+// ReadInt8 reads a int8.317+func (r *BinaryFileReader) ReadInt8() int8 {318+ return int8(r.ReadByte())319+}320+321+// ReadInt16 reads a int16.322+func (r *BinaryFileReader) ReadInt16() int16 {323+ return int16(r.ReadUint16())324+}325+326+// ReadInt32 reads a int32.327+func (r *BinaryFileReader) ReadInt32() int32 {328+ return int32(r.ReadUint32())329+}330+331+// ReadInt64 reads a int64.332+func (r *BinaryFileReader) ReadInt64() int64 {333+ return int64(r.ReadUint64())334+}335+336+type IBinaryReader interface {337+ Close() error338+ Len() int339+ Bytes(int, int64) ([]byte, error)340+}341+342+type binaryReaderFile struct {343+ f *os.File344+ size int64345+}346+347+func newBinaryReaderFile(filename string) (*binaryReaderFile, error) {348+ f, err := os.Open(filename)349+ if err != nil {350+ return nil, err351+ }352+ fi, err := f.Stat()353+ if err != nil {354+ f.Close()355+ return nil, err356+ }357+ return &binaryReaderFile{f, fi.Size()}, nil358+}359+360+// Close closes the reader.361+func (r *binaryReaderFile) Close() error {362+ return r.f.Close()363+}364+365+// Len returns the length of the underlying memory-mapped file.366+func (r *binaryReaderFile) Len() int {367+ return int(r.size)368+}369+370+func (r *binaryReaderFile) Bytes(n int, off int64) ([]byte, error) {371+ if _, err := r.f.Seek(off, 0); err != nil {372+ return nil, err373+ }374+375+ b := make([]byte, n)376+ m, err := r.f.Read(b)377+ if err != nil {378+ return nil, err379+ } else if m != n {380+ return nil, errors.New("file: could not read all bytes")381+ }382+ return b, nil383+}384+385+type binaryReaderBytes struct {386+ data []byte387+}388+389+func newBinaryReaderBytes(data []byte) (*binaryReaderBytes, error) {390+ return &binaryReaderBytes{data}, nil391+}392+393+// Close closes the reader.394+func (r *binaryReaderBytes) Close() error {395+ return nil396+}397+398+// Len returns the length of the underlying memory-mapped file.399+func (r *binaryReaderBytes) Len() int {400+ return len(r.data)401+}402+403+func (r *binaryReaderBytes) Bytes(n int, off int64) ([]byte, error) {404+ if off < 0 || int64(len(r.data)) < off {405+ return nil, fmt.Errorf("bytes: invalid offset %d", off)406+ }407+ return r.data[off : off+int64(n) : off+int64(n)], nil408+}409+410+type binaryReaderReader struct {411+ r io.Reader412+ n int64413+ readerAt bool414+ seeker bool415+}416+417+func newBinaryReaderReader(r io.Reader, n int64) (*binaryReaderReader, error) {418+ _, readerAt := r.(io.ReaderAt)419+ _, seeker := r.(io.Seeker)420+ return &binaryReaderReader{r, n, readerAt, seeker}, nil421+}422+423+// Close closes the reader.424+func (r *binaryReaderReader) Close() error {425+ if closer, ok := r.r.(io.Closer); ok {426+ return closer.Close()427+ }428+ return nil429+}430+431+// Len returns the length of the underlying memory-mapped file.432+func (r *binaryReaderReader) Len() int {433+ return int(r.n)434+}435+436+func (r *binaryReaderReader) Bytes(n int, off int64) ([]byte, error) {437+ // seeker seems faster than readerAt by 10%438+ if r.seeker {439+ if _, err := r.r.(io.Seeker).Seek(off, 0); err != nil {440+ return nil, err441+ }442+443+ b := make([]byte, n)444+ m, err := r.r.Read(b)445+ if err != nil {446+ return nil, err447+ } else if m != n {448+ return nil, errors.New("file: could not read all bytes")449+ }450+ return b, nil451+ } else if r.readerAt {452+ b := make([]byte, n)453+ m, err := r.r.(io.ReaderAt).ReadAt(b, off)454+ if err != nil {455+ return nil, err456+ } else if m != n {457+ return nil, errors.New("file: could not read all bytes")458+ }459+ return b, nil460+ }461+ return nil, errors.New("io.Seeker and io.ReaderAt not implemented")462+}463+464+type BinaryReader2 struct {465+ f IBinaryReader466+ pos int64467+ err error468+469+ Endian binary.ByteOrder470+}471+472+func NewBinaryReader2(f IBinaryReader) *BinaryReader2 {473+ return &BinaryReader2{474+ f: f,475+ Endian: binary.BigEndian,476+ }477+}478+479+func NewBinaryReader2Reader(r io.Reader, n int64) (*BinaryReader2, error) {480+ _, isReaderAt := r.(io.ReaderAt)481+ _, isSeeker := r.(io.Seeker)482+483+ var f IBinaryReader484+ if isReaderAt || isSeeker {485+ var err error486+ f, err = newBinaryReaderReader(r, n)487+ if err != nil {488+ return nil, err489+ }490+ } else {491+ b := make([]byte, n)492+ if _, err := io.ReadFull(r, b); err != nil {493+ return nil, err494+ }495+ f, _ = newBinaryReaderBytes(b)496+ }497+ return NewBinaryReader2(f), nil498+}499+500+func NewBinaryReader2Bytes(data []byte) (*BinaryReader2, error) {501+ f, _ := newBinaryReaderBytes(data)502+ return NewBinaryReader2(f), nil503+}504+505+func NewBinaryReader2File(filename string) (*BinaryReader2, error) {506+ f, err := newBinaryReaderFile(filename)507+ if err != nil {508+ return nil, err509+ }510+ return NewBinaryReader2(f), nil511+}512+513+func (r *BinaryReader2) Err() error {514+ return r.err515+}516+517+func (r *BinaryReader2) Close() error {518+ if err := r.f.Close(); err != nil {519+ return err520+ }521+ return r.err522+}523+524+// InPageCache returns true if the range is already in the page cache (for mmap).525+func (r *BinaryReader2) InPageCache(start, end int64) bool {526+ index := int64(r.Pos()) / PageSize527+ return start/PageSize == index && end/PageSize == index528+}529+530+// Free frees all previously read bytes, you cannot seek from before this position (for reader).531+func (r *BinaryReader2) Free() {532+}533+534+// Pos returns the reader's position.535+func (r *BinaryReader2) Pos() int64 {536+ return r.pos537+}538+539+// Len returns the remaining length of the buffer.540+func (r *BinaryReader2) Len() int {541+ return int(int64(r.f.Len()) - int64(r.pos))542+}543+544+func (r *BinaryReader2) Seek(pos int64) {545+ r.pos = pos546+}547+548+// Read complies with io.Reader.549+func (r *BinaryReader2) Read(b []byte) (int, error) {550+ data, err := r.f.Bytes(len(b), r.pos)551+ if err != nil && err != io.EOF {552+ return 0, err553+ }554+ n := copy(b, data)555+ r.pos += int64(len(b))556+ return n, err557+}558+559+// ReadBytes reads n bytes.560+func (r *BinaryReader2) ReadBytes(n int) []byte {561+ data, err := r.f.Bytes(n, r.pos)562+ if err != nil {563+ r.err = err564+ return nil565+ }566+ r.pos += int64(n)567+ return data568+}569+570+// ReadString reads a string of length n.571+func (r *BinaryReader2) ReadString(n int) string {572+ return string(r.ReadBytes(n))573+}574+575+// ReadByte reads a single byte.576+func (r *BinaryReader2) ReadByte() byte {577+ data := r.ReadBytes(1)578+ if data == nil {579+ return 0580+ }581+ return data[0]582+}583+584+// ReadUint8 reads a uint8.585+func (r *BinaryReader2) ReadUint8() uint8 {586+ return r.ReadByte()587+}588+589+// ReadUint16 reads a uint16.590+func (r *BinaryReader2) ReadUint16() uint16 {591+ data := r.ReadBytes(2)592+ if data == nil {593+ return 0594+ } else if r.Endian == binary.LittleEndian {595+ return uint16(data[1])<<8 | uint16(data[0])596+ }597+ return uint16(data[0])<<8 | uint16(data[1])598+}599+600+// ReadUint32 reads a uint32.601+func (r *BinaryReader2) ReadUint32() uint32 {602+ data := r.ReadBytes(4)603+ if data == nil {604+ return 0605+ } else if r.Endian == binary.LittleEndian {606+ return uint32(data[3])<<24 | uint32(data[2])<<16 | uint32(data[1])<<8 | uint32(data[0])607+ }608+ return uint32(data[0])<<24 | uint32(data[1])<<16 | uint32(data[2])<<8 | uint32(data[3])609+}610+611+// ReadUint64 reads a uint64.612+func (r *BinaryReader2) ReadUint64() uint64 {613+ data := r.ReadBytes(8)614+ if data == nil {615+ return 0616+ } else if r.Endian == binary.LittleEndian {617+ return uint64(data[7])<<56 | uint64(data[6])<<48 | uint64(data[5])<<40 | uint64(data[4])<<32 | uint64(data[3])<<24 | uint64(data[2])<<16 | uint64(data[1])<<8 | uint64(data[0])618+ }619+ return uint64(data[0])<<56 | uint64(data[1])<<48 | uint64(data[2])<<40 | uint64(data[3])<<32 | uint64(data[4])<<24 | uint64(data[5])<<16 | uint64(data[6])<<8 | uint64(data[7])620+}621+622+// ReadInt8 reads a int8.623+func (r *BinaryReader2) ReadInt8() int8 {624+ return int8(r.ReadByte())625+}626+627+// ReadInt16 reads a int16.628+func (r *BinaryReader2) ReadInt16() int16 {629+ return int16(r.ReadUint16())630+}631+632+// ReadInt32 reads a int32.633+func (r *BinaryReader2) ReadInt32() int32 {634+ return int32(r.ReadUint32())635+}636+637+// ReadInt64 reads a int64.638+func (r *BinaryReader2) ReadInt64() int64 {639+ return int64(r.ReadUint64())640+}641+642+// BinaryWriter is a big endian binary file format writer.643+type BinaryWriter struct {644+ buf []byte645+}646+647+// NewBinaryWriter returns a big endian binary file format writer.648+func NewBinaryWriter(buf []byte) *BinaryWriter {649+ return &BinaryWriter{buf}650+}651+652+// Len returns the buffer's length in bytes.653+func (w *BinaryWriter) Len() uint32 {654+ return uint32(len(w.buf))655+}656+657+// Bytes returns the buffer's bytes.658+func (w *BinaryWriter) Bytes() []byte {659+ return w.buf660+}661+662+// Write complies with io.Writer.663+func (w *BinaryWriter) Write(b []byte) (int, error) {664+ w.buf = append(w.buf, b...)665+ return len(b), nil666+}667+668+// WriteBytes writes the given bytes to the buffer.669+func (w *BinaryWriter) WriteBytes(v []byte) {670+ w.buf = append(w.buf, v...)671+}672+673+// WriteString writes the given string to the buffer.674+func (w *BinaryWriter) WriteString(v string) {675+ w.WriteBytes([]byte(v))676+}677+678+// WriteByte writes the given byte to the buffer.679+func (w *BinaryWriter) WriteByte(v byte) {680+ w.WriteBytes([]byte{v})681+}682+683+// WriteUint8 writes the given uint8 to the buffer.684+func (w *BinaryWriter) WriteUint8(v uint8) {685+ w.WriteByte(v)686+}687+688+// WriteUint16 writes the given uint16 to the buffer.689+func (w *BinaryWriter) WriteUint16(v uint16) {690+ pos := len(w.buf)691+ w.buf = append(w.buf, make([]byte, 2)...)692+ binary.BigEndian.PutUint16(w.buf[pos:], v)693+}694+695+// WriteUint32 writes the given uint32 to the buffer.696+func (w *BinaryWriter) WriteUint32(v uint32) {697+ pos := len(w.buf)698+ w.buf = append(w.buf, make([]byte, 4)...)699+ binary.BigEndian.PutUint32(w.buf[pos:], v)700+}701+702+// WriteUint64 writes the given uint64 to the buffer.703+func (w *BinaryWriter) WriteUint64(v uint64) {704+ pos := len(w.buf)705+ w.buf = append(w.buf, make([]byte, 8)...)706+ binary.BigEndian.PutUint64(w.buf[pos:], v)707+}708+709+// WriteInt8 writes the given int8 to the buffer.710+func (w *BinaryWriter) WriteInt8(v int8) {711+ w.WriteUint8(uint8(v))712+}713+714+// WriteInt16 writes the given int16 to the buffer.715+func (w *BinaryWriter) WriteInt16(v int16) {716+ w.WriteUint16(uint16(v))717+}718+719+// WriteInt32 writes the given int32 to the buffer.720+func (w *BinaryWriter) WriteInt32(v int32) {721+ w.WriteUint32(uint32(v))722+}723+724+// WriteInt64 writes the given int64 to the buffer.725+func (w *BinaryWriter) WriteInt64(v int64) {726+ w.WriteUint64(uint64(v))727+}728+729+// BitmapReader is a binary bitmap reader.730+type BitmapReader struct {731+ buf []byte732+ pos uint32 // TODO: to uint64733+ eof bool734+}735+736+// NewBitmapReader returns a binary bitmap reader.737+func NewBitmapReader(buf []byte) *BitmapReader {738+ return &BitmapReader{buf, 0, false}739+}740+741+// Pos returns the current bit position.742+func (r *BitmapReader) Pos() uint32 {743+ return r.pos744+}745+746+// EOF returns if we reached the buffer's end-of-file.747+func (r *BitmapReader) EOF() bool {748+ return r.eof749+}750+751+// Read reads the next bit.752+func (r *BitmapReader) Read() bool {753+ if r.eof || uint32(len(r.buf)) <= (r.pos+1)/8 {754+ r.eof = true755+ return false756+ }757+ bit := r.buf[r.pos>>3]&(0x80>>(r.pos&7)) != 0758+ r.pos += 1759+ return bit760+}761+762+// BitmapWriter is a binary bitmap writer.763+type BitmapWriter struct {764+ buf []byte765+ pos uint32766+}767+768+// NewBitmapWriter returns a binary bitmap writer.769+func NewBitmapWriter(buf []byte) *BitmapWriter {770+ return &BitmapWriter{buf, 0}771+}772+773+// Len returns the buffer's length in bytes.774+func (w *BitmapWriter) Len() uint32 {775+ return uint32(len(w.buf))776+}777+778+// Bytes returns the buffer's bytes.779+func (w *BitmapWriter) Bytes() []byte {780+ return w.buf781+}782+783+// Write writes the next bit.784+func (w *BitmapWriter) Write(bit bool) {785+ if uint32(len(w.buf)) <= (w.pos+1)/8 {786+ w.buf = append(w.buf, 0)787+ }788+ if bit {789+ w.buf[w.pos>>3] = w.buf[w.pos>>3] | (0x80 >> (w.pos & 7))790+ }791+ w.pos += 1792+}
A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/binary_unix.go +91 -0
@@ -0,0 +1,91 @@
1+//go:build unix2+3+package parse4+5+import (6+ "errors"7+ "fmt"8+ "io"9+ "os"10+ "runtime"11+ "syscall"12+)13+14+type binaryReaderMmap struct {15+ data []byte16+}17+18+func newBinaryReaderMmap(filename string) (*binaryReaderMmap, error) {19+ f, err := os.Open(filename)20+ if err != nil {21+ return nil, err22+ }23+ defer f.Close()24+25+ fi, err := f.Stat()26+ if err != nil {27+ return nil, err28+ }29+30+ size := fi.Size()31+ if size == 0 {32+ // Treat (size == 0) as a special case, avoiding the syscall, since33+ // "man 2 mmap" says "the length... must be greater than 0".34+ //35+ // As we do not call syscall.Mmap, there is no need to call36+ // runtime.SetFinalizer to enforce a balancing syscall.Munmap.37+ return &binaryReaderMmap{38+ data: make([]byte, 0),39+ }, nil40+ } else if size < 0 {41+ return nil, fmt.Errorf("mmap: file %q has negative size", filename)42+ } else if size != int64(int(size)) {43+ return nil, fmt.Errorf("mmap: file %q is too large", filename)44+ }45+46+ data, err := syscall.Mmap(int(f.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)47+ if err != nil {48+ return nil, err49+ }50+ r := &binaryReaderMmap{data}51+ runtime.SetFinalizer(r, (*binaryReaderMmap).Close)52+ return r, nil53+}54+55+// Close closes the reader.56+func (r *binaryReaderMmap) Close() error {57+ if r.data == nil {58+ return nil59+ } else if len(r.data) == 0 {60+ r.data = nil61+ return nil62+ }63+ data := r.data64+ r.data = nil65+ runtime.SetFinalizer(r, nil)66+ return syscall.Munmap(data)67+}68+69+// Len returns the length of the underlying memory-mapped file.70+func (r *binaryReaderMmap) Len() int {71+ return len(r.data)72+}73+74+func (r *binaryReaderMmap) Bytes(n int, off int64) ([]byte, error) {75+ if r.data == nil {76+ return nil, errors.New("mmap: closed")77+ } else if off < 0 || int64(len(r.data)) < off {78+ return nil, fmt.Errorf("mmap: invalid offset %d", off)79+ } else if int64(len(r.data)-n) < off {80+ return r.data[off:len(r.data):len(r.data)], io.EOF81+ }82+ return r.data[off : off+int64(n) : off+int64(n)], nil83+}84+85+func NewBinaryReader2Mmap(filename string) (*BinaryReader2, error) {86+ f, err := newBinaryReaderMmap(filename)87+ if err != nil {88+ return nil, err89+ }90+ return NewBinaryReader2(f), nil91+}
A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/buffer/buffer.go +12 -0
A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/buffer/lexer.go +163 -0
@@ -0,0 +1,163 @@
1+package buffer2+3+import (4+ "io"5+)6+7+var nullBuffer = []byte{0}8+9+// Lexer is a buffered reader that allows peeking forward and shifting, taking an io.Reader.10+// It keeps data in-memory until Free, taking a byte length, is called to move beyond the data.11+type Lexer struct {12+ buf []byte13+ pos int // index in buf14+ start int // index in buf15+ err error16+17+ restore func()18+}19+20+// NewLexer returns a new Lexer for a given io.Reader, and uses io.ReadAll to read it into a byte slice.21+// If the io.Reader implements Bytes, that is used instead.22+// It will append a NULL at the end of the buffer.23+func NewLexer(r io.Reader) *Lexer {24+ var b []byte25+ if r != nil {26+ if buffer, ok := r.(interface {27+ Bytes() []byte28+ }); ok {29+ b = buffer.Bytes()30+ } else {31+ var err error32+ b, err = io.ReadAll(r)33+ if err != nil {34+ return &Lexer{35+ buf: nullBuffer,36+ err: err,37+ }38+ }39+ }40+ }41+ return NewLexerBytes(b)42+}43+44+// NewLexerBytes returns a new Lexer for a given byte slice, and appends NULL at the end.45+// To avoid reallocation, make sure the capacity has room for one more byte.46+func NewLexerBytes(b []byte) *Lexer {47+ z := &Lexer{48+ buf: b,49+ }50+51+ n := len(b)52+ if n == 0 {53+ z.buf = nullBuffer54+ } else {55+ // Append NULL to buffer, but try to avoid reallocation56+ if cap(b) > n {57+ // Overwrite next byte but restore when done58+ b = b[:n+1]59+ c := b[n]60+ b[n] = 061+62+ z.buf = b63+ z.restore = func() {64+ b[n] = c65+ }66+ } else {67+ z.buf = append(b, 0)68+ }69+ }70+ return z71+}72+73+// Restore restores the replaced byte past the end of the buffer by NULL.74+func (z *Lexer) Restore() {75+ if z.restore != nil {76+ z.restore()77+ z.restore = nil78+ }79+}80+81+// Err returns the error returned from io.Reader or io.EOF when the end has been reached.82+func (z *Lexer) Err() error {83+ return z.PeekErr(0)84+}85+86+// PeekErr returns the error at position pos. When pos is zero, this is the same as calling Err().87+func (z *Lexer) PeekErr(pos int) error {88+ if z.err != nil {89+ return z.err90+ } else if z.pos+pos >= len(z.buf)-1 {91+ return io.EOF92+ }93+ return nil94+}95+96+// Peek returns the ith byte relative to the end position.97+// Peek returns 0 when an error has occurred, Err returns the error.98+func (z *Lexer) Peek(pos int) byte {99+ pos += z.pos100+ return z.buf[pos]101+}102+103+// PeekRune returns the rune and rune length of the ith byte relative to the end position.104+func (z *Lexer) PeekRune(pos int) (rune, int) {105+ // from unicode/utf8106+ c := z.Peek(pos)107+ if c < 0xC0 || z.Peek(pos+1) == 0 {108+ return rune(c), 1109+ } else if c < 0xE0 || z.Peek(pos+2) == 0 {110+ return rune(c&0x1F)<<6 | rune(z.Peek(pos+1)&0x3F), 2111+ } else if c < 0xF0 || z.Peek(pos+3) == 0 {112+ return rune(c&0x0F)<<12 | rune(z.Peek(pos+1)&0x3F)<<6 | rune(z.Peek(pos+2)&0x3F), 3113+ }114+ return rune(c&0x07)<<18 | rune(z.Peek(pos+1)&0x3F)<<12 | rune(z.Peek(pos+2)&0x3F)<<6 | rune(z.Peek(pos+3)&0x3F), 4115+}116+117+// Move advances the position.118+func (z *Lexer) Move(n int) {119+ z.pos += n120+}121+122+// Pos returns a mark to which can be rewinded.123+func (z *Lexer) Pos() int {124+ return z.pos - z.start125+}126+127+// Rewind rewinds the position to the given position.128+func (z *Lexer) Rewind(pos int) {129+ z.pos = z.start + pos130+}131+132+// Lexeme returns the bytes of the current selection.133+func (z *Lexer) Lexeme() []byte {134+ return z.buf[z.start:z.pos:z.pos]135+}136+137+// Skip collapses the position to the end of the selection.138+func (z *Lexer) Skip() {139+ z.start = z.pos140+}141+142+// Shift returns the bytes of the current selection and collapses the position to the end of the selection.143+func (z *Lexer) Shift() []byte {144+ b := z.buf[z.start:z.pos:z.pos]145+ z.start = z.pos146+ return b147+}148+149+// Offset returns the character position in the buffer.150+func (z *Lexer) Offset() int {151+ return z.pos152+}153+154+// Bytes returns the underlying buffer.155+func (z *Lexer) Bytes() []byte {156+ return z.buf[: len(z.buf)-1 : len(z.buf)-1]157+}158+159+// Reset resets position to the underlying buffer.160+func (z *Lexer) Reset() {161+ z.start = 0162+ z.pos = 0163+}
A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/buffer/reader.go +44 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/buffer/streamlexer.go +223 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/buffer/writer.go +65 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/common.go +546 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/css/README.md +170 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/css/hash.go +75 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/css/lex.go +698 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/css/parse.go +493 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/css/util.go +47 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/error.go +47 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/input.go +186 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/position.go +95 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/goat-wasm/vendor/github.com/tdewolff/parse/v2/util.go +216 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/goat-wasm/vendor/modules.txt +12 -0

Not shown: this page already shows 6000 lines of diff.

M diagramzip-render/scripts/deploy-compatibility-units.mjs +1 -1

Not shown: this page already shows 6000 lines of diff.

M diagramzip-render/scripts/deploy-worker-units.mjs +1 -2

Not shown: this page already shows 6000 lines of diff.

M diagramzip-render/scripts/smoke-units.mjs +5 -0

Not shown: this page already shows 6000 lines of diff.

M diagramzip-render/src/adapters/edge.ts +2 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/src/adapters/edge/goat.ts +69 -0

Not shown: this page already shows 6000 lines of diff.

M diagramzip-render/src/adapters/edge/types.ts +1 -1

Not shown: this page already shows 6000 lines of diff.

M diagramzip-render/src/catalog.ts +8 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/src/units/goat.ts +12 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/src/vendor/goat-wasm.d.ts +8 -0

Not shown: this page already shows 6000 lines of diff.

M diagramzip-render/test/catalog.test.ts +6 -0

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/test/goat.test.ts +66 -0

Not shown: this page already shows 6000 lines of diff.

D diagramzip-render/wrangler.compatibility/goat.jsonc +0 -1

Not shown: this page already shows 6000 lines of diff.

A diagramzip-render/wrangler.units/goat.jsonc +10 -0

Not shown: this page already shows 6000 lines of diff.

M diagramzip/src/renderer-units.js +1 -1

Not shown: this page already shows 6000 lines of diff.

M docs/refactor-engines.md +19 -12

Not shown: this page already shows 6000 lines of diff.