diff --git a/LICENSE b/LICENSE index 57d6b4ac9..bdcc8b850 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Chris Hager +Copyright (c) 2021 Stephen Ruiz Ltd Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/core/LICENSE b/packages/core/LICENSE new file mode 100644 index 000000000..bdcc8b850 --- /dev/null +++ b/packages/core/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Stephen Ruiz Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/core/src/utils/utils.ts b/packages/core/src/utils/utils.ts index 289140cff..0a43aa327 100644 --- a/packages/core/src/utils/utils.ts +++ b/packages/core/src/utils/utils.ts @@ -1656,7 +1656,7 @@ left past the initial left edge) then swap points on that axis. * Turn an array of points into a path of quadradic curves. * @param stroke ; */ - static getSvgPathFromStroke(points: number[][]): string { + static getSvgPathFromStroke(points: number[][], closed = true): string { if (!points.length) { return '' } @@ -1667,7 +1667,9 @@ left past the initial left edge) then swap points on that axis. .reduce( (acc, point, i, arr) => { if (i === max) { - acc.push('Z') + if (closed) { + acc.push('Z') + } } else { acc.push(point, Vec.med(point, arr[i + 1])) } diff --git a/packages/dev/LICENSE b/packages/dev/LICENSE new file mode 100644 index 000000000..bdcc8b850 --- /dev/null +++ b/packages/dev/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Stephen Ruiz Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/intersect/LICENSE b/packages/intersect/LICENSE new file mode 100644 index 000000000..bdcc8b850 --- /dev/null +++ b/packages/intersect/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Stephen Ruiz Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/tldraw/LICENSE b/packages/tldraw/LICENSE new file mode 100644 index 000000000..bdcc8b850 --- /dev/null +++ b/packages/tldraw/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Stephen Ruiz Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/tldraw/package.json b/packages/tldraw/package.json index 23e951de8..6e1c81cfd 100644 --- a/packages/tldraw/package.json +++ b/packages/tldraw/package.json @@ -67,9 +67,9 @@ "@tldraw/core": "^0.0.102", "@tldraw/intersect": "^0.0.102", "@tldraw/vec": "^0.0.102", - "perfect-freehand": "^1.0.9", + "perfect-freehand": "^1.0.12", "react-hotkeys-hook": "^3.4.0", "rko": "^0.5.25" }, "gitHead": "5cb031ddc264846ec6732d7179511cddea8ef034" -} +} \ No newline at end of file diff --git a/packages/tldraw/src/shape/shapes/draw/draw.tsx b/packages/tldraw/src/shape/shapes/draw/draw.tsx index ba55b9167..5c45d6598 100644 --- a/packages/tldraw/src/shape/shapes/draw/draw.tsx +++ b/packages/tldraw/src/shape/shapes/draw/draw.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { SVGContainer, TLBounds, Utils, TLTransformInfo, ShapeUtil } from '@tldraw/core' import { Vec } from '@tldraw/vec' import { intersectBoundsBounds, intersectBoundsPolyline } from '@tldraw/intersect' -import getStroke, { getStrokePoints } from 'perfect-freehand' +import getStroke, { getStrokeOutlinePoints, getStrokePoints } from 'perfect-freehand' import { defaultStyle, getShapeStyle } from '~shape/shape-styles' import { DrawShape, DashStyle, TLDrawShapeType, TLDrawToolType, TLDrawMeta } from '~types' import { EASINGS } from '~state/utils' @@ -38,8 +38,8 @@ export const Draw = new ShapeUtil(() => ({ const pathData = React.useMemo(() => { return style.dash === DashStyle.Draw - ? getDrawStrokePath(shape, isEditing) - : getSolidStrokePath(shape) + ? getDrawStrokePathData(shape, isEditing) + : getSolidStrokePathData(shape, isEditing) }, [points, style.size, style.dash, isEditing]) const styles = getShapeStyle(style, meta.isDarkMode) @@ -89,7 +89,7 @@ export const Draw = new ShapeUtil(() => ({ d={pathData} fill={styles.stroke} stroke={styles.stroke} - strokeWidth={strokeWidth} + strokeWidth={styles.strokeWidth} strokeLinejoin="round" strokeLinecap="round" pointerEvents="all" @@ -146,7 +146,7 @@ export const Draw = new ShapeUtil(() => ({ const { points } = shape const pathData = React.useMemo(() => { - return getSolidStrokePath(shape) + return getSolidStrokePathData(shape, false) }, [points]) const bounds = this.getBounds(shape) @@ -264,6 +264,8 @@ export const Draw = new ShapeUtil(() => ({ /* Helpers */ /* -------------------------------------------------- */ +const STREAMLINE = 0.65 + const simulatePressureSettings = { simulatePressure: true, } @@ -288,21 +290,34 @@ function getFillPath(shape: DrawShape) { thinning: 0.85, end: { taper: +styles.strokeWidth * 10 }, start: { taper: +styles.strokeWidth * 10 }, + last: true, }).map((pt) => pt.point) ) } -function getDrawStrokePath(shape: DrawShape, isEditing: boolean) { +function getDrawStrokePoints(shape: DrawShape, isEditing: boolean) { + return getStrokePoints(shape.points, { + streamline: STREAMLINE, + last: !isEditing, + }) +} + +/** + * Get path data for a stroke with the DashStyle.Draw dash style. + */ +function getDrawStrokePathData(shape: DrawShape, isEditing: boolean) { const styles = getShapeStyle(shape.style) if (shape.points.length < 2) return '' const options = shape.points[1][2] === 0.5 ? simulatePressureSettings : realPressureSettings - const stroke = getStroke(shape.points.slice(2), { + const strokePoints = getDrawStrokePoints(shape, isEditing) + + const stroke = getStrokeOutlinePoints(strokePoints, { size: 1 + styles.strokeWidth * 1.618, thinning: 0.6, - streamline: 0.7, + streamline: STREAMLINE, smoothing: 0.5, end: { taper: styles.strokeWidth * 10, easing: EASINGS.easeOutQuad }, easing: (t) => Math.sin((t * Math.PI) / 2), @@ -315,33 +330,17 @@ function getDrawStrokePath(shape: DrawShape, isEditing: boolean) { return path } -function getSolidStrokePath(shape: DrawShape) { - let { points } = shape +/** + * Get SVG path data for a shape that has a DashStyle other than DashStyles.Draw. + */ +function getSolidStrokePathData(shape: DrawShape, isEditing: boolean) { + const { points } = shape - let len = points.length + if (points.length === 0) return 'M 0 0 L 0 0' - if (len === 0) return 'M 0 0 L 0 0' - if (len < 3) return `M ${points[0][0]} ${points[0][1]}` + const strokePoints = getDrawStrokePoints(shape, isEditing).map((pt) => pt.point.slice(0, 2)) - points = getStrokePoints(points).map((pt) => pt.point) - - len = points.length - - const d = points.reduce( - (acc, [x0, y0], i, arr) => { - if (i === len - 1) { - acc.push('L', x0, y0) - return acc - } - - const [x1, y1] = arr[i + 1] - acc.push(x0.toFixed(2), y0.toFixed(2), ((x0 + x1) / 2).toFixed(2), ((y0 + y1) / 2).toFixed(2)) - return acc - }, - ['M', points[0][0], points[0][1], 'Q'] - ) - - const path = d.join(' ').replaceAll(/(\s[0-9]*\.[0-9]{2})([0-9]*)\b/g, '$1') + const path = Utils.getSvgPathFromStroke(strokePoints, false) return path } diff --git a/packages/tldraw/src/shape/shapes/rectangle/rectangle.tsx b/packages/tldraw/src/shape/shapes/rectangle/rectangle.tsx index 5ab685c25..846bbcca1 100644 --- a/packages/tldraw/src/shape/shapes/rectangle/rectangle.tsx +++ b/packages/tldraw/src/shape/shapes/rectangle/rectangle.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { Utils, SVGContainer, ShapeUtil } from '@tldraw/core' import { Vec } from '@tldraw/vec' -import getStroke from 'perfect-freehand' +import getStroke, { getStrokePoints } from 'perfect-freehand' import { getPerfectDashProps, defaultStyle, getShapeStyle } from '~shape/shape-styles' import { RectangleShape, DashStyle, TLDrawShapeType, TLDrawToolType, TLDrawMeta } from '~types' import { getBoundsRectangle, transformRectangle, transformSingleRectangle } from '../shared' @@ -37,8 +37,6 @@ export const Rectangle = new ShapeUtil getRectanglePath(shape)) @@ -136,26 +134,7 @@ export const Rectangle = new ShapeUtil - ) + return }, getBounds(shape) { @@ -171,7 +150,7 @@ export const Rectangle = new ShapeUtil pt.point.slice(0, 2)), + false + ) +} diff --git a/packages/vec/LICENSE b/packages/vec/LICENSE new file mode 100644 index 000000000..bdcc8b850 --- /dev/null +++ b/packages/vec/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Stephen Ruiz Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/www/LICENSE b/packages/www/LICENSE new file mode 100644 index 000000000..bdcc8b850 --- /dev/null +++ b/packages/www/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Stephen Ruiz Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/yarn.lock b/yarn.lock index 577cd88ca..aa7989785 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10906,10 +10906,10 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" -perfect-freehand@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/perfect-freehand/-/perfect-freehand-1.0.9.tgz#9b5fb96181004bcec62f3721d25cebea2ec34a3f" - integrity sha512-YCbnozlv+Wqmxexi/3YDftCXNjqs6bpTZ01tN+pBJbiA3b8DTvgZGPgd8mxxaXkAQzEsgD0bkS1h9vy3EWnm2w== +perfect-freehand@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/perfect-freehand/-/perfect-freehand-1.0.12.tgz#5f3614e099ad459ced4f50da9ce109fdf5b62b90" + integrity sha512-tY6ZVUbF672WJdHQMSz+YT45WgkeoLtbD5oe0TQNAZWCd4xD8B0Pcv+3URMEqhErR4JenRgSlV4kT7zHsbvzVg== performance-now@^2.1.0: version "2.1.0"