0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PDF-LIBを用いたPDF分割ウェブアプリの制作メモ

0
Last updated at Posted at 2024-08-24

1. 背景

  • A4サイズのPDFをプリンターで設定せずにポスター印刷できるようにしたかった。
  • そこで、「A4サイズのPDFを4分割し、4枚の個別のページに分けて再生成してくれる」ウェブアプリを作ることにした。
  • 使用技術はhtml, css, React, PDF-LIB
  • PDF-LIBは使い方が全くわからなかったので、ChatGPTをフル活用した。実装中は仕組みを理解せず進めたので、本記事の執筆を通してそこを咀嚼していきたい。
  • なお、PDF-LIBを用いたPDF加工部分のみに焦点を絞り、Reactでのstateやハンドラーの管理部分には触れない。

2. PDF-LIB

各部分の構成と役割の分析

PDFを加工する部分については、個別にjsファイルを作成し、関数として記述した。それをエクスポートし、利用した。単に監理のしやすさの問題で、別にコンポーネントの中で直接記述しても問題ない。以下、ソースコードの各部分とその役割を分けてみていく。

(1) PDF-LIBはライブラリのため、このようにインポートして使用する。

import { PDFDocument } from 'pdf-lib';

(2) 以下はPDF-LIBでファイル加工する際の定型文と思って良さそうだ。PDFを分割加工する処理をsplitPdfInfoFourという定数に入れて扱えるようにしている。以降登場する関数やオブジェクト、プロパティは基本的にPDF-LIB由来のものか、ここで定義されているものなので、定型的に使うものと思っておけば良い。

const splitPdfIntoFour = async (file) => {

  const fileReader = new FileReader(); //ファイルの読み込み
  fileReader.readAsArrayBuffer(file);

  return new Promise(async (resolve, reject) => {
    fileReader.onload = async () => {
      try {
        const pdfDoc = await PDFDocument.load(fileReader.result); //上で読み込んだファイルをPDFとしてロード

(3) ここからケースに応じて必要な処理が変わるようだ。ここでは、PDfの加工にあたって、1ページ目(といっても、基本1ページしか基本)をコピーしている。

        //上の続き
        const [sourcePage] = await pdfDoc.copyPages(pdfDoc, [0]); // 1ページ目をコピー

(4) 続いて、A4サイズのページを4分割にするために、サイズと位置を設定している。

        //上の続き
        const { width, height } = sourcePage.getSize(); //分割するために、元のページの縦横を取得
        const splitWidth = width / 2; //4分割にあたって、横を半分に
        const splitHeight = height / 2; //同じく、縦を半分に

(5) 新しいPDFデータを作成する

        // 上の続き
        const newPdf = await PDFDocument.create();

(6) PDFを4分割して、4ページに分けたいということで、4枚のページを生成している。
for文のネストを用いており、直感的には変なやり方に見えるが、これは分割したそれぞれの箇所をコピーするための仕組みの一部になっている(後述)。

        // 上の続き
        for (let y = 0; y < 2; y++) {
          for (let x = 0; x < 2; x++) {
            const newPage = newPdf.addPage([splitWidth, splitHeight]);

(7) 3でコピーした元ページsourcePageから、特定の領域を切り取って、6で作った新しいページに描画していく。ここのキモは、変数xyをfor文でうまくコントロールし、それを座標として使うことで、切り取りの範囲指定に生かしていることだ。また、for文で処理することで、1ページづつ順番にコピー→貼り付けができている。

            // コピーしたページから特定の領域を切り取って新しいページに描画
            const embeddedPage = await newPdf.embedPage(sourcePage); 
            newPage.drawPage(embeddedPage, {
              x: -x * splitWidth,
              y: -y * splitHeight,
              width: width,
              height: height,
            });
          }
        }

最後に、加工したPDFをバイナリ形式でエクスポートして終了。ここも定型文と思って良いのでは。

        // 上の続き
        const pdfBytes = await newPdf.save();
        resolve(pdfBytes);
      } catch (error) {
        reject(error);
      }
    };
    fileReader.onerror = reject;
  });
};

最後に、一連の関数をエクスポートし、他のjsで使用できるようにしておく。

export default splitPdfIntoFour;

スタックしたポイント

embedPageに関するエラーで完全に詰まった。
これはdrawPageメソッドの引数を正しく指定できていなかったことが問題。

動かなかったコードと問題の箇所

Const newPdf = await PDFDocument.create();

for (let y =0; y < 2; y++) {
    for (let x = 0; x < 2; x++) {
        const newPage = newPdf.addPage([splitwidth, splitHeight]); //ここで本来は'embedPage'メソッドでもとのページのデータを取り込まないといけない
        newPage.drawPage(page, { //そして、ここでそれを描画しないといけない
            x: -x * splitWidth,
            y: -y * splitHeight,
            width: width,
            height: height,
        });
    }
}

コメントに付してある通り、まずembedPageメソッドで大元のデータを新しいPDFのデータに取り込み、それをdrawPageメソッドで描画するという手順を踏まなくてはならなかった。上記コードを実際に実行すると、embeddedPagesがないというエラーが表示され、PDFファイルは生成されない。

最後に

今後PDF-LIBのメソッドと用法を整理して行きたい

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?