소스 검색

fix: bring in tldraw's optimization on using freehand

see https://github.com/tldraw/tldraw/pull/989
Peng Xiao 3 년 전
부모
커밋
f1102c57c9
3개의 변경된 파일71개의 추가작업 그리고 28개의 파일을 삭제
  1. 45 11
      tldraw/apps/tldraw-logseq/src/lib/shapes/PencilShape.tsx
  2. 0 1
      tldraw/apps/tldraw-logseq/src/styles.css
  3. 26 16
      tldraw/packages/core/src/utils/SvgPathUtils.ts

+ 45 - 11
tldraw/apps/tldraw-logseq/src/lib/shapes/PencilShape.tsx

@@ -3,13 +3,56 @@ import { SvgPathUtils, TLDrawShape, TLDrawShapeProps } from '@tldraw/core'
 import { SVGContainer, TLComponentProps } from '@tldraw/react'
 import { computed, makeObservable } from 'mobx'
 import { observer } from 'mobx-react-lite'
-import getStroke from 'perfect-freehand'
+import getStroke, { getStrokeOutlinePoints, getStrokePoints, StrokeOptions } from 'perfect-freehand'
 import { CustomStyleProps, withClampedStyles } from './style-props'
 
 export interface PencilShapeProps extends TLDrawShapeProps, CustomStyleProps {
   type: 'pencil'
 }
 
+const simulatePressureSettings: StrokeOptions = {
+  easing: t => Math.sin((t * Math.PI) / 2),
+  simulatePressure: true,
+}
+
+const realPressureSettings: StrokeOptions = {
+  easing: t => t * t,
+  simulatePressure: false,
+}
+
+function getFreehandOptions(shape: PencilShapeProps) {
+  const options: StrokeOptions = {
+    size: 1 + shape.strokeWidth * 1.5,
+    thinning: 0.65,
+    streamline: 0.65,
+    smoothing: 0.65,
+    ...(shape.points[1][2] === 0.5 ? simulatePressureSettings : realPressureSettings),
+    last: shape.isComplete,
+  }
+
+  return options
+}
+
+function getFillPath(shape: PencilShapeProps) {
+  if (shape.points.length < 2) return ''
+
+  return SvgPathUtils.getSvgPathFromStroke(
+    getStrokePoints(shape.points, getFreehandOptions(shape)).map(pt => pt.point)
+  )
+}
+
+function getDrawStrokePoints(shape: PencilShapeProps, options: StrokeOptions) {
+  return getStrokePoints(shape.points, options)
+}
+
+function getDrawStrokePathTDSnapshot(shape: PencilShapeProps) {
+  if (shape.points.length < 2) return ''
+  const options = getFreehandOptions(shape)
+  const strokePoints = getDrawStrokePoints(shape, options)
+  const path = SvgPathUtils.getSvgPathFromStroke(getStrokeOutlinePoints(strokePoints, options))
+  return path
+}
+
 export class PencilShape extends TLDrawShape<PencilShapeProps> {
   constructor(props = {} as Partial<PencilShapeProps>) {
     super(props)
@@ -34,16 +77,7 @@ export class PencilShape extends TLDrawShape<PencilShapeProps> {
   }
 
   @computed get pointsPath() {
-    const {
-      props: { points, isComplete, strokeWidth },
-    } = this
-    if (points.length < 2) {
-      return `M -4, 0
-      a 4,4 0 1,0 8,0
-      a 4,4 0 1,0 -8,0`
-    }
-    const stroke = getStroke(points, { size: 4 + strokeWidth * 2, last: isComplete })
-    return SvgPathUtils.getCurvedPathForPolygon(stroke)
+    return getDrawStrokePathTDSnapshot(this.props)
   }
 
   ReactComponent = observer(({ events, isErasing }: TLComponentProps) => {

+ 0 - 1
tldraw/apps/tldraw-logseq/src/styles.css

@@ -558,7 +558,6 @@ button.tl-select-input-trigger {
   }
 
   &[data-recently-changed=true] {
-    transition-delay: 0.5s;
     i.tie {
       transition-delay: 0.5s;
     }

+ 26 - 16
tldraw/packages/core/src/utils/SvgPathUtils.ts

@@ -1,4 +1,3 @@
-import Vec from '@tldraw/vec'
 
 export class SvgPathUtils {
   static getCurvedPathForPolygon(points: number[][]) {
@@ -45,23 +44,34 @@ export class SvgPathUtils {
    * @param stroke ;
    */
   static getSvgPathFromStroke(points: number[][], closed = true): string {
-    if (!points.length) {
-      return ''
+    const len = points.length
+
+    if (len < 4) {
+      return ``
     }
 
-    const max = points.length - 1
+    let a = points[0]
+    let b = points[1]
+    const c = points[2]
+
+    let result = `M${a[0].toFixed(2)},${a[1].toFixed(2)} Q${b[0].toFixed(2)},${b[1].toFixed(
+      2
+    )} ${average(b[0], c[0]).toFixed(2)},${average(b[1], c[1]).toFixed(2)} T`
+
+    for (let i = 2, max = len - 1; i < max; i++) {
+      a = points[i]
+      b = points[i + 1]
+      result += `${average(a[0], b[0]).toFixed(2)},${average(a[1], b[1]).toFixed(2)} `
+    }
 
-    return points
-      .reduce(
-        (acc, point, i, arr) => {
-          if (i === max) {
-            if (closed) acc.push('Z')
-          } else acc.push(point, Vec.med(point, arr[i + 1]))
-          return acc
-        },
-        ['M', points[0], 'Q']
-      )
-      .join(' ')
-      .replaceAll(this.TRIM_NUMBERS, '$1')
+    if (closed) {
+      result += 'Z'
+    }
+
+    return result
   }
 }
+
+function average(a: number, b: number): number {
+  return (a + b) / 2
+}