Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 99 additions & 13 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,103 @@ class PdfTk {

}

/**
* Takes a pdftk info string and turns it into an object.
* @static
* @public
* @param {string} data
* @returns {Object} Key value pairs and arrays of info data.
*/
static infoStringToObject(data) {
if (!data || !PdfTk.isString(data)) return null;
const KEY_DIVIDER = 'Begin';
const singleLines = data.split('\n');
let curKey = null;
const serialised = singleLines.reduce((acc, row) => {
const splitRow = row.split(KEY_DIVIDER);
//key could be Info, or Bookmark, or PageMedia, etc.
const key = splitRow[0];
//Key with -Begin- has encountered
if (row.indexOf(KEY_DIVIDER) > -1) {
//create a new index of data key if it does not exist yet
if (!acc.hasOwnProperty(key)) {
curKey = key;
acc[key] = [];
}
//a new row with 'Begin' always warants a new object to hold its values
acc[key].push({});
//and then return, as we don't add new entries at this point
return acc;
}
const container = acc[curKey];
const currentEntry = container[container.length - 1];
//contains the row minus the main key
const valueContainer = row.substring(curKey.length);

//check if current value is part of a parent key or a simple key value pair
if (valueContainer && row.substring(0, curKey.length) === curKey) {
const valueKey = valueContainer.split(':');
const key = valueKey.shift(); // valueKey[0] is always key
let value = valueKey.shift(); // valueKey[1] is always value
if (valueKey.length) {
//if value contains ':' it got split as well, join remainder
value = `${value}:${valueKey.join(':')}`;
}
currentEntry[key] = value.trim();
} else {
//item is not on key, and should just be added as is
const splitColon = row.split(':');
if (splitColon[1]) {
acc[splitColon[0]] = splitColon[1].trim();
}
}

return acc;
}, {});

return serialised;
}
/**
* Creates a pdftk info string value from an object.
*
* @static
* @public
* @param {Object} data
* @returns {String} Concatenated string value which can be passed to pdftk
*/
static infoObjectToString(data) {
if (!data || !PdfTk.isObject(data)) return null;
return Object.keys(data)
.reduce((acc, key) => {
const val = data[key];
//if value is array, split it and create a
//new string row with [key]Begin (BookmarkBegin,InfoBegin, etc.)
if (Array.isArray(val)) {
const vals = val.reduce((acc, item) => {
const innerValues = Object.keys(item).reduce(
(innerAcc, innerKey, innerIndex) => {
if (innerIndex === 0) {
innerAcc = `${innerAcc}\n${key}Begin`;
}
innerAcc = `${innerAcc}\n${key}${innerKey}: ${
item[innerKey]
}`;
return innerAcc;
},
''
);

return `${acc}${innerValues}`;
}, '');
return `${acc}${vals}`;
}
// if not an array, take as is and add to accumulator with newline
// for instance; `NumberOfPages: 6`
return `${acc}\n${key}: ${val}`;
}, '')
.trim();
}

/**
* Creates pdf info text file from JSON input.
* @static
Expand All @@ -254,19 +351,7 @@ class PdfTk {
* @returns {Buffer} Info text file as a buffer.
*/
static generateInfoFromJSON(data) {
const info = [];
for (const prop in data) {
/* istanbul ignore else */
if (data.hasOwnProperty(prop)) {
const begin = PdfTk.stringToBuffer('InfoBegin\nInfoKey: ');
const key = PdfTk.stringToBuffer(prop.toString());
const newline = PdfTk.stringToBuffer('\nInfoValue: ');
const value = PdfTk.stringToBuffer(data[prop].toString());
const newline2 = PdfTk.stringToBuffer('\n');
info.push(begin, key, newline, value, newline2);
}
}
return Buffer.concat(info);
return PdfTk.stringToBuffer(PdfTk.infoObjectToString(data));
}

/**
Expand Down Expand Up @@ -1072,6 +1157,7 @@ class PdfTk {
// module.exports = PdfTk;

module.exports = {
PdfTk,
input: file => new PdfTk(file),
configure: options => {
Object.defineProperties(PdfTk.prototype, {
Expand Down
125 changes: 125 additions & 0 deletions test/updateInfo.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/* globals describe, it */
const chai = require('chai');

const { expect, } = chai;

const pdftk = require('../');
const path = require('path');

const sample = `InfoBegin
InfoKey: ModDate
InfoValue: D:20190809100746+10'00'
InfoBegin
InfoKey: CreationDate
InfoValue: D:20190809100746+10'00'
InfoBegin
InfoKey: Creator
InfoValue: pdftk 2.02 - www.pdftk.com
InfoBegin
InfoKey: Producer
InfoValue: itext-paulo-155 (itextpdf.sf.net-lowagie.com)
PdfID0: b65ba3f658853c3c8df5d33b06e31195
PdfID1: b9f57d4e3b9b1162bb14654fe4d534c0
NumberOfPages: 6
BookmarkBegin
BookmarkTitle: Bookmark test Level 1
BookmarkLevel: 1
BookmarkPageNumber: 1
BookmarkBegin
BookmarkTitle: Bookmark test Level 2
BookmarkLevel: 2
BookmarkPageNumber: 1
BookmarkBegin
BookmarkTitle: Bookmark test Level 2-2
BookmarkLevel: 2
BookmarkPageNumber: 2
BookmarkBegin
BookmarkTitle: Bookmark test Level 3
BookmarkLevel: 3
BookmarkPageNumber: 5
PageMediaBegin
PageMediaNumber: 1
PageMediaRotation: 0
PageMediaRect: 0 0 594.96 841.92
PageMediaDimensions: 594.96 841.92
PageMediaBegin
PageMediaNumber: 2
PageMediaRotation: 0
PageMediaRect: 0 0 594.96 841.92
PageMediaDimensions: 594.96 841.92
PageMediaBegin
PageMediaNumber: 3
PageMediaRotation: 0
PageMediaRect: 0 0 594.96 841.92
PageMediaDimensions: 594.96 841.92
PageMediaBegin
PageMediaNumber: 4
PageMediaRotation: 0
PageMediaRect: 0 0 594.96 841.92
PageMediaDimensions: 594.96 841.92
PageMediaBegin
PageMediaNumber: 5
PageMediaRotation: 0
PageMediaRect: 0 0 594.96 841.92
PageMediaDimensions: 594.96 841.92
PageMediaBegin
PageMediaNumber: 6
PageMediaRotation: 0
PageMediaRect: 0 0 594.96 841.92
PageMediaDimensions: 594.96 841.92`;

describe('updateInfo', function () {

it('serialise and deserialise the info object with equal results', function () {
const serialised = pdftk.PdfTk.infoStringToObject(sample);
const deserialised = pdftk.PdfTk.infoObjectToString(serialised);
expect(sample).to.equal(deserialised);
});
it('should parse info from dumpdata correctly to show number of pages', function () {
const input = path.join(__dirname, './files/document1.pdf');
return pdftk
.input(input)
.dumpData()
.output()
.then(buffer => pdftk.PdfTk.infoStringToObject(buffer.toString('utf8')))
.then(object => expect(parseInt(object.NumberOfPages)).to.equal(5));

});
it('should write output file with added bookmarks', function () {
const strToObj = pdftk.PdfTk.infoStringToObject;
const input = path.join(__dirname, './files/document1.pdf');
const output = path.join(__dirname, './files/updateinfo.temp.pdf');
const newBookmark = { Title: 'Bookmark test title', Level: '1', PageNumber: '1', };

const dumpData = () => pdftk
.input(input)
.dumpData()
.output()
.then(buffer => buffer.toString('utf8'));

const addBookmark = () =>
dumpData().then(data => strToObj(data)).then(infoObject => {
infoObject.Bookmark = [
newBookmark,
];
return infoObject;
});

const writeFileWithUpdates = () =>
addBookmark().then(infoObject =>
pdftk
.input(input)
.updateInfo(infoObject)
.output(output));


return writeFileWithUpdates().then(() =>
pdftk
.input(output)
.dumpData()
.output()
.then(buffer => pdftk.PdfTk.infoStringToObject(buffer.toString('utf8')))
.then(obj => expect(obj.Bookmark[0].Title).to.equal(newBookmark.Title)));

});
});