@chanmeng666/archlang 0.4.0 → 0.5.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/dist/{chunk-DHNWMOP7.js → chunk-GUNWYUR2.js} +299 -36
- package/dist/chunk-GUNWYUR2.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +40 -0
- package/dist/index.js +1 -1
- package/examples/themed.arch +40 -0
- package/package.json +1 -1
- package/dist/chunk-DHNWMOP7.js.map +0 -1
|
@@ -301,6 +301,58 @@ function levenshtein(a, b) {
|
|
|
301
301
|
return dp[m];
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
+
// src/theme.ts
|
|
305
|
+
var DEFAULT_THEME = {
|
|
306
|
+
bg: "#ffffff",
|
|
307
|
+
pocheBase: "#e9e4db",
|
|
308
|
+
pocheHatch: "#b9b1a4",
|
|
309
|
+
wallStroke: "#1b1b1b",
|
|
310
|
+
roomFill: "#fbfaf7",
|
|
311
|
+
roomLabel: "#222222",
|
|
312
|
+
areaLabel: "#7a7a7a",
|
|
313
|
+
furnitureStroke: "#a8a29a",
|
|
314
|
+
furnitureFill: "#f4f2ee",
|
|
315
|
+
furnitureLabel: "#9a948c",
|
|
316
|
+
opening: "#ffffff",
|
|
317
|
+
doorLeaf: "#555555",
|
|
318
|
+
windowPane: "#3a6ea5",
|
|
319
|
+
dim: "#0E5484",
|
|
320
|
+
annotation: "#333333",
|
|
321
|
+
annotationMuted: "#888888",
|
|
322
|
+
column: "#4a4a4a",
|
|
323
|
+
lineWeight: 1,
|
|
324
|
+
font: "Helvetica, Arial, sans-serif"
|
|
325
|
+
};
|
|
326
|
+
var ALIASES = {
|
|
327
|
+
background: "bg",
|
|
328
|
+
wall: "wallStroke",
|
|
329
|
+
wallFill: "pocheBase",
|
|
330
|
+
wallHatch: "pocheHatch",
|
|
331
|
+
room: "roomFill",
|
|
332
|
+
furniture: "furnitureFill",
|
|
333
|
+
door: "doorLeaf",
|
|
334
|
+
window: "windowPane"
|
|
335
|
+
};
|
|
336
|
+
function resolveThemeKey(key) {
|
|
337
|
+
if (key in DEFAULT_THEME) return key;
|
|
338
|
+
if (key in ALIASES) return ALIASES[key];
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
function isNumericThemeKey(key) {
|
|
342
|
+
return key === "lineWeight";
|
|
343
|
+
}
|
|
344
|
+
function mergeTheme(...layers) {
|
|
345
|
+
const out = { ...DEFAULT_THEME };
|
|
346
|
+
for (const layer of layers) {
|
|
347
|
+
if (!layer) continue;
|
|
348
|
+
for (const k of Object.keys(layer)) {
|
|
349
|
+
const v = layer[k];
|
|
350
|
+
if (v !== void 0) out[k] = v;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return out;
|
|
354
|
+
}
|
|
355
|
+
|
|
304
356
|
// src/geometry.ts
|
|
305
357
|
var sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
|
|
306
358
|
var add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
|
|
@@ -392,6 +444,46 @@ function isOnSomeWall(walls, at, ref) {
|
|
|
392
444
|
return false;
|
|
393
445
|
}
|
|
394
446
|
|
|
447
|
+
// src/hatches.ts
|
|
448
|
+
var KNOWN_MATERIALS = ["poche", "concrete", "brick", "insulation", "tile", "none"];
|
|
449
|
+
var DEFAULT_MATERIAL = "poche";
|
|
450
|
+
function patternId(material) {
|
|
451
|
+
return material === "poche" ? "poche" : `hatch-${material}`;
|
|
452
|
+
}
|
|
453
|
+
var HATCHES = {
|
|
454
|
+
poche: (id, c) => `<pattern id="${id}" patternUnits="userSpaceOnUse" width="${c.fmt(c.gap)}" height="${c.fmt(c.gap)}" patternTransform="rotate(45)"><rect width="${c.fmt(c.gap)}" height="${c.fmt(c.gap)}" fill="${c.base}"/><line x1="0" y1="0" x2="0" y2="${c.fmt(c.gap)}" stroke="${c.line}" stroke-width="${c.fmt(c.thin * 0.7)}"/></pattern>`,
|
|
455
|
+
// Aggregate speckle.
|
|
456
|
+
concrete: (id, c) => {
|
|
457
|
+
const w = c.gap * 1.6;
|
|
458
|
+
return `<pattern id="${id}" patternUnits="userSpaceOnUse" width="${c.fmt(w)}" height="${c.fmt(w)}"><rect width="${c.fmt(w)}" height="${c.fmt(w)}" fill="${c.base}"/><circle cx="${c.fmt(w * 0.25)}" cy="${c.fmt(w * 0.3)}" r="${c.fmt(c.thin * 0.9)}" fill="${c.line}"/><circle cx="${c.fmt(w * 0.7)}" cy="${c.fmt(w * 0.62)}" r="${c.fmt(c.thin * 0.6)}" fill="${c.line}"/><circle cx="${c.fmt(w * 0.45)}" cy="${c.fmt(w * 0.85)}" r="${c.fmt(c.thin * 0.75)}" fill="${c.line}"/></pattern>`;
|
|
459
|
+
},
|
|
460
|
+
// Running-bond brick courses.
|
|
461
|
+
brick: (id, c) => {
|
|
462
|
+
const w = c.gap * 3;
|
|
463
|
+
const h = c.gap * 1.4;
|
|
464
|
+
const sw = c.fmt(c.thin * 0.6);
|
|
465
|
+
return `<pattern id="${id}" patternUnits="userSpaceOnUse" width="${c.fmt(w)}" height="${c.fmt(h)}"><rect width="${c.fmt(w)}" height="${c.fmt(h)}" fill="${c.base}"/><line x1="0" y1="${c.fmt(h)}" x2="${c.fmt(w)}" y2="${c.fmt(h)}" stroke="${c.line}" stroke-width="${sw}"/><line x1="0" y1="${c.fmt(h / 2)}" x2="${c.fmt(w)}" y2="${c.fmt(h / 2)}" stroke="${c.line}" stroke-width="${sw}"/><line x1="${c.fmt(w / 2)}" y1="0" x2="${c.fmt(w / 2)}" y2="${c.fmt(h / 2)}" stroke="${c.line}" stroke-width="${sw}"/><line x1="0" y1="${c.fmt(h / 2)}" x2="0" y2="${c.fmt(h)}" stroke="${c.line}" stroke-width="${sw}"/><line x1="${c.fmt(w)}" y1="${c.fmt(h / 2)}" x2="${c.fmt(w)}" y2="${c.fmt(h)}" stroke="${c.line}" stroke-width="${sw}"/></pattern>`;
|
|
466
|
+
},
|
|
467
|
+
// Cross-hatch batting.
|
|
468
|
+
insulation: (id, c) => {
|
|
469
|
+
const w = c.gap * 1.2;
|
|
470
|
+
return `<pattern id="${id}" patternUnits="userSpaceOnUse" width="${c.fmt(w)}" height="${c.fmt(w)}"><rect width="${c.fmt(w)}" height="${c.fmt(w)}" fill="${c.base}"/><path d="M0,0 L${c.fmt(w)},${c.fmt(w)} M${c.fmt(w)},0 L0,${c.fmt(w)}" stroke="${c.line}" stroke-width="${c.fmt(c.thin * 0.5)}" fill="none"/></pattern>`;
|
|
471
|
+
},
|
|
472
|
+
// Square tile grid.
|
|
473
|
+
tile: (id, c) => {
|
|
474
|
+
const w = c.gap * 1.8;
|
|
475
|
+
return `<pattern id="${id}" patternUnits="userSpaceOnUse" width="${c.fmt(w)}" height="${c.fmt(w)}"><rect width="${c.fmt(w)}" height="${c.fmt(w)}" fill="${c.base}"/><rect x="0" y="0" width="${c.fmt(w)}" height="${c.fmt(w)}" fill="none" stroke="${c.line}" stroke-width="${c.fmt(c.thin * 0.6)}"/></pattern>`;
|
|
476
|
+
},
|
|
477
|
+
// Solid fill, no hatch.
|
|
478
|
+
none: (id, c) => `<pattern id="${id}" patternUnits="userSpaceOnUse" width="${c.fmt(c.gap)}" height="${c.fmt(c.gap)}"><rect width="${c.fmt(c.gap)}" height="${c.fmt(c.gap)}" fill="${c.base}"/></pattern>`
|
|
479
|
+
};
|
|
480
|
+
function isKnownMaterial(name) {
|
|
481
|
+
return KNOWN_MATERIALS.includes(name);
|
|
482
|
+
}
|
|
483
|
+
function hatchPattern(material, c) {
|
|
484
|
+
return HATCHES[material](patternId(material), c);
|
|
485
|
+
}
|
|
486
|
+
|
|
395
487
|
// src/elements/wall.ts
|
|
396
488
|
var wall = {
|
|
397
489
|
kind: "wall",
|
|
@@ -402,6 +494,11 @@ var wall = {
|
|
|
402
494
|
const category = ctx.eatIdent().value;
|
|
403
495
|
ctx.eatKeyword("thickness");
|
|
404
496
|
const thickness = ctx.parseExpr();
|
|
497
|
+
let material;
|
|
498
|
+
if (ctx.isKeyword("material")) {
|
|
499
|
+
ctx.next();
|
|
500
|
+
material = ctx.eatIdent().value;
|
|
501
|
+
}
|
|
405
502
|
ctx.eat("lcurly");
|
|
406
503
|
const points = [];
|
|
407
504
|
let closed = false;
|
|
@@ -419,7 +516,7 @@ var wall = {
|
|
|
419
516
|
}
|
|
420
517
|
ctx.eat("rcurly");
|
|
421
518
|
if (points.length < 2) ctx.fail("A wall needs at least two points", kw);
|
|
422
|
-
return { kind: "wall", id, category, thickness, points, closed, line: kw.line };
|
|
519
|
+
return { kind: "wall", id, category, thickness, material, points, closed, line: kw.line };
|
|
423
520
|
},
|
|
424
521
|
idPrefix: (node) => node.category || "wall",
|
|
425
522
|
resolve(node, ctx) {
|
|
@@ -431,7 +528,18 @@ var wall = {
|
|
|
431
528
|
if (thickness <= 0) {
|
|
432
529
|
ctx.diag({ severity: "error", message: `Wall "${id}" must have a positive thickness`, code: "E_WALL_THICKNESS", span: n.span });
|
|
433
530
|
}
|
|
434
|
-
|
|
531
|
+
let material = DEFAULT_MATERIAL;
|
|
532
|
+
if (n.material !== void 0) {
|
|
533
|
+
if (isKnownMaterial(n.material)) material = n.material;
|
|
534
|
+
else
|
|
535
|
+
ctx.diag({
|
|
536
|
+
severity: "warning",
|
|
537
|
+
message: `Unknown wall material "${n.material}" (known: ${KNOWN_MATERIALS.join(", ")}); using the default hatch`,
|
|
538
|
+
code: "W_UNKNOWN_MATERIAL",
|
|
539
|
+
span: n.span
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
return { kind: "wall", id, category: n.category, thickness, material, points, closed: n.closed, span: n.span };
|
|
435
543
|
},
|
|
436
544
|
bounds(resolved) {
|
|
437
545
|
const w = resolved;
|
|
@@ -873,7 +981,7 @@ register(dim);
|
|
|
873
981
|
register(column);
|
|
874
982
|
|
|
875
983
|
// src/parser.ts
|
|
876
|
-
var SETTINGS = ["units", "grid", "scale", "north", "title", "let", "component"];
|
|
984
|
+
var SETTINGS = ["units", "grid", "scale", "north", "title", "theme", "let", "component"];
|
|
877
985
|
var STATEMENT_STARTS = /* @__PURE__ */ new Set([...SETTINGS, ...registry.keys()]);
|
|
878
986
|
var ParseError = class extends Error {
|
|
879
987
|
constructor(message, span) {
|
|
@@ -1036,6 +1144,10 @@ var Parser = class {
|
|
|
1036
1144
|
plan.title = n;
|
|
1037
1145
|
break;
|
|
1038
1146
|
}
|
|
1147
|
+
case "theme": {
|
|
1148
|
+
plan.theme = { ...plan.theme, ...this.parseTheme() };
|
|
1149
|
+
break;
|
|
1150
|
+
}
|
|
1039
1151
|
case "let": {
|
|
1040
1152
|
const n = this.parseLet();
|
|
1041
1153
|
n.span = this.spanFrom(start);
|
|
@@ -1145,6 +1257,35 @@ var Parser = class {
|
|
|
1145
1257
|
this.eat("rcurly");
|
|
1146
1258
|
return node;
|
|
1147
1259
|
}
|
|
1260
|
+
/** `theme { key: <value> … }` — colours (strings), `lineWeight` (number), `font` (string). */
|
|
1261
|
+
parseTheme() {
|
|
1262
|
+
this.eatKeyword("theme");
|
|
1263
|
+
this.eat("lcurly");
|
|
1264
|
+
const t = {};
|
|
1265
|
+
while (!this.isType("rcurly") && !this.isType("eof")) {
|
|
1266
|
+
const keyTok = this.eatIdent();
|
|
1267
|
+
if (this.isType("colon")) this.next();
|
|
1268
|
+
const resolved = resolveThemeKey(keyTok.value);
|
|
1269
|
+
if (!resolved) {
|
|
1270
|
+
this.diagnostics.push({
|
|
1271
|
+
severity: "warning",
|
|
1272
|
+
message: `Unknown theme key "${keyTok.value}"`,
|
|
1273
|
+
code: "W_UNKNOWN_THEME_KEY",
|
|
1274
|
+
span: { start: keyTok.start, end: keyTok.end }
|
|
1275
|
+
});
|
|
1276
|
+
if (this.isType("string") || this.isType("number")) this.next();
|
|
1277
|
+
else this.fail(`Expected a value for theme key "${keyTok.value}"`);
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
if (isNumericThemeKey(resolved)) {
|
|
1281
|
+
t[resolved] = this.eatNumber();
|
|
1282
|
+
} else {
|
|
1283
|
+
t[resolved] = this.eatString();
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
this.eat("rcurly");
|
|
1287
|
+
return t;
|
|
1288
|
+
}
|
|
1148
1289
|
parseLet() {
|
|
1149
1290
|
const kw = this.eatKeyword("let");
|
|
1150
1291
|
const name = this.eatIdent().value;
|
|
@@ -1343,6 +1484,7 @@ function resolve(ast) {
|
|
|
1343
1484
|
scale: ast.scale,
|
|
1344
1485
|
north: ast.north,
|
|
1345
1486
|
title: ast.title,
|
|
1487
|
+
theme: ast.theme,
|
|
1346
1488
|
elements,
|
|
1347
1489
|
walls
|
|
1348
1490
|
};
|
|
@@ -1362,26 +1504,104 @@ var RENDER_PASSES = [
|
|
|
1362
1504
|
"annotations"
|
|
1363
1505
|
];
|
|
1364
1506
|
|
|
1507
|
+
// src/geometry/union.ts
|
|
1508
|
+
function uniqSorted(values) {
|
|
1509
|
+
const out = [...new Set(values)].sort((a, b) => a - b);
|
|
1510
|
+
return out;
|
|
1511
|
+
}
|
|
1512
|
+
function rectUnionOutline(rects) {
|
|
1513
|
+
if (rects.length === 0) return [];
|
|
1514
|
+
const xs = uniqSorted(rects.flatMap((r) => [r.x0, r.x1]));
|
|
1515
|
+
const ys = uniqSorted(rects.flatMap((r) => [r.y0, r.y1]));
|
|
1516
|
+
const nx = xs.length - 1;
|
|
1517
|
+
const ny = ys.length - 1;
|
|
1518
|
+
const filled = (i, j) => {
|
|
1519
|
+
if (i < 0 || j < 0 || i >= nx || j >= ny) return false;
|
|
1520
|
+
const cx = (xs[i] + xs[i + 1]) / 2;
|
|
1521
|
+
const cy = (ys[j] + ys[j + 1]) / 2;
|
|
1522
|
+
return rects.some((r) => cx > r.x0 && cx < r.x1 && cy > r.y0 && cy < r.y1);
|
|
1523
|
+
};
|
|
1524
|
+
const key = (x, y) => `${x},${y}`;
|
|
1525
|
+
const starts = /* @__PURE__ */ new Map();
|
|
1526
|
+
const pushEdge = (ax, ay, bx, by) => {
|
|
1527
|
+
const k = key(ax, ay);
|
|
1528
|
+
const list = starts.get(k);
|
|
1529
|
+
if (list) list.push({ x: bx, y: by });
|
|
1530
|
+
else starts.set(k, [{ x: bx, y: by }]);
|
|
1531
|
+
};
|
|
1532
|
+
for (let i = 0; i < nx; i++) {
|
|
1533
|
+
for (let j = 0; j < ny; j++) {
|
|
1534
|
+
if (!filled(i, j)) continue;
|
|
1535
|
+
const x0 = xs[i];
|
|
1536
|
+
const x1 = xs[i + 1];
|
|
1537
|
+
const y0 = ys[j];
|
|
1538
|
+
const y1 = ys[j + 1];
|
|
1539
|
+
if (!filled(i, j - 1)) pushEdge(x1, y0, x0, y0);
|
|
1540
|
+
if (!filled(i - 1, j)) pushEdge(x0, y0, x0, y1);
|
|
1541
|
+
if (!filled(i, j + 1)) pushEdge(x0, y1, x1, y1);
|
|
1542
|
+
if (!filled(i + 1, j)) pushEdge(x1, y1, x1, y0);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
const used = /* @__PURE__ */ new Set();
|
|
1546
|
+
const edgeKey = (a, b) => `${a.x},${a.y}->${b.x},${b.y}`;
|
|
1547
|
+
const loops = [];
|
|
1548
|
+
const takeNext = (from, prevDir) => {
|
|
1549
|
+
const list = starts.get(key(from.x, from.y));
|
|
1550
|
+
if (!list) return null;
|
|
1551
|
+
const candidates = list.filter((to) => !used.has(edgeKey(from, to)));
|
|
1552
|
+
if (candidates.length === 0) return null;
|
|
1553
|
+
if (candidates.length === 1 || !prevDir) return candidates[0];
|
|
1554
|
+
let best = candidates[0];
|
|
1555
|
+
let bestScore = -Infinity;
|
|
1556
|
+
for (const c of candidates) {
|
|
1557
|
+
const dir = { x: c.x - from.x, y: c.y - from.y };
|
|
1558
|
+
const cross = prevDir.x * dir.y - prevDir.y * dir.x;
|
|
1559
|
+
if (cross > bestScore) {
|
|
1560
|
+
bestScore = cross;
|
|
1561
|
+
best = c;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
return best;
|
|
1565
|
+
};
|
|
1566
|
+
for (const [startKey, ends] of starts) {
|
|
1567
|
+
for (const firstEnd of ends) {
|
|
1568
|
+
const [sx, sy] = startKey.split(",").map(Number);
|
|
1569
|
+
const start = { x: sx, y: sy };
|
|
1570
|
+
if (used.has(edgeKey(start, firstEnd))) continue;
|
|
1571
|
+
const loop = [start];
|
|
1572
|
+
let cur = start;
|
|
1573
|
+
let next = firstEnd;
|
|
1574
|
+
let dir = null;
|
|
1575
|
+
while (next) {
|
|
1576
|
+
used.add(edgeKey(cur, next));
|
|
1577
|
+
if (next.x === start.x && next.y === start.y) break;
|
|
1578
|
+
loop.push(next);
|
|
1579
|
+
dir = { x: next.x - cur.x, y: next.y - cur.y };
|
|
1580
|
+
cur = next;
|
|
1581
|
+
next = takeNext(cur, dir);
|
|
1582
|
+
}
|
|
1583
|
+
if (loop.length >= 4) loops.push(mergeCollinear(loop));
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return loops;
|
|
1587
|
+
}
|
|
1588
|
+
function mergeCollinear(loop) {
|
|
1589
|
+
const n = loop.length;
|
|
1590
|
+
const out = [];
|
|
1591
|
+
for (let i = 0; i < n; i++) {
|
|
1592
|
+
const prev = loop[(i - 1 + n) % n];
|
|
1593
|
+
const cur = loop[i];
|
|
1594
|
+
const next = loop[(i + 1) % n];
|
|
1595
|
+
const d1x = cur.x - prev.x;
|
|
1596
|
+
const d1y = cur.y - prev.y;
|
|
1597
|
+
const d2x = next.x - cur.x;
|
|
1598
|
+
const d2y = next.y - cur.y;
|
|
1599
|
+
if (d1x * d2y - d1y * d2x !== 0) out.push(cur);
|
|
1600
|
+
}
|
|
1601
|
+
return out.length >= 3 ? out : loop;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1365
1604
|
// src/render.ts
|
|
1366
|
-
var THEME = {
|
|
1367
|
-
bg: "#ffffff",
|
|
1368
|
-
pocheBase: "#e9e4db",
|
|
1369
|
-
pocheHatch: "#b9b1a4",
|
|
1370
|
-
wallStroke: "#1b1b1b",
|
|
1371
|
-
roomFill: "#fbfaf7",
|
|
1372
|
-
roomLabel: "#222222",
|
|
1373
|
-
areaLabel: "#7a7a7a",
|
|
1374
|
-
furnitureStroke: "#a8a29a",
|
|
1375
|
-
furnitureFill: "#f4f2ee",
|
|
1376
|
-
furnitureLabel: "#9a948c",
|
|
1377
|
-
opening: "#ffffff",
|
|
1378
|
-
doorLeaf: "#555555",
|
|
1379
|
-
windowPane: "#3a6ea5",
|
|
1380
|
-
dim: "#0E5484",
|
|
1381
|
-
annotation: "#333333",
|
|
1382
|
-
annotationMuted: "#888888",
|
|
1383
|
-
column: "#4a4a4a"
|
|
1384
|
-
};
|
|
1385
1605
|
function fmt(v) {
|
|
1386
1606
|
const r = Math.round(v * 100) / 100;
|
|
1387
1607
|
return Object.is(r, -0) ? "0" : String(r);
|
|
@@ -1408,15 +1628,56 @@ function planBounds(ir) {
|
|
|
1408
1628
|
}
|
|
1409
1629
|
return b;
|
|
1410
1630
|
}
|
|
1631
|
+
function allOrthogonal(walls) {
|
|
1632
|
+
return walls.every((w) => segmentsOfWall(w).every((s) => s.a.x === s.b.x || s.a.y === s.b.y));
|
|
1633
|
+
}
|
|
1634
|
+
function loopsToPath(loops) {
|
|
1635
|
+
return loops.map((loop) => "M " + loop.map(pt).join(" L ") + " Z").join(" ");
|
|
1636
|
+
}
|
|
1637
|
+
function materialsUsed(walls) {
|
|
1638
|
+
return [...new Set(walls.map((w) => w.material))].sort();
|
|
1639
|
+
}
|
|
1640
|
+
function renderWalls(walls, ctx) {
|
|
1641
|
+
if (walls.length === 0) return [];
|
|
1642
|
+
const ops = [];
|
|
1643
|
+
for (const mat of materialsUsed(walls)) {
|
|
1644
|
+
const group = walls.filter((w) => w.material === mat);
|
|
1645
|
+
if (!allOrthogonal(group)) {
|
|
1646
|
+
const def = registry.get("wall");
|
|
1647
|
+
ops.push(...group.flatMap((w) => def.render(w, ctx)));
|
|
1648
|
+
continue;
|
|
1649
|
+
}
|
|
1650
|
+
const rects = [];
|
|
1651
|
+
for (const w of group) {
|
|
1652
|
+
for (const s of segmentsOfWall(w)) {
|
|
1653
|
+
const corners = segmentRectangle(s.a, s.b, s.thickness);
|
|
1654
|
+
const xsv = corners.map((c) => c.x);
|
|
1655
|
+
const ysv = corners.map((c) => c.y);
|
|
1656
|
+
rects.push({ x0: Math.min(...xsv), y0: Math.min(...ysv), x1: Math.max(...xsv), y1: Math.max(...ysv) });
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
const loops = rectUnionOutline(rects);
|
|
1660
|
+
if (loops.length === 0) continue;
|
|
1661
|
+
const d = loopsToPath(loops);
|
|
1662
|
+
ops.push({ pass: "wallFill", svg: `<path d="${d}" fill="url(#${patternId(mat)})" fill-rule="nonzero"/>` });
|
|
1663
|
+
ops.push({
|
|
1664
|
+
pass: "wallFace",
|
|
1665
|
+
svg: `<path d="${d}" fill="none" stroke="${ctx.theme.wallStroke}" stroke-width="${ctx.fmt(ctx.sizes.wallStroke)}" stroke-linejoin="miter"/>`
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
return ops;
|
|
1669
|
+
}
|
|
1411
1670
|
function render(ir, opts = {}) {
|
|
1671
|
+
const THEME = mergeTheme(DEFAULT_THEME, ir.theme, opts.theme);
|
|
1672
|
+
const lw = THEME.lineWeight;
|
|
1412
1673
|
const b = planBounds(ir);
|
|
1413
1674
|
const drawW = b.maxX - b.minX;
|
|
1414
1675
|
const drawH = b.maxY - b.minY;
|
|
1415
1676
|
const refDim = Math.max(drawW, drawH, 1);
|
|
1416
1677
|
const sizes = {
|
|
1417
1678
|
refDim,
|
|
1418
|
-
wallStroke: refDim * 28e-4,
|
|
1419
|
-
thin: refDim * 16e-4,
|
|
1679
|
+
wallStroke: refDim * 28e-4 * lw,
|
|
1680
|
+
thin: refDim * 16e-4 * lw,
|
|
1420
1681
|
roomFont: refDim * 0.03,
|
|
1421
1682
|
areaFont: refDim * 0.022,
|
|
1422
1683
|
dimFont: refDim * 0.02,
|
|
@@ -1432,28 +1693,30 @@ function render(ir, opts = {}) {
|
|
|
1432
1693
|
const out = [];
|
|
1433
1694
|
const svgAttrs = opts.width ? `width="${fmt(opts.width)}" height="${fmt(opts.width * vbH / vbW)}"` : "";
|
|
1434
1695
|
out.push(
|
|
1435
|
-
`<svg xmlns="http://www.w3.org/2000/svg" ${svgAttrs} viewBox="${fmt(vbX)} ${fmt(vbY)} ${fmt(vbW)} ${fmt(vbH)}" font-family="
|
|
1436
|
-
);
|
|
1437
|
-
out.push(
|
|
1438
|
-
`<defs><pattern id="poche" patternUnits="userSpaceOnUse" width="${fmt(hatchGap)}" height="${fmt(hatchGap)}" patternTransform="rotate(45)"><rect width="${fmt(hatchGap)}" height="${fmt(hatchGap)}" fill="${THEME.pocheBase}"/><line x1="0" y1="0" x2="0" y2="${fmt(hatchGap)}" stroke="${THEME.pocheHatch}" stroke-width="${fmt(thin * 0.7)}"/></pattern></defs>`
|
|
1696
|
+
`<svg xmlns="http://www.w3.org/2000/svg" ${svgAttrs} viewBox="${fmt(vbX)} ${fmt(vbY)} ${fmt(vbW)} ${fmt(vbH)}" font-family="${xml(THEME.font)}">`
|
|
1439
1697
|
);
|
|
1698
|
+
const hatchCtx = { fmt, gap: hatchGap, thin, base: THEME.pocheBase, line: THEME.pocheHatch };
|
|
1699
|
+
const patterns = materialsUsed(ir.walls).map((m) => hatchPattern(m, hatchCtx)).join("");
|
|
1700
|
+
out.push(`<defs>${patterns}</defs>`);
|
|
1440
1701
|
out.push(`<rect x="${fmt(vbX)}" y="${fmt(vbY)}" width="${fmt(vbW)}" height="${fmt(vbH)}" fill="${THEME.bg}"/>`);
|
|
1441
1702
|
const ctx = { fmt, pt, xml, theme: THEME, sizes, bounds: b };
|
|
1442
1703
|
const ops = ir.elements.flatMap((el) => {
|
|
1704
|
+
if (el.kind === "wall") return [];
|
|
1443
1705
|
const def = registry.get(el.kind);
|
|
1444
1706
|
return def ? def.render(el, ctx) : [];
|
|
1445
1707
|
});
|
|
1708
|
+
ops.push(...renderWalls(ir.walls, ctx));
|
|
1446
1709
|
for (const pass of RENDER_PASSES) {
|
|
1447
1710
|
for (const op of ops) if (op.pass === pass) out.push(op.svg);
|
|
1448
1711
|
}
|
|
1449
|
-
out.push(northArrow(ir, b, margin, refDim));
|
|
1450
|
-
out.push(scaleBar(b, margin, refDim, thin));
|
|
1451
|
-
const tb = titleBlock(ir, b, margin, refDim, thin);
|
|
1712
|
+
out.push(northArrow(ir, b, margin, refDim, THEME));
|
|
1713
|
+
out.push(scaleBar(b, margin, refDim, thin, THEME));
|
|
1714
|
+
const tb = titleBlock(ir, b, margin, refDim, thin, THEME);
|
|
1452
1715
|
if (tb) out.push(tb);
|
|
1453
1716
|
out.push("</svg>");
|
|
1454
1717
|
return out.join("\n");
|
|
1455
1718
|
}
|
|
1456
|
-
function northArrow(ir, b, margin, refDim) {
|
|
1719
|
+
function northArrow(ir, b, margin, refDim, THEME) {
|
|
1457
1720
|
const r = refDim * 0.045;
|
|
1458
1721
|
const cx = b.maxX - r;
|
|
1459
1722
|
const cy = b.minY - margin * 0.55;
|
|
@@ -1483,7 +1746,7 @@ function northArrow(ir, b, margin, refDim) {
|
|
|
1483
1746
|
const ly = cy + ny * (r + fs * 0.8);
|
|
1484
1747
|
return `<g><polygon points="${tri}" fill="${THEME.annotation}" transform="rotate(${fmt(deg)} ${fmt(cx)} ${fmt(cy)})"/><text x="${fmt(lx)}" y="${fmt(ly)}" font-size="${fmt(fs)}" fill="${THEME.annotation}" text-anchor="middle" dominant-baseline="central">N</text></g>`;
|
|
1485
1748
|
}
|
|
1486
|
-
function scaleBar(b, margin, refDim, thin) {
|
|
1749
|
+
function scaleBar(b, margin, refDim, thin, THEME) {
|
|
1487
1750
|
const barLen = niceBarLength(refDim * 0.3);
|
|
1488
1751
|
const x0 = b.minX;
|
|
1489
1752
|
const y0 = b.maxY + margin * 0.55;
|
|
@@ -1503,7 +1766,7 @@ function scaleBar(b, margin, refDim, thin) {
|
|
|
1503
1766
|
);
|
|
1504
1767
|
return `<g>${parts.join("")}</g>`;
|
|
1505
1768
|
}
|
|
1506
|
-
function titleBlock(ir, b, margin, refDim, thin) {
|
|
1769
|
+
function titleBlock(ir, b, margin, refDim, thin, THEME) {
|
|
1507
1770
|
const t = ir.title;
|
|
1508
1771
|
if (!t && !ir.scale) return null;
|
|
1509
1772
|
const boxW = refDim * 0.34;
|
|
@@ -1596,7 +1859,7 @@ function formatDiagnostic(source, d) {
|
|
|
1596
1859
|
var cache = /* @__PURE__ */ new Map();
|
|
1597
1860
|
var CACHE_MAX = 64;
|
|
1598
1861
|
function compile(source, opts = {}) {
|
|
1599
|
-
const key = JSON.stringify([source, opts.width ?? null]);
|
|
1862
|
+
const key = JSON.stringify([source, opts.width ?? null, opts.theme ?? null]);
|
|
1600
1863
|
if (!opts.noCache) {
|
|
1601
1864
|
const hit = cache.get(key);
|
|
1602
1865
|
if (hit) return hit;
|
|
@@ -1636,4 +1899,4 @@ export {
|
|
|
1636
1899
|
compile,
|
|
1637
1900
|
clearCache
|
|
1638
1901
|
};
|
|
1639
|
-
//# sourceMappingURL=chunk-
|
|
1902
|
+
//# sourceMappingURL=chunk-GUNWYUR2.js.map
|