embroidery-qc-image 1.0.32 → 1.0.33
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 +390 -3
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +390 -3
- 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;
|
|
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;AAiiBD,QAAA,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CA2HvD,CAAC;AA8nFF,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
|
@@ -389,6 +389,14 @@ const preloadImages = async (config, imageRefs) => {
|
|
|
389
389
|
seen.add(characterColorUrl);
|
|
390
390
|
}
|
|
391
391
|
});
|
|
392
|
+
// Load layer_colors for TEXT (used in stroke_patches and template_custom_text_patches)
|
|
393
|
+
position.layer_colors?.forEach((color) => {
|
|
394
|
+
const layerColorUrl = getImageUrl("threadColor", color);
|
|
395
|
+
if (!seen.has(layerColorUrl)) {
|
|
396
|
+
entries.push({ url: layerColorUrl });
|
|
397
|
+
seen.add(layerColorUrl);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
392
400
|
}
|
|
393
401
|
});
|
|
394
402
|
});
|
|
@@ -582,6 +590,10 @@ const EmbroideryQCImage = ({ config, className = "", style = {}, }) => {
|
|
|
582
590
|
position.character_colors?.forEach((color) => {
|
|
583
591
|
loadImage(getImageUrl("threadColor", color), imageRefs, incrementCounter);
|
|
584
592
|
});
|
|
593
|
+
// Load layer_colors for TEXT (used in stroke_patches and template_custom_text_patches)
|
|
594
|
+
position.layer_colors?.forEach((color) => {
|
|
595
|
+
loadImage(getImageUrl("threadColor", color), imageRefs, incrementCounter);
|
|
596
|
+
});
|
|
585
597
|
}
|
|
586
598
|
});
|
|
587
599
|
});
|
|
@@ -624,10 +636,14 @@ const renderStrokePatchesCanvas = (ctx, canvas, config, imageRefs) => {
|
|
|
624
636
|
renderErrorState(ctx, canvas, "Position phải là TEXT");
|
|
625
637
|
return;
|
|
626
638
|
}
|
|
639
|
+
if (!position.layer_colors || position.layer_colors.length < 3) {
|
|
640
|
+
renderErrorState(ctx, canvas, "Không có đủ màu cho stroke patches");
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
627
643
|
// Get layer colors with empty check (don't use fallback)
|
|
628
|
-
const textColor = position.layer_colors
|
|
629
|
-
const borderColor = position.layer_colors
|
|
630
|
-
const backgroundColor = position.layer_colors
|
|
644
|
+
const textColor = position.layer_colors[0];
|
|
645
|
+
const borderColor = position.layer_colors[1];
|
|
646
|
+
const backgroundColor = position.layer_colors[2];
|
|
631
647
|
const fabricColor = position.layer_colors?.[3]; // Màu vải
|
|
632
648
|
// For rendering, use fallback colors (fabricColor không cần fallback vì chỉ hiển thị)
|
|
633
649
|
const textColorForRender = textColor;
|
|
@@ -919,6 +935,371 @@ const renderStrokePatchesCanvas = (ctx, canvas, config, imageRefs) => {
|
|
|
919
935
|
}
|
|
920
936
|
ctx.restore();
|
|
921
937
|
};
|
|
938
|
+
// Helper function để parse size từ string như "3 X 3 INCHES"
|
|
939
|
+
const parseSize = (sizeStr) => {
|
|
940
|
+
if (!sizeStr || typeof sizeStr !== "string")
|
|
941
|
+
return null;
|
|
942
|
+
// Pattern để match "3 X 3 INCHES" hoặc "3x3" hoặc "3 X 3"
|
|
943
|
+
const match = sizeStr.match(/(\d+(?:\.\d+)?)\s*[xX]\s*(\d+(?:\.\d+)?)/i);
|
|
944
|
+
if (!match)
|
|
945
|
+
return null;
|
|
946
|
+
const width = parseFloat(match[1]);
|
|
947
|
+
const height = parseFloat(match[2]);
|
|
948
|
+
if (isNaN(width) || isNaN(height) || width <= 0 || height <= 0)
|
|
949
|
+
return null;
|
|
950
|
+
return { width, height };
|
|
951
|
+
};
|
|
952
|
+
const renderTemplateCustomTextPatchesCanvas = (ctx, canvas, config, imageRefs) => {
|
|
953
|
+
// Clear canvas
|
|
954
|
+
ctx.fillStyle = "#e7e7e7";
|
|
955
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
956
|
+
const padding = LAYOUT.PADDING * 6;
|
|
957
|
+
const usableWidth = canvas.width - padding * 2;
|
|
958
|
+
const usableHeight = canvas.height - padding * 2;
|
|
959
|
+
// Calculate sections
|
|
960
|
+
const topSectionHeight = Math.floor(usableHeight * (2 / 3)); // 2/3 top
|
|
961
|
+
const bottomSectionHeight = usableHeight - topSectionHeight; // 1/3 bottom
|
|
962
|
+
const topSectionY = padding;
|
|
963
|
+
const bottomSectionY = topSectionY + topSectionHeight;
|
|
964
|
+
// Get first side (template_custom_text_patches should have only one side)
|
|
965
|
+
const side = config.sides[0];
|
|
966
|
+
if (!side || !side.positions.length) {
|
|
967
|
+
renderErrorState(ctx, canvas, "Không có dữ liệu positions");
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
const position = side.positions[0];
|
|
971
|
+
if (position.type !== "TEXT") {
|
|
972
|
+
renderErrorState(ctx, canvas, "Position phải là TEXT");
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
if (!position.layer_colors || position.layer_colors.length < 3) {
|
|
976
|
+
renderErrorState(ctx, canvas, "Không có đủ màu cho template custom text patches");
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
// Parse size từ side.size
|
|
980
|
+
const parsedSize = side.size ? parseSize(side.size) : null;
|
|
981
|
+
if (!parsedSize) {
|
|
982
|
+
renderErrorState(ctx, canvas, "Không thể parse size từ dữ liệu");
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
// Get layer colors
|
|
986
|
+
const textColor = position.layer_colors[0];
|
|
987
|
+
const borderColor = position.layer_colors[1];
|
|
988
|
+
const backgroundColor = position.layer_colors[2];
|
|
989
|
+
const fabricColor = position.layer_colors?.[3]; // Màu vải
|
|
990
|
+
// Check if font is missing (but continue rendering)
|
|
991
|
+
const isFontMissing = !position.font || position.font.trim() === "";
|
|
992
|
+
// ============================================================================
|
|
993
|
+
// TOP SECTION (2/3): Hiển thị mẫu preview với khung hình chữ nhật
|
|
994
|
+
// ============================================================================
|
|
995
|
+
ctx.save();
|
|
996
|
+
// Draw "Hình mẫu:" label at the top
|
|
997
|
+
const titleFontSize = LAYOUT.HEADER_FONT_SIZE * 0.8;
|
|
998
|
+
ctx.font = `bold ${titleFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
999
|
+
ctx.fillStyle = "#CC0000"; // Red color
|
|
1000
|
+
ctx.fillText("Hình mẫu:", padding, topSectionY);
|
|
1001
|
+
// Adjust top section Y to account for title + extra spacing (40px)
|
|
1002
|
+
const extraSpacing = 40;
|
|
1003
|
+
const actualTopSectionY = topSectionY + titleFontSize + LAYOUT.LINE_GAP + extraSpacing;
|
|
1004
|
+
const actualTopSectionHeight = topSectionHeight - titleFontSize - LAYOUT.LINE_GAP - extraSpacing;
|
|
1005
|
+
// Tính kích thước khung hình chữ nhật dựa trên tỉ lệ size
|
|
1006
|
+
// Sử dụng tỉ lệ width/height từ parsedSize để giữ đúng aspect ratio
|
|
1007
|
+
const sizeAspectRatio = parsedSize.width / parsedSize.height;
|
|
1008
|
+
// Tính kích thước khung để fit trong top section
|
|
1009
|
+
let rectWidth = Math.min(usableWidth * 0.9, actualTopSectionHeight * sizeAspectRatio);
|
|
1010
|
+
let rectHeight = rectWidth / sizeAspectRatio;
|
|
1011
|
+
if (rectHeight > actualTopSectionHeight * 0.9) {
|
|
1012
|
+
rectHeight = actualTopSectionHeight * 0.9;
|
|
1013
|
+
rectWidth = rectHeight * sizeAspectRatio;
|
|
1014
|
+
}
|
|
1015
|
+
// Center the rectangle in top section
|
|
1016
|
+
const rectX = padding + usableWidth / 2 - rectWidth / 2;
|
|
1017
|
+
const rectY = actualTopSectionY + actualTopSectionHeight / 2 - rectHeight / 2;
|
|
1018
|
+
// Get color hex values
|
|
1019
|
+
const textColorHex = COLOR_MAP[textColor] || LAYOUT.LABEL_COLOR;
|
|
1020
|
+
const borderColorHex = COLOR_MAP[borderColor] || LAYOUT.LABEL_COLOR;
|
|
1021
|
+
const bgColorHex = COLOR_MAP[backgroundColor] || "#FFFFFF";
|
|
1022
|
+
// Border width gấp đôi: 4% của rectWidth, tối thiểu 40px
|
|
1023
|
+
const borderWidth = Math.max(40, rectWidth * 0.04);
|
|
1024
|
+
// Border radius để bo tròn góc
|
|
1025
|
+
const borderRadius = Math.min(rectWidth, rectHeight) * 0.08; // 8% của cạnh nhỏ hơn
|
|
1026
|
+
// Draw rectangle background với border radius
|
|
1027
|
+
ctx.fillStyle = bgColorHex;
|
|
1028
|
+
ctx.beginPath();
|
|
1029
|
+
ctx.roundRect(rectX, rectY, rectWidth, rectHeight, borderRadius);
|
|
1030
|
+
ctx.fill();
|
|
1031
|
+
// Draw rectangle border với border radius
|
|
1032
|
+
ctx.strokeStyle = borderColorHex;
|
|
1033
|
+
ctx.lineWidth = borderWidth;
|
|
1034
|
+
ctx.beginPath();
|
|
1035
|
+
ctx.roundRect(rectX, rectY, rectWidth, rectHeight, borderRadius);
|
|
1036
|
+
ctx.stroke();
|
|
1037
|
+
// Calculate text size to fit inside rectangle (với padding rất nhỏ)
|
|
1038
|
+
// Padding chỉ chừa một chút để tránh text chạm border
|
|
1039
|
+
const textPadding = Math.max(30, rectWidth * 0.03); // 3% padding, tối thiểu 30px
|
|
1040
|
+
const maxTextWidth = rectWidth - textPadding * 2 - borderWidth;
|
|
1041
|
+
const maxTextHeight = rectHeight - textPadding * 2 - borderWidth;
|
|
1042
|
+
const text = position.text || "";
|
|
1043
|
+
const textLines = text.split("\n");
|
|
1044
|
+
const fontToUse = isFontMissing ? LAYOUT.FONT_FAMILY : position.font;
|
|
1045
|
+
// Tìm font size tối đa để text vừa trong khung
|
|
1046
|
+
// Bắt đầu với kích thước lớn và giảm dần
|
|
1047
|
+
let previewFontSize = Math.min(rectWidth, rectHeight) * 0.8; // Start với 80% của cạnh nhỏ hơn
|
|
1048
|
+
let bestFontSize = 30; // Minimum font size
|
|
1049
|
+
// Binary search để tìm font size tối đa
|
|
1050
|
+
let low = 30;
|
|
1051
|
+
let high = previewFontSize;
|
|
1052
|
+
while (high - low > 1) {
|
|
1053
|
+
const mid = (low + high) / 2;
|
|
1054
|
+
ctx.font = `${mid}px ${fontToUse}`;
|
|
1055
|
+
// Kiểm tra width của tất cả các dòng
|
|
1056
|
+
const maxLineWidth = Math.max(...textLines.map(line => ctx.measureText(line).width));
|
|
1057
|
+
// Kiểm tra height với line height = 1.1 (giảm từ 1.2 để text to hơn)
|
|
1058
|
+
const lineHeight = mid * 1.1;
|
|
1059
|
+
const totalTextHeight = textLines.length * lineHeight;
|
|
1060
|
+
if (maxLineWidth <= maxTextWidth && totalTextHeight <= maxTextHeight) {
|
|
1061
|
+
// Font size này vừa, thử tăng lên
|
|
1062
|
+
bestFontSize = mid;
|
|
1063
|
+
low = mid;
|
|
1064
|
+
}
|
|
1065
|
+
else {
|
|
1066
|
+
// Font size này quá lớn, giảm xuống
|
|
1067
|
+
high = mid;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
previewFontSize = bestFontSize;
|
|
1071
|
+
ctx.font = `${previewFontSize}px ${fontToUse}`;
|
|
1072
|
+
// Center the text inside rectangle
|
|
1073
|
+
const finalLineHeight = previewFontSize * 1.1;
|
|
1074
|
+
const totalTextHeightFinal = textLines.length * finalLineHeight;
|
|
1075
|
+
const textStartY = rectY + rectHeight / 2 - totalTextHeightFinal / 2;
|
|
1076
|
+
// Draw text inside rectangle
|
|
1077
|
+
ctx.fillStyle = textColorHex;
|
|
1078
|
+
ctx.textAlign = "center";
|
|
1079
|
+
ctx.textBaseline = "top";
|
|
1080
|
+
textLines.forEach((line, index) => {
|
|
1081
|
+
const lineY = textStartY + index * finalLineHeight;
|
|
1082
|
+
ctx.fillText(line, rectX + rectWidth / 2, lineY);
|
|
1083
|
+
});
|
|
1084
|
+
ctx.restore();
|
|
1085
|
+
// ============================================================================
|
|
1086
|
+
// BOTTOM SECTION (1/3): Flex layout with info (left) and image (right)
|
|
1087
|
+
// ============================================================================
|
|
1088
|
+
ctx.save();
|
|
1089
|
+
const bottomPadding = 0; // Không padding ngang, sát lề
|
|
1090
|
+
const bottomUsableWidth = usableWidth; // Sử dụng toàn bộ width
|
|
1091
|
+
const bottomUsableHeight = bottomSectionHeight - LAYOUT.PADDING * 2; // Chỉ padding dọc
|
|
1092
|
+
// Split bottom section: 60% left for info, 40% right for image
|
|
1093
|
+
const infoSectionWidth = Math.floor(bottomUsableWidth * 0.6);
|
|
1094
|
+
const imageSectionWidth = bottomUsableWidth - infoSectionWidth;
|
|
1095
|
+
const imageSectionX = padding + infoSectionWidth;
|
|
1096
|
+
// Left side: Info list
|
|
1097
|
+
const infoFontSize = LAYOUT.OTHER_FONT_SIZE * 0.9;
|
|
1098
|
+
const infoLineHeight = infoFontSize * 1.4;
|
|
1099
|
+
let infoY = bottomSectionY + LAYOUT.PADDING;
|
|
1100
|
+
ctx.font = `${infoFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1101
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR;
|
|
1102
|
+
ctx.textAlign = "left";
|
|
1103
|
+
ctx.textBaseline = "top";
|
|
1104
|
+
// Asterisk prefix style
|
|
1105
|
+
const drawAsterisk = (x, y) => {
|
|
1106
|
+
ctx.save();
|
|
1107
|
+
ctx.fillStyle = "#CC0000"; // Red asterisk
|
|
1108
|
+
ctx.font = `bold ${infoFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1109
|
+
ctx.fillText("*", x, y);
|
|
1110
|
+
ctx.restore();
|
|
1111
|
+
};
|
|
1112
|
+
const asteriskWidth = ctx.measureText("*").width + 5;
|
|
1113
|
+
const startX = padding + asteriskWidth;
|
|
1114
|
+
// Font - render "Font: " với font mặc định, tên font với font đó
|
|
1115
|
+
drawAsterisk(padding, infoY);
|
|
1116
|
+
const fontPrefix = "Font: ";
|
|
1117
|
+
ctx.font = `${infoFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1118
|
+
ctx.fillText(fontPrefix, startX, infoY);
|
|
1119
|
+
if (isFontMissing) {
|
|
1120
|
+
// Hiển thị warning màu đỏ nếu thiếu font
|
|
1121
|
+
ctx.fillStyle = "#CC0000"; // Red color
|
|
1122
|
+
ctx.fillText("(Đang thiếu font chữ)", startX + ctx.measureText(fontPrefix).width, infoY);
|
|
1123
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR; // Reset color
|
|
1124
|
+
}
|
|
1125
|
+
else {
|
|
1126
|
+
// Render font name với chính font đó
|
|
1127
|
+
const prefixWidth = ctx.measureText(fontPrefix).width;
|
|
1128
|
+
const fontName = position.font || LAYOUT.FONT_FAMILY;
|
|
1129
|
+
ctx.font = `${infoFontSize}px ${fontName}`;
|
|
1130
|
+
ctx.fillText(fontName, startX + prefixWidth, infoY);
|
|
1131
|
+
}
|
|
1132
|
+
infoY += infoLineHeight;
|
|
1133
|
+
// Reset font về mặc định cho các dòng tiếp theo
|
|
1134
|
+
ctx.font = `${infoFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1135
|
+
// Màu chữ (Text Color) - layer_colors[0]
|
|
1136
|
+
drawAsterisk(padding, infoY);
|
|
1137
|
+
const textColorPrefix = "Màu chữ: ";
|
|
1138
|
+
ctx.fillText(textColorPrefix, startX, infoY);
|
|
1139
|
+
if (!textColor || textColor.trim() === "") {
|
|
1140
|
+
// Hiển thị warning màu đỏ nếu thiếu màu
|
|
1141
|
+
const prefixWidth = ctx.measureText(textColorPrefix).width;
|
|
1142
|
+
ctx.fillStyle = "#CC0000";
|
|
1143
|
+
ctx.fillText("(Chưa có màu)", startX + prefixWidth, infoY);
|
|
1144
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR; // Reset color
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
// Hiển thị tên màu
|
|
1148
|
+
const prefixWidth = ctx.measureText(textColorPrefix).width;
|
|
1149
|
+
ctx.fillText(textColor, startX + prefixWidth, infoY);
|
|
1150
|
+
// Draw text color swatch
|
|
1151
|
+
const swatchSize = infoFontSize * 1.3;
|
|
1152
|
+
const swatchX = startX +
|
|
1153
|
+
ctx.measureText(textColorPrefix + textColor).width +
|
|
1154
|
+
LAYOUT.ELEMENT_SPACING * 0.3;
|
|
1155
|
+
const swatchY = infoY + infoFontSize / 2 - swatchSize / 2;
|
|
1156
|
+
const textColorSwatchUrl = getImageUrl("threadColor", textColor);
|
|
1157
|
+
const textColorSwatchImg = imageRefs.current.get(textColorSwatchUrl);
|
|
1158
|
+
if (textColorSwatchImg?.complete && textColorSwatchImg.naturalHeight > 0) {
|
|
1159
|
+
const ratio = textColorSwatchImg.naturalWidth / textColorSwatchImg.naturalHeight;
|
|
1160
|
+
const swatchW = Math.max(1, Math.floor(swatchSize * ratio));
|
|
1161
|
+
ctx.drawImage(textColorSwatchImg, swatchX, swatchY, swatchW, swatchSize);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
infoY += infoLineHeight;
|
|
1165
|
+
// Màu nền (Background Color) - layer_colors[2]
|
|
1166
|
+
drawAsterisk(padding, infoY);
|
|
1167
|
+
const bgColorPrefix = "Màu nền: ";
|
|
1168
|
+
ctx.fillText(bgColorPrefix, startX, infoY);
|
|
1169
|
+
if (!backgroundColor || backgroundColor.trim() === "") {
|
|
1170
|
+
// Hiển thị warning màu đỏ nếu thiếu màu
|
|
1171
|
+
const prefixWidth = ctx.measureText(bgColorPrefix).width;
|
|
1172
|
+
ctx.fillStyle = "#CC0000";
|
|
1173
|
+
ctx.fillText("(Chưa có màu)", startX + prefixWidth, infoY);
|
|
1174
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR; // Reset color
|
|
1175
|
+
}
|
|
1176
|
+
else {
|
|
1177
|
+
// Hiển thị tên màu
|
|
1178
|
+
const prefixWidth = ctx.measureText(bgColorPrefix).width;
|
|
1179
|
+
ctx.fillText(backgroundColor, startX + prefixWidth, infoY);
|
|
1180
|
+
// Draw background color swatch
|
|
1181
|
+
const swatchSize = infoFontSize * 1.3;
|
|
1182
|
+
const bgSwatchX = startX +
|
|
1183
|
+
ctx.measureText(bgColorPrefix + backgroundColor).width +
|
|
1184
|
+
LAYOUT.ELEMENT_SPACING * 0.3;
|
|
1185
|
+
const bgSwatchY = infoY + infoFontSize / 2 - swatchSize / 2;
|
|
1186
|
+
const bgColorSwatchUrl = getImageUrl("threadColor", backgroundColor);
|
|
1187
|
+
const bgColorSwatchImg = imageRefs.current.get(bgColorSwatchUrl);
|
|
1188
|
+
if (bgColorSwatchImg?.complete && bgColorSwatchImg.naturalHeight > 0) {
|
|
1189
|
+
const ratio = bgColorSwatchImg.naturalWidth / bgColorSwatchImg.naturalHeight;
|
|
1190
|
+
const swatchW = Math.max(1, Math.floor(swatchSize * ratio));
|
|
1191
|
+
ctx.drawImage(bgColorSwatchImg, bgSwatchX, bgSwatchY, swatchW, swatchSize);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
infoY += infoLineHeight;
|
|
1195
|
+
// Màu viền (Border Color) - layer_colors[1]
|
|
1196
|
+
drawAsterisk(padding, infoY);
|
|
1197
|
+
const borderColorPrefix = "Màu viền: ";
|
|
1198
|
+
ctx.fillText(borderColorPrefix, startX, infoY);
|
|
1199
|
+
if (!borderColor || borderColor.trim() === "") {
|
|
1200
|
+
// Hiển thị warning màu đỏ nếu thiếu màu
|
|
1201
|
+
const prefixWidth = ctx.measureText(borderColorPrefix).width;
|
|
1202
|
+
ctx.fillStyle = "#CC0000";
|
|
1203
|
+
ctx.fillText("(Chưa có màu)", startX + prefixWidth, infoY);
|
|
1204
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR; // Reset color
|
|
1205
|
+
}
|
|
1206
|
+
else {
|
|
1207
|
+
// Hiển thị tên màu
|
|
1208
|
+
const prefixWidth = ctx.measureText(borderColorPrefix).width;
|
|
1209
|
+
ctx.fillText(borderColor, startX + prefixWidth, infoY);
|
|
1210
|
+
// Draw border color swatch
|
|
1211
|
+
const swatchSize = infoFontSize * 1.3;
|
|
1212
|
+
const borderSwatchX = startX +
|
|
1213
|
+
ctx.measureText(borderColorPrefix + borderColor).width +
|
|
1214
|
+
LAYOUT.ELEMENT_SPACING * 0.3;
|
|
1215
|
+
const borderSwatchY = infoY + infoFontSize / 2 - swatchSize / 2;
|
|
1216
|
+
const borderColorSwatchUrl = getImageUrl("threadColor", borderColor);
|
|
1217
|
+
const borderColorSwatchImg = imageRefs.current.get(borderColorSwatchUrl);
|
|
1218
|
+
if (borderColorSwatchImg?.complete &&
|
|
1219
|
+
borderColorSwatchImg.naturalHeight > 0) {
|
|
1220
|
+
const ratio = borderColorSwatchImg.naturalWidth / borderColorSwatchImg.naturalHeight;
|
|
1221
|
+
const swatchW = Math.max(1, Math.floor(swatchSize * ratio));
|
|
1222
|
+
ctx.drawImage(borderColorSwatchImg, borderSwatchX, borderSwatchY, swatchW, swatchSize);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
infoY += infoLineHeight;
|
|
1226
|
+
// Màu vải (Fabric Color) - layer_colors[3]
|
|
1227
|
+
drawAsterisk(padding, infoY);
|
|
1228
|
+
const fabricColorPrefix = "Màu vải: ";
|
|
1229
|
+
ctx.fillText(fabricColorPrefix, startX, infoY);
|
|
1230
|
+
if (!fabricColor || fabricColor.trim() === "") {
|
|
1231
|
+
// Hiển thị warning màu đỏ nếu thiếu màu
|
|
1232
|
+
const prefixWidth = ctx.measureText(fabricColorPrefix).width;
|
|
1233
|
+
ctx.fillStyle = "#CC0000";
|
|
1234
|
+
ctx.fillText("(Chưa có màu)", startX + prefixWidth, infoY);
|
|
1235
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR; // Reset color
|
|
1236
|
+
}
|
|
1237
|
+
else {
|
|
1238
|
+
// Hiển thị tên màu
|
|
1239
|
+
const prefixWidth = ctx.measureText(fabricColorPrefix).width;
|
|
1240
|
+
ctx.fillText(fabricColor, startX + prefixWidth, infoY);
|
|
1241
|
+
// Draw fabric color swatch
|
|
1242
|
+
const swatchSize = infoFontSize * 1.3;
|
|
1243
|
+
const fabricSwatchX = startX +
|
|
1244
|
+
ctx.measureText(fabricColorPrefix + fabricColor).width +
|
|
1245
|
+
LAYOUT.ELEMENT_SPACING * 0.3;
|
|
1246
|
+
const fabricSwatchY = infoY + infoFontSize / 2 - swatchSize / 2;
|
|
1247
|
+
const fabricColorSwatchUrl = getImageUrl("threadColor", fabricColor);
|
|
1248
|
+
const fabricColorSwatchImg = imageRefs.current.get(fabricColorSwatchUrl);
|
|
1249
|
+
if (fabricColorSwatchImg?.complete &&
|
|
1250
|
+
fabricColorSwatchImg.naturalHeight > 0) {
|
|
1251
|
+
const ratio = fabricColorSwatchImg.naturalWidth / fabricColorSwatchImg.naturalHeight;
|
|
1252
|
+
const swatchW = Math.max(1, Math.floor(swatchSize * ratio));
|
|
1253
|
+
ctx.drawImage(fabricColorSwatchImg, fabricSwatchX, fabricSwatchY, swatchW, swatchSize);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
infoY += infoLineHeight;
|
|
1257
|
+
// Attachment
|
|
1258
|
+
if (position.attachment) {
|
|
1259
|
+
drawAsterisk(padding + bottomPadding, infoY);
|
|
1260
|
+
const attachmentLabel = `Attachment: ${position.attachment}`;
|
|
1261
|
+
ctx.fillText(attachmentLabel, startX, infoY);
|
|
1262
|
+
infoY += infoLineHeight;
|
|
1263
|
+
}
|
|
1264
|
+
// Size
|
|
1265
|
+
if (side.size) {
|
|
1266
|
+
drawAsterisk(padding + bottomPadding, infoY);
|
|
1267
|
+
const sizeLabel = `Size: ${side.size}`;
|
|
1268
|
+
ctx.fillText(sizeLabel, startX, infoY);
|
|
1269
|
+
infoY += infoLineHeight;
|
|
1270
|
+
}
|
|
1271
|
+
// Right side: Image from config.image_url
|
|
1272
|
+
if (config.image_url) {
|
|
1273
|
+
// Draw "Mockup" label
|
|
1274
|
+
ctx.font = `bold ${infoFontSize * 1.2}px ${LAYOUT.FONT_FAMILY}`;
|
|
1275
|
+
ctx.fillStyle = "#000000";
|
|
1276
|
+
const mockupLabel = "Mockup";
|
|
1277
|
+
const mockupLabelWidth = ctx.measureText(mockupLabel).width;
|
|
1278
|
+
const mockupLabelX = imageSectionX + (imageSectionWidth - mockupLabelWidth) / 1.2;
|
|
1279
|
+
ctx.fillText(mockupLabel, mockupLabelX, bottomSectionY + LAYOUT.PADDING);
|
|
1280
|
+
const mockupLabelHeight = infoFontSize * 1.2 + LAYOUT.LINE_GAP * 0.5;
|
|
1281
|
+
const img = imageRefs.current.get(config.image_url) ??
|
|
1282
|
+
imageRefs.current.get("mockup");
|
|
1283
|
+
if (img?.complete && img.naturalWidth > 0) {
|
|
1284
|
+
const maxImgWidth = imageSectionWidth; // Sử dụng toàn bộ width, sát lề phải
|
|
1285
|
+
const maxImgHeight = bottomUsableHeight - mockupLabelHeight;
|
|
1286
|
+
const imgAspectRatio = img.naturalWidth / img.naturalHeight;
|
|
1287
|
+
let drawWidth = maxImgWidth;
|
|
1288
|
+
let drawHeight = drawWidth / imgAspectRatio;
|
|
1289
|
+
if (drawHeight > maxImgHeight) {
|
|
1290
|
+
drawHeight = maxImgHeight;
|
|
1291
|
+
drawWidth = drawHeight * imgAspectRatio;
|
|
1292
|
+
}
|
|
1293
|
+
const imgX = imageSectionX + (imageSectionWidth - drawWidth) / 0.8;
|
|
1294
|
+
const imgY = bottomSectionY +
|
|
1295
|
+
LAYOUT.PADDING +
|
|
1296
|
+
mockupLabelHeight +
|
|
1297
|
+
(bottomUsableHeight - mockupLabelHeight - drawHeight) / 2;
|
|
1298
|
+
ctx.drawImage(img, imgX, imgY, drawWidth, drawHeight);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
ctx.restore();
|
|
1302
|
+
};
|
|
922
1303
|
const renderEmbroideryCanvas = (canvas, config, canvasSize, imageRefs) => {
|
|
923
1304
|
const ctx = canvas.getContext("2d");
|
|
924
1305
|
if (!ctx)
|
|
@@ -939,6 +1320,12 @@ const renderEmbroideryCanvas = (canvas, config, canvasSize, imageRefs) => {
|
|
|
939
1320
|
renderStrokePatchesCanvas(ctx, canvas, config, imageRefs);
|
|
940
1321
|
return;
|
|
941
1322
|
}
|
|
1323
|
+
// Check if this is a template_custom_text_patches layout
|
|
1324
|
+
const hasTemplateCustomTextPatches = config.sides.some((side) => side.item_type && side.item_type.includes("template_custom_text_patches"));
|
|
1325
|
+
if (hasTemplateCustomTextPatches) {
|
|
1326
|
+
renderTemplateCustomTextPatchesCanvas(ctx, canvas, config, imageRefs);
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
942
1329
|
ctx.textAlign = LAYOUT.TEXT_ALIGN;
|
|
943
1330
|
ctx.textBaseline = LAYOUT.TEXT_BASELINE;
|
|
944
1331
|
if (config.image_url) {
|