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/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
- ctx.font = `${otherFontSize}px ${LAYOUT.FONT_FAMILY}`;
1179
- ctx.measureText(suffix).width;
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
- currentY += lineHeight;
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
- // Tính font-size hiệu dụng cho icon value
1332
- // Giới hạn thu nhỏ tối đa 50% (tối thiểu = iconFontSize * 0.5)
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
- if (baseMaxWidth > availableWidth) {
1342
- const shrinkRatio = availableWidth / baseMaxWidth;
1343
- effectiveIconValueFontSize = Math.max(MIN_ICON_VALUE_FONT_SIZE, iconFontSize * shrinkRatio);
1344
- // Kiểm tra xem sau khi thu nhỏ đến 50% vẫn overflow không
1345
- const minMaxWidth = measureIconValueWidth(MIN_ICON_VALUE_FONT_SIZE);
1346
- if (minMaxWidth > availableWidth) {
1347
- // Vẫn overflow, cần dùng wrap text
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 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 = MIN_ICON_VALUE_FONT_SIZE;
1475
+ effectiveIconValueFontSize = iconFontSize;
1350
1476
  }
1351
1477
  }
1352
- // Tính line height block height cho icon value
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
- // Vẽ icon value, căn giữa theo chiều dọc
1486
+ let totalTextHeight = iconImageHeight;
1378
1487
  if (needsWrap) {
1379
- // nhiều dòng sau khi wrap
1380
- allWrappedLines.forEach((line, index) => {
1381
- const lineY = textCenterY - (allWrappedLines.length - 1) / 2 * valueLineHeight + index * valueLineHeight;
1382
- // Kiểm tra overlap với mockup và điều chỉnh text nếu cần
1383
- if (mockupBounds) {
1384
- const effectiveMaxWidth = getEffectiveMaxWidth(valueStartX, lineY, valueLineHeight, availableWidth, mockupBounds);
1385
- const lineWidth = ctx.measureText(line).width;
1386
- if (lineWidth > effectiveMaxWidth) {
1387
- // Cắt text cho đến khi vừa với effectiveMaxWidth
1388
- let truncatedLine = line;
1389
- while (ctx.measureText(truncatedLine).width > effectiveMaxWidth && truncatedLine.length > 0) {
1390
- truncatedLine = truncatedLine.slice(0, -1);
1391
- }
1392
- ctx.fillText(truncatedLine, valueStartX, lineY);
1393
- const w = ctx.measureText(truncatedLine).width;
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
- else {
1405
- ctx.fillText(line, valueStartX, lineY);
1406
- const w = ctx.measureText(line).width;
1407
- if (w > maxValueLineWidth)
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
- // Chỉ một dòng, căn giữa
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
- // Kiểm tra overlap với mockup và điều chỉnh text nếu cần
1416
- if (mockupBounds) {
1417
- const effectiveMaxWidth = getEffectiveMaxWidth(valueStartX, textCenterY, valueLineHeight, availableWidth, mockupBounds);
1418
- const lineWidth = ctx.measureText(lineText).width;
1419
- if (lineWidth > effectiveMaxWidth) {
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
- else {
1438
- ctx.fillText(lineText, valueStartX, textCenterY);
1439
- const w = ctx.measureText(lineText).width;
1440
- if (w > maxValueLineWidth)
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: iconImageHeight + lineGap,
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