tldraw
Version:
A tiny little drawing editor.
155 lines (136 loc) • 3.54 kB
text/typescript
import {
EASINGS,
PI,
SIN,
TLDefaultDashStyle,
TLDrawShape,
TLDrawShapeSegment,
Vec,
b64Vecs,
modulate,
} from '@tldraw/editor'
import { StrokeOptions } from '../shared/freehand/types'
const PEN_EASING = (t: number) => t * 0.65 + SIN((t * PI) / 2) * 0.35
const simulatePressureSettings = (strokeWidth: number): StrokeOptions => {
return {
size: strokeWidth,
thinning: 0.5,
streamline: modulate(strokeWidth, [9, 16], [0.64, 0.74], true), // 0.62 + ((1 + strokeWidth) / 8) * 0.06,
smoothing: 0.62,
easing: EASINGS.easeOutSine,
simulatePressure: true,
}
}
const realPressureSettings = (strokeWidth: number): StrokeOptions => {
return {
size: 1 + strokeWidth * 1.2,
thinning: 0.62,
streamline: 0.62,
smoothing: 0.62,
simulatePressure: false,
easing: PEN_EASING,
}
}
const solidSettings = (strokeWidth: number): StrokeOptions => {
return {
size: strokeWidth,
thinning: 0,
streamline: modulate(strokeWidth, [9, 16], [0.64, 0.74], true), // 0.62 + ((1 + strokeWidth) / 8) * 0.06,
smoothing: 0.62,
simulatePressure: false,
easing: EASINGS.linear,
}
}
const solidRealPressureSettings = (strokeWidth: number): StrokeOptions => {
return {
size: strokeWidth,
thinning: 0,
streamline: 0.62,
smoothing: 0.62,
simulatePressure: false,
easing: EASINGS.linear,
}
}
export function getHighlightFreehandSettings({
strokeWidth,
showAsComplete,
}: {
strokeWidth: number
showAsComplete: boolean
}): StrokeOptions {
return {
size: 1 + strokeWidth,
thinning: 0,
streamline: 0.5,
smoothing: 0.5,
simulatePressure: false,
easing: EASINGS.easeOutSine,
last: showAsComplete,
}
}
export function getFreehandOptions(
shapeProps: { dash: TLDefaultDashStyle; isPen: boolean; isComplete: boolean },
strokeWidth: number,
forceComplete: boolean,
forceSolid: boolean
): StrokeOptions {
const last = shapeProps.isComplete || forceComplete
if (forceSolid) {
if (shapeProps.isPen) {
return { ...solidRealPressureSettings(strokeWidth), last }
} else {
return { ...solidSettings(strokeWidth), last }
}
}
if (shapeProps.dash === 'draw') {
if (shapeProps.isPen) {
return { ...realPressureSettings(strokeWidth), last }
} else {
return { ...simulatePressureSettings(strokeWidth), last }
}
}
return { ...solidSettings(strokeWidth), last }
}
/** @public */
export function getPointsFromDrawSegment(
segment: TLDrawShapeSegment,
scaleX: number,
scaleY: number,
points: Vec[] = []
) {
const _points = b64Vecs.decodePoints(segment.path)
// Apply scale factors (used for lazy resize and flipping)
if (scaleX !== 1 || scaleY !== 1) {
for (const point of _points) {
point.x *= scaleX
point.y *= scaleY
}
}
if (segment.type === 'free' || _points.length < 2) {
points.push(..._points.map(Vec.From))
} else {
const pointsToInterpolate = Math.max(4, Math.floor(Vec.Dist(_points[0], _points[1]) / 16))
points.push(...Vec.PointsBetween(_points[0], _points[1], pointsToInterpolate))
}
return points
}
/** @public */
export function getPointsFromDrawSegments(segments: TLDrawShapeSegment[], scaleX = 1, scaleY = 1) {
const points: Vec[] = []
for (const segment of segments) {
getPointsFromDrawSegment(segment, scaleX, scaleY, points)
}
return points
}
export function getDrawShapeStrokeDashArray(
shape: TLDrawShape,
strokeWidth: number,
dotAdjustment: number
) {
return {
draw: 'none',
solid: `none`,
dotted: `${dotAdjustment} ${strokeWidth * 2}`,
dashed: `${strokeWidth * 2} ${strokeWidth * 2}`,
}[shape.props.dash]
}