hummusRecipe
hummusRecipe copied to clipboard
Link annotations?
I'm still working through understanding PDF structures so forgive me if I'm not quite there but as I understand it links within the document are created using special link annotations, which are provided a URL action ('A') and height and width of the rectangle of the link. This could then be placed over an image for example.
Do you have any plans to support link annotations? From what I can see there isn't the necessary fields available in your API to support them?
Yes, it's in my scope. I will see what I can do and make it easier. You are right about the link annotation. If you want a link on your image, technically you will need to create a image xobjectform and a link annotation with a rectangle using the image dimension. https://github.com/chunyenHuang/hummusRecipe/blob/4ddd1eafe7cdee2505ef2512f1b0e037d980cef9/lib/annotation.js#L50 However, you should be able to create this by something like this eventually if I add the support for annotations.
.image(myCats, 'center', 'center', {
width: 300,
height: 300,
keepAspectRatio: false,
opacity: 0.4,
align: 'center center',
link: 'https://www.google.com'
})
+1 I'd also like to be able to add link annotations. Specifically, I'd like to be able to build a dynamic table of contents that includes links to particular pages in the PDF.
I would love to have this feature as well. However, I will need more time for it and probably wait for this Hummus PR #229
Until then, it's possible to create a table-of-contents PDF with internal page links using pdfkit, then hummusRecipe can append pages to the pdfkit-generated TOC PDF. You have to make sure that pdfkit has finished writing the TOC before reading the PDF using hummusRecipe, but aside from that, it's pretty straightforward.
KK, I will steal some ideas from Devon. Thanks for the alternative solution and reference.
I just wrote a way to write TOC with hummus, here is the code (public domain):
function main() {
const hummus = require('hummus');
let w = hummus.createWriter(...);
let ctx = w.getObjectsContext();
let events = w.getEvents();
let page_ids: number[] = [];
...
let outline = writeOutline(ctx, [
{ title: "Title", page_idx: 1 },
{ title: "Title 2", page_idx: 2, childs: [
{ title: "Title 3", page_idx: 3 },
]},
], page_ids);
events.on('OnCatalogWrite', (e: any) => {
let d = e.catalogDictionaryContext;
if (outline !== null) {
d.writeKey("Outlines");
d.writeObjectReferenceValue(outline);
d.writeKey("PageMode");
d.writeNameValue("UseOutlines");
}
});
w.end();
}
type Outline = { title: string, page_idx: number, childs?: Outline[] };
function writeOutline(ctx: any, outlines: Outline[], page_ids: number[]) : number | null
{
if (outlines.length === 0)
return null;
let outline = ctx.allocateNewObjectID();
let outline_ids = writeOutlines(ctx, outlines, outline, page_ids);
ctx.startNewIndirectObject(outline);
let d = ctx.startDictionary();
d.writeKey("Type");
d.writeNameValue("Outlines");
d.writeKey("Count");
d.writeNumberValue(outline_ids.length);
d.writeKey("First");
d.writeObjectReferenceValue(outline_ids[0]);
d.writeKey("Last");
d.writeObjectReferenceValue(outline_ids[outline_ids.length - 1]);
ctx.endDictionary(d);
ctx.endIndirectObject();
return outline;
}
function writeOutlines(ctx: any, outlines: Outline[], parent: number, page_ids: number[]) : number[]
{
let ids = outlines.map(() => ctx.allocateNewObjectID());
outlines.forEach(({ title, page_idx, childs }, i) => {
let id = ids[i];
let child_ids = childs && childs.length ? writeOutlines(ctx, childs, id, page_ids) : null;
ctx.startNewIndirectObject(id);
let d = ctx.startDictionary();
d.writeKey("Title");
d.writeLiteralStringValue(title);
d.writeKey("Parent");
d.writeObjectReferenceValue(parent);
d.writeKey("Dest");
ctx.startArray();
ctx.writeIndirectObjectReference(page_ids[page_idx]);
ctx.writeName("XYZ");
let c = ctx.startFreeContext();
c.write([ 32, 110, 117, 108, 108, 32, 110, 117, 108, 108, 32, 48, 32 ]/*" null null 0 "*/);
ctx.endFreeContext();
ctx.endArray();
ctx.endLine();
if (child_ids) {
d.writeKey("Count");
d.writeNumberValue(outlines.length);
d.writeKey("First");
d.writeObjectReferenceValue(child_ids[0]);
d.writeKey("Last");
d.writeObjectReferenceValue(child_ids[child_ids.length - 1]);
}
if (i + 1 < ids.length) {
d.writeKey("Next");
d.writeObjectReferenceValue(ids[i + 1]);
}
if (i > 0) {
d.writeKey("Prev");
d.writeObjectReferenceValue(ids[i - 1]);
}
ctx.endDictionary(d);
ctx.endIndirectObject();
});
return ids;
}
I created an npm module with this functionality, feel free to give feedback. Thanks!
It is pointless to support pdf editor/maker without modify existing pdf, inserting svg with HTML semantic elements. Make a clear new PDF is like newbie task. We live in 2022.
There is like two pdf libs for nodejs and none of them fulfills requirements I have. What is the point of editing a PDF without possibility to pass HTML element e.g. with url or edit metaData.