@diagrammo/dgmo 0.89.0 → 0.90.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/advanced.d.ts +435 -15
- package/dist/advanced.js +356 -197
- package/dist/block.d.ts +1 -1
- package/dist/block.js +86 -63
- package/dist/{calculator-35YVW4QO.js → calculator-PX3JHKAS.js} +2 -2
- package/dist/{chart-meta-Bev5Ebjr.d.ts → chart-meta-B_WTMP0y.d.ts} +1 -1
- package/dist/chart-meta.d.ts +2 -2
- package/dist/chart-meta.js +17 -16
- package/dist/{chart-types-Bf8C5QQW.d.ts → chart-types-V2NcgN5O.d.ts} +4 -0
- package/dist/{charts-d3-KEQSUWKP.js → charts-d3-QW4ERNUE.js} +8 -7
- package/dist/{chunk-ECMA6XE2.js → chunk-2EDJPHXQ.js} +9 -9
- package/dist/{chunk-7WGCQE2E.js → chunk-2ZVLI6GQ.js} +1 -1
- package/dist/{chunk-UWKRPQZO.js → chunk-3BQCSQQJ.js} +16 -13
- package/dist/{chunk-7C2RAGPT.js → chunk-3HBR3YAN.js} +12 -12
- package/dist/chunk-3UR4F7FE.js +14 -0
- package/dist/{chunk-NIUO2TNR.js → chunk-4HG4PYHM.js} +197 -203
- package/dist/{chunk-NSR2CGV6.js → chunk-4IYJOXGS.js} +10 -10
- package/dist/{chunk-BOO24UMJ.js → chunk-4YO7VJTP.js} +1 -1
- package/dist/{chunk-YMFQLWZ2.js → chunk-54YUDXGP.js} +1 -1
- package/dist/{chunk-UG2XG57E.js → chunk-5M52RPAS.js} +2 -2
- package/dist/{chunk-QIOYTQDZ.js → chunk-5M6PMV5Z.js} +9 -9
- package/dist/{chunk-S2WE7TVK.js → chunk-5MDQMB6A.js} +1 -1
- package/dist/{chunk-WPL5PFXE.js → chunk-5TZPIKNV.js} +5 -5
- package/dist/{chunk-LFKSHDVI.js → chunk-62VWZ3SE.js} +5 -4
- package/dist/{chunk-PMNITNPR.js → chunk-64KSPLU2.js} +2 -1
- package/dist/{chunk-VEPIAUSK.js → chunk-6BQSDF77.js} +1 -5
- package/dist/{chunk-HUNDJTTH.js → chunk-A5EE66RQ.js} +1 -1
- package/dist/{chunk-JVZ7KFAX.js → chunk-AAC3XQWV.js} +2 -2
- package/dist/{chunk-7KPHRTND.js → chunk-AHWA36O7.js} +1 -1
- package/dist/{chunk-FKZ2Y3Q5.js → chunk-C4W62JZO.js} +2 -2
- package/dist/{chunk-C4FRGZGX.js → chunk-CDT33RHG.js} +1 -1
- package/dist/{chunk-B3S76BZO.js → chunk-COPSI54O.js} +3 -3
- package/dist/{chunk-GUTZCRBT.js → chunk-CUY56BK4.js} +2 -2
- package/dist/{chunk-HSRQDWV7.js → chunk-CYF2X3CG.js} +2 -11
- package/dist/{chunk-NGC3IZRY.js → chunk-DNMICILX.js} +2 -2
- package/dist/{chunk-N4BBSDWI.js → chunk-DXPT2HPH.js} +140 -151
- package/dist/{chunk-TAP2NZEU.js → chunk-EB7AEM2H.js} +13 -13
- package/dist/{chunk-3AQJNY65.js → chunk-EEYQFJVK.js} +149 -43
- package/dist/{chunk-SLQT3JP3.js → chunk-EK7JYFCL.js} +1 -1
- package/dist/{chunk-3C2FWS2J.js → chunk-EWERNTOX.js} +1 -1
- package/dist/{chunk-4AJKOUBO.js → chunk-FDRHBQUG.js} +17 -15
- package/dist/{chunk-FJNXXGTR.js → chunk-FS2LZBHG.js} +2 -2
- package/dist/{chunk-LPYYZCSI.js → chunk-G5BGW4BX.js} +1 -1
- package/dist/{chunk-IQ6P3HDE.js → chunk-GQVXYRPC.js} +2 -2
- package/dist/{chunk-MHAYUS65.js → chunk-GXP344YZ.js} +3 -3
- package/dist/{chunk-HAMF4F3P.js → chunk-GYBKG4Z6.js} +5 -5
- package/dist/{chunk-Q6SQISHT.js → chunk-HIVJBESO.js} +306 -68
- package/dist/{chunk-ID2K3IHI.js → chunk-HLE5QGZJ.js} +1 -1
- package/dist/{chunk-L5AFONQU.js → chunk-HX2KJEPD.js} +1 -1
- package/dist/{chunk-SOPM5PLO.js → chunk-I6FWHICD.js} +13 -13
- package/dist/{chunk-3RYBTEF5.js → chunk-I74AQRVD.js} +6 -12
- package/dist/{chunk-NRPELUB6.js → chunk-ICYPGWOV.js} +2 -2
- package/dist/chunk-IHK3Y2HG.js +781 -0
- package/dist/{chunk-RXIKESRI.js → chunk-IMHNZITR.js} +2 -2
- package/dist/{chunk-DPSMX4EY.js → chunk-IRVQT4GR.js} +3 -3
- package/dist/{chunk-AZ24RI5L.js → chunk-J3ODKAWN.js} +2 -2
- package/dist/{chunk-BB6QKWHZ.js → chunk-JRQ4LBQI.js} +10 -10
- package/dist/{chunk-MY6GUH26.js → chunk-JX4RCC6B.js} +2 -2
- package/dist/{chunk-XUCQVKLQ.js → chunk-K4GUO7FV.js} +4 -4
- package/dist/{chunk-J64NZ6NF.js → chunk-KSZHIAI2.js} +1 -1
- package/dist/{chunk-CUYHST3B.js → chunk-LBRHDWEA.js} +1 -1
- package/dist/{chunk-GSYZZFI5.js → chunk-LC5LY5V2.js} +2 -2
- package/dist/{chunk-WP22MHH6.js → chunk-LNQDICFJ.js} +9 -9
- package/dist/{chunk-PIXJL77H.js → chunk-M6J25PAZ.js} +1 -1
- package/dist/{chunk-UQLHKLF4.js → chunk-MIVSVEK4.js} +8 -8
- package/dist/{chunk-6K7RODXG.js → chunk-MKFQ5E7J.js} +1 -1
- package/dist/{chunk-44GP7PD5.js → chunk-MPJQMMBZ.js} +12 -0
- package/dist/{chunk-WQ6G6FFM.js → chunk-NFWRIC2V.js} +1 -1
- package/dist/{chunk-7SX4IY6T.js → chunk-NJYA4QSN.js} +1 -1
- package/dist/{chunk-RU725VCJ.js → chunk-NMKLX57Y.js} +10 -35
- package/dist/{chunk-RPKBWJBB.js → chunk-NP2SE6MQ.js} +1 -1
- package/dist/{chunk-ITSBBQTH.js → chunk-NUYWNKHH.js} +2 -2
- package/dist/{chunk-DV2VEJCC.js → chunk-NWSPPGIE.js} +6 -6
- package/dist/{chunk-AYOVY75O.js → chunk-NY56FM55.js} +3 -3
- package/dist/{chunk-MFW6P7WH.js → chunk-OSIUWQ3N.js} +3 -3
- package/dist/{chunk-2GM7N5TV.js → chunk-OWJVSJLA.js} +9 -9
- package/dist/{chunk-GDJW64XE.js → chunk-OYKNMFUY.js} +1 -1
- package/dist/{chunk-4R6OMKXB.js → chunk-PIHAUVA4.js} +5 -5
- package/dist/{chunk-C7QRRTAW.js → chunk-PNCKZFPE.js} +2 -2
- package/dist/{chunk-DDPGEGHB.js → chunk-POBNN5DP.js} +2 -2
- package/dist/{chunk-XJ2TBXHA.js → chunk-PQ5NW3QZ.js} +8 -8
- package/dist/{chunk-FKLE6QA3.js → chunk-PXEUQ7EG.js} +12 -12
- package/dist/{chunk-WEF6MXFG.js → chunk-QOZ4RVQY.js} +8 -8
- package/dist/{chunk-LR6RRDWN.js → chunk-QRIW46LR.js} +5 -5
- package/dist/{chunk-7W4XLIVH.js → chunk-R2PTZ6QM.js} +10 -10
- package/dist/{chunk-M2BCY7H3.js → chunk-RCBHL2B5.js} +189 -136
- package/dist/{chunk-66YFDTT6.js → chunk-RJYFHEP4.js} +3 -3
- package/dist/{chunk-P3SKSKJI.js → chunk-RNXX2GOQ.js} +2 -2
- package/dist/{chunk-RGWYZR3S.js → chunk-RRBV4HIM.js} +2 -2
- package/dist/{chunk-44FTCIRI.js → chunk-RY5RRKYN.js} +8 -8
- package/dist/{chunk-Y7PSAHAY.js → chunk-S4LLHDGK.js} +3 -3
- package/dist/chunk-S7C32S2T.js +825 -0
- package/dist/{chunk-JRUEIRJB.js → chunk-SLO22FPS.js} +2 -2
- package/dist/{chunk-47E5UTXE.js → chunk-SMEVBJJJ.js} +2 -2
- package/dist/{chunk-QFHWYWQL.js → chunk-SVQDLYX7.js} +1 -1
- package/dist/{chunk-WP2RLGDI.js → chunk-TCX5PMEY.js} +1 -1
- package/dist/{chunk-EXKCRXPO.js → chunk-TQ644YGV.js} +12 -12
- package/dist/{chunk-WRCAH3UO.js → chunk-U762HK4D.js} +15 -15
- package/dist/{chunk-LYLNMN77.js → chunk-VCHYS3RD.js} +8 -3
- package/dist/{chunk-7UDAIP6W.js → chunk-WHRIN7N3.js} +1 -1
- package/dist/{chunk-ZUCXFXLD.js → chunk-X3WYJ5T7.js} +2 -2
- package/dist/{chunk-UKJLPA7R.js → chunk-X6IZP2BD.js} +1 -1
- package/dist/{chunk-LCJURK7L.js → chunk-Y6GQQ4Y2.js} +1 -1
- package/dist/{chunk-HTWH2XQL.js → chunk-YE7D2S75.js} +2 -2
- package/dist/{chunk-DZPHN2WN.js → chunk-YKGUSW6D.js} +10 -10
- package/dist/{chunk-K5XT7NHD.js → chunk-YWRZG6GN.js} +10 -10
- package/dist/{chunk-JQ57AWDL.js → chunk-ZJ4PYTCK.js} +1 -1
- package/dist/completion-registry.d.ts +6 -0
- package/dist/completion-registry.js +62 -6
- package/dist/{d3-B6524aaP.d.ts → d3-2S3PrXik.d.ts} +11 -0
- package/dist/dgmo-router-BJBXSZMV.js +94 -0
- package/dist/{dimensions-7MF7VSGS.js → dimensions-AMA2S7RO.js} +5 -5
- package/dist/editor.js +12 -0
- package/dist/{flowchart-parser-GG7XBLQH.js → flowchart-parser-ADZIFVCH.js} +3 -3
- package/dist/{flowchart-renderer-DZQZRL2F.js → flowchart-renderer-CAG6ZNJ6.js} +5 -5
- package/dist/highlight.js +12 -0
- package/dist/index.d.ts +26 -5
- package/dist/index.js +83 -56
- package/dist/{layout-EFEZ533S.js → layout-6OSF2UGP.js} +3 -3
- package/dist/{layout-IGAVGDME.js → layout-C6MKMOBI.js} +1 -1
- package/dist/{layout-XUW444O5.js → layout-QS2SESBU.js} +2 -2
- package/dist/{layout-PP36LUTA.js → layout-T2PGKQUQ.js} +3 -3
- package/dist/{layout-Q3RYSODA.js → layout-UFB2O4AO.js} +3 -3
- package/dist/{layout-MSSHF45T.js → layout-UPQGIT3N.js} +2 -2
- package/dist/{layout-6L3BIFEZ.js → layout-VQ2USZJ3.js} +3 -3
- package/dist/{layout-DXVAYF6W.js → layout-VRJ5YFUE.js} +2 -2
- package/dist/{palettes-WZAIQTEG.js → palettes-WQHELGVQ.js} +2 -1
- package/dist/{parser-KYPP2DX6.js → parser-223VOHSB.js} +4 -4
- package/dist/{parser-JOOAVBJI.js → parser-2PNV7UI3.js} +3 -3
- package/dist/{parser-GZ3LVF72.js → parser-3MGA2MTM.js} +2 -2
- package/dist/{parser-NOANDFJ4.js → parser-4AR25MAS.js} +2 -2
- package/dist/{parser-337TACHB.js → parser-4B2PIXHI.js} +3 -3
- package/dist/{parser-KZMJNKDW.js → parser-57YQEEMC.js} +2 -2
- package/dist/{parser-J4DVNL4W.js → parser-5ZD2NKGU.js} +4 -4
- package/dist/{parser-LANGKWRG.js → parser-6LMB7BQQ.js} +2 -2
- package/dist/parser-AL6YL7KM.js +12 -0
- package/dist/{parser-5655XSOR.js → parser-B2FWP7ZO.js} +3 -3
- package/dist/{parser-GEKPWSSK.js → parser-BJDQX4KV.js} +3 -3
- package/dist/{parser-UTXFJG4X.js → parser-CA5WC5HF.js} +3 -3
- package/dist/{parser-KZMALSB2.js → parser-E5HFYL7B.js} +4 -4
- package/dist/{parser-GCCWMBTU.js → parser-ESZ5J5IJ.js} +5 -4
- package/dist/{parser-256EIWB2.js → parser-FDXUQ5TI.js} +2 -2
- package/dist/{parser-AN3MGHRL.js → parser-FV6RGALQ.js} +3 -3
- package/dist/{parser-DGF3ZEO6.js → parser-HPR7G7F6.js} +3 -3
- package/dist/{parser-OKB64MMN.js → parser-I4HZVIHX.js} +3 -3
- package/dist/{parser-P4TOQYNN.js → parser-J3BM2AP2.js} +3 -3
- package/dist/{parser-NPXPJHWN.js → parser-JFNDS5E5.js} +3 -3
- package/dist/{parser-TL3JA4RI.js → parser-KFIK3EOO.js} +3 -3
- package/dist/{parser-IMM2JJX3.js → parser-LGUT7KKH.js} +4 -4
- package/dist/{parser-KFYRTGTC.js → parser-MYRGCK7Q.js} +4 -4
- package/dist/{parser-ZLKYDFI5.js → parser-NAUUUCI3.js} +3 -3
- package/dist/{parser-DHSFIUPU.js → parser-NJ5VEZA3.js} +3 -3
- package/dist/{parser-OVQTTTUJ.js → parser-OFTMA36M.js} +2 -2
- package/dist/{parser-AKBVJHMK.js → parser-OV2WDSEV.js} +3 -3
- package/dist/{parser-LGS65QPN.js → parser-Q3UAOLEZ.js} +2 -2
- package/dist/{parser-XYY5NHP4.js → parser-R6OETHWM.js} +2 -2
- package/dist/{parser-7OEE7QT6.js → parser-RARXAHC7.js} +2 -2
- package/dist/{parser-NGHQQGD2.js → parser-T2RVIRDG.js} +3 -3
- package/dist/{parser-WEVTMBJ5.js → parser-W2DWCCLO.js} +3 -3
- package/dist/{parser-HCGXNHHL.js → parser-WFY4ZJUW.js} +3 -3
- package/dist/{parser-SA3RAJFS.js → parser-WSONWDFN.js} +2 -2
- package/dist/{renderer-CHQZES6G.js → renderer-3N64GVNM.js} +2 -2
- package/dist/{renderer-JR2XNZAH.js → renderer-3QM2DK7I.js} +5 -5
- package/dist/{renderer-XHEMXBQM.js → renderer-3W5T3IU6.js} +5 -5
- package/dist/{renderer-2ZQ6DPVD.js → renderer-4Z7FRQ2A.js} +2 -2
- package/dist/{renderer-EAORWLVK.js → renderer-52CHSV6S.js} +7 -7
- package/dist/{renderer-ZXQXRNDR.js → renderer-6OOJNEQE.js} +6 -6
- package/dist/{renderer-IR5GGAPB.js → renderer-A3PMPUO6.js} +11 -7
- package/dist/{renderer-3AD4NDF5.js → renderer-AM4VZSBQ.js} +6 -6
- package/dist/{renderer-P4XCRNCY.js → renderer-B3CPMS5R.js} +6 -6
- package/dist/{renderer-VGQMJC5K.js → renderer-B4KE3NM4.js} +11 -10
- package/dist/renderer-COSJM7UG.js +80 -0
- package/dist/{renderer-NAA7D33G.js → renderer-FXCVH2M5.js} +10 -9
- package/dist/{renderer-J4OS77PK.js → renderer-GWH5ACJ5.js} +3 -3
- package/dist/{renderer-7HOSI6JJ.js → renderer-IG72UJHW.js} +4 -4
- package/dist/{renderer-QQHQY3GT.js → renderer-IO3D5E4E.js} +5 -5
- package/dist/{renderer-6OQM32X3.js → renderer-IPDCAACY.js} +3 -3
- package/dist/{renderer-VYWCMRWA.js → renderer-KK5BU5K2.js} +5 -5
- package/dist/{renderer-7MCC6GKH.js → renderer-LD2H7WGP.js} +2 -2
- package/dist/{renderer-DCJ4AQ3N.js → renderer-MK47ESCB.js} +5 -5
- package/dist/{renderer-BLHN3RCY.js → renderer-MRMFYYZT.js} +2 -2
- package/dist/{renderer-XFEZ5UBE.js → renderer-O5RQHGK2.js} +6 -6
- package/dist/{renderer-43ESBXVY.js → renderer-OA5D3ZKB.js} +6 -6
- package/dist/{renderer-YTEZK3GC.js → renderer-ON4PTUHJ.js} +7 -7
- package/dist/{renderer-JRVY7HAD.js → renderer-OPDSKCQS.js} +4 -4
- package/dist/{renderer-FCXRDYSJ.js → renderer-PLRMST57.js} +7 -7
- package/dist/{renderer-WB75JME5.js → renderer-PSMRAL72.js} +6 -6
- package/dist/{renderer-7L7J4DHO.js → renderer-PXGCR7N4.js} +7 -6
- package/dist/{renderer-CEIX5JPA.js → renderer-R7OYCJEA.js} +2 -2
- package/dist/{renderer-DBNDLKPE.js → renderer-SBXCPA3S.js} +7 -7
- package/dist/{renderer-4JE5ORRO.js → renderer-TMQNTUQS.js} +7 -7
- package/dist/renderer-TTKTBB4C.js +46 -0
- package/dist/{renderer-5GZNGZU5.js → renderer-UVFGW2O6.js} +9 -7
- package/dist/{renderer-7VQOKAII.js → renderer-W22GX72R.js} +6 -6
- package/dist/{renderer-radial-NA5DVBG4.js → renderer-radial-42LVSG74.js} +5 -5
- package/dist/{state-parser-FE3RF6XY.js → state-parser-ROKY5KSI.js} +4 -4
- package/dist/{state-renderer-4S2C2CHB.js → state-renderer-MMG23G4W.js} +8 -8
- package/dist/{themes-CzuBdx1T.d.ts → themes-Cm2YBcT8.d.ts} +38 -2
- package/docs/ai-integration.md +4 -3
- package/docs/language-reference.md +162 -12
- package/gallery/fixtures/sketch.dgmo +2 -2
- package/gallery/fixtures/whiteboard.dgmo +18 -0
- package/package.json +2 -1
- package/dist/dgmo-router-7WANZOBD.js +0 -92
- package/dist/renderer-TJJGFQGT.js +0 -78
- package/dist/{chunk-CX6OH5WP.js → chunk-A5PUOHIS.js} +3 -3
- package/dist/{chunk-5LE2CTLS.js → chunk-NTJ5HVV6.js} +3 -3
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseFirstLine,
|
|
3
|
+
recognizeGlobalBoolean
|
|
4
|
+
} from "./chunk-MPJQMMBZ.js";
|
|
5
|
+
import {
|
|
6
|
+
makeDgmoError,
|
|
7
|
+
makeFail,
|
|
8
|
+
suggest
|
|
9
|
+
} from "./chunk-BYU54C3S.js";
|
|
10
|
+
|
|
11
|
+
// src/whiteboard/diagnostics.ts
|
|
12
|
+
var WHITEBOARD_DIAGNOSTIC_CODES = {
|
|
13
|
+
UNKNOWN_ELEMENT: "W_WHITEBOARD_UNKNOWN_ELEMENT",
|
|
14
|
+
BAD_GEOMETRY: "W_WHITEBOARD_BAD_GEOMETRY",
|
|
15
|
+
OUT_OF_RANGE: "W_WHITEBOARD_OUT_OF_RANGE",
|
|
16
|
+
UNKNOWN_COLOR: "W_WHITEBOARD_UNKNOWN_COLOR",
|
|
17
|
+
UNKNOWN_KEY: "W_WHITEBOARD_UNKNOWN_KEY",
|
|
18
|
+
UNKNOWN_STYLE: "W_WHITEBOARD_UNKNOWN_STYLE",
|
|
19
|
+
BAD_VALUE: "W_WHITEBOARD_BAD_VALUE",
|
|
20
|
+
BAD_INK: "W_WHITEBOARD_BAD_INK",
|
|
21
|
+
BAD_IMAGE_REF: "W_WHITEBOARD_BAD_IMAGE_REF",
|
|
22
|
+
EMPTY_TEXT: "W_WHITEBOARD_EMPTY_TEXT",
|
|
23
|
+
UNEXPECTED_BODY: "W_WHITEBOARD_UNEXPECTED_BODY"
|
|
24
|
+
};
|
|
25
|
+
var s = (v) => typeof v === "string" || typeof v === "number" ? String(v) : "?";
|
|
26
|
+
var WHITEBOARD_DIAGNOSTICS = [
|
|
27
|
+
{
|
|
28
|
+
code: WHITEBOARD_DIAGNOSTIC_CODES.UNKNOWN_ELEMENT,
|
|
29
|
+
severity: "warning",
|
|
30
|
+
chartType: "whiteboard",
|
|
31
|
+
title: "Unknown whiteboard element",
|
|
32
|
+
message: (p) => `Unknown element "${s(p.word)}" \u2014 a whiteboard line starts with rectangle, ellipse, database, queue, note, arrow, line, text, image or ink; line skipped${p.hint ? `. ${s(p.hint)}` : ""}`,
|
|
33
|
+
hint: "Start the line with one of the ten element keywords.",
|
|
34
|
+
example: "whiteboard\nsquare Start at: 0 0, size: 120 60"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
code: WHITEBOARD_DIAGNOSTIC_CODES.BAD_GEOMETRY,
|
|
38
|
+
severity: "warning",
|
|
39
|
+
chartType: "whiteboard",
|
|
40
|
+
title: "Missing or malformed position",
|
|
41
|
+
message: (p) => `${s(p.element)} needs ${s(p.need)} \u2014 got ${s(p.got)}; line skipped`,
|
|
42
|
+
hint: "Positions and sizes are two whole numbers separated by a space: at: 40 -20, size: 160 80.",
|
|
43
|
+
example: "whiteboard\nellipse Idea at: 40, size: 160 80"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
code: WHITEBOARD_DIAGNOSTIC_CODES.OUT_OF_RANGE,
|
|
47
|
+
severity: "warning",
|
|
48
|
+
chartType: "whiteboard",
|
|
49
|
+
title: "Coordinate out of range",
|
|
50
|
+
message: (p) => `${s(p.key)}: "${s(p.raw)}" is out of range \u2014 coordinates stay within \xB1${s(p.max)} px and sizes are at least 1; line skipped`,
|
|
51
|
+
hint: "Keep the board near the origin; a runaway drag value is the usual cause.",
|
|
52
|
+
example: "whiteboard\nrectangle at: 0 0, size: 0 50"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
code: WHITEBOARD_DIAGNOSTIC_CODES.UNKNOWN_COLOR,
|
|
56
|
+
severity: "warning",
|
|
57
|
+
chartType: "whiteboard",
|
|
58
|
+
title: "Unknown colour",
|
|
59
|
+
message: (p) => `Unknown colour "${s(p.color)}" \u2014 drawn in ${p.fallback ? s(p.fallback) : "ink"} (valid: ink, red, green, blue, teal, purple, orange, yellow, cyan, gray)${p.hint ? `. ${s(p.hint)}` : ""}`,
|
|
60
|
+
hint: "Colours are palette names, never hex values.",
|
|
61
|
+
example: "whiteboard\ntext Ship it at: 0 0, color: crimson"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
code: WHITEBOARD_DIAGNOSTIC_CODES.UNKNOWN_KEY,
|
|
65
|
+
severity: "warning",
|
|
66
|
+
chartType: "whiteboard",
|
|
67
|
+
title: "Unknown metadata key",
|
|
68
|
+
message: (p) => `Unknown key "${s(p.key)}" on ${s(p.element)} \u2014 ignored (valid here: ${s(p.valid)})${p.hint ? `. ${s(p.hint)}` : ""}`,
|
|
69
|
+
hint: 'Quote a label that itself contains "word:" so it is not read as a key.',
|
|
70
|
+
example: "whiteboard\ntext Ship at: 0 0, colour: red"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
code: WHITEBOARD_DIAGNOSTIC_CODES.UNKNOWN_STYLE,
|
|
74
|
+
severity: "warning",
|
|
75
|
+
chartType: "whiteboard",
|
|
76
|
+
title: "Unknown line style",
|
|
77
|
+
message: (p) => `Unknown style "${s(p.style)}" \u2014 drawn solid (valid: dashed; solid is the default and never written)${p.hint ? `. ${s(p.hint)}` : ""}`,
|
|
78
|
+
hint: "Arrows and lines take `style: dashed`; leave style off for a solid stroke.",
|
|
79
|
+
example: "whiteboard\nline from: 0 0, to: 120 0, style: dotted"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
code: WHITEBOARD_DIAGNOSTIC_CODES.BAD_VALUE,
|
|
83
|
+
severity: "warning",
|
|
84
|
+
chartType: "whiteboard",
|
|
85
|
+
title: "Unknown whiteboard value",
|
|
86
|
+
message: (p) => `Unknown ${s(p.key)}: "${s(p.value)}" \u2014 ${s(p.fallback)} used instead (valid: ${s(p.valid)})${p.hint ? `. ${s(p.hint)}` : ""}`,
|
|
87
|
+
hint: "Shapes take `fill: solid` or `fill: outline`; arrows take `heads: both`; arrows and lines take `bend:` as a whole number.",
|
|
88
|
+
example: "whiteboard\nrectangle at: 0 0, size: 100 60, fill: hatched"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
code: WHITEBOARD_DIAGNOSTIC_CODES.BAD_INK,
|
|
92
|
+
severity: "warning",
|
|
93
|
+
chartType: "whiteboard",
|
|
94
|
+
title: "Unreadable ink stroke",
|
|
95
|
+
message: (p) => `Ink stroke skipped \u2014 ${s(p.reason)}`,
|
|
96
|
+
hint: "An ink line is `ink <colour> <width> <encoded path>`, written by the whiteboard canvas. Do not hand-write or edit the path.",
|
|
97
|
+
example: "whiteboard\nink red 3 !!!"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
code: WHITEBOARD_DIAGNOSTIC_CODES.BAD_IMAGE_REF,
|
|
101
|
+
severity: "warning",
|
|
102
|
+
chartType: "whiteboard",
|
|
103
|
+
title: "Unusable image reference",
|
|
104
|
+
message: (p) => `Image "${s(p.ref)}" skipped \u2014 an image is a relative path (name.assets/file.webp) or an https:// URL`,
|
|
105
|
+
hint: "Use a path relative to the diagram, or an https:// link.",
|
|
106
|
+
example: "whiteboard\nimage ftp://example.com/a.png at: 0 0, size: 320 200"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
code: WHITEBOARD_DIAGNOSTIC_CODES.EMPTY_TEXT,
|
|
110
|
+
severity: "warning",
|
|
111
|
+
chartType: "whiteboard",
|
|
112
|
+
title: "Empty text",
|
|
113
|
+
message: () => "A text line needs some text \u2014 line skipped",
|
|
114
|
+
hint: "Write the words between `text` and `at:`, or on indented lines under it.",
|
|
115
|
+
example: "whiteboard\ntext at: 0 0"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
code: WHITEBOARD_DIAGNOSTIC_CODES.UNEXPECTED_BODY,
|
|
119
|
+
severity: "warning",
|
|
120
|
+
chartType: "whiteboard",
|
|
121
|
+
title: "Indented lines under an element with no label",
|
|
122
|
+
message: (p) => `${s(p.element)} takes no indented lines \u2014 ${s(p.count)} ignored`,
|
|
123
|
+
hint: "Only shapes, notes, arrows, lines and text have a label that can run over indented lines. Unindent the line if it is meant as its own element.",
|
|
124
|
+
example: "whiteboard\nimage board.assets/a.webp at: 0 0, size: 320 200\n caption"
|
|
125
|
+
}
|
|
126
|
+
];
|
|
127
|
+
var BY_CODE = new Map(WHITEBOARD_DIAGNOSTICS.map((d) => [d.code, d]));
|
|
128
|
+
function whiteboardMessage(code, params) {
|
|
129
|
+
const msg = BY_CODE.get(code).message;
|
|
130
|
+
return typeof msg === "string" ? msg : msg(params);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/whiteboard/ink-codec.ts
|
|
134
|
+
var INK_DEFAULT_TOLERANCE = 0.75;
|
|
135
|
+
var INK_PRESSURE_LEVELS = 31;
|
|
136
|
+
var FLAG_PRESSURE = 1;
|
|
137
|
+
var FORMAT_VERSION = 0;
|
|
138
|
+
var MAX_DECODED_POINTS = 1e5;
|
|
139
|
+
var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
140
|
+
var B64_INDEX = Object.fromEntries(
|
|
141
|
+
[...B64].map((c, i) => [c, i])
|
|
142
|
+
);
|
|
143
|
+
function perpDistSq(p, a, b) {
|
|
144
|
+
const dx = b.x - a.x;
|
|
145
|
+
const dy = b.y - a.y;
|
|
146
|
+
const lenSq = dx * dx + dy * dy;
|
|
147
|
+
if (lenSq === 0) {
|
|
148
|
+
const ex2 = p.x - a.x;
|
|
149
|
+
const ey2 = p.y - a.y;
|
|
150
|
+
return ex2 * ex2 + ey2 * ey2;
|
|
151
|
+
}
|
|
152
|
+
let t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / lenSq;
|
|
153
|
+
t = Math.max(0, Math.min(1, t));
|
|
154
|
+
const ex = p.x - (a.x + t * dx);
|
|
155
|
+
const ey = p.y - (a.y + t * dy);
|
|
156
|
+
return ex * ex + ey * ey;
|
|
157
|
+
}
|
|
158
|
+
function simplify(points, tolerance) {
|
|
159
|
+
if (points.length <= 2 || tolerance <= 0) return [...points];
|
|
160
|
+
const keep = new Uint8Array(points.length);
|
|
161
|
+
keep[0] = 1;
|
|
162
|
+
keep[points.length - 1] = 1;
|
|
163
|
+
const tolSq = tolerance * tolerance;
|
|
164
|
+
const stack = [[0, points.length - 1]];
|
|
165
|
+
while (stack.length > 0) {
|
|
166
|
+
const [first, last] = stack.pop();
|
|
167
|
+
let maxD = 0;
|
|
168
|
+
let index = -1;
|
|
169
|
+
for (let i = first + 1; i < last; i++) {
|
|
170
|
+
const d = perpDistSq(points[i], points[first], points[last]);
|
|
171
|
+
if (d > maxD) {
|
|
172
|
+
maxD = d;
|
|
173
|
+
index = i;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (index !== -1 && maxD > tolSq) {
|
|
177
|
+
keep[index] = 1;
|
|
178
|
+
stack.push([first, index], [index, last]);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return points.filter((_, i) => keep[i] === 1);
|
|
182
|
+
}
|
|
183
|
+
function zigzag(n) {
|
|
184
|
+
return n >= 0 ? n * 2 : -n * 2 - 1;
|
|
185
|
+
}
|
|
186
|
+
function unzigzag(n) {
|
|
187
|
+
return n % 2 === 0 ? n / 2 : -(n + 1) / 2;
|
|
188
|
+
}
|
|
189
|
+
function pushVarint(out, value) {
|
|
190
|
+
let v = value;
|
|
191
|
+
while (v >= 128) {
|
|
192
|
+
out.push(v % 128 | 128);
|
|
193
|
+
v = Math.floor(v / 128);
|
|
194
|
+
}
|
|
195
|
+
out.push(v);
|
|
196
|
+
}
|
|
197
|
+
function toBase64Url(bytes) {
|
|
198
|
+
let s2 = "";
|
|
199
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
200
|
+
const b0 = bytes[i];
|
|
201
|
+
const b1 = bytes[i + 1];
|
|
202
|
+
const b2 = bytes[i + 2];
|
|
203
|
+
s2 += B64[b0 >> 2];
|
|
204
|
+
s2 += B64[(b0 & 3) << 4 | (b1 ?? 0) >> 4];
|
|
205
|
+
if (b1 !== void 0) s2 += B64[(b1 & 15) << 2 | (b2 ?? 0) >> 6];
|
|
206
|
+
if (b2 !== void 0) s2 += B64[b2 & 63];
|
|
207
|
+
}
|
|
208
|
+
return s2;
|
|
209
|
+
}
|
|
210
|
+
function fromBase64Url(s2) {
|
|
211
|
+
if (s2.length % 4 === 1) return null;
|
|
212
|
+
const out = [];
|
|
213
|
+
let buf = 0;
|
|
214
|
+
let bits = 0;
|
|
215
|
+
for (const ch of s2) {
|
|
216
|
+
const v = B64_INDEX[ch];
|
|
217
|
+
if (v === void 0) return null;
|
|
218
|
+
buf = buf << 6 | v;
|
|
219
|
+
bits += 6;
|
|
220
|
+
if (bits >= 8) {
|
|
221
|
+
bits -= 8;
|
|
222
|
+
out.push(buf >> bits & 255);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
function encodeInk(points, opts = {}) {
|
|
228
|
+
const tolerance = opts.tolerance ?? INK_DEFAULT_TOLERANCE;
|
|
229
|
+
const finite = points.filter(
|
|
230
|
+
(p) => Number.isFinite(p.x) && Number.isFinite(p.y)
|
|
231
|
+
);
|
|
232
|
+
const hasPressure = finite.length > 0 && finite.every((p) => typeof p.pressure === "number" && isFinite(p.pressure));
|
|
233
|
+
const simplified = simplify(finite, tolerance);
|
|
234
|
+
const rounded = [];
|
|
235
|
+
for (const p of simplified) {
|
|
236
|
+
const x = Math.round(p.x);
|
|
237
|
+
const y = Math.round(p.y);
|
|
238
|
+
const prev = rounded[rounded.length - 1];
|
|
239
|
+
if (prev?.x === x && prev.y === y) continue;
|
|
240
|
+
rounded.push(
|
|
241
|
+
hasPressure ? {
|
|
242
|
+
x,
|
|
243
|
+
y,
|
|
244
|
+
pressure: Math.round(
|
|
245
|
+
Math.max(0, Math.min(1, p.pressure)) * INK_PRESSURE_LEVELS
|
|
246
|
+
)
|
|
247
|
+
} : { x, y }
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
const bytes = [
|
|
251
|
+
FORMAT_VERSION << 1 | (hasPressure ? FLAG_PRESSURE : 0)
|
|
252
|
+
];
|
|
253
|
+
let px = 0;
|
|
254
|
+
let py = 0;
|
|
255
|
+
let pp = 0;
|
|
256
|
+
for (const p of rounded) {
|
|
257
|
+
pushVarint(bytes, zigzag(p.x - px));
|
|
258
|
+
pushVarint(bytes, zigzag(p.y - py));
|
|
259
|
+
px = p.x;
|
|
260
|
+
py = p.y;
|
|
261
|
+
if (hasPressure) {
|
|
262
|
+
pushVarint(bytes, zigzag(p.pressure - pp));
|
|
263
|
+
pp = p.pressure;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return toBase64Url(bytes);
|
|
267
|
+
}
|
|
268
|
+
function decodeInk(encoded) {
|
|
269
|
+
const fail = (error) => ({ points: [], error });
|
|
270
|
+
if (typeof encoded !== "string" || encoded.length === 0) {
|
|
271
|
+
return fail("empty ink payload");
|
|
272
|
+
}
|
|
273
|
+
const bytes = fromBase64Url(encoded);
|
|
274
|
+
if (!bytes?.length) {
|
|
275
|
+
return fail("ink payload is not base64url");
|
|
276
|
+
}
|
|
277
|
+
const flags = bytes[0];
|
|
278
|
+
if (flags >> 1 !== FORMAT_VERSION) {
|
|
279
|
+
return fail(`unknown ink format version ${String(flags >> 1)}`);
|
|
280
|
+
}
|
|
281
|
+
const hasPressure = (flags & FLAG_PRESSURE) !== 0;
|
|
282
|
+
let pos = 1;
|
|
283
|
+
const readVarint = () => {
|
|
284
|
+
let result = 0;
|
|
285
|
+
let mul = 1;
|
|
286
|
+
for (let i = 0; i < 8; i++) {
|
|
287
|
+
if (pos >= bytes.length) return null;
|
|
288
|
+
const b = bytes[pos++];
|
|
289
|
+
result += (b & 127) * mul;
|
|
290
|
+
if ((b & 128) === 0) return unzigzag(result);
|
|
291
|
+
mul *= 128;
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
};
|
|
295
|
+
const points = [];
|
|
296
|
+
let x = 0;
|
|
297
|
+
let y = 0;
|
|
298
|
+
let p = 0;
|
|
299
|
+
while (pos < bytes.length) {
|
|
300
|
+
const dx = readVarint();
|
|
301
|
+
const dy = readVarint();
|
|
302
|
+
const dp = hasPressure ? readVarint() : 0;
|
|
303
|
+
if (dx === null || dy === null || dp === null) {
|
|
304
|
+
return fail("ink payload is truncated");
|
|
305
|
+
}
|
|
306
|
+
x += dx;
|
|
307
|
+
y += dy;
|
|
308
|
+
p += dp;
|
|
309
|
+
if (hasPressure && (p < 0 || p > INK_PRESSURE_LEVELS)) {
|
|
310
|
+
return fail("ink pressure out of range");
|
|
311
|
+
}
|
|
312
|
+
points.push(
|
|
313
|
+
hasPressure ? { x, y, pressure: p / INK_PRESSURE_LEVELS } : { x, y }
|
|
314
|
+
);
|
|
315
|
+
if (points.length > MAX_DECODED_POINTS) {
|
|
316
|
+
return fail("ink payload is too long");
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return { points, error: null };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/whiteboard/types.ts
|
|
323
|
+
var WHITEBOARD_COLORS = [
|
|
324
|
+
"ink",
|
|
325
|
+
"red",
|
|
326
|
+
"green",
|
|
327
|
+
"blue",
|
|
328
|
+
"teal",
|
|
329
|
+
"purple",
|
|
330
|
+
"orange",
|
|
331
|
+
"yellow",
|
|
332
|
+
"cyan",
|
|
333
|
+
"gray"
|
|
334
|
+
];
|
|
335
|
+
var WHITEBOARD_SHAPE_KINDS = [
|
|
336
|
+
"rectangle",
|
|
337
|
+
"ellipse",
|
|
338
|
+
"database",
|
|
339
|
+
"queue"
|
|
340
|
+
];
|
|
341
|
+
var WHITEBOARD_ELEMENT_KEYWORDS = [
|
|
342
|
+
...WHITEBOARD_SHAPE_KINDS,
|
|
343
|
+
"note",
|
|
344
|
+
"arrow",
|
|
345
|
+
"line",
|
|
346
|
+
"text",
|
|
347
|
+
"image",
|
|
348
|
+
"ink"
|
|
349
|
+
];
|
|
350
|
+
var WHITEBOARD_STROKE_STYLES = ["solid", "dashed"];
|
|
351
|
+
var WHITEBOARD_FILLS = ["tint", "solid", "outline"];
|
|
352
|
+
var WHITEBOARD_HEADS = ["end", "both"];
|
|
353
|
+
var WHITEBOARD_NOTE_WIDTH = 160;
|
|
354
|
+
var WHITEBOARD_NOTE_HEIGHT = 99;
|
|
355
|
+
var WHITEBOARD_NOTE_COLOR = "yellow";
|
|
356
|
+
function isWhiteboardColor(value) {
|
|
357
|
+
return WHITEBOARD_COLORS.includes(value);
|
|
358
|
+
}
|
|
359
|
+
function isWhiteboardShapeKind(value) {
|
|
360
|
+
return WHITEBOARD_SHAPE_KINDS.includes(value);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// src/whiteboard/parser.ts
|
|
364
|
+
var WHITEBOARD_COORD_MAX = 1e6;
|
|
365
|
+
var META_KEY_RE = /(^|\s)([a-z][a-z-]*):(?=\s|$)/;
|
|
366
|
+
var INT_PAIR_RE = /^(-?\d+)\s+(-?\d+)$/;
|
|
367
|
+
var INK_LINE_RE = /^(\S+)\s+(\S+)\s+(\S+)$/;
|
|
368
|
+
var INK_WIDTH_RE = /^\d+(\.\d+)?$/;
|
|
369
|
+
var INK_WIDTH_MAX = 200;
|
|
370
|
+
var WRITTEN_STYLES = ["dashed"];
|
|
371
|
+
var WRITTEN_FILLS = ["solid", "outline"];
|
|
372
|
+
var KEYS_BY_KIND = {
|
|
373
|
+
shape: ["at", "size", "color", "fill"],
|
|
374
|
+
note: ["at", "size", "color"],
|
|
375
|
+
arrow: ["from", "to", "color", "style", "heads", "bend"],
|
|
376
|
+
line: ["from", "to", "color", "style", "bend"],
|
|
377
|
+
text: ["at", "color"],
|
|
378
|
+
image: ["at", "size"]
|
|
379
|
+
};
|
|
380
|
+
function splitLine(rest) {
|
|
381
|
+
let name;
|
|
382
|
+
let metaRegion;
|
|
383
|
+
const q = rest[0];
|
|
384
|
+
const close = q === '"' || q === "'" ? rest.indexOf(q, 1) : -1;
|
|
385
|
+
if (close > 0) {
|
|
386
|
+
name = rest.slice(1, close);
|
|
387
|
+
metaRegion = rest.slice(close + 1);
|
|
388
|
+
} else {
|
|
389
|
+
const m = META_KEY_RE.exec(rest);
|
|
390
|
+
if (m) {
|
|
391
|
+
const cut = m.index + m[1].length;
|
|
392
|
+
name = rest.slice(0, cut).trim();
|
|
393
|
+
metaRegion = rest.slice(cut);
|
|
394
|
+
} else {
|
|
395
|
+
name = rest.trim();
|
|
396
|
+
metaRegion = "";
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const meta = /* @__PURE__ */ new Map();
|
|
400
|
+
for (const part of metaRegion.split(",")) {
|
|
401
|
+
const t = part.trim();
|
|
402
|
+
if (!t) continue;
|
|
403
|
+
const colon = t.indexOf(":");
|
|
404
|
+
if (colon <= 0) {
|
|
405
|
+
meta.set("", t);
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
meta.set(t.slice(0, colon).trim().toLowerCase(), t.slice(colon + 1).trim());
|
|
409
|
+
}
|
|
410
|
+
return { name: name.trim(), meta };
|
|
411
|
+
}
|
|
412
|
+
function indentOf(raw) {
|
|
413
|
+
return raw.length - raw.trimStart().length;
|
|
414
|
+
}
|
|
415
|
+
function collectBody(lines, head) {
|
|
416
|
+
const base = indentOf(lines[head]);
|
|
417
|
+
const body = [];
|
|
418
|
+
let end = head;
|
|
419
|
+
let pendingBlanks = 0;
|
|
420
|
+
for (let j = head + 1; j < lines.length; j++) {
|
|
421
|
+
const raw = lines[j];
|
|
422
|
+
if (!raw.trim()) {
|
|
423
|
+
pendingBlanks++;
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (indentOf(raw) <= base) break;
|
|
427
|
+
for (; pendingBlanks > 0; pendingBlanks--) body.push("");
|
|
428
|
+
body.push(raw.trim());
|
|
429
|
+
end = j;
|
|
430
|
+
}
|
|
431
|
+
return { body, end };
|
|
432
|
+
}
|
|
433
|
+
function joinLabel(inline, body) {
|
|
434
|
+
const all = inline ? [inline, ...body] : [...body];
|
|
435
|
+
while (all.length > 0 && all[0] === "") all.shift();
|
|
436
|
+
while (all.length > 0 && all[all.length - 1] === "") all.pop();
|
|
437
|
+
return all.join("\n");
|
|
438
|
+
}
|
|
439
|
+
function isWhiteboardImageRef(ref) {
|
|
440
|
+
if (/^https:\/\/\S+$/i.test(ref)) return true;
|
|
441
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(ref)) return false;
|
|
442
|
+
if (ref.startsWith("/") || ref.startsWith("\\")) return false;
|
|
443
|
+
return ref.length > 0;
|
|
444
|
+
}
|
|
445
|
+
function parseWhiteboard(content, _palette) {
|
|
446
|
+
const options = { noTitle: false, noNotes: false };
|
|
447
|
+
const elements = [];
|
|
448
|
+
const result = {
|
|
449
|
+
type: "whiteboard",
|
|
450
|
+
title: null,
|
|
451
|
+
titleLineNumber: null,
|
|
452
|
+
elements,
|
|
453
|
+
options,
|
|
454
|
+
diagnostics: [],
|
|
455
|
+
error: null
|
|
456
|
+
};
|
|
457
|
+
const fail = makeFail(result);
|
|
458
|
+
const warn = (line, code, params) => {
|
|
459
|
+
result.diagnostics.push(
|
|
460
|
+
makeDgmoError(line, whiteboardMessage(code, params), "warning", code)
|
|
461
|
+
);
|
|
462
|
+
};
|
|
463
|
+
if (!content?.trim()) return fail(0, "No content provided");
|
|
464
|
+
const readColor = (raw, line, fallback = "ink") => {
|
|
465
|
+
if (raw === void 0) return fallback;
|
|
466
|
+
const c = raw.trim().toLowerCase();
|
|
467
|
+
if (isWhiteboardColor(c)) return c;
|
|
468
|
+
warn(line, WHITEBOARD_DIAGNOSTIC_CODES.UNKNOWN_COLOR, {
|
|
469
|
+
color: raw,
|
|
470
|
+
fallback,
|
|
471
|
+
hint: suggest(c, WHITEBOARD_COLORS) ?? ""
|
|
472
|
+
});
|
|
473
|
+
return fallback;
|
|
474
|
+
};
|
|
475
|
+
const readStyle = (raw, line) => {
|
|
476
|
+
if (raw === void 0) return "solid";
|
|
477
|
+
const v = raw.trim().toLowerCase();
|
|
478
|
+
if (v === "dashed") return "dashed";
|
|
479
|
+
warn(line, WHITEBOARD_DIAGNOSTIC_CODES.UNKNOWN_STYLE, {
|
|
480
|
+
style: raw,
|
|
481
|
+
hint: suggest(v, WRITTEN_STYLES) ?? ""
|
|
482
|
+
});
|
|
483
|
+
return "solid";
|
|
484
|
+
};
|
|
485
|
+
const readFill = (raw, line) => {
|
|
486
|
+
if (raw === void 0) return "tint";
|
|
487
|
+
const v = raw.trim().toLowerCase();
|
|
488
|
+
if (v === "solid" || v === "outline") return v;
|
|
489
|
+
warn(line, WHITEBOARD_DIAGNOSTIC_CODES.BAD_VALUE, {
|
|
490
|
+
key: "fill",
|
|
491
|
+
value: raw,
|
|
492
|
+
fallback: "tint",
|
|
493
|
+
valid: WRITTEN_FILLS.join(", "),
|
|
494
|
+
hint: suggest(v, WRITTEN_FILLS) ?? ""
|
|
495
|
+
});
|
|
496
|
+
return "tint";
|
|
497
|
+
};
|
|
498
|
+
const readHeads = (raw, line) => {
|
|
499
|
+
if (raw === void 0) return "end";
|
|
500
|
+
const v = raw.trim().toLowerCase();
|
|
501
|
+
if (v === "both") return "both";
|
|
502
|
+
warn(line, WHITEBOARD_DIAGNOSTIC_CODES.BAD_VALUE, {
|
|
503
|
+
key: "heads",
|
|
504
|
+
value: raw,
|
|
505
|
+
fallback: "one head, at to:",
|
|
506
|
+
valid: "both",
|
|
507
|
+
hint: suggest(v, ["both"]) ?? ""
|
|
508
|
+
});
|
|
509
|
+
return "end";
|
|
510
|
+
};
|
|
511
|
+
const readBend = (raw, line) => {
|
|
512
|
+
if (raw === void 0) return 0;
|
|
513
|
+
const v = raw.trim();
|
|
514
|
+
if (/^-?\d+$/.test(v) && Math.abs(Number(v)) <= WHITEBOARD_COORD_MAX) {
|
|
515
|
+
return Number(v);
|
|
516
|
+
}
|
|
517
|
+
warn(line, WHITEBOARD_DIAGNOSTIC_CODES.BAD_VALUE, {
|
|
518
|
+
key: "bend",
|
|
519
|
+
value: raw,
|
|
520
|
+
fallback: "straight",
|
|
521
|
+
valid: "a whole number of px",
|
|
522
|
+
hint: ""
|
|
523
|
+
});
|
|
524
|
+
return 0;
|
|
525
|
+
};
|
|
526
|
+
const readPair = (meta, key, element, line, positive) => {
|
|
527
|
+
const raw = meta.get(key);
|
|
528
|
+
const m = raw === void 0 ? null : INT_PAIR_RE.exec(raw);
|
|
529
|
+
if (!m) {
|
|
530
|
+
warn(line, WHITEBOARD_DIAGNOSTIC_CODES.BAD_GEOMETRY, {
|
|
531
|
+
element,
|
|
532
|
+
need: `${key}: <x> <y> as two whole numbers`,
|
|
533
|
+
got: raw === void 0 ? `no ${key}:` : `"${raw}"`
|
|
534
|
+
});
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
const a = Number(m[1]);
|
|
538
|
+
const b = Number(m[2]);
|
|
539
|
+
const bad = (v) => Math.abs(v) > WHITEBOARD_COORD_MAX || positive && v < 1;
|
|
540
|
+
if (bad(a) || bad(b)) {
|
|
541
|
+
warn(line, WHITEBOARD_DIAGNOSTIC_CODES.OUT_OF_RANGE, {
|
|
542
|
+
key,
|
|
543
|
+
raw,
|
|
544
|
+
max: WHITEBOARD_COORD_MAX
|
|
545
|
+
});
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
return [a, b];
|
|
549
|
+
};
|
|
550
|
+
const checkKeys = (meta, kind, element, line) => {
|
|
551
|
+
const valid = KEYS_BY_KIND[kind];
|
|
552
|
+
for (const key of meta.keys()) {
|
|
553
|
+
if (key === "" || valid.includes(key)) continue;
|
|
554
|
+
warn(line, WHITEBOARD_DIAGNOSTIC_CODES.UNKNOWN_KEY, {
|
|
555
|
+
key,
|
|
556
|
+
element,
|
|
557
|
+
valid: valid.map((k) => `${k}:`).join(", "),
|
|
558
|
+
hint: suggest(key, valid) ?? ""
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
const lines = content.split("\n");
|
|
563
|
+
let firstLineSeen = false;
|
|
564
|
+
for (let i = 0; i < lines.length; i++) {
|
|
565
|
+
const lineNumber = i + 1;
|
|
566
|
+
const trimmed = lines[i].trim();
|
|
567
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
568
|
+
if (!firstLineSeen) {
|
|
569
|
+
firstLineSeen = true;
|
|
570
|
+
const first = parseFirstLine(trimmed);
|
|
571
|
+
if (first?.chartType !== "whiteboard") {
|
|
572
|
+
let msg = `Expected chart type "whiteboard", got "${first?.chartType ?? trimmed.split(/\s+/)[0]}"`;
|
|
573
|
+
const hint = suggest(first?.chartType ?? "", ["whiteboard"]);
|
|
574
|
+
if (hint) msg += `. ${hint}`;
|
|
575
|
+
return fail(lineNumber, msg);
|
|
576
|
+
}
|
|
577
|
+
result.title = first.title ?? null;
|
|
578
|
+
result.titleLineNumber = lineNumber;
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
const shared = recognizeGlobalBoolean(trimmed);
|
|
582
|
+
if (shared !== null) {
|
|
583
|
+
if (shared === "no-title") options.noTitle = true;
|
|
584
|
+
if (shared === "no-notes") options.noNotes = true;
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
const sp = trimmed.search(/\s/);
|
|
588
|
+
const word = (sp === -1 ? trimmed : trimmed.slice(0, sp)).toLowerCase();
|
|
589
|
+
const rest = sp === -1 ? "" : trimmed.slice(sp + 1).trim();
|
|
590
|
+
const { body, end } = collectBody(lines, i);
|
|
591
|
+
i = end;
|
|
592
|
+
const noBody = (element) => {
|
|
593
|
+
if (body.length > 0) {
|
|
594
|
+
warn(lineNumber, WHITEBOARD_DIAGNOSTIC_CODES.UNEXPECTED_BODY, {
|
|
595
|
+
element,
|
|
596
|
+
count: body.filter((b) => b !== "").length
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
if (word === "ink") {
|
|
601
|
+
noBody("ink");
|
|
602
|
+
const m = INK_LINE_RE.exec(rest);
|
|
603
|
+
if (!m) {
|
|
604
|
+
warn(lineNumber, WHITEBOARD_DIAGNOSTIC_CODES.BAD_INK, {
|
|
605
|
+
reason: "expected `ink <colour> <width> <encoded path>`"
|
|
606
|
+
});
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
const color = readColor(m[1], lineNumber);
|
|
610
|
+
const widthRaw = m[2];
|
|
611
|
+
const width = Number(widthRaw);
|
|
612
|
+
if (!INK_WIDTH_RE.test(widthRaw) || width <= 0 || width > INK_WIDTH_MAX) {
|
|
613
|
+
warn(lineNumber, WHITEBOARD_DIAGNOSTIC_CODES.BAD_INK, {
|
|
614
|
+
reason: `width "${widthRaw}" must be a number above 0 and at most ${INK_WIDTH_MAX}`
|
|
615
|
+
});
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
const decoded = decodeInk(m[3]);
|
|
619
|
+
if (decoded.error !== null || decoded.points.length === 0) {
|
|
620
|
+
warn(lineNumber, WHITEBOARD_DIAGNOSTIC_CODES.BAD_INK, {
|
|
621
|
+
reason: decoded.error ?? "the stroke has no points"
|
|
622
|
+
});
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
if (decoded.points.some(
|
|
626
|
+
(p) => Math.abs(p.x) > WHITEBOARD_COORD_MAX || Math.abs(p.y) > WHITEBOARD_COORD_MAX
|
|
627
|
+
)) {
|
|
628
|
+
warn(lineNumber, WHITEBOARD_DIAGNOSTIC_CODES.BAD_INK, {
|
|
629
|
+
reason: `a point lies beyond \xB1${WHITEBOARD_COORD_MAX} px`
|
|
630
|
+
});
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
elements.push({
|
|
634
|
+
kind: "ink",
|
|
635
|
+
color,
|
|
636
|
+
width,
|
|
637
|
+
encoded: m[3],
|
|
638
|
+
points: decoded.points,
|
|
639
|
+
lineNumber
|
|
640
|
+
});
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
if (isWhiteboardShapeKind(word)) {
|
|
644
|
+
const { name, meta } = splitLine(rest);
|
|
645
|
+
checkKeys(meta, "shape", word, lineNumber);
|
|
646
|
+
const at = readPair(meta, "at", word, lineNumber, false);
|
|
647
|
+
if (!at) continue;
|
|
648
|
+
const size = readPair(meta, "size", word, lineNumber, true);
|
|
649
|
+
if (!size) continue;
|
|
650
|
+
elements.push({
|
|
651
|
+
kind: "shape",
|
|
652
|
+
shape: word,
|
|
653
|
+
x: at[0],
|
|
654
|
+
y: at[1],
|
|
655
|
+
width: size[0],
|
|
656
|
+
height: size[1],
|
|
657
|
+
label: joinLabel(name, body),
|
|
658
|
+
color: readColor(meta.get("color"), lineNumber),
|
|
659
|
+
fill: readFill(meta.get("fill"), lineNumber),
|
|
660
|
+
lineNumber
|
|
661
|
+
});
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
if (word === "note") {
|
|
665
|
+
const { name, meta } = splitLine(rest);
|
|
666
|
+
checkKeys(meta, "note", "note", lineNumber);
|
|
667
|
+
const at = readPair(meta, "at", "note", lineNumber, false);
|
|
668
|
+
if (!at) continue;
|
|
669
|
+
const size = meta.has("size") ? readPair(meta, "size", "note", lineNumber, true) : [WHITEBOARD_NOTE_WIDTH, WHITEBOARD_NOTE_HEIGHT];
|
|
670
|
+
if (!size) continue;
|
|
671
|
+
elements.push({
|
|
672
|
+
kind: "note",
|
|
673
|
+
x: at[0],
|
|
674
|
+
y: at[1],
|
|
675
|
+
width: size[0],
|
|
676
|
+
height: size[1],
|
|
677
|
+
text: joinLabel(name, body),
|
|
678
|
+
color: readColor(meta.get("color"), lineNumber, WHITEBOARD_NOTE_COLOR),
|
|
679
|
+
lineNumber
|
|
680
|
+
});
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
if (word === "arrow" || word === "line") {
|
|
684
|
+
const { name, meta } = splitLine(rest);
|
|
685
|
+
checkKeys(meta, word, word, lineNumber);
|
|
686
|
+
const from = readPair(meta, "from", word, lineNumber, false);
|
|
687
|
+
if (!from) continue;
|
|
688
|
+
const to = readPair(meta, "to", word, lineNumber, false);
|
|
689
|
+
if (!to) continue;
|
|
690
|
+
const common = {
|
|
691
|
+
x1: from[0],
|
|
692
|
+
y1: from[1],
|
|
693
|
+
x2: to[0],
|
|
694
|
+
y2: to[1],
|
|
695
|
+
label: joinLabel(name, body),
|
|
696
|
+
color: readColor(meta.get("color"), lineNumber),
|
|
697
|
+
style: readStyle(meta.get("style"), lineNumber),
|
|
698
|
+
bend: readBend(meta.get("bend"), lineNumber),
|
|
699
|
+
lineNumber
|
|
700
|
+
};
|
|
701
|
+
elements.push(
|
|
702
|
+
word === "arrow" ? {
|
|
703
|
+
kind: "arrow",
|
|
704
|
+
...common,
|
|
705
|
+
heads: readHeads(meta.get("heads"), lineNumber)
|
|
706
|
+
} : { kind: "line", ...common }
|
|
707
|
+
);
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
if (word === "text") {
|
|
711
|
+
const { name, meta } = splitLine(rest);
|
|
712
|
+
checkKeys(meta, "text", "text", lineNumber);
|
|
713
|
+
const text = joinLabel(name, body);
|
|
714
|
+
if (!text) {
|
|
715
|
+
warn(lineNumber, WHITEBOARD_DIAGNOSTIC_CODES.EMPTY_TEXT, {});
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
const at = readPair(meta, "at", "text", lineNumber, false);
|
|
719
|
+
if (!at) continue;
|
|
720
|
+
elements.push({
|
|
721
|
+
kind: "text",
|
|
722
|
+
x: at[0],
|
|
723
|
+
y: at[1],
|
|
724
|
+
text,
|
|
725
|
+
color: readColor(meta.get("color"), lineNumber),
|
|
726
|
+
lineNumber
|
|
727
|
+
});
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (word === "image") {
|
|
731
|
+
const { name, meta } = splitLine(rest);
|
|
732
|
+
checkKeys(meta, "image", "image", lineNumber);
|
|
733
|
+
noBody("image");
|
|
734
|
+
if (!isWhiteboardImageRef(name)) {
|
|
735
|
+
warn(lineNumber, WHITEBOARD_DIAGNOSTIC_CODES.BAD_IMAGE_REF, { ref: name });
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
const at = readPair(meta, "at", "image", lineNumber, false);
|
|
739
|
+
if (!at) continue;
|
|
740
|
+
const size = readPair(meta, "size", "image", lineNumber, true);
|
|
741
|
+
if (!size) continue;
|
|
742
|
+
elements.push({
|
|
743
|
+
kind: "image",
|
|
744
|
+
ref: name,
|
|
745
|
+
x: at[0],
|
|
746
|
+
y: at[1],
|
|
747
|
+
width: size[0],
|
|
748
|
+
height: size[1],
|
|
749
|
+
lineNumber
|
|
750
|
+
});
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
warn(lineNumber, WHITEBOARD_DIAGNOSTIC_CODES.UNKNOWN_ELEMENT, {
|
|
754
|
+
word,
|
|
755
|
+
hint: suggest(word, WHITEBOARD_ELEMENT_KEYWORDS) ?? ""
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
if (!firstLineSeen) return fail(0, "No content provided");
|
|
759
|
+
return result;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
export {
|
|
763
|
+
WHITEBOARD_DIAGNOSTICS,
|
|
764
|
+
INK_DEFAULT_TOLERANCE,
|
|
765
|
+
INK_PRESSURE_LEVELS,
|
|
766
|
+
encodeInk,
|
|
767
|
+
decodeInk,
|
|
768
|
+
WHITEBOARD_COLORS,
|
|
769
|
+
WHITEBOARD_SHAPE_KINDS,
|
|
770
|
+
WHITEBOARD_ELEMENT_KEYWORDS,
|
|
771
|
+
WHITEBOARD_STROKE_STYLES,
|
|
772
|
+
WHITEBOARD_FILLS,
|
|
773
|
+
WHITEBOARD_HEADS,
|
|
774
|
+
WHITEBOARD_NOTE_WIDTH,
|
|
775
|
+
WHITEBOARD_NOTE_HEIGHT,
|
|
776
|
+
WHITEBOARD_NOTE_COLOR,
|
|
777
|
+
isWhiteboardColor,
|
|
778
|
+
isWhiteboardShapeKind,
|
|
779
|
+
isWhiteboardImageRef,
|
|
780
|
+
parseWhiteboard
|
|
781
|
+
};
|