How to increase the transformer and text according to the lines total height?

110 Views Asked by At

I am struggling to update my transformer's height to match the total height of the text inside, when I decrease the transformer width, the text breaks into several lines causing them to go out of the transformer height, making it unable to see, I then manually have to increase the transformer (also text object I guess) to make the lines below visible

Is there a way to make it automatic?

This is my text component

import React, { useState } from 'react';
import { useRef, useEffect } from 'react';
import { Text as TextObject, Transformer } from 'react-konva';
import { Html } from 'react-konva-utils';
import { BsFillCheckSquareFill } from 'react-icons/bs';
import { usePropertiesContext } from '@/hooks/PropertiesContent';

interface TextProps {
  x: number;
  y: number;
  fontSize: number;
  fill: string;
  fontVariant: string;
  align: undefined;
  fontFamily: string;
  opacity: number;
  rotation: number;
  isSelected: boolean;
  boundingBoxWidth: number;
  boundingBoxHeight: number;
  onSelect: () => void;
  onChange: ({ x, y }: { x: number; y: number }) => void;
}

const Text: React.FC<TextProps> = ({
  x,
  y,
  fontSize,
  fill,
  fontVariant,
  align,
  fontFamily,
  opacity,
  rotation,
  isSelected,
  boundingBoxWidth,
  boundingBoxHeight,
  onSelect,
  onChange,
}) => {
  const { settextRotation } = usePropertiesContext();
  const [textHeight, setTextHeight] = useState(20);
  const [text, setText] = useState('Your Text');
  const [edit, setEdit] = useState(false);
  const [textWidth, setTextWidth] = useState(120);
  const [realHeight, setRealHeight] = useState(20);
  const [rotationAngle, setRotationAngle] = useState(rotation); // Store the rotation angle
  const [transformerHeight, setTransformerHeight] = useState(textHeight);
  const shapeRef = useRef() as any;
  const trRef = useRef<Transformer>() as any;

  useEffect(() => {
    if (!edit && isSelected) {
      trRef.current?.nodes([shapeRef.current]);
      trRef.current.getLayer().batchDraw();
    }
  }, [edit, isSelected]);

  useEffect(() => {
    // Access the Konva.Text instance
    const textNode = shapeRef.current;

    if (textNode) {
      const newTextHeight = textNode.height();
      console.log(`Height of Text element: ${newTextHeight}`);
      setRealHeight(newTextHeight);
    }

    // Update the rotation angle in state
    setRotationAngle(rotation);
  }, [rotation]);

  // Function to update the size and position of the TextObject and Transformer
  const updateSizeAndPosition = (
    newTextWidth: number,
    newTextHeight: number
  ) => {
    setTextWidth(newTextWidth);
    setTextHeight(newTextHeight);

    shapeRef.current?.width(newTextWidth);
    shapeRef.current?.height(newTextHeight);

    // Update Transformer size and position
    trRef.current?.boundBoxFunc(trRef.current.getClientRect(), {
      x: x,
      y: y,
      width: newTextWidth,
      height: newTextHeight,
    });

    setTransformerHeight(newTextHeight); // Update the transformer height here

    trRef.current?.getLayer().batchDraw();
  };

  return (
    <React.Fragment>
      <Html
        divProps={{
          style: {
            position: 'absolute',
            top: y + 'px',
            left: x + 'px',
            transform: `rotate(${rotationAngle}deg)`, // Apply rotation to the textarea
          },
        }}
      >
        {edit ? (
          <div className="flex items-start gap-x-2">
            <textarea
              value={text}
              onChange={(e) => {
                setText(e.target.value);
                const newHeight = e.target.scrollHeight;
                const newWidth = e.target.scrollWidth;
                updateSizeAndPosition(newWidth, newHeight);
              }}
              style={{
                width: textWidth + 'px',
                height: textHeight + 'px',
                backgroundColor: 'transparent',
                color: fill,
                textAlign: align,
                fontSize: fontSize,
                fontFamily: fontFamily,
                fontVariant: fontVariant,
                opacity: opacity,
                lineHeight: 1,
                appearance: 'none',
                overflow: 'hidden' /* Hide the scrollbar */,
                resize: 'none' /* Disable resizing */,
                border: 'none',
                outline: 'none',
              }}
            />
            <button onClick={() => setEdit(false)}>
              <BsFillCheckSquareFill className="text-blue-500 hover:text-blue-600 text-lg shadow-sm" />
            </button>
          </div>
        ) : (
          <></>
        )}
      </Html>
      {!edit ? (
        <TextObject
          onClick={onSelect}
          onTap={onSelect}
          onDblClick={() => setEdit(true)}
          onDblTap={() => setEdit(true)}
          text={text}
          ref={shapeRef}
          fontSize={fontSize}
          fill={fill}
          x={x}
          y={y}
          opacity={opacity}
          width={textWidth}
          fontFamily={fontFamily}
          fontVariant={fontVariant}
          align={align}
          rotation={rotation}
          height={textHeight}
          draggable
          onDragEnd={(e) => {
            onChange({
              x: e.target.x(),
              y: e.target.y(),
            });
          }}
          onDragMove={(e) => {
            // Do not let the object move outside of the pdf page
            if (e.target.x() - 10 < 0) e.target.x(10);
            if (e.target.y() - 10 < 0) e.target.y(10);
            if (e.target.x() + e.target.width() + 10 > boundingBoxWidth)
              e.target.x(boundingBoxWidth - e.target.width() - 10);
            if (e.target.y() + e.target.height() + 10 > boundingBoxHeight)
              e.target.y(boundingBoxHeight - e.target.height() - 10);
          }}
        />
      ) : (
        <></>
      )}

      {isSelected && !edit && (
        <Transformer
          x={x}
          y={y}
          width={textWidth}
          height={transformerHeight}
          rotateAnchorOffset={30}
          anchorCornerRadius={90}
          padding={5}
          ref={trRef}
          boundBoxFunc={(oldBox, newBox) => {
            if (newBox.width < 5 || newBox.height < 5) {
              return oldBox;
            }
            return newBox;
          }}
          onTransform={(e) => {
            const node = e.target;
            const newRotation = Math.round(node.rotation()); // Get the new rotation
            settextRotation(newRotation); // Update the rotation angle in state
            node.setAttrs({
              width: Math.max(node.width() * node.scaleX(), 20),
              height: Math.max(node.height() * node.scaleY(), 20),
              scaleX: 1,
              scaleY: 1,
            });
            setTextWidth(node.width() * node.scaleX());
            setTextHeight(node.height() * node.scaleY());
            onChange({
              x: e.target.x(),
              y: e.target.y(),
            });
          }}
        />
      )}
    </React.Fragment>
  );
};

export default Text;
1

There are 1 best solutions below

0
lavrton On

If you explicitly set the height of the text, Konva will always respect it. If text overflow defined height, then Konva will cut it and render only visible lines.

There are two ways to resolve the issue.

  1. You can just remove height property from <Text /> component. So it will be "auto" by default. You can still keep it in the state for other purposes.

  2. When you update the state with the updated text height, instead of reading it directly from the text instance, you can reset it to undefined first then get it. After reset, Konva will return the height required for all lines.

// reset height
node.height(undefined);
// then read real value
setTextHeight(node.height() * node.scaleY());