const fs = require('fs');
const hummus = require('hummus');
/**
* @name info
* @desc Add new PDF information, or retrieve existing PDF information.
* @memberof Recipe
* @function
* @param {Object} [options] - The options (when missing obtains existing PDF information)
* @param {number} [options.version] - The pdf version
* @param {string} [options.author] - The author
* @param {string} [options.title] - The title
* @param {string} [options.subject] - The subject
* @param {string[]} [options.keywords] - The array of keywords
*/
exports.info = function info(options) {
let result;
if (! options) {
result = this._readInfo();
} else {
this.toWriteInfo_ = this.toWriteInfo_ || {};
Object.assign(this.toWriteInfo_, options);
result = this;
}
return result;
};
exports._readInfo = function _readInfo() {
if (!this.isNewPDF && !this.infoDictionary) {
const copyFrom = this.isBufferSrc ? new hummus.PDFRStreamForBuffer(this.src) : this.src;
const copyCtx = this.writer.createPDFCopyingContext(copyFrom);
const infoDict = copyCtx.getSourceDocumentParser().queryDictionaryObject(
copyCtx.getSourceDocumentParser().getTrailer(), 'Info'
);
const oldInfo = (infoDict && infoDict.toJSObject) ? infoDict.toJSObject() : null;
if (oldInfo) {
this.infoDictionary = {};
Object.getOwnPropertyNames(oldInfo).forEach((key) => {
if (!oldInfo[key]) {
return;
}
const oldInforSrc = this._parseObjectByType(oldInfo[key]);
if (!oldInforSrc) {
return;
}
switch (key) {
case 'Trapped':
if (oldInforSrc && oldInforSrc.value) {
this.infoDictionary.trapped = oldInforSrc.value;
}
break;
case 'CreationDate':
if (oldInforSrc && oldInforSrc.value) {
this.infoDictionary.creationDate = oldInforSrc.value;
}
break;
case 'ModDate':
if (oldInforSrc && oldInforSrc.value) {
this.infoDictionary.modDate = oldInforSrc.value;
}
break;
case 'Creator':
if (oldInforSrc && oldInforSrc.toText) {
this.infoDictionary.creator = oldInforSrc.toText();
}
break;
case 'Producer':
if (oldInforSrc && oldInforSrc.toText) {
this.infoDictionary.producer = oldInforSrc.toText();
}
break;
default:
if (oldInforSrc && oldInforSrc.toText) {
this.infoDictionary[key.toLowerCase()] = oldInforSrc.toText();
}
}
});
}
}
return this.infoDictionary;
};
exports._writeInfo = function _writeInfo() {
const options = this.toWriteInfo_ || {};
const oldInfo = this._readInfo();
/*
#41, #48
This issue is due to the unhandled process exit from HummusJS.
I have to disable this part before it gets fixed in HummusJS.
*/
const infoDictionary = this.writer.getDocumentContext().getInfoDictionary();
const fields = [{
key: 'author',
type: 'string'
}, {
key: 'title',
type: 'string'
}, {
key: 'subject',
type: 'string'
}, {
key: 'keywords',
type: 'array'
}];
// const ignores = [
// 'CreationDate', 'Creator', 'ModDate', 'Producer'
// ];
if (oldInfo) {
Object.getOwnPropertyNames(oldInfo).forEach((key) => {
if (!oldInfo[key]) {
return;
}
switch (key) {
case 'trapped':
infoDictionary.trapped = oldInfo.trapped;
break;
case 'creationDate':
infoDictionary.setCreationDate(oldInfo.creationDate);
break;
case 'modDate':
infoDictionary.addAdditionalInfoEntry('source-ModDate', oldInfo.modDate);
break;
case 'creator':
infoDictionary.addAdditionalInfoEntry('source-Creator', oldInfo.creator);
break;
case 'producer':
infoDictionary.addAdditionalInfoEntry('source-Producer', oldInfo.producer);
break;
default:
infoDictionary[key] = oldInfo[key];
}
});
}
if (this.isNewPDF) {
infoDictionary.setCreationDate(new Date());
}
infoDictionary.setModDate(new Date());
infoDictionary.producer = 'PDFHummus (https://github.com/galkahana/HummusJS)';
infoDictionary.creator = 'Hummus-Recipe (https://github.com/chunyenHuang/hummusRecipe)';
fields.forEach((item) => {
let value = options[item.key];
if (!value) {
return;
} else {
switch (item.type) {
case 'string':
value = value.toString();
break;
case 'date':
value = new Date(value);
break;
case 'array':
value = (Array.isArray(value)) ? value : [value];
break;
default:
}
}
if (item.func) {
infoDictionary[item.func](value);
} else {
infoDictionary[item.key] = value;
}
});
return this;
};
/**
* @name custom
* @desc Add custom information to pdf
* @memberof Recipe
* @function
* @param {string} [key] - The key
* @param {string} [value] - The value
*/
exports.custom = function custom(key, value) {
const infoDictionary = this.writer.getDocumentContext().getInfoDictionary();
infoDictionary.addAdditionalInfoEntry(key.toString(), value.toString());
return this;
};
exports.structure = function structure(output) {
// PDF file format http://lotabout.me/orgwiki/pdf.html
// const outputFileType = path.extname(output);
const outputFile = fs.openSync(output, 'w');
const hummus = this.hummus;
const pdfReader = this.pdfReader;
const tabWidth = ' ';
const structures = [
'Info',
'Root', // catalog
'Size',
'Prev',
'ID',
// 'Encrypt',
// 'XRefStm'
];
const write = (item) => {
const mIteratedObjectIDs = {};
let mTabLevel = 0;
const addTabs = () => {
let output = '';
for (let i = 0; i < mTabLevel; ++i) {
output += tabWidth;
}
return output;
};
const logToFile = (inString) => {
fs.writeSync(outputFile, addTabs() + inString + '\r\n');
};
const iterateObjectTypes = (inObject) => {
const type = inObject.getType();
const label = hummus.getTypeLabel(type);
let output = '';
let objectID, jsArray, aDictionary, keys;
switch (type) {
case hummus.ePDFObjectIndirectObjectReference:
++mTabLevel;
objectID = inObject.toPDFIndirectObjectReference().getObjectID();
output += `Indirect object reference (${objectID}): `;
logToFile(output);
if (!Object.prototype.hasOwnProperty.call(mIteratedObjectIDs, objectID)) {
mIteratedObjectIDs[objectID] = true;
iterateObjectTypes(pdfReader.parseNewObject(objectID));
}
for (var i = 0; i < mTabLevel; ++i) {
output += ' ';
}
--mTabLevel;
return;
case hummus.ePDFObjectArray:
jsArray = inObject.toPDFArray().toJSArray();
output += `- ${label} [${jsArray.length}]`;
logToFile(output);
++mTabLevel;
jsArray.forEach((element) => {
iterateObjectTypes(element);
});
--mTabLevel;
break;
case hummus.ePDFObjectDictionary:
aDictionary = inObject.toPDFDictionary().toJSObject();
keys = Object.getOwnPropertyNames(aDictionary).join(', ');
output += `- ${label} {${keys}}`;
logToFile(output);
++mTabLevel;
Object.getOwnPropertyNames(aDictionary).forEach((element) => {
logToFile(element + ' *');
iterateObjectTypes(aDictionary[element]);
});
--mTabLevel;
break;
case hummus.ePDFObjectStream:
output += 'Stream . iterating stream dictionary:';
logToFile(output);
iterateObjectTypes(inObject.toPDFStream().getDictionary());
break;
default:
output += `${tabWidth}${label}: ${inObject}`;
logToFile(output);
}
};
const itemTrailer = pdfReader.queryDictionaryObject(pdfReader.getTrailer(), item);
logToFile(item);
iterateObjectTypes(itemTrailer);
};
structures.forEach((item) => {
write(item);
});
fs.closeSync(outputFile);
return this;
};
exports._parseObjectByType = function _parseObjectByType(inObject) {
if (!inObject) {
return;
}
const hummus = this.hummus;
const pdfReader = this.pdfReader;
const type = inObject.getType();
const label = hummus.getTypeLabel(type);
const saveToObject = this.pdfStructure || {};
let objectID, parsed, dictionaryObject, dictionary;
switch (type) {
case hummus.ePDFObjectIndirectObjectReference:
objectID = inObject.toPDFIndirectObjectReference().getObjectID();
parsed = pdfReader.parseNewObject(objectID);
return this._parseObjectByType(parsed);
case hummus.ePDFObjectArray:
inObject.toPDFArray().toJSArray().forEach((element) => {
this._parseObjectByType(element);
});
break;
case hummus.ePDFObjectDictionary:
dictionaryObject = inObject.toPDFDictionary().toJSObject();
Object.getOwnPropertyNames(dictionaryObject).forEach((element) => {
this._parseObjectByType(dictionaryObject[element]);
});
break;
case hummus.ePDFObjectStream:
dictionary = inObject.toPDFStream().getDictionary();
return this._parseObjectByType(dictionary);
default:
saveToObject[`${label}-${Date.now()*Math.random()}`] = inObject;
return inObject;
}
};