@digital-blueprint/annotpdf
Version:
Javascript library for creating annotations in PDF documents
270 lines • 14 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const util_1 = require("./util");
const array_util_1 = require("./array-util");
const stream_1 = require("./stream");
/**
* While the general Util class holds low level methods to navigate through the pdf data, the ObjectUtil
* is purposefully build to extract complete objects. It returns those as json dictionaries.
* */
class ObjectUtil {
static extractDictKeyRec(data, ptr, dict) {
let next = util_1.Util.readNextWord(data, ptr);
let next_string = next.result;
if (!next_string)
return ptr;
let _ptr = util_1.Util.skipDelimiter(next_string, 0);
if (next_string[0] && util_1.Util.DICT_END[0] === next_string[0]) {
let wordLookup = util_1.Util.readNextWord(data, next.end_index + 1);
if (wordLookup.result && util_1.Util.DICT_END[0] === wordLookup.result[0]) {
return wordLookup.end_index;
}
}
return ObjectUtil.extractDictValueRec(data, next.end_index + 1, dict, util_1.Util.convertAsciiToString(next_string));
}
static extractDictValueRec(data, ptr, dict, current_key = undefined) {
let next = util_1.Util.readNextWord(data, ptr);
let next_string = next.result || new Uint8Array([]);
ptr = next.end_index - next_string.length + 1;
if (next_string[0] === util_1.Util.ARRAY_START[0]) {
if (!current_key)
throw Error("Invalid anonymous array definition");
// handle array
let extracted_array = array_util_1.ArrayUtil.extractArray(data, ptr);
dict[current_key] = extracted_array.result;
return ObjectUtil.extractDictKeyRec(data, extracted_array.end_index + 1, dict);
}
else if (next_string[0] === util_1.Util.DICT_START[0] && data[next.end_index + 1] === util_1.Util.DICT_START[0]) {
if (current_key) {
let sup_dict = {};
let end_sub_dict = ObjectUtil.extractDictKeyRec(data, next.end_index + 2, sup_dict);
dict[current_key] = sup_dict;
return ObjectUtil.extractDictKeyRec(data, end_sub_dict + 1, dict);
}
else {
return ObjectUtil.extractDictKeyRec(data, next.end_index + 2, dict);
}
}
else if (next_string[0] === util_1.Util.HEX_STRING_START[0]) {
if (!current_key)
throw Error("Invalid anonymous string definition");
let extracted_string = util_1.Util.extractHexString(data, ptr);
dict[current_key] = extracted_string.result;
return ObjectUtil.extractDictKeyRec(data, extracted_string.end_index + 1, dict);
}
else if (next_string[0] === util_1.Util.LITERAL_STRING_START[0]) {
if (!current_key)
throw Error("Invalid anonymous string definition");
let extracted_string = util_1.Util.extractString(data, ptr);
dict[current_key] = extracted_string.result;
return ObjectUtil.extractDictKeyRec(data, extracted_string.end_index + 1, dict);
}
else if (next_string[0] === 47) { // /
if (!current_key)
throw Error("Invalid anonymous property definition");
let opt_value = util_1.Util.extractOptionValue(data, ptr);
dict[current_key] = "/" + opt_value.result;
return ObjectUtil.extractDictKeyRec(data, opt_value.end_index + 1, dict);
}
else { // It is a number, but this number might be part of a Reference
let lookupNext = util_1.Util.readNextWord(data, next.end_index + 1);
let lookupNextWord = lookupNext.result || new Uint8Array([]);
if (!current_key)
throw Error("Invalid anonymous reference/number definition");
let value_end_ptr = next.end_index + 1;
if (lookupNextWord[0] === 47 || lookupNextWord[0] === util_1.Util.DICT_END[0]) { // is a number
dict[current_key] = util_1.Util.extractNumber(data, ptr).result;
}
else { // is a rereference
let extracted_reference = util_1.Util.extractReferenceTyped(data, ptr);
dict[current_key] = extracted_reference.result;
value_end_ptr = extracted_reference.end_index + 1;
}
// handle Reference
return ObjectUtil.extractDictKeyRec(data, value_end_ptr, dict);
}
throw Error(`Could not interpret: ${util_1.Util.convertAsciiToString(next_string)}`);
}
/**
* Locates the object start in case the ptr does not point correctly
* */
static locateObjectStart(data, ptr) {
let obj_ptr = util_1.Util.locateSequence(util_1.Util.OBJ, data, ptr);
let endobj_ptr = util_1.Util.locateSequence(util_1.Util.ENDOBJ, data, ptr);
let consumeGenerationAndNumberRevere = (ptr) => {
// <id> <generation> obj ... endobj
// ^
// being somewhere here
let new_ptr = util_1.Util.skipSpacesReverse(data, ptr - 1);
// <id> <generation> obj ... endobj
// ^
// being somewhere here
while (new_ptr > 0 && !util_1.Util.isDelimiter(data[new_ptr]))
--new_ptr;
// <id> <generation> obj ... endobj
// ^
// being somewhere here
new_ptr = util_1.Util.skipSpacesReverse(data, new_ptr - 1);
// <id> <generation> obj ... endobj
// ^
// being somewhere here
while (new_ptr > 0 && !util_1.Util.isDelimiter(data[new_ptr]))
--new_ptr;
return new_ptr;
};
if (obj_ptr !== -1 && obj_ptr < endobj_ptr) {
return consumeGenerationAndNumberRevere(obj_ptr);
}
else if (obj_ptr > endobj_ptr || (obj_ptr === -1 && endobj_ptr > -1)) {
// <id> <generation> obj ... endobj
// ^
// being somewhere here
let new_ptr = util_1.Util.locateSequenceReversed(util_1.Util.OBJ, data, ptr);
return consumeGenerationAndNumberRevere(new_ptr);
}
else {
throw Error("Could not correct object start");
}
}
/**
* Parses a PDF object and returns a dictionary containing its fields
* */
static extractObject(data, xref, objectLookupTable = undefined) {
if (typeof xref !== 'number' && xref.compressed) {
if (!objectLookupTable)
throw Error("Provide ObjectLookupTable to extract stream object");
return ObjectUtil.extractStreamObject(data, xref.id, xref.generation, objectLookupTable[xref.pointer]);
}
let ret_obj = {};
let ptr = typeof xref === 'number' ? xref : xref.pointer;
let object_id = util_1.Util.extractObjectId(data, ptr);
if (isNaN(object_id.obj) || isNaN(object_id.generation)) {
return ObjectUtil.extractObject(data, ObjectUtil.locateObjectStart(data, ptr), objectLookupTable);
}
ret_obj.id = object_id;
ptr = util_1.Util.locateSequence(util_1.Util.OBJ, data, ptr);
if (ptr === -1)
throw { message: "Object missing 'obj' sequence", name: "MissingObjSequenceError" };
ptr += util_1.Util.OBJ.length;
let ptr_obj_end = util_1.Util.locateSequence(util_1.Util.ENDOBJ, data, ptr);
data = data.slice(ptr, ptr_obj_end);
// determine the type of the object:
let next = util_1.Util.readNextWord(data, 0);
if (next.result && next.result[0] === util_1.Util.DICT_START[0]) { // object contains a dict
let result_dict = {};
ObjectUtil.extractDictValueRec(data, 0, result_dict);
ret_obj.value = result_dict;
}
else if (next.result && next.result[0] === util_1.Util.ARRAY_START[0]) { // object contains an array
let lst = array_util_1.ArrayUtil.extractArray(data, 0);
ret_obj.value = lst.result;
}
else {
throw Error(`Invalid object type - starting with: ${next.result}`);
}
// check if the object has a stream part
ptr = util_1.Util.locateSequence(util_1.Util.STREAM, data, 0);
if (-1 !== ptr) {
// extract stream part
let ptr_stream_data_start = ptr + util_1.Util.STREAM.length;
ptr_stream_data_start = util_1.Util.skipDelimiter(data, ptr_stream_data_start);
let ptr_stream_data_end = ptr_stream_data_start + ret_obj.value["/Length"];
ret_obj.stream = ObjectUtil.extractStreamData(data.slice(ptr_stream_data_start, ptr_stream_data_end), ret_obj.value["/Filter"], ObjectUtil.translateDecodeParams(ret_obj));
}
return ret_obj;
}
static extractStreamObject(data, object_id_to_extract, offset, streamObj_xref) {
let ptr = streamObj_xref.pointer;
let ret_obj = {};
// extract object id
let object_id = util_1.Util.extractObjectId(data, ptr);
ret_obj.id = object_id;
ptr = util_1.Util.locateSequence(util_1.Util.OBJ, data, ptr) + util_1.Util.OBJ.length;
let ptr_obj_end = util_1.Util.locateSequence(util_1.Util.ENDOBJ, data, ptr);
data = data.slice(ptr, ptr_obj_end);
// extract dict part
let next = util_1.Util.readNextWord(data, 0);
if (!next.result || next.result[0] !== util_1.Util.DICT_START[0]) {
throw Error("Invalid stream object -- no dict");
}
let result_dict = {};
ObjectUtil.extractDictValueRec(data, 0, result_dict);
ret_obj.value = result_dict;
if (result_dict["/Filter"] && Array.isArray(result_dict["/Filter"]) && result_dict["/Filter"].length > 0)
result_dict["/Filter"] = result_dict["/Filter"][0];
// check if filter is supported
if (!result_dict["/Filter"] || result_dict["/Filter"] !== "/FlateDecode")
throw Error(`Unsupported stream filter: ${result_dict["/Filter"]} - Only supported filter is FlateDecode`);
if (!result_dict["/Type"] || result_dict["/Type"] !== "/ObjStm")
throw Error(`Invalid stream object type: ${result_dict["/Type"]}`);
// extract the stream length
let streamLength = result_dict["/Length"];
// extract stream part
let ptr_stream_data_start = util_1.Util.locateSequence(util_1.Util.STREAM, data) + util_1.Util.STREAM.length;
ptr_stream_data_start = util_1.Util.skipDelimiter(data, ptr_stream_data_start);
let ptr_stream_data_end = ptr_stream_data_start + streamLength;
let stream = ObjectUtil.extractStreamData(data.slice(ptr_stream_data_start, ptr_stream_data_end), result_dict["/Filter"], ObjectUtil.translateDecodeParams(result_dict));
if (!result_dict["/N"])
throw Error("Invalid stream object -- no number of objects specified");
if (!result_dict["/First"])
throw Error("Invalid stream object -- no offset to the first objct specified");
let streamReferences = ObjectUtil.extractStreamObjectTable(stream, result_dict["/N"], result_dict["/First"]);
if (!streamReferences[object_id_to_extract])
throw Error(`Object ${object_id_to_extract} not in stream object`);
let result_obj = { id: { obj: object_id_to_extract, generation: 0 } };
let stream_data = stream.getData();
next = util_1.Util.readNextWord(stream_data, streamReferences[object_id_to_extract]);
if (next.result && next.result[0] === util_1.Util.DICT_START[0]) { // object contains a dict
let value = {};
result_obj.pointer_stream_end = ObjectUtil.extractDictValueRec(stream_data, streamReferences[object_id_to_extract], value);
result_obj.value = value;
result_obj.stream = stream;
result_obj.pointer_stream_start = streamReferences[object_id_to_extract];
}
else if (next.result && next.result[0] === util_1.Util.ARRAY_START[0]) { // object contains an array
let lst = array_util_1.ArrayUtil.extractArray(stream_data, streamReferences[object_id_to_extract]);
result_obj.value = lst.result;
result_obj.stream = stream;
result_obj.pointer_stream_start = streamReferences[object_id_to_extract];
}
else {
throw Error(`Invalid stream object type - starting with: ${next.result}`);
}
return result_obj;
}
static translateDecodeParams(dict) {
if (!dict.value)
return undefined;
if (!dict.value["/DecodeParms"])
return undefined;
if (!dict.value["/DecodeParms"]["/Columns"])
return undefined;
if (!dict.value["/DecodeParms"]["/Predictor"])
return undefined;
return { columns: dict.value["/DecodeParms"]["/Columns"], predictor: dict.value["/DecodeParms"]["/Predictor"] };
}
static extractStreamObjectTable(stream, number_of_obj, offset_first_obj) {
let references = {};
for (let i = 0; i < number_of_obj; ++i) {
let obj_id = stream.getNumber();
let pointer = stream.getNumber() + offset_first_obj;
references[obj_id] = pointer;
}
return references;
}
static extractStreamData(streamData, compression, decodeParameters = undefined) {
let stream = undefined;
if (compression === '/FlateDecode') {
stream = new stream_1.FlateStream(streamData, decodeParameters);
}
else {
throw Error(`Unsupported stream filter: ${compression} - Only supported filter is FlateDecode (right now)`);
}
if (!stream)
throw Error("Could not derive stream");
return stream;
}
}
exports.ObjectUtil = ObjectUtil;
ObjectUtil.i = 0;
//# sourceMappingURL=object-util.js.map