|
1 | 1 | import { SafePathNode } from '@react-pdf/layout'; |
| 2 | +import absPath from 'abs-svg-path'; |
| 3 | +import parsePath from 'parse-svg-path'; |
| 4 | + |
2 | 5 | import { Context } from '../types'; |
3 | 6 |
|
| 7 | +/** |
| 8 | + * pdfkit mishandles chained smooth quadratic commands (T): after drawing it |
| 9 | + * reflects the control point a second time, so every T after the first uses a |
| 10 | + * stale control point and curves render deformed. Expand T into explicit Q |
| 11 | + * commands with the correct reflection before handing the path to pdfkit. |
| 12 | + */ |
| 13 | +type Segment = (string | number)[]; |
| 14 | + |
| 15 | +const expandSmoothQuadratics = (d: string): string => { |
| 16 | + const segments: Segment[] = absPath(parsePath(d)); |
| 17 | + |
| 18 | + let x = 0; |
| 19 | + let y = 0; |
| 20 | + let startX = 0; |
| 21 | + let startY = 0; |
| 22 | + let quadX: number | null = null; |
| 23 | + let quadY: number | null = null; |
| 24 | + |
| 25 | + const out = segments.map((segment: Segment) => { |
| 26 | + let seg = segment; |
| 27 | + const command = seg[0]; |
| 28 | + |
| 29 | + if (command === 'T') { |
| 30 | + const cx = quadX === null ? x : 2 * x - quadX; |
| 31 | + const cy = quadY === null ? y : 2 * y - quadY; |
| 32 | + seg = ['Q', cx, cy, seg[1], seg[2]]; |
| 33 | + quadX = cx; |
| 34 | + quadY = cy; |
| 35 | + } else if (command === 'Q') { |
| 36 | + quadX = seg[1] as number; |
| 37 | + quadY = seg[2] as number; |
| 38 | + } else { |
| 39 | + quadX = null; |
| 40 | + quadY = null; |
| 41 | + } |
| 42 | + |
| 43 | + if (command === 'M') { |
| 44 | + startX = seg[1] as number; |
| 45 | + startY = seg[2] as number; |
| 46 | + } |
| 47 | + |
| 48 | + if (command === 'H') { |
| 49 | + x = seg[1] as number; |
| 50 | + } else if (command === 'V') { |
| 51 | + y = seg[1] as number; |
| 52 | + } else if (command === 'Z') { |
| 53 | + x = startX; |
| 54 | + y = startY; |
| 55 | + } else { |
| 56 | + x = seg[seg.length - 2] as number; |
| 57 | + y = seg[seg.length - 1] as number; |
| 58 | + } |
| 59 | + |
| 60 | + return seg; |
| 61 | + }); |
| 62 | + |
| 63 | + return out.map((seg: Segment) => seg[0] + seg.slice(1).join(' ')).join(''); |
| 64 | +}; |
| 65 | + |
4 | 66 | const renderPath = (ctx: Context, node: SafePathNode) => { |
5 | 67 | const d = node.props?.d; |
6 | 68 |
|
7 | | - if (d) ctx.path(node.props.d); |
| 69 | + if (!d) return; |
| 70 | + |
| 71 | + ctx.path(/[Tt]/.test(d) ? expandSmoothQuadratics(d) : d); |
8 | 72 | }; |
9 | 73 |
|
10 | 74 | export default renderPath; |
0 commit comments