Skip to content

Commit 70a92d8

Browse files
authored
fix: align anchor link hitboxes (#372)
Co-authored-by: amandeavor <amandeavor@users.noreply.github.com>
1 parent 4eb28f1 commit 70a92d8

4 files changed

Lines changed: 39 additions & 3 deletions

File tree

‎src/nodes/anchor.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export class Anchor extends Group {
1414

1515
context.pdf.link(
1616
scale * (box[0] * context.transform.sx + context.transform.tx),
17-
ph - scale * (box[1] * context.transform.sy + context.transform.ty),
17+
ph - scale * ((box[1] + box[3]) * context.transform.sy + context.transform.ty),
1818
scale * box[2],
1919
scale * box[3],
2020
{ url: href }

‎src/nodes/text.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,10 @@ export class TextNode extends GraphicsNode {
164164
renderingMode: textRenderingMode === 'fill' ? void 0 : textRenderingMode,
165165
charSpace: charSpace === 0 ? void 0 : charSpace
166166
})
167+
const textBottom = textY + dy + 0.1 * pdfFontSize
167168
this.boundingBox = [
168169
textX + dx - xOffset,
169-
textY + dy + 0.1 * pdfFontSize,
170+
textBottom - pdfFontSize,
170171
context.textMeasure.measureTextWidth(transformedText, context.attributeState),
171172
pdfFontSize
172173
]

‎test/anchor/anchor.spec.ts‎

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, it, expect } from 'vitest'
1+
import { describe, it, expect, vi } from 'vitest'
22
import { svg2pdf } from '../../src/svg2pdf'
33
import jsPDF from 'jspdf'
44
import { loadSvg } from '../utils/loadSvg'
@@ -8,6 +8,36 @@ describe('anchor', () => {
88
const { svgElement, width, height } = await loadSvg('/test/anchor/anchor.svg')
99
const pdf = new jsPDF(width > height ? 'l' : 'p', 'pt', [width, height])
1010
await svg2pdf(svgElement, pdf, { loadExternalStyleSheets: true })
11+
1112
await expect(pdf.output('arraybuffer')).toMatchPdfSnapshot('./anchor.pdf')
1213
})
14+
15+
it('aligns rectangular link hitboxes with their SVG bounds', async () => {
16+
const { svgElement, width, height } = await loadSvg('/test/anchor/rect-link.svg')
17+
const pdf = new jsPDF(width > height ? 'l' : 'p', 'pt', [width, height])
18+
const link = vi.spyOn(pdf, 'link')
19+
await svg2pdf(svgElement, pdf)
20+
21+
const rectHitbox = link.mock.calls.find(
22+
([, , , , options]) => options.url === 'https://example.com/hitbox'
23+
)
24+
expect(rectHitbox).toBeDefined()
25+
expect(rectHitbox?.slice(0, 4)).toEqual([180, 190, 100, 100])
26+
})
27+
28+
it('preserves text link hitboxes while normalizing text bounds', async () => {
29+
const { svgElement, width, height } = await loadSvg('/test/anchor/anchor.svg')
30+
const pdf = new jsPDF(width > height ? 'l' : 'p', 'pt', [width, height])
31+
const link = vi.spyOn(pdf, 'link')
32+
await svg2pdf(svgElement, pdf, { loadExternalStyleSheets: true })
33+
34+
const textHitbox = link.mock.calls.find(
35+
([, , , , options]) =>
36+
options.url ===
37+
'https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/dominant-baseline#hanging'
38+
)
39+
expect(textHitbox).toBeDefined()
40+
expect(textHitbox?.[1]).toBeCloseTo(277)
41+
expect(textHitbox?.[3]).toBe(30)
42+
})
1343
})

‎test/anchor/rect-link.svg‎

Lines changed: 5 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)