Skip to content

exportToPdf throws on a /Line annotation with no /LE, losing the whole export #6

Description

@LRomuloux

exportToPdf throws on a /Line annotation with no /LE, losing the whole export

Version: inklayer-react@1.2.2 · React 19 · Vite 8 · Chrome on Windows 11

Summary

With enableNativeAnnotations on, exporting a PDF that contains an ordinary
/Line annotation — a straight line with no arrowhead — throws and produces no
file at all:

Error: Arrow annotation 5R has no arrow shape.

A /Line without /LE is the most common redline mark there is; every PDF
markup tool I have produces them. In our case a single such annotation in a
133-annotation drawing meant the export silently produced nothing.

Reproduction

A 595-byte PDF is enough: one blank page, one /Line annotation, no /LE.

Generate it (Python, pypdf):

from pypdf import PdfWriter
from pypdf.annotations import Line

w = PdfWriter()
w.add_blank_page(width=612, height=792)
w.add_annotation(page_number=0, annotation=Line(
    p1=(100, 600), p2=(400, 650), rect=(90, 590, 410, 660), text=""))
with open("line-without-le.pdf", "wb") as fh:
    w.write(fh)

The annotation it writes is:

/Subtype /Line   /L [100 600 400 650]   /LE  (absent)

Then:

<PdfAnnotator
  url="/line-without-le.pdf"
  enableNativeAnnotations={true}
  actions={({ exportToPdf }) => (
    <button onClick={() => exportToPdf('out.pdf')}>Export PDF</button>
  )}
/>

Click Export PDF. Nothing is downloaded; the console shows the error above.

Cause

From dist/index.es.js, in the export path:

class Ll extends Ye {
  async parse() {
    const { annotation: e, ... } = this,
          s = JSON.parse(e.konvaString),
          a = s.children.filter((y) => y.className === "Arrow");
    if (a.length === 0)
      throw new Error(`Arrow annotation ${e.id} has no arrow shape.`);

The importer appears to map /Line onto the arrow annotation type, but for a
line with no /LE no Arrow child ends up in konvaString. The exporter then
requires one and throws. So importer and exporter disagree about what a /Line
without line endings is.

Per the PDF spec, /LE is optional on a /Line annotation and defaults to
[/None /None] — no arrowheads. A plain line is a valid, common /Line.

Suggested fixes

Two independent things, either of which would have saved the export:

  1. Treat a /Line with no /LE (or [/None /None]) as a line rather than an
    arrow
    , so the shape the exporter looks for exists.

  2. Do not let one annotation take down the export. Even with the mapping
    fixed, some annotation somewhere will be unrepresentable. Skipping it with a
    warning — or reporting the failures to the caller — loses one mark; throwing
    loses the document. A void-returning exportToPdf gives the host
    application no way to know it failed, so from the outside it looks like
    nothing happened at all.

Related, possibly by design

With enableNativeAnnotations off (the default), the export succeeds but
carries none of the document's existing annotations. Round-tripping a file with
133 markups returned 2 — the ones drawn in InkLayer during the session. /Link
annotations survived; every /Ink, /Line, /FreeText, /Square and /Stamp
did not.

That may be intentional given what the flag says. If it is, it might be worth
noting in the README that exporting with it off rewrites the file without its
original annotations — for a review tool, silently dropping the other party's
markup is a surprising default, and it is not visible until someone looks for a
comment that is no longer there.

Context

Thanks for the library — everything else worked well. It loaded a real 2.7 MB
two-page document quickly, data/ArrayBuffer input meant no CORS or token
handling, exportToPdf via the actions prop produced a proper
application/pdf with the text still selectable rather than a rasterised page,
and exceljs being a lazy chunk is a nice touch for anyone not using the Excel
export. We were evaluating it for annotating construction-contract documents,
where preserving the other side's markup is the whole game.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions