諸々の事情で,Base64 化されたファイル文字列をブラウザで復元して扱いたいケースがあり,そのとき検証した作業ログ.画像と PDF データを扱う.
画像ファイルの場合
Base64 エンコードされた画像データは,<img>
タグの src
に指定することができる.
Data URLs の形式で,次のように記述する.
<!-- data:[<mediatype>][;base64],<data> -->
<img src="data:image/png;base64,R0lGODlhAQABAIAA•••"/>
PDF ファイルの場合
PDF データは,Blob に変換した後,ファイル URL を取得しアクセスすると表示できる.
const base64Str = 'JVBERi0xLjMNCiXi48/TDQoNCjE•••';
const file = new Blob([atob(base64Str)], {
type: 'application/pdf'
});
const fileURL = URL.createObjectURL(file);
window.open(fileURL);
と,思ったが正常に表示できるケースと,表示されないケースがあり,原因がわからず.. 具体的には,枠は表示されるが内容が真っ白になってしまうという状況.
pdf.js を使う
Mozilla 製の pdf.js を使うと正常に表示できた. pdf.js には viewer もついているが,.pdf ファイルの実態が必要になるので使わず,今回はファイル内容を直接扱っている.
ビルド済みのパッケージ pdfjs-dist を使う.
.getDocument
で Bae64 化されたファイルを読み込み,pdfDoc.getPage(1).render()
でファイル内容をレンダリングする.この例は,1ページだけレンダリングするが,pdfDoc.numPages
で得られるページ数分描画すれば複数ファイル内容を一度に表示可能.
import PDFJS from 'pdfjs-dist';
const base64Str = 'JVBERi0xLjMNCiXi48/TDQoNCjE•••';
const canvasContainer = document.createElement('div');
PDFJS.getDocument({data: atob(base64Str)}).then((pdfDoc) => {
// 1ページ目
const firstPage = pdfDoc.getPage(1);
const viewport = firstPage.getViewport(options.scale);
const canvas = document.createElement('canvas');
canvas.height = viewport.height;
canvas.width = viewport.width;
canvas.style.display = 'block';
const ctx = canvas.getContext('2d');
const renderContext = {
canvasContext: ctx,
viewport: viewport
};
// 1ページ目がレンダリング完了したら,別タブにファイル内容を表示
return firstPage.render(renderContext).then(() => {
canvasContainer.appendChild(canvas);
const win = window.open("");
win.document.body.appendChild(canvasContainer);
});
});
まとめ
pdf.jsのソースコード を追った感じだと,
- Base64 文字列を Byte 配列( Uint8Array ) に変換
- ファイルを構築する処理を Woker に委譲 (
GetDocRequest
メッセージ + Bytes を Worker に送信) - Worker 内で
WorkerMessageHandler.createDocumentHandler
が呼ばれる - PDFManager 内部で PDFDocument.prototype.parse でパース (PDF の構文に基づく)
という処理の流れ. 実際のパース処理がかなり複雑で全部読んでいないが,自分で実装するのはかなりツラそう.. 前述の Blob に変換する方式だと内部で PDF 構造の判別をしているわけではないので,正常に表示されないのも当たり前な気がする.