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:
-
Treat a /Line with no /LE (or [/None /None]) as a line rather than an
arrow, so the shape the exporter looks for exists.
-
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.
exportToPdf throws on a
/Lineannotation with no/LE, losing the whole exportVersion:
inklayer-react@1.2.2· React 19 · Vite 8 · Chrome on Windows 11Summary
With
enableNativeAnnotationson, exporting a PDF that contains an ordinary/Lineannotation — a straight line with no arrowhead — throws and produces nofile at all:
A
/Linewithout/LEis the most common redline mark there is; every PDFmarkup 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
/Lineannotation, no/LE.Generate it (Python,
pypdf):The annotation it writes is:
Then:
Click Export PDF. Nothing is downloaded; the console shows the error above.
Cause
From
dist/index.es.js, in the export path:The importer appears to map
/Lineonto the arrow annotation type, but for aline with no
/LEnoArrowchild ends up inkonvaString. The exporter thenrequires one and throws. So importer and exporter disagree about what a
/Linewithout line endings is.
Per the PDF spec,
/LEis optional on a/Lineannotation 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:
Treat a
/Linewith no/LE(or[/None /None]) as a line rather than anarrow, so the shape the exporter looks for exists.
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-returningexportToPdfgives the hostapplication no way to know it failed, so from the outside it looks like
nothing happened at all.
Related, possibly by design
With
enableNativeAnnotationsoff (the default), the export succeeds butcarries none of the document's existing annotations. Round-tripping a file with
133 markups returned 2 — the ones drawn in InkLayer during the session.
/Linkannotations survived; every
/Ink,/Line,/FreeText,/Squareand/Stampdid 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/ArrayBufferinput meant no CORS or tokenhandling,
exportToPdfvia theactionsprop produced a properapplication/pdfwith the text still selectable rather than a rasterised page,and
exceljsbeing a lazy chunk is a nice touch for anyone not using the Excelexport. We were evaluating it for annotating construction-contract documents,
where preserving the other side's markup is the whole game.