えらいひと「スマホから見たVisualforceページを見た目そのままスマホからPDF化したい」
ぼく「なんでそういうこと言うの?」
※昔に書いたものなのでふんわりしています
そもそもできるのか?
VisualforceのPDF化はrenderAs="pdf"
でてきますが、制限が多く見た目そのままとはならなかったため、別の手段を探すことに……。
いろいろ調べてみると↓のようにやればできるとかなんとか。
- html2canvasにCanvasに表示したWebページを画像化
- 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>
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');
})();
}
captureImage
のurl
にキャプチャした画像が入ります。画像サイズは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を作成します。
えらいひと「あっ、やっぱりいいや。これなしで」
ぼく「なんでそういうこと言うの?????」