embroidery-qc-image 1.0.28 → 1.0.30
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/components/EmbroideryQCImage.d.ts.map +1 -1
- package/dist/index.esm.js +203 -123
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +203 -123
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmbroideryQCImage.d.ts","sourceRoot":"","sources":["../../src/components/EmbroideryQCImage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAY,MAAM,UAAU,CAAC;AAChF,OAAO,yBAAyB,CAAC;AAiKjC,MAAM,WAAW,8BAA8B;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,WAAW,GAAG,YAAY,GAAG,YAAY,CAAC;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAofD,QAAA,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAmHvD,CAAC;
|
|
1
|
+
{"version":3,"file":"EmbroideryQCImage.d.ts","sourceRoot":"","sources":["../../src/components/EmbroideryQCImage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAY,MAAM,UAAU,CAAC;AAChF,OAAO,yBAAyB,CAAC;AAiKjC,MAAM,WAAW,8BAA8B;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,WAAW,GAAG,YAAY,GAAG,YAAY,CAAC;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAofD,QAAA,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAmHvD,CAAC;AA2kDF,eAAO,MAAM,6BAA6B,GACxC,QAAQ,kBAAkB,EAC1B,UAAS,8BAAmC,KAC3C,OAAO,CAAC,IAAI,GAAG,IAAI,CAuBrB,CAAC;AAEF,eAAO,MAAM,6BAA6B,GACxC,QAAQ,kBAAkB,EAC1B,UAAS,8BAAmC,KAC3C,OAAO,CAAC,MAAM,GAAG,IAAI,CAuBvB,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
|
package/dist/index.esm.js
CHANGED
|
@@ -569,21 +569,21 @@ const renderEmbroideryCanvas = (canvas, config, canvasSize, imageRefs) => {
|
|
|
569
569
|
// Tìm scaleFactor tối ưu bằng binary search trong [0, 1]
|
|
570
570
|
const maxIterations = 12;
|
|
571
571
|
const epsilon = 0.001; // độ chính xác cho khoảng cách low-high
|
|
572
|
-
const contentHeight = canvas.height
|
|
572
|
+
const contentHeight = canvas.height;
|
|
573
573
|
let low = 0;
|
|
574
574
|
let high = 1;
|
|
575
575
|
for (let i = 0; i < maxIterations; i++) {
|
|
576
576
|
const testScale = (low + high) / 2;
|
|
577
577
|
// (Không cần clear measureCanvas vì chỉ dùng để đo chiều cao, nhưng làm sạch cho dễ debug)
|
|
578
578
|
measureCtx.clearRect(0, 0, measureCanvas.width, measureCanvas.height);
|
|
579
|
-
// Đo warning & message đúng theo renderWarning,
|
|
580
|
-
let testMeasureY =
|
|
579
|
+
// Đo warning & message đúng theo renderWarning, có bao gồm padding
|
|
580
|
+
let testMeasureY = LAYOUT.PADDING;
|
|
581
581
|
if (config.warning_message) {
|
|
582
|
-
const testWarningHeight = renderWarning(measureCtx, measureCanvas, config.warning_message, testScale);
|
|
582
|
+
const testWarningHeight = renderWarning(measureCtx, measureCanvas, config.warning_message, testScale, testMeasureY);
|
|
583
583
|
testMeasureY += testWarningHeight;
|
|
584
584
|
}
|
|
585
585
|
if (config.message) {
|
|
586
|
-
const testMessageHeight = renderWarning(measureCtx, measureCanvas, config.message, testScale,
|
|
586
|
+
const testMessageHeight = renderWarning(measureCtx, measureCanvas, config.message, testScale, testMeasureY, "", DEFAULT_ERROR_COLOR);
|
|
587
587
|
testMeasureY += testMessageHeight;
|
|
588
588
|
}
|
|
589
589
|
// Đo lại chiều cao của các sides với scaleFactor, tiếp nối sau warning/message
|
|
@@ -591,7 +591,7 @@ const renderEmbroideryCanvas = (canvas, config, canvasSize, imageRefs) => {
|
|
|
591
591
|
const sideHeight = renderSide(measureCtx, side, testMeasureY, canvas.width, canvas.height, testScale, imageRefs, mockupBounds);
|
|
592
592
|
testMeasureY += sideHeight + measureSpacing * testScale;
|
|
593
593
|
});
|
|
594
|
-
// Tổng chiều cao content
|
|
594
|
+
// Tổng chiều cao content
|
|
595
595
|
const totalHeight = testMeasureY;
|
|
596
596
|
if (totalHeight > contentHeight) {
|
|
597
597
|
// Content đang cao hơn vùng cho phép -> giảm scale
|
|
@@ -607,23 +607,17 @@ const renderEmbroideryCanvas = (canvas, config, canvasSize, imageRefs) => {
|
|
|
607
607
|
}
|
|
608
608
|
const scaleFactor = low;
|
|
609
609
|
drawMockup(ctx, canvas, imageRefs);
|
|
610
|
+
// Calculate currentY: padding top + actual warning & message height (no spacing)
|
|
611
|
+
let currentY = LAYOUT.PADDING;
|
|
610
612
|
// Render warning & message with scaleFactor and get actual heights
|
|
611
|
-
let actualWarningHeight = 0;
|
|
612
|
-
let actualMessageHeight = 0;
|
|
613
613
|
if (config.warning_message) {
|
|
614
|
-
actualWarningHeight = renderWarning(ctx, canvas, config.warning_message, scaleFactor);
|
|
614
|
+
const actualWarningHeight = renderWarning(ctx, canvas, config.warning_message, scaleFactor, currentY);
|
|
615
|
+
currentY += actualWarningHeight;
|
|
615
616
|
}
|
|
616
617
|
if (config.message) {
|
|
617
|
-
actualMessageHeight = renderWarning(ctx, canvas, config.message, scaleFactor,
|
|
618
|
+
const actualMessageHeight = renderWarning(ctx, canvas, config.message, scaleFactor, currentY, "", // message: không cần prefix "Note"
|
|
618
619
|
DEFAULT_ERROR_COLOR // message: hiển thị màu đỏ
|
|
619
620
|
);
|
|
620
|
-
}
|
|
621
|
-
// Calculate currentY: padding top + actual warning & message height (no spacing)
|
|
622
|
-
let currentY = LAYOUT.PADDING;
|
|
623
|
-
if (config.warning_message && actualWarningHeight > 0) {
|
|
624
|
-
currentY += actualWarningHeight;
|
|
625
|
-
}
|
|
626
|
-
if (config.message && actualMessageHeight > 0) {
|
|
627
621
|
currentY += actualMessageHeight;
|
|
628
622
|
}
|
|
629
623
|
config.sides.forEach((side) => {
|
|
@@ -696,9 +690,8 @@ const renderWarning = (ctx, canvas, message, scaleFactor = 1, offsetY = 0, prefi
|
|
|
696
690
|
lines = buildWrappedLines(ctx, sanitizedMessage, maxWidth);
|
|
697
691
|
longestLineWidth = Math.max(...lines.map((line) => ctx.measureText(line).width));
|
|
698
692
|
}
|
|
699
|
-
const startY = LAYOUT.PADDING * scaleFactor + offsetY;
|
|
700
693
|
lines.forEach((line, index) => {
|
|
701
|
-
const y =
|
|
694
|
+
const y = offsetY + index * lineHeight;
|
|
702
695
|
ctx.fillText(line, leftX, y);
|
|
703
696
|
});
|
|
704
697
|
ctx.restore();
|
|
@@ -1167,25 +1160,123 @@ displayIndex, showLabels, scaleFactor, imageRefs, mockupBounds = null) => {
|
|
|
1167
1160
|
currentY += result.height;
|
|
1168
1161
|
}
|
|
1169
1162
|
if (showLabels.font && position.font) {
|
|
1170
|
-
// Render "Font: " với font mặc định
|
|
1171
1163
|
const prefix = "Font: ";
|
|
1172
|
-
ctx.font = `${otherFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1173
|
-
const prefixWidth = ctx.measureText(prefix).width;
|
|
1174
|
-
let currentX = x + prefixWidth;
|
|
1175
|
-
ctx.fillText(prefix, x, currentY);
|
|
1176
|
-
// Render tên font với font từ config
|
|
1177
|
-
ctx.font = `${otherFontSize}px ${position.font}`;
|
|
1178
|
-
const fontNameWidth = ctx.measureText(position.font).width;
|
|
1179
|
-
ctx.fillText(position.font, currentX, currentY);
|
|
1180
|
-
currentX += fontNameWidth;
|
|
1181
|
-
// Render "(Mặc định)" hoặc "(Custom)" với font mặc định
|
|
1182
1164
|
const suffix = position.is_font_default === true ? " (Mặc định)" : " (Custom)";
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
ctx.fillText(suffix, currentX, currentY);
|
|
1186
|
-
// Tính toán height và di chuyển cursorY
|
|
1165
|
+
const fontName = position.font;
|
|
1166
|
+
const fullText = `${prefix}${fontName}${suffix}`;
|
|
1187
1167
|
const lineHeight = otherFontSize + lineGap;
|
|
1188
|
-
|
|
1168
|
+
const textTopY = currentY;
|
|
1169
|
+
const effectiveMaxWidth = mockupBounds
|
|
1170
|
+
? getEffectiveMaxWidth(x, textTopY, lineHeight, maxWidth, mockupBounds)
|
|
1171
|
+
: maxWidth;
|
|
1172
|
+
const MIN_FONT_FONT_SIZE = otherFontSize * 0.5;
|
|
1173
|
+
const measureFontTextWidth = (fontSize) => {
|
|
1174
|
+
ctx.font = `${fontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1175
|
+
const prefixWidth = ctx.measureText(prefix).width;
|
|
1176
|
+
ctx.font = `${fontSize}px ${position.font}`;
|
|
1177
|
+
const fontNameWidth = ctx.measureText(fontName).width;
|
|
1178
|
+
ctx.font = `${fontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1179
|
+
const suffixWidth = ctx.measureText(suffix).width;
|
|
1180
|
+
return prefixWidth + fontNameWidth + suffixWidth;
|
|
1181
|
+
};
|
|
1182
|
+
const checkFontSizeFits = (fontSize) => {
|
|
1183
|
+
const textTopY = currentY;
|
|
1184
|
+
const lineHeight = fontSize + lineGap;
|
|
1185
|
+
const effectiveMaxWidth = mockupBounds
|
|
1186
|
+
? getEffectiveMaxWidth(x, textTopY, lineHeight, maxWidth, mockupBounds)
|
|
1187
|
+
: maxWidth;
|
|
1188
|
+
const textWidth = measureFontTextWidth(fontSize);
|
|
1189
|
+
return textWidth <= effectiveMaxWidth;
|
|
1190
|
+
};
|
|
1191
|
+
let effectiveFontSize = otherFontSize;
|
|
1192
|
+
let needsWrap = false;
|
|
1193
|
+
// Bước 1: Thử giảm font size, kiểm tra xem có vừa chiều ngang không
|
|
1194
|
+
const baseMaxWidth = measureFontTextWidth(otherFontSize);
|
|
1195
|
+
if (baseMaxWidth > effectiveMaxWidth) {
|
|
1196
|
+
// Binary search để tìm font size lớn nhất mà vẫn vừa
|
|
1197
|
+
let left = MIN_FONT_FONT_SIZE;
|
|
1198
|
+
let right = otherFontSize;
|
|
1199
|
+
let bestFontSize = MIN_FONT_FONT_SIZE;
|
|
1200
|
+
while (right - left > 0.1) {
|
|
1201
|
+
const mid = (left + right) / 2;
|
|
1202
|
+
if (checkFontSizeFits(mid)) {
|
|
1203
|
+
bestFontSize = mid;
|
|
1204
|
+
left = mid;
|
|
1205
|
+
}
|
|
1206
|
+
else {
|
|
1207
|
+
right = mid;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
if (checkFontSizeFits(bestFontSize)) {
|
|
1211
|
+
// Bước 1 thành công: font size đã shrink vừa chiều ngang
|
|
1212
|
+
effectiveFontSize = Math.floor(bestFontSize);
|
|
1213
|
+
}
|
|
1214
|
+
else {
|
|
1215
|
+
// Bước 1 thất bại: đã shrink đến MIN nhưng vẫn không vừa, sang bước 2
|
|
1216
|
+
needsWrap = true;
|
|
1217
|
+
effectiveFontSize = otherFontSize;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
if (needsWrap) {
|
|
1221
|
+
// Bước 2: Xuống dòng với font size gốc
|
|
1222
|
+
ctx.font = `${effectiveFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1223
|
+
const wrappedLines = buildWrappedLines(ctx, fullText, effectiveMaxWidth, x, textTopY, lineHeight, mockupBounds);
|
|
1224
|
+
wrappedLines.forEach((line, index) => {
|
|
1225
|
+
const lineY = textTopY + index * lineHeight;
|
|
1226
|
+
const lineEffectiveMaxWidth = mockupBounds
|
|
1227
|
+
? getEffectiveMaxWidth(x, lineY, lineHeight, maxWidth, mockupBounds)
|
|
1228
|
+
: maxWidth;
|
|
1229
|
+
let lineToRender = line;
|
|
1230
|
+
const lineWidth = ctx.measureText(lineToRender).width;
|
|
1231
|
+
if (lineWidth > lineEffectiveMaxWidth) {
|
|
1232
|
+
while (ctx.measureText(lineToRender).width > lineEffectiveMaxWidth && lineToRender.length > 0) {
|
|
1233
|
+
lineToRender = lineToRender.slice(0, -1);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
ctx.fillText(lineToRender, x, lineY);
|
|
1237
|
+
});
|
|
1238
|
+
currentY += wrappedLines.length * lineHeight;
|
|
1239
|
+
}
|
|
1240
|
+
else {
|
|
1241
|
+
// Bước 1 thành công: Render với font size đã shrink (1 dòng)
|
|
1242
|
+
const shrunkTextTopY = currentY;
|
|
1243
|
+
const shrunkLineHeight = effectiveFontSize + lineGap;
|
|
1244
|
+
const shrunkEffectiveMaxWidth = mockupBounds
|
|
1245
|
+
? getEffectiveMaxWidth(x, shrunkTextTopY, shrunkLineHeight, maxWidth, mockupBounds)
|
|
1246
|
+
: maxWidth;
|
|
1247
|
+
ctx.font = `${effectiveFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1248
|
+
const prefixWidth = ctx.measureText(prefix).width;
|
|
1249
|
+
let currentX = x + prefixWidth;
|
|
1250
|
+
ctx.fillText(prefix, x, currentY);
|
|
1251
|
+
ctx.font = `${effectiveFontSize}px ${position.font}`;
|
|
1252
|
+
const fontNameWidth = ctx.measureText(fontName).width;
|
|
1253
|
+
const totalWidth = prefixWidth + fontNameWidth;
|
|
1254
|
+
if (totalWidth > shrunkEffectiveMaxWidth) {
|
|
1255
|
+
// Cần cắt font name
|
|
1256
|
+
let truncatedFontName = fontName;
|
|
1257
|
+
while (ctx.measureText(truncatedFontName).width > shrunkEffectiveMaxWidth - prefixWidth && truncatedFontName.length > 0) {
|
|
1258
|
+
truncatedFontName = truncatedFontName.slice(0, -1);
|
|
1259
|
+
}
|
|
1260
|
+
ctx.fillText(truncatedFontName, currentX, currentY);
|
|
1261
|
+
currentX += ctx.measureText(truncatedFontName).width;
|
|
1262
|
+
}
|
|
1263
|
+
else {
|
|
1264
|
+
ctx.fillText(fontName, currentX, currentY);
|
|
1265
|
+
currentX += fontNameWidth;
|
|
1266
|
+
}
|
|
1267
|
+
ctx.font = `${effectiveFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1268
|
+
const remainingWidth = shrunkEffectiveMaxWidth - (currentX - x);
|
|
1269
|
+
if (remainingWidth > 0) {
|
|
1270
|
+
let truncatedSuffix = suffix;
|
|
1271
|
+
while (ctx.measureText(truncatedSuffix).width > remainingWidth && truncatedSuffix.length > 0) {
|
|
1272
|
+
truncatedSuffix = truncatedSuffix.slice(0, -1);
|
|
1273
|
+
}
|
|
1274
|
+
if (truncatedSuffix.length > 0) {
|
|
1275
|
+
ctx.fillText(truncatedSuffix, currentX, currentY);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
currentY += shrunkLineHeight;
|
|
1279
|
+
}
|
|
1189
1280
|
}
|
|
1190
1281
|
if (showLabels.color) {
|
|
1191
1282
|
const colorValue = position.character_colors?.join(", ") || position.color;
|
|
@@ -1331,126 +1422,115 @@ const renderIconPosition = (ctx, position, x, y, maxWidth, scaleFactor, imageRef
|
|
|
1331
1422
|
}
|
|
1332
1423
|
}
|
|
1333
1424
|
}
|
|
1334
|
-
// Tính available width cho icon value (trừ đi khoảng trống cho icon_image)
|
|
1335
1425
|
const availableWidth = Math.max(1, maxWidth - labelWidth - iconImageReservedWidth);
|
|
1336
|
-
|
|
1337
|
-
|
|
1426
|
+
const textCenterY = cursorY + iconImageHeight / 2;
|
|
1427
|
+
const valueStartX = x + labelWidth;
|
|
1428
|
+
const textTopY = textCenterY - iconFontSize / 2;
|
|
1429
|
+
const lineHeight = iconFontSize;
|
|
1430
|
+
const effectiveMaxWidth = mockupBounds
|
|
1431
|
+
? getEffectiveMaxWidth(valueStartX, textTopY, lineHeight, availableWidth, mockupBounds)
|
|
1432
|
+
: availableWidth;
|
|
1338
1433
|
const MIN_ICON_VALUE_FONT_SIZE = iconFontSize * 0.5;
|
|
1339
1434
|
const measureIconValueWidth = (fontSize) => {
|
|
1340
1435
|
ctx.font = `${fontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1341
1436
|
return ctx.measureText(` ${iconValue}`).width;
|
|
1342
1437
|
};
|
|
1438
|
+
const checkFontSizeFits = (fontSize) => {
|
|
1439
|
+
const textTopY = textCenterY - fontSize / 2;
|
|
1440
|
+
const lineHeight = fontSize;
|
|
1441
|
+
const effectiveMaxWidth = mockupBounds
|
|
1442
|
+
? getEffectiveMaxWidth(valueStartX, textTopY, lineHeight, availableWidth, mockupBounds)
|
|
1443
|
+
: availableWidth;
|
|
1444
|
+
const textWidth = measureIconValueWidth(fontSize);
|
|
1445
|
+
return textWidth <= effectiveMaxWidth;
|
|
1446
|
+
};
|
|
1343
1447
|
let effectiveIconValueFontSize = iconFontSize;
|
|
1344
|
-
const baseMaxWidth = measureIconValueWidth(iconFontSize);
|
|
1345
1448
|
let needsWrap = false;
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
//
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1449
|
+
// Bước 1: Thử giảm font size, kiểm tra xem có vừa chiều ngang không
|
|
1450
|
+
const baseMaxWidth = measureIconValueWidth(iconFontSize);
|
|
1451
|
+
if (baseMaxWidth > effectiveMaxWidth) {
|
|
1452
|
+
// Binary search để tìm font size lớn nhất mà vẫn vừa
|
|
1453
|
+
let left = MIN_ICON_VALUE_FONT_SIZE;
|
|
1454
|
+
let right = iconFontSize;
|
|
1455
|
+
let bestFontSize = MIN_ICON_VALUE_FONT_SIZE;
|
|
1456
|
+
while (right - left > 0.1) {
|
|
1457
|
+
const mid = (left + right) / 2;
|
|
1458
|
+
if (checkFontSizeFits(mid)) {
|
|
1459
|
+
bestFontSize = mid;
|
|
1460
|
+
left = mid;
|
|
1461
|
+
}
|
|
1462
|
+
else {
|
|
1463
|
+
right = mid;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
if (checkFontSizeFits(bestFontSize)) {
|
|
1467
|
+
// Bước 1 thành công: font size đã shrink vừa chiều ngang
|
|
1468
|
+
effectiveIconValueFontSize = Math.floor(bestFontSize);
|
|
1469
|
+
}
|
|
1470
|
+
else {
|
|
1471
|
+
// Bước 1 thất bại: đã shrink đến MIN nhưng vẫn không vừa, sang bước 2
|
|
1353
1472
|
needsWrap = true;
|
|
1354
|
-
effectiveIconValueFontSize =
|
|
1473
|
+
effectiveIconValueFontSize = iconFontSize;
|
|
1355
1474
|
}
|
|
1356
1475
|
}
|
|
1357
|
-
//
|
|
1358
|
-
const valueLineHeight = effectiveIconValueFontSize;
|
|
1359
|
-
let allWrappedLines = [];
|
|
1360
|
-
// Text align center: căn giữa theo chiều dọc trong block
|
|
1361
|
-
const textCenterY = cursorY + iconImageHeight / 2;
|
|
1362
|
-
const valueStartX = x + labelWidth;
|
|
1363
|
-
if (needsWrap) {
|
|
1364
|
-
// Dùng wrap text logic
|
|
1365
|
-
const wrappedLines = buildWrappedLines(ctx, iconValue, availableWidth, valueStartX, textCenterY, valueLineHeight, mockupBounds);
|
|
1366
|
-
allWrappedLines = wrappedLines;
|
|
1367
|
-
wrappedLines.length * valueLineHeight;
|
|
1368
|
-
}
|
|
1369
|
-
else {
|
|
1370
|
-
// Không cần wrap, chỉ một dòng
|
|
1371
|
-
allWrappedLines = [iconValue];
|
|
1372
|
-
}
|
|
1373
|
-
// Vẽ label với textBaseline = middle để align center với value
|
|
1476
|
+
// Bước 2: Nếu bước 1 thất bại, xuống dòng với font size gốc
|
|
1374
1477
|
ctx.textBaseline = "middle";
|
|
1375
1478
|
ctx.font = `bold ${iconFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1376
1479
|
ctx.fillStyle = LAYOUT.LABEL_COLOR;
|
|
1377
1480
|
ctx.fillText(iconLabel, x, textCenterY);
|
|
1378
|
-
// Vẽ icon value với align center
|
|
1379
1481
|
ctx.font = `${effectiveIconValueFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1380
1482
|
ctx.fillStyle = DEFAULT_ERROR_COLOR;
|
|
1381
1483
|
let maxValueLineWidth = 0;
|
|
1382
|
-
|
|
1484
|
+
let totalTextHeight = iconImageHeight;
|
|
1383
1485
|
if (needsWrap) {
|
|
1384
|
-
//
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
if (w > maxValueLineWidth)
|
|
1400
|
-
maxValueLineWidth = w;
|
|
1401
|
-
}
|
|
1402
|
-
else {
|
|
1403
|
-
ctx.fillText(line, valueStartX, lineY);
|
|
1404
|
-
const w = ctx.measureText(line).width;
|
|
1405
|
-
if (w > maxValueLineWidth)
|
|
1406
|
-
maxValueLineWidth = w;
|
|
1486
|
+
// Bước 2: Xuống dòng với font size gốc
|
|
1487
|
+
const wrappedLines = buildWrappedLines(ctx, iconValue, effectiveMaxWidth, valueStartX, textTopY, lineHeight, mockupBounds);
|
|
1488
|
+
// Tính height dựa trên số dòng thực tế
|
|
1489
|
+
totalTextHeight = Math.max(iconImageHeight, wrappedLines.length * lineHeight);
|
|
1490
|
+
wrappedLines.forEach((line, index) => {
|
|
1491
|
+
const lineTopY = textTopY + index * lineHeight;
|
|
1492
|
+
const lineCenterY = lineTopY + lineHeight / 2;
|
|
1493
|
+
const lineEffectiveMaxWidth = mockupBounds
|
|
1494
|
+
? getEffectiveMaxWidth(valueStartX, lineTopY, lineHeight, availableWidth, mockupBounds)
|
|
1495
|
+
: availableWidth;
|
|
1496
|
+
let lineToRender = line;
|
|
1497
|
+
const lineWidth = ctx.measureText(lineToRender).width;
|
|
1498
|
+
if (lineWidth > lineEffectiveMaxWidth) {
|
|
1499
|
+
while (ctx.measureText(lineToRender).width > lineEffectiveMaxWidth && lineToRender.length > 0) {
|
|
1500
|
+
lineToRender = lineToRender.slice(0, -1);
|
|
1407
1501
|
}
|
|
1408
1502
|
}
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
maxValueLineWidth = w;
|
|
1414
|
-
}
|
|
1503
|
+
ctx.fillText(lineToRender, valueStartX, lineCenterY);
|
|
1504
|
+
const w = ctx.measureText(lineToRender).width;
|
|
1505
|
+
if (w > maxValueLineWidth)
|
|
1506
|
+
maxValueLineWidth = w;
|
|
1415
1507
|
});
|
|
1416
1508
|
}
|
|
1417
1509
|
else {
|
|
1418
|
-
//
|
|
1510
|
+
// Bước 1 thành công: Render với font size đã shrink (1 dòng)
|
|
1511
|
+
const shrunkTextTopY = textCenterY - effectiveIconValueFontSize / 2;
|
|
1512
|
+
const shrunkLineHeight = effectiveIconValueFontSize;
|
|
1513
|
+
const shrunkEffectiveMaxWidth = mockupBounds
|
|
1514
|
+
? getEffectiveMaxWidth(valueStartX, shrunkTextTopY, shrunkLineHeight, availableWidth, mockupBounds)
|
|
1515
|
+
: availableWidth;
|
|
1419
1516
|
const lineText = ` ${iconValue}`;
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
// Cắt text cho đến khi vừa với effectiveMaxWidth
|
|
1426
|
-
let truncatedLine = lineText;
|
|
1427
|
-
while (ctx.measureText(truncatedLine).width > effectiveMaxWidth && truncatedLine.length > 0) {
|
|
1428
|
-
truncatedLine = truncatedLine.slice(0, -1);
|
|
1429
|
-
}
|
|
1430
|
-
ctx.fillText(truncatedLine, valueStartX, textCenterY);
|
|
1431
|
-
const w = ctx.measureText(truncatedLine).width;
|
|
1432
|
-
if (w > maxValueLineWidth)
|
|
1433
|
-
maxValueLineWidth = w;
|
|
1434
|
-
}
|
|
1435
|
-
else {
|
|
1436
|
-
ctx.fillText(lineText, valueStartX, textCenterY);
|
|
1437
|
-
const w = ctx.measureText(lineText).width;
|
|
1438
|
-
if (w > maxValueLineWidth)
|
|
1439
|
-
maxValueLineWidth = w;
|
|
1517
|
+
let lineToRender = lineText;
|
|
1518
|
+
const lineWidth = ctx.measureText(lineToRender).width;
|
|
1519
|
+
if (lineWidth > shrunkEffectiveMaxWidth) {
|
|
1520
|
+
while (ctx.measureText(lineToRender).width > shrunkEffectiveMaxWidth && lineToRender.length > 0) {
|
|
1521
|
+
lineToRender = lineToRender.slice(0, -1);
|
|
1440
1522
|
}
|
|
1441
1523
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
maxValueLineWidth = w;
|
|
1447
|
-
}
|
|
1524
|
+
ctx.fillText(lineToRender, valueStartX, textCenterY);
|
|
1525
|
+
const w = ctx.measureText(lineToRender).width;
|
|
1526
|
+
if (w > maxValueLineWidth)
|
|
1527
|
+
maxValueLineWidth = w;
|
|
1448
1528
|
}
|
|
1449
1529
|
ctx.fillStyle = LAYOUT.LABEL_COLOR;
|
|
1450
1530
|
// Reset textBaseline về top cho các phần tiếp theo
|
|
1451
1531
|
ctx.textBaseline = LAYOUT.TEXT_BASELINE;
|
|
1452
1532
|
const iconResult = {
|
|
1453
|
-
height:
|
|
1533
|
+
height: totalTextHeight + lineGap,
|
|
1454
1534
|
// tổng width của cả label + value, dùng để canh icon image lệch sang phải
|
|
1455
1535
|
lastLineWidth: labelWidth + maxValueLineWidth};
|
|
1456
1536
|
// Kiểm tra xem phần icon image có vượt quá canvas không trước khi render
|