I'm new to ES6 and Promise. I'm trying pdf.js to extract texts from all pages of a pdf file into a string array. And when extraction is done, I want to parse the array somehow. Say pdf file(passed via typedarray correctly) has 4 pages and my code is:
let str = [];
PDFJS.getDocument(typedarray).then(function(pdf) { for(let i = 1; i <= pdf.numPages; i++) { pdf.getPage(i).then(function(page) { page.getTextContent().then(function(textContent) { for(let j = 0; j < textContent.items.length; j++) { str.push(textContent.items[j].str); } parse(str); }); }); }
});It manages to work, but, of course, the problem is my parse function is called 4 times. I just want to call parse only after all 4-pages-extraction is done.
6 Answers
Similar to -- collect page promises using Promise.all and don't forget to chain then's:
function gettext(pdfUrl){ var pdf = pdfjsLib.getDocument(pdfUrl); return pdf.then(function(pdf) { // get all pages text var maxPages = pdf.pdfInfo.numPages; var countPromises = []; // collecting all page promises for (var j = 1; j <= maxPages; j++) { var page = pdf.getPage(j); var txt = ""; countPromises.push(page.then(function(page) { // add page promise var textContent = page.getTextContent(); return textContent.then(function(text){ // return content promise return text.items.map(function (s) { return s.str; }).join(''); // value page text }); })); } // Wait for all pages and join text return Promise.all(countPromises).then(function (texts) { return texts.join(''); }); });
}
// waiting on gettext to finish completion, or error
gettext("").then(function (text) { alert('parse ' + text);
},
function (reason) { console.error(reason);
});<script src=""></script> 3 A bit more cleaner version of @async5 and updated according to the latest version of "pdfjs-dist": "^2.0.943"
import PDFJS from "pdfjs-dist";
import PDFJSWorker from "pdfjs-dist/build/pdf.worker.js"; // add this to fit 2.3.0
PDFJS.disableTextLayer = true;
PDFJS.disableWorker = true; // not availaible anymore since 2.3.0 (see imports)
const getPageText = async (pdf: Pdf, pageNo: number) => { const page = await pdf.getPage(pageNo); const tokenizedText = await page.getTextContent(); const pageText = tokenizedText.items.map(token => token.str).join(""); return pageText;
};
/* see example of a PDFSource below */
export const getPDFText = async (source: PDFSource): Promise<string> => { Object.assign(window, {pdfjsWorker: PDFJSWorker}); // added to fit 2.3.0 const pdf: Pdf = await PDFJS.getDocument(source).promise; const maxPages = pdf.numPages; const pageTextPromises = []; for (let pageNo = 1; pageNo <= maxPages; pageNo += 1) { pageTextPromises.push(getPageText(pdf, pageNo)); } const pageTexts = await Promise.all(pageTextPromises); return pageTexts.join(" ");
};This is the corresponding typescript declaration file that I have used if anyone needs it.
declare module "pdfjs-dist";
type TokenText = { str: string;
};
type PageText = { items: TokenText[];
};
type PdfPage = { getTextContent: () => Promise<PageText>;
};
type Pdf = { numPages: number; getPage: (pageNo: number) => Promise<PdfPage>;
};
type PDFSource = Buffer | string;
declare module 'pdfjs-dist/build/pdf.worker.js'; // needed in 2.3.0Example of how to get a PDFSource from a File with Buffer (from node types) :
file.arrayBuffer().then((ab: ArrayBuffer) => { const pdfSource: PDFSource = Buffer.from(ab);
}); Here's a shorter (not necessarily better) version:
async function getPdfText(data) { let doc = await pdfjsLib.getDocument({data}).promise; let pageTexts = Array.from({length: doc.numPages}, async (v,i) => { return (await (await doc.getPage(i+1)).getTextContent()).items.map(token => token.str).join(''); }); return (await Promise.all(pageTexts)).join('');
}Here, data is a string or buffer (or you could change it to take the url, etc., instead).
Here's another Typescript version with await and Promise.all based on the other answers:
import { getDocument } from "pdfjs-dist";
import { DocumentInitParameters, PDFDataRangeTransport, TypedArray,
} from "pdfjs-dist/types/display/api";
export const getPdfText = async ( src: string | TypedArray | DocumentInitParameters | PDFDataRangeTransport
): Promise<string> => { const pdf = await getDocument(src).promise; const pageList = await Promise.all(Array.from({ length: pdf.numPages }, (_, i) => pdf.getPage(i + 1))); const textList = await Promise.all(pageList.map((p) => p.getTextContent())); return textList .map(({ items }) => items.map(({ str }) => str).join("")) .join("");
}; If you use the PDFViewer component, here is my solution that doesn't involve any promise or asynchrony:
function getDocumentText(viewer) { let text = ''; for (let i = 0; i < viewer.pagesCount; i++) { const { textContentItemsStr } = viewer.getPageView(i).textLayer; for (let item of textContentItemsStr) text += item; } return text;
} I wouldn't know how to do it either, but thanks to async5 I did it. I copied his code and updated it to the new version of pdf.js. I made minimal corrections and also took the liberty of not grouping all the pages into a single string. In addition, I used a regular expression that removes many of the empty spaces that PDF unfortunately ends up creating (it does not solve all cases, but the vast majority). The way I did it should be the way that most will feel comfortable working, however, feel free to remove the regex or make any other changes.
// pdf-to-text.js v1, require pdf.js ( )
// load pdf.js and pdf.worker.js
function pdfToText(url, separator = ' ') { let pdf = pdfjsLib.getDocument(url); return pdf.promise.then(function(pdf) { // get all pages text let maxPages = pdf._pdfInfo.numPages; let countPromises = []; // collecting all page promises for (let i = 1; i <= maxPages; i++) { let page = pdf.getPage(i); countPromises.push(page.then(function(page) { // add page promise let textContent = page.getTextContent(); return textContent.then(function(text) { // return content promise return text.items.map(function(obj) { return obj.str; }).join(separator); // value page text }); })); }; // wait for all pages and join text return Promise.all(countPromises).then(function(texts) { for(let i = 0; i < texts.length; i++){ texts[i] = texts[i].replace(/\s+/g, ' ').trim(); }; return texts; }); });
};
// example of use:
// waiting on pdfToText to finish completion, or error
pdfToText('files/pdf-name.pdf').then(function(pdfTexts) { console.log(pdfTexts); // RESULT: ['TEXT-OF-PAGE-1', 'TEXT-OF-PAGE-2', ...]
}, function(reason) { console.error(reason);
});