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

# 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で作った新しいページに描画していく。ここのキモは、変数`x`と`y`を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のメソッドと用法を整理して行きたい