@apocaliss92/nodedreame 1.11.3 → 1.11.4
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 +164 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -4
- package/dist/index.d.ts +25 -4
- package/dist/index.js +164 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1229,14 +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
|
-
/**
|
|
1239
|
-
|
|
1246
|
+
/**
|
|
1247
|
+
* A named map palette. The first four mirror the HA integration's `color_scheme`
|
|
1248
|
+
* choices; `flat` / `dark-neon` / `materico` are node-dreame additions for
|
|
1249
|
+
* distinct visual styles.
|
|
1250
|
+
*/
|
|
1251
|
+
type MapColorScheme = 'dreame-light' | 'dreame-dark' | 'mijia-light' | 'tasshack' | 'flat' | 'dark-neon' | 'materico';
|
|
1240
1252
|
interface RenderVacuumPngOptions {
|
|
1241
1253
|
/** Integer upscale factor (nearest-neighbour). Default 1. */
|
|
1242
1254
|
scale?: number;
|
|
@@ -1256,6 +1268,15 @@ interface RenderVacuumPngOptions {
|
|
|
1256
1268
|
showObstacles?: boolean;
|
|
1257
1269
|
/** Draw the segment id as a tiny label at each room centroid. Default false. */
|
|
1258
1270
|
showSegmentLabels?: boolean;
|
|
1271
|
+
/** Draw each room's NAME (falls back to `Room <id>`) at its centroid. Default
|
|
1272
|
+
* false. Mutually useful with `showSegmentLabels` (names take the centroid). */
|
|
1273
|
+
showSegmentNames?: boolean;
|
|
1274
|
+
/** Tint the cleaned-area overlay (`cleanedArea.cleaned`) when present. Default false. */
|
|
1275
|
+
showCleanedArea?: boolean;
|
|
1276
|
+
/** Outline low-clearance "sneak under furniture" zones. Default true. */
|
|
1277
|
+
showFurniture?: boolean;
|
|
1278
|
+
/** Colour AI obstacle markers by their `type` (else a single accent). Default true. */
|
|
1279
|
+
colorObstaclesByType?: boolean;
|
|
1259
1280
|
}
|
|
1260
1281
|
declare function renderVacuumPng(map: VacuumMap, opts?: RenderVacuumPngOptions): Buffer;
|
|
1261
1282
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1229,14 +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
|
-
/**
|
|
1239
|
-
|
|
1246
|
+
/**
|
|
1247
|
+
* A named map palette. The first four mirror the HA integration's `color_scheme`
|
|
1248
|
+
* choices; `flat` / `dark-neon` / `materico` are node-dreame additions for
|
|
1249
|
+
* distinct visual styles.
|
|
1250
|
+
*/
|
|
1251
|
+
type MapColorScheme = 'dreame-light' | 'dreame-dark' | 'mijia-light' | 'tasshack' | 'flat' | 'dark-neon' | 'materico';
|
|
1240
1252
|
interface RenderVacuumPngOptions {
|
|
1241
1253
|
/** Integer upscale factor (nearest-neighbour). Default 1. */
|
|
1242
1254
|
scale?: number;
|
|
@@ -1256,6 +1268,15 @@ interface RenderVacuumPngOptions {
|
|
|
1256
1268
|
showObstacles?: boolean;
|
|
1257
1269
|
/** Draw the segment id as a tiny label at each room centroid. Default false. */
|
|
1258
1270
|
showSegmentLabels?: boolean;
|
|
1271
|
+
/** Draw each room's NAME (falls back to `Room <id>`) at its centroid. Default
|
|
1272
|
+
* false. Mutually useful with `showSegmentLabels` (names take the centroid). */
|
|
1273
|
+
showSegmentNames?: boolean;
|
|
1274
|
+
/** Tint the cleaned-area overlay (`cleanedArea.cleaned`) when present. Default false. */
|
|
1275
|
+
showCleanedArea?: boolean;
|
|
1276
|
+
/** Outline low-clearance "sneak under furniture" zones. Default true. */
|
|
1277
|
+
showFurniture?: boolean;
|
|
1278
|
+
/** Colour AI obstacle markers by their `type` (else a single accent). Default true. */
|
|
1279
|
+
colorObstaclesByType?: boolean;
|
|
1259
1280
|
}
|
|
1260
1281
|
declare function renderVacuumPng(map: VacuumMap, opts?: RenderVacuumPngOptions): Buffer;
|
|
1261
1282
|
|
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.4";
|
|
4
4
|
|
|
5
5
|
// src/transport/errors.ts
|
|
6
6
|
var DreameError = class extends Error {
|
|
@@ -2898,6 +2898,66 @@ var SCHEMES = {
|
|
|
2898
2898
|
virtualWall: [195, 35, 35, 230],
|
|
2899
2899
|
obstacle: [225, 145, 25, 255],
|
|
2900
2900
|
label: [35, 42, 54, 255]
|
|
2901
|
+
},
|
|
2902
|
+
// Flat-design: soft neutral floor, bright low-saturation segment fills, thin
|
|
2903
|
+
// mid-grey walls, a single vivid accent for the path.
|
|
2904
|
+
flat: {
|
|
2905
|
+
floor: [236, 239, 241, 255],
|
|
2906
|
+
wall: [120, 132, 145, 255],
|
|
2907
|
+
carpet: [205, 185, 160, 200],
|
|
2908
|
+
segSat: 0.28,
|
|
2909
|
+
segVal: 0.99,
|
|
2910
|
+
path: [255, 138, 101, 240],
|
|
2911
|
+
robotBody: [38, 50, 56, 255],
|
|
2912
|
+
robotHeading: [255, 255, 255, 255],
|
|
2913
|
+
charger: [38, 166, 154, 255],
|
|
2914
|
+
noGoFill: [239, 83, 80, 60],
|
|
2915
|
+
noGoBorder: [229, 57, 53, 210],
|
|
2916
|
+
noMopFill: [66, 165, 245, 55],
|
|
2917
|
+
noMopBorder: [30, 136, 229, 200],
|
|
2918
|
+
virtualWall: [229, 57, 53, 225],
|
|
2919
|
+
obstacle: [255, 167, 38, 255],
|
|
2920
|
+
label: [55, 71, 79, 255]
|
|
2921
|
+
},
|
|
2922
|
+
// Dark-neon: near-black floor with high-saturation glowing segment fills, a
|
|
2923
|
+
// bright cyan path and magenta robot.
|
|
2924
|
+
"dark-neon": {
|
|
2925
|
+
floor: [16, 18, 27, 255],
|
|
2926
|
+
wall: [5, 6, 10, 255],
|
|
2927
|
+
carpet: [60, 50, 80, 200],
|
|
2928
|
+
segSat: 0.85,
|
|
2929
|
+
segVal: 0.95,
|
|
2930
|
+
path: [0, 230, 255, 240],
|
|
2931
|
+
robotBody: [255, 45, 170, 255],
|
|
2932
|
+
robotHeading: [255, 255, 255, 255],
|
|
2933
|
+
charger: [0, 255, 150, 255],
|
|
2934
|
+
noGoFill: [255, 40, 90, 80],
|
|
2935
|
+
noGoBorder: [255, 40, 90, 235],
|
|
2936
|
+
noMopFill: [40, 130, 255, 75],
|
|
2937
|
+
noMopBorder: [40, 160, 255, 225],
|
|
2938
|
+
virtualWall: [255, 40, 90, 235],
|
|
2939
|
+
obstacle: [255, 220, 0, 255],
|
|
2940
|
+
label: [230, 240, 255, 255]
|
|
2941
|
+
},
|
|
2942
|
+
// Materico (Material-design): light grey surface, Material-palette segment
|
|
2943
|
+
// fills, indigo/teal accents.
|
|
2944
|
+
materico: {
|
|
2945
|
+
floor: [245, 245, 245, 255],
|
|
2946
|
+
wall: [97, 97, 97, 255],
|
|
2947
|
+
carpet: [188, 170, 144, 205],
|
|
2948
|
+
segSat: 0.55,
|
|
2949
|
+
segVal: 0.93,
|
|
2950
|
+
path: [63, 81, 181, 235],
|
|
2951
|
+
robotBody: [48, 63, 159, 255],
|
|
2952
|
+
robotHeading: [255, 255, 255, 255],
|
|
2953
|
+
charger: [0, 150, 136, 255],
|
|
2954
|
+
noGoFill: [244, 67, 54, 65],
|
|
2955
|
+
noGoBorder: [211, 47, 47, 215],
|
|
2956
|
+
noMopFill: [33, 150, 243, 60],
|
|
2957
|
+
noMopBorder: [25, 118, 210, 205],
|
|
2958
|
+
virtualWall: [211, 47, 47, 225],
|
|
2959
|
+
obstacle: [255, 152, 0, 255],
|
|
2960
|
+
label: [33, 33, 33, 255]
|
|
2901
2961
|
}
|
|
2902
2962
|
};
|
|
2903
2963
|
function hsvToRgba(h, s, v) {
|
|
@@ -2940,10 +3000,13 @@ function renderVacuumPng(map, opts = {}) {
|
|
|
2940
3000
|
const h = Math.max(1, map.dimensions.height * scale);
|
|
2941
3001
|
const png = new PNG({ width: w, height: h });
|
|
2942
3002
|
png.data.fill(0);
|
|
3003
|
+
const dim = map.dimensions;
|
|
2943
3004
|
for (const layer of map.layers) {
|
|
2944
|
-
paintLayer(png, layer, scale, pal);
|
|
3005
|
+
paintLayer(png, layer, dim.height, scale, pal);
|
|
3006
|
+
}
|
|
3007
|
+
if (opts.showCleanedArea === true && map.cleanedArea !== null) {
|
|
3008
|
+
paintCleanedArea(png, map.cleanedArea, dim, scale, pal.path);
|
|
2945
3009
|
}
|
|
2946
|
-
const dim = map.dimensions;
|
|
2947
3010
|
if (opts.showNoGo !== false) {
|
|
2948
3011
|
for (const area of map.restrictedAreas) {
|
|
2949
3012
|
const fill = area.kind === "noMop" ? pal.noMopFill : pal.noGoFill;
|
|
@@ -2966,10 +3029,17 @@ function renderVacuumPng(map, opts = {}) {
|
|
|
2966
3029
|
drawPolyline(png, path.points, dim, scale, pal.path, Math.max(1, scale));
|
|
2967
3030
|
}
|
|
2968
3031
|
}
|
|
3032
|
+
if (opts.showFurniture !== false) {
|
|
3033
|
+
for (const zone of map.lowLyingAreas) {
|
|
3034
|
+
paintFurnitureZone(png, zone.points, dim, scale);
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
2969
3037
|
if (opts.showObstacles !== false) {
|
|
3038
|
+
const byType = opts.colorObstaclesByType !== false;
|
|
2970
3039
|
for (const ob of map.obstacles) {
|
|
2971
3040
|
const p = worldToPx(ob.x, ob.y, dim, scale);
|
|
2972
|
-
|
|
3041
|
+
const color = byType ? obstacleColor(ob.type) : pal.obstacle;
|
|
3042
|
+
drawDiamond(png, p.x, p.y, Math.max(2, scale * 2), color);
|
|
2973
3043
|
}
|
|
2974
3044
|
}
|
|
2975
3045
|
if (opts.showCharger !== false && map.dock !== null) {
|
|
@@ -2978,31 +3048,33 @@ function renderVacuumPng(map, opts = {}) {
|
|
|
2978
3048
|
if (opts.showRobot !== false && map.robot !== null) {
|
|
2979
3049
|
drawRobot(png, map.robot, dim, scale, pal);
|
|
2980
3050
|
}
|
|
2981
|
-
if (opts.showSegmentLabels === true) {
|
|
3051
|
+
if (opts.showSegmentNames === true || opts.showSegmentLabels === true) {
|
|
2982
3052
|
for (const seg of map.segments) {
|
|
2983
3053
|
const p = worldToPx(seg.centroid.x, seg.centroid.y, dim, scale);
|
|
2984
|
-
|
|
3054
|
+
const text = opts.showSegmentNames === true ? labelText(seg.name, seg.id) : String(seg.id);
|
|
3055
|
+
drawLabel(png, p.x, p.y, text, pal.label, Math.max(1, scale));
|
|
2985
3056
|
}
|
|
2986
3057
|
}
|
|
2987
3058
|
return PNG.sync.write(png);
|
|
2988
3059
|
}
|
|
2989
3060
|
function worldToPx(worldX, worldY, dim, scale) {
|
|
3061
|
+
const cellY = (worldY - dim.top) / dim.gridSize;
|
|
2990
3062
|
return {
|
|
2991
3063
|
x: Math.round((worldX - dim.left) / dim.gridSize * scale),
|
|
2992
|
-
y: Math.round((
|
|
3064
|
+
y: Math.round((dim.height - 1 - cellY) * scale)
|
|
2993
3065
|
};
|
|
2994
3066
|
}
|
|
2995
|
-
function paintLayer(png, layer, scale, pal) {
|
|
3067
|
+
function paintLayer(png, layer, dimHeight, scale, pal) {
|
|
2996
3068
|
const color = layer.type === "segment" ? segmentColor(layer.segmentId ?? 0, pal) : layer.type === "wall" ? pal.wall : layer.type === "carpet" ? pal.carpet : pal.floor;
|
|
2997
3069
|
for (const run of layer.runs) {
|
|
2998
|
-
paintRun(png, run, color, scale);
|
|
3070
|
+
paintRun(png, run, dimHeight, color, scale);
|
|
2999
3071
|
}
|
|
3000
3072
|
}
|
|
3001
|
-
function paintRun(png, run, color, scale) {
|
|
3073
|
+
function paintRun(png, run, dimHeight, color, scale) {
|
|
3002
3074
|
const [xPx, yPx, len] = run;
|
|
3075
|
+
const baseY = (dimHeight - 1 - yPx) * scale;
|
|
3003
3076
|
for (let i = 0; i < len; i += 1) {
|
|
3004
3077
|
const baseX = (xPx + i) * scale;
|
|
3005
|
-
const baseY = yPx * scale;
|
|
3006
3078
|
for (let dy = 0; dy < scale; dy += 1) {
|
|
3007
3079
|
for (let dx = 0; dx < scale; dx += 1) {
|
|
3008
3080
|
setPixel(png, baseX + dx, baseY + dy, color);
|
|
@@ -3108,7 +3180,7 @@ function drawRobot(png, robot, dim, scale, pal) {
|
|
|
3108
3180
|
drawDisk(png, p.x, p.y, r, pal.robotBody);
|
|
3109
3181
|
const rad = robot.angle * Math.PI / 180;
|
|
3110
3182
|
const hx = Math.round(p.x + Math.cos(rad) * r);
|
|
3111
|
-
const hy = Math.round(p.y
|
|
3183
|
+
const hy = Math.round(p.y - Math.sin(rad) * r);
|
|
3112
3184
|
drawLine(png, p.x, p.y, hx, hy, pal.robotHeading, Math.max(1, Math.trunc(scale / 2) + 1));
|
|
3113
3185
|
}
|
|
3114
3186
|
function drawCharger(png, dock, dim, scale, pal) {
|
|
@@ -3116,6 +3188,58 @@ function drawCharger(png, dock, dim, scale, pal) {
|
|
|
3116
3188
|
const r = Math.max(2, scale * 2);
|
|
3117
3189
|
fillRect(png, p.x - r, p.y - r, p.x + r, p.y + r, pal.charger);
|
|
3118
3190
|
}
|
|
3191
|
+
function drawDiamond(png, cx, cy, r, color) {
|
|
3192
|
+
for (let dy = -r; dy <= r; dy += 1) {
|
|
3193
|
+
const w = r - Math.abs(dy);
|
|
3194
|
+
for (let dx = -w; dx <= w; dx += 1) {
|
|
3195
|
+
setPixel(png, cx + dx, cy + dy, color);
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
function obstacleColor(type) {
|
|
3200
|
+
const hue = Math.abs(Math.trunc(type)) * 47 % 360;
|
|
3201
|
+
return hsvToRgba(hue, 0.85, 0.95);
|
|
3202
|
+
}
|
|
3203
|
+
var FURNITURE_LINE = [150, 110, 200, 235];
|
|
3204
|
+
function paintFurnitureZone(png, points, dim, scale) {
|
|
3205
|
+
if (points.length < 2) return;
|
|
3206
|
+
for (let i = 0; i < points.length; i += 1) {
|
|
3207
|
+
const a = points[i];
|
|
3208
|
+
const b = points[(i + 1) % points.length];
|
|
3209
|
+
if (a === void 0 || b === void 0) continue;
|
|
3210
|
+
const pa = worldToPx(a.x, a.y, dim, scale);
|
|
3211
|
+
const pb = worldToPx(b.x, b.y, dim, scale);
|
|
3212
|
+
drawLine(png, pa.x, pa.y, pb.x, pb.y, FURNITURE_LINE, Math.max(1, scale));
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
function paintCleanedArea(png, overlay, parentDim, scale, baseColor) {
|
|
3216
|
+
const tint = [baseColor[0], baseColor[1], baseColor[2], 55];
|
|
3217
|
+
const inner = overlay.dimensions;
|
|
3218
|
+
const block = Math.max(1, Math.round(inner.gridSize / parentDim.gridSize * scale));
|
|
3219
|
+
for (const [xPx, yPx, len] of overlay.cleaned) {
|
|
3220
|
+
for (let i = 0; i < len; i += 1) {
|
|
3221
|
+
const worldX = inner.left + (xPx + i) * inner.gridSize;
|
|
3222
|
+
const worldY = inner.top + yPx * inner.gridSize;
|
|
3223
|
+
const p = worldToPx(worldX, worldY, parentDim, scale);
|
|
3224
|
+
for (let dy = 0; dy < block; dy += 1) {
|
|
3225
|
+
for (let dx = 0; dx < block; dx += 1) {
|
|
3226
|
+
setPixel(png, p.x + dx, p.y + dy, tint);
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
function labelText(name, id) {
|
|
3233
|
+
const raw = (name ?? "").trim();
|
|
3234
|
+
const base = raw.length > 0 ? raw : `ROOM ${id}`;
|
|
3235
|
+
const upper = base.toUpperCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
3236
|
+
let out = "";
|
|
3237
|
+
for (const ch of upper) {
|
|
3238
|
+
if (GLYPHS[ch] !== void 0) out += ch;
|
|
3239
|
+
}
|
|
3240
|
+
const trimmed = out.trim().slice(0, 14);
|
|
3241
|
+
return trimmed.length > 0 ? trimmed : `ROOM ${id}`;
|
|
3242
|
+
}
|
|
3119
3243
|
var GLYPHS = {
|
|
3120
3244
|
"0": [7, 5, 5, 5, 7],
|
|
3121
3245
|
"1": [2, 6, 2, 2, 7],
|
|
@@ -3127,7 +3251,34 @@ var GLYPHS = {
|
|
|
3127
3251
|
"7": [7, 1, 2, 2, 2],
|
|
3128
3252
|
"8": [7, 5, 7, 5, 7],
|
|
3129
3253
|
"9": [7, 5, 7, 1, 7],
|
|
3130
|
-
"-": [0, 0, 7, 0, 0]
|
|
3254
|
+
"-": [0, 0, 7, 0, 0],
|
|
3255
|
+
" ": [0, 0, 0, 0, 0],
|
|
3256
|
+
A: [2, 5, 7, 5, 5],
|
|
3257
|
+
B: [6, 5, 6, 5, 6],
|
|
3258
|
+
C: [3, 4, 4, 4, 3],
|
|
3259
|
+
D: [6, 5, 5, 5, 6],
|
|
3260
|
+
E: [7, 4, 6, 4, 7],
|
|
3261
|
+
F: [7, 4, 6, 4, 4],
|
|
3262
|
+
G: [3, 4, 5, 5, 3],
|
|
3263
|
+
H: [5, 5, 7, 5, 5],
|
|
3264
|
+
I: [7, 2, 2, 2, 7],
|
|
3265
|
+
J: [1, 1, 1, 5, 2],
|
|
3266
|
+
K: [5, 5, 6, 5, 5],
|
|
3267
|
+
L: [4, 4, 4, 4, 7],
|
|
3268
|
+
M: [5, 7, 7, 5, 5],
|
|
3269
|
+
N: [5, 7, 7, 7, 5],
|
|
3270
|
+
O: [2, 5, 5, 5, 2],
|
|
3271
|
+
P: [6, 5, 6, 4, 4],
|
|
3272
|
+
Q: [2, 5, 5, 6, 3],
|
|
3273
|
+
R: [6, 5, 6, 5, 5],
|
|
3274
|
+
S: [3, 4, 2, 1, 6],
|
|
3275
|
+
T: [7, 2, 2, 2, 2],
|
|
3276
|
+
U: [5, 5, 5, 5, 7],
|
|
3277
|
+
V: [5, 5, 5, 5, 2],
|
|
3278
|
+
W: [5, 5, 7, 7, 5],
|
|
3279
|
+
X: [5, 5, 2, 5, 5],
|
|
3280
|
+
Y: [5, 5, 2, 2, 2],
|
|
3281
|
+
Z: [7, 1, 2, 4, 7]
|
|
3131
3282
|
};
|
|
3132
3283
|
function drawLabel(png, cx, cy, text, color, px) {
|
|
3133
3284
|
const glyphW = 3 * px;
|