mirror of
https://github.com/foliojs/pdfkit.git
synced 2026-02-01 16:56:57 +00:00
Add shortcut for elements containing only marked content.
This commit is contained in:
parent
4c4a49ebff
commit
e3442a9aa2
@ -14,7 +14,6 @@ doc.pipe(fs.createWriteStream('accessible.pdf'));
|
||||
|
||||
// Set some meta data
|
||||
doc.info['Title'] = 'Test Document';
|
||||
|
||||
doc.info['Author'] = 'Devon Govett';
|
||||
|
||||
// Initialise document logical structure
|
||||
@ -24,166 +23,186 @@ doc.addStructure(struct);
|
||||
// Register a font name for use later
|
||||
doc.registerFont('Palatino', 'fonts/PalatinoBold.ttf');
|
||||
|
||||
// Set the font, draw some text, and embed an image
|
||||
struct.add(doc.struct('P', [ doc.markStructureContent('P') ]));
|
||||
doc
|
||||
.font('Palatino')
|
||||
.fontSize(25)
|
||||
.text('Some text with an embedded font! ', 100, 100);
|
||||
// Set the font and draw some text
|
||||
struct.add(doc.struct('P', () => {
|
||||
doc
|
||||
.font('Palatino')
|
||||
.fontSize(25)
|
||||
.text('Some text with an embedded font! ', 100, 100);
|
||||
}));
|
||||
|
||||
// Embed some images
|
||||
var imageSection = doc.struct('Sect');
|
||||
struct.add(imageSection);
|
||||
imageSection.add(doc.struct('H', [ doc.markStructureContent('H') ]));
|
||||
doc
|
||||
.fontSize(18)
|
||||
.text('PNG and JPEG images: ');
|
||||
|
||||
imageSection.add(doc.struct('H', () => {
|
||||
doc
|
||||
.fontSize(18)
|
||||
.text('PNG and JPEG images: ');
|
||||
}));
|
||||
|
||||
imageSection.add(doc.struct('Figure', {
|
||||
alt: "Promotional image of an Apple laptop. "
|
||||
}, [ doc.markStructureContent('Figure') ]));
|
||||
doc
|
||||
.image('images/test.png', 100, 160, {
|
||||
width: 412
|
||||
});
|
||||
alt: "Promotional image of an Apple laptop. "
|
||||
}, () => {
|
||||
doc
|
||||
.image('images/test.png', 100, 160, {
|
||||
width: 412
|
||||
});
|
||||
}));
|
||||
|
||||
imageSection.add(doc.struct('Figure', {
|
||||
alt: "Photograph of a path flanked by blossoming trees with surrounding hedges. "
|
||||
}, [ doc.markStructureContent('Figure') ]));
|
||||
doc
|
||||
.image('images/test.jpeg', 190, 400, {
|
||||
height: 300
|
||||
});
|
||||
alt: "Photograph of a path flanked by blossoming trees with surrounding hedges. "
|
||||
}, () => {
|
||||
doc
|
||||
.image('images/test.jpeg', 190, 400, {
|
||||
height: 300
|
||||
});
|
||||
}));
|
||||
|
||||
imageSection.end();
|
||||
|
||||
// Add another page
|
||||
doc
|
||||
.addPage();
|
||||
doc.addPage();
|
||||
|
||||
// Draw a triangle, a circle and a star
|
||||
var vectorSection = doc.struct('Sect');
|
||||
struct.add(vectorSection);
|
||||
vectorSection.add(doc.struct('H', [ doc.markStructureContent('H') ]));
|
||||
doc
|
||||
.fontSize(25)
|
||||
.text('Here are some vector graphics... ', 100, 100);
|
||||
|
||||
// Draw a triangle and a circle
|
||||
vectorSection.add(doc.struct('Figure', {
|
||||
alt: "Orange triangle. "
|
||||
}, [ doc.markStructureContent('Figure') ]));
|
||||
doc
|
||||
.save()
|
||||
.moveTo(100, 150)
|
||||
.lineTo(100, 250)
|
||||
.lineTo(200, 250)
|
||||
.fill('#FF8800');
|
||||
vectorSection.add(doc.struct('H', () => {
|
||||
doc
|
||||
.fontSize(25)
|
||||
.text('Here are some vector graphics... ', 100, 100);
|
||||
}));
|
||||
|
||||
vectorSection.add(doc.struct('Figure', {
|
||||
alt: "Purple circle. "
|
||||
}, [ doc.markStructureContent('Figure') ]));
|
||||
doc.circle(280, 200, 50).fill('#7722FF');
|
||||
alt: "Orange triangle. "
|
||||
}, () => {
|
||||
doc
|
||||
.save()
|
||||
.moveTo(100, 150)
|
||||
.lineTo(100, 250)
|
||||
.lineTo(200, 250)
|
||||
.fill('#FF8800');
|
||||
}));
|
||||
|
||||
vectorSection.add(doc.struct('Figure', {
|
||||
alt: "Red star with hollow center. "
|
||||
}, [ doc.markStructureContent('Figure') ]));
|
||||
doc
|
||||
.scale(0.6)
|
||||
.translate(470, 140)
|
||||
// render an SVG path
|
||||
.path('M 250,75 L 323,301 131,161 369,161 177,301 z')
|
||||
// fill using the even-odd winding rule
|
||||
.fill('red', 'even-odd')
|
||||
.restore();
|
||||
alt: "Purple circle. "
|
||||
}, () => {
|
||||
doc.circle(280, 200, 50).fill('#7722FF');
|
||||
}));
|
||||
|
||||
vectorSection.add(doc.struct('Figure', {
|
||||
alt: "Red star with hollow center. "
|
||||
}, () => {
|
||||
doc
|
||||
.scale(0.6)
|
||||
.translate(470, 140)
|
||||
// render an SVG path
|
||||
.path('M 250,75 L 323,301 131,161 369,161 177,301 z')
|
||||
// fill using the even-odd winding rule
|
||||
.fill('red', 'even-odd')
|
||||
.restore();
|
||||
}));
|
||||
|
||||
vectorSection.end();
|
||||
|
||||
// Draw some text wrapped to 412 points wide
|
||||
var loremIpsum =
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus nec hendrerit felis. Morbi aliquam facilisis risus eu lacinia. Sed eu leo in turpis fringilla hendrerit. Ut nec accumsan nisl. Suspendisse rhoncus nisl posuere tortor tempus et dapibus elit porta. Cras leo neque, elementum a rhoncus ut, vestibulum non nibh. Phasellus pretium justo turpis. Etiam vulputate, odio vitae tincidunt ultricies, eros odio dapibus nisi, ut tincidunt lacus arcu eu elit. Aenean velit erat, vehicula eget lacinia ut, dignissim non tellus. Aliquam nec lacus mi, sed vestibulum nunc. Suspendisse potenti. Curabitur vitae sem turpis. Vestibulum sed neque eget dolor dapibus porttitor at sit amet sem. Fusce a turpis lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;\nMauris at ante tellus. Vestibulum a metus lectus. Praesent tempor purus a lacus blandit eget gravida ante hendrerit. Cras et eros metus. Sed commodo malesuada eros, vitae interdum augue semper quis. Fusce id magna nunc. Curabitur sollicitudin placerat semper. Cras et mi neque, a dignissim risus. Nulla venenatis porta lacus, vel rhoncus lectus tempor vitae. Duis sagittis venenatis rutrum. Curabitur tempor massa tortor.';
|
||||
// Draw some text wrapped to 412 points wide, split into paragraphs
|
||||
var wrappedSection = doc.struct('Sect');
|
||||
struct.add(wrappedSection);
|
||||
wrappedSection.add(doc.struct('H', [ doc.markStructureContent('H') ]));
|
||||
doc
|
||||
.text('And here is some wrapped text... ', 100, 300)
|
||||
.font('Helvetica', 13)
|
||||
// move down 1 line
|
||||
.moveDown()
|
||||
.text(loremIpsum, {
|
||||
width: 412,
|
||||
align: 'justify',
|
||||
indent: 30,
|
||||
paragraphGap: 5,
|
||||
structParent: wrappedSection
|
||||
});
|
||||
|
||||
wrappedSection.add(doc.struct('H', () => {
|
||||
doc
|
||||
.text('And here is some wrapped text... ', 100, 300)
|
||||
.font('Helvetica', 13)
|
||||
// move down 1 line
|
||||
.moveDown()
|
||||
}));
|
||||
|
||||
var loremIpsum =
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus nec hendrerit felis. Morbi aliquam facilisis risus eu lacinia. Sed eu leo in turpis fringilla hendrerit. Ut nec accumsan nisl. Suspendisse rhoncus nisl posuere tortor tempus et dapibus elit porta. Cras leo neque, elementum a rhoncus ut, vestibulum non nibh. Phasellus pretium justo turpis. Etiam vulputate, odio vitae tincidunt ultricies, eros odio dapibus nisi, ut tincidunt lacus arcu eu elit. Aenean velit erat, vehicula eget lacinia ut, dignissim non tellus. Aliquam nec lacus mi, sed vestibulum nunc. Suspendisse potenti. Curabitur vitae sem turpis. Vestibulum sed neque eget dolor dapibus porttitor at sit amet sem. Fusce a turpis lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;\nMauris at ante tellus. Vestibulum a metus lectus. Praesent tempor purus a lacus blandit eget gravida ante hendrerit. Cras et eros metus. Sed commodo malesuada eros, vitae interdum augue semper quis. Fusce id magna nunc. Curabitur sollicitudin placerat semper. Cras et mi neque, a dignissim risus. Nulla venenatis porta lacus, vel rhoncus lectus tempor vitae. Duis sagittis venenatis rutrum. Curabitur tempor massa tortor.';
|
||||
doc.text(loremIpsum, {
|
||||
width: 412,
|
||||
align: 'justify',
|
||||
indent: 30,
|
||||
paragraphGap: 5,
|
||||
structParent: wrappedSection
|
||||
});
|
||||
|
||||
wrappedSection.end();
|
||||
|
||||
// Add another page, and set the font back
|
||||
doc
|
||||
.addPage();
|
||||
// Add another page
|
||||
doc.addPage();
|
||||
|
||||
// Set the font back and draw tiger line art based on an SVG
|
||||
var tigerSection = doc.struct('Sect');
|
||||
struct.add(tigerSection);
|
||||
tigerSection.add(doc.struct('H', [ doc.markStructureContent('H') ]));
|
||||
doc
|
||||
.font('Palatino', 25)
|
||||
.text('Rendering some SVG paths...', 100, 100)
|
||||
.translate(220, 300);
|
||||
|
||||
tigerSection.add(doc.struct('H', () => {
|
||||
doc
|
||||
.font('Palatino', 25)
|
||||
.text('Rendering some SVG paths...', 100, 100)
|
||||
.translate(220, 300);
|
||||
}));
|
||||
|
||||
tigerSection.add(doc.struct('Figure', {
|
||||
alt: "Tiger line art. "
|
||||
}, [ doc.markStructureContent('Figure') ]));
|
||||
var i, len, part;
|
||||
// Render each path that makes up the tiger image
|
||||
for (i = 0, len = tiger.length; i < len; i++) {
|
||||
part = tiger[i];
|
||||
doc.save();
|
||||
doc.path(part.path); // render an SVG path
|
||||
if (part['stroke-width']) {
|
||||
doc.lineWidth(part['stroke-width']);
|
||||
}
|
||||
if (part.fill !== 'none' && part.stroke !== 'none') {
|
||||
doc.fillAndStroke(part.fill, part.stroke);
|
||||
} else {
|
||||
if (part.fill !== 'none') {
|
||||
doc.fill(part.fill);
|
||||
alt: "Tiger line art. "
|
||||
}, () => {
|
||||
var i, len, part;
|
||||
// Render each path that makes up the tiger image
|
||||
for (i = 0, len = tiger.length; i < len; i++) {
|
||||
part = tiger[i];
|
||||
doc.save();
|
||||
doc.path(part.path); // render an SVG path
|
||||
if (part['stroke-width']) {
|
||||
doc.lineWidth(part['stroke-width']);
|
||||
}
|
||||
if (part.stroke !== 'none') {
|
||||
doc.stroke(part.stroke);
|
||||
if (part.fill !== 'none' && part.stroke !== 'none') {
|
||||
doc.fillAndStroke(part.fill, part.stroke);
|
||||
} else {
|
||||
if (part.fill !== 'none') {
|
||||
doc.fill(part.fill);
|
||||
}
|
||||
if (part.stroke !== 'none') {
|
||||
doc.stroke(part.stroke);
|
||||
}
|
||||
}
|
||||
doc.restore();
|
||||
}
|
||||
doc.restore();
|
||||
}
|
||||
}));
|
||||
|
||||
tigerSection.end();
|
||||
|
||||
// Add some text with annotations
|
||||
doc
|
||||
.addPage();
|
||||
// Add another page
|
||||
doc.addPage();
|
||||
|
||||
// Add some text with annotations
|
||||
var linkSection = doc.struct('Sect');
|
||||
struct.add(linkSection);
|
||||
linkSection.add(doc.struct('Link', [ doc.markStructureContent('Link') ]));
|
||||
doc
|
||||
.fillColor('blue')
|
||||
.text('Here is a link!', 100, 100, {
|
||||
link: 'http://google.com/',
|
||||
underline: true
|
||||
});
|
||||
|
||||
linkSection.add(doc.struct('Link', () => {
|
||||
doc
|
||||
.fillColor('blue')
|
||||
.text('Here is a link!', 100, 100, {
|
||||
link: 'http://google.com/',
|
||||
underline: true
|
||||
});
|
||||
}));
|
||||
|
||||
linkSection.end();
|
||||
|
||||
// Add a list with a font loaded from a TrueType collection file
|
||||
var listSection = doc.struct('Sect');
|
||||
struct.add(listSection);
|
||||
|
||||
var list = doc.struct('List');
|
||||
listSection.add(list);
|
||||
|
||||
doc
|
||||
.fillColor('#000')
|
||||
.font('fonts/Chalkboard.ttc', 'Chalkboard', 16)
|
||||
.list(['One, ', 'Two, ', 'Three. '], 100, 150, { structParent: list });
|
||||
|
||||
// This recursively ends the list as well
|
||||
listSection.end();
|
||||
listSection.end(); // This recursively ends the list as well
|
||||
|
||||
// End and flush the document
|
||||
doc.end();
|
||||
|
||||
Binary file not shown.
@ -94,7 +94,7 @@ Example of marking structure content:
|
||||
Example of the simplest of structure trees:
|
||||
|
||||
// Add a single structure element which includes the structure content to the document's structure
|
||||
doc.addStructure(doc.struct('P', [ myStructContent ]));
|
||||
doc.addStructure(doc.struct('P', myStructContent));
|
||||
|
||||
Tags/element types to use are listed in a later section.
|
||||
|
||||
@ -124,17 +124,17 @@ content (and any descendent marking):
|
||||
|
||||
### Complex Structure
|
||||
|
||||
Multiple elements may be added directly to the document, and may nest:
|
||||
Multiple elements may be added directly to the document, or to structure elements, and may nest:
|
||||
|
||||
// Create nested structure elements
|
||||
const section1 = doc.struct('Sect', [
|
||||
doc.struct('P', [
|
||||
someTextStructureContent,
|
||||
doc.struct('Link', [ someLinkStructureContent ]),
|
||||
doc.struct('Link', someLinkStructureContent),
|
||||
moreTextStructureContent
|
||||
])
|
||||
]);
|
||||
const section2 = doc.struct('Sect', [ secondSectionStructureContent ]);
|
||||
const section2 = doc.struct('Sect', secondSectionStructureContent);
|
||||
|
||||
// Add them to the document's structure
|
||||
doc.addStructure(section1).addStructure(section2);
|
||||
@ -146,7 +146,7 @@ you have finished adding to them, allowing them to be flushed out as soon as pos
|
||||
|
||||
// Begin a new section and add it to the document's structure
|
||||
const mySection = doc.struct('Sect');
|
||||
doc.addToStructure(mySection);
|
||||
doc.addStructure(mySection);
|
||||
|
||||
// Create a new paragraph and add it to the section
|
||||
const myParagraph = doc.struct('P');
|
||||
@ -160,10 +160,70 @@ you have finished adding to them, allowing them to be flushed out as soon as pos
|
||||
// End the paragraph, allowing it to be flushed out, freeing memory
|
||||
myParagraph.end();
|
||||
|
||||
Note that if you provide content when creating a structure element (i.e. providing it to
|
||||
Note that if you provide children when creating a structure element (i.e. providing them to
|
||||
`doc.struct()` rather than using `structElem.add()`) then `structElem.end()` is called
|
||||
automatically. You therefore should not add additional content, as the element may already have
|
||||
been flushed out. Do not mix atomic and incremental styles for the same structure element.
|
||||
automatically. You therefore cannot add additional children with `structElem.add()`, i.e.
|
||||
you cannot mix atomic and incremental styles for the same structure element.
|
||||
|
||||
For an element to be flushed out, it must:
|
||||
|
||||
* be ended,
|
||||
* have been added to its parent, and
|
||||
* if it has content defined through closures (see next section), be attached to the document's
|
||||
structure (through its ancestors)
|
||||
|
||||
When you call `doc.end()`, the document's structure is recursively ended, resulting in all
|
||||
elements being flushed out. If you created elements but forgot to add them to the document's
|
||||
structure, they will not be flushed, but the PDF stream will wait for them to be flushed before
|
||||
ending, causing your application to hang. Make sure if you create any elements, you add them
|
||||
to a parent, so ultimately all elements are attached to the document. It's best to add
|
||||
elements to their parents as you go.
|
||||
|
||||
### Shortcut for Elements Containing Only Marked Content
|
||||
|
||||
The common case where a structure element contains only content marked with a tag matching
|
||||
the structure element type can be achieved by using a closure:
|
||||
|
||||
doc.addStructure(doc.struct('P', () => {
|
||||
doc.text('Hello, world! ');
|
||||
}));
|
||||
|
||||
This is equivalent to:
|
||||
|
||||
const myStruct = doc.struct('P');
|
||||
doc.addStructure(myStruct);
|
||||
const myStructContent = doc.markStructureContent('P');
|
||||
doc.text('Hello, world! ');
|
||||
doc.endMarkedContent();
|
||||
myStruct.add(myStructContent);
|
||||
myStruct.end();
|
||||
|
||||
Note that the content is marked and the closure is executed *if/when the element is attached to
|
||||
the document's structure*. This means that you can do something like this:
|
||||
|
||||
const myParagraph = doc.struct('P', [
|
||||
() => { doc.text("Please see ", { continued: true }); },
|
||||
doc.struct('Link', () => {
|
||||
doc.text("something", { link: "http://www.example.com/", continued: true });
|
||||
}),
|
||||
() => { doc.text(" for details. ", { link: null }); }
|
||||
]);
|
||||
|
||||
and no content will be added to the page until/unless something like this is done:
|
||||
|
||||
doc.addStructure(section1);
|
||||
section1.add(myParagraph); // Content is added now
|
||||
|
||||
or alternatively:
|
||||
|
||||
section1.add(myParagraph);
|
||||
doc.addStructure(section1); // Content is added now
|
||||
|
||||
This is important because otherwise when the `Link` element is constructed, its content
|
||||
will be added to the page, and then the list containing the link element will be passed to
|
||||
the construct the `P` element, and only during the construction of the `P` element will the
|
||||
other `P` content be added to the page, resulting in page content being out of order.
|
||||
It's best to add elements to their parents as you go.
|
||||
|
||||
### Structure Element Options
|
||||
|
||||
@ -182,7 +242,7 @@ Example of a structure tree with options specified:
|
||||
}, [
|
||||
doc.struct('H', [
|
||||
doc.struct('Span', {
|
||||
expanded: 'Portable Document Format/Universal Accessibility',
|
||||
expanded: 'Portable Document Format for Universal Accessibility',
|
||||
actual: 'PDF/UA'
|
||||
}, [
|
||||
pdfUAStructureContent
|
||||
@ -214,13 +274,12 @@ Example of creating structure automatically with `text()`:
|
||||
doc.addStructure(section);
|
||||
doc.text("Foo. \nBar. ", { structParent: section });
|
||||
|
||||
// Equivalent code if performed manually
|
||||
This is equivalent to:
|
||||
|
||||
const section = doc.struct('Sect');
|
||||
doc.addStructure(section);
|
||||
section.add(doc.struct('P', [ doc.markStructureContent('P') ]));
|
||||
doc.text("Foo. ");
|
||||
section.add(doc.struct('P', [ doc.markStructureContent('P') ]));
|
||||
doc.text("Bar. ");
|
||||
section.add(doc.struct('P', () => { doc.text("Foo. "); });
|
||||
section.add(doc.struct('P', () => { doc.text("Bar. "); });
|
||||
|
||||
The `list()` method also accepts a `structParent` option. By default, it add list items
|
||||
(type `LI`) to the parent, each of which contains a label (type `Lbl`, which holds the bullet,
|
||||
@ -268,7 +327,7 @@ Non-structure tags:
|
||||
other formats like HTML, but 'transparent' to its content which is processed normally)
|
||||
* `Private` - content only meaningful to the creator (element and its content not intended to
|
||||
be exported to other formats like HTML)
|
||||
|
||||
|
||||
"Block" elements:
|
||||
|
||||
* `H` - heading (first element in a section, etc.)
|
||||
@ -279,7 +338,7 @@ Non-structure tags:
|
||||
* `LI` - list item; should contain `Lbl` and/or `LBody`
|
||||
* `Lbl` - label (bullet, number, or "dictionary headword")
|
||||
* `LBody` - list body (item text, or "dictionary definition"); may have nested lists or other blocks
|
||||
|
||||
|
||||
"Table" elements:
|
||||
|
||||
* `Table` - table; should either contain `TR`, or `THead`, `TBody` and/or `TFoot`
|
||||
@ -289,7 +348,7 @@ Non-structure tags:
|
||||
* `THead` - table header row group
|
||||
* `TBody` - table body row group; may have more than one per table
|
||||
* `TFoot` - table footer row group
|
||||
|
||||
|
||||
"Inline" elements:
|
||||
|
||||
* `Span` - generic inline content
|
||||
@ -307,7 +366,7 @@ Non-structure tags:
|
||||
* `Warichu` - Japanese/Chinese longer description
|
||||
* `WT` - Warichu text
|
||||
* `WP` - Warichu punctuation
|
||||
|
||||
|
||||
"Illustration" elements (should have `alt` and/or `actualtext` set):
|
||||
|
||||
* `Figure` - figure
|
||||
|
||||
BIN
docs/guide.pdf
BIN
docs/guide.pdf
Binary file not shown.
@ -101,6 +101,7 @@ export default {
|
||||
addStructure(structElem) {
|
||||
const structTreeRoot = this.getStructTreeRoot();
|
||||
structElem.setParent(structTreeRoot);
|
||||
structElem.setAttached();
|
||||
this.structChildren.push(structElem);
|
||||
if (!structTreeRoot.data.K) {
|
||||
structTreeRoot.data.K = [];
|
||||
|
||||
@ -9,7 +9,9 @@ class PDFStructureElement {
|
||||
constructor(document, type, options = {}, children = null) {
|
||||
this.document = document;
|
||||
|
||||
this._attached = false;
|
||||
this._ended = false;
|
||||
this._flushed = false;
|
||||
this.dictionary = document.ref({
|
||||
// Type: "StructElem",
|
||||
S: type
|
||||
@ -17,7 +19,7 @@ class PDFStructureElement {
|
||||
|
||||
const data = this.dictionary.data;
|
||||
|
||||
if (Array.isArray(options)) {
|
||||
if (Array.isArray(options) || this._isValidChild(options)) {
|
||||
children = options;
|
||||
options = {};
|
||||
}
|
||||
@ -38,9 +40,12 @@ class PDFStructureElement {
|
||||
data.ActualText = new String(options.actual);
|
||||
}
|
||||
|
||||
this.structElemChildren = [];
|
||||
this._children = [];
|
||||
|
||||
if (children) {
|
||||
if (!Array.isArray(children)) {
|
||||
children = [children];
|
||||
}
|
||||
children.forEach((child) => this.add(child));
|
||||
this.end();
|
||||
}
|
||||
@ -51,17 +56,136 @@ class PDFStructureElement {
|
||||
throw new Error(`Cannot add child to already-ended structure element`);
|
||||
}
|
||||
|
||||
if (!this.dictionary.data.K) {
|
||||
this.dictionary.data.K = [];
|
||||
}
|
||||
|
||||
if (!(child instanceof PDFStructureElement || child instanceof PDFStructureContent)) {
|
||||
if (!this._isValidChild(child)) {
|
||||
throw new Error(`Invalid structure element child`);
|
||||
}
|
||||
|
||||
if (child instanceof PDFStructureElement) {
|
||||
child.setParent(this.dictionary);
|
||||
this.structElemChildren.push(child);
|
||||
if (this._attached) {
|
||||
child.setAttached();
|
||||
}
|
||||
}
|
||||
|
||||
if (child instanceof PDFStructureContent) {
|
||||
this._addContentToParentTree(child);
|
||||
}
|
||||
|
||||
if (typeof child === 'function' && this._attached) {
|
||||
// _contentForClosure() adds the content to the parent tree
|
||||
child = this._contentForClosure(child);
|
||||
}
|
||||
|
||||
this._children.push(child);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
_addContentToParentTree(content) {
|
||||
content.refs.forEach(({ pageRef, mcid }) => {
|
||||
const pageStructParents = this.document.getStructParentTree()
|
||||
.get(pageRef.data.StructParents);
|
||||
pageStructParents[mcid] = this.dictionary;
|
||||
});
|
||||
}
|
||||
|
||||
setParent(parentRef) {
|
||||
if (this.dictionary.data.P) {
|
||||
throw new Error(`Structure element added to more than one parent`);
|
||||
}
|
||||
|
||||
this.dictionary.data.P = parentRef;
|
||||
|
||||
this._flush();
|
||||
}
|
||||
|
||||
setAttached() {
|
||||
if (this._attached) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._children.forEach((child, index) => {
|
||||
if (child instanceof PDFStructureElement) {
|
||||
child.setAttached();
|
||||
}
|
||||
if (typeof child === 'function') {
|
||||
this._children[index] = this._contentForClosure(child);
|
||||
}
|
||||
});
|
||||
|
||||
this._attached = true;
|
||||
|
||||
this._flush();
|
||||
}
|
||||
|
||||
end() {
|
||||
if (this._ended) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._children
|
||||
.filter((child) => child instanceof PDFStructureElement)
|
||||
.forEach((child) => child.end());
|
||||
|
||||
this._ended = true;
|
||||
|
||||
this._flush();
|
||||
}
|
||||
|
||||
_isValidChild(child) {
|
||||
return child instanceof PDFStructureElement ||
|
||||
child instanceof PDFStructureContent ||
|
||||
typeof child === 'function';
|
||||
}
|
||||
|
||||
_contentForClosure(closure) {
|
||||
const content = this.document.markStructureContent(this.dictionary.S);
|
||||
closure();
|
||||
this.document.endMarkedContent();
|
||||
|
||||
this._addContentToParentTree(content);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
_isFlushable() {
|
||||
if (!this.dictionary.data.P || !this._ended) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._children.every((child) => {
|
||||
if (typeof child === 'function') {
|
||||
return false;
|
||||
}
|
||||
if (child instanceof PDFStructureElement) {
|
||||
return child._isFlushable();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
_flush() {
|
||||
if (this._flushed || !this._isFlushable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dictionary.data.K = [];
|
||||
|
||||
this._children.forEach((child) => this._flushChild(child));
|
||||
|
||||
this.dictionary.end();
|
||||
|
||||
// free memory used by children; the dictionary itself may still be
|
||||
// referenced by a parent structure element or root, but we can
|
||||
// at least trim the tree here
|
||||
this._children = [];
|
||||
this.dictionary.data.K = null;
|
||||
|
||||
this._flushed = true;
|
||||
}
|
||||
|
||||
_flushChild(child) {
|
||||
if (child instanceof PDFStructureElement) {
|
||||
this.dictionary.data.K.push(child.dictionary);
|
||||
}
|
||||
|
||||
@ -80,50 +204,8 @@ class PDFStructureElement {
|
||||
MCID: mcid
|
||||
});
|
||||
}
|
||||
|
||||
const pageStructParents = this.document.getStructParentTree()
|
||||
.get(pageRef.data.StructParents);
|
||||
pageStructParents[mcid] = this.dictionary;
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setParent(parentRef) {
|
||||
if (this.dictionary.data.P) {
|
||||
throw new Error(`Structure element added to more than one parent`);
|
||||
}
|
||||
|
||||
this.dictionary.data.P = parentRef;
|
||||
|
||||
if (this._ended) {
|
||||
this._flush();
|
||||
}
|
||||
}
|
||||
|
||||
end() {
|
||||
if (this._ended) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.structElemChildren.forEach((child) => child.end());
|
||||
|
||||
this._ended = true;
|
||||
|
||||
if (this.dictionary.data.P) {
|
||||
this._flush();
|
||||
}
|
||||
}
|
||||
|
||||
_flush() {
|
||||
this.dictionary.end();
|
||||
|
||||
// free memory used by children; the dictionary itself may still be
|
||||
// referenced by a parent structure element or root, but we can
|
||||
// at least trim the tree here
|
||||
this.structElemChildren = [];
|
||||
this.dictionary.data.K = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -233,16 +233,16 @@ EMC
|
||||
const pContent3 = document.markStructureContent("P");
|
||||
document.markContent("Span");
|
||||
|
||||
const section1 = document.struct('Section', [
|
||||
const section1 = document.struct('Sect', [
|
||||
document.struct('P', [
|
||||
pContent1,
|
||||
document.struct('Link', [ linkContent ]),
|
||||
document.struct('Link', linkContent),
|
||||
pContent2
|
||||
])
|
||||
]);
|
||||
const section2 = document.struct('Section', [
|
||||
const section2 = document.struct('Sect', [
|
||||
document.struct('P', [
|
||||
pContent3,
|
||||
pContent3
|
||||
])
|
||||
]);
|
||||
document.addStructure(section1).addStructure(section2);
|
||||
@ -275,27 +275,27 @@ EMC
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`12 0 obj`,
|
||||
`<<\n/S /Section\n/K [11 0 R]\n/P 8 0 R\n>>`,
|
||||
`<<\n/S /Sect\n/P 8 0 R\n/K [11 0 R]\n>>`,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`11 0 obj`,
|
||||
`<<\n/S /P\n/K [0 10 0 R 2]\n/Pg 7 0 R\n/P 12 0 R\n>>`,
|
||||
`<<\n/S /P\n/P 12 0 R\n/K [0 10 0 R 2]\n/Pg 7 0 R\n>>`,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`10 0 obj`,
|
||||
`<<\n/S /Link\n/K [1]\n/Pg 7 0 R\n/P 11 0 R\n>>`,
|
||||
`<<\n/S /Link\n/P 11 0 R\n/K [1]\n/Pg 7 0 R\n>>`,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`14 0 obj`,
|
||||
`<<\n/S /Section\n/K [13 0 R]\n/P 8 0 R\n>>`,
|
||||
`<<\n/S /Sect\n/P 8 0 R\n/K [13 0 R]\n>>`,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`13 0 obj`,
|
||||
`<<\n/S /P\n/K [3]\n/Pg 7 0 R\n/P 14 0 R\n>>`,
|
||||
`<<\n/S /P\n/P 14 0 R\n/K [3]\n/Pg 7 0 R\n>>`,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
@ -314,9 +314,9 @@ EMC
|
||||
const pContent3 = document.markStructureContent("P");
|
||||
document.markContent("Span");
|
||||
|
||||
const section1 = document.struct('Section');
|
||||
const section1 = document.struct('Sect');
|
||||
document.addStructure(section1);
|
||||
const section2 = document.struct('Section');
|
||||
const section2 = document.struct('Sect');
|
||||
document.addStructure(section2);
|
||||
const link = document.struct('Link');
|
||||
link.add(linkContent);
|
||||
@ -355,7 +355,7 @@ EMC
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`10 0 obj`,
|
||||
`<<\n/S /Section\n/P 8 0 R\n/K [13 0 R]\n>>`,
|
||||
`<<\n/S /Sect\n/P 8 0 R\n/K [13 0 R]\n>>`,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
@ -365,17 +365,17 @@ EMC
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`12 0 obj`,
|
||||
`<<\n/S /Link\n/K [1]\n/Pg 7 0 R\n/P 13 0 R\n>>`,
|
||||
`<<\n/S /Link\n/P 13 0 R\n/K [1]\n/Pg 7 0 R\n>>`,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`11 0 obj`,
|
||||
`<<\n/S /Section\n/P 8 0 R\n/K [14 0 R]\n>>`,
|
||||
`<<\n/S /Sect\n/P 8 0 R\n/K [14 0 R]\n>>`,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`14 0 obj`,
|
||||
`<<\n/S /P\n/K [3]\n/Pg 7 0 R\n/P 11 0 R\n>>`,
|
||||
`<<\n/S /P\n/P 11 0 R\n/K [3]\n/Pg 7 0 R\n>>`,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
@ -385,6 +385,78 @@ EMC
|
||||
]);
|
||||
});
|
||||
|
||||
test('constructed with closures', () => {
|
||||
const docData = logData(document);
|
||||
|
||||
const section1 = document.struct('Sect');
|
||||
document.addStructure(section1);
|
||||
const section2 = document.struct('Sect');
|
||||
const link = document.struct('Link', () => {});
|
||||
const p1 = document.struct('P');
|
||||
section1.add(p1);
|
||||
p1.add(() => {}).add(link).add(() => {}).end();
|
||||
const p2 = document.struct('P', [() => {}]);
|
||||
section2.add(p2);
|
||||
document.addStructure(section2);
|
||||
|
||||
document.end();
|
||||
|
||||
expect(docData).toContainChunk([
|
||||
`3 0 obj`,
|
||||
/\/StructTreeRoot 9 0 R/,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`3 0 obj`,
|
||||
/\/Markings 13 0 R/,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`9 0 obj`,
|
||||
`<<
|
||||
/Type /StructTreeRoot
|
||||
/ParentTree <<
|
||||
/Nums [
|
||||
0 [12 0 R 11 0 R 12 0 R 14 0 R]
|
||||
]
|
||||
>>
|
||||
/ParentTreeNextKey 1
|
||||
/K [8 0 R 10 0 R]
|
||||
>>`,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`8 0 obj`,
|
||||
`<<\n/S /Sect\n/P 9 0 R\n/K [12 0 R]\n>>`,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`12 0 obj`,
|
||||
`<<\n/S /P\n/P 8 0 R\n/K [0 11 0 R 2]\n/Pg 7 0 R\n>>`,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`11 0 obj`,
|
||||
`<<\n/S /Link\n/P 12 0 R\n/K [1]\n/Pg 7 0 R\n>>`,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`10 0 obj`,
|
||||
`<<\n/S /Sect\n/P 9 0 R\n/K [14 0 R]\n>>`,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`14 0 obj`,
|
||||
`<<\n/S /P\n/P 10 0 R\n/K [3]\n/Pg 7 0 R\n>>`,
|
||||
`endobj`
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
`13 0 obj`,
|
||||
`<<\n>>`,
|
||||
`endobj`
|
||||
]);
|
||||
});
|
||||
|
||||
test('with options', () => {
|
||||
const docData = logData(document);
|
||||
|
||||
@ -408,6 +480,7 @@ EMC
|
||||
/E (My Expansion)
|
||||
/ActualText (My Actual Text)
|
||||
/P 9 0 R
|
||||
/K []
|
||||
>>`,
|
||||
`endobj`
|
||||
]);
|
||||
@ -550,12 +623,12 @@ EMC
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
'11 0 obj',
|
||||
'<<\n/S /P\n/K [0]\n/Pg 7 0 R\n/P 8 0 R\n>>',
|
||||
'<<\n/S /P\n/P 8 0 R\n/K [0]\n/Pg 7 0 R\n>>',
|
||||
'endobj',
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
'13 0 obj',
|
||||
'<<\n/S /P\n/K [1]\n/Pg 7 0 R\n/P 8 0 R\n>>',
|
||||
'<<\n/S /P\n/P 8 0 R\n/K [1]\n/Pg 7 0 R\n>>',
|
||||
'endobj',
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
@ -637,22 +710,22 @@ EMC
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
'12 0 obj',
|
||||
'<<\n/S /Lbl\n/K [0]\n/Pg 7 0 R\n/P 10 0 R\n>>',
|
||||
'<<\n/S /Lbl\n/P 10 0 R\n/K [0]\n/Pg 7 0 R\n>>',
|
||||
'endobj'
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
'13 0 obj',
|
||||
'<<\n/S /LBody\n/K [1]\n/Pg 7 0 R\n/P 10 0 R\n>>',
|
||||
'<<\n/S /LBody\n/P 10 0 R\n/K [1]\n/Pg 7 0 R\n>>',
|
||||
'endobj'
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
'16 0 obj',
|
||||
'<<\n/S /Lbl\n/K [2]\n/Pg 7 0 R\n/P 15 0 R\n>>',
|
||||
'<<\n/S /Lbl\n/P 15 0 R\n/K [2]\n/Pg 7 0 R\n>>',
|
||||
'endobj'
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
'17 0 obj',
|
||||
'<<\n/S /LBody\n/K [3]\n/Pg 7 0 R\n/P 15 0 R\n>>',
|
||||
'<<\n/S /LBody\n/P 15 0 R\n/K [3]\n/Pg 7 0 R\n>>',
|
||||
'endobj'
|
||||
]);
|
||||
expect(docData).toContainChunk([
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user