【JavaScript】pdf-lib で左揃え、右揃え、中央揃えで文字列を埋め込む

 pdf-lib は JavaScript の PDF 生成・編集用ライブラリです。ソースコードが TypeScript でまとまっている点、フォントを Base64 にせずとも読み込める点、操作できる範囲が多い点など便利なのですがレイアウトを開発者側で全て制御する必要があるという欠点もあります。この記事では文字列を揃えて書き込む方法を紹介します。

PDF-LIB · Create and modify PDF documents in any JavaScript environment.
Hopding/pdf-lib: Create and modify PDF documents in any JavaScript environment

 紹介するのは左揃え、右揃え、中央揃えです。
 前提として次の関数で PDF とフォントのインスタンスを生成します。揃えについての紹介で出てくるconst {pdfDoc, fontIpamp} = await makePdfDoc()はこの関数を呼び出しています。

/** 新規PDFインスタンス生成 */
export const makePdfDoc = async (): Promise<{
  pdfDoc: PDFDocument,
  fontIpamp: PDFFont
}> => {
  const makePdfDocInstance = async () => {
    const pdfDoc = await PDFDocument.create();
    pdfDoc.registerFontkit(fontkit);
    return pdfDoc;
  }
  const getFont = async () => {
    return await fetch('/fonts/ipamp.ttf').then((res) => res.arrayBuffer());
  }
  return await Promise.all([makePdfDocInstance(), getFont()])
    .then(async ([pdfDoc, fontRaw]) => {
      // フォント埋め込み
      const fontIpamp = await pdfDoc.embedFont(fontRaw, {subset: true});
      return {
        pdfDoc, fontIpamp
      }
    })
}

 まずは左揃えです。これは簡単です。X座標を固定して書き込むだけです。Y座標について PDF を読む感覚の座標系と pdf-lib の中の座標系が異なりやすいのでそれは注意です。

const {pdfDoc, fontIpamp} = await makePdfDoc()

const page = pdfDoc.addPage(PageSizes.A4)
// XYの原点は左下。高さ = 上辺のY座標
const topY = page.getSize().height;
const lineHeight = 18;
const fontSize = 16;
/** 左揃え例 */
const leftX = 25;
const baseY = 35;
// 左上を原点の様にレイアウトを考える場合、Y座標は(左下原点における上辺のY座標 - 左上原点座標におけるY座標)で定まります。
page.drawText('左揃え', {x: leftX, y: topY- baseY, font: fontIpamp, size: fontSize});
// 何行かに並べて書く場合、あらかじめ行の高さを決めておいて
// for(i = 0; i < 行数; i++){ y = 最初のY座標 + 行の高さ * i} っぽく順々にY座標をずらすのが便利です
page.drawText('左ぞろえ二行目', {x: leftX, y: topY - (baseY + lineHeight), font: fontIpamp, size: fontSize});
page.drawText('左揃え3行目', {x: leftX, y: topY - (baseY + lineHeight * 2), font: fontIpamp, size: fontSize});

 右揃えです。pdf-lib 内に含まれているフォントの機能にはテキストの幅を取得するものがあります。これを用いて揃える右端のX座標 - テキストの幅で配置するべき X 座標を得られます。

const {pdfDoc, fontIpamp} = await makePdfDoc()
const page = pdfDoc.addPage(PageSizes.A4)
// XYの原点は左下。高さ = 上辺のY座標
const topY = page.getSize().height;
const lineHeight = 18;
const fontSize = 16;
/** 右揃え例 */
// 右端X座標の指定
// ページの右端から x pt 離れている位置を定義したい場合はページの幅 - x とします
const rightX = page.getSize().width - 25;
const baseY = 35;
// フォントインスタンスの widthOfTextAtSize メソッドから
// あるテキストを指定したフォントサイズで書いた場合の幅を取得できます
const textList = ['右揃え', '右ぞろえ二行目', '右揃え3行目'];
textList.forEach((text, i) => {
  const textWidth = fontIpamp.widthOfTextAtSize(text, fontSize);
  // 揃える右端のX座標 - テキストの幅 = テキストの左端のX座標 となるのでこれを x に指定します
  // 後は左揃えと同じです
  page.drawText(text, {x: rightX - textWidth, y: topY - (baseY + lineHeight * i), font: fontIpamp, size: fontSize})
})

 中央揃えです。図の様に中央揃えのテキストを入れたい範囲とテキストの幅からテキストの左端のX座標が得られるのでこれを用います。

const {pdfDoc, fontIpamp} = await makePdfDoc()
const page = pdfDoc.addPage(PageSizes.A4)
// XYの原点は左下。高さ = 上辺のY座標
const topY = page.getSize().height;
const lineHeight = 18;
const fontSize = 16;

/** 中央揃え例 */
// テキストが入る領域の左端を用紙左端、右端を用紙右端として中央揃えをします
const textBoxEdgeX: [number, number] = [0, page.getSize().width];
const baseY = 35;
const textList = ['中央揃え', '中央ぞろえ二行目', '中央揃え3行目'];
textList.forEach((text, i) => {
  // テキストが入る領域のX座標 + (テキストが入る領域の幅 - テキストの幅) / 2 = テキストの左端のX座標 となるのでこれを x に指定します
  // これは図がイメージしやすいです。
  // 後は他と同じです
  const textWidth = fontIpamp.widthOfTextAtSize(text, fontSize)
  const boxWidth = textBoxEdgeX[1] - textBoxEdgeX[0];
  page.drawText(text, {
    size: fontSize,
    x: textBoxEdgeX[0] + (boxWidth - textWidth) / 2,
    y: topY - (baseY + lineHeight * i),
    font: fontIpamp
  })
})

 これで pdf-lib で文字列を左右にいい感じで振り分けられます。ただ、この例のコードそのままですとコードがかさんで読み難いので何がしか関数なりメソッドなりに分割した方が便利です。これは例えば次の様にできます。
 

クリックでソースコードを展開
import './style.css'
import {makePdfDoc} from "./pdf/makePdfDoc";
import {PageSizes, PDFFont, PDFPage} from "pdf-lib";
// @ts-ignore
import download from "downloadjs";

document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
  <div>
    <button id="pdf-dl" type="button">PDFダウンロード</button>
  </div>
`
const btnEl = document.querySelector<HTMLButtonElement>('#pdf-dl');
if (btnEl) {
  btnEl.addEventListener('click', async () => {
    const {pdfDoc, fontIpamp} = await makePdfDoc()
    const page = pdfDoc.addPage(PageSizes.A4)
    // XYの原点は左下。高さ = 上辺のY座標
    const topY = page.getSize().height;
    const lineHeight = 18;
    const fontSize = 16;

    const baseY = 35;

    const leftX = 25;
    ['左揃え', '左ぞろえ二行目', '左揃え3行目'].forEach((text ,i) => {
      drawTextAlignLeft(text, {leftX, y: topY - (baseY + lineHeight * i), font:fontIpamp, fontSize, page});
    })

    const rightX = page.getSize().width - 25;
    ['右揃え', '右ぞろえ二行目', '右揃え3行目'].forEach((text, i) => {
      drawTextAlignRight(text, {rightX, y: topY - (baseY + lineHeight * i), font: fontIpamp, fontSize, page})
    });

    const textBoxEdgeX : [number, number] = [0, PageSizes.A4[0]];
    ['中央揃え', '中央ぞろえ二行目', '中央揃え3行目'].forEach((text, i) => {
      drawTextAlignCenter(text, {textBoxEdgeX, y: topY - (baseY + lineHeight * i), font: fontIpamp, fontSize, page})
    })

    const pdfRaw = await pdfDoc.save();
    download(pdfRaw, "demo.pdf", "application/pdf");
  })
}


function drawTextAlignCenter(text: string, option: {
  textBoxEdgeX: [number, number], y: number, fontSize: number, font: PDFFont, page: PDFPage
}) {
  const {textBoxEdgeX, y, fontSize, font, page} = option;
  const boxWidth = textBoxEdgeX[1] - textBoxEdgeX[0];
  const textWidth = font.widthOfTextAtSize(text, fontSize)
  page.drawText(text, {
    size: fontSize,
    x: textBoxEdgeX[0] + (boxWidth - textWidth) / 2,
    y: y,
    font: font
  })
}

function drawTextAlignLeft(text: string, option: {
  leftX: number, y: number, fontSize: number, font: PDFFont, page: PDFPage
}) {
  const {
    leftX, y, fontSize, font, page
  } = option;
  page.drawText(text, {
    x: leftX,
    y: y,
    font: font,
    size: fontSize
  });
}
function drawTextAlignRight(text: string, option: {
  rightX: number, y: number, fontSize: number, font: PDFFont, page: PDFPage
}) {
  const {
    rightX, y, fontSize, font, page
  } = option;
  const textWidth = font.widthOfTextAtSize(text, fontSize);
  page.drawText(text, {
    x: rightX - textWidth,
    y: y,
    font: font,
    size: fontSize
  });
}
>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG