embroidery-qc-image 1.0.29 → 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 +191 -104
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +191 -104
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1162,25 +1162,123 @@ displayIndex, showLabels, scaleFactor, imageRefs, mockupBounds = null) => {
|
|
|
1162
1162
|
currentY += result.height;
|
|
1163
1163
|
}
|
|
1164
1164
|
if (showLabels.font && position.font) {
|
|
1165
|
-
// Render "Font: " với font mặc định
|
|
1166
1165
|
const prefix = "Font: ";
|
|
1167
|
-
ctx.font = `${otherFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1168
|
-
const prefixWidth = ctx.measureText(prefix).width;
|
|
1169
|
-
let currentX = x + prefixWidth;
|
|
1170
|
-
ctx.fillText(prefix, x, currentY);
|
|
1171
|
-
// Render tên font với font từ config
|
|
1172
|
-
ctx.font = `${otherFontSize}px ${position.font}`;
|
|
1173
|
-
const fontNameWidth = ctx.measureText(position.font).width;
|
|
1174
|
-
ctx.fillText(position.font, currentX, currentY);
|
|
1175
|
-
currentX += fontNameWidth;
|
|
1176
|
-
// Render "(Mặc định)" hoặc "(Custom)" với font mặc định
|
|
1177
1166
|
const suffix = position.is_font_default === true ? " (Mặc định)" : " (Custom)";
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
ctx.fillText(suffix, currentX, currentY);
|
|
1181
|
-
// Tính toán height và di chuyển cursorY
|
|
1167
|
+
const fontName = position.font;
|
|
1168
|
+
const fullText = `${prefix}${fontName}${suffix}`;
|
|
1182
1169
|
const lineHeight = otherFontSize + lineGap;
|
|
1183
|
-
|
|
1170
|
+
const textTopY = currentY;
|
|
1171
|
+
const effectiveMaxWidth = mockupBounds
|
|
1172
|
+
? getEffectiveMaxWidth(x, textTopY, lineHeight, maxWidth, mockupBounds)
|
|
1173
|
+
: maxWidth;
|
|
1174
|
+
const MIN_FONT_FONT_SIZE = otherFontSize * 0.5;
|
|
1175
|
+
const measureFontTextWidth = (fontSize) => {
|
|
1176
|
+
ctx.font = `${fontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1177
|
+
const prefixWidth = ctx.measureText(prefix).width;
|
|
1178
|
+
ctx.font = `${fontSize}px ${position.font}`;
|
|
1179
|
+
const fontNameWidth = ctx.measureText(fontName).width;
|
|
1180
|
+
ctx.font = `${fontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1181
|
+
const suffixWidth = ctx.measureText(suffix).width;
|
|
1182
|
+
return prefixWidth + fontNameWidth + suffixWidth;
|
|
1183
|
+
};
|
|
1184
|
+
const checkFontSizeFits = (fontSize) => {
|
|
1185
|
+
const textTopY = currentY;
|
|
1186
|
+
const lineHeight = fontSize + lineGap;
|
|
1187
|
+
const effectiveMaxWidth = mockupBounds
|
|
1188
|
+
? getEffectiveMaxWidth(x, textTopY, lineHeight, maxWidth, mockupBounds)
|
|
1189
|
+
: maxWidth;
|
|
1190
|
+
const textWidth = measureFontTextWidth(fontSize);
|
|
1191
|
+
return textWidth <= effectiveMaxWidth;
|
|
1192
|
+
};
|
|
1193
|
+
let effectiveFontSize = otherFontSize;
|
|
1194
|
+
let needsWrap = false;
|
|
1195
|
+
// Bước 1: Thử giảm font size, kiểm tra xem có vừa chiều ngang không
|
|
1196
|
+
const baseMaxWidth = measureFontTextWidth(otherFontSize);
|
|
1197
|
+
if (baseMaxWidth > effectiveMaxWidth) {
|
|
1198
|
+
// Binary search để tìm font size lớn nhất mà vẫn vừa
|
|
1199
|
+
let left = MIN_FONT_FONT_SIZE;
|
|
1200
|
+
let right = otherFontSize;
|
|
1201
|
+
let bestFontSize = MIN_FONT_FONT_SIZE;
|
|
1202
|
+
while (right - left > 0.1) {
|
|
1203
|
+
const mid = (left + right) / 2;
|
|
1204
|
+
if (checkFontSizeFits(mid)) {
|
|
1205
|
+
bestFontSize = mid;
|
|
1206
|
+
left = mid;
|
|
1207
|
+
}
|
|
1208
|
+
else {
|
|
1209
|
+
right = mid;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
if (checkFontSizeFits(bestFontSize)) {
|
|
1213
|
+
// Bước 1 thành công: font size đã shrink vừa chiều ngang
|
|
1214
|
+
effectiveFontSize = Math.floor(bestFontSize);
|
|
1215
|
+
}
|
|
1216
|
+
else {
|
|
1217
|
+
// Bước 1 thất bại: đã shrink đến MIN nhưng vẫn không vừa, sang bước 2
|
|
1218
|
+
needsWrap = true;
|
|
1219
|
+
effectiveFontSize = otherFontSize;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
if (needsWrap) {
|
|
1223
|
+
// Bước 2: Xuống dòng với font size gốc
|
|
1224
|
+
ctx.font = `${effectiveFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1225
|
+
const wrappedLines = buildWrappedLines(ctx, fullText, effectiveMaxWidth, x, textTopY, lineHeight, mockupBounds);
|
|
1226
|
+
wrappedLines.forEach((line, index) => {
|
|
1227
|
+
const lineY = textTopY + index * lineHeight;
|
|
1228
|
+
const lineEffectiveMaxWidth = mockupBounds
|
|
1229
|
+
? getEffectiveMaxWidth(x, lineY, lineHeight, maxWidth, mockupBounds)
|
|
1230
|
+
: maxWidth;
|
|
1231
|
+
let lineToRender = line;
|
|
1232
|
+
const lineWidth = ctx.measureText(lineToRender).width;
|
|
1233
|
+
if (lineWidth > lineEffectiveMaxWidth) {
|
|
1234
|
+
while (ctx.measureText(lineToRender).width > lineEffectiveMaxWidth && lineToRender.length > 0) {
|
|
1235
|
+
lineToRender = lineToRender.slice(0, -1);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
ctx.fillText(lineToRender, x, lineY);
|
|
1239
|
+
});
|
|
1240
|
+
currentY += wrappedLines.length * lineHeight;
|
|
1241
|
+
}
|
|
1242
|
+
else {
|
|
1243
|
+
// Bước 1 thành công: Render với font size đã shrink (1 dòng)
|
|
1244
|
+
const shrunkTextTopY = currentY;
|
|
1245
|
+
const shrunkLineHeight = effectiveFontSize + lineGap;
|
|
1246
|
+
const shrunkEffectiveMaxWidth = mockupBounds
|
|
1247
|
+
? getEffectiveMaxWidth(x, shrunkTextTopY, shrunkLineHeight, maxWidth, mockupBounds)
|
|
1248
|
+
: maxWidth;
|
|
1249
|
+
ctx.font = `${effectiveFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1250
|
+
const prefixWidth = ctx.measureText(prefix).width;
|
|
1251
|
+
let currentX = x + prefixWidth;
|
|
1252
|
+
ctx.fillText(prefix, x, currentY);
|
|
1253
|
+
ctx.font = `${effectiveFontSize}px ${position.font}`;
|
|
1254
|
+
const fontNameWidth = ctx.measureText(fontName).width;
|
|
1255
|
+
const totalWidth = prefixWidth + fontNameWidth;
|
|
1256
|
+
if (totalWidth > shrunkEffectiveMaxWidth) {
|
|
1257
|
+
// Cần cắt font name
|
|
1258
|
+
let truncatedFontName = fontName;
|
|
1259
|
+
while (ctx.measureText(truncatedFontName).width > shrunkEffectiveMaxWidth - prefixWidth && truncatedFontName.length > 0) {
|
|
1260
|
+
truncatedFontName = truncatedFontName.slice(0, -1);
|
|
1261
|
+
}
|
|
1262
|
+
ctx.fillText(truncatedFontName, currentX, currentY);
|
|
1263
|
+
currentX += ctx.measureText(truncatedFontName).width;
|
|
1264
|
+
}
|
|
1265
|
+
else {
|
|
1266
|
+
ctx.fillText(fontName, currentX, currentY);
|
|
1267
|
+
currentX += fontNameWidth;
|
|
1268
|
+
}
|
|
1269
|
+
ctx.font = `${effectiveFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1270
|
+
const remainingWidth = shrunkEffectiveMaxWidth - (currentX - x);
|
|
1271
|
+
if (remainingWidth > 0) {
|
|
1272
|
+
let truncatedSuffix = suffix;
|
|
1273
|
+
while (ctx.measureText(truncatedSuffix).width > remainingWidth && truncatedSuffix.length > 0) {
|
|
1274
|
+
truncatedSuffix = truncatedSuffix.slice(0, -1);
|
|
1275
|
+
}
|
|
1276
|
+
if (truncatedSuffix.length > 0) {
|
|
1277
|
+
ctx.fillText(truncatedSuffix, currentX, currentY);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
currentY += shrunkLineHeight;
|
|
1281
|
+
}
|
|
1184
1282
|
}
|
|
1185
1283
|
if (showLabels.color) {
|
|
1186
1284
|
const colorValue = position.character_colors?.join(", ") || position.color;
|
|
@@ -1326,126 +1424,115 @@ const renderIconPosition = (ctx, position, x, y, maxWidth, scaleFactor, imageRef
|
|
|
1326
1424
|
}
|
|
1327
1425
|
}
|
|
1328
1426
|
}
|
|
1329
|
-
// Tính available width cho icon value (trừ đi khoảng trống cho icon_image)
|
|
1330
1427
|
const availableWidth = Math.max(1, maxWidth - labelWidth - iconImageReservedWidth);
|
|
1331
|
-
|
|
1332
|
-
|
|
1428
|
+
const textCenterY = cursorY + iconImageHeight / 2;
|
|
1429
|
+
const valueStartX = x + labelWidth;
|
|
1430
|
+
const textTopY = textCenterY - iconFontSize / 2;
|
|
1431
|
+
const lineHeight = iconFontSize;
|
|
1432
|
+
const effectiveMaxWidth = mockupBounds
|
|
1433
|
+
? getEffectiveMaxWidth(valueStartX, textTopY, lineHeight, availableWidth, mockupBounds)
|
|
1434
|
+
: availableWidth;
|
|
1333
1435
|
const MIN_ICON_VALUE_FONT_SIZE = iconFontSize * 0.5;
|
|
1334
1436
|
const measureIconValueWidth = (fontSize) => {
|
|
1335
1437
|
ctx.font = `${fontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1336
1438
|
return ctx.measureText(` ${iconValue}`).width;
|
|
1337
1439
|
};
|
|
1440
|
+
const checkFontSizeFits = (fontSize) => {
|
|
1441
|
+
const textTopY = textCenterY - fontSize / 2;
|
|
1442
|
+
const lineHeight = fontSize;
|
|
1443
|
+
const effectiveMaxWidth = mockupBounds
|
|
1444
|
+
? getEffectiveMaxWidth(valueStartX, textTopY, lineHeight, availableWidth, mockupBounds)
|
|
1445
|
+
: availableWidth;
|
|
1446
|
+
const textWidth = measureIconValueWidth(fontSize);
|
|
1447
|
+
return textWidth <= effectiveMaxWidth;
|
|
1448
|
+
};
|
|
1338
1449
|
let effectiveIconValueFontSize = iconFontSize;
|
|
1339
|
-
const baseMaxWidth = measureIconValueWidth(iconFontSize);
|
|
1340
1450
|
let needsWrap = false;
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
//
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1451
|
+
// Bước 1: Thử giảm font size, kiểm tra xem có vừa chiều ngang không
|
|
1452
|
+
const baseMaxWidth = measureIconValueWidth(iconFontSize);
|
|
1453
|
+
if (baseMaxWidth > effectiveMaxWidth) {
|
|
1454
|
+
// Binary search để tìm font size lớn nhất mà vẫn vừa
|
|
1455
|
+
let left = MIN_ICON_VALUE_FONT_SIZE;
|
|
1456
|
+
let right = iconFontSize;
|
|
1457
|
+
let bestFontSize = MIN_ICON_VALUE_FONT_SIZE;
|
|
1458
|
+
while (right - left > 0.1) {
|
|
1459
|
+
const mid = (left + right) / 2;
|
|
1460
|
+
if (checkFontSizeFits(mid)) {
|
|
1461
|
+
bestFontSize = mid;
|
|
1462
|
+
left = mid;
|
|
1463
|
+
}
|
|
1464
|
+
else {
|
|
1465
|
+
right = mid;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
if (checkFontSizeFits(bestFontSize)) {
|
|
1469
|
+
// Bước 1 thành công: font size đã shrink vừa chiều ngang
|
|
1470
|
+
effectiveIconValueFontSize = Math.floor(bestFontSize);
|
|
1471
|
+
}
|
|
1472
|
+
else {
|
|
1473
|
+
// Bước 1 thất bại: đã shrink đến MIN nhưng vẫn không vừa, sang bước 2
|
|
1348
1474
|
needsWrap = true;
|
|
1349
|
-
effectiveIconValueFontSize =
|
|
1475
|
+
effectiveIconValueFontSize = iconFontSize;
|
|
1350
1476
|
}
|
|
1351
1477
|
}
|
|
1352
|
-
//
|
|
1353
|
-
const valueLineHeight = effectiveIconValueFontSize;
|
|
1354
|
-
let allWrappedLines = [];
|
|
1355
|
-
// Text align center: căn giữa theo chiều dọc trong block
|
|
1356
|
-
const textCenterY = cursorY + iconImageHeight / 2;
|
|
1357
|
-
const valueStartX = x + labelWidth;
|
|
1358
|
-
if (needsWrap) {
|
|
1359
|
-
// Dùng wrap text logic
|
|
1360
|
-
const wrappedLines = buildWrappedLines(ctx, iconValue, availableWidth, valueStartX, textCenterY, valueLineHeight, mockupBounds);
|
|
1361
|
-
allWrappedLines = wrappedLines;
|
|
1362
|
-
wrappedLines.length * valueLineHeight;
|
|
1363
|
-
}
|
|
1364
|
-
else {
|
|
1365
|
-
// Không cần wrap, chỉ một dòng
|
|
1366
|
-
allWrappedLines = [iconValue];
|
|
1367
|
-
}
|
|
1368
|
-
// Vẽ label với textBaseline = middle để align center với value
|
|
1478
|
+
// Bước 2: Nếu bước 1 thất bại, xuống dòng với font size gốc
|
|
1369
1479
|
ctx.textBaseline = "middle";
|
|
1370
1480
|
ctx.font = `bold ${iconFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1371
1481
|
ctx.fillStyle = LAYOUT.LABEL_COLOR;
|
|
1372
1482
|
ctx.fillText(iconLabel, x, textCenterY);
|
|
1373
|
-
// Vẽ icon value với align center
|
|
1374
1483
|
ctx.font = `${effectiveIconValueFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1375
1484
|
ctx.fillStyle = DEFAULT_ERROR_COLOR;
|
|
1376
1485
|
let maxValueLineWidth = 0;
|
|
1377
|
-
|
|
1486
|
+
let totalTextHeight = iconImageHeight;
|
|
1378
1487
|
if (needsWrap) {
|
|
1379
|
-
//
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
if (w > maxValueLineWidth)
|
|
1395
|
-
maxValueLineWidth = w;
|
|
1396
|
-
}
|
|
1397
|
-
else {
|
|
1398
|
-
ctx.fillText(line, valueStartX, lineY);
|
|
1399
|
-
const w = ctx.measureText(line).width;
|
|
1400
|
-
if (w > maxValueLineWidth)
|
|
1401
|
-
maxValueLineWidth = w;
|
|
1488
|
+
// Bước 2: Xuống dòng với font size gốc
|
|
1489
|
+
const wrappedLines = buildWrappedLines(ctx, iconValue, effectiveMaxWidth, valueStartX, textTopY, lineHeight, mockupBounds);
|
|
1490
|
+
// Tính height dựa trên số dòng thực tế
|
|
1491
|
+
totalTextHeight = Math.max(iconImageHeight, wrappedLines.length * lineHeight);
|
|
1492
|
+
wrappedLines.forEach((line, index) => {
|
|
1493
|
+
const lineTopY = textTopY + index * lineHeight;
|
|
1494
|
+
const lineCenterY = lineTopY + lineHeight / 2;
|
|
1495
|
+
const lineEffectiveMaxWidth = mockupBounds
|
|
1496
|
+
? getEffectiveMaxWidth(valueStartX, lineTopY, lineHeight, availableWidth, mockupBounds)
|
|
1497
|
+
: availableWidth;
|
|
1498
|
+
let lineToRender = line;
|
|
1499
|
+
const lineWidth = ctx.measureText(lineToRender).width;
|
|
1500
|
+
if (lineWidth > lineEffectiveMaxWidth) {
|
|
1501
|
+
while (ctx.measureText(lineToRender).width > lineEffectiveMaxWidth && lineToRender.length > 0) {
|
|
1502
|
+
lineToRender = lineToRender.slice(0, -1);
|
|
1402
1503
|
}
|
|
1403
1504
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
maxValueLineWidth = w;
|
|
1409
|
-
}
|
|
1505
|
+
ctx.fillText(lineToRender, valueStartX, lineCenterY);
|
|
1506
|
+
const w = ctx.measureText(lineToRender).width;
|
|
1507
|
+
if (w > maxValueLineWidth)
|
|
1508
|
+
maxValueLineWidth = w;
|
|
1410
1509
|
});
|
|
1411
1510
|
}
|
|
1412
1511
|
else {
|
|
1413
|
-
//
|
|
1512
|
+
// Bước 1 thành công: Render với font size đã shrink (1 dòng)
|
|
1513
|
+
const shrunkTextTopY = textCenterY - effectiveIconValueFontSize / 2;
|
|
1514
|
+
const shrunkLineHeight = effectiveIconValueFontSize;
|
|
1515
|
+
const shrunkEffectiveMaxWidth = mockupBounds
|
|
1516
|
+
? getEffectiveMaxWidth(valueStartX, shrunkTextTopY, shrunkLineHeight, availableWidth, mockupBounds)
|
|
1517
|
+
: availableWidth;
|
|
1414
1518
|
const lineText = ` ${iconValue}`;
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
// Cắt text cho đến khi vừa với effectiveMaxWidth
|
|
1421
|
-
let truncatedLine = lineText;
|
|
1422
|
-
while (ctx.measureText(truncatedLine).width > effectiveMaxWidth && truncatedLine.length > 0) {
|
|
1423
|
-
truncatedLine = truncatedLine.slice(0, -1);
|
|
1424
|
-
}
|
|
1425
|
-
ctx.fillText(truncatedLine, valueStartX, textCenterY);
|
|
1426
|
-
const w = ctx.measureText(truncatedLine).width;
|
|
1427
|
-
if (w > maxValueLineWidth)
|
|
1428
|
-
maxValueLineWidth = w;
|
|
1429
|
-
}
|
|
1430
|
-
else {
|
|
1431
|
-
ctx.fillText(lineText, valueStartX, textCenterY);
|
|
1432
|
-
const w = ctx.measureText(lineText).width;
|
|
1433
|
-
if (w > maxValueLineWidth)
|
|
1434
|
-
maxValueLineWidth = w;
|
|
1519
|
+
let lineToRender = lineText;
|
|
1520
|
+
const lineWidth = ctx.measureText(lineToRender).width;
|
|
1521
|
+
if (lineWidth > shrunkEffectiveMaxWidth) {
|
|
1522
|
+
while (ctx.measureText(lineToRender).width > shrunkEffectiveMaxWidth && lineToRender.length > 0) {
|
|
1523
|
+
lineToRender = lineToRender.slice(0, -1);
|
|
1435
1524
|
}
|
|
1436
1525
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
maxValueLineWidth = w;
|
|
1442
|
-
}
|
|
1526
|
+
ctx.fillText(lineToRender, valueStartX, textCenterY);
|
|
1527
|
+
const w = ctx.measureText(lineToRender).width;
|
|
1528
|
+
if (w > maxValueLineWidth)
|
|
1529
|
+
maxValueLineWidth = w;
|
|
1443
1530
|
}
|
|
1444
1531
|
ctx.fillStyle = LAYOUT.LABEL_COLOR;
|
|
1445
1532
|
// Reset textBaseline về top cho các phần tiếp theo
|
|
1446
1533
|
ctx.textBaseline = LAYOUT.TEXT_BASELINE;
|
|
1447
1534
|
const iconResult = {
|
|
1448
|
-
height:
|
|
1535
|
+
height: totalTextHeight + lineGap,
|
|
1449
1536
|
// tổng width của cả label + value, dùng để canh icon image lệch sang phải
|
|
1450
1537
|
lastLineWidth: labelWidth + maxValueLineWidth};
|
|
1451
1538
|
// Kiểm tra xem phần icon image có vượt quá canvas không trước khi render
|