sizeHelpers.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import { ExcalidrawElement } from "./types";
  2. import { mutateElement } from "./mutateElement";
  3. import { isFreeDrawElement, isLinearElement } from "./typeChecks";
  4. import { SHIFT_LOCKING_ANGLE } from "../constants";
  5. import { AppState } from "../types";
  6. export const isInvisiblySmallElement = (
  7. element: ExcalidrawElement,
  8. ): boolean => {
  9. if (isLinearElement(element) || isFreeDrawElement(element)) {
  10. return element.points.length < 2;
  11. }
  12. return element.width === 0 && element.height === 0;
  13. };
  14. /**
  15. * Makes a perfect shape or diagonal/horizontal/vertical line
  16. */
  17. export const getPerfectElementSize = (
  18. elementType: AppState["activeTool"]["type"],
  19. width: number,
  20. height: number,
  21. ): { width: number; height: number } => {
  22. const absWidth = Math.abs(width);
  23. const absHeight = Math.abs(height);
  24. if (
  25. elementType === "line" ||
  26. elementType === "arrow" ||
  27. elementType === "freedraw"
  28. ) {
  29. const lockedAngle =
  30. Math.round(Math.atan(absHeight / absWidth) / SHIFT_LOCKING_ANGLE) *
  31. SHIFT_LOCKING_ANGLE;
  32. if (lockedAngle === 0) {
  33. height = 0;
  34. } else if (lockedAngle === Math.PI / 2) {
  35. width = 0;
  36. } else {
  37. height = absWidth * Math.tan(lockedAngle) * Math.sign(height) || height;
  38. }
  39. } else if (elementType !== "selection") {
  40. height = absWidth * Math.sign(height);
  41. }
  42. return { width, height };
  43. };
  44. export const getLockedLinearCursorAlignSize = (
  45. originX: number,
  46. originY: number,
  47. x: number,
  48. y: number,
  49. ) => {
  50. let width = x - originX;
  51. let height = y - originY;
  52. const lockedAngle =
  53. Math.round(Math.atan(height / width) / SHIFT_LOCKING_ANGLE) *
  54. SHIFT_LOCKING_ANGLE;
  55. if (lockedAngle === 0) {
  56. height = 0;
  57. } else if (lockedAngle === Math.PI / 2) {
  58. width = 0;
  59. } else {
  60. // locked angle line, y = mx + b => mx - y + b = 0
  61. const a1 = Math.tan(lockedAngle);
  62. const b1 = -1;
  63. const c1 = originY - a1 * originX;
  64. // line through cursor, perpendicular to locked angle line
  65. const a2 = -1 / a1;
  66. const b2 = -1;
  67. const c2 = y - a2 * x;
  68. // intersection of the two lines above
  69. const intersectX = (b1 * c2 - b2 * c1) / (a1 * b2 - a2 * b1);
  70. const intersectY = (c1 * a2 - c2 * a1) / (a1 * b2 - a2 * b1);
  71. // delta
  72. width = intersectX - originX;
  73. height = intersectY - originY;
  74. }
  75. return { width, height };
  76. };
  77. export const resizePerfectLineForNWHandler = (
  78. element: ExcalidrawElement,
  79. x: number,
  80. y: number,
  81. ) => {
  82. const anchorX = element.x + element.width;
  83. const anchorY = element.y + element.height;
  84. const distanceToAnchorX = x - anchorX;
  85. const distanceToAnchorY = y - anchorY;
  86. if (Math.abs(distanceToAnchorX) < Math.abs(distanceToAnchorY) / 2) {
  87. mutateElement(element, {
  88. x: anchorX,
  89. width: 0,
  90. y,
  91. height: -distanceToAnchorY,
  92. });
  93. } else if (Math.abs(distanceToAnchorY) < Math.abs(element.width) / 2) {
  94. mutateElement(element, {
  95. y: anchorY,
  96. height: 0,
  97. });
  98. } else {
  99. const nextHeight =
  100. Math.sign(distanceToAnchorY) *
  101. Math.sign(distanceToAnchorX) *
  102. element.width;
  103. mutateElement(element, {
  104. x,
  105. y: anchorY - nextHeight,
  106. width: -distanceToAnchorX,
  107. height: nextHeight,
  108. });
  109. }
  110. };
  111. export const getNormalizedDimensions = (
  112. element: Pick<ExcalidrawElement, "width" | "height" | "x" | "y">,
  113. ): {
  114. width: ExcalidrawElement["width"];
  115. height: ExcalidrawElement["height"];
  116. x: ExcalidrawElement["x"];
  117. y: ExcalidrawElement["y"];
  118. } => {
  119. const ret = {
  120. width: element.width,
  121. height: element.height,
  122. x: element.x,
  123. y: element.y,
  124. };
  125. if (element.width < 0) {
  126. const nextWidth = Math.abs(element.width);
  127. ret.width = nextWidth;
  128. ret.x = element.x - nextWidth;
  129. }
  130. if (element.height < 0) {
  131. const nextHeight = Math.abs(element.height);
  132. ret.height = nextHeight;
  133. ret.y = element.y - nextHeight;
  134. }
  135. return ret;
  136. };