workspace-pdf.js

'use strict';
(() => {
 const W=window.WORKSPACE;
 let libraries,fontBytes;
 const load=src=>new Promise((resolve,reject)=>{const s=document.createElement('script');s.src=src;s.onload=resolve;s.onerror=()=>reject(Error('PDFライブラリを取得できません。'));document.head.append(s);});
 const prepare=()=>libraries??=Promise.all([load('assets/pdf-lib.min.js'),load('assets/fontkit.min.js')]).catch(error=>{libraries=null;throw error;});
 W.downloadPDF=async(id,button)=>{
  const original=button.textContent;button.disabled=true;button.textContent='PDF作成中…';
  try{
   await prepare();fontBytes??=await fetch('assets/pdf-japanese.ttf').then(r=>{if(!r.ok)throw Error('日本語フォントを取得できません。');return r.arrayBuffer();});
   const {PDFDocument,rgb}=window.PDFLib,pdf=await PDFDocument.create();pdf.registerFontkit(window.fontkit);const font=await pdf.embedFont(fontBytes,{subset:true});
   const selectedPeriod=W.period?.();
   const green=rgb(.063,.13,.106),ink=rgb(.12,.20,.16),gray=rgb(.35,.42,.38),pale=rgb(.93,.95,.92),month=W.state.month,created=new Intl.DateTimeFormat('ja-JP',{timeZone:'Asia/Tokyo'}).format(new Date());
   let page,y;
   const newPage=title=>{page=pdf.addPage([595.28,841.89]);page.drawRectangle({x:0,y:755,width:596,height:87,color:green});page.drawText('SAMURAI  /  '+month,{x:42,y:806,size:11,font,color:rgb(1,1,1)});page.drawText(title,{x:42,y:779,size:16,font,color:rgb(1,1,1)});y=725;};
   const line=(text,size=10,color=ink)=>{let current='';for(const c of String(text)){if(c==='\n'){put(current,size,color);current='';continue;}if(font.widthOfTextAtSize(current+c,size)>510){put(current,size,color);current=c;}else current+=c;}if(current)put(current,size,color);};
   const put=(text,size,color)=>{if(y<65)newPage('続き');page.drawText(text,{x:42,y,size,font,color});y-=size*1.65;};
   const heading=text=>{y-=12;line(text,13,green);y-=5;};
   const docPage=doc=>{newPage(doc.title);line('対象月:'+month+'  作成日:'+created,10,gray);const info=W.values(doc);line('区分:'+info.source,10,gray);heading('数値一覧');
    for(const [key,label,unit]of doc.metrics){if(y<95)newPage(doc.title+'(続き)');page.drawRectangle({x:36,y:y-11,width:523,height:31,color:pale});page.drawText(label,{x:44,y,size:10,font,color:ink});const value=W.format(info.data[key])+' '+unit;page.drawText(value,{x:550-font.widthOfTextAtSize(value,12),y,size:12,font,color:ink});y-=39;}
    if(!Object.keys(info.data).length){y-=6;line('この月の資料・数値は未接続です。未接続の数値を実績として表示していません。',10,gray);}
    if(id!=='monthly'&&selectedPeriod){heading('対象期間:'+W.periodLabel());line('主要数値は終了月の値です。月末残高・比率を期間分合計していません。',9,gray);if(selectedPeriod.start!==selectedPeriod.end){for(const row of W.periodRows(doc)){heading(row.month+' / '+row.source);for(const [key,label,unit]of doc.metrics)line(label+':'+W.format(row.data[key])+' '+unit,10);}}}
    heading('月締めメモ');line(W.state.notes[month+'/'+doc.id]||'メモなし',10);heading('関連資料');line(doc.parentTitle+' / '+doc.title,10);line('画面内のメインメニューと関連資料から、ほかの資料へ移動できます。',10,gray);
   };
   const title=id==='monthly'?month+' 月締めまとめ':W.find(id)?.title;if(!title)throw Error('資料が見つかりません。');pdf.setTitle(title);pdf.setAuthor('SAMURAI');pdf.setSubject(month+' 月締め確認用。サンプル・未接続・入力値の区分を表示。');pdf.setCreator('SAMURAI Workspace');
   if(id==='monthly'){
    const available=W.docs.filter(d=>Object.keys(W.values(d).data).length),included=W.docs.filter(d=>Object.keys(W.values(d).data).length||(W.state.notes[month+'/'+d.id]||'').trim());
    newPage(month+' 月締めまとめ');line('対象月:'+month+'  作成日:'+created,10,gray);heading('資料の確認状況');line('資料数:'+W.docs.length+' / 数値のある資料:'+available.length+'(検算用サンプルを含む)',11);line('このPDFは画面確認用です。承認や業務上の確定処理ではありません。',10,gray);heading('確認チェック');['数値を照合','資料を確認','差異・申し送りを記録','PDFを保存'].forEach((label,n)=>line((W.state.checks[month]?.[n]?'確認済み:':'未確認:')+label));heading('資料一覧');for(const doc of W.docs){line(doc.parentTitle+' / '+doc.title,10);line(' '+W.values(doc).source,9,gray);y-=5;}for(const doc of included)docPage(doc);
   }else docPage(W.find(id));
   for(const [n,p]of pdf.getPages().entries())p.drawText(`${month} / ${n+1} of ${pdf.getPageCount()}  |  SAMURAI`,{x:42,y:32,size:8,font,color:gray});
   const bytes=await pdf.save(),url=URL.createObjectURL(new Blob([bytes],{type:'application/pdf'})),a=document.createElement('a');a.href=url;a.download=(id==='monthly'?month+'_月締めまとめ':(selectedPeriod&&selectedPeriod.start!==selectedPeriod.end?selectedPeriod.start+'_'+selectedPeriod.end:month)+'_'+title).replace(/[\\/:*?"<>|]/g,'_')+'.pdf';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);W.ctx.notify('PDFファイルを作成しました。');
  }finally{button.disabled=false;button.textContent=original;}
 };
})();