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(
strokeWidth: number,
export function getHighlightFreehandSettings({
strokeWidth,
showAsComplete,
isPen,
}: {
strokeWidth: number
showAsComplete: boolean
): StrokeOptions {
isPen: boolean
}): StrokeOptions {
return {
size: 1 + strokeWidth,
thinning: 0.1,
streamline: 0.1, // 0.62 + ((1 + strokeWidth) / 8) * 0.06,
streamline: 0.5,
smoothing: 0.5,
simulatePressure: true,
easing: EASINGS.easeOutSine,
simulatePressure: !isPen,
easing: isPen ? PEN_EASING : EASINGS.easeOutSine,
last: showAsComplete,
}
}

View file

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

View file

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