У меня есть поток pdf64 файла pdf. Теперь мне нужно преобразовать его в файл PDF в Native Javascript. Как я могу это сделать? [Дубликат]

Нет способа перекрестного браузера сделать это по соображениям безопасности. То, что обычно делают люди, - это наложение входного файла на что-то другое, и его видимость скрыта, поэтому он запускается сам по себе. Подробнее здесь.

295
задан Jeremy 27 April 2016 в 03:48
поделиться

8 ответов

Функция, описанная ниже , доступна на NPM : var b64toBlob = require('b64-to-blob')

Функция atob будет декодировать строку с кодировкой base64 в новая строка с символом для каждого байта двоичных данных.

var byteCharacters = atob(b64Data);

Кодовая точка каждого символа (charCode) будет значением байта. Мы можем создать массив байтовых значений, применяя это, используя метод .charCodeAt для каждого символа в строке.

var byteNumbers = new Array(byteCharacters.length);
for (var i = 0; i < byteCharacters.length; i++) {
    byteNumbers[i] = byteCharacters.charCodeAt(i);
}

Вы можете преобразовать этот массив байтовых значений в реальный типизированный массив байтов, пройдя это к конструктору Uint8Array.

var byteArray = new Uint8Array(byteNumbers);

Это, в свою очередь, можно преобразовать в Blob, обернув его в массив, передающий его конструктору Blob.

var blob = new Blob([byteArray], {type: contentType});

Выполняется код выше. Однако производительность может быть немного улучшена за счет обработки byteCharacters меньшими фрагментами, а не сразу. В моем грубом тестировании 512 байт кажется хорошим размером среза. Это дает нам следующую функцию.

function b64toBlob(b64Data, contentType, sliceSize) {
  contentType = contentType || '';
  sliceSize = sliceSize || 512;

  var byteCharacters = atob(b64Data);
  var byteArrays = [];

  for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    var slice = byteCharacters.slice(offset, offset + sliceSize);

    var byteNumbers = new Array(slice.length);
    for (var i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    var byteArray = new Uint8Array(byteNumbers);

    byteArrays.push(byteArray);
  }

  var blob = new Blob(byteArrays, {type: contentType});
  return blob;
}
var blob = b64toBlob(b64Data, contentType);
var blobUrl = URL.createObjectURL(blob);

window.location = blobUrl;

Полный пример:

function b64toBlob(b64Data, contentType, sliceSize) {
  contentType = contentType || '';
  sliceSize = sliceSize || 512;

  var byteCharacters = atob(b64Data);
  var byteArrays = [];

  for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    var slice = byteCharacters.slice(offset, offset + sliceSize);

    var byteNumbers = new Array(slice.length);
    for (var i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    var byteArray = new Uint8Array(byteNumbers);

    byteArrays.push(byteArray);
  }
    
  var blob = new Blob(byteArrays, {type: contentType});
  return blob;
}


var contentType = 'image/png';
var b64Data = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';

var blob = b64toBlob(b64Data, contentType);
var blobUrl = URL.createObjectURL(blob);

var img = document.createElement('img');
img.src = blobUrl;
document.body.appendChild(img);

... или ES6:

'use strict';

const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
  const byteCharacters = atob(b64Data);
  const byteArrays = [];
  
  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);
    
    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }
    
    const byteArray = new Uint8Array(byteNumbers);
    
    byteArrays.push(byteArray);
  }
  
  const blob = new Blob(byteArrays, {type: contentType});
  return blob;
}


const contentType = 'image/png';
const b64Data = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';

const blob = b64toBlob(b64Data, contentType);
const blobUrl = URL.createObjectURL(blob);

const img = document.createElement('img');
img.src = blobUrl;
document.body.appendChild(img);

565
ответ дан Jeremy 15 August 2018 в 19:34
поделиться
  • 1
    Привет, Джереми. У нас был этот код в нашем веб-приложении, и это не вызвало никаких проблем, пока файлы, загружаемые, не были больше по размеру. Таким образом, это вызвало зависания и сбои на рабочем сервере, когда пользователи использовали Chrome или IE для загрузки файлов размером более 100 МБ. Мы обнаружили, что в следующей строке IE была выделена ошибка памяти «var byteNumbers = new Array (slice.length)». Однако в chrome это был цикл for, вызывающий такую ​​же проблему. Мы не смогли найти правильное решение этой проблемы, тогда мы перешли к прямой загрузке файлов с помощью window.open. Можете ли вы здесь помочь? – Akshay Raut 29 March 2018 в 10:02
  • 2
    ты дал щедрость себе? – sweaver2112 29 June 2018 в 09:04
  • 3
    – Denny Weinberg 21 August 2018 в 17:45
  • 4
    – Pouya Jabbarisani 3 September 2018 в 13:02

См. этот пример: https://jsfiddle.net/pqhdce2L/

function b64toBlob(b64Data, contentType, sliceSize) {
  contentType = contentType || '';
  sliceSize = sliceSize || 512;

  var byteCharacters = atob(b64Data);
  var byteArrays = [];

  for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    var slice = byteCharacters.slice(offset, offset + sliceSize);

    var byteNumbers = new Array(slice.length);
    for (var i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    var byteArray = new Uint8Array(byteNumbers);

    byteArrays.push(byteArray);
  }
    
  var blob = new Blob(byteArrays, {type: contentType});
  return blob;
}


var contentType = 'image/png';
var b64Data = Your Base64 encode;

var blob = b64toBlob(b64Data, contentType);
var blobUrl = URL.createObjectURL(blob);

var img = document.createElement('img');
img.src = blobUrl;
document.body.appendChild(img);

9
ответ дан Alejandro Guevara 15 August 2018 в 19:34
поделиться

Для данных изображения мне проще использовать canvas.toBlob (асинхронный)

function b64toBlob(b64, onsuccess, onerror) {
    var img = new Image();

    img.onerror = onerror;

    img.onload = function onload() {
        var canvas = document.createElement('canvas');
        canvas.width = img.width;
        canvas.height = img.height;

        var ctx = canvas.getContext('2d');
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

        canvas.toBlob(onsuccess);
    };

    img.src = b64;
}

var base64Data = 'data:image/jpg;base64,/9j/4AAQSkZJRgABAQA...';
b64toBlob(base64Data,
    function(blob) {
        var url = window.URL.createObjectURL(blob);
        // do something with url
    }, function(error) {
        // handle error
    });
11
ответ дан amirnissim 15 August 2018 в 19:34
поделиться
  • 1
    Я думаю, вы потеряете некоторую информацию с этим ... как мета-информация, как конвертация любого изображения в png, так что это не тот же результат, также это работает только для изображений – Endless 13 September 2014 в 17:07
  • 2
    Я думаю, вы можете улучшить его, извлекая тип изображения image/jpg из строки base64, а затем передайте его как второй параметр в функцию toBlob, чтобы результат был тем же самым. Помимо этого, я думаю, что это идеально - это экономит 30% трафика и вашего дискового пространства на сервере (по сравнению с base64), и он отлично работает даже с прозрачным PNG. – icl7126 20 June 2018 в 06:30

Оптимизированная (но менее читаемая) реализация:

function base64toBlob(base64Data, contentType) {
    contentType = contentType || '';
    var sliceSize = 1024;
    var byteCharacters = atob(base64Data);
    var bytesLength = byteCharacters.length;
    var slicesCount = Math.ceil(bytesLength / sliceSize);
    var byteArrays = new Array(slicesCount);

    for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
        var begin = sliceIndex * sliceSize;
        var end = Math.min(begin + sliceSize, bytesLength);

        var bytes = new Array(end - begin);
        for (var offset = begin, i = 0; offset < end; ++i, ++offset) {
            bytes[i] = byteCharacters[offset].charCodeAt(0);
        }
        byteArrays[sliceIndex] = new Uint8Array(bytes);
    }
    return new Blob(byteArrays, { type: contentType });
}
47
ответ дан Bacher 15 August 2018 в 19:34
поделиться
  • 1
    Есть ли причина для нарезки байтов в капли? Если я не использую, есть ли недостаток или риск? – Alfred Huang 24 September 2015 в 03:21
  • 2
    Отлично работает на Android с Ionic 1 / Angular 1. Требуется Slice, иначе я запускаю OOM (Android 6.0.1). – Jürgen 'Kashban' Wahlmann 3 March 2017 в 14:16
  • 3
    Только в качестве примера я мог бы легко работать с любым типом документа в корпоративной среде как в IE 11, так и в Chrome. – santos 22 February 2018 в 16:27

Не удалось избежать публикации минималистского метода без зависимости или библиотек. Для этого требуется новый API-интерфейс. Могу ли я использовать его?

var url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="

fetch(url)
.then(res => res.blob())
.then(blob => console.log(blob))

С помощью этого метода вы также можете легко получить arraybuffer, text и json


Я сделал простой тест производительности по сравнению с версией синхронизации Джереми es6. Версия синхронизации будет блокировать UI некоторое время.

// get some dummy gradient image
var img=function(){var a=document.createElement("canvas"),b=a.getContext("2d"),c=b.createLinearGradient(0,0,200,100);a.width=a.height=3000;c.addColorStop(0,"red");c.addColorStop(1,"blue");b.fillStyle=c;b.fillRect(0,0,a.width,a.height);return a.toDataURL()}();


async function perf() {
  
  const blob = await fetch(img).then(res => res.blob())
  // turn it to a dataURI
  const url = img
  const b64Data = url.split(',')[1]

  // Jeremy Banks solution
  const b64toBlob = (b64Data, contentType = '', sliceSize=512) => {
    const byteCharacters = atob(b64Data);
    const byteArrays = [];
    
    for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
      const slice = byteCharacters.slice(offset, offset + sliceSize);
      
      const byteNumbers = new Array(slice.length);
      for (let i = 0; i < slice.length; i++) {
        byteNumbers[i] = slice.charCodeAt(i);
      }
      
      const byteArray = new Uint8Array(byteNumbers);
      
      byteArrays.push(byteArray);
    }
    
    const blob = new Blob(byteArrays, {type: contentType});
    return blob;
  }

  // bench blocking method
  let i = 1000
  console.time('b64')
  while (i--) {
    await b64toBlob(b64Data)
  }
  console.timeEnd('b64')
  
  // bench non blocking
  i = 1000

  // so that the function is not reconstructed each time
  const toBlob = res => res.blob()
  console.time('fetch')
  while (i--) {
    await fetch(url).then(toBlob)
  }
  console.timeEnd('fetch')
  console.log('done')
}

perf()

98
ответ дан Endless 15 August 2018 в 19:34
поделиться
  • 1
    Будет ли это работать, если размер строки с кодировкой base64 большой, скажем, более 665536 символов, что является пределом для размеров URI в Opera? – BlackSheep 24 October 2017 в 17:54
  • 2
    Не знаю, я знаю, что это может быть предел для adressbar, но делать что-то с AJAX может быть исключением, поскольку его не нужно отображать. Вы должны проверить это. Если бы там, где я, я бы никогда не получил строку base64 в первую очередь. Думая, что это плохая практика, требуется больше памяти и времени для декодирования и кодирования. createObjectURL вместо readAsDataURL намного лучше, например. И если вы загружаете файлы с помощью ajax, выберите FormData вместо JSON или используйте canvas.toBlob вместо toDataURL – Endless 24 October 2017 в 20:19
  • 3
  • 4

Для всех браузеров, особенно на Android. Возможно, вы можете добавить это

   try{
       blob = new Blob( byteArrays, {type : contentType});
    }
    catch(e){
        // TypeError old chrome and FF
        window.BlobBuilder = window.BlobBuilder || 
                             window.WebKitBlobBuilder || 
                             window.MozBlobBuilder || 
                             window.MSBlobBuilder;
        if(e.name == 'TypeError' && window.BlobBuilder){
            var bb = new BlobBuilder();
            bb.append(byteArrays);
            blob = bb.getBlob(contentType);
        }
        else if(e.name == "InvalidStateError"){
            // InvalidStateError (tested on FF13 WinXP)
            blob = new Blob(byteArrays, {type : contentType});
        }
        else{
            // We're screwed, blob constructor unsupported entirely   
        }
    }
17
ответ дан Jayce Lin 15 August 2018 в 19:34
поделиться

Я заметил, что Internet Explorer 11 становится невероятно медленным, когда вырезаете данные, подобные jeremy. Это верно для Chrome, но у IE возникает проблема при передаче разрезанных данных в Blob-Constructor. На моей машине, передавая 5 МБ данных, происходит сбой в IE, а потребление памяти идет через крышу. Chrome быстро создает blob.

Запустите этот код для сравнения:

var byteArrays = [],
    megaBytes = 2,
    byteArray = new Uint8Array(megaBytes*1024*1024),
    block,
    blobSlowOnIE, blobFastOnIE,
    i;

for (i = 0; i < (megaBytes*1024); i++) {
    block = new Uint8Array(1024);
    byteArrays.push(block);
}

//debugger;

console.profile("No Slices");
blobSlowOnIE = new Blob(byteArrays,  { type: 'text/plain' });
console.profileEnd();

console.profile("Slices");
blobFastOnIE = new Blob([byteArray],  { type: 'text/plain' });
console.profileEnd();

Поэтому я решил включить оба метода, описанные jeremy в одну функцию. Кредиты идут к нему за это.

function base64toBlob(base64Data, contentType, sliceSize) {

    var byteCharacters,
        byteArray,
        byteNumbers,
        blobData,
        blob;

    contentType = contentType || '';

    byteCharacters = atob(base64Data);

    // Get blob data sliced or not
    blobData = sliceSize ? getBlobDataSliced() : getBlobDataAtOnce();

    blob = new Blob(blobData, { type: contentType });

    return blob;


    /*
     * Get blob data in one slice.
     * => Fast in IE on new Blob(...)
     */
    function getBlobDataAtOnce() {
        byteNumbers = new Array(byteCharacters.length);

        for (var i = 0; i < byteCharacters.length; i++) {
            byteNumbers[i] = byteCharacters.charCodeAt(i);
        }

        byteArray = new Uint8Array(byteNumbers);

        return [byteArray];
    }

    /*
     * Get blob data in multiple slices.
     * => Slow in IE on new Blob(...)
     */
    function getBlobDataSliced() {

        var slice,
            byteArrays = [];

        for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
            slice = byteCharacters.slice(offset, offset + sliceSize);

            byteNumbers = new Array(slice.length);

            for (var i = 0; i < slice.length; i++) {
                byteNumbers[i] = slice.charCodeAt(i);
            }

            byteArray = new Uint8Array(byteNumbers);

            // Add slice
            byteArrays.push(byteArray);
        }

        return byteArrays;
    }
}
8
ответ дан martinoss 15 August 2018 в 19:34
поделиться
  • 1
    Спасибо, что включили это. С недавним обновлением до IE11 (между 5/2016 и 8/2016), генерирование капли из массивов началось с большего количества бара. Отправляя единственный конструктор Uint8Array в конструктор блога, он почти не использовал ram и фактически завершил процесс. – Andrew Vogel 11 August 2016 в 18:05
  • 2
    Увеличение размера среза в тестовом образце от 1K до 8..16K значительно сокращает время в IE. На моем ПК исходный код занял от 5 до 8 секунд, код с 8K-блоками занял всего 356 мс и 225 мс для блоков 16К – Victor 14 April 2017 в 10:33
0
ответ дан gabriele.genta 5 September 2018 в 18:47
поделиться
Другие вопросы по тегам:

Похожие вопросы: