@aeolun/muhammara
Version:
Create, read and modify PDF files and streams. A drop in replacement for hummusjs PDF library
355 lines (330 loc) • 10.1 kB
JavaScript
const fs = require("fs");
const muhammara = require("../muhammara");
/**
* @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 muhammara.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 =
"MuhammaraJS (https://github.com/julianhille/MuhammaraJS)";
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 muhammara = this.muhammara;
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 = muhammara.getTypeLabel(type);
let output = "";
let objectID, jsArray, aDictionary, keys;
switch (type) {
case muhammara.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 muhammara.ePDFObjectArray:
jsArray = inObject.toPDFArray().toJSArray();
output += `- ${label} [${jsArray.length}]`;
logToFile(output);
++mTabLevel;
jsArray.forEach((element) => {
iterateObjectTypes(element);
});
--mTabLevel;
break;
case muhammara.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 muhammara.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 muhammara = this.muhammara;
const pdfReader = this.pdfReader;
const type = inObject.getType();
const label = muhammara.getTypeLabel(type);
const saveToObject = this.pdfStructure || {};
let objectID, parsed, dictionaryObject, dictionary;
switch (type) {
case muhammara.ePDFObjectIndirectObjectReference:
objectID = inObject.toPDFIndirectObjectReference().getObjectID();
parsed = pdfReader.parseNewObject(objectID);
return this._parseObjectByType(parsed);
case muhammara.ePDFObjectArray:
inObject
.toPDFArray()
.toJSArray()
.forEach((element) => {
this._parseObjectByType(element);
});
break;
case muhammara.ePDFObjectDictionary:
dictionaryObject = inObject.toPDFDictionary().toJSObject();
Object.getOwnPropertyNames(dictionaryObject).forEach((element) => {
this._parseObjectByType(dictionaryObject[element]);
});
break;
case muhammara.ePDFObjectStream:
dictionary = inObject.toPDFStream().getDictionary();
return this._parseObjectByType(dictionary);
default:
saveToObject[`${label}-${Date.now() * Math.random()}`] = inObject;
return inObject;
}
};