Delphi×PDFiumでPDFからWEBカタログ自動生成|画像変換対応
全ページ同じサイズのPDFファイルを指定するだけで、WEBカタログが自動生成できる仕組みをDelphiで構築できます。
このページでは、PDFiumを使ってPDFから画像変換を行い、それらをHTML形式で出力するWEBカタログ生成手法を紹介します。
DLLとライブラリの導入、VCLフォームの構成、ページごとの画像保存処理まで、構造的に整理されたサンプルコード付きで解説しています。
DelphiでPDFiumを使用する基本的な方法は https://mam-mam.net/delphi/vcl_pdfium.html を参照してください。
出来上がったアプリケーションは、https://mam-mam.net/download/web-digital-catalog/からダウンロードできます。
①DLLファイルのダウンロード
DelphiでPDFiumを使う方法|PDF表示・テキスト抽出・画像変換のサンプル付き解説
を参考に「pdfium.dll」ファイルを用意します。
②ライブラリファイルのダウンロード
DelphiでPDFiumを使う方法|PDF表示・テキスト抽出・画像変換のサンプル付き解説
を参考に「PdfiumCore.pas」「PdfiumCtrl.pas」「PdfiumLib.pas」ファイルを用意します。
プロジェクトの作成
Delphiを起動し[ファイル]⇒[新規作成]⇒[VCL フォーム アプリケーション -Delphi]をクリックします。
フォームに
TLabelをドラッグ&ドロップ(Label1)し、Captionプロパティを「タイトル:」にします。
TEditをドラッグ&ドロップ(Edit1)し、Textプロパティを「デジタルカタログ」にします。
TLabelをドラッグ&ドロップ(Label2)し、Captionプロパティを「ディスクリプション:」にします。
TEditをドラッグ&ドロップ(Edit2)し、Textプロパティを「デジタルカタログです。」にします。
TOpenDialogをドラッグ&ドロップ(OpenDilaog1)し、Filterプロパティを「*.pdf|*.pdf」に設定します。
TButtonをドラッグ&ドロップ(Button1)し、Captionプロパティを「デジタルカタログ作成」に設定します。
TPanelをドラッグ&ドロップ(Panel1)します。
TMemoをドラッグ&ドロップ(Memo1)し、Alignプロパティを「alBottom」、ScrollBarsプロパティを「ssBoth」に設定します。
TMemoをドラッグ&ドロップ(Memo2)し、ScrollBarsプロパティを「ssBoth」に設定し、Linesプロパティを以下に設定します。(とても長いです)
<html lang="ja">
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="content-language" content="ja">
<meta charset="UTF-8">
<title>%title%</title>
<meta name="Description" content="%description%">
<style>
html, body { height:100%; box-sizing:border-box;}
*, *::before, *::after { box-sizing:inherit;}
body{
margin:0; padding:0; display:flex; flex-direction:column; font-size:20px;
width:100%; max-width:100%; height:100%; max-height:100%; min-height:100%;
font-family:"游ゴシック",YuGothic,"Hiragino Kaku Gothic ProN","Hiragino Sans","Meiryo UI","メイリオ",Meiryo,
"MS Pゴシック","Helvetica Neue",Arial,sans-serif;
background:#FFFFFF;
}
.view{
margin:0; padding:0; width:100%; height:calc(100% - 40px - 40px); display:flex; flex-wrap:nowrap;
}
.view>.left{
margin:0; padding:0; height:100%; width:16px; display:flex;
align-items:center; justify-content:center; flex-wrap:wrap; background:none;
}
.view>.center{
margin:0; padding:8px; width:calc(100% - 16px * 2);
max-width:100%; height:100%; overflow:hidden; position:relative;
}
.view>.center>.imgs{
margin:0; padding:0; position:relative; transform-style: preserve-3d;
perspective:300vw; cursor:grab;
}
.view>.right{
margin:0; padding:0; width:16px; display:flex; align-items:center;
justify-content:center; flex-wrap:wrap; background:none;
}
.control{
margin:0; width:100%; height:80px; display:flex; flex-direction:column;
justify-content:center; background:none;
}
.control>div:nth-child(1){
margin:0; width:100%; height:40px; display:flex; align-items:center;
justify-content:center;
}
.control>div:nth-child(1)>#page{
height:100%; margin:0; padding:2px 8px; font-size:calc(28px - 2px * 2);
}
.control>div:nth-child(2){
margin:0; width:100%; height:40px; display:flex; align-items:center; justify-content:center;
}
.button-circle{
display:inline-block; margin:2px; padding:2px; height:32px;
width:32px; color:#000; text-shadow:1px 1px 2px #999;
background:linear-gradient(#FFF, #CCC); border:none; text-decoration:none;
box-shadow: 0px 0px 4px 2px rgba(0,0,0,0.2); cursor:pointer;
border-radius:50% 50% 50% 50%; vertical-align:middle; user-select: none; font-size:0;
}
.button-circle:active{
box-shadow: 0px 0px 4px 1px rgba(0,0,0,0.2) inset; background:linear-gradient(#EEE, #BBB);
}
.button-circle:hover{
background:linear-gradient(#FFF, #DDD);
}
.button-pdf{
font-size:14px; color:#666; position:relative;
}
</style>
<script>
let catalog;
class TCatalog{
constructor(){
this.urls=[%urls%];
this.imgs=[];
this.imgsDiv=[];
this.page=0;
this.baseZ=1000;
this.paging=false;
this.zoom={x:0, y:0, z:1.0, elm:document.querySelector(".view>.center>.imgs"), parent:document.querySelector(".view>.center")};
this.ani=null; //animateオブジェクトを入れる
this.drag={isDown:false, isDrag:false, x:0, y:0};
for(let i=0;i<this.urls.length;i++){
this.imgs[i]=new Image();
this.imgs[i].setAttribute("draggable","false");
}
let t=this.page+3;
if(t>=this.urls.length){t=this.urls.length-1;}
let sub=this.urls.slice(this.page,t);
Promise.all(
sub.map(function(url){
return new Promise(function(resolve,reject){
let img=new Image();
img.onload=function(){resolve(img);}
img.onerror=function(e){reject(e);}
img.src=url;
});
})
).then(function(imgs){
imgs.forEach(function(img,i){
this.imgs[i]=img;
this.imgs[i].setAttribute("draggable","false");
}.bind(this));
this.imgW=this.imgs[0].naturalWidth;
this.imgH=this.imgs[0].naturalHeight;
this.updateViewportState();
this.setChange();
window.addEventListener("resize",function(){
requestAnimationFrame(function(){
this.updateViewportState();
this.setChange();
}.bind(this));
}.bind(this));
document.querySelectorAll(".first-page").forEach(function(elm,idx,arr){
elm.addEventListener("click",this.gotoFirst.bind(this));
}.bind(this));
document.querySelectorAll(".previous-page").forEach(function(elm,idx,arr){
elm.addEventListener("click",this.gotoPrevious.bind(this));
}.bind(this));
document.querySelectorAll(".next-page").forEach(function(elm,idx,arr){
elm.addEventListener("click",this.gotoNext.bind(this));
}.bind(this));
document.querySelectorAll(".last-page").forEach(function(elm,idx,arr){
elm.addEventListener("click",this.gotoLast.bind(this));
}.bind(this));
document.querySelectorAll(".zoom-in").forEach(function(elm,idx,arr){
elm.addEventListener("click",this.gotoZoomIn.bind(this));
}.bind(this));
document.querySelectorAll(".zoom-out").forEach(function(elm,idx,arr){
elm.addEventListener("click",this.gotoZoomOut.bind(this));
}.bind(this));
this.zoom.parent.addEventListener("mousedown",function(e){
this.drag.isDown=true;
this.drag.x=e.clientX;
this.drag.y=e.clientY;
}.bind(this));
this.zoom.parent.addEventListener("mousemove",function(e){
if(this.drag.isDown && !this.drag.isDrag){
if(Math.abs(this.drag.x-e.clientX)>2 || Math.abs(this.drag.y-e.clientY)>2){
this.drag.isDrag=true;
}
}
if(this.drag.isDrag){
this.zoom.x += this.drag.x-e.clientX;
this.zoom.y += this.drag.y-e.clientY;
this.zoom.x = this.zoom.x<0 ? 0 : this.zoom.x;
this.zoom.x = this.zoom.x>(this.zoom.parent.scrollWidth - this.zoom.parent.clientWidth) ? (this.zoom.parent.scrollWidth - this.zoom.parent.clientWidth) : this.zoom.x;
this.zoom.y = this.zoom.y<0 ? 0 : this.zoom.y;
this.zoom.y = this.zoom.y>(this.zoom.parent.scrollHeight - this.zoom.parent.clientHeight) ? (this.zoom.parent.scrollHeight - this.zoom.parent.clientHeight) : this.zoom.y;
this.zoom.parent.scrollLeft = this.zoom.x;
this.zoom.parent.scrollTop = this.zoom.y;
this.drag.x = e.clientX;
this.drag.y = e.clientY;
}
}.bind(this));
this.zoom.parent.addEventListener("mouseup",function(e){
this.drag.isDrag=false;
this.drag.isDown=false;
}.bind(this));
this.zoom.parent.addEventListener("touchstart",function(e){
e.preventDefault();
let t = e.touches[0];
this.drag.isDown=true;
this.drag.x = t.clientX;
this.drag.y = t.clientY;
}.bind(this), {passive:false});
this.zoom.parent.addEventListener("touchmove", function(e){
e.preventDefault();
let t = e.touches[0];
if(this.drag.isDown && !this.drag.isDrag){
if(Math.abs(this.drag.x-t.clientX)>2 || Math.abs(this.drag.y-t.clientY)>2){
this.drag.isDrag=true;
}
}
if (this.drag.isDrag) {
this.zoom.x += this.drag.x - t.clientX;
this.zoom.y += this.drag.y - t.clientY;
this.zoom.x = this.zoom.x<0 ? 0 : this.zoom.x;
this.zoom.x = this.zoom.x>(this.zoom.parent.scrollWidth - this.zoom.parent.clientWidth) ? (this.zoom.parent.scrollWidth - this.zoom.parent.clientWidth) : this.zoom.x;
this.zoom.y = this.zoom.y<0 ? 0 : this.zoom.y;
this.zoom.y = this.zoom.y>(this.zoom.parent.scrollHeight - this.zoom.parent.clientHeight) ? (this.zoom.parent.scrollHeight - this.zoom.parent.clientHeight) : this.zoom.y;
this.zoom.parent.scrollLeft = this.zoom.x;
this.zoom.parent.scrollTop = this.zoom.y;
this.drag.x = t.clientX;
this.drag.y = t.clientY;
}
}.bind(this), {passive:false});
this.zoom.parent.addEventListener("touchend", function(e){
this.drag.isDrag = false;
this.drag.isDown=false;
}.bind(this));
window.addEventListener("mouseup",function(){
this.drag.isDrag=false;
this.drag.isDown=false;
}.bind(this));
window.addEventListener("touchend",function(){
this.drag.isDrag=false;
this.drag.isDown=false;
}.bind(this));
}.bind(this)).catch(function(e){
//1つでも画像ファイルがロードできなければエラー
console.log('Error:ロードできない画像有り');
});
}
//画像の再構成
setChange(){
this.zoom.elm.innerHTML="";
this.imgsDiv=[];
if(this.sp){
//スマホの場合
for(let i=0;i<this.imgs.length;i++){
this.imgsDiv[i]=document.createElement("div");
this.imgsDiv[i].style.cssText="width:100%;height:100%;position:absolute;box-shadow:3px 4px 4px 0px rgba(0,0,0,0.2);z-index:"+this.baseZ+i+";";
//this.imgsDiv[i].style.filter = "drop-shadow(4px 4px 4px rgba(0,0,0,0.2))";
if(i<=this.page){
this.imgsDiv[i].style.left="0%";
}else{
this.imgsDiv[i].style.left="100%";
}
if (Math.abs(i - this.page) <= 2) {
this.imgsDiv[i].style.display = "block";
}else{
this.imgsDiv[i].style.display = "none";
}
this.zoom.elm.appendChild(this.imgsDiv[i]);
this.imgs[i].style.cssText="width:100%;height:100%;position:absolute;";
this.imgsDiv[i].appendChild(this.imgs[i]);
}
this.zoom.elm.style.overflow="hidden";
}else{
let j=0;
for(let i=0;i<this.imgs.length;i++){
if(i%2===0){
j=this.imgsDiv.length;
this.imgsDiv[j]=document.createElement("div");
this.imgsDiv[j].style.cssText="width:50%;height:100%;position:absolute;transform-style:preserve-3d;transform-origin:right center;box-shadow:-3px 4px 6px 0px rgba(0,0,0,0.2);";
if (Math.abs(j - Math.floor(this.page / 2)) <= 2) {
this.imgsDiv[j].style.display = "block";
}else{
this.imgsDiv[j].style.display = "none";
}
this.zoom.elm.appendChild(this.imgsDiv[j]);
this.imgs[i].style.cssText="width:100%;height:100%;position:absolute;transform:rotateY(180deg);backface-visibility:hidden;user-select:none;";
if( i<this.page ){
this.imgsDiv[j].style.transform="rotateY(0deg)";
this.imgsDiv[j].style.zIndex=this.baseZ-(this.page-i);
}else{
this.imgsDiv[j].style.transform="rotateY(180deg)";
this.imgsDiv[j].style.zIndex=this.baseZ+(this.page-i);
}
}else{
this.imgs[i].style.cssText="width:100%;height:100%;position:absolute;transform:rotateY(0deg);backface-visibility:hidden;user-select:none;";
}
this.imgsDiv[j].appendChild(this.imgs[i]);
}
this.zoom.elm.style.overflow="visible";
}
}
updateZIndexPC(flag=false){
let n=Math.floor((this.page+1)/2);
if(flag){n--;}
if(n<0){n=0;}
for(let i=0;i<this.imgsDiv.length;i++){
if(i<=n){
this.imgsDiv[i].style.zIndex=this.baseZ-(n-i);
}else{
this.imgsDiv[i].style.zIndex=this.baseZ-(i-n);
}
}
}
//this.zoom.elm(.view>.center>.imgs)のサイズ調整
updateViewportState(){
//親のパディングを引いた幅を求める・・・box-sizing:border-boxの為
let sty=window.getComputedStyle(this.zoom.parent);
this.zoom.parentW=parseFloat(sty.width)-parseFloat(sty.paddingLeft)-parseFloat(sty.paddingRight);
this.zoom.parentH=parseFloat(sty.height)-parseFloat(sty.paddingTop)-parseFloat(sty.paddingBottom);
if(this.zoom.parentW>=this.zoom.parentH){
this.sp=false;
}else{
this.sp=true;
}
if(this.sp){
// this.imgW:this.imgH = this.zoom.parentW:this.zoom.parentH
//の縦横比で高さ、幅のどちらに合わせるか決める
this.zoom.elm.style.boxShadow="4px 4px 4px 0px rgba(0,0,0,0.3)";
if(this.imgW/this.imgH >= this.zoom.parentW/this.zoom.parentH){
//子の幅の割合が親の幅の割合より大きいから親の幅で決める
this.zoom.elm.style.width=(this.zoom.parentW*this.zoom.z)+"px";
this.zoom.elm.style.left="0px";
this.zoom.elm.style.height=this.imgH*this.zoom.parentW/(this.imgW)*this.zoom.z+"px";
let t=(this.zoom.parentH-parseFloat(this.zoom.elm.style.height))/2;
if(t<0){t=0;}
this.zoom.elm.style.top=t+"px";
}else{
this.zoom.elm.style.height=(this.zoom.parentH*this.zoom.z)+"px";
this.zoom.elm.style.top="0px";
this.zoom.elm.style.width=(this.imgW*this.zoom.parentH/this.imgH)*this.zoom.z+"px";
let l=(this.zoom.parentW-parseFloat(this.zoom.elm.style.width))/2;
if(l<0){l=0;}
this.zoom.elm.style.left=l+"px";
}
}else{
// this.imgW*2:this.imgH = this.zoom.parentW:this.zoom.parentH の縦横比で高さ、幅のどちらに合わせるか決める
this.zoom.elm.style.boxShadow="";
if(this.imgW*2/this.imgH >= this.zoom.parentW/this.zoom.parentH){
//子の幅の割合が親の幅の割合より大きいから親の幅で決める
this.zoom.elm.style.width=(this.zoom.parentW*this.zoom.z)+"px";
this.zoom.elm.style.left="0";
this.zoom.elm.style.height=this.imgH*this.zoom.parentW/(this.imgW*2)*this.zoom.z+"px";
let t=(this.zoom.parentH-parseFloat(this.zoom.elm.style.height))/2;
if(t<0){t=0;}
this.zoom.elm.style.top=t+"px";
}else{
this.zoom.elm.style.height=(this.zoom.parentH*this.zoom.z)+"px";
this.zoom.elm.style.top="0";
this.zoom.elm.style.width=(this.imgW*2*this.zoom.parentH/this.imgH)*this.zoom.z+"px";
let l=(this.zoom.parentW-parseFloat(this.zoom.elm.style.width))/2;
if(l<0){l=0;}
this.zoom.elm.style.left=l+"px";
}
}
this.zoom.parent.scrollLeft=this.zoom.x;
this.zoom.parent.scrollTop=this.zoom.y;
document.querySelector("#page").innerHTML= (this.page+1)+"/"+this.imgs.length;
}
gotoZoomIn(){
let sc=window.getComputedStyle(this.zoom.elm);
let wc=parseFloat(sc.width)/ this.zoom.z;
let hc=parseFloat(sc.height)/ this.zoom.z;
let nz=this.zoom.z+0.5;
if(nz>4.0){ nz=4.0; }
if(nz==1.0){
this.zoom.x=0;
this.zoom.y=0;
}else{
this.zoom.x += wc*(nz-this.zoom.z)/2;
this.zoom.y += hc*(nz-this.zoom.z)/2;
}
this.zoom.z=nz;
this.updateViewportState();
}
gotoZoomOut(){
let sc=window.getComputedStyle(this.zoom.elm);
let wc=parseFloat(sc.width)/ this.zoom.z;
let hc=parseFloat(sc.height)/ this.zoom.z;
let nz=this.zoom.z-0.5;
if(nz<1.0){ nz=1.0; }
if(nz==1.0){
this.zoom.x=0;
this.zoom.y=0;
}else{
this.zoom.x += wc*(nz-this.zoom.z)/2;
this.zoom.y += hc*(nz-this.zoom.z)/2;
}
this.zoom.z=nz;
this.updateViewportState();
}
gotoPrevious(){
if(this.paging){return;}
if(this.page==0){return;}
this.paging=true;
this.zoom.x=0;
this.zoom.y=0;
this.zoom.z=1.0;
this.updateViewportState();
if(this.sp){ //■スマホ
this.zoom.x=0;
this.zoom.y=0;
this.zoom.z=1.0;
this.updateViewportState();
this.ani=this.imgsDiv[this.page].animate(
[ {left:"0%"}, {left:"100%"} ],
{
delay:0, direction:'normal', duration:300, easing:'linear',
endDelay:0, fill:'none', iterationStart:0.0, iterations:1,
}
);
this.ani.onfinish=function(){
this.imgsDiv[this.page+1].style.left="100%";
this.paging=false;
}.bind(this);
this.page--;
this.removeImgSrc();
}else{ //■PC
if(this.page % 2==1){
this.page--;
}else{
this.page-=2;
}
if(this.page<0){this.page=0;}
let aniDiv=Math.ceil(this.page/2);
this.removeImgSrc();
this.updateZIndexPC();
this.ani=this.imgsDiv[aniDiv].animate(
[ {transform:'rotateY(0deg)'},{transform:'rotateY(180deg)'} ],
{
delay:0, direction:'normal', duration:300, easing: 'linear',
endDelay:0, fill:'none', iterationStart: 0.0, iterations:1,
}
);
this.ani.onfinish=function(aniDiv){
this.imgsDiv[aniDiv].style.transform="rotateY(180deg)";
this.paging=false;
}.bind(this, aniDiv);
}
document.querySelector("#page").innerHTML= (this.page+1)+"/"+this.imgs.length;
}
gotoNext(){
if(this.paging){return;}
if(this.page>=(this.urls.length-1)){return;}
this.paging=true;
this.zoom.x=0;
this.zoom.y=0;
this.zoom.z=1.0;
this.updateViewportState();
if(this.sp){
this.page++;
if(this.page>(this.urls.length-1)){this.page=this.urls.length-1;}
this.removeImgSrc();
this.ani=this.imgsDiv[this.page].animate(
[ {left:"100%"}, {left:"0%"} ],
{
delay:0, direction:'normal', duration: 300, easing:'linear',
endDelay:0, fill:'none', iterationStart:0.0, iterations: 1,
}
);
this.ani.onfinish=function(){
this.imgsDiv[this.page].style.left="0%";
this.paging=false;
}.bind(this);
}else{ //PC
if(this.page%2==1){
this.page+=3;
}else{
this.page+=2;
}
if(this.page>(this.urls.length-1)){this.page=this.urls.length-1;}
let aniDiv=Math.floor((this.page-1)/2);
this.updateZIndexPC(true);
this.ani=this.imgsDiv[aniDiv].animate(
[ {transform:'rotateY(180deg)'},{transform:'rotateY(0deg)'} ],
{
delay:0, direction:'normal', duration:300, easing:'linear',
endDelay:0, fill:'none', iterationStart:0.0, iterations: 1,
}
);
this.ani.onfinish=function(aniDiv){
this.imgsDiv[aniDiv].style.transform="rotateY(0deg)";
this.paging=false;
}.bind(this, aniDiv);
this.removeImgSrc();
}
document.querySelector("#page").innerHTML= (this.page+1)+"/"+this.imgs.length;
}
gotoLast(){
if(this.paging){return;}
if(this.page>=(this.urls.length-1)){return;}
this.goto(this.urls.length-1);
}
gotoFirst(){
if(this.paging){return;}
if(this.page<=0){return;}
this.goto(0);
}
goto(pg){
if(this.paging){return;}
this.zoom.x=0;
this.zoom.y=0;
this.zoom.z=1.0;
this.page=pg;
this.updateViewportState();
this.removeImgSrc();
if(this.sp){
for(let i=0;i<this.imgsDiv.length;i++){
if(i<=pg){
this.imgsDiv[i].style.left="0%";
}else{
this.imgsDiv[i].style.left="100%";
}
}
}else{
let newDiv=Math.floor((this.page-1)/2);
this.updateZIndexPC();
for(let i=0;i<this.imgsDiv.length;i++){
if(i<=newDiv){
this.imgsDiv[i].style.transform="rotateY(0deg)";
}else{
this.imgsDiv[i].style.transform="rotateY(180deg)";
}
if(Math.abs(i - newDiv) <= 2) {
this.imgsDiv[i].style.display = "block";
}else{
this.imgsDiv[i].style.display = "none";
}
}
}
document.querySelector("#page").innerHTML= (this.page+1)+"/"+this.imgs.length;
}
//現在ページの前後3ページ以外のソースを消す
removeImgSrc(){
for(let i=0;i<this.imgs.length;i++){
if(Math.abs(i-this.page)<=3){
this.imgs[i].src=this.urls[i];
}else{
this.imgs[i].src="";
}
if(this.sp){
if (Math.abs(i - this.page) <= 2) {
this.imgsDiv[i].style.display = "block";
}else{
this.imgsDiv[i].style.display = "none";
}
}else{
let divPage=Math.floor(i/2);
if (Math.abs(i - this.page) <= 4) {
this.imgsDiv[divPage].style.display = "block";
}else{
this.imgsDiv[divPage].style.display = "none";
}
}
}
}
}
document.addEventListener("DOMContentLoaded",function(){
catalog=new TCatalog();
});
</script>
</head>
<body>
<div class="view">
<div class="left">
</div>
<div class="center">
<div class="imgs"></div>
</div>
<div class="right">
</div>
</div>
<div class="control">
<div>
<a class="button-circle first-page" title="最初へ">
<svg style="width:28px;height:auto;" viewBox="0 0 64 64">
<path d="M48,4 l0,56 l-32,-28 z" style="fill:#555;stroke:none;stroke-width:0;"/>
<line style="fill:none;stroke:#555;stroke-width:6;" x1="16" y1="4" x2="16" y2="60"/>
</svg>
</a>
<a class="button-circle previous-page" title="前へ">
<svg style="width:28px;height:auto;" viewBox="0 0 64 64">
<path d="M48,4 l0,56 l-40,-28 z" style="fill:#555;stroke:none;stroke-width:0;"/>
</svg>
</a>
<div id="page" style="height:auto;align-self:center;"></div>
<a class="button-circle next-page" title="次へ">
<svg style="width:28px;height:auto;" viewBox="0 0 64 64">
<path d="M16,4 l0,56 l40,-28 z" style="fill:#555;stroke:none;stroke-width:0;"/>
</svg>
</a>
<a class="button-circle last-page" title="最後へ">
<svg style="width:28px;height:auto;" viewBox="0 0 64 64">
<path d="M16,4 l0,56 l32,-28 z" style="fill:#555;stroke:none;stroke-width:0;"/>
<line style="fill:none;stroke:#555;stroke-width:6;" x1="48" y1="4" x2="48" y2="60"/>
</svg>
</a>
</div>
<div>
<a class="button-circle zoom-in" title="拡大">
<svg style="width:28px;height:auto;" viewBox="0 0 64 64">
<circle style="fill:none;stroke:#555;stroke-width:6;" cx="24" cy="24" r="16" />
<line style="fill:none;stroke:#555;stroke-width:6;" x1="36" y1="36" x2="52" y2="52" />
<line style="fill:none;stroke:#555;stroke-width:4;" x1="14" y1="24" x2="34" y2="24" />
<line style="fill:none;stroke:#555;stroke-width:4;" x1="24" y1="14" x2="24" y2="34" />
</svg>
</a>
<a class="button-circle zoom-out" title="縮小">
<svg style="width:28px;height:auto;" viewBox="0 0 64 64">
<circle style="fill:none;stroke:#555;stroke-width:6;" cx="24" cy="24" r="16" />
<line style="fill:none;stroke:#555;stroke-width:6;" x1="36" y1="36" x2="52" y2="52" />
<line style="fill:none;stroke:#555;stroke-width:4;" x1="14" y1="24" x2="34" y2="24" />
</svg>
</a>
<a class="button-circle button-pdf" href="catalog.pdf" target="_blank" title="PDF">
<span>PDF</span>
</a>
</div>
</div>
</body>
</html>
[ファイル]⇒[すべて保存]をクリックして、
「c:\Users\・・・\Embarcadero\Studio\Projects」に「VCL_PDFium_Catalog」フォルダを作成して、ユニットを「Unit1.pas」、プロジェクトを「Project1.dproj」として保存します。
「VCL_PDFium_Catalog」フォルダに「PdfiumCore.pas」「PdfiumCtrl.pas」「PdfiumLib.pas」ファイルをコピーします。
「VCL_PDFium_Catalog\win32\debug」フォルダに「pdfium-win-x86.tgz」を解凍した「bin」ディレクトリにある「pdfium.dll」ファイルをコピーします。
ソースコードの記述
IDEを「コード」に切り替えて(「F12」キーを押す)以下ソースコードを貼り付けます。
「デザイン」に切り替えて(「F12」キーを押す)、
「Form1のOnCreate」イベントプロパティに「FormCreate」を割り当て、
「Form1のOnDestroy」イベントプロパティに「FormDestroy」を割り当て、
「Button1のOnClick」イベントプロパティに「Button1Click」を割り当て、
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, StrUtils,
Vcl.ExtCtrls, {Vcl.ComCtrls,} pdfiumcore, pdfiumCtrl;
type
TForm1 = class(TForm)
Button1: TButton;
OpenDialog1: TOpenDialog;
Panel1: TPanel;
Memo1: TMemo;
Memo2: TMemo;
Edit1: TEdit;
Edit2: TEdit;
Label1: TLabel;
Label2: TLabel;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private 宣言 }
PdfControl:TPdfControl;
FilePath:String;//実行ファイルのあるフォルダ
Path:String;//画像作成時の年月日時分秒
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses winapi.shellapi, System.Win.Registry, Vcl.Imaging.pngimage;
//デフォルトブラウザのフルパスを取得する
function GetDefaultBrowserCommand: string;
var Reg: TRegistry;
i:Integer;
begin
Reg := TRegistry.Create(KEY_READ);
try
Reg.RootKey := HKEY_CLASSES_ROOT;
if Reg.OpenKeyReadOnly('http\shell\open\command') then
Result := Reg.ReadString('');
finally
Reg.Free;
end;
if Result<>'' then
begin
if LeftStr(Result,1)='"' then
begin
i := Pos('"', Result,2);
Result:=Result.Substring(0,i);
end;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var st, HtmlText:string;
i:Integer;
Rct:TRect;
Bmp:TBitmap;
Png:TPngImage;
StrList,UrlList,TextList:TStringList;
SettingWidth:Integer;
begin
st:=OpenDialog1.FileName;
if st<>'' then
begin
OpenDialog1.InitialDir:=ExtractFileDir(st);
OpenDialog1.FileName:=ExtractFileName(st);
end;
if not OpenDialog1.Execute(self.Handle) then exit;
Button1.Enabled:=False;
SettingWidth:=1500;//幅1500pxで各ページを出力する
Path:=FormatDateTime('yyyymmddhhnnss',now());
CreateDir(FilePath+Path);
CreateDir(FilePath+Path+'\imgs');
Memo1.Clear;
CopyFile(PChar(OpenDialog1.FileName),PChar(FilePath+Path+'\catalog.pdf'),False);
PdfControl.LoadFromFile(OpenDialog1.FileName);
Bmp:=TBitmap.Create;
Png:=TPngImage.Create;
UrlList:=TStringList.Create;
TextList:=TStringList.Create;
try
for i := 0 to PdfControl.PageCount-1 do
begin
PdfControl.PageIndex:=i;
Rct:=pdfcontrol.GetPageRect;
Bmp.Width:=SettingWidth;
Bmp.Height:=Rct.Height*SettingWidth div Rct.Width;
PdfControl.Document.Pages[i].Draw(
Bmp.Canvas.Handle,0,0, Bmp.Width, Bmp.Height
);
Bmp.PixelFormat:=pf24bit;
Png.Assign(Bmp);
Png.CompressionLevel:=7;
Png.SaveToFile(FilePath+Path+'\imgs\'+Format('%.4d.png', [i]));
Memo1.Lines.Add( '変換中 '+IntToStr(i+1)+'/'+IntToStr(PdfControl.PageCount)+':'+Format('%.4d.png', [i]) );
UrlList.Add('./imgs/'+Format('%.4d.png', [i]));
Application.ProcessMessages;
end;
finally
Bmp.Free;
Png.Free;
end;
st:='';
for i := 0 to UrlList.Count-1 do
st:=st+'"'+UrlList[i]+'"'+IfThen(i<(UrlList.Count-1),',');
HtmlText:=Memo2.Text;
HtmlText:=StringReplace(HtmlText,'%title%',Edit1.Text,[rfReplaceAll]);
HtmlText:=StringReplace(HtmlText,'%description%',Edit2.Text,[rfReplaceAll]);
HtmlText:=StringReplace(HtmlText,'%urls%',st,[rfReplaceAll]);
StrList:=TStringList.Create;
try
StrList.Text:=HtmlText;
StrList.SaveToFile(FilePath+Path+'\index.html',TEncoding.UTF8);
finally
StrList.Free;
end;
UrlList.Free;
TextList.Free;
Button1.Enabled:=True;
Memo1.Lines.Add(FilePath+Path);
Memo1.Lines.Add('デジタルカタログの作成完了');
//ShellExecute(Handle, 'open', 'explorer.exe', PChar(FilePath+Path), nil, SW_SHOW);
st:=GetDefaultBrowserCommand();
ShellExecute(Handle, 'open', PChar(st), PChar('"file:///'+FilePath+Path+'\index.html"'), nil, SW_SHOW);
PdfControl.Close;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
PDFiumDllDir := ExtractFileDir(Application.ExeName);
PdfControl := TPdfControl.Create(Self);
PdfControl.Parent := Panel1;
FilePath:=ExtractFilePath(Application.ExeName);
if RightStr(FilePath,1)<>'\' then Path:=Path+'\';
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
PdfControl.Free;
end;
end.
実行する
実行して「Button1」をクリックすると、ファイル選択ダイアログが表示されるので、全て同じサイズのページで2ページ以上あるPDFファイルを選択します。
PDFファイルから文字列を抜き出し画像変換され、WEBブラウザで動作するデジタルカタログが生成されます。
作成したWEBデジタルカタログはこちら
