Fix exporting of cropped images. (#2268)

Open this PR in different browsers, and you will see that the svg below
will render differently in different browsers. The svg was exported from
our staging.

Seems like Chrome handles `clip-path` when set via the style differently
than Safari and Firefox. I've reworked the logic so that it now uses a
`clip-path` definition and applies that to the image.


![shape_xPSLLIG9yQkqAACrv1OxE](https://github.com/tldraw/tldraw/assets/2523721/4d0baba3-f5bf-4e78-96fe-aaa91ead107f)

Also fixes a bunch of issues when copy pasting via the menu. It seems
like we can't store the write function to a variable:

![image](https://github.com/tldraw/tldraw/assets/2523721/8d38edaf-8d63-462b-9f1a-c38960add7d7)


Fixes https://github.com/tldraw/tldraw/issues/2254

### Before
![CleanShot 2023-11-30 at 09 30
24](https://github.com/tldraw/tldraw/assets/2523721/93c06d5f-bfb2-4d97-8819-a8560c770304)

### After
![CleanShot 2023-11-30 at 09 30
55](https://github.com/tldraw/tldraw/assets/2523721/29e0f699-d0e8-4fb2-b90e-1d14ee609e52)

### 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

### Test Plan

1. Add an image, apply crop to it (best if you crop from all sides, just
to make sure)
2. Copy as png and make sure the image is correctly cropped in the
created image.


### Release Notes

- Fix exporting of cropped images.
This commit is contained in:
Mitja Bezenšek 2023-12-05 16:15:05 +01:00 committed by GitHub
parent 6f59e54da6
commit dcf2ad9820
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 45 additions and 29 deletions

View file

@ -186,14 +186,29 @@ export class ImageShapeUtil extends BaseBoxShapeUtil<TLImageShape> {
const crop = shape.props.crop
if (containerStyle.transform && crop) {
const { transform, width, height } = containerStyle
const croppedWidth = (crop.bottomRight.x - crop.topLeft.x) * width
const croppedHeight = (crop.bottomRight.y - crop.topLeft.y) * height
const points = [
new Vec2d(crop.topLeft.x * width, crop.topLeft.y * height),
new Vec2d(crop.bottomRight.x * width, crop.topLeft.y * height),
new Vec2d(crop.bottomRight.x * width, crop.bottomRight.y * height),
new Vec2d(crop.topLeft.x * width, crop.bottomRight.y * height),
new Vec2d(0, 0),
new Vec2d(croppedWidth, 0),
new Vec2d(croppedWidth, croppedHeight),
new Vec2d(0, croppedHeight),
]
const polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon')
polygon.setAttribute('points', points.map((p) => `${p.x},${p.y}`).join(' '))
const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath')
clipPath.setAttribute('id', 'cropClipPath')
clipPath.appendChild(polygon)
const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs')
defs.appendChild(clipPath)
g.appendChild(defs)
const innerElement = document.createElementNS('http://www.w3.org/2000/svg', 'g')
innerElement.style.clipPath = `polygon(${points.map((p) => `${p.x}px ${p.y}px`).join(',')})`
innerElement.setAttribute('clip-path', 'url(#cropClipPath)')
image.setAttribute('width', width.toString())
image.setAttribute('height', height.toString())
image.style.transform = transform

View file

@ -23,7 +23,6 @@ export function copyAs(
// Note: it's important that this function itself isn't async - we need to create the relevant
// `ClipboardItem`s synchronously to make sure safari knows that the user _wants_ to copy
// See https://bugs.webkit.org/show_bug.cgi?id=222262
const write = window.navigator.clipboard?.write
return editor
.getSvg(ids?.length ? ids : [...editor.getCurrentPageShapeIds()], {
@ -39,8 +38,8 @@ export function copyAs(
switch (format) {
case 'svg': {
if (window.navigator.clipboard) {
if (write) {
write([
if (window.navigator.clipboard.write) {
window.navigator.clipboard.write([
new ClipboardItem({
'text/plain': new Blob([getSvgAsString(svg)], { type: 'text/plain' }),
}),
@ -71,27 +70,29 @@ export function copyAs(
})
const mimeType = format === 'jpeg' ? 'image/jpeg' : 'image/png'
if (write) {
write([
new ClipboardItem({
[mimeType]: blobPromise,
}),
]).catch((err: any) => {
// Firefox will fail with the above if `dom.events.asyncClipboard.clipboardItem` is enabled.
// See <https://github.com/tldraw/tldraw/issues/1325>
if (!err.toString().match(/^TypeError: DOMString not supported/)) {
console.error(err)
}
if (window.navigator.clipboard.write) {
window.navigator.clipboard
.write([
new ClipboardItem({
[mimeType]: blobPromise,
}),
])
.catch((err: any) => {
// Firefox will fail with the above if `dom.events.asyncClipboard.clipboardItem` is enabled.
// See <https://github.com/tldraw/tldraw/issues/1325>
if (!err.toString().match(/^TypeError: DOMString not supported/)) {
console.error(err)
}
blobPromise.then((blob) => {
window.navigator.clipboard.write([
new ClipboardItem({
// Note: This needs to use the promise based approach for safari/ios to not bail on a permissions error.
[mimeType]: blob,
}),
])
blobPromise.then((blob) => {
window.navigator.clipboard.write([
new ClipboardItem({
// Note: This needs to use the promise based approach for safari/ios to not bail on a permissions error.
[mimeType]: blob,
}),
])
})
})
})
}
break
@ -101,8 +102,8 @@ export function copyAs(
const data = editor.getContentFromCurrentPage(ids)
const jsonStr = JSON.stringify(data)
if (write) {
write([
if (window.navigator.clipboard.write) {
window.navigator.clipboard.write([
new ClipboardItem({
'text/plain': new Blob([jsonStr], { type: 'text/plain' }),
}),