Use smarter rounding for shape container div width/height (#1930)

This is a follow up to #1915 which caused the shape container div
dimensions to be wildly inaccurate. We thought it wouldn't matter but we
had a note on discord from someone who was relying on the div container
being accurate.

This rounds the shape dimensions to the nearest integer that is
compatible with the user's device pixel ratio, using the method
pioneered in #1858 (thanks @BrianHung)

### Change Type

- [x] `patch` — Bug fix
- [ ] `minor` — New feature
- [ ] `major` — Breaking change
- [ ] `dependencies` — Changes to package dependencies[^1]
- [ ] `documentation` — Changes to the documentation only[^2]
- [ ] `tests` — Changes to any test code only[^2]
- [ ] `internal` — Any other changes that don't affect the published
package[^2]
- [ ] I don't know

[^1]: publishes a `patch` release, for devDependencies use `internal`
[^2]: will not publish a new version


### Release Notes

- Improves the precision of the shape dimensions rounding logic
This commit is contained in:
David Sheldrick 2023-09-19 14:14:51 +01:00 committed by GitHub
parent 5dc1436d80
commit 0e621e3de1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 15 additions and 9 deletions

View file

@ -2,6 +2,7 @@ import { track, useQuickReactor, useStateTracking } from '@tldraw/state'
import { TLShape, TLShapeId } from '@tldraw/tlschema' import { TLShape, TLShapeId } from '@tldraw/tlschema'
import * as React from 'react' import * as React from 'react'
import { ShapeUtil } from '../editor/shapes/ShapeUtil' import { ShapeUtil } from '../editor/shapes/ShapeUtil'
import { nearestMultiple } from '../hooks/useDPRMultiple'
import { useEditor } from '../hooks/useEditor' import { useEditor } from '../hooks/useEditor'
import { useEditorComponents } from '../hooks/useEditorComponents' import { useEditorComponents } from '../hooks/useEditorComponents'
import { Matrix2d } from '../primitives/Matrix2d' import { Matrix2d } from '../primitives/Matrix2d'
@ -79,14 +80,18 @@ export const Shape = track(function Shape({
if (!shape) return null if (!shape) return null
const bounds = editor.getShapeGeometry(shape).bounds const bounds = editor.getShapeGeometry(shape).bounds
setProperty( const dpr = editor.instanceState.devicePixelRatio
'width', // dprMultiple is the smallest number we can multiply dpr by to get an integer
`calc(${Math.max(1, Math.ceil(bounds.width)) + 'px'} * var(--tl-dpr-multiple))` // it's usually 1, 2, or 4 (for e.g. dpr of 2, 2.5 and 2.25 respectively)
) const dprMultiple = nearestMultiple(dpr)
setProperty( // We round the shape width and height up to the nearest multiple of dprMultiple to avoid the browser
'height', // making miscalculations when applying the transform.
`calc(${Math.max(1, Math.ceil(bounds.height)) + 'px'} * var(--tl-dpr-multiple))` const widthRemainder = bounds.w % dprMultiple
) const width = widthRemainder === 0 ? bounds.w : bounds.w + (dprMultiple - widthRemainder)
const heightRemainder = bounds.h % dprMultiple
const height = heightRemainder === 0 ? bounds.h : bounds.h + (dprMultiple - heightRemainder)
setProperty('width', Math.max(width, dprMultiple) + 'px')
setProperty('height', Math.max(height, dprMultiple) + 'px')
}, },
[editor] [editor]
) )

View file

@ -8,7 +8,8 @@ function gcd(a: number, b: number): number {
return b === 0 ? a : gcd(b, a % b) return b === 0 ? a : gcd(b, a % b)
} }
function nearestMultiple(float: number) { // Returns the lowest value that the given number can be multiplied by to reach an integer
export function nearestMultiple(float: number) {
const decimal = float.toString().split('.')[1] const decimal = float.toString().split('.')[1]
if (!decimal) return 1 if (!decimal) return 1
const denominator = Math.pow(10, decimal.length) const denominator = Math.pow(10, decimal.length)