diff --git a/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts b/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts index ce08f8bb..ed119ee4 100644 --- a/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts +++ b/src/common/components/mock-components/front-rich-components/tabsbar/business/tabsbar.business.ts @@ -1,6 +1,6 @@ import { Layer } from 'konva/lib/Layer'; import { balanceSpacePerItem } from './balance-space'; -import { calcTextWidth } from './calc-text-width'; +import { calcTextDimensions } from '@/common/utils/calc-text-dimensions'; export const adjustTabWidths = (args: { tabs: string[]; @@ -35,9 +35,14 @@ export const adjustTabWidths = (args: { } const arrangeTabsInfo = tabs.reduce( (acc: OriginalTabInfo[], tab, index): OriginalTabInfo[] => { - const tabFullTextWidth = - calcTextWidth(tab, font.fontSize, font.fontFamily, konvaLayer) + - totalInnerXPadding; + const { width: textWidth } = calcTextDimensions( + tab, + font.fontSize, + font.fontFamily, + konvaLayer + ); + + const tabFullTextWidth = textWidth + totalInnerXPadding; const desiredWidth = Math.max(totalMinTabSpace, tabFullTextWidth); return [ ...acc, diff --git a/src/common/components/mock-components/front-text-components/front-text-hooks/resize-fontsize-change.hook.ts b/src/common/components/mock-components/front-text-components/front-text-hooks/resize-fontsize-change.hook.ts new file mode 100644 index 00000000..a629b2f3 --- /dev/null +++ b/src/common/components/mock-components/front-text-components/front-text-hooks/resize-fontsize-change.hook.ts @@ -0,0 +1,63 @@ +import { calcTextDimensions } from '@/common/utils/calc-text-dimensions'; +import { useCanvasContext } from '@/core/providers'; +import { useEffect, useRef } from 'react'; + +export const useResizeOnFontSizeChange = ( + id: string, + coords: { x: number; y: number }, + text: string, + fontSize: number, + fontVariant: string, + multiline: boolean = false +) => { + const previousFontSize = useRef(fontSize); + const { updateShapeSizeAndPosition, stageRef } = useCanvasContext(); + const konvaLayer = stageRef.current?.getLayers()[0]; + + useEffect(() => { + if (previousFontSize.current !== fontSize) { + previousFontSize.current = fontSize; + + const paragraphLines = _extractParagraphLines(text); + const longestLine = _findLongestString(paragraphLines); + + const { width, height } = calcTextDimensions( + multiline ? longestLine : text, + fontSize, + fontVariant, + konvaLayer + ); + + updateShapeSizeAndPosition( + id, + coords, + { + width: width * 1.15, + height: multiline + ? _calcParagraphTotalHeight(height, 0.8, paragraphLines.length) + : height, + }, + true + ); + } + }, [fontSize]); +}; + +/* Hook Helper functions */ +function _extractParagraphLines(multilineText: string) { + return multilineText.split(/\r?\n/).filter(line => line.trim() !== ''); +} + +function _findLongestString(stringsArray: string[]): string { + return stringsArray.reduce((longest, current) => + current.length > longest.length ? current : longest + ); +} + +function _calcParagraphTotalHeight( + heightPerLine: number, + lineHeight: number = 1.3, + linesQty: number +) { + return heightPerLine * lineHeight * linesQty; +} diff --git a/src/common/components/mock-components/front-text-components/heading1-text-shape.tsx b/src/common/components/mock-components/front-text-components/heading1-text-shape.tsx index ed5ef8c5..1e76ba40 100644 --- a/src/common/components/mock-components/front-text-components/heading1-text-shape.tsx +++ b/src/common/components/mock-components/front-text-components/heading1-text-shape.tsx @@ -6,6 +6,7 @@ import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes/shape-rest import { useShapeProps } from '../../shapes/use-shape-props.hook'; import { BASIC_SHAPE } from '../front-components/shape.const'; import { useGroupShapeProps } from '../mock-components.utils'; +import { useResizeOnFontSizeChange } from './front-text-hooks/resize-fontsize-change.hook'; const heading1SizeRestrictions: ShapeSizeRestrictions = { minWidth: 40, @@ -56,6 +57,8 @@ export const Heading1Shape = forwardRef((props, ref) => { ref ); + useResizeOnFontSizeChange(id, { x, y }, text, fontSize, fontVariant); + return ( ((props, ref) => { ref ); + useResizeOnFontSizeChange(id, { x, y }, text, fontSize, fontVariant); + return ( ((props, ref) => { ref ); + useResizeOnFontSizeChange(id, { x, y }, text, fontSize, fontVariant); + return ( ((props, ref) => { const { width: restrictedWidth, height: restrictedHeight } = restrictedSize; - const { textColor, textDecoration, fontSize, textAlignment } = useShapeProps( - otherProps, - BASIC_SHAPE - ); + const { textColor, textDecoration, fontSize, textAlignment, fontVariant } = + useShapeProps(otherProps, BASIC_SHAPE); const commonGroupProps = useGroupShapeProps( props, @@ -53,6 +52,8 @@ export const LinkShape = forwardRef((props, ref) => { ref ); + useResizeOnFontSizeChange(id, { x, y }, text, fontSize, fontVariant); + return ( ((props, ref) => { ref ); + useResizeOnFontSizeChange(id, { x, y }, text, fontSize, fontVariant); + return ( ((props, ref) => { ); const { width: restrictedWidth, height: restrictedHeight } = restrictedSize; - const { textColor, fontSize, textAlignment } = useShapeProps( + const { textColor, fontSize, textAlignment, fontVariant } = useShapeProps( otherProps, BASIC_SHAPE ); @@ -52,6 +53,8 @@ export const ParagraphShape = forwardRef((props, ref) => { ref ); + useResizeOnFontSizeChange(id, { x, y }, text, fontSize, fontVariant, true); + return ( ((props, ref) => { ref ); + useResizeOnFontSizeChange(id, { x, y }, text, fontSize, fontVariant); + return ( { const context = konvaLayer.getContext(); context.font = `${fontSize}px ${fontfamily}`; - return context.measureText(text).width; + const { width, fontBoundingBoxAscent, fontBoundingBoxDescent } = + context.measureText(text); + const totalHeight = fontBoundingBoxAscent + fontBoundingBoxDescent; + return { width, height: totalHeight }; }; const _getTextCreatingNewCanvas = ( @@ -43,8 +46,13 @@ const _getTextCreatingNewCanvas = ( const context = canvas.getContext('2d'); if (context) { context.font = `${fontSize}px ${fontfamily}`; - return context.measureText(text).width; + const { width, fontBoundingBoxAscent, fontBoundingBoxDescent } = + context.measureText(text); + const height = fontBoundingBoxAscent + fontBoundingBoxDescent; + return { width, height }; } const charAverageWidth = fontSize * 0.7; - return text.length * charAverageWidth + charAverageWidth * 0.8; + const width = text.length * charAverageWidth + charAverageWidth * 0.8; + const height = fontSize * 1.5; + return { width, height }; }; diff --git a/src/pods/properties/components/image-black-and-white/image-black-and-white-selector.component.tsx b/src/pods/properties/components/image-black-and-white/image-black-and-white-selector.component.tsx index dc3e1ba1..1bb4c8d4 100644 --- a/src/pods/properties/components/image-black-and-white/image-black-and-white-selector.component.tsx +++ b/src/pods/properties/components/image-black-and-white/image-black-and-white-selector.component.tsx @@ -14,6 +14,7 @@ export const ImageBlackAndWhite: React.FC = props => {

{label}

onChange(!imageBlackAndWhite)} className={classes.checkbox} /> diff --git a/src/pods/toolbar/components/export-button/export-button.tsx b/src/pods/toolbar/components/export-button/export-button.tsx index 668f9f34..698ffcc5 100644 --- a/src/pods/toolbar/components/export-button/export-button.tsx +++ b/src/pods/toolbar/components/export-button/export-button.tsx @@ -4,6 +4,7 @@ import classes from '@/pods/toolbar/toolbar.pod.module.css'; import { Stage } from 'konva/lib/Stage'; import { calculateCanvasBounds } from './export-button.utils'; import { ToolbarButton } from '../toolbar-button'; +import Konva from 'konva'; export const ExportButton = () => { const { stageRef, shapes } = useCanvasContext(); @@ -21,20 +22,38 @@ export const ExportButton = () => { stage.scale({ x: 1, y: 1 }); }; + const applyFiltersToImages = (stage: Stage) => { + stage.find('Image').forEach(node => { + if (node.filters()?.includes(Konva.Filters.Grayscale)) { + node.cache({ + x: 0, + y: 0, + width: node.width(), + height: node.height(), + pixelRatio: 2, + }); + } + }); + }; + const handleExport = () => { if (stageRef.current) { const originalStage = stageRef.current; const clonedStage = originalStage.clone(); + + applyFiltersToImages(clonedStage); resetScale(clonedStage); + const bounds = calculateCanvasBounds(shapes); const dataURL = clonedStage.toDataURL({ - mimeType: 'image/png', // Change to jpeg to download as jpeg + mimeType: 'image/png', // Swap to 'image/jpeg' if necessary x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height, pixelRatio: 2, }); + createDownloadLink(dataURL); } };