embroidery-qc-image 1.0.31 → 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 +532 -83
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +532 -83
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -391,6 +391,14 @@ const preloadImages = async (config, imageRefs) => {
|
|
|
391
391
|
seen.add(characterColorUrl);
|
|
392
392
|
}
|
|
393
393
|
});
|
|
394
|
+
// Load layer_colors for TEXT (used in stroke_patches and template_custom_text_patches)
|
|
395
|
+
position.layer_colors?.forEach((color) => {
|
|
396
|
+
const layerColorUrl = getImageUrl("threadColor", color);
|
|
397
|
+
if (!seen.has(layerColorUrl)) {
|
|
398
|
+
entries.push({ url: layerColorUrl });
|
|
399
|
+
seen.add(layerColorUrl);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
394
402
|
}
|
|
395
403
|
});
|
|
396
404
|
});
|
|
@@ -584,6 +592,10 @@ const EmbroideryQCImage = ({ config, className = "", style = {}, }) => {
|
|
|
584
592
|
position.character_colors?.forEach((color) => {
|
|
585
593
|
loadImage(getImageUrl("threadColor", color), imageRefs, incrementCounter);
|
|
586
594
|
});
|
|
595
|
+
// Load layer_colors for TEXT (used in stroke_patches and template_custom_text_patches)
|
|
596
|
+
position.layer_colors?.forEach((color) => {
|
|
597
|
+
loadImage(getImageUrl("threadColor", color), imageRefs, incrementCounter);
|
|
598
|
+
});
|
|
587
599
|
}
|
|
588
600
|
});
|
|
589
601
|
});
|
|
@@ -626,36 +638,21 @@ const renderStrokePatchesCanvas = (ctx, canvas, config, imageRefs) => {
|
|
|
626
638
|
renderErrorState(ctx, canvas, "Position phải là TEXT");
|
|
627
639
|
return;
|
|
628
640
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
renderErrorState(ctx, canvas, "Thiếu font");
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
// Validate layer_colors
|
|
635
|
-
if (!position.layer_colors) {
|
|
636
|
-
renderErrorState(ctx, canvas, "Thiếu layer_colors");
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
if (position.layer_colors.length !== 3) {
|
|
640
|
-
renderErrorState(ctx, canvas, `layer_colors phải có đúng 3 phần tử, hiện tại có ${position.layer_colors.length}`);
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
// Validate từng màu
|
|
644
|
-
const [textColor, borderColor, backgroundColor] = position.layer_colors;
|
|
645
|
-
const missingColors = [];
|
|
646
|
-
if (!textColor || textColor.trim() === "") {
|
|
647
|
-
missingColors.push("Màu text (vị trí 1)");
|
|
648
|
-
}
|
|
649
|
-
if (!borderColor || borderColor.trim() === "") {
|
|
650
|
-
missingColors.push("Màu border (vị trí 2)");
|
|
651
|
-
}
|
|
652
|
-
if (!backgroundColor || backgroundColor.trim() === "") {
|
|
653
|
-
missingColors.push("Màu background (vị trí 3)");
|
|
654
|
-
}
|
|
655
|
-
if (missingColors.length > 0) {
|
|
656
|
-
renderErrorState(ctx, canvas, `Thiếu màu trong layer_colors:\n${missingColors.join("\n")}`);
|
|
641
|
+
if (!position.layer_colors || position.layer_colors.length < 3) {
|
|
642
|
+
renderErrorState(ctx, canvas, "Không có đủ màu cho stroke patches");
|
|
657
643
|
return;
|
|
658
644
|
}
|
|
645
|
+
// Get layer colors with empty check (don't use fallback)
|
|
646
|
+
const textColor = position.layer_colors[0];
|
|
647
|
+
const borderColor = position.layer_colors[1];
|
|
648
|
+
const backgroundColor = position.layer_colors[2];
|
|
649
|
+
const fabricColor = position.layer_colors?.[3]; // Màu vải
|
|
650
|
+
// For rendering, use fallback colors (fabricColor không cần fallback vì chỉ hiển thị)
|
|
651
|
+
const textColorForRender = textColor;
|
|
652
|
+
const borderColorForRender = borderColor;
|
|
653
|
+
const backgroundColorForRender = backgroundColor;
|
|
654
|
+
// Check if font is missing (but continue rendering)
|
|
655
|
+
const isFontMissing = !position.font || position.font.trim() === "";
|
|
659
656
|
// ============================================================================
|
|
660
657
|
// TOP SECTION (2/3): Hiển thị mẫu preview
|
|
661
658
|
// ============================================================================
|
|
@@ -671,7 +668,8 @@ const renderStrokePatchesCanvas = (ctx, canvas, config, imageRefs) => {
|
|
|
671
668
|
const actualTopSectionHeight = topSectionHeight - titleFontSize - LAYOUT.LINE_GAP - extraSpacing;
|
|
672
669
|
// Calculate text size to fit top section
|
|
673
670
|
let previewFontSize = LAYOUT.HEADER_FONT_SIZE * 3; // Start with large size
|
|
674
|
-
|
|
671
|
+
const fontToUse = isFontMissing ? LAYOUT.FONT_FAMILY : position.font;
|
|
672
|
+
ctx.font = `${previewFontSize}px ${fontToUse}`;
|
|
675
673
|
const text = position.text || "";
|
|
676
674
|
const maxTextWidth = usableWidth * 0.9; // Use 75% of width for better padding
|
|
677
675
|
const maxTextHeight = actualTopSectionHeight; // Use 60% of height
|
|
@@ -679,23 +677,23 @@ const renderStrokePatchesCanvas = (ctx, canvas, config, imageRefs) => {
|
|
|
679
677
|
let textWidth = ctx.measureText(text).width;
|
|
680
678
|
while (textWidth > maxTextWidth && previewFontSize > 50) {
|
|
681
679
|
previewFontSize *= 0.95;
|
|
682
|
-
ctx.font = `${previewFontSize}px ${
|
|
680
|
+
ctx.font = `${previewFontSize}px ${fontToUse}`;
|
|
683
681
|
textWidth = ctx.measureText(text).width;
|
|
684
682
|
}
|
|
685
683
|
// Ensure text height also fits
|
|
686
684
|
while (previewFontSize > maxTextHeight && previewFontSize > 50) {
|
|
687
685
|
previewFontSize *= 0.95;
|
|
688
|
-
ctx.font = `${previewFontSize}px ${
|
|
686
|
+
ctx.font = `${previewFontSize}px ${fontToUse}`;
|
|
689
687
|
}
|
|
690
688
|
// Update textWidth after final scaling
|
|
691
689
|
textWidth = ctx.measureText(text).width;
|
|
692
690
|
// Center the text in top section
|
|
693
691
|
const textX = padding + usableWidth / 2 - textWidth / 2;
|
|
694
692
|
const textY = actualTopSectionY + actualTopSectionHeight / 2 - previewFontSize / 2;
|
|
695
|
-
// Get color hex values
|
|
696
|
-
const textColorHex = COLOR_MAP[
|
|
697
|
-
const borderColorHex = COLOR_MAP[
|
|
698
|
-
const bgColorHex = COLOR_MAP[
|
|
693
|
+
// Get color hex values (use render colors with fallback)
|
|
694
|
+
const textColorHex = COLOR_MAP[textColorForRender] || LAYOUT.LABEL_COLOR;
|
|
695
|
+
const borderColorHex = COLOR_MAP[borderColorForRender] || LAYOUT.LABEL_COLOR;
|
|
696
|
+
const bgColorHex = COLOR_MAP[backgroundColorForRender] || "#FFFFFF";
|
|
699
697
|
// Calculate stroke widths
|
|
700
698
|
// Background needs to be MUCH thicker to create spacing from text
|
|
701
699
|
// Border needs to be even thicker to wrap around background
|
|
@@ -755,62 +753,507 @@ const renderStrokePatchesCanvas = (ctx, canvas, config, imageRefs) => {
|
|
|
755
753
|
const fontPrefix = "Font: ";
|
|
756
754
|
ctx.font = `${infoFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
757
755
|
ctx.fillText(fontPrefix, startX, infoY);
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
756
|
+
if (isFontMissing) {
|
|
757
|
+
// Hiển thị warning màu đỏ nếu thiếu font
|
|
758
|
+
ctx.fillStyle = "#CC0000"; // Red color
|
|
759
|
+
ctx.fillText("(Đang thiếu font chữ)", startX + ctx.measureText(fontPrefix).width, infoY);
|
|
760
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR; // Reset color
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
// Render font name với chính font đó
|
|
764
|
+
const prefixWidth = ctx.measureText(fontPrefix).width;
|
|
765
|
+
const fontName = position.font || LAYOUT.FONT_FAMILY;
|
|
766
|
+
ctx.font = `${infoFontSize}px ${fontName}`;
|
|
767
|
+
ctx.fillText(fontName, startX + prefixWidth, infoY);
|
|
768
|
+
}
|
|
769
|
+
infoY += infoLineHeight;
|
|
770
|
+
// Reset font về mặc định cho các dòng tiếp theo
|
|
771
|
+
ctx.font = `${infoFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
772
|
+
// Màu chữ (Text Color) - layer_colors[0]
|
|
773
|
+
drawAsterisk(padding, infoY);
|
|
774
|
+
const textColorPrefix = "Màu chữ: ";
|
|
775
|
+
ctx.fillText(textColorPrefix, startX, infoY);
|
|
776
|
+
if (!textColor || textColor.trim() === "") {
|
|
777
|
+
// Hiển thị warning màu đỏ nếu thiếu màu
|
|
778
|
+
const prefixWidth = ctx.measureText(textColorPrefix).width;
|
|
779
|
+
ctx.fillStyle = "#CC0000";
|
|
780
|
+
ctx.fillText("(Chưa có màu)", startX + prefixWidth, infoY);
|
|
781
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR; // Reset color
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
// Hiển thị tên màu
|
|
785
|
+
const prefixWidth = ctx.measureText(textColorPrefix).width;
|
|
786
|
+
ctx.fillText(textColor, startX + prefixWidth, infoY);
|
|
787
|
+
// Draw text color swatch
|
|
788
|
+
const swatchSize = infoFontSize * 1.3;
|
|
789
|
+
const swatchX = startX +
|
|
790
|
+
ctx.measureText(textColorPrefix + textColor).width +
|
|
791
|
+
LAYOUT.ELEMENT_SPACING * 0.3;
|
|
792
|
+
const swatchY = infoY + infoFontSize / 2 - swatchSize / 2;
|
|
793
|
+
const textColorSwatchUrl = getImageUrl("threadColor", textColor);
|
|
794
|
+
const textColorSwatchImg = imageRefs.current.get(textColorSwatchUrl);
|
|
795
|
+
if (textColorSwatchImg?.complete && textColorSwatchImg.naturalHeight > 0) {
|
|
796
|
+
const ratio = textColorSwatchImg.naturalWidth / textColorSwatchImg.naturalHeight;
|
|
797
|
+
const swatchW = Math.max(1, Math.floor(swatchSize * ratio));
|
|
798
|
+
ctx.drawImage(textColorSwatchImg, swatchX, swatchY, swatchW, swatchSize);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
infoY += infoLineHeight;
|
|
802
|
+
// Màu nền (Background Color) - layer_colors[2]
|
|
803
|
+
drawAsterisk(padding, infoY);
|
|
804
|
+
const bgColorPrefix = "Màu nền: ";
|
|
805
|
+
ctx.fillText(bgColorPrefix, startX, infoY);
|
|
806
|
+
if (!backgroundColor || backgroundColor.trim() === "") {
|
|
807
|
+
// Hiển thị warning màu đỏ nếu thiếu màu
|
|
808
|
+
const prefixWidth = ctx.measureText(bgColorPrefix).width;
|
|
809
|
+
ctx.fillStyle = "#CC0000";
|
|
810
|
+
ctx.fillText("(Chưa có màu)", startX + prefixWidth, infoY);
|
|
811
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR; // Reset color
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
// Hiển thị tên màu
|
|
815
|
+
const prefixWidth = ctx.measureText(bgColorPrefix).width;
|
|
816
|
+
ctx.fillText(backgroundColor, startX + prefixWidth, infoY);
|
|
817
|
+
// Draw background color swatch
|
|
818
|
+
const swatchSize = infoFontSize * 1.3;
|
|
819
|
+
const bgSwatchX = startX +
|
|
820
|
+
ctx.measureText(bgColorPrefix + backgroundColor).width +
|
|
821
|
+
LAYOUT.ELEMENT_SPACING * 0.3;
|
|
822
|
+
const bgSwatchY = infoY + infoFontSize / 2 - swatchSize / 2;
|
|
823
|
+
const bgColorSwatchUrl = getImageUrl("threadColor", backgroundColor);
|
|
824
|
+
const bgColorSwatchImg = imageRefs.current.get(bgColorSwatchUrl);
|
|
825
|
+
if (bgColorSwatchImg?.complete && bgColorSwatchImg.naturalHeight > 0) {
|
|
826
|
+
const ratio = bgColorSwatchImg.naturalWidth / bgColorSwatchImg.naturalHeight;
|
|
827
|
+
const swatchW = Math.max(1, Math.floor(swatchSize * ratio));
|
|
828
|
+
ctx.drawImage(bgColorSwatchImg, bgSwatchX, bgSwatchY, swatchW, swatchSize);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
infoY += infoLineHeight;
|
|
832
|
+
// Màu viền (Border Color) - layer_colors[1]
|
|
833
|
+
drawAsterisk(padding, infoY);
|
|
834
|
+
const borderColorPrefix = "Màu viền: ";
|
|
835
|
+
ctx.fillText(borderColorPrefix, startX, infoY);
|
|
836
|
+
if (!borderColor || borderColor.trim() === "") {
|
|
837
|
+
// Hiển thị warning màu đỏ nếu thiếu màu
|
|
838
|
+
const prefixWidth = ctx.measureText(borderColorPrefix).width;
|
|
839
|
+
ctx.fillStyle = "#CC0000";
|
|
840
|
+
ctx.fillText("(Chưa có màu)", startX + prefixWidth, infoY);
|
|
841
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR; // Reset color
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
// Hiển thị tên màu
|
|
845
|
+
const prefixWidth = ctx.measureText(borderColorPrefix).width;
|
|
846
|
+
ctx.fillText(borderColor, startX + prefixWidth, infoY);
|
|
847
|
+
// Draw border color swatch
|
|
848
|
+
const swatchSize = infoFontSize * 1.3;
|
|
849
|
+
const borderSwatchX = startX +
|
|
850
|
+
ctx.measureText(borderColorPrefix + borderColor).width +
|
|
851
|
+
LAYOUT.ELEMENT_SPACING * 0.3;
|
|
852
|
+
const borderSwatchY = infoY + infoFontSize / 2 - swatchSize / 2;
|
|
853
|
+
const borderColorSwatchUrl = getImageUrl("threadColor", borderColor);
|
|
854
|
+
const borderColorSwatchImg = imageRefs.current.get(borderColorSwatchUrl);
|
|
855
|
+
if (borderColorSwatchImg?.complete &&
|
|
856
|
+
borderColorSwatchImg.naturalHeight > 0) {
|
|
857
|
+
const ratio = borderColorSwatchImg.naturalWidth / borderColorSwatchImg.naturalHeight;
|
|
858
|
+
const swatchW = Math.max(1, Math.floor(swatchSize * ratio));
|
|
859
|
+
ctx.drawImage(borderColorSwatchImg, borderSwatchX, borderSwatchY, swatchW, swatchSize);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
infoY += infoLineHeight;
|
|
863
|
+
// Màu vải (Fabric Color) - layer_colors[3]
|
|
864
|
+
drawAsterisk(padding, infoY);
|
|
865
|
+
const fabricColorPrefix = "Màu vải: ";
|
|
866
|
+
ctx.fillText(fabricColorPrefix, startX, infoY);
|
|
867
|
+
if (!fabricColor || fabricColor.trim() === "") {
|
|
868
|
+
// Hiển thị warning màu đỏ nếu thiếu màu
|
|
869
|
+
const prefixWidth = ctx.measureText(fabricColorPrefix).width;
|
|
870
|
+
ctx.fillStyle = "#CC0000";
|
|
871
|
+
ctx.fillText("(Chưa có màu)", startX + prefixWidth, infoY);
|
|
872
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR; // Reset color
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
// Hiển thị tên màu
|
|
876
|
+
const prefixWidth = ctx.measureText(fabricColorPrefix).width;
|
|
877
|
+
ctx.fillText(fabricColor, startX + prefixWidth, infoY);
|
|
878
|
+
// Draw fabric color swatch
|
|
879
|
+
const swatchSize = infoFontSize * 1.3;
|
|
880
|
+
const fabricSwatchX = startX +
|
|
881
|
+
ctx.measureText(fabricColorPrefix + fabricColor).width +
|
|
882
|
+
LAYOUT.ELEMENT_SPACING * 0.3;
|
|
883
|
+
const fabricSwatchY = infoY + infoFontSize / 2 - swatchSize / 2;
|
|
884
|
+
const fabricColorSwatchUrl = getImageUrl("threadColor", fabricColor);
|
|
885
|
+
const fabricColorSwatchImg = imageRefs.current.get(fabricColorSwatchUrl);
|
|
886
|
+
if (fabricColorSwatchImg?.complete &&
|
|
887
|
+
fabricColorSwatchImg.naturalHeight > 0) {
|
|
888
|
+
const ratio = fabricColorSwatchImg.naturalWidth / fabricColorSwatchImg.naturalHeight;
|
|
889
|
+
const swatchW = Math.max(1, Math.floor(swatchSize * ratio));
|
|
890
|
+
ctx.drawImage(fabricColorSwatchImg, fabricSwatchX, fabricSwatchY, swatchW, swatchSize);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
infoY += infoLineHeight;
|
|
894
|
+
// Attachment
|
|
895
|
+
if (position.attachment) {
|
|
896
|
+
drawAsterisk(padding + bottomPadding, infoY);
|
|
897
|
+
const attachmentLabel = `Attachment: ${position.attachment}`;
|
|
898
|
+
ctx.fillText(attachmentLabel, startX, infoY);
|
|
899
|
+
infoY += infoLineHeight;
|
|
900
|
+
}
|
|
901
|
+
// Size
|
|
902
|
+
if (side.size) {
|
|
903
|
+
drawAsterisk(padding + bottomPadding, infoY);
|
|
904
|
+
const sizeLabel = `Size: ${side.size}`;
|
|
905
|
+
ctx.fillText(sizeLabel, startX, infoY);
|
|
906
|
+
infoY += infoLineHeight;
|
|
907
|
+
}
|
|
908
|
+
// Right side: Image from config.image_url
|
|
909
|
+
if (config.image_url) {
|
|
910
|
+
// Draw "Mockup" label
|
|
911
|
+
ctx.font = `bold ${infoFontSize * 1.2}px ${LAYOUT.FONT_FAMILY}`;
|
|
912
|
+
ctx.fillStyle = "#000000";
|
|
913
|
+
const mockupLabel = "Mockup";
|
|
914
|
+
const mockupLabelWidth = ctx.measureText(mockupLabel).width;
|
|
915
|
+
const mockupLabelX = imageSectionX + (imageSectionWidth - mockupLabelWidth) / 1.2;
|
|
916
|
+
ctx.fillText(mockupLabel, mockupLabelX, bottomSectionY + LAYOUT.PADDING);
|
|
917
|
+
const mockupLabelHeight = infoFontSize * 1.2 + LAYOUT.LINE_GAP * 0.5;
|
|
918
|
+
const img = imageRefs.current.get(config.image_url) ??
|
|
919
|
+
imageRefs.current.get("mockup");
|
|
920
|
+
if (img?.complete && img.naturalWidth > 0) {
|
|
921
|
+
const maxImgWidth = imageSectionWidth; // Sử dụng toàn bộ width, sát lề phải
|
|
922
|
+
const maxImgHeight = bottomUsableHeight - mockupLabelHeight;
|
|
923
|
+
const imgAspectRatio = img.naturalWidth / img.naturalHeight;
|
|
924
|
+
let drawWidth = maxImgWidth;
|
|
925
|
+
let drawHeight = drawWidth / imgAspectRatio;
|
|
926
|
+
if (drawHeight > maxImgHeight) {
|
|
927
|
+
drawHeight = maxImgHeight;
|
|
928
|
+
drawWidth = drawHeight * imgAspectRatio;
|
|
929
|
+
}
|
|
930
|
+
const imgX = imageSectionX + (imageSectionWidth - drawWidth) / 0.8;
|
|
931
|
+
const imgY = bottomSectionY +
|
|
932
|
+
LAYOUT.PADDING +
|
|
933
|
+
mockupLabelHeight +
|
|
934
|
+
(bottomUsableHeight - mockupLabelHeight - drawHeight) / 2;
|
|
935
|
+
ctx.drawImage(img, imgX, imgY, drawWidth, drawHeight);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
ctx.restore();
|
|
939
|
+
};
|
|
940
|
+
// Helper function để parse size từ string như "3 X 3 INCHES"
|
|
941
|
+
const parseSize = (sizeStr) => {
|
|
942
|
+
if (!sizeStr || typeof sizeStr !== "string")
|
|
943
|
+
return null;
|
|
944
|
+
// Pattern để match "3 X 3 INCHES" hoặc "3x3" hoặc "3 X 3"
|
|
945
|
+
const match = sizeStr.match(/(\d+(?:\.\d+)?)\s*[xX]\s*(\d+(?:\.\d+)?)/i);
|
|
946
|
+
if (!match)
|
|
947
|
+
return null;
|
|
948
|
+
const width = parseFloat(match[1]);
|
|
949
|
+
const height = parseFloat(match[2]);
|
|
950
|
+
if (isNaN(width) || isNaN(height) || width <= 0 || height <= 0)
|
|
951
|
+
return null;
|
|
952
|
+
return { width, height };
|
|
953
|
+
};
|
|
954
|
+
const renderTemplateCustomTextPatchesCanvas = (ctx, canvas, config, imageRefs) => {
|
|
955
|
+
// Clear canvas
|
|
956
|
+
ctx.fillStyle = "#e7e7e7";
|
|
957
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
958
|
+
const padding = LAYOUT.PADDING * 6;
|
|
959
|
+
const usableWidth = canvas.width - padding * 2;
|
|
960
|
+
const usableHeight = canvas.height - padding * 2;
|
|
961
|
+
// Calculate sections
|
|
962
|
+
const topSectionHeight = Math.floor(usableHeight * (2 / 3)); // 2/3 top
|
|
963
|
+
const bottomSectionHeight = usableHeight - topSectionHeight; // 1/3 bottom
|
|
964
|
+
const topSectionY = padding;
|
|
965
|
+
const bottomSectionY = topSectionY + topSectionHeight;
|
|
966
|
+
// Get first side (template_custom_text_patches should have only one side)
|
|
967
|
+
const side = config.sides[0];
|
|
968
|
+
if (!side || !side.positions.length) {
|
|
969
|
+
renderErrorState(ctx, canvas, "Không có dữ liệu positions");
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
const position = side.positions[0];
|
|
973
|
+
if (position.type !== "TEXT") {
|
|
974
|
+
renderErrorState(ctx, canvas, "Position phải là TEXT");
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
if (!position.layer_colors || position.layer_colors.length < 3) {
|
|
978
|
+
renderErrorState(ctx, canvas, "Không có đủ màu cho template custom text patches");
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
// Parse size từ side.size
|
|
982
|
+
const parsedSize = side.size ? parseSize(side.size) : null;
|
|
983
|
+
if (!parsedSize) {
|
|
984
|
+
renderErrorState(ctx, canvas, "Không thể parse size từ dữ liệu");
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
// Get layer colors
|
|
988
|
+
const textColor = position.layer_colors[0];
|
|
989
|
+
const borderColor = position.layer_colors[1];
|
|
990
|
+
const backgroundColor = position.layer_colors[2];
|
|
991
|
+
const fabricColor = position.layer_colors?.[3]; // Màu vải
|
|
992
|
+
// Check if font is missing (but continue rendering)
|
|
993
|
+
const isFontMissing = !position.font || position.font.trim() === "";
|
|
994
|
+
// ============================================================================
|
|
995
|
+
// TOP SECTION (2/3): Hiển thị mẫu preview với khung hình chữ nhật
|
|
996
|
+
// ============================================================================
|
|
997
|
+
ctx.save();
|
|
998
|
+
// Draw "Hình mẫu:" label at the top
|
|
999
|
+
const titleFontSize = LAYOUT.HEADER_FONT_SIZE * 0.8;
|
|
1000
|
+
ctx.font = `bold ${titleFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1001
|
+
ctx.fillStyle = "#CC0000"; // Red color
|
|
1002
|
+
ctx.fillText("Hình mẫu:", padding, topSectionY);
|
|
1003
|
+
// Adjust top section Y to account for title + extra spacing (40px)
|
|
1004
|
+
const extraSpacing = 40;
|
|
1005
|
+
const actualTopSectionY = topSectionY + titleFontSize + LAYOUT.LINE_GAP + extraSpacing;
|
|
1006
|
+
const actualTopSectionHeight = topSectionHeight - titleFontSize - LAYOUT.LINE_GAP - extraSpacing;
|
|
1007
|
+
// Tính kích thước khung hình chữ nhật dựa trên tỉ lệ size
|
|
1008
|
+
// Sử dụng tỉ lệ width/height từ parsedSize để giữ đúng aspect ratio
|
|
1009
|
+
const sizeAspectRatio = parsedSize.width / parsedSize.height;
|
|
1010
|
+
// Tính kích thước khung để fit trong top section
|
|
1011
|
+
let rectWidth = Math.min(usableWidth * 0.9, actualTopSectionHeight * sizeAspectRatio);
|
|
1012
|
+
let rectHeight = rectWidth / sizeAspectRatio;
|
|
1013
|
+
if (rectHeight > actualTopSectionHeight * 0.9) {
|
|
1014
|
+
rectHeight = actualTopSectionHeight * 0.9;
|
|
1015
|
+
rectWidth = rectHeight * sizeAspectRatio;
|
|
1016
|
+
}
|
|
1017
|
+
// Center the rectangle in top section
|
|
1018
|
+
const rectX = padding + usableWidth / 2 - rectWidth / 2;
|
|
1019
|
+
const rectY = actualTopSectionY + actualTopSectionHeight / 2 - rectHeight / 2;
|
|
1020
|
+
// Get color hex values
|
|
1021
|
+
const textColorHex = COLOR_MAP[textColor] || LAYOUT.LABEL_COLOR;
|
|
1022
|
+
const borderColorHex = COLOR_MAP[borderColor] || LAYOUT.LABEL_COLOR;
|
|
1023
|
+
const bgColorHex = COLOR_MAP[backgroundColor] || "#FFFFFF";
|
|
1024
|
+
// Border width gấp đôi: 4% của rectWidth, tối thiểu 40px
|
|
1025
|
+
const borderWidth = Math.max(40, rectWidth * 0.04);
|
|
1026
|
+
// Border radius để bo tròn góc
|
|
1027
|
+
const borderRadius = Math.min(rectWidth, rectHeight) * 0.08; // 8% của cạnh nhỏ hơn
|
|
1028
|
+
// Draw rectangle background với border radius
|
|
1029
|
+
ctx.fillStyle = bgColorHex;
|
|
1030
|
+
ctx.beginPath();
|
|
1031
|
+
ctx.roundRect(rectX, rectY, rectWidth, rectHeight, borderRadius);
|
|
1032
|
+
ctx.fill();
|
|
1033
|
+
// Draw rectangle border với border radius
|
|
1034
|
+
ctx.strokeStyle = borderColorHex;
|
|
1035
|
+
ctx.lineWidth = borderWidth;
|
|
1036
|
+
ctx.beginPath();
|
|
1037
|
+
ctx.roundRect(rectX, rectY, rectWidth, rectHeight, borderRadius);
|
|
1038
|
+
ctx.stroke();
|
|
1039
|
+
// Calculate text size to fit inside rectangle (với padding rất nhỏ)
|
|
1040
|
+
// Padding chỉ chừa một chút để tránh text chạm border
|
|
1041
|
+
const textPadding = Math.max(30, rectWidth * 0.03); // 3% padding, tối thiểu 30px
|
|
1042
|
+
const maxTextWidth = rectWidth - textPadding * 2 - borderWidth;
|
|
1043
|
+
const maxTextHeight = rectHeight - textPadding * 2 - borderWidth;
|
|
1044
|
+
const text = position.text || "";
|
|
1045
|
+
const textLines = text.split("\n");
|
|
1046
|
+
const fontToUse = isFontMissing ? LAYOUT.FONT_FAMILY : position.font;
|
|
1047
|
+
// Tìm font size tối đa để text vừa trong khung
|
|
1048
|
+
// Bắt đầu với kích thước lớn và giảm dần
|
|
1049
|
+
let previewFontSize = Math.min(rectWidth, rectHeight) * 0.8; // Start với 80% của cạnh nhỏ hơn
|
|
1050
|
+
let bestFontSize = 30; // Minimum font size
|
|
1051
|
+
// Binary search để tìm font size tối đa
|
|
1052
|
+
let low = 30;
|
|
1053
|
+
let high = previewFontSize;
|
|
1054
|
+
while (high - low > 1) {
|
|
1055
|
+
const mid = (low + high) / 2;
|
|
1056
|
+
ctx.font = `${mid}px ${fontToUse}`;
|
|
1057
|
+
// Kiểm tra width của tất cả các dòng
|
|
1058
|
+
const maxLineWidth = Math.max(...textLines.map(line => ctx.measureText(line).width));
|
|
1059
|
+
// Kiểm tra height với line height = 1.1 (giảm từ 1.2 để text to hơn)
|
|
1060
|
+
const lineHeight = mid * 1.1;
|
|
1061
|
+
const totalTextHeight = textLines.length * lineHeight;
|
|
1062
|
+
if (maxLineWidth <= maxTextWidth && totalTextHeight <= maxTextHeight) {
|
|
1063
|
+
// Font size này vừa, thử tăng lên
|
|
1064
|
+
bestFontSize = mid;
|
|
1065
|
+
low = mid;
|
|
1066
|
+
}
|
|
1067
|
+
else {
|
|
1068
|
+
// Font size này quá lớn, giảm xuống
|
|
1069
|
+
high = mid;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
previewFontSize = bestFontSize;
|
|
1073
|
+
ctx.font = `${previewFontSize}px ${fontToUse}`;
|
|
1074
|
+
// Center the text inside rectangle
|
|
1075
|
+
const finalLineHeight = previewFontSize * 1.1;
|
|
1076
|
+
const totalTextHeightFinal = textLines.length * finalLineHeight;
|
|
1077
|
+
const textStartY = rectY + rectHeight / 2 - totalTextHeightFinal / 2;
|
|
1078
|
+
// Draw text inside rectangle
|
|
1079
|
+
ctx.fillStyle = textColorHex;
|
|
1080
|
+
ctx.textAlign = "center";
|
|
1081
|
+
ctx.textBaseline = "top";
|
|
1082
|
+
textLines.forEach((line, index) => {
|
|
1083
|
+
const lineY = textStartY + index * finalLineHeight;
|
|
1084
|
+
ctx.fillText(line, rectX + rectWidth / 2, lineY);
|
|
1085
|
+
});
|
|
1086
|
+
ctx.restore();
|
|
1087
|
+
// ============================================================================
|
|
1088
|
+
// BOTTOM SECTION (1/3): Flex layout with info (left) and image (right)
|
|
1089
|
+
// ============================================================================
|
|
1090
|
+
ctx.save();
|
|
1091
|
+
const bottomPadding = 0; // Không padding ngang, sát lề
|
|
1092
|
+
const bottomUsableWidth = usableWidth; // Sử dụng toàn bộ width
|
|
1093
|
+
const bottomUsableHeight = bottomSectionHeight - LAYOUT.PADDING * 2; // Chỉ padding dọc
|
|
1094
|
+
// Split bottom section: 60% left for info, 40% right for image
|
|
1095
|
+
const infoSectionWidth = Math.floor(bottomUsableWidth * 0.6);
|
|
1096
|
+
const imageSectionWidth = bottomUsableWidth - infoSectionWidth;
|
|
1097
|
+
const imageSectionX = padding + infoSectionWidth;
|
|
1098
|
+
// Left side: Info list
|
|
1099
|
+
const infoFontSize = LAYOUT.OTHER_FONT_SIZE * 0.9;
|
|
1100
|
+
const infoLineHeight = infoFontSize * 1.4;
|
|
1101
|
+
let infoY = bottomSectionY + LAYOUT.PADDING;
|
|
1102
|
+
ctx.font = `${infoFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1103
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR;
|
|
1104
|
+
ctx.textAlign = "left";
|
|
1105
|
+
ctx.textBaseline = "top";
|
|
1106
|
+
// Asterisk prefix style
|
|
1107
|
+
const drawAsterisk = (x, y) => {
|
|
1108
|
+
ctx.save();
|
|
1109
|
+
ctx.fillStyle = "#CC0000"; // Red asterisk
|
|
1110
|
+
ctx.font = `bold ${infoFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1111
|
+
ctx.fillText("*", x, y);
|
|
1112
|
+
ctx.restore();
|
|
1113
|
+
};
|
|
1114
|
+
const asteriskWidth = ctx.measureText("*").width + 5;
|
|
1115
|
+
const startX = padding + asteriskWidth;
|
|
1116
|
+
// Font - render "Font: " với font mặc định, tên font với font đó
|
|
1117
|
+
drawAsterisk(padding, infoY);
|
|
1118
|
+
const fontPrefix = "Font: ";
|
|
1119
|
+
ctx.font = `${infoFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
1120
|
+
ctx.fillText(fontPrefix, startX, infoY);
|
|
1121
|
+
if (isFontMissing) {
|
|
1122
|
+
// Hiển thị warning màu đỏ nếu thiếu font
|
|
1123
|
+
ctx.fillStyle = "#CC0000"; // Red color
|
|
1124
|
+
ctx.fillText("(Đang thiếu font chữ)", startX + ctx.measureText(fontPrefix).width, infoY);
|
|
1125
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR; // Reset color
|
|
1126
|
+
}
|
|
1127
|
+
else {
|
|
1128
|
+
// Render font name với chính font đó
|
|
1129
|
+
const prefixWidth = ctx.measureText(fontPrefix).width;
|
|
1130
|
+
const fontName = position.font || LAYOUT.FONT_FAMILY;
|
|
1131
|
+
ctx.font = `${infoFontSize}px ${fontName}`;
|
|
1132
|
+
ctx.fillText(fontName, startX + prefixWidth, infoY);
|
|
1133
|
+
}
|
|
762
1134
|
infoY += infoLineHeight;
|
|
763
1135
|
// Reset font về mặc định cho các dòng tiếp theo
|
|
764
1136
|
ctx.font = `${infoFontSize}px ${LAYOUT.FONT_FAMILY}`;
|
|
765
|
-
// Màu chữ (Text Color)
|
|
1137
|
+
// Màu chữ (Text Color) - layer_colors[0]
|
|
766
1138
|
drawAsterisk(padding, infoY);
|
|
767
|
-
const
|
|
768
|
-
ctx.fillText(
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
ctx.
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
1139
|
+
const textColorPrefix = "Màu chữ: ";
|
|
1140
|
+
ctx.fillText(textColorPrefix, startX, infoY);
|
|
1141
|
+
if (!textColor || textColor.trim() === "") {
|
|
1142
|
+
// Hiển thị warning màu đỏ nếu thiếu màu
|
|
1143
|
+
const prefixWidth = ctx.measureText(textColorPrefix).width;
|
|
1144
|
+
ctx.fillStyle = "#CC0000";
|
|
1145
|
+
ctx.fillText("(Chưa có màu)", startX + prefixWidth, infoY);
|
|
1146
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR; // Reset color
|
|
1147
|
+
}
|
|
1148
|
+
else {
|
|
1149
|
+
// Hiển thị tên màu
|
|
1150
|
+
const prefixWidth = ctx.measureText(textColorPrefix).width;
|
|
1151
|
+
ctx.fillText(textColor, startX + prefixWidth, infoY);
|
|
1152
|
+
// Draw text color swatch
|
|
1153
|
+
const swatchSize = infoFontSize * 1.3;
|
|
1154
|
+
const swatchX = startX +
|
|
1155
|
+
ctx.measureText(textColorPrefix + textColor).width +
|
|
1156
|
+
LAYOUT.ELEMENT_SPACING * 0.3;
|
|
1157
|
+
const swatchY = infoY + infoFontSize / 2 - swatchSize / 2;
|
|
1158
|
+
const textColorSwatchUrl = getImageUrl("threadColor", textColor);
|
|
1159
|
+
const textColorSwatchImg = imageRefs.current.get(textColorSwatchUrl);
|
|
1160
|
+
if (textColorSwatchImg?.complete && textColorSwatchImg.naturalHeight > 0) {
|
|
1161
|
+
const ratio = textColorSwatchImg.naturalWidth / textColorSwatchImg.naturalHeight;
|
|
1162
|
+
const swatchW = Math.max(1, Math.floor(swatchSize * ratio));
|
|
1163
|
+
ctx.drawImage(textColorSwatchImg, swatchX, swatchY, swatchW, swatchSize);
|
|
1164
|
+
}
|
|
781
1165
|
}
|
|
782
1166
|
infoY += infoLineHeight;
|
|
783
|
-
// Màu nền (Background Color)
|
|
784
|
-
drawAsterisk(padding
|
|
785
|
-
const
|
|
786
|
-
ctx.fillText(
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1167
|
+
// Màu nền (Background Color) - layer_colors[2]
|
|
1168
|
+
drawAsterisk(padding, infoY);
|
|
1169
|
+
const bgColorPrefix = "Màu nền: ";
|
|
1170
|
+
ctx.fillText(bgColorPrefix, startX, infoY);
|
|
1171
|
+
if (!backgroundColor || backgroundColor.trim() === "") {
|
|
1172
|
+
// Hiển thị warning màu đỏ nếu thiếu màu
|
|
1173
|
+
const prefixWidth = ctx.measureText(bgColorPrefix).width;
|
|
1174
|
+
ctx.fillStyle = "#CC0000";
|
|
1175
|
+
ctx.fillText("(Chưa có màu)", startX + prefixWidth, infoY);
|
|
1176
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR; // Reset color
|
|
1177
|
+
}
|
|
1178
|
+
else {
|
|
1179
|
+
// Hiển thị tên màu
|
|
1180
|
+
const prefixWidth = ctx.measureText(bgColorPrefix).width;
|
|
1181
|
+
ctx.fillText(backgroundColor, startX + prefixWidth, infoY);
|
|
1182
|
+
// Draw background color swatch
|
|
1183
|
+
const swatchSize = infoFontSize * 1.3;
|
|
1184
|
+
const bgSwatchX = startX +
|
|
1185
|
+
ctx.measureText(bgColorPrefix + backgroundColor).width +
|
|
1186
|
+
LAYOUT.ELEMENT_SPACING * 0.3;
|
|
1187
|
+
const bgSwatchY = infoY + infoFontSize / 2 - swatchSize / 2;
|
|
1188
|
+
const bgColorSwatchUrl = getImageUrl("threadColor", backgroundColor);
|
|
1189
|
+
const bgColorSwatchImg = imageRefs.current.get(bgColorSwatchUrl);
|
|
1190
|
+
if (bgColorSwatchImg?.complete && bgColorSwatchImg.naturalHeight > 0) {
|
|
1191
|
+
const ratio = bgColorSwatchImg.naturalWidth / bgColorSwatchImg.naturalHeight;
|
|
1192
|
+
const swatchW = Math.max(1, Math.floor(swatchSize * ratio));
|
|
1193
|
+
ctx.drawImage(bgColorSwatchImg, bgSwatchX, bgSwatchY, swatchW, swatchSize);
|
|
1194
|
+
}
|
|
796
1195
|
}
|
|
797
1196
|
infoY += infoLineHeight;
|
|
798
|
-
// Màu viền (Border Color)
|
|
799
|
-
drawAsterisk(padding
|
|
800
|
-
const
|
|
801
|
-
ctx.fillText(
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
ctx.measureText(
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
1197
|
+
// Màu viền (Border Color) - layer_colors[1]
|
|
1198
|
+
drawAsterisk(padding, infoY);
|
|
1199
|
+
const borderColorPrefix = "Màu viền: ";
|
|
1200
|
+
ctx.fillText(borderColorPrefix, startX, infoY);
|
|
1201
|
+
if (!borderColor || borderColor.trim() === "") {
|
|
1202
|
+
// Hiển thị warning màu đỏ nếu thiếu màu
|
|
1203
|
+
const prefixWidth = ctx.measureText(borderColorPrefix).width;
|
|
1204
|
+
ctx.fillStyle = "#CC0000";
|
|
1205
|
+
ctx.fillText("(Chưa có màu)", startX + prefixWidth, infoY);
|
|
1206
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR; // Reset color
|
|
1207
|
+
}
|
|
1208
|
+
else {
|
|
1209
|
+
// Hiển thị tên màu
|
|
1210
|
+
const prefixWidth = ctx.measureText(borderColorPrefix).width;
|
|
1211
|
+
ctx.fillText(borderColor, startX + prefixWidth, infoY);
|
|
1212
|
+
// Draw border color swatch
|
|
1213
|
+
const swatchSize = infoFontSize * 1.3;
|
|
1214
|
+
const borderSwatchX = startX +
|
|
1215
|
+
ctx.measureText(borderColorPrefix + borderColor).width +
|
|
1216
|
+
LAYOUT.ELEMENT_SPACING * 0.3;
|
|
1217
|
+
const borderSwatchY = infoY + infoFontSize / 2 - swatchSize / 2;
|
|
1218
|
+
const borderColorSwatchUrl = getImageUrl("threadColor", borderColor);
|
|
1219
|
+
const borderColorSwatchImg = imageRefs.current.get(borderColorSwatchUrl);
|
|
1220
|
+
if (borderColorSwatchImg?.complete &&
|
|
1221
|
+
borderColorSwatchImg.naturalHeight > 0) {
|
|
1222
|
+
const ratio = borderColorSwatchImg.naturalWidth / borderColorSwatchImg.naturalHeight;
|
|
1223
|
+
const swatchW = Math.max(1, Math.floor(swatchSize * ratio));
|
|
1224
|
+
ctx.drawImage(borderColorSwatchImg, borderSwatchX, borderSwatchY, swatchW, swatchSize);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
infoY += infoLineHeight;
|
|
1228
|
+
// Màu vải (Fabric Color) - layer_colors[3]
|
|
1229
|
+
drawAsterisk(padding, infoY);
|
|
1230
|
+
const fabricColorPrefix = "Màu vải: ";
|
|
1231
|
+
ctx.fillText(fabricColorPrefix, startX, infoY);
|
|
1232
|
+
if (!fabricColor || fabricColor.trim() === "") {
|
|
1233
|
+
// Hiển thị warning màu đỏ nếu thiếu màu
|
|
1234
|
+
const prefixWidth = ctx.measureText(fabricColorPrefix).width;
|
|
1235
|
+
ctx.fillStyle = "#CC0000";
|
|
1236
|
+
ctx.fillText("(Chưa có màu)", startX + prefixWidth, infoY);
|
|
1237
|
+
ctx.fillStyle = LAYOUT.LABEL_COLOR; // Reset color
|
|
1238
|
+
}
|
|
1239
|
+
else {
|
|
1240
|
+
// Hiển thị tên màu
|
|
1241
|
+
const prefixWidth = ctx.measureText(fabricColorPrefix).width;
|
|
1242
|
+
ctx.fillText(fabricColor, startX + prefixWidth, infoY);
|
|
1243
|
+
// Draw fabric color swatch
|
|
1244
|
+
const swatchSize = infoFontSize * 1.3;
|
|
1245
|
+
const fabricSwatchX = startX +
|
|
1246
|
+
ctx.measureText(fabricColorPrefix + fabricColor).width +
|
|
1247
|
+
LAYOUT.ELEMENT_SPACING * 0.3;
|
|
1248
|
+
const fabricSwatchY = infoY + infoFontSize / 2 - swatchSize / 2;
|
|
1249
|
+
const fabricColorSwatchUrl = getImageUrl("threadColor", fabricColor);
|
|
1250
|
+
const fabricColorSwatchImg = imageRefs.current.get(fabricColorSwatchUrl);
|
|
1251
|
+
if (fabricColorSwatchImg?.complete &&
|
|
1252
|
+
fabricColorSwatchImg.naturalHeight > 0) {
|
|
1253
|
+
const ratio = fabricColorSwatchImg.naturalWidth / fabricColorSwatchImg.naturalHeight;
|
|
1254
|
+
const swatchW = Math.max(1, Math.floor(swatchSize * ratio));
|
|
1255
|
+
ctx.drawImage(fabricColorSwatchImg, fabricSwatchX, fabricSwatchY, swatchW, swatchSize);
|
|
1256
|
+
}
|
|
814
1257
|
}
|
|
815
1258
|
infoY += infoLineHeight;
|
|
816
1259
|
// Attachment
|
|
@@ -879,6 +1322,12 @@ const renderEmbroideryCanvas = (canvas, config, canvasSize, imageRefs) => {
|
|
|
879
1322
|
renderStrokePatchesCanvas(ctx, canvas, config, imageRefs);
|
|
880
1323
|
return;
|
|
881
1324
|
}
|
|
1325
|
+
// Check if this is a template_custom_text_patches layout
|
|
1326
|
+
const hasTemplateCustomTextPatches = config.sides.some((side) => side.item_type && side.item_type.includes("template_custom_text_patches"));
|
|
1327
|
+
if (hasTemplateCustomTextPatches) {
|
|
1328
|
+
renderTemplateCustomTextPatchesCanvas(ctx, canvas, config, imageRefs);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
882
1331
|
ctx.textAlign = LAYOUT.TEXT_ALIGN;
|
|
883
1332
|
ctx.textBaseline = LAYOUT.TEXT_BASELINE;
|
|
884
1333
|
if (config.image_url) {
|