While many barcode SDKs can decode barcodes from images, only a few excel at extracting 1D and 2D barcodes from multi-page PDF files. This is where the Dynamsoft Barcode Reader SDK stands out, offering robust capabilities to accurately scan and decode barcodes across multiple pages within PDFs. In this tutorial, we will start by using Dynamsoft C++ Barcode SDK v10.x to tackle the challenges of PDF barcode decoding and then integrate the functionality into a Node.js addon, helping developers streamline barcode extraction from complex PDF documents.
Node.js Barcode Reader Demo Video
Prerequisites
- Obtain a Dynamsoft Capture Vision Trial License.
-
Create a multi-page PDF file using this free online tool provided by Dynamsoft.
A Simple C++ Program to Decode Barcodes from Images
To get started with the C++ API of the Dynamsoft Barcode Reader SDK, you can refer to the ReadAnImage.cpp file provided in the official repository.
#include <iostream>
#include <string>
#include "../../../Include/DynamsoftCaptureVisionRouter.h"
using namespace std;
using namespace dynamsoft::license;
using namespace dynamsoft::cvr;
using namespace dynamsoft::dbr;
using namespace dynamsoft::basic_structures;
#if defined(_WIN64) || defined(_WIN32)
#ifdef _WIN64
#pragma comment(lib, "../../../Distributables/Lib/Windows/x64/DynamsoftLicensex64.lib")
#pragma comment(lib, "../../../Distributables/Lib/Windows/x64/DynamsoftCaptureVisionRouterx64.lib")
#else
#pragma comment(lib, "../../../Distributables/Lib/Windows/x86/DynamsoftLicensex86.lib")
#pragma comment(lib, "../../../Distributables/Lib/Windows/x86/DynamsoftCaptureVisionRouterx86.lib")
#endif
#endif
int main()
{
int errorCode = 1;
char errorMsg[512];
errorCode = CLicenseManager::InitLicense("LICENSE-KEY", errorMsg, 512);
if (errorCode != ErrorCode::EC_OK && errorCode != ErrorCode::EC_LICENSE_CACHE_USED)
{
cout << "License initialization failed: ErrorCode: " << errorCode << ", ErrorString: " << errorMsg << endl;
}
else
{
CCaptureVisionRouter *cvr = new CCaptureVisionRouter;
string imageFile = "../../../Images/GeneralBarcodes.png";
CCapturedResult *result = cvr->Capture(imageFile.c_str(), CPresetTemplate::PT_READ_BARCODES);
if (result->GetErrorCode() != 0)
{
cout << "Error: " << result->GetErrorCode() << "," << result->GetErrorString() << endl;
}
CDecodedBarcodesResult *barcodeResult = result->GetDecodedBarcodesResult();
if (barcodeResult == nullptr || barcodeResult->GetItemsCount() == 0)
{
cout << "No barcode found." << endl;
}
else
{
int barcodeResultItemCount = barcodeResult->GetItemsCount();
cout << "Decoded " << barcodeResultItemCount << " barcodes" << endl;
for (int j = 0; j < barcodeResultItemCount; j++)
{
const CBarcodeResultItem *barcodeResultItem = barcodeResult->GetItem(j);
cout << "Result " << j + 1 << endl;
cout << "Barcode Format: " << barcodeResultItem->GetFormatString() << endl;
cout << "Barcode Text: " << barcodeResultItem->GetText() << endl;
}
}
if (barcodeResult)
barcodeResult->Release();
if (result)
result->Release();
delete cvr, cvr = NULL;
}
cout << "Press any key to quit..." << endl;
cin.ignore();
return 0;
}
The primary methods used in the code snippet above are InitLicense()
and Capture()
:
- InitLicense() initializes the SDK with your license key.
- Capture() processes the specified image file to decode barcodes and returns the results.
Note: Supported image formats include BMP, JPEG, PNG, single-page TIFF and single-page PDF. For multi-page TIFF/PDF files, use the CFileFetcher class to handle page-by-page processing.
Reading Barcodes from Multi-Page PDFs
Let's modify the implementation to read barcodes from multi-page PDF files.
-
Create a
MyImageSourceStateListener
class that inherits from CImageSourceStateListener. This class listens for image source states and stops capturing when the image source is exhausted.
class MyImageSourceStateListener : public CImageSourceStateListener { private: CCaptureVisionRouter *m_router; public: MyImageSourceStateListener(CCaptureVisionRouter *router) { m_router = router; } virtual void OnImageSourceStateReceived(ImageSourceState state) { if (state == ISS_EXHAUSTED) m_router->StopCapturing(); } };
-
Create a
MyCapturedResultReceiver
class that inherits from CCapturedResultReceiver. This class receives decoded barcode results from any image source. ThefileTag->GetPageNumber()
method retrieves the page number of the image in the PDF or TIFF file.
class MyCapturedResultReceiver : public CCapturedResultReceiver { virtual void OnDecodedBarcodesReceived(CDecodedBarcodesResult *pResult) override { if (pResult->GetErrorCode() != EC_OK) { cout << "Error: " << pResult->GetErrorString() << endl; } else { auto tag = pResult->GetOriginalImageTag(); if (tag) { cout << "ImageID:" << tag->GetImageId() << endl; CFileImageTag *fileTag = (CFileImageTag *)tag; cout << "Page number:" << fileTag->GetPageNumber() << endl; } int count = pResult->GetItemsCount(); cout << "Decoded " << count << " barcodes" << endl; for (int i = 0; i < count; i++) { const CBarcodeResultItem *barcodeResultItem = pResult->GetItem(i); if (barcodeResultItem != NULL) { cout << "Result " << i + 1 << endl; cout << "Barcode Format: " << barcodeResultItem->GetFormatString() << endl; cout << "Barcode Text: " << barcodeResultItem->GetText() << endl; } } } cout << endl; } };
-
Instantiate the
CFileFetcher
class and set the PDF file path.
CFileFetcher *fileFetcher = new CFileFetcher(); cvr->SetInput(fileFetcher);
-
Read barcodes from the PDF file. Setting the second parameter of
StartCapturing()
totrue
blocks the main thread until the capturing process is complete.
cvr->SetInput(fileFetcher); cvr->AddResultReceiver(capturedReceiver); cvr->AddImageSourceStateListener(listener); char errorMsg[512] = {0}; int errorCode = cvr->StartCapturing(CPresetTemplate::PT_READ_BARCODES, true, errorMsg, 512);
The Complete C++ Program
#include <stdio.h>
#include <string>
#include <vector>
#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#include <conio.h>
#include <io.h>
#else
#include <cstring>
#include <dirent.h>
#include <sys/time.h>
#endif
#include <fstream>
#include <streambuf>
#include <iostream>
#include <sstream>
#include "DynamsoftCaptureVisionRouter.h"
#include "DynamsoftUtility.h"
#include "template.h"
using namespace std;
using namespace dynamsoft::license;
using namespace dynamsoft::cvr;
using namespace dynamsoft::dbr;
using namespace dynamsoft::utility;
using namespace dynamsoft::basic_structures;
class MyCapturedResultReceiver : public CCapturedResultReceiver
{
virtual void OnDecodedBarcodesReceived(CDecodedBarcodesResult *pResult) override
{
if (pResult->GetErrorCode() != EC_OK)
{
cout << "Error: " << pResult->GetErrorString() << endl;
}
else
{
auto tag = pResult->GetOriginalImageTag();
if (tag)
{
cout << "ImageID:" << tag->GetImageId() << endl;
CFileImageTag *fileTag = (CFileImageTag *)tag;
cout << "Page number:" << fileTag->GetPageNumber() << endl;
}
int count = pResult->GetItemsCount();
cout << "Decoded " << count << " barcodes" << endl;
for (int i = 0; i < count; i++)
{
const CBarcodeResultItem *barcodeResultItem = pResult->GetItem(i);
if (barcodeResultItem != NULL)
{
cout << "Result " << i + 1 << endl;
cout << "Barcode Format: " << barcodeResultItem->GetFormatString() << endl;
cout << "Barcode Text: " << barcodeResultItem->GetText() << endl;
}
}
}
cout << endl;
}
};
class MyImageSourceStateListener : public CImageSourceStateListener
{
private:
CCaptureVisionRouter *m_router;
public:
MyImageSourceStateListener(CCaptureVisionRouter *router)
{
m_router = router;
}
virtual void OnImageSourceStateReceived(ImageSourceState state)
{
if (state == ISS_EXHAUSTED)
m_router->StopCapturing();
}
};
bool GetImagePath(char *pImagePath)
{
std::string input;
while (true)
{
std::cout << "\n>> Step 1: Input your image file's full path:\n";
std::getline(std::cin, input);
input.erase(0, input.find_first_not_of(" \t\n\r\"\'")); // Trim leading
input.erase(input.find_last_not_of(" \t\n\r\"\'") + 1); // Trim trailing
if (input == "q" || input == "Q")
{
return true;
}
std::strncpy(pImagePath, input.c_str(), 511);
pImagePath[511] = '\0';
std::ifstream file(pImagePath);
if (file.good())
{
file.close();
return false;
}
std::cout << "Please input a valid path.\n";
}
}
int main(int argc, char *argv[])
{
printf("*************************************************\r\n");
printf("Welcome to Dynamsoft Barcode Demo\r\n");
printf("*************************************************\r\n");
printf("Hints: Please input 'Q' or 'q' to quit the application.\r\n");
int iRet = -1;
char szErrorMsg[256];
iRet = CLicenseManager::InitLicense(LICENSE-KEY, szErrorMsg, 256);
if (iRet != EC_OK)
{
cout << szErrorMsg << endl;
}
int errorCode = 1;
char errorMsg[512] = {0};
CCaptureVisionRouter *cvr = new CCaptureVisionRouter;
errorCode = cvr->InitSettings(jsonString.c_str(), errorMsg, 512);
if (errorCode != EC_OK)
{
cout << "error:" << errorMsg << endl;
return -1;
}
char pszImageFile[512] = {0};
bool bExit = false;
CImageSourceStateListener *listener = new MyImageSourceStateListener(cvr);
CFileFetcher *fileFetcher = new CFileFetcher();
CCapturedResultReceiver *capturedReceiver = new MyCapturedResultReceiver;
cvr->SetInput(fileFetcher);
cvr->AddResultReceiver(capturedReceiver);
cvr->AddImageSourceStateListener(listener);
while (1)
{
bExit = GetImagePath(pszImageFile);
if (bExit)
break;
float costTime = 0.0;
int errorCode = 0;
string path = string(pszImageFile);
fileFetcher->SetFile(pszImageFile);
errorCode = cvr->StartCapturing(CPresetTemplate::PT_READ_BARCODES, true, errorMsg, 512);
}
delete cvr, cvr = NULL, listener, capturedReceiver, fileFetcher;
return 0;
}
Integrating C++ Barcode Decoding Functionality into a Node.js Addon
We have an existing Node.js barcode SDK project, barcode4nodejs, built with Dynamsoft C++ Barcode SDK v9.x. The following steps outline how to upgrade the underlying C++ barcode decoding functionality to v10.x with minimal changes: no modifications are needed for the JavaScript code, while only a few adjustments are required for the C++ part.
- Update the shared libraries in the
platforms
folder for Windows, Linux and macOS. -
Modify the
binding.gyp
to link the new shared libraries.
{ ... "targets": [ { ... "conditions": [ ["OS=='linux'", { "defines": ["LINUX_DBR"], "cflags": ["-std=c++11", "-DNAPI_CPP_EXCEPTIONS", "-fexceptions"], "cflags_cc": ["-std=c++11", "-DNAPI_CPP_EXCEPTIONS", "-fexceptions"], "ldflags": ["-Wl,-rpath,'$$ORIGIN'"], "libraries": [ "-lDynamsoftCore", "-lDynamsoftLicense", "-lDynamsoftCaptureVisionRouter", "-lDynamsoftUtility", "-L../platforms/linux/<(arch)" ], "copies": [ { "destination": "build/Release/", "files": [ "./platforms/linux/<(arch)/libDynamicImage.so", "./platforms/linux/<(arch)/libDynamicPdf.so", "./platforms/linux/<(arch)/libDynamicPdfCore.so", "./platforms/linux/<(arch)/libDynamsoftBarcodeReader.so", "./platforms/linux/<(arch)/libDynamsoftCaptureVisionRouter.so", "./platforms/linux/<(arch)/libDynamsoftCore.so", "./platforms/linux/<(arch)/libDynamsoftImageProcessing.so", "./platforms/linux/<(arch)/libDynamsoftLicense.so", "./platforms/linux/<(arch)/libDynamsoftUtility.so", ] } ] }], ["OS=='win'", { "defines": ["WINDOWS_DBR", "NAPI_CPP_EXCEPTIONS"], "libraries": [ "-l../platforms/windows/DynamsoftCorex64.lib", "-l../platforms/windows/DynamsoftLicensex64.lib", "-l../platforms/windows/DynamsoftCaptureVisionRouterx64.lib", "-l../platforms/windows/DynamsoftUtilityx64.lib" ], "copies": [ { "destination": "build/Release/", "files": [ "./platforms/windows/DynamicImagex64.dll", "./platforms/windows/DynamicPdfCorex64.dll", "./platforms/windows/DynamicPdfx64.dll", "./platforms/windows/DynamsoftBarcodeReaderx64.dll", "./platforms/windows/DynamsoftCaptureVisionRouterx64.dll", "./platforms/windows/DynamsoftCorex64.dll", "./platforms/windows/DynamsoftImageProcessingx64.dll", "./platforms/windows/DynamsoftLicensex64.dll", "./platforms/windows/DynamsoftUtilityx64.dll", "./platforms/windows/vcomp140.dll" ] } ] }], ["OS=='mac'", { "defines": ["MAC_DBR"], "cflags": ["-std=c++11", "-DNAPI_CPP_EXCEPTIONS"], "cflags_cc": ["-std=c++11", "-DNAPI_CPP_EXCEPTIONS"], "ldflags": ["-Wl,-rpath,@loader_path"], "libraries": [ "-lDynamsoftCore", "-lDynamsoftLicense", "-lDynamsoftCaptureVisionRouter", "-lDynamsoftUtility", "-L../platforms/macos" ], "copies": [ { "destination": "build/Release/", "files": [ "./platforms/macos/libDynamicImagex64.dylib", "./platforms/macos/libDynamicPdf.dylib", "./platforms/macos/libDynamicPdfCore.dylib", "./platforms/macos/libDynamsoftBarcodeReader.dylib", "./platforms/macos/libDynamsoftCaptureVisionRouter.dylib", "./platforms/macos/libDynamsoftCore.dylib", "./platforms/macos/libDynamsoftImageProcessing.dylib", "./platforms/macos/libDynamsoftLicense.dylib", "./platforms/macos/libDynamsoftUtility.dylib", ] } ] }] ] } ] }
-
Copy the necessary header files to the
src
folder.
DynamsoftBarcodeReader.h DynamsoftCaptureVisionRouter.h DynamsoftCodeParser.h DynamsoftCore.h DynamsoftDocumentNormalizer.h DynamsoftImageProcessing.h DynamsoftLabelRecognizer.h DynamsoftLicense.h DynamsoftUtility.h template.h
-
Add
CCaptureVisionRouter *
,CFileFetcher *
,CImageSourceStateListener *
, andMyCapturedResultReceiver *
pointers as members of theBarcodeReader
class indbr.h
.
#ifndef DBR_H #define DBR_H #include <napi.h> #include <string> #include <uv.h> #include <vector> #include "DynamsoftCaptureVisionRouter.h" #include "DynamsoftUtility.h" using namespace std; using namespace dynamsoft::license; using namespace dynamsoft::cvr; using namespace dynamsoft::dbr; using namespace dynamsoft::utility; using namespace dynamsoft::basic_structures; class MyCapturedResultReceiver : public CCapturedResultReceiver { public: vector<CDecodedBarcodesResult *> results; public: virtual void OnDecodedBarcodesReceived(CDecodedBarcodesResult *pResult) override; }; typedef enum { NO_BUFFER, FILE_STREAM, YUYV_BUFFER, BASE64, RGB_BUFFER, } BufferType; struct BarcodeWorker { uv_work_t request; Napi::FunctionReference callback; int iFormat; std::string filename; vector<CDecodedBarcodesResult *> pResults; unsigned char *buffer; int size; int errorCode; int width; int height; BufferType bufferType; bool useTemplate; int stride; std::string base64string; std::string templateContent; int elapsedTime; CCaptureVisionRouter *handler; MyCapturedResultReceiver *capturedReceiver; CFileFetcher *fileFetcher; }; class MyImageSourceStateListener : public CImageSourceStateListener { private: CCaptureVisionRouter *m_router; BarcodeWorker *m_worker; public: MyImageSourceStateListener(CCaptureVisionRouter *router); virtual void OnImageSourceStateReceived(ImageSourceState state) override; }; class BarcodeReader : public Napi::ObjectWrap<BarcodeReader> { ... private: CCaptureVisionRouter *handler; CFileFetcher *fileFetcher; CImageSourceStateListener *listener; MyCapturedResultReceiver *capturedReceiver; static Napi::FunctionReference constructor; ... }; #endif // DBR_H
-
Update C++ implementation in
dbr.cc
:-
Instantiate
CCaptureVisionRouter
,MyImageSourceStateListener
,CFileFetcher
, andMyCapturedResultReceiver
in theBarcodeReader
constructor.
BarcodeReader::BarcodeReader(const Napi::CallbackInfo &info) : Napi::ObjectWrap<BarcodeReader>(info) { Napi::Env env = info.Env(); handler = new CCaptureVisionRouter; char errorMsgBuffer[256]; int ret = handler->InitSettings(jsonString.c_str(), errorMsgBuffer, 256); if (ret) { printf("InitSettings: %s\n", errorMsgBuffer); } listener = new MyImageSourceStateListener(handler); fileFetcher = new CFileFetcher(); handler->SetInput(fileFetcher); capturedReceiver = new MyCapturedResultReceiver; handler->AddResultReceiver(capturedReceiver); handler->AddImageSourceStateListener(listener); }
-
Implement the callback functions to track the image processing lifecycle and store barcode results for later use. Use
Retain()
to prevent auto-release of results.
void MyCapturedResultReceiver::OnDecodedBarcodesReceived(CDecodedBarcodesResult *pResult) { pResult->Retain(); results.push_back(pResult); } MyImageSourceStateListener::MyImageSourceStateListener(CCaptureVisionRouter *router) { m_router = router; } void MyImageSourceStateListener::OnImageSourceStateReceived(ImageSourceState state) { if (state == ISS_EXHAUSTED) { m_router->StopCapturing(); } }
-
Call
StartCapturing()
in theProcessImage()
function to initiate barcode detection on the built-in thread pool. The main thread will remain blocked until all barcode decoding tasks are complete.
void BarcodeReader::ProcessImage(BarcodeWorker *worker) { CCaptureVisionRouter *handler = worker->handler; CCapturedResult *result = NULL; if (!worker->useTemplate) { SimplifiedCaptureVisionSettings pSettings = {}; handler->GetSimplifiedSettings("", &pSettings); pSettings.barcodeSettings.barcodeFormatIds = worker->iFormat <= 0 ? BF_ALL : worker->iFormat; char szErrorMsgBuffer[256]; handler->UpdateSettings("", &pSettings, szErrorMsgBuffer, 256); } else { char errorMessage[256]; int ret = handler->InitSettings(worker->templateContent.c_str(), errorMessage, 256); if (ret) { printf("Returned value: %d, error message: %s\n", ret, errorMessage); } } int starttime = gettime(); int ret = 0; switch (worker->bufferType) { case FILE_STREAM: if (worker->buffer) { worker->fileFetcher->SetFile(worker->buffer, worker->size); } break; case YUYV_BUFFER: if (worker->buffer) { int width = worker->width, height = worker->height; int size = width * height; int index = 0; unsigned char *data = new unsigned char[size]; for (int i = 0; i < size; i++) { data[i] = worker->buffer[index]; index += 2; } CImageData *imageData = new CImageData(size, data, width, height, width, IPF_GRAYSCALED); worker->fileFetcher->SetFile(imageData); delete imageData, imageData = NULL; delete[] data, data = NULL; } break; case BASE64: if (worker->base64string != "") { worker->fileFetcher->SetFile(worker->buffer, worker->size); } break; case RGB_BUFFER: if (worker->buffer) { int width = worker->width, height = worker->height, stride = worker->stride; ImagePixelFormat format = IPF_RGB_888; if (width == stride) { format = IPF_GRAYSCALED; } else if (width * 3 == stride) { format = IPF_RGB_888; } else if (width * 4 == stride) { format = IPF_ARGB_8888; } CImageData *imageData = new CImageData(stride * height, worker->buffer, width, height, stride, format); worker->fileFetcher->SetFile(imageData); delete imageData, imageData = NULL; } break; default: worker->fileFetcher->SetFile(worker->filename.c_str()); } char errorMsg[512] = {0}; int errorCode = worker->handler->StartCapturing("", true, errorMsg, 512); if (errorCode != 0) { printf("StartCapturing: %s\n", errorMsg); } int endtime = gettime(); int elapsedTime = endtime - starttime; worker->pResults = worker->capturedReceiver->results; worker->errorCode = ret; worker->elapsedTime = elapsedTime; }
-
Use
Napi::Object
to encapsulate barcode results, making them accessible in the JavaScript layer.
void BarcodeReader::WrapResults(BarcodeWorker *worker, Napi::Env env, Napi::Object &result) { vector<CDecodedBarcodesResult *> pResults = worker->pResults; Napi::Array barcodeResults = Napi::Array::New(env); for (int j = 0; j < pResults.size(); j++) { CDecodedBarcodesResult *barcodeResult = pResults[j]; if (barcodeResult) { CFileImageTag *fileTag = (CFileImageTag *)barcodeResult->GetOriginalImageTag(); int count = barcodeResult->GetItemsCount(); for (int i = 0; i < count; i++) { const CBarcodeResultItem *barcodeResultItem = barcodeResult->GetItem(i); CPoint *points = barcodeResultItem->GetLocation().points; Napi::Object res = Napi::Object::New(env); res.Set("format", barcodeResultItem->GetFormatString()); res.Set("value", barcodeResultItem->GetText()); res.Set("x1", Napi::Number::New(env, points[0][0])); res.Set("y1", Napi::Number::New(env, points[0][1])); res.Set("x2", Napi::Number::New(env, points[1][0])); res.Set("y2", Napi::Number::New(env, points[1][1])); res.Set("x3", Napi::Number::New(env, points[2][0])); res.Set("y3", Napi::Number::New(env, points[2][1])); res.Set("x4", Napi::Number::New(env, points[3][0])); res.Set("y4", Napi::Number::New(env, points[3][1])); res.Set("page", Napi::Number::New(env, fileTag->GetPageNumber())); res.Set("time", Napi::Number::New(env, worker->elapsedTime)); res.Set("angle", Napi::Number::New(env, barcodeResultItem->GetAngle())); res.Set("isMirrored", Napi::Number::New(env, barcodeResultItem->IsMirrored())); barcodeResults.Set(i + j * count, res); } barcodeResult->Release(); } } result = barcodeResults; worker->pResults.clear(); }
-
Build the Node.js Addon
Use node-gyp
to build the Node.js barcode SDK module.
node-gyp configure
node-gyp build
On macOS, you'll need to run the following commands to ensure the .dylib
files are accessible:
install_name_tool -change @rpath/libDynamsoftCore.dylib @loader_path/libDynamsoftCore.dylib build/Release/dbr.node
install_name_tool -change @rpath/libDynamsoftLicense.dylib @loader_path/libDynamsoftLicense.dylib build/Release/dbr.node
install_name_tool -change @rpath/libDynamsoftCaptureVisionRouter.dylib @loader_path/libDynamsoftCaptureVisionRouter.dylib build/Release/dbr.node
install_name_tool -change @rpath/libDynamsoftUtility.dylib @loader_path/libDynamsoftUtility.dylib build/Release/dbr.node
install_name_tool -change @rpath/libDynamsoftImageProcessing.dylib @loader_path/libDynamsoftImageProcessing.dylib build/Release/libDynamsoftUtility.dylib
Reading Barcodes from Multi-Page PDFs in Node.js
-
Create a
test.js
file with the following code:
var dbr = require('barcode4nodejs'); var barcodeTypes = dbr.barcodeTypes; var readline = require('readline'); var fs = require('fs'); dbr.initLicense("LICENSE-KEY"); function decodeFileStreamAsync(fileName) { let stats = fs.statSync(fileName); let fileSize = stats['size']; fs.open(fileName, 'r', function (status, fd) { if (status) { console.log(status.message); return; } let buffer = Buffer.alloc(fileSize); fs.read(fd, buffer, 0, fileSize, 0, function (err, bytesRead, data) { (async function () { try { var result = await dbr.decodeFileStreamAsync(buffer, fileSize, barcodeTypes, ""); console.log(result); setTimeout(() => { console.log('terminated'); }, 1000); } catch (error) { console.log(error); } })(); }); }); } let args = process.argv; if (args.includes('-f')) { let fIndex = args.indexOf('-f'); if (args[fIndex + 1]) { decodeFileStreamAsync(args[fIndex + 1]); } else { console.log('Please add a file.'); } }
You need to replace
LICENSE-KEY
with your own. -
Run the script with the PDF file created earlier.
node test.js -f multipage.pdf
Top comments (0)