From b299d32fc360db9c1a862de01c6e491be6fbb30b Mon Sep 17 00:00:00 2001 From: Jeff Avallone Date: Sat, 26 Jan 2019 17:25:38 -0500 Subject: [PATCH] Adding a layout pass to SVG image components text nodes are the only elements that need to be "measured". The dimensions of all other image components can be determined based on the dimensions of their children. This adds a pre-rendering pass to work out dimensions so multiple renders don't need to happen --- src/components/App/__snapshots__/test.js.snap | 8 ++--- src/components/App/index.js | 8 ++--- src/components/App/test.js | 1 + src/components/Render/index.js | 16 +++++++--- src/layout.js | 22 +++++++++++++ src/rendering/SVG/index.js | 23 +++++--------- src/rendering/SVG/layout.js | 12 +++++++ src/rendering/Text/index.js | 31 ++----------------- src/rendering/Text/layout.js | 27 ++++++++++++++++ src/syntax/js.js | 2 ++ src/syntax/pcre.js | 2 ++ 11 files changed, 97 insertions(+), 55 deletions(-) create mode 100644 src/layout.js create mode 100644 src/rendering/SVG/layout.js create mode 100644 src/rendering/Text/layout.js diff --git a/src/components/App/__snapshots__/test.js.snap b/src/components/App/__snapshots__/test.js.snap index c84d246..6304af7 100644 --- a/src/components/App/__snapshots__/test.js.snap +++ b/src/components/App/__snapshots__/test.js.snap @@ -22,8 +22,8 @@ exports[`App removing rendered expression 1`] = ` `; @@ -139,8 +139,8 @@ exports[`App rendering an expression 3`] = ` `; @@ -167,8 +167,8 @@ exports[`App rendering image details 1`] = ` `; @@ -201,8 +201,8 @@ exports[`App rendering image details 2`] = ` /> `; diff --git a/src/components/App/index.js b/src/components/App/index.js index df11c78..95c3683 100644 --- a/src/components/App/index.js +++ b/src/components/App/index.js @@ -73,13 +73,13 @@ class App extends React.PureComponent { `syntax/${ syntax }` ); - const parsed = syntaxModule.parse(expr); + const exprData = syntaxModule.layout(syntaxModule.parse(expr)); this.setState({ loading: false, render: { syntax, - parsed, + exprData, Component: syntaxModule.Render } }); @@ -119,7 +119,7 @@ class App extends React.PureComponent { imageDetails, render: { syntax: renderSyntax, - parsed, + exprData, Component } } = this.state; @@ -137,7 +137,7 @@ class App extends React.PureComponent { }; const renderProps = { onRender: this.handleSvg, - parsed + data: exprData }; const doRender = renderSyntax === syntax; diff --git a/src/components/App/test.js b/src/components/App/test.js index 3d66b22..982ff1c 100644 --- a/src/components/App/test.js +++ b/src/components/App/test.js @@ -6,6 +6,7 @@ import { App } from 'components/App'; jest.mock('syntax/js', () => ({ parse: expr => `PARSED(${ expr })`, + layout: parsed => `LAYOUT(${ parsed })`, Render: () => '' })); diff --git a/src/components/Render/index.js b/src/components/Render/index.js index 1e1c790..73ac638 100644 --- a/src/components/Render/index.js +++ b/src/components/Render/index.js @@ -28,13 +28,21 @@ const render = (data, extraProps) => { class Render extends React.PureComponent { static propTypes = { - parsed: PropTypes.object.isRequired, + data: PropTypes.object.isRequired, onRender: PropTypes.func.isRequired } svgContainer = React.createRef() - provideSVGData = () => { + componentDidMount() { + this.provideSVGData(); + } + + componentDidUpdate() { + this.provideSVGData(); + } + + provideSVGData() { if (!this.svgContainer.current) { return; } @@ -48,10 +56,10 @@ class Render extends React.PureComponent { } render() { - const { parsed } = this.props; + const { data } = this.props; return
- { render(parsed, { onReflow: this.provideSVGData }) } + { render(data, { onReflow: this.provideSVGData }) }
; } } diff --git a/src/layout.js b/src/layout.js new file mode 100644 index 0000000..6b05543 --- /dev/null +++ b/src/layout.js @@ -0,0 +1,22 @@ +import SVG from 'rendering/SVG/layout'; +import Text from 'rendering/Text/layout'; + +const nodeTypes = { + SVG, + Text +}; + +const layout = data => { + if (typeof data == 'string') { + return data; + } + + const { type } = data; + + return nodeTypes[type]({ + props: {}, + ...data + }); +}; + +export default layout; diff --git a/src/rendering/SVG/index.js b/src/rendering/SVG/index.js index 4db45d6..1eb24bd 100644 --- a/src/rendering/SVG/index.js +++ b/src/rendering/SVG/index.js @@ -24,7 +24,9 @@ class SVG extends React.PureComponent { static propTypes = { onReflow: PropTypes.func, children: PropTypes.node, - padding: PropTypes.number + padding: PropTypes.number, + innerWidth: PropTypes.number, + innerHeight: PropTypes.number } static defaultProps = { @@ -36,18 +38,11 @@ class SVG extends React.PureComponent { height: 0 } - handleReflow = box => { - const { padding } = this.props; - - this.setState({ - width: Math.round(box.width + 2 * padding), - height: Math.round(box.height + 2 * padding) - }, () => this.props.onReflow(this)); - } - render() { - const { width, height } = this.state; - const { padding, children } = this.props; + const { padding, innerWidth, innerHeight, children } = this.props; + + const width = Math.round(innerWidth + 2 * padding); + const height = Math.round(innerHeight + 2 * padding); const svgProps = { width, @@ -60,9 +55,7 @@ class SVG extends React.PureComponent { return - { React.cloneElement(React.Children.only(children), { - onReflow: this.handleReflow - }) } + { children } ; } diff --git a/src/rendering/SVG/layout.js b/src/rendering/SVG/layout.js new file mode 100644 index 0000000..1caf46d --- /dev/null +++ b/src/rendering/SVG/layout.js @@ -0,0 +1,12 @@ +import layout from 'layout'; + +const layoutSVG = data => { + const child = layout(data.children[0]); + + data.props.innerWidth = child.box.width; + data.props.innerHeight = child.box.height; + + return data; +}; + +export default layoutSVG; diff --git a/src/rendering/Text/index.js b/src/rendering/Text/index.js index 0b446fd..118ebcb 100644 --- a/src/rendering/Text/index.js +++ b/src/rendering/Text/index.js @@ -6,6 +6,7 @@ import * as style from './style'; class Text extends React.PureComponent { static propTypes = { quoted: PropTypes.bool, + transform: PropTypes.string, onReflow: PropTypes.func, children: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.node), @@ -13,31 +14,6 @@ class Text extends React.PureComponent { ]).isRequired } - state = { - transform: '' - } - - textRef = React.createRef() - - componentDidMount() { - this.reflow(); - } - - componentDidUpdate() { - this.reflow(); - } - - reflow() { - const box = this.textRef.current.getBBox(); - const transform = `translate(${ -box.x } ${ -box.y })`; - - if (transform === this.state.transform) { - return; // No update required - } - - this.setState({ transform }, () => this.props.onReflow(box)); - } - renderContent() { const { children, quoted } = this.props; @@ -53,12 +29,11 @@ class Text extends React.PureComponent { } render() { - const { transform } = this.state; + const { transform } = this.props; const textProps = { style: style.text, - transform, - ref: this.textRef + transform }; return diff --git a/src/rendering/Text/layout.js b/src/rendering/Text/layout.js new file mode 100644 index 0000000..cfe82e1 --- /dev/null +++ b/src/rendering/Text/layout.js @@ -0,0 +1,27 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import Text from 'rendering/Text'; + +const layoutText = data => { + const container = document.createElement('div'); + document.body.appendChild(container); + + ReactDOM.render( + + { data.children } + , + container); + + const box = container.querySelector('svg > text').getBBox(); + document.body.removeChild(container); + + data.box = { + width: box.width, + height: box.height + }; + data.props.transform = `translate(${ -box.x } ${ -box.y })`; + return data; +}; + +export default layoutText; diff --git a/src/syntax/js.js b/src/syntax/js.js index 7214bd3..a3c68bd 100644 --- a/src/syntax/js.js +++ b/src/syntax/js.js @@ -1,4 +1,5 @@ import Render from 'components/Render'; +import layout from 'layout'; const parse = expr => { return { @@ -19,5 +20,6 @@ const parse = expr => { export { parse, + layout, Render }; diff --git a/src/syntax/pcre.js b/src/syntax/pcre.js index a078762..997046a 100644 --- a/src/syntax/pcre.js +++ b/src/syntax/pcre.js @@ -1,4 +1,5 @@ import Render from 'components/Render'; +import layout from 'layout'; const parse = expr => { return { @@ -19,5 +20,6 @@ const parse = expr => { export { parse, + layout, Render };