← GPT-AI-BANNER
CODE · 3.5 KB

src/document-reader.js

Workspace snapshot · 09/04 13:35

import * as pdfjs from "pdfjs-dist/legacy/build/pdf.mjs";
import pdfWorker from "pdfjs-dist/legacy/build/pdf.worker.min.mjs?url";
import mammoth from "mammoth/mammoth.browser";

pdfjs.GlobalWorkerOptions.workerSrc = pdfWorker;

function compact(text, maxLength = 12000) {
  return String(text || "").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/gu, " ").replace(/\s+/gu, " ").trim().slice(0, maxLength);
}

function rgbToHex(red, green, blue) {
  return `#${[red, green, blue].map((value) => value.toString(16).padStart(2, "0")).join("")}`;
}

async function extractPagePalette(page) {
  if (typeof document === "undefined") return [];
  const viewport = page.getViewport({ scale: 0.35 });
  const canvas = document.createElement("canvas");
  canvas.width = Math.max(1, Math.round(viewport.width));
  canvas.height = Math.max(1, Math.round(viewport.height));
  const context = canvas.getContext("2d", { willReadFrequently: true });
  if (!context) return [];
  await page.render({ canvasContext: context, viewport }).promise;
  const pixels = context.getImageData(0, 0, canvas.width, canvas.height).data;
  const buckets = new Map();
  for (let index = 0; index < pixels.length; index += 64) {
    const alpha = pixels[index + 3];
    if (alpha < 180) continue;
    const red = pixels[index]; const green = pixels[index + 1]; const blue = pixels[index + 2];
    if (red > 242 && green > 242 && blue > 242) continue;
    if (Math.max(red, green, blue) - Math.min(red, green, blue) < 8 && red > 205) continue;
    const quantized = [red, green, blue].map((value) => Math.round(value / 32) * 32).map((value) => Math.min(255, value));
    const key = quantized.join(",");
    buckets.set(key, (buckets.get(key) || 0) + 1);
  }
  return [...buckets.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([key]) => rgbToHex(...key.split(",").map(Number)));
}

async function readPdf(file) {
  const pdf = await pdfjs.getDocument({ data: new Uint8Array(await file.arrayBuffer()) }).promise;
  const pages = [];
  let palette = [];
  for (let pageNumber = 1; pageNumber <= Math.min(pdf.numPages, 40); pageNumber += 1) {
    const page = await pdf.getPage(pageNumber);
    if (pageNumber === 1) palette = await extractPagePalette(page);
    const content = await page.getTextContent();
    pages.push(content.items.map((item) => item.str).join(" "));
  }
  return { text: compact(pages.join("\n")), palette };
}

async function readDocx(file) {
  const result = await mammoth.extractRawText({ arrayBuffer: await file.arrayBuffer() });
  return compact(result.value);
}

export async function readServiceDocument(file) {
  if (!file) return { text: "", palette: [], status: "" };
  if (file.size > 15 * 1024 * 1024) throw new Error("資料は15MB以下にしてください");
  const extension = file.name.split(".").pop()?.toLowerCase();
  let text = ""; let palette = [];
  if (["txt", "md", "csv"].includes(extension)) text = compact(await file.text());
  else if (extension === "pdf") ({ text, palette } = await readPdf(file));
  else if (extension === "docx") text = await readDocx(file);
  else throw new Error("PDF・DOCX・TXT・MDに対応しています");
  if (!text) throw new Error("資料から文章を抽出できませんでした。文字を選択できる資料を使ってください");
  const paletteStatus = palette.length ? `・基調色${palette.length}色を検出` : "";
  return { text, palette, status: `${extension.toUpperCase()}から${text.length.toLocaleString("ja-JP")}文字を読み込みました${paletteStatus}` };
}