newElement.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import { randomSeed } from "roughjs/bin/math";
  2. import nanoid from "nanoid";
  3. import { Drawable } from "roughjs/bin/core";
  4. import { Point } from "roughjs/bin/geometry";
  5. import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
  6. import { measureText } from "../utils";
  7. export function newElement(
  8. type: string,
  9. x: number,
  10. y: number,
  11. strokeColor: string,
  12. backgroundColor: string,
  13. fillStyle: string,
  14. strokeWidth: number,
  15. roughness: number,
  16. opacity: number,
  17. width = 0,
  18. height = 0,
  19. ) {
  20. const element = {
  21. id: nanoid(),
  22. type,
  23. x,
  24. y,
  25. width,
  26. height,
  27. strokeColor,
  28. backgroundColor,
  29. fillStyle,
  30. strokeWidth,
  31. roughness,
  32. opacity,
  33. isSelected: false,
  34. seed: randomSeed(),
  35. shape: null as Drawable | Drawable[] | null,
  36. points: [] as Point[],
  37. canvas: null as HTMLCanvasElement | null,
  38. canvasZoom: 1, // The zoom level used to render the cached canvas
  39. canvasOffsetX: 0,
  40. canvasOffsetY: 0,
  41. };
  42. return element;
  43. }
  44. export function newTextElement(
  45. element: ExcalidrawElement,
  46. text: string,
  47. font: string,
  48. ) {
  49. const metrics = measureText(text, font);
  50. const textElement: ExcalidrawTextElement = {
  51. ...element,
  52. shape: null,
  53. type: "text",
  54. text: text,
  55. font: font,
  56. // Center the text
  57. x: element.x - metrics.width / 2,
  58. y: element.y - metrics.height / 2,
  59. width: metrics.width,
  60. height: metrics.height,
  61. baseline: metrics.baseline,
  62. };
  63. return textElement;
  64. }
  65. // Simplified deep clone for the purpose of cloning ExcalidrawElement only
  66. // (doesn't clone Date, RegExp, Map, Set, Typed arrays etc.)
  67. //
  68. // Adapted from https://github.com/lukeed/klona
  69. function _duplicateElement(val: any, depth: number = 0) {
  70. if (val == null || typeof val !== "object") {
  71. return val;
  72. }
  73. if (Object.prototype.toString.call(val) === "[object Object]") {
  74. const tmp =
  75. typeof val.constructor === "function"
  76. ? Object.create(Object.getPrototypeOf(val))
  77. : {};
  78. for (const k in val) {
  79. if (val.hasOwnProperty(k)) {
  80. // don't copy top-level shape property, which we want to regenerate
  81. if (depth === 0 && (k === "shape" || k === "canvas")) {
  82. continue;
  83. }
  84. tmp[k] = _duplicateElement(val[k], depth + 1);
  85. }
  86. }
  87. return tmp;
  88. }
  89. if (Array.isArray(val)) {
  90. let k = val.length;
  91. const arr = new Array(k);
  92. while (k--) {
  93. arr[k] = _duplicateElement(val[k], depth + 1);
  94. }
  95. return arr;
  96. }
  97. return val;
  98. }
  99. export function duplicateElement(element: ReturnType<typeof newElement>) {
  100. const copy = _duplicateElement(element);
  101. copy.id = nanoid();
  102. copy.seed = randomSeed();
  103. return copy;
  104. }