fix: 增强DOCX字体实际使用门禁
This commit is contained in:
@@ -7,6 +7,7 @@ import { aggregatePdfTextLines, buildPageContentText, normalizePdfText } from ".
|
||||
import type {
|
||||
CreatePdfSnapshotOptions,
|
||||
PdfDocumentSnapshot,
|
||||
PdfFontSnapshot,
|
||||
PdfPageSnapshot,
|
||||
PdfSnapshotLimits,
|
||||
PdfTextItemSnapshot,
|
||||
@@ -25,6 +26,13 @@ interface PdfJsTextStyle {
|
||||
fontFamily?: string;
|
||||
}
|
||||
|
||||
interface PdfJsResolvedFont {
|
||||
name?: string;
|
||||
fallbackName?: string;
|
||||
isType3Font?: boolean;
|
||||
missingFile?: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_PDF_SNAPSHOT_LIMITS: PdfSnapshotLimits = {
|
||||
maxPdfBytes: 100 * 1024 * 1024,
|
||||
maxPages: 500,
|
||||
@@ -60,6 +68,7 @@ function createTextItemSnapshot(
|
||||
item: PdfJsTextItem,
|
||||
pageHeightPt: number,
|
||||
styles: Readonly<Record<string, PdfJsTextStyle>>,
|
||||
fonts: ReadonlyMap<string, PdfFontSnapshot>,
|
||||
): PdfTextItemSnapshot | undefined {
|
||||
const normalizedText = normalizePdfText(item.str);
|
||||
if (!normalizedText) {
|
||||
@@ -77,7 +86,8 @@ function createTextItemSnapshot(
|
||||
0,
|
||||
);
|
||||
const baselineY = pageHeightPt - baselineFromBottom;
|
||||
const fontFamily = styles[item.fontName]?.fontFamily;
|
||||
const font = fonts.get(item.fontName);
|
||||
const fontFamily = font?.name ?? styles[item.fontName]?.fontFamily;
|
||||
return {
|
||||
text: item.str,
|
||||
normalizedText,
|
||||
@@ -90,10 +100,45 @@ function createTextItemSnapshot(
|
||||
baselineY,
|
||||
fontName: item.fontName,
|
||||
...(fontFamily ? { fontFamily } : {}),
|
||||
...(font?.fallbackFamily
|
||||
? { fallbackFontFamily: font.fallbackFamily }
|
||||
: {}),
|
||||
hasEol: item.hasEOL,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePdfFontName(value: string) {
|
||||
return value.replace(/^[A-Z]{6}\+/u, "");
|
||||
}
|
||||
|
||||
function resolvePageFonts(
|
||||
page: Awaited<ReturnType<Awaited<ReturnType<typeof getDocument>["promise"]>["getPage"]>>,
|
||||
fontNames: readonly string[],
|
||||
) {
|
||||
const fonts = new Map<string, PdfFontSnapshot>();
|
||||
for (const internalName of fontNames) {
|
||||
try {
|
||||
const resolved = page.commonObjs.get(internalName) as PdfJsResolvedFont;
|
||||
const name = resolved.name?.trim();
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
fonts.set(internalName, {
|
||||
internalName,
|
||||
name: normalizePdfFontName(name),
|
||||
...(resolved.fallbackName
|
||||
? { fallbackFamily: resolved.fallbackName }
|
||||
: {}),
|
||||
isType3Font: resolved.isType3Font === true,
|
||||
missingFile: resolved.missingFile === true,
|
||||
});
|
||||
} catch {
|
||||
// PDF.js 可能不给未实际绘制的字体建立公共对象;保留文本但不伪造字体。
|
||||
}
|
||||
}
|
||||
return fonts;
|
||||
}
|
||||
|
||||
function resolveLimits(
|
||||
input: Partial<PdfSnapshotLimits> | undefined,
|
||||
): PdfSnapshotLimits {
|
||||
@@ -145,6 +190,17 @@ export async function createPdfDocumentSnapshot(
|
||||
disableNormalization: false,
|
||||
includeMarkedContent: false,
|
||||
});
|
||||
await page.getOperatorList();
|
||||
const pageFonts = resolvePageFonts(
|
||||
page,
|
||||
[
|
||||
...new Set(
|
||||
textContent.items.flatMap((item) =>
|
||||
isTextItem(item) ? [item.fontName] : [],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
const styles = textContent.styles as Readonly<
|
||||
Record<string, PdfJsTextStyle>
|
||||
>;
|
||||
@@ -156,6 +212,7 @@ export async function createPdfDocumentSnapshot(
|
||||
item,
|
||||
pointViewport.height,
|
||||
styles,
|
||||
pageFonts,
|
||||
);
|
||||
return snapshot ? [snapshot] : [];
|
||||
});
|
||||
@@ -168,6 +225,9 @@ export async function createPdfDocumentSnapshot(
|
||||
items,
|
||||
lines,
|
||||
contentText: buildPageContentText(lines),
|
||||
fonts: [...pageFonts.values()].sort((left, right) =>
|
||||
left.name.localeCompare(right.name, "en"),
|
||||
),
|
||||
};
|
||||
const pageNumberLine = lines.find((line) => line.role === "page-number");
|
||||
if (pageNumberLine) {
|
||||
@@ -218,12 +278,20 @@ export async function createPdfDocumentSnapshot(
|
||||
.map((page) => page.contentText)
|
||||
.filter(Boolean)
|
||||
.join("\n\f\n");
|
||||
const fonts = [
|
||||
...new Map(
|
||||
pages
|
||||
.flatMap((page) => page.fonts)
|
||||
.map((font) => [font.name.toLocaleLowerCase("en-US"), font]),
|
||||
).values(),
|
||||
].sort((left, right) => left.name.localeCompare(right.name, "en"));
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
source: options.source,
|
||||
sha256: sha256(pdfBytes),
|
||||
pageCount: pages.length,
|
||||
contentText,
|
||||
fonts,
|
||||
pages,
|
||||
};
|
||||
} finally {
|
||||
|
||||
@@ -20,9 +20,18 @@ export interface PdfTextItemSnapshot {
|
||||
baselineY: number;
|
||||
fontName?: string;
|
||||
fontFamily?: string;
|
||||
fallbackFontFamily?: string;
|
||||
hasEol: boolean;
|
||||
}
|
||||
|
||||
export interface PdfFontSnapshot {
|
||||
internalName: string;
|
||||
name: string;
|
||||
fallbackFamily?: string;
|
||||
isType3Font: boolean;
|
||||
missingFile: boolean;
|
||||
}
|
||||
|
||||
export type PdfTextLineRole = "content" | "page-number";
|
||||
|
||||
export interface PdfTextLineSnapshot {
|
||||
@@ -132,6 +141,7 @@ export interface PdfPageSnapshot {
|
||||
items: PdfTextItemSnapshot[];
|
||||
lines: PdfTextLineSnapshot[];
|
||||
contentText: string;
|
||||
fonts: PdfFontSnapshot[];
|
||||
pageNumberText?: string;
|
||||
raster?: PdfPageRaster;
|
||||
}
|
||||
@@ -142,6 +152,7 @@ export interface PdfDocumentSnapshot {
|
||||
sha256: string;
|
||||
pageCount: number;
|
||||
contentText: string;
|
||||
fonts: PdfFontSnapshot[];
|
||||
pages: PdfPageSnapshot[];
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,18 @@ describe("PDF.js 页面快照", () => {
|
||||
expect(first.pages[0]?.widthPt).toBeCloseTo(595, 3);
|
||||
expect(first.pages[0]?.heightPt).toBeCloseTo(842, 3);
|
||||
expect(first.contentText).toContain("Visual diff fixture");
|
||||
expect(first.fonts).toEqual([
|
||||
expect.objectContaining({
|
||||
name: "Helvetica",
|
||||
fallbackFamily: "sans-serif",
|
||||
isType3Font: false,
|
||||
missingFile: true,
|
||||
}),
|
||||
]);
|
||||
expect(first.pages[0]?.items[0]).toMatchObject({
|
||||
fontFamily: "Helvetica",
|
||||
fallbackFontFamily: "sans-serif",
|
||||
});
|
||||
expect(first.pages[0]?.raster).toMatchObject({
|
||||
widthPx: 1190,
|
||||
heightPx: 1684,
|
||||
|
||||
Reference in New Issue
Block a user