2
3

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.

VisualforceページのPDF化〜Contentへ保存

Last updated at Posted at 2022-12-21

えらいひと「スマホから見たVisualforceページを見た目そのままスマホからPDF化したい」
ぼく「なんでそういうこと言うの?」

※昔に書いたものなのでふんわりしています

そもそもできるのか?

VisualforceのPDF化はrenderAs="pdf"でてきますが、制限が多く見た目そのままとはならなかったため、別の手段を探すことに……。
いろいろ調べてみると↓のようにやればできるとかなんとか。

  1. html2canvasにCanvasに表示したWebページを画像化
  2. jsPDFでPDF化

とりあえずやってみる

Visualforce

<apex:page showHeader="false" sidebar="false" applyBodyTag="false">
    <head>
        <!-- viewport -->
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"/>
        <!-- inclide -->
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
        <title>このページをPDF化するよ</title>
    </head>

    <body>
        <nav>
            <button type="button" id="btn" onclick="convertPdf();">PDF作成!</button>
        </nav>
        <section>
            <div id="page_1">
                <p>吾輩は猫である。</p>
            </div>
        </section>
        <section>
            <div id="page_2">
                <p>おはようございます</p>
                <p>こんにちは</p>
                <p>こんばんは</p>
                <p>おやすみなさい</p>
            </div>
        </section>
    </body>
</apex:page>

スクリーンショット 2022-12-21 14.33.16.png
PDF化したいVisualforceはこんな感じで作りました。

JavaScript

/* デバイスピクセル比 */
var device_pixel = 2.5;
/* キャプチャ情報を入れるリスト */
var captureImages;
/* html2canvas options */
var canvasOptions = {scale: device_pixel};

/* keys */
const KEY_URL = 'url';
const KEY_WIDTH = 'width';
const KEY_HEIGHT = 'height';
const FILE_NAME = 'ファイル.pdf';

/**
 * キャプチャ処理
 * @param element DOM要素
 */
var capture = async (element) => {
  await html2canvas(element, canvasOptions).then(canvas => {
      let captureImage = new Map();
      captureImage.set(KEY_URL, canvas.toDataURL('image/jpeg'));
      captureImage.set(KEY_WIDTH, canvas.width / device_pixel);
      captureImage.set(KEY_HEIGHT, canvas.height / device_pixel);
      captureImages.push(captureImage);
  });
}

/**
 * PDF作成
 * @param format jsPDF format
 */
function createPdf(format) {

  const { jsPDF } = window.jspdf;
  let pdf = new jsPDF({orientation: 'p', unit: 'px', format: format});

  /* キャプチャ数だけループ */
  for (let i = 0; i < captureImages.length; i++) {
    /* 1ページ目以降ページ追加 */
    if (i != 0) pdf.addPage(format);
    
    let c = captureImages[i];
    pdf.addImage(c.get(KEY_URL), 'JPEG', 0, 0, c.get(KEY_WIDTH), c.get(KEY_HEIGHT));
  }
  pdf.setProperties({title: FILE_NAME});
  return pdf;
}

/**
 * PDF作成
 */
function convertPdf() {

  /* リスト作成 */
  captureImages = new Array();
  /* windowサイズを800pxで計算する */
  canvasOptions['windowWidth'] = 800;

  (async () => {
    /* ページ分キャプチャ */
    let width = height = 0;
    let pages = document.querySelectorAll("[id^='page']");
    for (let i = 0; i < pages.length; i++) {
      await capture(pages[i]);

      /* 縦幅を一番大きいものに合わせる */
      let c = captureImages[i];
      height = (height < c.get(KEY_HEIGHT)) ? c.get(KEY_HEIGHT) : height;
    }
    width = captureImages[0].get(KEY_WIDTH);

    /* PDF format */
    const PDF_FORMAT = [width, height];
    let pdf = createPdf(PDF_FORMAT);
    window.open(pdf.output('bloburl',{filename: FILE_NAME}),'_blank');
  })();
}

captureImageurlにキャプチャした画像が入ります。画像サイズはcanvasのサイズはデバイスピクセル比で割って計算します。
デバイスピクセル比は解像度とPDF化したときのファイルサイズの兼ね合いから2と3の間をとって2.5としました。

window.open(pdf.output('bloburl',{filename: FILE_NAME}),'_blank');

「PDF作成!」ボタンを押すとWebページをキャプチャして作成したPDFファイルが開く仕組み。

ぼく「できました!!」

えらいひと「PDF化したのを自動でファイルに保存して欲しい」
ぼく「なんでそういうこと言うの?」

Contentへ保存できるように修正

えらいひとに言われたのでPDF化したファイルを開く処理からPDF化したファイルをContentにブチ込む処理へ修正します。泣いてはいけません。

Visualforceを修正

<apex:page showHeader="false" sidebar="false" applyBodyTag="false" controller="PdfController">
    <!-- 〜〜略〜〜 -->
    <body>
        <nav>
            <button type="button" id="btn" onclick="convertPdf('{! recordId}');">PDF作成!</button>
        </nav>
        <!-- 〜〜略〜〜 -->
    </body>
</apex:page>

コントローラーを追加してconvertPdfにレコードIdを渡します。

JavaScriptを修正

/* RemortAction */
var remoteAction = function(recordId, blob) {
  PdfController.insertPdf(recordId, blob, function(result, event) {
      if (event.status) console.log("success!");
  });
};

/**
 * PDF作成
 */
function convertPdf(recordId) {

  /* リスト作成 */
  captureImages = new Array();
  /* windowサイズを800pxで計算する */
  canvasOptions['windowWidth'] = 800;

  (async () => {
    /* ページ分キャプチャ */
    let width = height = 0;
    let pages = document.querySelectorAll("[id^='page']");
    for (let i = 0; i < pages.length; i++) {
      await capture(pages[i]);

      /* 縦幅を一番大きいものに合わせる */
      let c = captureImages[i];
      height = (height < c.get(KEY_HEIGHT)) ? c.get(KEY_HEIGHT) : height;
    }
    width = captureImages[0].get(KEY_WIDTH);

    /* PDF format(余白を100px持たせる) */
    const PDF_FORMAT = [width, height + 100];
    let pdf = createPdf(PDF_FORMAT);
    //window.open(pdf.output('bloburl',{filename: FILE_NAME}),'_blank');

    /* RemortAction */
    let blob = pdf.output('datauristring',{filename: FILE_NAME});
    remoteAction(recordId, blob);
  })();
}

RemortActionを呼び出す処理を追加します。

Apexコントローラーを作成

public class PdfController {
    /* 〜〜略〜〜 */
    
    @RemoteAction
    public static String insertPdf(Id recordId, String b) {
        
        ContentVersion cv = new ContentVersion();
        cv.ContentLocation = 'S';
        cv.PathOnClient = 'file.pdf';
        cv.Title = 'ファイル';
        // data:application/pdf;base64,バイナリ→base64エンコード済みの文字列 の形でくる
        // base64でデコードしてからいれる
        cv.VersionData = EncodingUtil.base64Decode(b.substringAfter(','));
        insert cv;

        // レコードへの関連
        ContentDocumentLink cdl = new ContentDocumentLink();
        cdl.ContentDocumentId = [SELECT ContentDocumentId FROM ContentVersion WHERE Id = :cv.Id].ContentDocumentId;
        cdl.LinkedEntityId = recordId;
        cdl.ShareType = 'V';
        insert cdl;
    }
}

RemoteActionでApexを呼び出し、Contentを作成します。

えらいひと「あっ、やっぱりいいや。これなしで」

ぼく「なんでそういうこと言うの?????」

2
3
1

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
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?