feature: vor und nachteile

This commit is contained in:
Hikyu
2026-05-31 13:54:31 +02:00
parent 552a947a1c
commit e3dad17446
23270 changed files with 3024794 additions and 200 deletions
@@ -0,0 +1,11 @@
MIT License
Copyright (c) lillallol
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,336 @@
# outline-pdf-data-structure
## Table of contents
<!--#region toc-->
- [Table of contents](#table-of-contents)
- [Installation](#installation)
- [Description](#description)
- [Code coverage](#code-coverage)
- [Example](#example)
- [Documentation](#documentation)
- [Concretions](#concretions)
- [outlinePdfDataStructure](#----outlinepdfdatastructure)
- [Motivation](#motivation)
- [Contributing](#contributing)
- [Changelog](#changelog)
- [1.0.3](#103)
- [1.0.2](#102)
- [1.0.1](#101)
- [1.0.0](#100)
- [License](#license)
<!--#endregion toc-->
## Installation
```bash
npm install @lillallol/outline-pdf-data-structure
```
## Description
Creates a pdf outline data structure as defined in the pdf specification, from a human readable string representation of the pdf outline. You can then use that data structure to "hydrate" a real pdf outline data structure like it is done in [@lillallol/outline-pdf](https://github.com/lillallol/outline-pdf).
## Code coverage
The testing code coverage is around 90%.
## Example
<!--#region example !./src/example/example.test.ts-->
```ts
import { outlinePdfDataStructure } from "../";
describe(outlinePdfDataStructure.name, () => {
it("returns low level information about the provided outline string representation", () => {
expect(
outlinePdfDataStructure(
`
1||Document
2|-|Section 1
3|-|Section 2
4|--|Subsection 1
5|-|Section 3
6||Summary
`,
6
)
).toEqual({
outlineRootCount: 6,
outlineItems: [
{
Title: "Document",
Parent: -1,
Next: 5,
First: 1,
Last: 4,
Count: 4,
Dest: 1 - 1,
},
{
Title: "Section 1",
Parent: 0,
Next: 2,
Dest: 2 - 1,
},
{
Title: "Section 2",
Parent: 0,
Prev: 1,
Next: 4,
First: 3,
Last: 3,
Count: 1,
Dest: 3 - 1,
},
{
Title: "Subsection 1",
Parent: 2,
Dest: 4 - 1,
},
{
Title: "Section 3",
Parent: 0,
Prev: 2,
Dest: 5 - 1,
},
{
Title: "Summary",
Parent: -1,
Prev: 0,
Dest: 6 - 1,
},
],
});
});
});
```
<!--#endregion example-->
## Documentation
<!--#region documentation ./documentation.md-->
<h3 id="-concretions">Concretions</h3>
<h4 id="-concretion-outlinePdfDataStructure">
outlinePdfDataStructure
</h4>
```ts
/**
* @description
* It returns all the information needed to create a real pdf data structure.
*/
export declare function outlinePdfDataStructure(
inputOutline: string,
totalNumberOfPages: number
): outlinePdfDataStructureReturnType;
```
<details open="">
<summary id="-concretion-outlinePdfDataStructure-references">
<a href="#-concretion-outlinePdfDataStructure-references">#</a>
references
</summary>
<br>
<blockquote>
<details>
<summary id="-concretion-outlinePdfDataStructure-references-outlinePdfDataStructureReturnType">
<a href="#-concretion-outlinePdfDataStructure-references-outlinePdfDataStructureReturnType">#</a>
<b>outlinePdfDataStructureReturnType</b>
</summary>
```ts
export declare type outlinePdfDataStructureReturnType = {
/**
* @description
* It returns a low level programmatic representation of the outline.
*/
outlineItems: outlineItem[];
/**
* @description
* The total number of outline nodes.
*/
outlineRootCount: number;
};
```
</details>
<blockquote>
<details>
<summary id="-concretion-outlinePdfDataStructure-references-outlinePdfDataStructureReturnType-outlineItem">
<a href="#-concretion-outlinePdfDataStructure-references-outlinePdfDataStructureReturnType-outlineItem">#</a>
<b>outlineItem</b>
</summary>
```ts
export declare type outlineItem = {
/**
* @description
* The title that will be visible in the outline of the pdf for the context
* outline node.
*/
Title: string;
/**
* @description
* The index (of the array that contains all the outline nodes) of the
* parent outline node of the context outline node.
*
* Outline nodes of depth `0` have `-1` for this value.
*/
Parent: number;
/**
* @description
* The index (of the array that contains all the outline nodes) of the
* previous sibling of the context outline node. It is `undefined` for the
* case there is no previous sibling.
*/
Prev?: number;
/**
* @description
* The index (of the array that contains all the outline nodes) of the next
* sibling of the context outline node. It is `undefined` for the case there
* is no next sibling.
*/
Next?: number;
/**
* @description
* The index (of the array that contains all the outline nodes) of the first
* immediate child of the context outline node. It is `undefined` for the
* case there is no immediate child.
*/
First?: number;
/**
* @description
* The index (of the array that contains all the outline nodes) of the last
* immediate child of the context outline node. It is `undefined` for the
* case there is no immediate child.
*/
Last?: number;
/**
* @description
* Total number of outline nodes that are descendants to the context outline
* node.
*/
Count?: number;
/**
* @description
* The page of the pdf that the outline node hyper links to.
*/
Dest: number;
};
```
</details>
</blockquote>
</blockquote>
</details>
<hr>
<!--#endregion documentation -->
## Motivation
This whole package was part of [@lillallol/outline-pdf](https://github.com/lillallol/outline-pdf), but then I decide to do separation of concerns, because it would make both projects more readable.
## Contributing
I am open to suggestions/pull request to improve this program.
You will find the following commands useful:
- Clones the github repository of this project:
```bash
git clone https://github.com/lillallol/outline-pdf-data-structure
```
- Installs the node modules (nothing will work without them):
```bash
npm install
```
- Tests the code and generates code coverage:
```bash
npm run test
```
The generated code coverage is saved in `./coverage`.
- Lints the source folder using typescript and eslint:
```bash
npm run lint
```
- Builds the typescript code from the `./src` folder to javascript code in `./dist`:
```bash
npm run build-ts
```
- Injects in place the generated toc and imported files to `README.md`:
```bash
npm run build-md
```
- Checks the project for spelling mistakes:
```bash
npm run spell-check
```
Take a look at the related configuration `./cspell.json`.
- Checks `./src` for dead typescript files:
```bash
npm run dead-files
```
Take a look at the related configuration `./unimportedrc.json`.
## Changelog
### 1.0.3
- Added documentation section in `README.md`. The documentation was generated via [ts-doc-gen-md](https://www.npmjs.com/package/ts-doc-gen-md) and added in the `README.md` via [md-in-place](https://www.npmjs.com/package/md-in-place).
- Added changelog section in `README.md`.
- Added contributing section in `README.md`.
- Added example section in `README.md`.
- Did some internal changes in the code (added `tagUnindent`, created files: `errorMessages.ts`,`constants.ts`,`publicApi.ts`).
### 1.0.2
**bug fixes**
- Changed count to be number of descendants instead of immediate descendants.
### 1.0.1
Some minor link fixes in the `README.md`.
### 1.0.0
First release.
## License
MIT
@@ -0,0 +1,3 @@
export declare const constants: {
trimmedTocLineValidPattern: RegExp;
};
@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.constants = void 0;
exports.constants = {
trimmedTocLineValidPattern: /^(?<collapse>[+\-]?)(?<pageNumber>\d+)\|(?<depth>-*)\|(?<title>.*)$/
};
@@ -0,0 +1,6 @@
import { IOutline } from "../publicApi";
/**
* @description Returns the index of the last immediate child for the provided outline node.
* It throws if there is no child.
*/
export declare function getIndexOfLastImmediateChild(outline: IOutline, i: number): number;
@@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getIndexOfLastImmediateChild = void 0;
const errorMessages_1 = require("../errorMessages");
/**
* @description Returns the index of the last immediate child for the provided outline node.
* It throws if there is no child.
*/
function getIndexOfLastImmediateChild(outline, i) {
let candidateIndex;
const parentDepth = outline[i].depth;
for (let ii = i + 1; ii < outline.length; ii++) {
if (outline[ii].depth <= parentDepth)
break;
if (outline[ii].depth === parentDepth + 1)
candidateIndex = ii;
}
if (candidateIndex === undefined)
throw Error(errorMessages_1.internalErrorMessages.internalLibraryError);
return candidateIndex;
}
exports.getIndexOfLastImmediateChild = getIndexOfLastImmediateChild;
@@ -0,0 +1,6 @@
import { IOutline } from "../publicApi";
/**
* @description It returns the index of the immediate next sibling for the specified outline node.
* It throws if it does not exist.
*/
export declare function getIndexOfImmediateNextSibling(outline: IOutline, i: number): number;
@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getIndexOfImmediateNextSibling = void 0;
const errorMessages_1 = require("../errorMessages");
/**
* @description It returns the index of the immediate next sibling for the specified outline node.
* It throws if it does not exist.
*/
function getIndexOfImmediateNextSibling(outline, i) {
const contextDepth = outline[i].depth;
for (let ii = i + 1; ii < outline.length; ii++) {
if (outline[ii].depth < contextDepth)
break;
if (outline[ii].depth === contextDepth)
return ii;
}
throw Error(errorMessages_1.internalErrorMessages.internalLibraryError);
}
exports.getIndexOfImmediateNextSibling = getIndexOfImmediateNextSibling;
@@ -0,0 +1,6 @@
import type { IOutline } from "../publicApi";
/**
* @description It returns the index of the parent of the provided outline node.
* It returns `-1` when the provided outline node has zero depth.
*/
export declare function getIndexOfImmediateParent(outline: IOutline, i: number): number;
@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getIndexOfImmediateParent = void 0;
/**
* @description It returns the index of the parent of the provided outline node.
* It returns `-1` when the provided outline node has zero depth.
*/
function getIndexOfImmediateParent(outline, i) {
const contextDepth = outline[i].depth;
for (let ii = i; ii > -1; ii--) {
if (contextDepth - 1 === outline[ii].depth)
return ii;
}
return -1;
}
exports.getIndexOfImmediateParent = getIndexOfImmediateParent;
@@ -0,0 +1,6 @@
import type { IOutline } from "../publicApi";
/**
* @description It returns the index of the immediate previous sibling for the provided outline node,
* or throws if it does not find one.
*/
export declare function getIndexOfImmediatePreviousSibling(outline: IOutline, i: number): number;
@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getIndexOfImmediatePreviousSibling = void 0;
const errorMessages_1 = require("../errorMessages");
/**
* @description It returns the index of the immediate previous sibling for the provided outline node,
* or throws if it does not find one.
*/
function getIndexOfImmediatePreviousSibling(outline, i) {
const contextDepth = outline[i].depth;
for (let ii = i - 1; ii > -1; ii--) {
if (outline[ii].depth < contextDepth)
break;
if (outline[ii].depth === contextDepth)
return ii;
}
throw Error(errorMessages_1.internalErrorMessages.internalLibraryError);
}
exports.getIndexOfImmediatePreviousSibling = getIndexOfImmediatePreviousSibling;
@@ -0,0 +1,5 @@
import type { IOutline } from "../publicApi";
/**
* @description It returns the number of descendant outline nodes of the provided outline node.
*/
export declare function getNumberOfDescendants(outline: IOutline, i: number): number;
@@ -0,0 +1,20 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getNumberOfDescendants = void 0;
/**
* @description It returns the number of descendant outline nodes of the provided outline node.
*/
function getNumberOfDescendants(outline, i) {
let count = 0;
const contextDepth = outline[i].depth;
for (let ii = i + 1; ii < outline.length; ii++) {
if (contextDepth < outline[ii].depth) {
count++;
}
else {
break;
}
}
return count;
}
exports.getNumberOfDescendants = getNumberOfDescendants;
@@ -0,0 +1,5 @@
import type { IOutline } from "../publicApi";
/**
* @description Returns a predicate on whether the provided outline node has a child.
*/
export declare function hasChild(outline: IOutline, i: number): boolean;
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasChild = void 0;
/**
* @description Returns a predicate on whether the provided outline node has a child.
*/
function hasChild(outline, i) {
if (i === outline.length - 1)
return false;
return outline[i].depth + 1 === outline[i + 1].depth;
}
exports.hasChild = hasChild;
@@ -0,0 +1,5 @@
import type { IOutline } from "../publicApi";
/**
* @description It returns a predicate on whether the provided outline node has an immediate next sibling.
*/
export declare function hasImmediateNextSibling(outline: IOutline, i: number): boolean;
@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasImmediateNextSibling = void 0;
const getIndexOfImmediateNextSibling_1 = require("./getIndexOfImmediateNextSibling");
/**
* @description It returns a predicate on whether the provided outline node has an immediate next sibling.
*/
function hasImmediateNextSibling(outline, i) {
try {
getIndexOfImmediateNextSibling_1.getIndexOfImmediateNextSibling(outline, i);
return true;
}
catch (e) {
return false;
}
}
exports.hasImmediateNextSibling = hasImmediateNextSibling;
@@ -0,0 +1,5 @@
import type { IOutline } from "../publicApi";
/**
* @description It returns a predicate on whether the provided outline node has an immediate previous sibling.
*/
export declare function hasImmediatePreviousSibling(outline: IOutline, i: number): boolean;
@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasImmediatePreviousSibling = void 0;
const getIndexOfImmediatePreviousSibling_1 = require("./getIndexOfImmediatePreviousSibling");
/**
* @description It returns a predicate on whether the provided outline node has an immediate previous sibling.
*/
function hasImmediatePreviousSibling(outline, i) {
try {
getIndexOfImmediatePreviousSibling_1.getIndexOfImmediatePreviousSibling(outline, i);
return true;
}
catch (e) {
return false;
}
}
exports.hasImmediatePreviousSibling = hasImmediatePreviousSibling;
@@ -0,0 +1,6 @@
import type { outlinePdfDataStructureReturnType } from "../publicApi";
/**
* @description
* It returns all the information needed to create a real pdf data structure.
*/
export declare function outlinePdfDataStructure(inputOutline: string, totalNumberOfPages: number): outlinePdfDataStructureReturnType;
@@ -0,0 +1,46 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.outlinePdfDataStructure = void 0;
const hasChild_1 = require("./hasChild");
const getIndexOfImmediateLastChild_1 = require("./getIndexOfImmediateLastChild");
const getIndexOfImmediateNextSibling_1 = require("./getIndexOfImmediateNextSibling");
const getIndexOfImmediatePreviousSibling_1 = require("./getIndexOfImmediatePreviousSibling");
const hasImmediatePreviousSibling_1 = require("./hasImmediatePreviousSibling");
const hasImmediateNextSibling_1 = require("./hasImmediateNextSibling");
const getIndexOfImmediateParentFactory_1 = require("./getIndexOfImmediateParentFactory");
const printedToOutline_1 = require("./printedToOutline");
const getNumberOfDescendants_1 = require("./getNumberOfDescendants");
/**
* @description
* It returns all the information needed to create a real pdf data structure.
*/
function outlinePdfDataStructure(inputOutline, totalNumberOfPages) {
const outlineItems = [];
const outline = printedToOutline_1.printedToOutline(inputOutline, totalNumberOfPages);
outline.forEach((node, i) => {
node.count = getNumberOfDescendants_1.getNumberOfDescendants(outline, i) * (node.collapse ? -1 : 1);
});
for (let i = 0; i < outline.length; i++) {
outlineItems[i] = {
Title: outline[i].title,
Parent: getIndexOfImmediateParentFactory_1.getIndexOfImmediateParent(outline, i),
...(hasImmediatePreviousSibling_1.hasImmediatePreviousSibling(outline, i) && {
Prev: getIndexOfImmediatePreviousSibling_1.getIndexOfImmediatePreviousSibling(outline, i),
}),
...(hasImmediateNextSibling_1.hasImmediateNextSibling(outline, i) && {
Next: getIndexOfImmediateNextSibling_1.getIndexOfImmediateNextSibling(outline, i),
}),
...(hasChild_1.hasChild(outline, i) && {
First: i + 1,
Last: getIndexOfImmediateLastChild_1.getIndexOfLastImmediateChild(outline, i),
Count: outline[i].count,
}),
Dest: outline[i].pageNumber - 1,
};
}
return {
outlineItems: outlineItems,
outlineRootCount: outline.length,
};
}
exports.outlinePdfDataStructure = outlinePdfDataStructure;
@@ -0,0 +1,26 @@
import type { IOutline } from "../publicApi";
/**
* @description Converts the outline string representation to its programmatic representation.
* It throws if the outline string representation is not valid.
* ```ts
* //input
* printedToOutline(`
* 1||Document
* 2|-|Section 1
* -3|-|Section 2
* 4|--|Subsection 1
* 5|-|Section 3
* 6||Summary
* `,6)
* //output
* [
* { pageNumber: 1, depth: 0, title: "Document" , collapse : false , line : "1||Document"},
* { pageNumber: 2, depth: 1, title: "Section 1" , collapse : false , line : "2|-|Section 1"},
* { pageNumber: 3, depth: 1, title: "Section 2" , collapse : true , line : "-3|-|Section 2"},
* { pageNumber: 4, depth: 2, title: "Subsection 1" , collapse : false , line : "4|--|Subsection 1"},
* { pageNumber: 5, depth: 1, title: "Section 3" , collapse : false , line : "5|-|Section 3"},
* { pageNumber: 6, depth: 0, title: "Summary" , collapse : false , line : "6||Summary"},
* ]
* ```
*/
export declare function printedToOutline(inputOutline: string, totalNumberOfPages: number): IOutline;
@@ -0,0 +1,89 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.printedToOutline = void 0;
const constants_1 = require("../constants");
const errorMessages_1 = require("../errorMessages");
/**
* @description Converts the outline string representation to its programmatic representation.
* It throws if the outline string representation is not valid.
* ```ts
* //input
* printedToOutline(`
* 1||Document
* 2|-|Section 1
* -3|-|Section 2
* 4|--|Subsection 1
* 5|-|Section 3
* 6||Summary
* `,6)
* //output
* [
* { pageNumber: 1, depth: 0, title: "Document" , collapse : false , line : "1||Document"},
* { pageNumber: 2, depth: 1, title: "Section 1" , collapse : false , line : "2|-|Section 1"},
* { pageNumber: 3, depth: 1, title: "Section 2" , collapse : true , line : "-3|-|Section 2"},
* { pageNumber: 4, depth: 2, title: "Subsection 1" , collapse : false , line : "4|--|Subsection 1"},
* { pageNumber: 5, depth: 1, title: "Section 3" , collapse : false , line : "5|-|Section 3"},
* { pageNumber: 6, depth: 0, title: "Summary" , collapse : false , line : "6||Summary"},
* ]
* ```
*/
function printedToOutline(inputOutline, totalNumberOfPages) {
if (inputOutline.trim() === "")
throw Error(errorMessages_1.errorMessages.emptyOutline);
let lastNode;
const toReturn = inputOutline
.trim()
.split("\n")
.map((untrimmedLine, i) => {
const line = untrimmedLine.trim();
const match = line.match(constants_1.constants.trimmedTocLineValidPattern);
if (match === null)
throw Error(errorMessages_1.errorMessages.wrongPatternInLine(line));
const { groups } = match;
if (groups === undefined)
throw Error(errorMessages_1.internalErrorMessages.internalLibraryError);
const { pageNumber, depth, title, collapse } = groups;
if (pageNumber === undefined)
throw Error(errorMessages_1.internalErrorMessages.internalLibraryError);
if (title === undefined)
throw Error(errorMessages_1.internalErrorMessages.internalLibraryError);
const nodeToReturn = {
pageNumber: Number(pageNumber),
depth: depth === undefined ? 0 : depth.length,
title: title,
collapse: (() => {
if (collapse === undefined)
return false;
if (collapse === "-")
return true;
if (collapse === "+")
return false;
if (collapse === "")
return false;
throw Error(errorMessages_1.internalErrorMessages.internalLibraryError);
})(),
line: line,
};
if (nodeToReturn.pageNumber === 0)
throw Error(errorMessages_1.errorMessages.zeroPageInOutlineIsNotAllowed(line));
if (nodeToReturn.pageNumber > totalNumberOfPages)
throw Error(errorMessages_1.errorMessages.pageNumberInOutlineExceedsMaximum(line, totalNumberOfPages));
if (i === 0 && nodeToReturn.depth !== 0)
throw Error(errorMessages_1.errorMessages.depthOfOutlineHasToStartWithZero);
if (i !== 0) {
if (!(nodeToReturn.depth <= lastNode.depth + 1)) {
throw Error(errorMessages_1.errorMessages.wrongDepthDisplacement(lastNode.line, nodeToReturn.line));
}
if (nodeToReturn.pageNumber < lastNode.pageNumber) {
throw Error(errorMessages_1.errorMessages.invalidDisplacementOfPage(lastNode.line, nodeToReturn.line));
}
if (lastNode.collapse && lastNode.depth >= nodeToReturn.depth) {
throw Error(errorMessages_1.errorMessages.nodeIsCollapsedWithoutChildren(lastNode.line));
}
}
lastNode = nodeToReturn;
return nodeToReturn;
});
return toReturn;
}
exports.printedToOutline = printedToOutline;
@@ -0,0 +1,13 @@
export declare const errorMessages: {
nodeIsCollapsedWithoutChildren: (line: string) => string;
emptyOutline: string;
wrongDepthDisplacement: (oldLine: string, newLine: string) => string;
zeroPageInOutlineIsNotAllowed: (line: string) => string;
pageNumberInOutlineExceedsMaximum: (line: string, max: number) => string;
depthOfOutlineHasToStartWithZero: string;
wrongPatternInLine: (line: string) => string;
invalidDisplacementOfPage: (oldLine: string, newLine: string) => string;
};
export declare const internalErrorMessages: {
internalLibraryError: string;
};
@@ -0,0 +1,68 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.internalErrorMessages = exports.errorMessages = void 0;
const constants_1 = require("./constants");
const es_utils_1 = require("./es-utils");
exports.errorMessages = {
nodeIsCollapsedWithoutChildren: (line) => es_utils_1.tagUnindent `
Outline node:
${line}
has no children and it is collapsed. You have to un collapse it or add children.
`,
emptyOutline: "no outline has been provided",
wrongDepthDisplacement: (oldLine, newLine) => es_utils_1.tagUnindent `
Wrong depth displacement for the following part of the outline:
${oldLine}
${newLine}
`,
zeroPageInOutlineIsNotAllowed: (line) => es_utils_1.tagUnindent `
Zero page number is not allowed in outline:
${line}
`,
pageNumberInOutlineExceedsMaximum: (line, max) => es_utils_1.tagUnindent `
Pdf file has:
${max}
number of pages and outline points out of this range:
${line}
`,
depthOfOutlineHasToStartWithZero: `The outline should start with zero depth.`,
wrongPatternInLine: (line) => es_utils_1.tagUnindent `
The line:
${line}
has wrong pattern. It does satisfy the following regular expression:
${constants_1.constants.trimmedTocLineValidPattern.source}
`,
invalidDisplacementOfPage: (oldLine, newLine) => es_utils_1.tagUnindent `
The page is not displaced correctly:
${oldLine}
${newLine}
`
};
exports.internalErrorMessages = {
internalLibraryError: es_utils_1.tagUnindent `
Something went wrong. If you have not used the library in a way
it is not supposed to be used, then copy this error message and
open an issue here:
https://github.com/lillallol/outline-pdf-data-structure/issues
`,
};
@@ -0,0 +1,19 @@
/**
* @description
* It returns the common maximum indentation length among the lines of the
* provided string. Lines that are only spaces are not taken into account.
*
* It throws error if a `\s` character that is not ` ` (space) is encountered
* in the indentation.
* @example
* commonMinIndentLength(
* ` hello
* world!
* `);
* //returns
* 3;
*/
export declare function commonMaxIndentLength(s: string): number;
export declare const _errorMessages: {
badIndentSpaceCharacter: string;
};
@@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports._errorMessages = exports.commonMaxIndentLength = void 0;
/**
* @description
* It returns the common maximum indentation length among the lines of the
* provided string. Lines that are only spaces are not taken into account.
*
* It throws error if a `\s` character that is not ` ` (space) is encountered
* in the indentation.
* @example
* commonMinIndentLength(
* ` hello
* world!
* `);
* //returns
* 3;
*/
function commonMaxIndentLength(s) {
let minIndentLength = Infinity;
s.split("\n").forEach((line) => {
if (/^[ ]+$/.test(line) || line.length === 0)
return;
let newMinIndentLength = 0;
for (let i = 0; i < line.length; i++) {
if (/\s/.test(line[i]) && line[i] !== " ")
throw Error(exports._errorMessages.badIndentSpaceCharacter);
if (line[i] !== " ")
break;
newMinIndentLength++;
}
if (newMinIndentLength < minIndentLength)
minIndentLength = newMinIndentLength;
});
return minIndentLength;
}
exports.commonMaxIndentLength = commonMaxIndentLength;
exports._errorMessages = {
badIndentSpaceCharacter: "Only space characters are allowed in the indented part of the string",
};
@@ -0,0 +1,5 @@
/**
* @description
* Return the last line of the provided string.
*/
export declare function getLastLineOfString(string: string): string;
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLastLineOfString = void 0;
/**
* @description
* Return the last line of the provided string.
*/
function getLastLineOfString(string) {
const lines = string.split("\n");
return lines[lines.length - 1];
}
exports.getLastLineOfString = getLastLineOfString;
@@ -0,0 +1 @@
export { tagUnindent } from "./tagUnindent";
@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.tagUnindent = void 0;
var tagUnindent_1 = require("./tagUnindent");
Object.defineProperty(exports, "tagUnindent", { enumerable: true, get: function () { return tagUnindent_1.tagUnindent; } });
@@ -0,0 +1,30 @@
/**
* @description
* Tag function that returns the template literal it is provided as a string, but
* with its common maximum indentation removed.
*
* The placeholders that are provided as single string element arrays are multi-line indented.
*
* It throws if :
*
* - the first line is non empty
* - the last line is not only spaces
* - the string contains `\s` characters that are not space characters in its indentation
*
* @example
* expect(
* tagUnindent`
* path : (${`"./some/where"`})
* index : ${0}
* message :
* ${["hello\nworld"]}
* `
* ).toBe(
* `path : ("./some/where")\n` +
* `index : 0\n` +
* `message : \n` +
* ` hello\n` +
* ` world`
* );
*/
export declare function tagUnindent(stringArray: TemplateStringsArray, ...placeholders: (number | string | [string])[]): string;
@@ -0,0 +1,65 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.tagUnindent = void 0;
const getLastLineOfString_1 = require("./getLastLineOfString");
const unindent_1 = require("./unindent");
/**
* @description
* Tag function that returns the template literal it is provided as a string, but
* with its common maximum indentation removed.
*
* The placeholders that are provided as single string element arrays are multi-line indented.
*
* It throws if :
*
* - the first line is non empty
* - the last line is not only spaces
* - the string contains `\s` characters that are not space characters in its indentation
*
* @example
* expect(
* tagUnindent`
* path : (${`"./some/where"`})
* index : ${0}
* message :
* ${["hello\nworld"]}
* `
* ).toBe(
* `path : ("./some/where")\n` +
* `index : 0\n` +
* `message : \n` +
* ` hello\n` +
* ` world`
* );
*/
function tagUnindent(stringArray, ...placeholders) {
const stringToUnindent = (() => {
if (placeholders.length === 0)
return stringArray[0];
let toReturn = "";
for (let i = 0; i < placeholders.length; i++) {
const currentPlaceholder = placeholders[i];
if (Array.isArray(currentPlaceholder)) {
const lastStringArrayLineLength = getLastLineOfString_1.getLastLineOfString(stringArray[i]).length;
const [placeholderSingleElementArrayString] = currentPlaceholder;
toReturn =
toReturn +
stringArray[i] +
placeholderSingleElementArrayString
.split("\n")
.map((line, i) => {
if (i === 0)
return line;
return " ".repeat(lastStringArrayLineLength) + line;
})
.join("\n");
}
else {
toReturn = toReturn + stringArray[i] + currentPlaceholder;
}
}
return toReturn + stringArray[stringArray.length - 1];
})();
return unindent_1.unindent(stringToUnindent);
}
exports.tagUnindent = tagUnindent;
@@ -0,0 +1,42 @@
/**
* @description
* It removes the first and last line of the provided string. From the remaining lines it
* calculates the common maximum indentation, ignoring empty lines, and then it subtracts it from
* each non empty line.
*
* The subtraction happens also for empty lines that have length bigger than the common minimum
* indentation length. If they have length less than the common minimum indentation length they are
* replaced with an empty string;
*
* - If `\s` characters are encountered in the indentation string that are not ` ` (space), it throws error.
* - If the first line is not an empty string, it throws error.
* - If the last line is not only space characters it throws error.
* @example
* unindent(
* `
* transaction tree string representation
*
* |_ "p"
* |_ "a"
* |_ "t"
* |_ "h"
* | |_ "prop1" -1 => 1
* |_ "t"
* |_ "prop" "11" => "1"
* `);
* //returns
* ` transaction tree string representation
*
* |_ "p"
* |_ "a"
* |_ "t"
* |_ "h"
* | |_ "prop1" -1 => 1
* |_ "t"
* |_ "prop" "11" => "1"`;
*/
export declare function unindent(s: string): string;
export declare const _errorMessage: {
badFirstLine: string;
badLastLine: string;
};
@@ -0,0 +1,63 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports._errorMessage = exports.unindent = void 0;
const commonMaxIndentLength_1 = require("./commonMaxIndentLength");
/**
* @description
* It removes the first and last line of the provided string. From the remaining lines it
* calculates the common maximum indentation, ignoring empty lines, and then it subtracts it from
* each non empty line.
*
* The subtraction happens also for empty lines that have length bigger than the common minimum
* indentation length. If they have length less than the common minimum indentation length they are
* replaced with an empty string;
*
* - If `\s` characters are encountered in the indentation string that are not ` ` (space), it throws error.
* - If the first line is not an empty string, it throws error.
* - If the last line is not only space characters it throws error.
* @example
* unindent(
* `
* transaction tree string representation
*
* |_ "p"
* |_ "a"
* |_ "t"
* |_ "h"
* | |_ "prop1" -1 => 1
* |_ "t"
* |_ "prop" "11" => "1"
* `);
* //returns
* ` transaction tree string representation
*
* |_ "p"
* |_ "a"
* |_ "t"
* |_ "h"
* | |_ "prop1" -1 => 1
* |_ "t"
* |_ "prop" "11" => "1"`;
*/
function unindent(s) {
const lines = s.split("\n");
if (lines[0] !== "")
throw Error(exports._errorMessage.badFirstLine);
if (!/^[ ]*$/.test(lines[lines.length - 1]))
throw Error(exports._errorMessage.badLastLine);
const minIndentLength = commonMaxIndentLength_1.commonMaxIndentLength(s);
return s
.split("\n")
.slice(1, -1)
.map((line) => {
if (/^\s*$/.test(line) && line.length < minIndentLength)
return "";
return line.slice(minIndentLength, line.length);
})
.join("\n");
}
exports.unindent = unindent;
exports._errorMessage = {
badFirstLine: `first line has to be an empty string`,
badLastLine: "last line has to be only space characters",
};
@@ -0,0 +1 @@
export { outlinePdfDataStructure } from "./core/outlinePdfDataStructure";
@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.outlinePdfDataStructure = void 0;
var outlinePdfDataStructure_1 = require("./core/outlinePdfDataStructure");
Object.defineProperty(exports, "outlinePdfDataStructure", { enumerable: true, get: function () { return outlinePdfDataStructure_1.outlinePdfDataStructure; } });
@@ -0,0 +1,76 @@
export declare type IOutline = IOutlineNode[];
export declare type IOutlineNode = {
depth: number;
pageNumber: number;
title: string;
collapse: boolean;
count?: number;
line: string;
};
export declare type outlinePdfDataStructureReturnType = {
/**
* @description
* It returns a low level programmatic representation of the outline.
*/
outlineItems: outlineItem[];
/**
* @description
* The total number of outline nodes.
*/
outlineRootCount: number;
};
export declare type outlineItem = {
/**
* @description
* The title that will be visible in the outline of the pdf for the context
* outline node.
*/
Title: string;
/**
* @description
* The index (of the array that contains all the outline nodes) of the
* parent outline node of the context outline node.
*
* Outline nodes of depth `0` have `-1` for this value.
*/
Parent: number;
/**
* @description
* The index (of the array that contains all the outline nodes) of the
* previous sibling of the context outline node. It is `undefined` for the
* case there is no previous sibling.
*/
Prev?: number;
/**
* @description
* The index (of the array that contains all the outline nodes) of the next
* sibling of the context outline node. It is `undefined` for the case there
* is no next sibling.
*/
Next?: number;
/**
* @description
* The index (of the array that contains all the outline nodes) of the first
* immediate child of the context outline node. It is `undefined` for the
* case there is no immediate child.
*/
First?: number;
/**
* @description
* The index (of the array that contains all the outline nodes) of the last
* immediate child of the context outline node. It is `undefined` for the
* case there is no immediate child.
*/
Last?: number;
/**
* @description
* Total number of outline nodes that are descendants to the context outline
* node.
*/
Count?: number;
/**
* @description
* The page of the pdf that the outline node hyper links to.
*/
Dest: number;
};
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,60 @@
{
"name": "@lillallol/outline-pdf-data-structure",
"version": "1.0.3",
"description": "Create a pdf outline data structure from a high level string representation of pdf outline. This data structure can be used to create a real pdf outline.",
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"scripts": {
"check-updates": "ncu",
"update": "npx ncu -u;npm install",
"dead-files": "unimported",
"spell-check": "cspell \"./**/*\"",
"lint": "tsc --noEmit && eslint ./src/**/*.ts --max-warnings=0",
"test": "rm -rf ./coverage && npx jest --clearCache && npx jest ./src --coverage --silent",
"build-ts": "rm -rf ./dist;npx tsc -p ./tsconfig.build.json",
"build-md": "rm -rf ./documentation.md; node ./doc-gen.js; npx md-in-place",
"build": "npm run build-ts;npm run build-md;",
"pre-bump": "npm run dead-files && npm run spell-check && npm run lint && npm run test && npm run build && git add --all && git commit",
"release": "npm run pre-bump && npm publish --access public && npm run after-bump",
"bump-patch": "npm run pre-bump && npm version patch && npm publish && npm run after-bump",
"bump-minor": "npm run pre-bump && npm version minor && npm publish && npm run after-bump",
"bump-major": "npm run pre-bump && npm version major && npm publish && npm run after-bump",
"after-bump": "git push origin master && rm -rf ./dist"
},
"repository": {
"type": "git",
"url": "git+https://github.com/lillallol/outline-pdf-data-structure.git"
},
"keywords": [
"outline",
"pdf",
"index",
"data-structure",
"bookmarks",
"pdf specification"
],
"author": "lillallol",
"license": "MIT",
"files": [
"dist"
],
"bugs": {
"url": "https://github.com/lillallol/outline-pdf-data-structure/issues"
},
"homepage": "https://github.com/lillallol/outline-pdf-data-structure#readme",
"devDependencies": {
"@types/jest": "^26.0.23",
"@typescript-eslint/eslint-plugin": "^4.23.0",
"@typescript-eslint/parser": "^4.23.0",
"cspell": "^5.4.1",
"eslint": "^7.26.0",
"jest": "^26.6.3",
"md-in-place": "^1.0.1",
"npm-check-updates": "^11.5.13",
"prettier": "^2.3.0",
"ts-doc-gen-md": "*",
"ts-jest": "^26.5.6",
"typescript": "^4.2.4",
"unimported": "^1.11.0"
}
}
+11
View File
@@ -0,0 +1,11 @@
MIT License
Copyright (c) 2020 lillallol
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+579
View File
@@ -0,0 +1,579 @@
# outline-pdf
> **NOTICE**: If you have outlined your pdf using a version from 2.0.0 to 3.0.3, then if you edit your pdf it will get corrupted due to a bug introduced by my module. From version 3.0.4 this bug has been resolved.
## Table of contents
<!--#region toc-->
- [Table of contents](#table-of-contents)
- [Installation](#installation)
- [Description](#description)
- [Code coverage](#code-coverage)
- [Example](#example)
- [Documentation](#documentation)
- [Concretions](#concretions)
- [outlinePdfFactory](#----outlinepdffactory)
- [Motivation](#motivation)
- [You might find interesting](#you-might-find-interesting)
- [Acknowledgments](#acknowledgments)
- [FAQs](#faqs)
- [Contributing](#contributing)
- [Help needed](#help-needed)
- [Changelog](#changelog)
- [4.0.0](#400)
- [3.0.4](#304)
- [3.0.3](#303)
- [3.0.2](#302)
- [3.0.1](#301)
- [3.0.0](#300)
- [2.0.4](#204)
- [2.0.3](#203)
- [2.0.2](#202)
- [2.0.1](#201)
- [2.0.0](#200)
- [1.0.2](#102)
- [1.0.1](#101)
- [1.0.0](#100)
- [License](#license)
<!--#endregion toc-->
## Installation
```bash
npm install @lillallol/outline-pdf
```
## Description
> Take a look at [outline-pdf-cjs](https://www.npmjs.com/package/@lillallol/outline-pdf-cjs) if you are using node.
Adds outline to pdf that do not have one. As of right now, it is the only module in the npm registry that has the following characteristics :
- Only javascript dependencies. No code from other programming languages is used.
- Enables collapsing and nesting in the outline.
- Fast download from the npm registry.
- High level API.
- Works in both the browser and node.
## Code coverage
Testing code coverage is more than 90%.
## Example
<!--#region example !./src/index.test.ts-->
```ts
import * as fs from "fs";
import * as path from "path";
import * as pdfLib from "pdf-lib";
import { outlinePdfFactory } from ".";
jest.setTimeout(60 * 10e3);
describe(outlinePdfFactory.name, () => {
it("adds the provided outline to the loaded pdf", async () => {
const outlinePdf = outlinePdfFactory(pdfLib);
const absolutePathToPdfToOutline = path.resolve(__dirname, "test.pdf");
const absolutePathToSaveOutlinedPdf = path.resolve(__dirname, "test.outline.pdf");
try {
fs.unlinkSync(absolutePathToSaveOutlinedPdf);
} catch (e) {
//file does not exist so no problem
}
const pdf = fs.readFileSync(absolutePathToPdfToOutline, { encoding: "base64" });
const outline: string = `
1||Some random title 1
2|-|Some random title 2
-3|--|Some random title 3
4|---|Some random title 4
5|---|Some random title 5
6|-|Some random title 6
7||Some random title 7
`;
const outlinedPdf = await outlinePdf({ outline, pdf }).then((pdfDocument) => pdfDocument.save());
fs.writeFileSync(absolutePathToSaveOutlinedPdf, outlinedPdf);
});
});
```
<!--#endregion example-->
## Documentation
<!--#region documentation ./documentation.md -->
<h3 id="-concretions">Concretions</h3>
<h4 id="-concretion-outlinePdfFactory">
outlinePdfFactory
</h4>
```ts
export declare const outlinePdfFactory: IOutlinePdfFactory;
```
<details open="">
<summary id="-concretion-outlinePdfFactory-references">
<a href="#-concretion-outlinePdfFactory-references">#</a>
references
</summary>
<br>
<blockquote>
<details>
<summary id="-concretion-outlinePdfFactory-references-IOutlinePdfFactory">
<a href="#-concretion-outlinePdfFactory-references-IOutlinePdfFactory">#</a>
<b>IOutlinePdfFactory</b>
</summary>
```ts
export declare type IOutlinePdfFactory = (pdfLib: pdfLib) => IOutlinePdf;
```
</details>
<blockquote>
<details>
<summary id="-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib">
<a href="#-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib">#</a>
<b>pdfLib</b>
</summary>
```ts
/**
* @description
* Just use:
*
* ```ts
* import * as pdfLib from "pdf-lib";
* ```
*/
export declare type pdfLib = {
PDFDict: typeof PDFDict;
PDFName: typeof PDFName;
PDFNumber: typeof PDFNumber;
PDFRef: typeof PDFRef;
PDFArray: typeof PDFArray;
PDFNull: typeof PDFNull;
PDFDocument: typeof PDFDocument;
PDFPageLeaf: typeof PDFPageLeaf;
PDFHexString: typeof PDFHexString;
};
```
</details>
<blockquote>
<details>
<summary id="-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFDict">
<a href="#-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFDict">#</a>
<b>PDFDict</b>
</summary>
```ts
import type { PDFDict } from "pdf-lib";
```
</details>
<details>
<summary id="-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFName">
<a href="#-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFName">#</a>
<b>PDFName</b>
</summary>
```ts
import type { PDFName } from "pdf-lib";
```
</details>
<details>
<summary id="-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFNumber">
<a href="#-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFNumber">#</a>
<b>PDFNumber</b>
</summary>
```ts
import type { PDFNumber } from "pdf-lib";
```
</details>
<details>
<summary id="-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFRef">
<a href="#-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFRef">#</a>
<b>PDFRef</b>
</summary>
```ts
import type { PDFRef } from "pdf-lib";
```
</details>
<details>
<summary id="-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFArray">
<a href="#-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFArray">#</a>
<b>PDFArray</b>
</summary>
```ts
import type { PDFArray } from "pdf-lib";
```
</details>
<details>
<summary id="-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFNull">
<a href="#-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFNull">#</a>
<b>PDFNull</b>
</summary>
```ts
import type { PDFNull } from "pdf-lib";
```
</details>
<details>
<summary id="-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFDocument">
<a href="#-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFDocument">#</a>
<b>PDFDocument</b>
</summary>
```ts
import type { PDFDocument } from "pdf-lib";
```
</details>
<details>
<summary id="-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFPageLeaf">
<a href="#-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFPageLeaf">#</a>
<b>PDFPageLeaf</b>
</summary>
```ts
import type { PDFPageLeaf } from "pdf-lib";
```
</details>
<details>
<summary id="-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFHexString">
<a href="#-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFHexString">#</a>
<b>PDFHexString</b>
</summary>
```ts
import type { PDFHexString } from "pdf-lib";
```
</details>
</blockquote><details>
<summary id="-concretion-outlinePdfFactory-references-IOutlinePdfFactory-IOutlinePdf">
<a href="#-concretion-outlinePdfFactory-references-IOutlinePdfFactory-IOutlinePdf">#</a>
<b>IOutlinePdf</b>
</summary>
```ts
export declare type IOutlinePdf = (_: {
/**
* @description
* The pdf to outline.
*
* I you provide the pdf as a string, then make sure it is of base 64.
*/
pdf: string | Uint8Array | ArrayBuffer | PDFDocument;
/**
* @description
* String representation of the outline.
*
* Example:
*
* ```ts
* `
* 1||some title 1
* 12|-|some title 2
* -30|--|some title 3
* 34|---|some title 4
* 35|---|some title 5
* 60|--|some title 6
* 67|-|some title 7
* 80||some title 8
* `
* ```
*
* where the:
*
* * first column is the page number, and if it negative it means that this
* part of the outline is collapsed
* * second column is the outline depth
* * third column is the outline title
*/
outline: string;
}) => Promise<PDFDocument>;
```
<p>
<a href="#-concretion-outlinePdfFactory-references-IOutlinePdfFactory-pdfLib-PDFDocument">
<b>PDFDocument</b>
</a>
</p>
</details>
<blockquote>
</blockquote>
</blockquote>
</blockquote>
</details>
<hr>
<!--#endregion documentation -->
## Motivation
I had to use this module in one of my projects: [scrap-it](https://www.npmjs.com/package/scrap-it). I could not find one that satisfied my needs, so I decided to create my own.
## You might find interesting
- [@lillallol/outline-pdf-cjs](https://www.npmjs.com/package/@lillallol/outline-pdf-cjs)
- the equivalent of the current module for node
- uses the current module
- [@lillallol/outline-pdf-data-structure](https://www.npmjs.com/package/@lillallol/outline-pdf-data-structure)
- creates a pdf outline data structure from a high level string representation of the pdf outline
- used by the current module
- [pdf-lib](https://www.npmjs.com/package/pdf-lib)
- low level pdf library for both browser and node
- [hummus-toc](https://www.npmjs.com/package/@ocelot-consulting/hummus-toc)
- uses [hummus](https://www.npmjs.com/package/hummus) which is deprecated
- only javascript dependencies
- takes a lot of time to download
- [pdfjs](https://www.npmjs.com/package/pdfjs)
- only javascript dependencies
- downloads fast
## Acknowledgments
The following comments : [1](https://github.com/Hopding/pdf-lib/issues/127#issuecomment-502450179) , [2](https://github.com/Hopding/pdf-lib/issues/127#issuecomment-641710694) , contributed tremendously in the creation of this module.
## FAQs
<details>
<summary>Why do I have to inject <code>pdf-lib</code> to use this module.</summary>
No <code>pdf-lib</code> code is hard coded in this module. When someone wants to use this module and also use <code>pdf-lib</code>, to create a new module, there will be no <code>pdf-lib</code> code repeated in the generated bundle.
</details>
## Contributing
I am open to suggestions/pull request to improve this program.
You will find the following commands useful:
- Clones the github repository of this project:
```bash
git clone https://github.com/lillallol/outline-pdf
```
- Installs the node modules (nothing will work without them):
```bash
npm install
```
- Tests the source code:
```bash
npm run test
```
- Lints the source folder using typescript and eslint:
```bash
npm run lint
```
- Builds the typescript code from the `./src` folder to javascript code in `./dist`:
```bash
npm run build-ts
```
- Injects in place the generated toc and imported files to `README.md`:
```bash
npm run build-md
```
- Checks the project for spelling mistakes:
```bash
npm run spell-check
```
Take a look at the related configuration `./cspell.json`.
- Checks `./src` for dead typescript files:
```bash
npm run dead-files
```
Take a look at the related configuration `./unimportedrc.json`.
- Logs in terminal which `dependencies` and `devDependencies` have a new version published in npm:
```bash
npm run check-updates
```
- Updates the `dependencies` and `devDependencies` to their latest version:
```bash
npm run update
```
- Formats all `.ts` files of the `./src` folder:
```bash
npm run format
```
### Help needed
I want to:
- read the outline of a pdf
- delete the outline of a pdf
using `pdf-lib`.
Finally I want to create the following tests:
- test that my module correctly outlines a pdf by reading the outline of the pdf that got outlined
- test that after outlining the pdf, any kind of pdf editing (pdf annotations for example), will not corrupt the outlined pdf
## Changelog
### 4.0.0
**Breaking changes**
- The interface of `outlinePdf` has been simplified.
**Other**
- Replaced the `README.md` browser example, with the test file of the entry point function.
- Added sections Code coverage, Documentation, Contributing in `README.md`.
### 3.0.4
**Bug fixes**
- Esm entry point was importing module `@lillallol/outline-pdf-data-structure` without `.js` extension.
- Fixed bug that was introduced from version 2.0.0 and was caused by creating more pdf refs than the number of outline nodes, which made the outlined pdf file get corrupted if it was edited with a pdf annotator.
**Other**
- Added `CHANGELOG.md`.
### 3.0.3
**bug fixes**
- Fixed bug that made the module crash if it had more outline nodes than pdf pages.
### 3.0.2
- Updated dependencies.
### 3.0.1
**bug fixes**
- Fixed the bug that made the outline work only if there was one outline node for each pdf page.
### 3.0.0
**breaking changes**
- `outlinePdfCjs` is now on its own package: `@lillallol/outline-pdf-cjs`.
### 2.0.4
- Internal changes.
### 2.0.3
**bug fixes**
- Outline does not bug with non english characters now.
### 2.0.2
- Minor changes in `README.md`.
### 2.0.1
- Minor changes in `README.md`.
### 2.0.0
**Breaking changes**
- `outlinePdf` is renamed to `outlinePdfCjsFactory`. You now have to inject `pdf-lib` in the factory.
- added `outlinePdfFactory`. It can outline pdf in both browser and nodejs. You need to inject `pdf-lib` in the factory.
**Other**
- added entry for esm
### 1.0.2
- Added error messages for some edge cases.
### 1.0.1
- Minor changes in `README.md`.
### 1.0.0
- Published the package.
## License
MIT
+1
View File
@@ -0,0 +1 @@
export { outlinePdfFactory } from "./outlinePdf/outlinePdfFactory";
+62
View File
@@ -0,0 +1,62 @@
import { PDFDict, PDFName, PDFNumber, PDFRef, PDFArray, PDFNull, PDFDocument, PDFPageLeaf, PDFHexString } from 'pdf-lib';
declare type IOutlinePdfFactory = (pdfLib: pdfLib) => IOutlinePdf;
/**
* @description
* Just use:
*
* ```ts
* import * as pdfLib from "pdf-lib";
* ```
*/
declare type pdfLib = {
PDFDict: typeof PDFDict;
PDFName: typeof PDFName;
PDFNumber: typeof PDFNumber;
PDFRef: typeof PDFRef;
PDFArray: typeof PDFArray;
PDFNull: typeof PDFNull;
PDFDocument: typeof PDFDocument;
PDFPageLeaf: typeof PDFPageLeaf;
PDFHexString: typeof PDFHexString;
};
declare type IOutlinePdf = (_: {
/**
* @description
* The pdf to outline.
*
* I you provide the pdf as a string, then make sure it is of base 64.
*/
pdf: string | Uint8Array | ArrayBuffer | PDFDocument;
/**
* @description
* String representation of the outline.
*
* Example:
*
* ```ts
* `
* 1||some title 1
* 12|-|some title 2
* -30|--|some title 3
* 34|---|some title 4
* 35|---|some title 5
* 60|--|some title 6
* 67|-|some title 7
* 80||some title 8
* `
* ```
*
* where the:
*
* * first column is the page number, and if it negative it means that this
* part of the outline is collapsed
* * second column is the outline depth
* * third column is the outline title
*/
outline: string;
}) => Promise<PDFDocument>;
declare const outlinePdfFactory: IOutlinePdfFactory;
export { outlinePdfFactory };
+809
View File
@@ -0,0 +1,809 @@
function unwrapExports (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
function createCommonjsModule(fn, basedir, module) {
return module = {
path: basedir,
exports: {},
require: function (path, base) {
return commonjsRequire(path, (base === undefined || base === null) ? module.path : base);
}
}, fn(module, module.exports), module.exports;
}
function commonjsRequire () {
throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
}
var createOutlineDictFactory_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.createOutlineDictFactory = void 0;
function createOutlineDictFactory(_) {
const { PDFDict, PDFName } = _;
return function createOutlineDict(doc, _) {
const outlinesDictMap = new Map();
outlinesDictMap.set(PDFName.Type, PDFName.of("Outlines"));
outlinesDictMap.set(PDFName.of("First"), _.First);
outlinesDictMap.set(PDFName.of("Last"), _.Last);
outlinesDictMap.set(PDFName.of("Count"), _.Count);
return PDFDict.fromMapWithContext(outlinesDictMap, doc.context);
};
}
exports.createOutlineDictFactory = createOutlineDictFactory;
});
unwrapExports(createOutlineDictFactory_1);
createOutlineDictFactory_1.createOutlineDictFactory;
var createOutlineNodeFactory_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.createOutlineNodeFactory = void 0;
function createOutlineNodeFactory(_) {
const { PDFDict, PDFName } = _;
return function createOutlineNode(doc, _) {
const map = new Map();
map.set(PDFName.Title, _.Title);
map.set(PDFName.Parent, _.Parent);
if (_.Prev !== undefined)
map.set(PDFName.of("Prev"), _.Prev);
if (_.Next !== undefined)
map.set(PDFName.of("Next"), _.Next);
if (_.First !== undefined)
map.set(PDFName.of("First"), _.First);
if (_.Last !== undefined)
map.set(PDFName.of("Last"), _.Last);
if (_.Count !== undefined)
map.set(PDFName.of("Count"), _.Count);
map.set(PDFName.of("Dest"), _.Dest);
return PDFDict.fromMapWithContext(map, doc.context);
};
}
exports.createOutlineNodeFactory = createOutlineNodeFactory;
});
unwrapExports(createOutlineNodeFactory_1);
createOutlineNodeFactory_1.createOutlineNodeFactory;
var getPageRefsFactory_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPageRefsFactory = void 0;
function getPageRefsFactory(_) {
const { PDFPageLeaf } = _;
return function getPageRefs(pdfDoc) {
const refs = [];
pdfDoc.catalog.Pages().traverse((kid, ref) => {
if (kid instanceof PDFPageLeaf)
refs.push(ref);
});
return refs;
};
}
exports.getPageRefsFactory = getPageRefsFactory;
});
unwrapExports(getPageRefsFactory_1);
getPageRefsFactory_1.getPageRefsFactory;
var hasChild_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasChild = void 0;
/**
* @description Returns a predicate on whether the provided outline node has a child.
*/
function hasChild(outline, i) {
if (i === outline.length - 1)
return false;
return outline[i].depth + 1 === outline[i + 1].depth;
}
exports.hasChild = hasChild;
});
unwrapExports(hasChild_1);
hasChild_1.hasChild;
var constants = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.constants = void 0;
exports.constants = {
trimmedTocLineValidPattern: /^(?<collapse>[+\-]?)(?<pageNumber>\d+)\|(?<depth>-*)\|(?<title>.*)$/
};
});
unwrapExports(constants);
constants.constants;
var getLastLineOfString_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLastLineOfString = void 0;
/**
* @description
* Return the last line of the provided string.
*/
function getLastLineOfString(string) {
const lines = string.split("\n");
return lines[lines.length - 1];
}
exports.getLastLineOfString = getLastLineOfString;
});
unwrapExports(getLastLineOfString_1);
getLastLineOfString_1.getLastLineOfString;
var commonMaxIndentLength_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports._errorMessages = exports.commonMaxIndentLength = void 0;
/**
* @description
* It returns the common maximum indentation length among the lines of the
* provided string. Lines that are only spaces are not taken into account.
*
* It throws error if a `\s` character that is not ` ` (space) is encountered
* in the indentation.
* @example
* commonMinIndentLength(
* ` hello
* world!
* `);
* //returns
* 3;
*/
function commonMaxIndentLength(s) {
let minIndentLength = Infinity;
s.split("\n").forEach((line) => {
if (/^[ ]+$/.test(line) || line.length === 0)
return;
let newMinIndentLength = 0;
for (let i = 0; i < line.length; i++) {
if (/\s/.test(line[i]) && line[i] !== " ")
throw Error(exports._errorMessages.badIndentSpaceCharacter);
if (line[i] !== " ")
break;
newMinIndentLength++;
}
if (newMinIndentLength < minIndentLength)
minIndentLength = newMinIndentLength;
});
return minIndentLength;
}
exports.commonMaxIndentLength = commonMaxIndentLength;
exports._errorMessages = {
badIndentSpaceCharacter: "Only space characters are allowed in the indented part of the string",
};
});
unwrapExports(commonMaxIndentLength_1);
commonMaxIndentLength_1._errorMessages;
commonMaxIndentLength_1.commonMaxIndentLength;
var unindent_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports._errorMessage = exports.unindent = void 0;
/**
* @description
* It removes the first and last line of the provided string. From the remaining lines it
* calculates the common maximum indentation, ignoring empty lines, and then it subtracts it from
* each non empty line.
*
* The subtraction happens also for empty lines that have length bigger than the common minimum
* indentation length. If they have length less than the common minimum indentation length they are
* replaced with an empty string;
*
* - If `\s` characters are encountered in the indentation string that are not ` ` (space), it throws error.
* - If the first line is not an empty string, it throws error.
* - If the last line is not only space characters it throws error.
* @example
* unindent(
* `
* transaction tree string representation
*
* |_ "p"
* |_ "a"
* |_ "t"
* |_ "h"
* | |_ "prop1" -1 => 1
* |_ "t"
* |_ "prop" "11" => "1"
* `);
* //returns
* ` transaction tree string representation
*
* |_ "p"
* |_ "a"
* |_ "t"
* |_ "h"
* | |_ "prop1" -1 => 1
* |_ "t"
* |_ "prop" "11" => "1"`;
*/
function unindent(s) {
const lines = s.split("\n");
if (lines[0] !== "")
throw Error(exports._errorMessage.badFirstLine);
if (!/^[ ]*$/.test(lines[lines.length - 1]))
throw Error(exports._errorMessage.badLastLine);
const minIndentLength = commonMaxIndentLength_1.commonMaxIndentLength(s);
return s
.split("\n")
.slice(1, -1)
.map((line) => {
if (/^\s*$/.test(line) && line.length < minIndentLength)
return "";
return line.slice(minIndentLength, line.length);
})
.join("\n");
}
exports.unindent = unindent;
exports._errorMessage = {
badFirstLine: `first line has to be an empty string`,
badLastLine: "last line has to be only space characters",
};
});
unwrapExports(unindent_1);
unindent_1._errorMessage;
unindent_1.unindent;
var tagUnindent_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.tagUnindent = void 0;
/**
* @description
* Tag function that returns the template literal it is provided as a string, but
* with its common maximum indentation removed.
*
* The placeholders that are provided as single string element arrays are multi-line indented.
*
* It throws if :
*
* - the first line is non empty
* - the last line is not only spaces
* - the string contains `\s` characters that are not space characters in its indentation
*
* @example
* expect(
* tagUnindent`
* path : (${`"./some/where"`})
* index : ${0}
* message :
* ${["hello\nworld"]}
* `
* ).toBe(
* `path : ("./some/where")\n` +
* `index : 0\n` +
* `message : \n` +
* ` hello\n` +
* ` world`
* );
*/
function tagUnindent(stringArray, ...placeholders) {
const stringToUnindent = (() => {
if (placeholders.length === 0)
return stringArray[0];
let toReturn = "";
for (let i = 0; i < placeholders.length; i++) {
const currentPlaceholder = placeholders[i];
if (Array.isArray(currentPlaceholder)) {
const lastStringArrayLineLength = getLastLineOfString_1.getLastLineOfString(stringArray[i]).length;
const [placeholderSingleElementArrayString] = currentPlaceholder;
toReturn =
toReturn +
stringArray[i] +
placeholderSingleElementArrayString
.split("\n")
.map((line, i) => {
if (i === 0)
return line;
return " ".repeat(lastStringArrayLineLength) + line;
})
.join("\n");
}
else {
toReturn = toReturn + stringArray[i] + currentPlaceholder;
}
}
return toReturn + stringArray[stringArray.length - 1];
})();
return unindent_1.unindent(stringToUnindent);
}
exports.tagUnindent = tagUnindent;
});
unwrapExports(tagUnindent_1);
tagUnindent_1.tagUnindent;
var esUtils = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.tagUnindent = void 0;
Object.defineProperty(exports, "tagUnindent", { enumerable: true, get: function () { return tagUnindent_1.tagUnindent; } });
});
unwrapExports(esUtils);
esUtils.tagUnindent;
var errorMessages = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.internalErrorMessages = exports.errorMessages = void 0;
exports.errorMessages = {
nodeIsCollapsedWithoutChildren: (line) => esUtils.tagUnindent `
Outline node:
${line}
has no children and it is collapsed. You have to un collapse it or add children.
`,
emptyOutline: "no outline has been provided",
wrongDepthDisplacement: (oldLine, newLine) => esUtils.tagUnindent `
Wrong depth displacement for the following part of the outline:
${oldLine}
${newLine}
`,
zeroPageInOutlineIsNotAllowed: (line) => esUtils.tagUnindent `
Zero page number is not allowed in outline:
${line}
`,
pageNumberInOutlineExceedsMaximum: (line, max) => esUtils.tagUnindent `
Pdf file has:
${max}
number of pages and outline points out of this range:
${line}
`,
depthOfOutlineHasToStartWithZero: `The outline should start with zero depth.`,
wrongPatternInLine: (line) => esUtils.tagUnindent `
The line:
${line}
has wrong pattern. It does satisfy the following regular expression:
${constants.constants.trimmedTocLineValidPattern.source}
`,
invalidDisplacementOfPage: (oldLine, newLine) => esUtils.tagUnindent `
The page is not displaced correctly:
${oldLine}
${newLine}
`
};
exports.internalErrorMessages = {
internalLibraryError: esUtils.tagUnindent `
Something went wrong. If you have not used the library in a way
it is not supposed to be used, then copy this error message and
open an issue here:
https://github.com/lillallol/outline-pdf-data-structure/issues
`,
};
});
unwrapExports(errorMessages);
errorMessages.internalErrorMessages;
errorMessages.errorMessages;
var getIndexOfImmediateLastChild = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.getIndexOfLastImmediateChild = void 0;
/**
* @description Returns the index of the last immediate child for the provided outline node.
* It throws if there is no child.
*/
function getIndexOfLastImmediateChild(outline, i) {
let candidateIndex;
const parentDepth = outline[i].depth;
for (let ii = i + 1; ii < outline.length; ii++) {
if (outline[ii].depth <= parentDepth)
break;
if (outline[ii].depth === parentDepth + 1)
candidateIndex = ii;
}
if (candidateIndex === undefined)
throw Error(errorMessages.internalErrorMessages.internalLibraryError);
return candidateIndex;
}
exports.getIndexOfLastImmediateChild = getIndexOfLastImmediateChild;
});
unwrapExports(getIndexOfImmediateLastChild);
getIndexOfImmediateLastChild.getIndexOfLastImmediateChild;
var getIndexOfImmediateNextSibling_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.getIndexOfImmediateNextSibling = void 0;
/**
* @description It returns the index of the immediate next sibling for the specified outline node.
* It throws if it does not exist.
*/
function getIndexOfImmediateNextSibling(outline, i) {
const contextDepth = outline[i].depth;
for (let ii = i + 1; ii < outline.length; ii++) {
if (outline[ii].depth < contextDepth)
break;
if (outline[ii].depth === contextDepth)
return ii;
}
throw Error(errorMessages.internalErrorMessages.internalLibraryError);
}
exports.getIndexOfImmediateNextSibling = getIndexOfImmediateNextSibling;
});
unwrapExports(getIndexOfImmediateNextSibling_1);
getIndexOfImmediateNextSibling_1.getIndexOfImmediateNextSibling;
var getIndexOfImmediatePreviousSibling_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.getIndexOfImmediatePreviousSibling = void 0;
/**
* @description It returns the index of the immediate previous sibling for the provided outline node,
* or throws if it does not find one.
*/
function getIndexOfImmediatePreviousSibling(outline, i) {
const contextDepth = outline[i].depth;
for (let ii = i - 1; ii > -1; ii--) {
if (outline[ii].depth < contextDepth)
break;
if (outline[ii].depth === contextDepth)
return ii;
}
throw Error(errorMessages.internalErrorMessages.internalLibraryError);
}
exports.getIndexOfImmediatePreviousSibling = getIndexOfImmediatePreviousSibling;
});
unwrapExports(getIndexOfImmediatePreviousSibling_1);
getIndexOfImmediatePreviousSibling_1.getIndexOfImmediatePreviousSibling;
var hasImmediatePreviousSibling_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasImmediatePreviousSibling = void 0;
/**
* @description It returns a predicate on whether the provided outline node has an immediate previous sibling.
*/
function hasImmediatePreviousSibling(outline, i) {
try {
getIndexOfImmediatePreviousSibling_1.getIndexOfImmediatePreviousSibling(outline, i);
return true;
}
catch (e) {
return false;
}
}
exports.hasImmediatePreviousSibling = hasImmediatePreviousSibling;
});
unwrapExports(hasImmediatePreviousSibling_1);
hasImmediatePreviousSibling_1.hasImmediatePreviousSibling;
var hasImmediateNextSibling_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasImmediateNextSibling = void 0;
/**
* @description It returns a predicate on whether the provided outline node has an immediate next sibling.
*/
function hasImmediateNextSibling(outline, i) {
try {
getIndexOfImmediateNextSibling_1.getIndexOfImmediateNextSibling(outline, i);
return true;
}
catch (e) {
return false;
}
}
exports.hasImmediateNextSibling = hasImmediateNextSibling;
});
unwrapExports(hasImmediateNextSibling_1);
hasImmediateNextSibling_1.hasImmediateNextSibling;
var getIndexOfImmediateParentFactory = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.getIndexOfImmediateParent = void 0;
/**
* @description It returns the index of the parent of the provided outline node.
* It returns `-1` when the provided outline node has zero depth.
*/
function getIndexOfImmediateParent(outline, i) {
const contextDepth = outline[i].depth;
for (let ii = i; ii > -1; ii--) {
if (contextDepth - 1 === outline[ii].depth)
return ii;
}
return -1;
}
exports.getIndexOfImmediateParent = getIndexOfImmediateParent;
});
unwrapExports(getIndexOfImmediateParentFactory);
getIndexOfImmediateParentFactory.getIndexOfImmediateParent;
var printedToOutline_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.printedToOutline = void 0;
/**
* @description Converts the outline string representation to its programmatic representation.
* It throws if the outline string representation is not valid.
* ```ts
* //input
* printedToOutline(`
* 1||Document
* 2|-|Section 1
* -3|-|Section 2
* 4|--|Subsection 1
* 5|-|Section 3
* 6||Summary
* `,6)
* //output
* [
* { pageNumber: 1, depth: 0, title: "Document" , collapse : false , line : "1||Document"},
* { pageNumber: 2, depth: 1, title: "Section 1" , collapse : false , line : "2|-|Section 1"},
* { pageNumber: 3, depth: 1, title: "Section 2" , collapse : true , line : "-3|-|Section 2"},
* { pageNumber: 4, depth: 2, title: "Subsection 1" , collapse : false , line : "4|--|Subsection 1"},
* { pageNumber: 5, depth: 1, title: "Section 3" , collapse : false , line : "5|-|Section 3"},
* { pageNumber: 6, depth: 0, title: "Summary" , collapse : false , line : "6||Summary"},
* ]
* ```
*/
function printedToOutline(inputOutline, totalNumberOfPages) {
if (inputOutline.trim() === "")
throw Error(errorMessages.errorMessages.emptyOutline);
let lastNode;
const toReturn = inputOutline
.trim()
.split("\n")
.map((untrimmedLine, i) => {
const line = untrimmedLine.trim();
const match = line.match(constants.constants.trimmedTocLineValidPattern);
if (match === null)
throw Error(errorMessages.errorMessages.wrongPatternInLine(line));
const { groups } = match;
if (groups === undefined)
throw Error(errorMessages.internalErrorMessages.internalLibraryError);
const { pageNumber, depth, title, collapse } = groups;
if (pageNumber === undefined)
throw Error(errorMessages.internalErrorMessages.internalLibraryError);
if (title === undefined)
throw Error(errorMessages.internalErrorMessages.internalLibraryError);
const nodeToReturn = {
pageNumber: Number(pageNumber),
depth: depth === undefined ? 0 : depth.length,
title: title,
collapse: (() => {
if (collapse === undefined)
return false;
if (collapse === "-")
return true;
if (collapse === "+")
return false;
if (collapse === "")
return false;
throw Error(errorMessages.internalErrorMessages.internalLibraryError);
})(),
line: line,
};
if (nodeToReturn.pageNumber === 0)
throw Error(errorMessages.errorMessages.zeroPageInOutlineIsNotAllowed(line));
if (nodeToReturn.pageNumber > totalNumberOfPages)
throw Error(errorMessages.errorMessages.pageNumberInOutlineExceedsMaximum(line, totalNumberOfPages));
if (i === 0 && nodeToReturn.depth !== 0)
throw Error(errorMessages.errorMessages.depthOfOutlineHasToStartWithZero);
if (i !== 0) {
if (!(nodeToReturn.depth <= lastNode.depth + 1)) {
throw Error(errorMessages.errorMessages.wrongDepthDisplacement(lastNode.line, nodeToReturn.line));
}
if (nodeToReturn.pageNumber < lastNode.pageNumber) {
throw Error(errorMessages.errorMessages.invalidDisplacementOfPage(lastNode.line, nodeToReturn.line));
}
if (lastNode.collapse && lastNode.depth >= nodeToReturn.depth) {
throw Error(errorMessages.errorMessages.nodeIsCollapsedWithoutChildren(lastNode.line));
}
}
lastNode = nodeToReturn;
return nodeToReturn;
});
return toReturn;
}
exports.printedToOutline = printedToOutline;
});
unwrapExports(printedToOutline_1);
printedToOutline_1.printedToOutline;
var getNumberOfDescendants_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.getNumberOfDescendants = void 0;
/**
* @description It returns the number of descendant outline nodes of the provided outline node.
*/
function getNumberOfDescendants(outline, i) {
let count = 0;
const contextDepth = outline[i].depth;
for (let ii = i + 1; ii < outline.length; ii++) {
if (contextDepth < outline[ii].depth) {
count++;
}
else {
break;
}
}
return count;
}
exports.getNumberOfDescendants = getNumberOfDescendants;
});
unwrapExports(getNumberOfDescendants_1);
getNumberOfDescendants_1.getNumberOfDescendants;
var outlinePdfDataStructure_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.outlinePdfDataStructure = void 0;
/**
* @description
* It returns all the information needed to create a real pdf data structure.
*/
function outlinePdfDataStructure(inputOutline, totalNumberOfPages) {
const outlineItems = [];
const outline = printedToOutline_1.printedToOutline(inputOutline, totalNumberOfPages);
outline.forEach((node, i) => {
node.count = getNumberOfDescendants_1.getNumberOfDescendants(outline, i) * (node.collapse ? -1 : 1);
});
for (let i = 0; i < outline.length; i++) {
outlineItems[i] = {
Title: outline[i].title,
Parent: getIndexOfImmediateParentFactory.getIndexOfImmediateParent(outline, i),
...(hasImmediatePreviousSibling_1.hasImmediatePreviousSibling(outline, i) && {
Prev: getIndexOfImmediatePreviousSibling_1.getIndexOfImmediatePreviousSibling(outline, i),
}),
...(hasImmediateNextSibling_1.hasImmediateNextSibling(outline, i) && {
Next: getIndexOfImmediateNextSibling_1.getIndexOfImmediateNextSibling(outline, i),
}),
...(hasChild_1.hasChild(outline, i) && {
First: i + 1,
Last: getIndexOfImmediateLastChild.getIndexOfLastImmediateChild(outline, i),
Count: outline[i].count,
}),
Dest: outline[i].pageNumber - 1,
};
}
return {
outlineItems: outlineItems,
outlineRootCount: outline.length,
};
}
exports.outlinePdfDataStructure = outlinePdfDataStructure;
});
unwrapExports(outlinePdfDataStructure_1);
outlinePdfDataStructure_1.outlinePdfDataStructure;
var dist$1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.outlinePdfDataStructure = void 0;
Object.defineProperty(exports, "outlinePdfDataStructure", { enumerable: true, get: function () { return outlinePdfDataStructure_1.outlinePdfDataStructure; } });
});
unwrapExports(dist$1);
dist$1.outlinePdfDataStructure;
var outlinePdfFactory_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.outlinePdfFactory = void 0;
const outlinePdfFactory = function outlinePdfFactory(pdfLib) {
const { PDFArray, PDFDocument, PDFName, PDFNull, PDFNumber, PDFHexString } = pdfLib;
const getPageRefs = getPageRefsFactory_1.getPageRefsFactory(pdfLib);
const createOutlineNode = createOutlineNodeFactory_1.createOutlineNodeFactory(pdfLib);
const createOutlineDict = createOutlineDictFactory_1.createOutlineDictFactory(pdfLib);
return async function outlineToPdf(parameters) {
const { outline: inputOutline, pdf: inputPdf } = parameters;
const doc = await (() => {
if (inputPdf instanceof PDFDocument)
return inputPdf;
return PDFDocument.load(inputPdf);
})();
const pageRefs = getPageRefs(doc);
const pageRefsLength = pageRefs.length;
const outlineRootRef = doc.context.nextRef();
//Pointing the "Outlines" property of the PDF "Catalog" to the first object of your outlines
doc.catalog.set(PDFName.of("Outlines"), outlineRootRef);
const outlineItemRef = [];
const outlineItem = [];
// const outlineRootRef = outlinesDictRef;
const { outlineItems: pseudoOutlineItems, outlineRootCount } = dist$1.outlinePdfDataStructure(inputOutline, pageRefsLength);
for (let i = 0; i < pseudoOutlineItems.length; i++) {
outlineItemRef.push(doc.context.nextRef());
}
for (let i = 0; i < pseudoOutlineItems.length; i++) {
const { Title, Dest, Parent, Count, First, Last, Next, Prev } = pseudoOutlineItems[i];
outlineItem[i] = createOutlineNode(doc, {
Title: PDFHexString.fromText(Title),
Parent: Parent !== -1 ? outlineItemRef[Parent] : outlineRootRef,
...(Prev !== undefined && {
Prev: outlineItemRef[Prev],
}),
...(Next !== undefined && {
Next: outlineItemRef[Next],
}),
...(First !== undefined && { First: outlineItemRef[First] }),
...(Last !== undefined && { Last: outlineItemRef[Last] }),
...(Count !== undefined && { Count: PDFNumber.of(Count) }),
Dest: (() => {
const array = PDFArray.withContext(doc.context);
array.push(pageRefs[Dest]);
array.push(PDFName.of("XYZ"));
array.push(PDFNull);
array.push(PDFNull);
array.push(PDFNull);
return array;
})(),
});
}
const outlinesDict = createOutlineDict(doc, {
First: outlineItemRef[0],
Last: outlineItemRef[pseudoOutlineItems.length - 1],
Count: PDFNumber.of(outlineRootCount),
});
//First 'Outline' object. Refer to table H.3 in Annex H.6 of PDF Specification doc.
doc.context.assign(outlineRootRef, outlinesDict);
//Actual outline items that will be displayed
for (let i = 0; i < pseudoOutlineItems.length; i++) {
doc.context.assign(outlineItemRef[i], outlineItem[i]);
}
return doc;
};
};
exports.outlinePdfFactory = outlinePdfFactory;
});
unwrapExports(outlinePdfFactory_1);
outlinePdfFactory_1.outlinePdfFactory;
var dist = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.outlinePdfFactory = void 0;
Object.defineProperty(exports, "outlinePdfFactory", { enumerable: true, get: function () { return outlinePdfFactory_1.outlinePdfFactory; } });
});
var index = unwrapExports(dist);
var dist_1 = dist.outlinePdfFactory;
export default index;
export { dist_1 as outlinePdfFactory };
+5
View File
@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.outlinePdfFactory = void 0;
var outlinePdfFactory_1 = require("./outlinePdf/outlinePdfFactory");
Object.defineProperty(exports, "outlinePdfFactory", { enumerable: true, get: function () { return outlinePdfFactory_1.outlinePdfFactory; } });
@@ -0,0 +1,9 @@
import type { PDFDocument, PDFNumber, PDFRef, PDFName as _PDFName, PDFDict as _PDFDict } from "pdf-lib";
export declare function createOutlineDictFactory(_: {
PDFName: typeof _PDFName;
PDFDict: typeof _PDFDict;
}): (doc: PDFDocument, _: {
First: PDFRef;
Last: PDFRef;
Count: PDFNumber;
}) => _PDFDict;
@@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createOutlineDictFactory = void 0;
function createOutlineDictFactory(_) {
const { PDFDict, PDFName } = _;
return function createOutlineDict(doc, _) {
const outlinesDictMap = new Map();
outlinesDictMap.set(PDFName.Type, PDFName.of("Outlines"));
outlinesDictMap.set(PDFName.of("First"), _.First);
outlinesDictMap.set(PDFName.of("Last"), _.Last);
outlinesDictMap.set(PDFName.of("Count"), _.Count);
return PDFDict.fromMapWithContext(outlinesDictMap, doc.context);
};
}
exports.createOutlineDictFactory = createOutlineDictFactory;
@@ -0,0 +1,14 @@
import type { PDFDocument, PDFNumber, PDFRef, PDFHexString, PDFArray, PDFName as _PDFName, PDFDict as _PDFDict } from "pdf-lib";
export declare function createOutlineNodeFactory(_: {
PDFName: typeof _PDFName;
PDFDict: typeof _PDFDict;
}): (doc: PDFDocument, _: {
Title: PDFHexString;
Parent: PDFRef;
Prev?: PDFRef;
Next?: PDFRef;
First?: PDFRef;
Last?: PDFRef;
Count?: PDFNumber;
Dest: PDFArray;
}) => _PDFDict;
@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createOutlineNodeFactory = void 0;
function createOutlineNodeFactory(_) {
const { PDFDict, PDFName } = _;
return function createOutlineNode(doc, _) {
const map = new Map();
map.set(PDFName.Title, _.Title);
map.set(PDFName.Parent, _.Parent);
if (_.Prev !== undefined)
map.set(PDFName.of("Prev"), _.Prev);
if (_.Next !== undefined)
map.set(PDFName.of("Next"), _.Next);
if (_.First !== undefined)
map.set(PDFName.of("First"), _.First);
if (_.Last !== undefined)
map.set(PDFName.of("Last"), _.Last);
if (_.Count !== undefined)
map.set(PDFName.of("Count"), _.Count);
map.set(PDFName.of("Dest"), _.Dest);
return PDFDict.fromMapWithContext(map, doc.context);
};
}
exports.createOutlineNodeFactory = createOutlineNodeFactory;
@@ -0,0 +1,4 @@
import type { PDFDocument, PDFPageLeaf as _PDFPageLeaf, PDFRef } from "pdf-lib";
export declare function getPageRefsFactory(_: {
PDFPageLeaf: typeof _PDFPageLeaf;
}): (pdfDoc: PDFDocument) => PDFRef[];
@@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPageRefsFactory = void 0;
function getPageRefsFactory(_) {
const { PDFPageLeaf } = _;
return function getPageRefs(pdfDoc) {
const refs = [];
pdfDoc.catalog.Pages().traverse((kid, ref) => {
if (kid instanceof PDFPageLeaf)
refs.push(ref);
});
return refs;
};
}
exports.getPageRefsFactory = getPageRefsFactory;
@@ -0,0 +1,2 @@
import type { IOutlinePdfFactory } from "../publicApi";
export declare const outlinePdfFactory: IOutlinePdfFactory;
@@ -0,0 +1,71 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.outlinePdfFactory = void 0;
const createOutlineDictFactory_1 = require("./createOutlineDictFactory");
const createOutlineNodeFactory_1 = require("./createOutlineNodeFactory");
const getPageRefsFactory_1 = require("./getPageRefsFactory");
const outline_pdf_data_structure_1 = require("@lillallol/outline-pdf-data-structure");
const outlinePdfFactory = function outlinePdfFactory(pdfLib) {
const { PDFArray, PDFDocument, PDFName, PDFNull, PDFNumber, PDFHexString } = pdfLib;
const getPageRefs = getPageRefsFactory_1.getPageRefsFactory(pdfLib);
const createOutlineNode = createOutlineNodeFactory_1.createOutlineNodeFactory(pdfLib);
const createOutlineDict = createOutlineDictFactory_1.createOutlineDictFactory(pdfLib);
return async function outlineToPdf(parameters) {
const { outline: inputOutline, pdf: inputPdf } = parameters;
const doc = await (() => {
if (inputPdf instanceof PDFDocument)
return inputPdf;
return PDFDocument.load(inputPdf);
})();
const pageRefs = getPageRefs(doc);
const pageRefsLength = pageRefs.length;
const outlineRootRef = doc.context.nextRef();
//Pointing the "Outlines" property of the PDF "Catalog" to the first object of your outlines
doc.catalog.set(PDFName.of("Outlines"), outlineRootRef);
const outlineItemRef = [];
const outlineItem = [];
// const outlineRootRef = outlinesDictRef;
const { outlineItems: pseudoOutlineItems, outlineRootCount } = outline_pdf_data_structure_1.outlinePdfDataStructure(inputOutline, pageRefsLength);
for (let i = 0; i < pseudoOutlineItems.length; i++) {
outlineItemRef.push(doc.context.nextRef());
}
for (let i = 0; i < pseudoOutlineItems.length; i++) {
const { Title, Dest, Parent, Count, First, Last, Next, Prev } = pseudoOutlineItems[i];
outlineItem[i] = createOutlineNode(doc, {
Title: PDFHexString.fromText(Title),
Parent: Parent !== -1 ? outlineItemRef[Parent] : outlineRootRef,
...(Prev !== undefined && {
Prev: outlineItemRef[Prev],
}),
...(Next !== undefined && {
Next: outlineItemRef[Next],
}),
...(First !== undefined && { First: outlineItemRef[First] }),
...(Last !== undefined && { Last: outlineItemRef[Last] }),
...(Count !== undefined && { Count: PDFNumber.of(Count) }),
Dest: (() => {
const array = PDFArray.withContext(doc.context);
array.push(pageRefs[Dest]);
array.push(PDFName.of("XYZ"));
array.push(PDFNull);
array.push(PDFNull);
array.push(PDFNull);
return array;
})(),
});
}
const outlinesDict = createOutlineDict(doc, {
First: outlineItemRef[0],
Last: outlineItemRef[pseudoOutlineItems.length - 1],
Count: PDFNumber.of(outlineRootCount),
});
//First 'Outline' object. Refer to table H.3 in Annex H.6 of PDF Specification doc.
doc.context.assign(outlineRootRef, outlinesDict);
//Actual outline items that will be displayed
for (let i = 0; i < pseudoOutlineItems.length; i++) {
doc.context.assign(outlineItemRef[i], outlineItem[i]);
}
return doc;
};
};
exports.outlinePdfFactory = outlinePdfFactory;
+57
View File
@@ -0,0 +1,57 @@
import type { PDFDict, PDFName, PDFNumber, PDFRef, PDFArray, PDFNull, PDFDocument, PDFPageLeaf, PDFHexString } from "pdf-lib";
export declare type IOutlinePdfFactory = (pdfLib: pdfLib) => IOutlinePdf;
/**
* @description
* Just use:
*
* ```ts
* import * as pdfLib from "pdf-lib";
* ```
*/
export declare type pdfLib = {
PDFDict: typeof PDFDict;
PDFName: typeof PDFName;
PDFNumber: typeof PDFNumber;
PDFRef: typeof PDFRef;
PDFArray: typeof PDFArray;
PDFNull: typeof PDFNull;
PDFDocument: typeof PDFDocument;
PDFPageLeaf: typeof PDFPageLeaf;
PDFHexString: typeof PDFHexString;
};
export declare type IOutlinePdf = (_: {
/**
* @description
* The pdf to outline.
*
* I you provide the pdf as a string, then make sure it is of base 64.
*/
pdf: string | Uint8Array | ArrayBuffer | PDFDocument;
/**
* @description
* String representation of the outline.
*
* Example:
*
* ```ts
* `
* 1||some title 1
* 12|-|some title 2
* -30|--|some title 3
* 34|---|some title 4
* 35|---|some title 5
* 60|--|some title 6
* 67|-|some title 7
* 80||some title 8
* `
* ```
*
* where the:
*
* * first column is the page number, and if it negative it means that this
* part of the outline is collapsed
* * second column is the outline depth
* * third column is the outline title
*/
outline: string;
}) => Promise<PDFDocument>;
+2
View File
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+76
View File
@@ -0,0 +1,76 @@
{
"name": "@lillallol/outline-pdf",
"version": "4.0.0",
"description": "High level API to add outline to a pdf without any dependency on other programming languages. Works in both browser and node.",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"module": "./dist/index.esm.js",
"scripts": {
"start": "http-server -c-1",
"check-updates": "ncu --reject @rollup/plugin-commonjs",
"update": "npx ncu -u --reject @rollup/plugin-commonjs;npm install",
"dead-files": "unimported",
"spell-check": "cspell \"./**/*\"",
"lint": "tsc --noEmit && eslint ./src/**/*.ts --max-warnings=0",
"format": "npx prettier --write ./src/**/*.ts",
"test": "rm -rf ./coverage && npx jest --clearCache && npx jest ./src --coverage --silent",
"build-ts": "rm -rf ./dist; npx tsc -p ./tsconfig.build.json",
"build-md": "rm -rf ./documentation.md; node ./doc-gen.js; npx md-in-place",
"build-esm": "npx rollup -c ./rollup.config.js",
"build": "npm run build-ts;npm run build-md;npm run build-esm",
"pre-bump": "npm run dead-files && npm run spell-check && npm run format && npm run lint && npm run test && npm run build && git add --all && git commit",
"release": "npm run pre-bump && npm publish --access public && npm run after-bump",
"bump-patch": "npm run pre-bump && npm version patch && npm publish && npm run after-bump",
"bump-minor": "npm run pre-bump && npm version minor && npm publish && npm run after-bump",
"bump-major": "npm run pre-bump && npm version major && npm publish && npm run after-bump",
"after-bump": "git push origin master && rm -rf ./dist"
},
"files": [
"dist"
],
"keywords": [
"outline",
"pdf",
"index",
"bookmarks",
"bookmark",
"pdf-lib",
"toc",
"table of contents",
"collapse",
"expand",
"high level API",
"native",
"browser"
],
"author": "lillallol",
"license": "MIT",
"devDependencies": {
"@rollup/plugin-commonjs": "12.0.0",
"@rollup/plugin-node-resolve": "^13.0.0",
"@types/jest": "^26.0.23",
"@typescript-eslint/eslint-plugin": "^4.24.0",
"@typescript-eslint/parser": "^4.24.0",
"cspell": "^5.4.1",
"eslint": "^7.27.0",
"http-server": "^0.12.3",
"jest": "^26.6.3",
"md-in-place": "^1.0.1",
"npm-check-updates": "^11.5.13",
"prettier": "^2.3.0",
"rollup-plugin-dts": "^3.0.2",
"ts-doc-gen-md": "*",
"ts-jest": "^26.5.6",
"typescript": "^4.2.4",
"unimported": "^1.11.0"
},
"dependencies": {
"@lillallol/outline-pdf-data-structure": "^1.0.3",
"pdf-lib": "^1.16.0"
},
"bugs": {
"url": "https://github.com/lillallol/outline-pdf/issues"
},
"repository": "https://github.com/lillallol/outline-pdf",
"homepage": "https://github.com/lillallol/outline-pdf"
}