Skip to content

SVG path parser drops arcs whose flags are written without separators (a10 10 0 0110-10) #166

Open

Description

@aheissenberger

What were you trying to do?

Draw an SVG logo with page.drawSvg(). The SVG was minified (SVGO does this by default), so its arcs write the large-arc and sweep flags without separators. For example a10 10 0 01-10.2-10.1 means flags 0 and 1, then the end point -10.2 -10.1.

How did you attempt to do it?

page.drawSvg(svg, …). It goes through the same path parser as page.drawSvgPath(d, …), which the reproduction below uses directly.

What actually happened?

The arc is dropped: only the moveto is written. In a longer path the arguments that follow shift, so the letters of the logo came out garbled.

What did you expect to happen?

The same curve as with separated flags. The path data grammar defines each flag as a single character, 0 or 1, and allows the next token to follow without whitespace or a comma. So 0110-10 is the four arguments 0, 1, 10 and -10.

How can we reproduce the issue?

import { PDFDocument } from '@cantoo/pdf-lib'

// The path operators pdf-lib writes for SVG path data.
const operators = async (d) => {
  const pdfDoc = await PDFDocument.create()
  const page = pdfDoc.addPage([100, 100])
  page.drawSvgPath(d, { x: 0, y: 100 })
  const content = page.node.normalizedEntries().Contents.asArray()
    .map((ref) => pdfDoc.context.lookup(ref).getContentsString())
    .join('')
  return content.split('\n').filter((line) => / [mlc]$/.test(line)).join(' | ')
}

// The same quarter circle from (0,10) to (10,0). The second path writes the arc flags 0 and 1 without a
// separator, which the SVG path grammar allows and minifiers such as SVGO output.
console.log('separated:', await operators('M0 10 a10 10 0 0 1 10 -10'))
console.log('compact:  ', await operators('M0 10 a10 10 0 0110-10'))

Output with 2.11.1:

separated: 0 10 m | -0.000000000000000676353751098178 4.477152501692068 4.477152501692063 0.0000000000000010145306266472669 9.999999999999998 0 c
compact:   0 10 m

Cause. parse() in src/api/svgPath.ts splits numbers only at whitespace, commas, a - and a second .. It reads 0110 as the single number 110, so a10 10 0 0110-10 has five arguments instead of seven and is not drawn.

Possible fix. While the current command is a/A and the argument index within the 7-argument group is 3 or 4, take exactly one character (0 or 1) as the flag and start the next argument right after it.

Version

2.11.1

What environment are you running pdf-lib in?

Node (v26); the same happens in the browser.

Checklist

  • My report includes a Short, Self Contained, Correct (Compilable) Example.
  • I have attached all PDFs, images, and other files needed to run my SSCCE. (None are needed.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions