embroidery-qc-image 1.0.26 → 1.0.27

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
@@ -1071,16 +1071,22 @@ displayIndex, showLabels, scaleFactor, imageRefs) => {
1071
1071
  effectiveTextFontSize = textFontSize * shrinkRatio;
1072
1072
  }
1073
1073
  }
1074
- // Vẽ phần value với font hiệu dụng, màu đỏ
1074
+ // Line height luôn theo Text label (textFontSize), không theo effectiveTextFontSize
1075
+ const valueLineHeight = textFontSize;
1076
+ const textBlockHeight = lines.length * valueLineHeight;
1077
+ // Text align center: căn giữa theo chiều dọc trong block
1078
+ const textCenterY = currentY + textBlockHeight / 2;
1079
+ // Vẽ phần value với font hiệu dụng, màu đỏ, align center
1080
+ ctx.textBaseline = "middle";
1075
1081
  ctx.font = `${effectiveTextFontSize}px ${LAYOUT.FONT_FAMILY}`;
1076
1082
  ctx.fillStyle = DEFAULT_ERROR_COLOR;
1077
- const valueLineHeight = effectiveTextFontSize; // giữ giống wrapText cũ (lineHeight = fontSize)
1078
- let localY = currentY;
1083
+ // Vẽ từ trên xuống: căn giữa mỗi dòng
1079
1084
  lines.forEach((line, idx) => {
1080
- ctx.fillText(line, valueStartX, localY);
1081
- localY += valueLineHeight;
1085
+ const lineY = textCenterY - (lines.length - 1) / 2 * valueLineHeight + idx * valueLineHeight;
1086
+ ctx.fillText(line, valueStartX, lineY);
1082
1087
  });
1083
- const textBlockHeight = lines.length * valueLineHeight;
1088
+ // Reset textBaseline về top cho các phần tiếp theo
1089
+ ctx.textBaseline = LAYOUT.TEXT_BASELINE;
1084
1090
  currentY += textBlockHeight;
1085
1091
  drawnHeight += textBlockHeight;
1086
1092
  // Draw additional labels (skip when text is empty)
@@ -1241,29 +1247,102 @@ const renderIconPosition = (ctx, position, x, y, maxWidth, scaleFactor, imageRef
1241
1247
  // Kiểm tra xem có icon_image không để tính height phù hợp
1242
1248
  const hasIconImage = position.icon_image && position.icon_image.trim().length > 0;
1243
1249
  const iconImageHeight = hasIconImage ? iconFontSize * 2 : iconFontSize;
1244
- // Text align bottom: đặt text ở dưới cùng của dòng
1245
- const textBottomY = cursorY + iconImageHeight;
1246
- // Đo width trước khi vẽ
1250
+ // Đo width của label
1247
1251
  ctx.font = `bold ${iconFontSize}px ${LAYOUT.FONT_FAMILY}`;
1248
1252
  const labelWidth = ctx.measureText(iconLabel).width;
1249
- ctx.font = `${iconFontSize}px ${LAYOUT.FONT_FAMILY}`;
1250
- const valueText = ` ${iconValue}`;
1251
- const valueWidth = ctx.measureText(valueText).width;
1252
- // Vẽ text với textBaseline = bottom
1253
- ctx.textBaseline = "bottom";
1253
+ // Chuẩn hóa xuống dòng cho icon_value (giống text value)
1254
+ const normalizeNewlines = (text) => text
1255
+ .replace(/\r\n/g, "\n")
1256
+ .replace(/\r/g, "\n")
1257
+ .replace(/\\n/g, "\n");
1258
+ const normalizedIconValue = normalizeNewlines(iconValue);
1259
+ const iconValueLines = normalizedIconValue.split("\n");
1260
+ // Tính kích thước icon_image nếu có để chừa khoảng trống
1261
+ let iconImageReservedWidth = 0;
1262
+ if (hasIconImage) {
1263
+ const iconUrl = getIconImageUrl(position);
1264
+ if (iconUrl) {
1265
+ const img = imageRefs.current.get(iconUrl);
1266
+ if (img?.complete && img.naturalHeight > 0) {
1267
+ const ratio = img.naturalWidth / img.naturalHeight;
1268
+ const iconHeight = iconFontSize * 2;
1269
+ const iconWidth = Math.max(1, Math.floor(iconHeight * ratio));
1270
+ iconImageReservedWidth = iconWidth + LAYOUT.ELEMENT_SPACING * scaleFactor;
1271
+ }
1272
+ }
1273
+ }
1274
+ // Tính available width cho icon value (trừ đi khoảng trống cho icon_image)
1275
+ const availableWidth = Math.max(1, maxWidth - labelWidth - iconImageReservedWidth);
1276
+ // Tính font-size hiệu dụng cho icon value
1277
+ // Giới hạn thu nhỏ tối đa 50% (tối thiểu = iconFontSize * 0.5)
1278
+ const MIN_ICON_VALUE_FONT_SIZE = iconFontSize * 0.5;
1279
+ const measureMaxLineWidth = (fontSize) => {
1280
+ ctx.font = `${fontSize}px ${LAYOUT.FONT_FAMILY}`;
1281
+ let maxLineWidth = 0;
1282
+ iconValueLines.forEach((line) => {
1283
+ const w = ctx.measureText(` ${line}`).width;
1284
+ if (w > maxLineWidth)
1285
+ maxLineWidth = w;
1286
+ });
1287
+ return maxLineWidth;
1288
+ };
1289
+ let effectiveIconValueFontSize = iconFontSize;
1290
+ const baseMaxWidth = measureMaxLineWidth(iconFontSize);
1291
+ let needsWrap = false;
1292
+ if (baseMaxWidth > availableWidth) {
1293
+ const shrinkRatio = availableWidth / baseMaxWidth;
1294
+ effectiveIconValueFontSize = Math.max(MIN_ICON_VALUE_FONT_SIZE, iconFontSize * shrinkRatio);
1295
+ // Kiểm tra xem sau khi thu nhỏ đến 50% có vẫn overflow không
1296
+ const minMaxWidth = measureMaxLineWidth(MIN_ICON_VALUE_FONT_SIZE);
1297
+ if (minMaxWidth > availableWidth) {
1298
+ // Vẫn overflow, cần dùng wrap text
1299
+ needsWrap = true;
1300
+ effectiveIconValueFontSize = MIN_ICON_VALUE_FONT_SIZE;
1301
+ }
1302
+ }
1303
+ // Tính line height và block height cho icon value
1304
+ const valueLineHeight = effectiveIconValueFontSize;
1305
+ let allWrappedLines = [];
1306
+ if (needsWrap) {
1307
+ // Dùng wrap text logic: wrap tất cả các dòng trước
1308
+ iconValueLines.forEach((line) => {
1309
+ const wrappedLines = buildWrappedLines(ctx, line, availableWidth);
1310
+ allWrappedLines.push(...wrappedLines);
1311
+ });
1312
+ allWrappedLines.length * valueLineHeight;
1313
+ }
1314
+ else {
1315
+ allWrappedLines = iconValueLines;
1316
+ iconValueLines.length * valueLineHeight;
1317
+ }
1318
+ // Text align center: căn giữa theo chiều dọc trong block
1319
+ const textCenterY = cursorY + iconImageHeight / 2;
1320
+ // Vẽ label với textBaseline = middle để align center với value
1321
+ ctx.textBaseline = "middle";
1254
1322
  ctx.font = `bold ${iconFontSize}px ${LAYOUT.FONT_FAMILY}`;
1255
1323
  ctx.fillStyle = LAYOUT.LABEL_COLOR;
1256
- ctx.fillText(iconLabel, x, textBottomY);
1257
- ctx.font = `${iconFontSize}px ${LAYOUT.FONT_FAMILY}`;
1324
+ ctx.fillText(iconLabel, x, textCenterY);
1325
+ const valueStartX = x + labelWidth;
1326
+ // Vẽ icon value với align center
1327
+ ctx.font = `${effectiveIconValueFontSize}px ${LAYOUT.FONT_FAMILY}`;
1258
1328
  ctx.fillStyle = DEFAULT_ERROR_COLOR;
1259
- ctx.fillText(valueText, x + labelWidth, textBottomY);
1329
+ let maxValueLineWidth = 0;
1330
+ // Vẽ từng dòng, căn giữa theo chiều dọc
1331
+ allWrappedLines.forEach((line, index) => {
1332
+ const lineY = textCenterY - (allWrappedLines.length - 1) / 2 * valueLineHeight + index * valueLineHeight;
1333
+ const lineText = needsWrap ? line : ` ${line}`;
1334
+ ctx.fillText(lineText, valueStartX, lineY);
1335
+ const w = ctx.measureText(lineText).width;
1336
+ if (w > maxValueLineWidth)
1337
+ maxValueLineWidth = w;
1338
+ });
1260
1339
  ctx.fillStyle = LAYOUT.LABEL_COLOR;
1261
1340
  // Reset textBaseline về top cho các phần tiếp theo
1262
1341
  ctx.textBaseline = LAYOUT.TEXT_BASELINE;
1263
1342
  const iconResult = {
1264
1343
  height: iconImageHeight + lineGap,
1265
1344
  // tổng width của cả label + value, dùng để canh icon image lệch sang phải
1266
- lastLineWidth: labelWidth + valueWidth};
1345
+ lastLineWidth: labelWidth + maxValueLineWidth};
1267
1346
  // Draw icon image
1268
1347
  const iconUrl = getIconImageUrl(position);
1269
1348
  if (iconUrl) {
@@ -1276,7 +1355,7 @@ const renderIconPosition = (ctx, position, x, y, maxWidth, scaleFactor, imageRef
1276
1355
  const iconX = x +
1277
1356
  Math.ceil(iconResult.lastLineWidth) +
1278
1357
  LAYOUT.ELEMENT_SPACING * scaleFactor;
1279
- const iconY = textBottomY - iconHeight; // Align bottom với text
1358
+ const iconY = textCenterY - iconHeight / 2; // Align center với text
1280
1359
  ctx.drawImage(img, iconX, iconY, swatchW, iconHeight);
1281
1360
  }
1282
1361
  }