← GPT-AI-SLIDE
CODE · 3.0 KB

src/pptx-preflight.js

Workspace snapshot · 09/04 13:45

import JSZip from "jszip";
const decodeXml=(value)=>String(value||"").replaceAll("&amp;","&").replaceAll("&lt;","<").replaceAll("&gt;",">").replaceAll("&quot;",'"');
const slideNumber=(name)=>Number(name.match(/slide(\d+)\.xml$/u)?.[1]||0);
const systemFonts=new Set(["Arial","Calibri","Aptos","Times New Roman","Yu Gothic","Meiryo","+mj-lt","+mn-lt","+mj-ea","+mn-ea"]);
export async function analyzePptx(input){
  const buffer=input instanceof ArrayBuffer?input:await input.arrayBuffer(); const zip=await JSZip.loadAsync(buffer);
  const names=Object.keys(zip.files).filter((name)=>/^ppt\/slides\/slide\d+\.xml$/u.test(name)).sort((a,b)=>slideNumber(a)-slideNumber(b));
  if(!names.length)throw new Error("PPTXのスライドを確認できませんでした");
  const slides=[]; const fonts=new Set();
  for(const name of names){
    const xml=await zip.file(name).async("string"); const text=[...xml.matchAll(/<a:t>([\s\S]*?)<\/a:t>/gu)].map((m)=>decodeXml(m[1])).join("");
    const textElements=(xml.match(/<a:t>/gu)||[]).length, shapes=(xml.match(/<p:sp>/gu)||[]).length, pictures=(xml.match(/<p:pic>/gu)||[]).length, charts=(xml.match(/<c:chart\b/gu)||[]).length;
    const hasAutoFit=/<a:(?:spAutoFit|normAutoFit)\b/gu.test(xml); for(const match of xml.matchAll(/typeface="([^"]+)"/gu))fonts.add(decodeXml(match[1]));
    const risks=[]; if(!textElements&&pictures)risks.push("テキストがなく、全面画像化されている可能性"); if(text.length>700)risks.push("文字量が多く、表示環境差であふれる可能性"); if(text.length>240&&!hasAutoFit)risks.push("長文だが自動フィット設定を確認できない");
    slides.push({page:slideNumber(name),textCharacters:text.length,textElements,editableShapes:shapes,pictures,charts,risks});
  }
  const customFonts=[...fonts].filter((font)=>!systemFonts.has(font)), flattened=slides.filter((slide)=>slide.risks.some((risk)=>risk.includes("全面画像化"))).length, riskySlides=slides.filter((slide)=>slide.risks.length).length;
  const totalEditable=slides.reduce((sum,slide)=>sum+slide.textElements+slide.editableShapes+slide.charts,0); const score=Math.max(0,100-Math.min(40,riskySlides*8)-Math.min(25,customFonts.length*5)-Math.min(35,flattened*15));
  const findings=[
    {level:flattened?"warn":"pass",label:"編集可能性",detail:flattened?`${flattened}ページで全面画像化の可能性`:`${totalEditable}個の編集可能候補を検出`},
    {level:customFonts.length?"warn":"pass",label:"フォント",detail:customFonts.length?`標準外候補: ${customFonts.join(", ")}`:"標準フォント範囲内"},
    {level:riskySlides?"warn":"pass",label:"レイアウト",detail:riskySlides?`${riskySlides}ページに要確認項目`:`${slides.length}ページで機械的リスクなし`}
  ];
  return {score,verdict:score>=85?"送信前チェック良好":score>=60?"要確認":"修正推奨",slideCount:slides.length,customFonts,slides,findings,privacy:"ファイル内容はブラウザ内で解析し、外部送信しません"};
}