diff --git a/bundle/latex2html5.bundle.js b/bundle/latex2html5.bundle.js index 0b608e7a..e8bc1f15 100644 --- a/bundle/latex2html5.bundle.js +++ b/bundle/latex2html5.bundle.js @@ -3302,6 +3302,23 @@ var LaTeX2HTML5 = (() => { } }; var psgraph = { + /** + * Maps a pointer's client position into the picture's own pixel space. A + * page may draw the svg smaller than its natural size (CSS `zoom` or a + * transform to fit a phone screen), and neither `offsetX` nor a raw + * `clientX - rect.left` accounts for that, so divide out the drawn scale. + * Measuring the svg itself, not the event target, also keeps a pointer over + * a child path from reading coordinates relative to that path. + */ + pointerCoords(svg, clientX, clientY) { + if (!svg) return [0, 0]; + const rect = svg.getBoundingClientRect(); + const width = parseFloat(svg.getAttribute("width") || "") || rect.width; + const height = parseFloat(svg.getAttribute("height") || "") || rect.height; + const scaleX = rect.width && width ? rect.width / width : 1; + const scaleY = rect.height && height ? rect.height / height : 1; + return [(clientX - rect.left) / scaleX, (clientY - rect.top) / scaleY]; + }, env: null, getSize() { const padding = 20; @@ -3832,16 +3849,14 @@ var LaTeX2HTML5 = (() => { function(event) { event.preventDefault(); var touch = event.touches ? event.touches[0] : null; - var rect = event.target.getBoundingClientRect(); - var touchcoords = touch ? [touch.clientX - rect.left, touch.clientY - rect.top] : [0, 0]; - drawLayer(touchcoords, { pointer: true }); + if (!touch) return; + drawLayer(psgraph.pointerCoords(svg.node(), touch.clientX, touch.clientY), { pointer: true }); } ); svg.on( "mousemove", function(event) { - var coords = [event.offsetX || 0, event.offsetY || 0]; - drawLayer(coords, { pointer: true }); + drawLayer(psgraph.pointerCoords(svg.node(), event.clientX, event.clientY), { pointer: true }); } ); psgraph.processRputElements.call(this, el); diff --git a/packages/html5/test/pspicture.test.ts b/packages/html5/test/pspicture.test.ts index 97fc2a7a..40a1a3e0 100644 --- a/packages/html5/test/pspicture.test.ts +++ b/packages/html5/test/pspicture.test.ts @@ -180,6 +180,31 @@ describe('pspicture component (SVG rendering)', () => { expect(shapeOrder(svg)).toEqual(before); }); + it('maps the pointer into picture space when the svg is drawn scaled down', () => { + // A page fitting a 200px picture onto a phone draws it at 100px (CSS zoom + // or a transform). The pointer's screen offset must be divided back out, + // or a dragged vector lands at half the distance from the finger. + const env = parsePspicture(` +\\begin{pspicture}(0,0)(4,4) +\\userline[linewidth=2pt]{->}(0,0)(2,2) +\\end{pspicture} + `); + + const div = pspicture(env); + document.body.appendChild(div); + const svg = div.querySelector('svg')!; + const natural = parseFloat(svg.getAttribute('width')!); + svg.getBoundingClientRect = () => + ({ left: 10, top: 20, width: natural / 2, height: natural / 2, right: 0, bottom: 0, x: 10, y: 20 }) as DOMRect; + + svg.dispatchEvent(new MouseEvent('mousemove', { bubbles: true, clientX: 10 + natural / 4, clientY: 20 + natural / 8 })); + + const d = svg.querySelector('path')!.getAttribute('d')!; + const [x2, y2] = d.trim().split(/[\s,LM]+/).filter(Boolean).slice(-2).map(Number); + expect(x2).toBeCloseTo(natural / 2); + expect(y2).toBeCloseTo(natural / 4); + }); + it('renders psdots as small circles', () => { const env = parsePspicture(` \\begin{pspicture}(0,0)(4,4) diff --git a/packages/pstricks/src/lib/psgraph.ts b/packages/pstricks/src/lib/psgraph.ts index b75f8278..d2c9d16c 100644 --- a/packages/pstricks/src/lib/psgraph.ts +++ b/packages/pstricks/src/lib/psgraph.ts @@ -583,6 +583,24 @@ class PatchSelection { } const psgraph: any = { + /** + * Maps a pointer's client position into the picture's own pixel space. A + * page may draw the svg smaller than its natural size (CSS `zoom` or a + * transform to fit a phone screen), and neither `offsetX` nor a raw + * `clientX - rect.left` accounts for that, so divide out the drawn scale. + * Measuring the svg itself, not the event target, also keeps a pointer over + * a child path from reading coordinates relative to that path. + */ + pointerCoords(svg: Element | null, clientX: number, clientY: number): number[] { + if (!svg) return [0, 0]; + const rect = svg.getBoundingClientRect(); + const width = parseFloat(svg.getAttribute('width') || '') || rect.width; + const height = parseFloat(svg.getAttribute('height') || '') || rect.height; + const scaleX = rect.width && width ? rect.width / width : 1; + const scaleY = rect.height && height ? rect.height / height : 1; + return [(clientX - rect.left) / scaleX, (clientY - rect.top) / scaleY]; + }, + env: null as any, getSize(): { width: number; height: number } { const padding = 20; @@ -1596,17 +1614,15 @@ const psgraph: any = { function (this: any, event: any) { event.preventDefault(); var touch = event.touches ? event.touches[0] : null; - var rect = event.target.getBoundingClientRect(); - var touchcoords = touch ? [touch.clientX - rect.left, touch.clientY - rect.top] : [0, 0]; - drawLayer(touchcoords, { pointer: true }); + if (!touch) return; + drawLayer(psgraph.pointerCoords(svg.node(), touch.clientX, touch.clientY), { pointer: true }); } ); svg.on( 'mousemove', function (this: any, event: any) { - var coords = [event.offsetX || 0, event.offsetY || 0]; - drawLayer(coords, { pointer: true }); + drawLayer(psgraph.pointerCoords(svg.node(), event.clientX, event.clientY), { pointer: true }); } ); diff --git a/packages/react/src/components/pspicture.tsx b/packages/react/src/components/pspicture.tsx index 8b8fc69f..dcbbcf96 100644 --- a/packages/react/src/components/pspicture.tsx +++ b/packages/react/src/components/pspicture.tsx @@ -35,17 +35,14 @@ export default (props: PspictureProps) => { }, [props]); const handleMouseMove = (event: React.MouseEvent) => { - const coords = [event.nativeEvent.offsetX || 0, event.nativeEvent.offsetY || 0]; - userEventHandler(coords); + userEventHandler(psgraph.pointerCoords(svgRef.current, event.clientX, event.clientY)); }; const handleTouchMove = (event: React.TouchEvent) => { event.preventDefault(); const touch = event.touches[0]; if (touch && svgRef.current) { - const rect = svgRef.current.getBoundingClientRect(); - const coords = [touch.clientX - rect.left, touch.clientY - rect.top]; - userEventHandler(coords); + userEventHandler(psgraph.pointerCoords(svgRef.current, touch.clientX, touch.clientY)); } };