@apocaliss92/nodedreame 1.11.3 → 1.11.5
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/index.cjs +159 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -5
- package/dist/index.d.ts +22 -5
- package/dist/index.js +159 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1229,18 +1229,26 @@ interface VacuumMap {
|
|
|
1229
1229
|
*
|
|
1230
1230
|
* Coordinate transform: every overlay coordinate is mm in the world frame.
|
|
1231
1231
|
* `dimensions.left` / `.top` are the world-mm of pixel (0,0) and `.gridSize` is
|
|
1232
|
-
* mm-per-pixel, so `
|
|
1233
|
-
*
|
|
1232
|
+
* mm-per-pixel, so `xPx = (worldX - left) / gridSize` (then × `scale`).
|
|
1233
|
+
*
|
|
1234
|
+
* Y IS FLIPPED. Dreame's grid has world-Y increasing DOWNWARD relative to how
|
|
1235
|
+
* the robot/dock/app present the floor, so rendering rows top-down produced a
|
|
1236
|
+
* VERTICALLY MIRRORED map vs the Dreamehome app / Home Assistant (live-confirmed
|
|
1237
|
+
* on r2538z). We reflect in cell space — `yPx = ((height - 1) - cellY) × scale` —
|
|
1238
|
+
* applied identically to the run-length base layers ({@link paintRun}) and the
|
|
1239
|
+
* world-frame overlays ({@link worldToPx}) so they stay pixel-aligned, and the
|
|
1240
|
+
* robot heading's Y component is negated ({@link drawRobot}). Glyph labels are
|
|
1241
|
+
* drawn upright (only their anchor is flipped).
|
|
1234
1242
|
*
|
|
1235
1243
|
* Pure: only `pngjs` + arithmetic. No casts.
|
|
1236
1244
|
*/
|
|
1237
1245
|
|
|
1238
|
-
/** A named map palette
|
|
1239
|
-
type MapColorScheme = '
|
|
1246
|
+
/** A named map palette — node-dreame's own distinct visual styles. */
|
|
1247
|
+
type MapColorScheme = 'flat' | 'dark-neon' | 'materico';
|
|
1240
1248
|
interface RenderVacuumPngOptions {
|
|
1241
1249
|
/** Integer upscale factor (nearest-neighbour). Default 1. */
|
|
1242
1250
|
scale?: number;
|
|
1243
|
-
/** Palette. Default `'
|
|
1251
|
+
/** Palette. Default `'flat'`. */
|
|
1244
1252
|
colorScheme?: MapColorScheme;
|
|
1245
1253
|
/** Draw the cleaning/mopping path polylines. Default true. */
|
|
1246
1254
|
showPath?: boolean;
|
|
@@ -1256,6 +1264,15 @@ interface RenderVacuumPngOptions {
|
|
|
1256
1264
|
showObstacles?: boolean;
|
|
1257
1265
|
/** Draw the segment id as a tiny label at each room centroid. Default false. */
|
|
1258
1266
|
showSegmentLabels?: boolean;
|
|
1267
|
+
/** Draw each room's NAME (falls back to `Room <id>`) at its centroid. Default
|
|
1268
|
+
* false. Mutually useful with `showSegmentLabels` (names take the centroid). */
|
|
1269
|
+
showSegmentNames?: boolean;
|
|
1270
|
+
/** Tint the cleaned-area overlay (`cleanedArea.cleaned`) when present. Default false. */
|
|
1271
|
+
showCleanedArea?: boolean;
|
|
1272
|
+
/** Outline low-clearance "sneak under furniture" zones. Default true. */
|
|
1273
|
+
showFurniture?: boolean;
|
|
1274
|
+
/** Colour AI obstacle markers by their `type` (else a single accent). Default true. */
|
|
1275
|
+
colorObstaclesByType?: boolean;
|
|
1259
1276
|
}
|
|
1260
1277
|
declare function renderVacuumPng(map: VacuumMap, opts?: RenderVacuumPngOptions): Buffer;
|
|
1261
1278
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1229,18 +1229,26 @@ interface VacuumMap {
|
|
|
1229
1229
|
*
|
|
1230
1230
|
* Coordinate transform: every overlay coordinate is mm in the world frame.
|
|
1231
1231
|
* `dimensions.left` / `.top` are the world-mm of pixel (0,0) and `.gridSize` is
|
|
1232
|
-
* mm-per-pixel, so `
|
|
1233
|
-
*
|
|
1232
|
+
* mm-per-pixel, so `xPx = (worldX - left) / gridSize` (then × `scale`).
|
|
1233
|
+
*
|
|
1234
|
+
* Y IS FLIPPED. Dreame's grid has world-Y increasing DOWNWARD relative to how
|
|
1235
|
+
* the robot/dock/app present the floor, so rendering rows top-down produced a
|
|
1236
|
+
* VERTICALLY MIRRORED map vs the Dreamehome app / Home Assistant (live-confirmed
|
|
1237
|
+
* on r2538z). We reflect in cell space — `yPx = ((height - 1) - cellY) × scale` —
|
|
1238
|
+
* applied identically to the run-length base layers ({@link paintRun}) and the
|
|
1239
|
+
* world-frame overlays ({@link worldToPx}) so they stay pixel-aligned, and the
|
|
1240
|
+
* robot heading's Y component is negated ({@link drawRobot}). Glyph labels are
|
|
1241
|
+
* drawn upright (only their anchor is flipped).
|
|
1234
1242
|
*
|
|
1235
1243
|
* Pure: only `pngjs` + arithmetic. No casts.
|
|
1236
1244
|
*/
|
|
1237
1245
|
|
|
1238
|
-
/** A named map palette
|
|
1239
|
-
type MapColorScheme = '
|
|
1246
|
+
/** A named map palette — node-dreame's own distinct visual styles. */
|
|
1247
|
+
type MapColorScheme = 'flat' | 'dark-neon' | 'materico';
|
|
1240
1248
|
interface RenderVacuumPngOptions {
|
|
1241
1249
|
/** Integer upscale factor (nearest-neighbour). Default 1. */
|
|
1242
1250
|
scale?: number;
|
|
1243
|
-
/** Palette. Default `'
|
|
1251
|
+
/** Palette. Default `'flat'`. */
|
|
1244
1252
|
colorScheme?: MapColorScheme;
|
|
1245
1253
|
/** Draw the cleaning/mopping path polylines. Default true. */
|
|
1246
1254
|
showPath?: boolean;
|
|
@@ -1256,6 +1264,15 @@ interface RenderVacuumPngOptions {
|
|
|
1256
1264
|
showObstacles?: boolean;
|
|
1257
1265
|
/** Draw the segment id as a tiny label at each room centroid. Default false. */
|
|
1258
1266
|
showSegmentLabels?: boolean;
|
|
1267
|
+
/** Draw each room's NAME (falls back to `Room <id>`) at its centroid. Default
|
|
1268
|
+
* false. Mutually useful with `showSegmentLabels` (names take the centroid). */
|
|
1269
|
+
showSegmentNames?: boolean;
|
|
1270
|
+
/** Tint the cleaned-area overlay (`cleanedArea.cleaned`) when present. Default false. */
|
|
1271
|
+
showCleanedArea?: boolean;
|
|
1272
|
+
/** Outline low-clearance "sneak under furniture" zones. Default true. */
|
|
1273
|
+
showFurniture?: boolean;
|
|
1274
|
+
/** Colour AI obstacle markers by their `type` (else a single accent). Default true. */
|
|
1275
|
+
colorObstaclesByType?: boolean;
|
|
1259
1276
|
}
|
|
1260
1277
|
declare function renderVacuumPng(map: VacuumMap, opts?: RenderVacuumPngOptions): Buffer;
|
|
1261
1278
|
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/support/version.ts
|
|
2
2
|
var LIBRARY_NAME = "nodedreame";
|
|
3
|
-
var LIBRARY_VERSION = "1.11.
|
|
3
|
+
var LIBRARY_VERSION = "1.11.5";
|
|
4
4
|
|
|
5
5
|
// src/transport/errors.ts
|
|
6
6
|
var DreameError = class extends Error {
|
|
@@ -2827,77 +2827,65 @@ function pose(x, y, angle, absent) {
|
|
|
2827
2827
|
// src/models/vacuum/map/render.ts
|
|
2828
2828
|
import { PNG } from "pngjs";
|
|
2829
2829
|
var SCHEMES = {
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2830
|
+
// Flat-design: soft neutral floor, bright low-saturation segment fills, thin
|
|
2831
|
+
// mid-grey walls, a single vivid accent for the path.
|
|
2832
|
+
flat: {
|
|
2833
|
+
floor: [236, 239, 241, 255],
|
|
2834
|
+
wall: [120, 132, 145, 255],
|
|
2835
|
+
carpet: [205, 185, 160, 200],
|
|
2836
|
+
segSat: 0.28,
|
|
2837
|
+
segVal: 0.99,
|
|
2838
|
+
path: [255, 138, 101, 240],
|
|
2839
|
+
robotBody: [38, 50, 56, 255],
|
|
2838
2840
|
robotHeading: [255, 255, 255, 255],
|
|
2839
|
-
charger: [
|
|
2840
|
-
noGoFill: [
|
|
2841
|
-
noGoBorder: [
|
|
2842
|
-
noMopFill: [
|
|
2843
|
-
noMopBorder: [
|
|
2844
|
-
virtualWall: [
|
|
2845
|
-
obstacle: [
|
|
2846
|
-
label: [
|
|
2847
|
-
},
|
|
2848
|
-
"dreame-dark": {
|
|
2849
|
-
floor: [40, 46, 56, 255],
|
|
2850
|
-
wall: [15, 15, 18, 255],
|
|
2851
|
-
carpet: [80, 66, 52, 200],
|
|
2852
|
-
segSat: 0.5,
|
|
2853
|
-
segVal: 0.62,
|
|
2854
|
-
path: [120, 170, 230, 235],
|
|
2855
|
-
robotBody: [120, 170, 230, 255],
|
|
2856
|
-
robotHeading: [20, 24, 30, 255],
|
|
2857
|
-
charger: [60, 200, 110, 255],
|
|
2858
|
-
noGoFill: [220, 70, 70, 80],
|
|
2859
|
-
noGoBorder: [230, 70, 70, 220],
|
|
2860
|
-
noMopFill: [70, 130, 230, 70],
|
|
2861
|
-
noMopBorder: [80, 140, 235, 210],
|
|
2862
|
-
virtualWall: [230, 70, 70, 235],
|
|
2863
|
-
obstacle: [240, 180, 60, 255],
|
|
2864
|
-
label: [225, 230, 240, 255]
|
|
2841
|
+
charger: [38, 166, 154, 255],
|
|
2842
|
+
noGoFill: [239, 83, 80, 60],
|
|
2843
|
+
noGoBorder: [229, 57, 53, 210],
|
|
2844
|
+
noMopFill: [66, 165, 245, 55],
|
|
2845
|
+
noMopBorder: [30, 136, 229, 200],
|
|
2846
|
+
virtualWall: [229, 57, 53, 225],
|
|
2847
|
+
obstacle: [255, 167, 38, 255],
|
|
2848
|
+
label: [55, 71, 79, 255]
|
|
2865
2849
|
},
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2850
|
+
// Dark-neon: near-black floor with high-saturation glowing segment fills, a
|
|
2851
|
+
// bright cyan path and magenta robot.
|
|
2852
|
+
"dark-neon": {
|
|
2853
|
+
floor: [16, 18, 27, 255],
|
|
2854
|
+
wall: [5, 6, 10, 255],
|
|
2855
|
+
carpet: [60, 50, 80, 200],
|
|
2856
|
+
segSat: 0.85,
|
|
2857
|
+
segVal: 0.95,
|
|
2858
|
+
path: [0, 230, 255, 240],
|
|
2859
|
+
robotBody: [255, 45, 170, 255],
|
|
2874
2860
|
robotHeading: [255, 255, 255, 255],
|
|
2875
|
-
charger: [
|
|
2876
|
-
noGoFill: [
|
|
2877
|
-
noGoBorder: [
|
|
2878
|
-
noMopFill: [
|
|
2879
|
-
noMopBorder: [
|
|
2880
|
-
virtualWall: [
|
|
2881
|
-
obstacle: [
|
|
2882
|
-
label: [
|
|
2861
|
+
charger: [0, 255, 150, 255],
|
|
2862
|
+
noGoFill: [255, 40, 90, 80],
|
|
2863
|
+
noGoBorder: [255, 40, 90, 235],
|
|
2864
|
+
noMopFill: [40, 130, 255, 75],
|
|
2865
|
+
noMopBorder: [40, 160, 255, 225],
|
|
2866
|
+
virtualWall: [255, 40, 90, 235],
|
|
2867
|
+
obstacle: [255, 220, 0, 255],
|
|
2868
|
+
label: [230, 240, 255, 255]
|
|
2883
2869
|
},
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2870
|
+
// Materico (Material-design): light grey surface, Material-palette segment
|
|
2871
|
+
// fills, indigo/teal accents.
|
|
2872
|
+
materico: {
|
|
2873
|
+
floor: [245, 245, 245, 255],
|
|
2874
|
+
wall: [97, 97, 97, 255],
|
|
2875
|
+
carpet: [188, 170, 144, 205],
|
|
2876
|
+
segSat: 0.55,
|
|
2877
|
+
segVal: 0.93,
|
|
2878
|
+
path: [63, 81, 181, 235],
|
|
2879
|
+
robotBody: [48, 63, 159, 255],
|
|
2892
2880
|
robotHeading: [255, 255, 255, 255],
|
|
2893
|
-
charger: [
|
|
2894
|
-
noGoFill: [
|
|
2895
|
-
noGoBorder: [
|
|
2896
|
-
noMopFill: [
|
|
2897
|
-
noMopBorder: [
|
|
2898
|
-
virtualWall: [
|
|
2899
|
-
obstacle: [
|
|
2900
|
-
label: [
|
|
2881
|
+
charger: [0, 150, 136, 255],
|
|
2882
|
+
noGoFill: [244, 67, 54, 65],
|
|
2883
|
+
noGoBorder: [211, 47, 47, 215],
|
|
2884
|
+
noMopFill: [33, 150, 243, 60],
|
|
2885
|
+
noMopBorder: [25, 118, 210, 205],
|
|
2886
|
+
virtualWall: [211, 47, 47, 225],
|
|
2887
|
+
obstacle: [255, 152, 0, 255],
|
|
2888
|
+
label: [33, 33, 33, 255]
|
|
2901
2889
|
}
|
|
2902
2890
|
};
|
|
2903
2891
|
function hsvToRgba(h, s, v) {
|
|
@@ -2935,15 +2923,18 @@ function segmentColor(id, pal) {
|
|
|
2935
2923
|
}
|
|
2936
2924
|
function renderVacuumPng(map, opts = {}) {
|
|
2937
2925
|
const scale = Math.max(1, Math.trunc(opts.scale ?? 1));
|
|
2938
|
-
const pal = SCHEMES[opts.colorScheme ?? "
|
|
2926
|
+
const pal = SCHEMES[opts.colorScheme ?? "flat"] ?? SCHEMES.flat;
|
|
2939
2927
|
const w = Math.max(1, map.dimensions.width * scale);
|
|
2940
2928
|
const h = Math.max(1, map.dimensions.height * scale);
|
|
2941
2929
|
const png = new PNG({ width: w, height: h });
|
|
2942
2930
|
png.data.fill(0);
|
|
2931
|
+
const dim = map.dimensions;
|
|
2943
2932
|
for (const layer of map.layers) {
|
|
2944
|
-
paintLayer(png, layer, scale, pal);
|
|
2933
|
+
paintLayer(png, layer, dim.height, scale, pal);
|
|
2934
|
+
}
|
|
2935
|
+
if (opts.showCleanedArea === true && map.cleanedArea !== null) {
|
|
2936
|
+
paintCleanedArea(png, map.cleanedArea, dim, scale, pal.path);
|
|
2945
2937
|
}
|
|
2946
|
-
const dim = map.dimensions;
|
|
2947
2938
|
if (opts.showNoGo !== false) {
|
|
2948
2939
|
for (const area of map.restrictedAreas) {
|
|
2949
2940
|
const fill = area.kind === "noMop" ? pal.noMopFill : pal.noGoFill;
|
|
@@ -2966,10 +2957,17 @@ function renderVacuumPng(map, opts = {}) {
|
|
|
2966
2957
|
drawPolyline(png, path.points, dim, scale, pal.path, Math.max(1, scale));
|
|
2967
2958
|
}
|
|
2968
2959
|
}
|
|
2960
|
+
if (opts.showFurniture !== false) {
|
|
2961
|
+
for (const zone of map.lowLyingAreas) {
|
|
2962
|
+
paintFurnitureZone(png, zone.points, dim, scale);
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2969
2965
|
if (opts.showObstacles !== false) {
|
|
2966
|
+
const byType = opts.colorObstaclesByType !== false;
|
|
2970
2967
|
for (const ob of map.obstacles) {
|
|
2971
2968
|
const p = worldToPx(ob.x, ob.y, dim, scale);
|
|
2972
|
-
|
|
2969
|
+
const color = byType ? obstacleColor(ob.type) : pal.obstacle;
|
|
2970
|
+
drawDiamond(png, p.x, p.y, Math.max(2, scale * 2), color);
|
|
2973
2971
|
}
|
|
2974
2972
|
}
|
|
2975
2973
|
if (opts.showCharger !== false && map.dock !== null) {
|
|
@@ -2978,31 +2976,33 @@ function renderVacuumPng(map, opts = {}) {
|
|
|
2978
2976
|
if (opts.showRobot !== false && map.robot !== null) {
|
|
2979
2977
|
drawRobot(png, map.robot, dim, scale, pal);
|
|
2980
2978
|
}
|
|
2981
|
-
if (opts.showSegmentLabels === true) {
|
|
2979
|
+
if (opts.showSegmentNames === true || opts.showSegmentLabels === true) {
|
|
2982
2980
|
for (const seg of map.segments) {
|
|
2983
2981
|
const p = worldToPx(seg.centroid.x, seg.centroid.y, dim, scale);
|
|
2984
|
-
|
|
2982
|
+
const text = opts.showSegmentNames === true ? labelText(seg.name, seg.id) : String(seg.id);
|
|
2983
|
+
drawLabel(png, p.x, p.y, text, pal.label, Math.max(1, scale));
|
|
2985
2984
|
}
|
|
2986
2985
|
}
|
|
2987
2986
|
return PNG.sync.write(png);
|
|
2988
2987
|
}
|
|
2989
2988
|
function worldToPx(worldX, worldY, dim, scale) {
|
|
2989
|
+
const cellY = (worldY - dim.top) / dim.gridSize;
|
|
2990
2990
|
return {
|
|
2991
2991
|
x: Math.round((worldX - dim.left) / dim.gridSize * scale),
|
|
2992
|
-
y: Math.round((
|
|
2992
|
+
y: Math.round((dim.height - 1 - cellY) * scale)
|
|
2993
2993
|
};
|
|
2994
2994
|
}
|
|
2995
|
-
function paintLayer(png, layer, scale, pal) {
|
|
2995
|
+
function paintLayer(png, layer, dimHeight, scale, pal) {
|
|
2996
2996
|
const color = layer.type === "segment" ? segmentColor(layer.segmentId ?? 0, pal) : layer.type === "wall" ? pal.wall : layer.type === "carpet" ? pal.carpet : pal.floor;
|
|
2997
2997
|
for (const run of layer.runs) {
|
|
2998
|
-
paintRun(png, run, color, scale);
|
|
2998
|
+
paintRun(png, run, dimHeight, color, scale);
|
|
2999
2999
|
}
|
|
3000
3000
|
}
|
|
3001
|
-
function paintRun(png, run, color, scale) {
|
|
3001
|
+
function paintRun(png, run, dimHeight, color, scale) {
|
|
3002
3002
|
const [xPx, yPx, len] = run;
|
|
3003
|
+
const baseY = (dimHeight - 1 - yPx) * scale;
|
|
3003
3004
|
for (let i = 0; i < len; i += 1) {
|
|
3004
3005
|
const baseX = (xPx + i) * scale;
|
|
3005
|
-
const baseY = yPx * scale;
|
|
3006
3006
|
for (let dy = 0; dy < scale; dy += 1) {
|
|
3007
3007
|
for (let dx = 0; dx < scale; dx += 1) {
|
|
3008
3008
|
setPixel(png, baseX + dx, baseY + dy, color);
|
|
@@ -3108,7 +3108,7 @@ function drawRobot(png, robot, dim, scale, pal) {
|
|
|
3108
3108
|
drawDisk(png, p.x, p.y, r, pal.robotBody);
|
|
3109
3109
|
const rad = robot.angle * Math.PI / 180;
|
|
3110
3110
|
const hx = Math.round(p.x + Math.cos(rad) * r);
|
|
3111
|
-
const hy = Math.round(p.y
|
|
3111
|
+
const hy = Math.round(p.y - Math.sin(rad) * r);
|
|
3112
3112
|
drawLine(png, p.x, p.y, hx, hy, pal.robotHeading, Math.max(1, Math.trunc(scale / 2) + 1));
|
|
3113
3113
|
}
|
|
3114
3114
|
function drawCharger(png, dock, dim, scale, pal) {
|
|
@@ -3116,6 +3116,58 @@ function drawCharger(png, dock, dim, scale, pal) {
|
|
|
3116
3116
|
const r = Math.max(2, scale * 2);
|
|
3117
3117
|
fillRect(png, p.x - r, p.y - r, p.x + r, p.y + r, pal.charger);
|
|
3118
3118
|
}
|
|
3119
|
+
function drawDiamond(png, cx, cy, r, color) {
|
|
3120
|
+
for (let dy = -r; dy <= r; dy += 1) {
|
|
3121
|
+
const w = r - Math.abs(dy);
|
|
3122
|
+
for (let dx = -w; dx <= w; dx += 1) {
|
|
3123
|
+
setPixel(png, cx + dx, cy + dy, color);
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
function obstacleColor(type) {
|
|
3128
|
+
const hue = Math.abs(Math.trunc(type)) * 47 % 360;
|
|
3129
|
+
return hsvToRgba(hue, 0.85, 0.95);
|
|
3130
|
+
}
|
|
3131
|
+
var FURNITURE_LINE = [150, 110, 200, 235];
|
|
3132
|
+
function paintFurnitureZone(png, points, dim, scale) {
|
|
3133
|
+
if (points.length < 2) return;
|
|
3134
|
+
for (let i = 0; i < points.length; i += 1) {
|
|
3135
|
+
const a = points[i];
|
|
3136
|
+
const b = points[(i + 1) % points.length];
|
|
3137
|
+
if (a === void 0 || b === void 0) continue;
|
|
3138
|
+
const pa = worldToPx(a.x, a.y, dim, scale);
|
|
3139
|
+
const pb = worldToPx(b.x, b.y, dim, scale);
|
|
3140
|
+
drawLine(png, pa.x, pa.y, pb.x, pb.y, FURNITURE_LINE, Math.max(1, scale));
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
function paintCleanedArea(png, overlay, parentDim, scale, baseColor) {
|
|
3144
|
+
const tint = [baseColor[0], baseColor[1], baseColor[2], 55];
|
|
3145
|
+
const inner = overlay.dimensions;
|
|
3146
|
+
const block = Math.max(1, Math.round(inner.gridSize / parentDim.gridSize * scale));
|
|
3147
|
+
for (const [xPx, yPx, len] of overlay.cleaned) {
|
|
3148
|
+
for (let i = 0; i < len; i += 1) {
|
|
3149
|
+
const worldX = inner.left + (xPx + i) * inner.gridSize;
|
|
3150
|
+
const worldY = inner.top + yPx * inner.gridSize;
|
|
3151
|
+
const p = worldToPx(worldX, worldY, parentDim, scale);
|
|
3152
|
+
for (let dy = 0; dy < block; dy += 1) {
|
|
3153
|
+
for (let dx = 0; dx < block; dx += 1) {
|
|
3154
|
+
setPixel(png, p.x + dx, p.y + dy, tint);
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
function labelText(name, id) {
|
|
3161
|
+
const raw = (name ?? "").trim();
|
|
3162
|
+
const base = raw.length > 0 ? raw : `ROOM ${id}`;
|
|
3163
|
+
const upper = base.toUpperCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
3164
|
+
let out = "";
|
|
3165
|
+
for (const ch of upper) {
|
|
3166
|
+
if (GLYPHS[ch] !== void 0) out += ch;
|
|
3167
|
+
}
|
|
3168
|
+
const trimmed = out.trim().slice(0, 14);
|
|
3169
|
+
return trimmed.length > 0 ? trimmed : `ROOM ${id}`;
|
|
3170
|
+
}
|
|
3119
3171
|
var GLYPHS = {
|
|
3120
3172
|
"0": [7, 5, 5, 5, 7],
|
|
3121
3173
|
"1": [2, 6, 2, 2, 7],
|
|
@@ -3127,7 +3179,34 @@ var GLYPHS = {
|
|
|
3127
3179
|
"7": [7, 1, 2, 2, 2],
|
|
3128
3180
|
"8": [7, 5, 7, 5, 7],
|
|
3129
3181
|
"9": [7, 5, 7, 1, 7],
|
|
3130
|
-
"-": [0, 0, 7, 0, 0]
|
|
3182
|
+
"-": [0, 0, 7, 0, 0],
|
|
3183
|
+
" ": [0, 0, 0, 0, 0],
|
|
3184
|
+
A: [2, 5, 7, 5, 5],
|
|
3185
|
+
B: [6, 5, 6, 5, 6],
|
|
3186
|
+
C: [3, 4, 4, 4, 3],
|
|
3187
|
+
D: [6, 5, 5, 5, 6],
|
|
3188
|
+
E: [7, 4, 6, 4, 7],
|
|
3189
|
+
F: [7, 4, 6, 4, 4],
|
|
3190
|
+
G: [3, 4, 5, 5, 3],
|
|
3191
|
+
H: [5, 5, 7, 5, 5],
|
|
3192
|
+
I: [7, 2, 2, 2, 7],
|
|
3193
|
+
J: [1, 1, 1, 5, 2],
|
|
3194
|
+
K: [5, 5, 6, 5, 5],
|
|
3195
|
+
L: [4, 4, 4, 4, 7],
|
|
3196
|
+
M: [5, 7, 7, 5, 5],
|
|
3197
|
+
N: [5, 7, 7, 7, 5],
|
|
3198
|
+
O: [2, 5, 5, 5, 2],
|
|
3199
|
+
P: [6, 5, 6, 4, 4],
|
|
3200
|
+
Q: [2, 5, 5, 6, 3],
|
|
3201
|
+
R: [6, 5, 6, 5, 5],
|
|
3202
|
+
S: [3, 4, 2, 1, 6],
|
|
3203
|
+
T: [7, 2, 2, 2, 2],
|
|
3204
|
+
U: [5, 5, 5, 5, 7],
|
|
3205
|
+
V: [5, 5, 5, 5, 2],
|
|
3206
|
+
W: [5, 5, 7, 7, 5],
|
|
3207
|
+
X: [5, 5, 2, 5, 5],
|
|
3208
|
+
Y: [5, 5, 2, 2, 2],
|
|
3209
|
+
Z: [7, 1, 2, 4, 7]
|
|
3131
3210
|
};
|
|
3132
3211
|
function drawLabel(png, cx, cy, text, color, px) {
|
|
3133
3212
|
const glyphW = 3 * px;
|