highlighter fixes (#1530)

Fixes the following issues with highlighter:
* Exported highlighter has much larger stroke width than in-app
highlighter
* Selecting two highlighter shapes with different sizes would hide the
size option from the styles panel
* Highlighter lines drawn on ipad look noise-y

### Change Type

<!-- 💡 Indicate the type of change your pull request is. -->
<!-- 🤷‍♀️ If you're not sure, don't select anything -->
<!-- ✂️ Feel free to delete unselected options -->

<!-- To select one, put an x in the box: [x] -->

- [x] `patch` — Bug Fix
- [ ] `minor` — New Feature
- [ ] `major` — Breaking Change
- [ ] `dependencies` — Dependency Update (publishes a `patch` release,
for devDependencies use `internal`)
- [ ] `documentation` — Changes to the documentation only (will not
publish a new version)
- [ ] `tests` — Changes to any testing-related code only (will not
publish a new version)
- [ ] `internal` — Any other changes that don't affect the published
package (will not publish a new version)

### Test Plan

-

### Release Notes

[aq bug fixes]
This commit is contained in:
alex 2023-06-06 12:06:28 +01:00 committed by GitHub
parent d646994554
commit d9548556cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 51 additions and 76 deletions

View file

@ -36,17 +36,22 @@ const solidSettings = (strokeWidth: number): StrokeOptions => {
} }
} }
export function getHighlightFreehandSettings( export function getHighlightFreehandSettings({
strokeWidth: number, strokeWidth,
showAsComplete,
isPen,
}: {
strokeWidth: number
showAsComplete: boolean showAsComplete: boolean
): StrokeOptions { isPen: boolean
}): StrokeOptions {
return { return {
size: 1 + strokeWidth, size: 1 + strokeWidth,
thinning: 0.1, thinning: 0.1,
streamline: 0.1, // 0.62 + ((1 + strokeWidth) / 8) * 0.06, streamline: 0.5,
smoothing: 0.5, smoothing: 0.5,
simulatePressure: true, simulatePressure: !isPen,
easing: EASINGS.easeOutSine, easing: isPen ? PEN_EASING : EASINGS.easeOutSine,
last: showAsComplete, last: showAsComplete,
} }
} }

View file

@ -1,21 +1,12 @@
/* eslint-disable react-hooks/rules-of-hooks */ /* eslint-disable react-hooks/rules-of-hooks */
import { import { Box2d, getStrokePoints, linesIntersect, Vec2d, VecLike } from '@tldraw/primitives'
Box2d,
getStrokeOutlinePoints,
getStrokePoints,
linesIntersect,
setStrokePointRadii,
Vec2d,
VecLike,
} from '@tldraw/primitives'
import { TLDrawShapeSegment, TLHighlightShape } from '@tldraw/tlschema' import { TLDrawShapeSegment, TLHighlightShape } from '@tldraw/tlschema'
import { last, rng } from '@tldraw/utils' import { last, rng } from '@tldraw/utils'
import { SVGContainer } from '../../../components/SVGContainer' import { SVGContainer } from '../../../components/SVGContainer'
import { FONT_SIZES } from '../../../constants' import { FONT_SIZES } from '../../../constants'
import { getSvgPathFromStroke, getSvgPathFromStrokePoints } from '../../../utils/svg' import { getSvgPathFromStrokePoints } from '../../../utils/svg'
import { getHighlightFreehandSettings, getPointsFromSegments } from '../DrawShapeUtil/getPath' import { getHighlightFreehandSettings, getPointsFromSegments } from '../DrawShapeUtil/getPath'
import { ShapeUtil, TLOnResizeHandler } from '../ShapeUtil' import { ShapeUtil, TLOnResizeHandler } from '../ShapeUtil'
import { ShapeFill } from '../shared/ShapeFill'
import { TLExportColors } from '../shared/TLExportColors' import { TLExportColors } from '../shared/TLExportColors'
import { useForceSolid } from '../shared/useForceSolid' import { useForceSolid } from '../shared/useForceSolid'
@ -133,7 +124,11 @@ export class HighlightShapeUtil extends ShapeUtil<TLHighlightShape> {
} }
const showAsComplete = shape.props.isComplete || last(shape.props.segments)?.type === 'straight' const showAsComplete = shape.props.isComplete || last(shape.props.segments)?.type === 'straight'
const options = getHighlightFreehandSettings(strokeWidth, showAsComplete) const options = getHighlightFreehandSettings({
strokeWidth,
showAsComplete,
isPen: shape.props.isPen,
})
const strokePoints = getStrokePoints(allPointsFromSegments, options) const strokePoints = getStrokePoints(allPointsFromSegments, options)
let strokePath let strokePath
@ -202,6 +197,29 @@ function getIndicatorDot(point: VecLike, sw: number) {
},0` },0`
} }
function getHighlightSvgPath(shape: TLHighlightShape, strokeWidth: number, forceSolid: boolean) {
const allPointsFromSegments = getPointsFromSegments(shape.props.segments)
const showAsComplete = shape.props.isComplete || last(shape.props.segments)?.type === 'straight'
let sw = strokeWidth
if (!forceSolid && !shape.props.isPen && allPointsFromSegments.length === 1) {
sw += rng(shape.id)() * (strokeWidth / 6)
}
const options = getHighlightFreehandSettings({
strokeWidth: sw,
showAsComplete,
isPen: shape.props.isPen,
})
const strokePoints = getStrokePoints(allPointsFromSegments, options)
const solidStrokePath =
strokePoints.length > 1
? getSvgPathFromStrokePoints(strokePoints, false)
: getShapeDot(allPointsFromSegments[0])
return { solidStrokePath, sw }
}
function HighlightRenderer({ function HighlightRenderer({
strokeWidth, strokeWidth,
shape, shape,
@ -212,51 +230,17 @@ function HighlightRenderer({
opacity?: number opacity?: number
}) { }) {
const forceSolid = useForceSolid() const forceSolid = useForceSolid()
const allPointsFromSegments = getPointsFromSegments(shape.props.segments) const { solidStrokePath, sw } = getHighlightSvgPath(shape, strokeWidth, forceSolid)
const showAsComplete = shape.props.isComplete || last(shape.props.segments)?.type === 'straight'
let sw = strokeWidth
if (!forceSolid && !shape.props.isPen && allPointsFromSegments.length === 1) {
sw += rng(shape.id)() * (strokeWidth / 6)
}
const options = getHighlightFreehandSettings(sw, showAsComplete)
const strokePoints = getStrokePoints(allPointsFromSegments, options)
const solidStrokePath =
strokePoints.length > 1
? getSvgPathFromStrokePoints(strokePoints, false)
: getShapeDot(allPointsFromSegments[0])
if (!forceSolid || strokePoints.length < 2) {
setStrokePointRadii(strokePoints, options)
return (
<SVGContainer id={shape.id} style={{ opacity }}>
<path
d={solidStrokePath}
strokeLinecap="round"
fill="none"
pointerEvents="all"
stroke={`var(--palette-${shape.props.color}-highlight)`}
strokeWidth={sw}
/>
</SVGContainer>
)
}
return ( return (
<SVGContainer id={shape.id} style={{ opacity }}> <SVGContainer id={shape.id} style={{ opacity }}>
<ShapeFill fill="none" color={shape.props.color} d={solidStrokePath} />
<path <path
d={solidStrokePath} d={solidStrokePath}
strokeLinecap="round" strokeLinecap="round"
fill="none" fill="none"
pointerEvents="all"
stroke={`var(--palette-${shape.props.color}-highlight)`} stroke={`var(--palette-${shape.props.color}-highlight)`}
strokeWidth={strokeWidth} strokeWidth={sw}
strokeDashoffset="0"
/> />
</SVGContainer> </SVGContainer>
) )
@ -268,28 +252,14 @@ function highlighterToSvg(
opacity: number, opacity: number,
colors: TLExportColors colors: TLExportColors
) { ) {
const { color } = shape.props const { solidStrokePath, sw } = getHighlightSvgPath(shape, strokeWidth, false)
const allPointsFromSegments = getPointsFromSegments(shape.props.segments)
const showAsComplete = shape.props.isComplete || last(shape.props.segments)?.type === 'straight'
let sw = strokeWidth
if (!shape.props.isPen && allPointsFromSegments.length === 1) {
sw += rng(shape.id)() * (strokeWidth / 6)
}
const options = getHighlightFreehandSettings(sw, showAsComplete)
const strokePoints = getStrokePoints(allPointsFromSegments, options)
setStrokePointRadii(strokePoints, options)
const strokeOutlinePoints = getStrokeOutlinePoints(strokePoints, options)
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path') const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
path.setAttribute('d', getSvgPathFromStroke(strokeOutlinePoints, true)) path.setAttribute('d', solidStrokePath)
path.setAttribute('fill', colors.highlight[color]) path.setAttribute('fill', 'none')
path.setAttribute('stroke-linecap', 'round') path.setAttribute('stroke', colors.highlight[shape.props.color])
path.setAttribute('opacity', opacity.toString()) path.setAttribute('stroke-width', `${sw}`)
path.setAttribute('opacity', `${opacity}`)
return path return path
} }

View file

@ -92,7 +92,7 @@ function CommonStylePickerSet({ props }: { props: TLNullableShapeProps }) {
return null return null
} }
const showPickers = fill || dash || size const showPickers = fill !== undefined || dash !== undefined || size !== undefined
const opacityIndex = styles.opacity.findIndex((s) => s.id === opacity) const opacityIndex = styles.opacity.findIndex((s) => s.id === opacity)