Почему nodeJs не читает весь двоичный файл с диска?

У меня есть файл PDF, который я хочу прочитать в памяти с помощью NodeJS. В идеале я хотел бы закодировать его, используя base64 для его передачи. Но почему-то функция read не читает полный файл PDF, что для меня не имеет смысла. Исходный PDF-файл был сгенерирован с помощью pdfKit, и его можно просмотреть с помощью программы для чтения PDF-файлов.

Исходный файл test.pdf имеет размер 90 КБ на диске. Но если я читаю и записываю его обратно на диск, там всего 82 КБ, а новый PDF test-out.pdf не в порядке. Программа просмотра PDF говорит:

Невозможно открыть документ. PDF-документ поврежден.

Следовательно, кодировка base64 также работает некорректно. Я протестировал его, используя этот веб-сервис. Кто-нибудь знает, почему и что здесь происходит? И как решить.

Я уже нашел этот пост.

fs = require('fs');
let buf = fs.readFileSync('test.pdf'); // returns raw buffer binary data
// buf = fs.readFileSync('test.pdf', {encoding:'base64'}); // for the base64 encoded data
// ...transfer the base64 data...
fs.writeFileSync('test-out.pdf', buf); // should be pdf again

РЕДАКТИРОВАТЬ MCVE:

const fs = require('fs');
const PDFDocument = require('pdfkit');

let filepath = 'output.pdf';

class PDF {
  constructor() {
    this.doc = new PDFDocument();
    this.setupdocument();
    this.doc.pipe(fs.createWriteStream(filepath));
  }

  setupdocument() {
    var pageNumber = 1;
    this.doc.on('pageAdded', () => {
        this.doc.text(++pageNumber, 0.5 * (this.doc.page.width - 100), 40, {width: 100, align: 'center'});
      }
    );

    this.doc.moveDown();
    // draw some headline text
    this.doc.fontSize(25).text('Some Headline');
    this.doc.fontSize(15).text('Generated: ' + new Date().toUTCString());
    this.doc.moveDown();
    this.doc.font('Times-Roman', 11);
  }

  report(object) {

    this.doc.moveDown();
    this.doc
      .text(object.location+' '+object.table+' '+Date.now())
      .font('Times-Roman', 11)
      .moveDown()
      .text(object.name)
      .font('Times-Roman', 11);

    this.doc.end();
    let report = fs.readFileSync(filepath);
    return report;
  }
}

let pdf = new PDF();
let buf = pdf.report({location: 'athome', table:'wood', name:'Bob'});
fs.writeFileSync('outfile1.pdf', buf);

person avermaet    schedule 26.01.2021    source источник


Ответы (2)


Опция encoding для fs.readFileSync() предназначена для того, чтобы вы сообщили функции readFile, в какой кодировке уже находится файл, чтобы код, читающий файл, знал, как интерпретировать данные, которые он читает. Он не конвертирует его в эту кодировку.

В этом случае ваш PDF-файл является двоичным - это не base64, поэтому вы говорите ему попытаться преобразовать его из base64 в двоичный, что приводит к тому, что он искажает данные.

Вы вообще не должны передавать параметр encoding, и тогда вы получите двоичный буфер RAW (это то, чем является файл PDF - необработанный двоичный файл). Если вы затем по какой-то причине захотите преобразовать это в base64, вы можете сделать с ним buf.toString('base64'). Но это не его исходный формат, и если вы запишете преобразованные данные обратно на диск, это не будет легальным PDF-файлом.

Чтобы просто прочитать и записать один и тот же файл в файл с другим именем, полностью отключите параметр кодирования:

const fs = require('fs');
let buf = fs.readFileSync('test.pdf'); // get raw buffer binary data
fs.writeFileSync('test-out.pdf', buf); // write out raw buffer binary data
person jfriend00    schedule 26.01.2021
comment
Кодировка по умолчанию — «utf8» в операциях чтения/записи, поэтому нужно ли нам использовать «двоичный» при работе с двоичными файлами, такими как pdf, изображения и т. д.? - person Vinay; 26.01.2021
comment
@Viney. Согласно fs.readFileSync() документации, по умолчанию используется null, что означает просто оставить его. как бы то ни было - не пытайтесь интерпретировать это то, что вы хотите для двоичного кода. - person jfriend00; 26.01.2021
comment
Спасибо за подтверждение и объяснение. Основная проблема в том, что readFileSync почему-то не читает (весь) файл. В MCVE, который я только что добавил к моему вопросу, ничего не читается, а в моем реальном документе читаются только те же 82 КБ, независимо от того, насколько велик файл на самом деле (без ошибок). Ты знаешь почему? Он работает, как и ожидалось, с другими pdf-файлами (например, сгенерированными в MS Word), но не с pdfkit. Но, как указано в вопросе, сгенерированный файл из pdfkit output.pdf может быть прочитан как обычно и, кажется, полностью в порядке. output1.pdf не работает. - person avermaet; 26.01.2021

После долгих поисков я нашел эту проблему Github. Проблема в моем вопросе, похоже, заключается в вызове doc.end(), который по какой-то причине не ждет завершения потока (событие finish потока записи). Поэтому, как было предложено в выпуске Github, работают следующие подходы:

  • на основе обратного вызова:
doc = new PDFDocument();
writeStream = fs.createWriteStream('filename.pdf');
doc.pipe(writeStream);
doc.end()
writeStream.on('finish', function () {
    // do stuff with the PDF file
});
  • или на основе обещаний:
const stream = fs.createWriteStream(localFilePath);
doc.pipe(stream);
.....
doc.end();
await new Promise<void>(resolve => {
  stream.on("finish", function() {
    resolve();
  });
});
  • или, что еще лучше, вместо прямого вызова doc.end() вызовите функцию savePdfToFile ниже:
function savePdfToFile(pdf : PDFKit.PDFDocument, fileName : string) : Promise<void> {
  return new Promise<void>((resolve, reject) => {

    //  To determine when the PDF has finished being written sucessfully 
    //  we need to confirm the following 2 conditions:
    //
    //  1. The write stream has been closed
    //  2. PDFDocument.end() was called syncronously without an error being thrown

    let pendingStepCount = 2;

    const stepFinished = () => {
      if (--pendingStepCount == 0) {
        resolve();
      }
    };

    const writeStream = fs.createWriteStream(fileName);
    writeStream.on('close', stepFinished);
    pdf.pipe(writeStream);

    pdf.end();

    stepFinished();
  }); 
}

Эта функция должна корректно обрабатывать следующие ситуации:

  • PDF успешно сгенерирован
  • Ошибка возникает внутри pdf.end() перед закрытием потока записи
  • Ошибка возникает внутри pdf.end() после закрытия потока записи
person avermaet    schedule 01.02.2021