toSvg method example (#4124)
An example of how to use the toSvg method on custom shape. Resolves #4057 ### Change type - [ ] `bugfix` - [ ] `improvement` - [ ] `feature` - [ ] `api` - [x] `other` ### Test plan 1. Create a shape... 2. - [ ] Unit tests - [ ] End to end tests ### Release notes - [Examples App] added an example for the toSvg method on a custom shape.
This commit is contained in:
parent
fe3dc0d965
commit
c5b2569bfc
2 changed files with 157 additions and 0 deletions
|
@ -0,0 +1,144 @@
|
||||||
|
import { ReactElement } from 'react'
|
||||||
|
import {
|
||||||
|
Geometry2d,
|
||||||
|
HTMLContainer,
|
||||||
|
RecordProps,
|
||||||
|
Rectangle2d,
|
||||||
|
ShapeUtil,
|
||||||
|
SvgExportContext,
|
||||||
|
T,
|
||||||
|
TLBaseShape,
|
||||||
|
Tldraw,
|
||||||
|
} from 'tldraw'
|
||||||
|
|
||||||
|
import 'tldraw/tldraw.css'
|
||||||
|
|
||||||
|
// There's a guide at the bottom of this file!
|
||||||
|
|
||||||
|
type ICustomShape = TLBaseShape<
|
||||||
|
'my-custom-shape',
|
||||||
|
{
|
||||||
|
w: number
|
||||||
|
h: number
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
|
const LIGHT_FILL = '#ff8888'
|
||||||
|
const DARK_FILL = '#ffcccc'
|
||||||
|
|
||||||
|
export class MyShapeUtil extends ShapeUtil<ICustomShape> {
|
||||||
|
static override type = 'my-custom-shape' as const
|
||||||
|
static override props: RecordProps<ICustomShape> = {
|
||||||
|
w: T.number,
|
||||||
|
h: T.number,
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultProps(): ICustomShape['props'] {
|
||||||
|
return {
|
||||||
|
w: 200,
|
||||||
|
h: 200,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override canEdit = () => false
|
||||||
|
override canResize = () => false
|
||||||
|
override isAspectRatioLocked = () => false
|
||||||
|
|
||||||
|
getGeometry(shape: ICustomShape): Geometry2d {
|
||||||
|
return new Rectangle2d({
|
||||||
|
width: shape.props.w,
|
||||||
|
height: shape.props.h,
|
||||||
|
isFilled: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
component(_shape: ICustomShape) {
|
||||||
|
const isDarkmode = this.editor.user.getIsDarkMode()
|
||||||
|
return <HTMLContainer style={{ backgroundColor: isDarkmode ? DARK_FILL : LIGHT_FILL }} />
|
||||||
|
}
|
||||||
|
|
||||||
|
indicator(shape: ICustomShape) {
|
||||||
|
return this.getSvgRect(shape)
|
||||||
|
}
|
||||||
|
|
||||||
|
// [1]
|
||||||
|
override toSvg(
|
||||||
|
shape: ICustomShape,
|
||||||
|
ctx: SvgExportContext
|
||||||
|
): ReactElement | null | Promise<ReactElement | null> {
|
||||||
|
// ctx.addExportDef(getFontDef(shape))
|
||||||
|
const isDarkmode = ctx.isDarkMode
|
||||||
|
const fill = isDarkmode ? DARK_FILL : LIGHT_FILL
|
||||||
|
return this.getSvgRect(shape, { fill })
|
||||||
|
}
|
||||||
|
|
||||||
|
getSvgRect(shape: ICustomShape, props?: { fill: string }) {
|
||||||
|
return <rect width={shape.props.w} height={shape.props.h} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
// [2]
|
||||||
|
|
||||||
|
// override toBackgroundSvg(
|
||||||
|
// shape: ICustomShape,
|
||||||
|
// ctx: SvgExportContext
|
||||||
|
// ): ReactElement | null | Promise<ReactElement | null> {
|
||||||
|
// const isDarkmode = ctx.isDarkMode
|
||||||
|
// const fill = isDarkmode ? '#333' : '#efefef'
|
||||||
|
// return <rect width={shape.props.w} height={shape.props.h} fill={fill} />
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// [3]
|
||||||
|
|
||||||
|
// function getFontDef(shape: ICustomShape): SvgExportDef {
|
||||||
|
// //
|
||||||
|
// return {
|
||||||
|
// some unique key,
|
||||||
|
// key: 'my-custom-shape-font',
|
||||||
|
// getElement: async () => {
|
||||||
|
// return <style></style> element
|
||||||
|
// check out the defaultStyleDefs.tsx file for an example of how
|
||||||
|
// we do this for tldraw fonts
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
const customShape = [MyShapeUtil]
|
||||||
|
export default function CustomShapeToSvgExample() {
|
||||||
|
return (
|
||||||
|
<div className="tldraw__editor">
|
||||||
|
<Tldraw
|
||||||
|
shapeUtils={customShape}
|
||||||
|
onMount={(editor) => {
|
||||||
|
editor.createShape({ type: 'my-custom-shape', x: 100, y: 100 })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
The "export as SVG/PNG" and "copy as SVG/PNG" actions use the `toSvg` or `toBackgroundSvg`
|
||||||
|
methods of a shape util. If a shape does not have a `toSvg` or `toBackgroundSvg` method
|
||||||
|
defined, it will default to an empty box.
|
||||||
|
|
||||||
|
For more information on creating a custom shape, check out the custom shape example.
|
||||||
|
|
||||||
|
[1]
|
||||||
|
This method should return a React element that represents the shape as an SVG element.
|
||||||
|
If your shape is HTML, then you will need to convert it to an SVG representation. In this
|
||||||
|
example we've used a `rect` element to represent the shape. Other shapes may require more
|
||||||
|
complex work to render them as SVGs, especially if they contain text. Check out [3] for more
|
||||||
|
info.
|
||||||
|
|
||||||
|
[2]
|
||||||
|
The `toBackgroundSvg` method is used to render a layer behind the shape when exporting as SVG.
|
||||||
|
We use this in the tldraw codebase to make the highlighter shape. It's commented out here as
|
||||||
|
we don't need it for this example.
|
||||||
|
|
||||||
|
[3]
|
||||||
|
If your shape contains text, you may need to add a font definition to the SVG. This is done
|
||||||
|
using the `addExportDef` method of the `SvgExportContext`. Your font def must contain a unique
|
||||||
|
key and a function that returns a React element. Check out the `getFontDefForExport` function
|
||||||
|
in the `defaultStyleDefs.tsx` file for an example of how this is done for tldraw fonts.
|
||||||
|
|
||||||
|
*/
|
13
apps/examples/src/examples/toSvg-method-example/README.md
Normal file
13
apps/examples/src/examples/toSvg-method-example/README.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
title: Shape toSvg method
|
||||||
|
component: ./CustomShapeToSvgExample.tsx
|
||||||
|
category: shapes/tools
|
||||||
|
priority: 3
|
||||||
|
keywords: [basic, svg, custom, export, copy]
|
||||||
|
---
|
||||||
|
|
||||||
|
How to use the toSvg method of a shape util to determine how your custom shapes look when copied/exported as an image.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The "export as SVG/PNG" and "copy as SVG/PNG" actions use the `toSvg` or `toBackgroundSvg` methods of a shape util. If a shape does not have a `toSvg` or `toBackgroundSvg` method defined, it will default to an empty box.
|
Loading…
Reference in a new issue