Ver código fonte

feat: improve freedraw shape (#3984)

vanilla_orig
Steve Ruiz 3 anos atrás
pai
commit
7199d13f48
Nenhuma conta vinculada ao e-mail do autor do commit
3 arquivos alterados com 40 adições e 25 exclusões
  1. 1
    1
      package.json
  2. 35
    20
      src/renderer/renderElement.ts
  3. 4
    4
      yarn.lock

+ 1
- 1
package.json Ver arquivo

@@ -35,7 +35,7 @@
35 35
     "nanoid": "3.1.22",
36 36
     "open-color": "1.8.0",
37 37
     "pako": "1.0.11",
38
-    "perfect-freehand": "0.4.7",
38
+    "perfect-freehand": "1.0.6",
39 39
     "png-chunk-text": "1.0.0",
40 40
     "png-chunks-encode": "1.0.0",
41 41
     "png-chunks-extract": "1.0.0",

+ 35
- 20
src/renderer/renderElement.ts Ver arquivo

@@ -32,7 +32,7 @@ import { isPathALoop } from "../math";
32 32
 import rough from "roughjs/bin/rough";
33 33
 import { Zoom } from "../types";
34 34
 import { getDefaultAppState } from "../appState";
35
-import getFreeDrawShape from "perfect-freehand";
35
+import { getStroke, StrokeOptions } from "perfect-freehand";
36 36
 import { MAX_DECIMALS_FOR_SVG_EXPORT } from "../constants";
37 37
 
38 38
 const defaultAppState = getDefaultAppState();
@@ -789,40 +789,55 @@ export function getFreeDrawPath2D(element: ExcalidrawFreeDrawElement) {
789 789
 }
790 790
 
791 791
 export function getFreeDrawSvgPath(element: ExcalidrawFreeDrawElement) {
792
+  // If input points are empty (should they ever be?) return a dot
792 793
   const inputPoints = element.simulatePressure
793 794
     ? element.points
794 795
     : element.points.length
795 796
     ? element.points.map(([x, y], i) => [x, y, element.pressures[i]])
796
-    : [[0, 0, 0]];
797
+    : [[0, 0, 0.5]];
797 798
 
798 799
   // Consider changing the options for simulated pressure vs real pressure
799
-  const options = {
800
+  const options: StrokeOptions = {
800 801
     simulatePressure: element.simulatePressure,
801
-    size: element.strokeWidth * 6,
802
-    thinning: 0.5,
802
+    size: element.strokeWidth * 4.25,
803
+    thinning: 0.6,
803 804
     smoothing: 0.5,
804 805
     streamline: 0.5,
805
-    easing: (t: number) => t * (2 - t),
806
-    last: true,
806
+    easing: (t) => Math.sin((t * Math.PI) / 2), // https://easings.net/#easeOutSine
807
+    last: false,
807 808
   };
808 809
 
809
-  const points = getFreeDrawShape(inputPoints as number[][], options);
810
-  const d: (string | number)[] = [];
810
+  return getSvgPathFromStroke(getStroke(inputPoints as number[][], options));
811
+}
811 812
 
812
-  let [p0, p1] = points;
813
+function med(A: number[], B: number[]) {
814
+  return [(A[0] + B[0]) / 2, (A[1] + B[1]) / 2];
815
+}
813 816
 
814
-  d.push("M", p0[0], p0[1], "Q");
817
+// Trim SVG path data so number are each two decimal points. This
818
+// improves SVG exports, and prevents rendering errors on points
819
+// with long decimals.
820
+const TO_FIXED_PRECISION = /(\s?[A-Z]?,?-?[0-9]*\.[0-9]{0,2})(([0-9]|e|-)*)/g;
815 821
 
816
-  for (let i = 0; i < points.length; i++) {
817
-    d.push(p0[0], p0[1], (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2);
818
-    p0 = p1;
819
-    p1 = points[i];
822
+function getSvgPathFromStroke(points: number[][]): string {
823
+  if (!points.length) {
824
+    return "";
820 825
   }
821 826
 
822
-  p1 = points[0];
823
-  d.push(p0[0], p0[1], (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2);
824
-
825
-  d.push("Z");
827
+  const max = points.length - 1;
826 828
 
827
-  return d.join(" ");
829
+  return points
830
+    .reduce(
831
+      (acc, point, i, arr) => {
832
+        if (i === max) {
833
+          acc.push(point, med(point, arr[0]), "L", arr[0], "Z");
834
+        } else {
835
+          acc.push(point, med(point, arr[i + 1]));
836
+        }
837
+        return acc;
838
+      },
839
+      ["M", points[0], "Q"],
840
+    )
841
+    .join(" ")
842
+    .replaceAll(TO_FIXED_PRECISION, "$1");
828 843
 }

+ 4
- 4
yarn.lock Ver arquivo

@@ -9260,10 +9260,10 @@ pepjs@0.5.3:
9260 9260
   version "0.5.3"
9261 9261
   resolved "https://registry.npmjs.org/pepjs/-/pepjs-0.5.3.tgz"
9262 9262
 
9263
-perfect-freehand@0.4.7:
9264
-  version "0.4.7"
9265
-  resolved "https://registry.yarnpkg.com/perfect-freehand/-/perfect-freehand-0.4.7.tgz#4d85fd64881ba81b2a4eaa6ac4e8983ccb21dd43"
9266
-  integrity sha512-SSSFL8VzXiOHQdUTyNyOb0JC+btVZRy9bi6jos7Nb7PBTI0PHX5jM6RgCTSrubQ8Ul9qOYWmWgJBrwVGHwyJZQ==
9263
+perfect-freehand@1.0.6:
9264
+  version "1.0.6"
9265
+  resolved "https://registry.yarnpkg.com/perfect-freehand/-/perfect-freehand-1.0.6.tgz#feeb25450241f036ec13b43fa84bbb16f8e78e0f"
9266
+  integrity sha512-wWkFwpgUirsfBDTb9nG6+VnFR0ge119QKU2Nu96vR4MHZMPGfOsQRD7cUk+9CK5P+TUmnrtX8yOEzUrQ6KHJoA==
9267 9267
 
9268 9268
 performance-now@^2.1.0:
9269 9269
   version "2.1.0"

Carregando…
Cancelar
Salvar