mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-15 11:00:47 +02:00
opensource text editor (#5146)
# Description of Changes <!-- Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered Closes #(issue_number) --> --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,421 @@
|
||||
import { PdfJsonDocument, PdfJsonFont } from '@app/tools/pdfTextEditor/pdfTextEditorTypes';
|
||||
|
||||
export type FontStatus = 'perfect' | 'embedded-subset' | 'system-fallback' | 'missing' | 'unknown';
|
||||
|
||||
export interface FontAnalysis {
|
||||
fontId: string;
|
||||
baseName: string;
|
||||
status: FontStatus;
|
||||
embedded: boolean;
|
||||
isSubset: boolean;
|
||||
isStandard14: boolean;
|
||||
hasWebFormat: boolean;
|
||||
webFormat?: string;
|
||||
subtype?: string;
|
||||
encoding?: string;
|
||||
warnings: string[];
|
||||
suggestions: string[];
|
||||
}
|
||||
|
||||
export interface DocumentFontAnalysis {
|
||||
fonts: FontAnalysis[];
|
||||
canReproducePerfectly: boolean;
|
||||
hasWarnings: boolean;
|
||||
summary: {
|
||||
perfect: number;
|
||||
embeddedSubset: number;
|
||||
systemFallback: number;
|
||||
missing: number;
|
||||
unknown: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a font name indicates it's a subset font.
|
||||
* Subset fonts typically have a 6-character prefix like "ABCDEE+"
|
||||
*/
|
||||
const isSubsetFont = (baseName: string | null | undefined): boolean => {
|
||||
if (!baseName) return false;
|
||||
// Check for common subset patterns: ABCDEF+FontName
|
||||
return /^[A-Z]{6}\+/.test(baseName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a font is one of the standard 14 PDF fonts that are guaranteed
|
||||
* to be available on all PDF readers
|
||||
*/
|
||||
const isStandard14Font = (font: PdfJsonFont): boolean => {
|
||||
if (font.standard14Name) return true;
|
||||
|
||||
const baseName = (font.baseName || '').toLowerCase().replace(/[-_\s]/g, '');
|
||||
|
||||
const standard14Patterns = [
|
||||
'timesroman', 'timesbold', 'timesitalic', 'timesbolditalic',
|
||||
'helvetica', 'helveticabold', 'helveticaoblique', 'helveticaboldoblique',
|
||||
'courier', 'courierbold', 'courieroblique', 'courierboldoblique',
|
||||
'symbol', 'zapfdingbats'
|
||||
];
|
||||
|
||||
// Check exact matches or if the base name contains the pattern
|
||||
return standard14Patterns.some(pattern => {
|
||||
// Exact match
|
||||
if (baseName === pattern) return true;
|
||||
// Contains pattern (e.g., "ABCDEF+Helvetica" matches "helvetica")
|
||||
if (baseName.includes(pattern)) return true;
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a font has a fallback available on the backend.
|
||||
* These fonts are embedded in the Stirling PDF backend and can be used
|
||||
* for PDF export even if not in the original PDF.
|
||||
*
|
||||
* Based on PdfJsonFallbackFontService.java
|
||||
*/
|
||||
const hasBackendFallbackFont = (font: PdfJsonFont): boolean => {
|
||||
const baseName = (font.baseName || '').toLowerCase().replace(/[-_\s]/g, '');
|
||||
|
||||
// Backend has these font families available (from PdfJsonFallbackFontService)
|
||||
const backendFonts = [
|
||||
// Liberation fonts (metric-compatible with MS core fonts)
|
||||
'arial', 'helvetica', 'arimo',
|
||||
'times', 'timesnewroman', 'tinos',
|
||||
'courier', 'couriernew', 'cousine',
|
||||
'liberation', 'liberationsans', 'liberationserif', 'liberationmono',
|
||||
// DejaVu fonts
|
||||
'dejavu', 'dejavusans', 'dejavuserif', 'dejavumono', 'dejavusansmono',
|
||||
// Noto fonts
|
||||
'noto', 'notosans'
|
||||
];
|
||||
|
||||
return backendFonts.some(pattern => {
|
||||
if (baseName === pattern) return true;
|
||||
if (baseName.includes(pattern)) return true;
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts the base font name from a subset font name
|
||||
* e.g., "ABCDEF+Arial" -> "Arial"
|
||||
*/
|
||||
const extractBaseFontName = (baseName: string | null | undefined): string | null => {
|
||||
if (!baseName) return null;
|
||||
const match = baseName.match(/^[A-Z]{6}\+(.+)$/);
|
||||
return match ? match[1] : baseName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Analyzes a single font to determine if it can be reproduced perfectly
|
||||
* Takes allFonts to check if full versions of subset fonts are available
|
||||
*/
|
||||
export const analyzeFontReproduction = (font: PdfJsonFont, allFonts?: PdfJsonFont[]): FontAnalysis => {
|
||||
const fontId = font.id || font.uid || 'unknown';
|
||||
const baseName = font.baseName || 'Unknown Font';
|
||||
const isSubset = isSubsetFont(font.baseName);
|
||||
const isStandard14 = isStandard14Font(font);
|
||||
const hasBackendFallback = hasBackendFallbackFont(font);
|
||||
const embedded = font.embedded ?? false;
|
||||
|
||||
// Check available web formats (ordered by preference)
|
||||
const webFormats = [
|
||||
{ key: 'webProgram', format: font.webProgramFormat },
|
||||
{ key: 'pdfProgram', format: font.pdfProgramFormat },
|
||||
{ key: 'program', format: font.programFormat },
|
||||
];
|
||||
|
||||
const availableWebFormat = webFormats.find(f => f.format);
|
||||
const hasWebFormat = !!availableWebFormat;
|
||||
const webFormat = availableWebFormat?.format || undefined;
|
||||
|
||||
const warnings: string[] = [];
|
||||
const suggestions: string[] = [];
|
||||
let status: FontStatus = 'unknown';
|
||||
|
||||
// Check if we have the full font when this is a subset
|
||||
let hasFullFontVersion = false;
|
||||
if (isSubset && allFonts) {
|
||||
const baseFont = extractBaseFontName(font.baseName);
|
||||
if (baseFont) {
|
||||
// Look for a non-subset version of this font with a web format
|
||||
hasFullFontVersion = allFonts.some(f => {
|
||||
const otherBaseName = extractBaseFontName(f.baseName);
|
||||
const isNotSubset = !isSubsetFont(f.baseName);
|
||||
const hasFormat = !!(f.webProgramFormat || f.pdfProgramFormat || f.programFormat);
|
||||
const sameBase = otherBaseName?.toLowerCase() === baseFont.toLowerCase();
|
||||
return sameBase && isNotSubset && hasFormat && (f.embedded ?? false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Analyze font status - focusing on PDF export quality
|
||||
if (isStandard14) {
|
||||
// Standard 14 fonts are always available in PDF readers - perfect for export!
|
||||
status = 'perfect';
|
||||
suggestions.push('Standard PDF font (Times, Helvetica, or Courier). Always available in PDF readers.');
|
||||
suggestions.push('Exported PDFs will render consistently across all PDF readers.');
|
||||
} else if (embedded && !isSubset) {
|
||||
// Perfect: Fully embedded with complete character set
|
||||
status = 'perfect';
|
||||
suggestions.push('Font is fully embedded. Exported PDFs will reproduce text perfectly, even with edits.');
|
||||
} else if (embedded && isSubset && (hasFullFontVersion || hasBackendFallback)) {
|
||||
// Subset but we have the full font or backend fallback - perfect!
|
||||
status = 'perfect';
|
||||
if (hasFullFontVersion) {
|
||||
suggestions.push('Full font version is also available in the document. Exported PDFs can reproduce all characters.');
|
||||
} else if (hasBackendFallback) {
|
||||
suggestions.push('Backend has the full font available. Exported PDFs can reproduce all characters, including new text.');
|
||||
}
|
||||
} else if (embedded && isSubset) {
|
||||
// Good, but subset: May have missing characters if user adds new text
|
||||
status = 'embedded-subset';
|
||||
warnings.push('This is a subset font - only specific characters are embedded in the PDF.');
|
||||
warnings.push('Exported PDFs may have missing characters if you add new text with this font.');
|
||||
suggestions.push('Existing text will export correctly. New characters may render as boxes (☐) or fallback glyphs.');
|
||||
} else if (!embedded && hasBackendFallback) {
|
||||
// Not embedded, but backend has it - perfect for export!
|
||||
status = 'perfect';
|
||||
suggestions.push('Backend has this font available. Exported PDFs will use the backend fallback font.');
|
||||
suggestions.push('Text will export correctly with consistent appearance.');
|
||||
} else if (!embedded) {
|
||||
// Not embedded - must rely on system fonts (risky for export)
|
||||
status = 'missing';
|
||||
warnings.push('Font is not embedded in the PDF.');
|
||||
warnings.push('Exported PDFs will substitute with a fallback font, which may look very different.');
|
||||
suggestions.push('Consider re-embedding fonts or accepting that the exported PDF will use fallback fonts.');
|
||||
} else if (embedded && !hasWebFormat) {
|
||||
// Embedded but no web format available (still okay for export)
|
||||
status = 'perfect';
|
||||
suggestions.push('Font is embedded in the PDF. Exported PDFs will reproduce correctly.');
|
||||
suggestions.push('Web preview may use a fallback font, but the final PDF export will be accurate.');
|
||||
}
|
||||
|
||||
// Additional warnings based on font properties
|
||||
if (font.subtype === 'Type0' && font.cidSystemInfo) {
|
||||
const registry = font.cidSystemInfo.registry || '';
|
||||
const ordering = font.cidSystemInfo.ordering || '';
|
||||
if (registry.includes('Adobe') && (ordering.includes('Identity') || ordering.includes('UCS'))) {
|
||||
// CID fonts with Identity encoding are common for Asian languages
|
||||
if (!embedded || !hasWebFormat) {
|
||||
warnings.push('This CID font may contain Asian or Unicode characters.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (font.encoding && !font.encoding.includes('WinAnsiEncoding') && !font.encoding.includes('MacRomanEncoding')) {
|
||||
// Custom encodings may cause issues
|
||||
if (font.encoding !== 'Identity-H' && font.encoding !== 'Identity-V') {
|
||||
warnings.push(`Custom encoding detected: ${font.encoding}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
fontId,
|
||||
baseName,
|
||||
status,
|
||||
embedded,
|
||||
isSubset,
|
||||
isStandard14,
|
||||
hasWebFormat,
|
||||
webFormat,
|
||||
subtype: font.subtype || undefined,
|
||||
encoding: font.encoding || undefined,
|
||||
warnings,
|
||||
suggestions,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets fonts used on a specific page
|
||||
*/
|
||||
export const getFontsForPage = (
|
||||
document: PdfJsonDocument | null,
|
||||
pageIndex: number
|
||||
): PdfJsonFont[] => {
|
||||
if (!document?.fonts || !document?.pages || pageIndex < 0 || pageIndex >= document.pages.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const page = document.pages[pageIndex];
|
||||
if (!page?.textElements) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get unique font IDs used on this page
|
||||
const fontIdsOnPage = new Set<string>();
|
||||
page.textElements.forEach(element => {
|
||||
if (element?.fontId) {
|
||||
fontIdsOnPage.add(element.fontId);
|
||||
}
|
||||
});
|
||||
|
||||
// Filter fonts to only those used on this page
|
||||
const allFonts = document.fonts.filter((font): font is PdfJsonFont => font !== null && font !== undefined);
|
||||
|
||||
const fontsOnPage = allFonts.filter(font => {
|
||||
// Match by ID
|
||||
if (font.id && fontIdsOnPage.has(font.id)) {
|
||||
return true;
|
||||
}
|
||||
// Match by UID
|
||||
if (font.uid && fontIdsOnPage.has(font.uid)) {
|
||||
return true;
|
||||
}
|
||||
// Match by page-specific ID (pageNumber:id format)
|
||||
if (font.pageNumber === pageIndex + 1 && font.id) {
|
||||
const pageSpecificId = `${font.pageNumber}:${font.id}`;
|
||||
if (fontIdsOnPage.has(pageSpecificId) || fontIdsOnPage.has(font.id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Deduplicate by base font name to avoid showing the same font multiple times
|
||||
const uniqueFonts = new Map<string, PdfJsonFont>();
|
||||
fontsOnPage.forEach(font => {
|
||||
const baseName = extractBaseFontName(font.baseName) || font.baseName || font.id || 'unknown';
|
||||
const key = baseName.toLowerCase();
|
||||
|
||||
// Keep the first occurrence, or prefer non-subset over subset
|
||||
const existing = uniqueFonts.get(key);
|
||||
if (!existing) {
|
||||
uniqueFonts.set(key, font);
|
||||
} else {
|
||||
// Prefer non-subset fonts over subset fonts
|
||||
const existingIsSubset = isSubsetFont(existing.baseName);
|
||||
const currentIsSubset = isSubsetFont(font.baseName);
|
||||
if (existingIsSubset && !currentIsSubset) {
|
||||
uniqueFonts.set(key, font);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(uniqueFonts.values());
|
||||
};
|
||||
|
||||
/**
|
||||
* Analyzes all fonts in a PDF document (or just fonts for a specific page)
|
||||
*/
|
||||
export const analyzeDocumentFonts = (
|
||||
document: PdfJsonDocument | null,
|
||||
pageIndex?: number
|
||||
): DocumentFontAnalysis => {
|
||||
if (!document?.fonts || document.fonts.length === 0) {
|
||||
return {
|
||||
fonts: [],
|
||||
canReproducePerfectly: true,
|
||||
hasWarnings: false,
|
||||
summary: {
|
||||
perfect: 0,
|
||||
embeddedSubset: 0,
|
||||
systemFallback: 0,
|
||||
missing: 0,
|
||||
unknown: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const allFonts = document.fonts.filter((font): font is PdfJsonFont => font !== null && font !== undefined);
|
||||
|
||||
// Filter to page-specific fonts if pageIndex is provided
|
||||
const fontsToAnalyze = pageIndex !== undefined
|
||||
? getFontsForPage(document, pageIndex)
|
||||
: allFonts;
|
||||
|
||||
if (fontsToAnalyze.length === 0) {
|
||||
return {
|
||||
fonts: [],
|
||||
canReproducePerfectly: true,
|
||||
hasWarnings: false,
|
||||
summary: {
|
||||
perfect: 0,
|
||||
embeddedSubset: 0,
|
||||
systemFallback: 0,
|
||||
missing: 0,
|
||||
unknown: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const fontAnalyses = fontsToAnalyze.map(font => analyzeFontReproduction(font, allFonts));
|
||||
|
||||
// Calculate summary
|
||||
const summary = {
|
||||
perfect: fontAnalyses.filter(f => f.status === 'perfect').length,
|
||||
embeddedSubset: fontAnalyses.filter(f => f.status === 'embedded-subset').length,
|
||||
systemFallback: fontAnalyses.filter(f => f.status === 'system-fallback').length,
|
||||
missing: fontAnalyses.filter(f => f.status === 'missing').length,
|
||||
unknown: fontAnalyses.filter(f => f.status === 'unknown').length,
|
||||
};
|
||||
|
||||
// Can reproduce perfectly ONLY if all fonts are truly perfect (not subsets)
|
||||
const canReproducePerfectly = fontAnalyses.every(f => f.status === 'perfect');
|
||||
|
||||
// Has warnings if any font has issues (including subsets)
|
||||
const hasWarnings = fontAnalyses.some(
|
||||
f => f.warnings.length > 0 || f.status === 'missing' || f.status === 'system-fallback' || f.status === 'embedded-subset'
|
||||
);
|
||||
|
||||
return {
|
||||
fonts: fontAnalyses,
|
||||
canReproducePerfectly,
|
||||
hasWarnings,
|
||||
summary,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a human-readable description of the font status
|
||||
*/
|
||||
export const getFontStatusDescription = (status: FontStatus): string => {
|
||||
switch (status) {
|
||||
case 'perfect':
|
||||
return 'Fully embedded - perfect reproduction';
|
||||
case 'embedded-subset':
|
||||
return 'Embedded (subset) - existing text will render correctly';
|
||||
case 'system-fallback':
|
||||
return 'Using system font - appearance may differ';
|
||||
case 'missing':
|
||||
return 'Not embedded - will use fallback font';
|
||||
case 'unknown':
|
||||
return 'Unknown status';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a color indicator for the font status
|
||||
*/
|
||||
export const getFontStatusColor = (status: FontStatus): string => {
|
||||
switch (status) {
|
||||
case 'perfect':
|
||||
return 'green';
|
||||
case 'embedded-subset':
|
||||
return 'blue';
|
||||
case 'system-fallback':
|
||||
return 'yellow';
|
||||
case 'missing':
|
||||
return 'red';
|
||||
case 'unknown':
|
||||
return 'gray';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets an icon indicator for the font status
|
||||
*/
|
||||
export const getFontStatusIcon = (status: FontStatus): string => {
|
||||
switch (status) {
|
||||
case 'perfect':
|
||||
return '✓';
|
||||
case 'embedded-subset':
|
||||
return '⚠';
|
||||
case 'system-fallback':
|
||||
return '⚠';
|
||||
case 'missing':
|
||||
return '✗';
|
||||
case 'unknown':
|
||||
return '?';
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,228 @@
|
||||
export interface PdfJsonFontCidSystemInfo {
|
||||
registry?: string | null;
|
||||
ordering?: string | null;
|
||||
supplement?: number | null;
|
||||
}
|
||||
|
||||
export interface PdfJsonTextColor {
|
||||
colorSpace?: string | null;
|
||||
components?: number[] | null;
|
||||
}
|
||||
|
||||
export interface PdfJsonCosValue {
|
||||
type?: string | null;
|
||||
value?: unknown;
|
||||
items?: PdfJsonCosValue[] | null;
|
||||
entries?: Record<string, PdfJsonCosValue | null> | null;
|
||||
stream?: PdfJsonStream | null;
|
||||
}
|
||||
|
||||
export interface PdfJsonFont {
|
||||
id?: string;
|
||||
pageNumber?: number | null;
|
||||
uid?: string | null;
|
||||
baseName?: string | null;
|
||||
subtype?: string | null;
|
||||
encoding?: string | null;
|
||||
cidSystemInfo?: PdfJsonFontCidSystemInfo | null;
|
||||
embedded?: boolean | null;
|
||||
program?: string | null;
|
||||
programFormat?: string | null;
|
||||
webProgram?: string | null;
|
||||
webProgramFormat?: string | null;
|
||||
pdfProgram?: string | null;
|
||||
pdfProgramFormat?: string | null;
|
||||
toUnicode?: string | null;
|
||||
standard14Name?: string | null;
|
||||
fontDescriptorFlags?: number | null;
|
||||
ascent?: number | null;
|
||||
descent?: number | null;
|
||||
capHeight?: number | null;
|
||||
xHeight?: number | null;
|
||||
italicAngle?: number | null;
|
||||
unitsPerEm?: number | null;
|
||||
cosDictionary?: PdfJsonCosValue | null;
|
||||
}
|
||||
|
||||
export interface PdfJsonTextElement {
|
||||
text?: string | null;
|
||||
fontId?: string | null;
|
||||
fontSize?: number | null;
|
||||
fontMatrixSize?: number | null;
|
||||
fontSizeInPt?: number | null;
|
||||
characterSpacing?: number | null;
|
||||
wordSpacing?: number | null;
|
||||
spaceWidth?: number | null;
|
||||
zOrder?: number | null;
|
||||
horizontalScaling?: number | null;
|
||||
leading?: number | null;
|
||||
rise?: number | null;
|
||||
renderingMode?: number | null;
|
||||
x?: number | null;
|
||||
y?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
textMatrix?: number[] | null;
|
||||
fillColor?: PdfJsonTextColor | null;
|
||||
strokeColor?: PdfJsonTextColor | null;
|
||||
charCodes?: number[] | null;
|
||||
fallbackUsed?: boolean | null;
|
||||
}
|
||||
|
||||
export interface PdfJsonImageElement {
|
||||
id?: string | null;
|
||||
objectName?: string | null;
|
||||
inlineImage?: boolean | null;
|
||||
nativeWidth?: number | null;
|
||||
nativeHeight?: number | null;
|
||||
x?: number | null;
|
||||
y?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
left?: number | null;
|
||||
right?: number | null;
|
||||
top?: number | null;
|
||||
bottom?: number | null;
|
||||
transform?: number[] | null;
|
||||
zOrder?: number | null;
|
||||
imageData?: string | null;
|
||||
imageFormat?: string | null;
|
||||
}
|
||||
|
||||
export interface PdfJsonStream {
|
||||
dictionary?: Record<string, unknown> | null;
|
||||
rawData?: string | null;
|
||||
}
|
||||
|
||||
export interface PdfJsonPage {
|
||||
pageNumber?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
rotation?: number | null;
|
||||
mediaBox?: number[] | null;
|
||||
cropBox?: number[] | null;
|
||||
textElements?: PdfJsonTextElement[] | null;
|
||||
imageElements?: PdfJsonImageElement[] | null;
|
||||
resources?: unknown;
|
||||
contentStreams?: PdfJsonStream[] | null;
|
||||
}
|
||||
|
||||
export interface PdfJsonMetadata {
|
||||
title?: string | null;
|
||||
author?: string | null;
|
||||
subject?: string | null;
|
||||
keywords?: string | null;
|
||||
creator?: string | null;
|
||||
producer?: string | null;
|
||||
creationDate?: string | null;
|
||||
modificationDate?: string | null;
|
||||
trapped?: string | null;
|
||||
numberOfPages?: number | null;
|
||||
}
|
||||
|
||||
export interface PdfJsonDocument {
|
||||
metadata?: PdfJsonMetadata | null;
|
||||
xmpMetadata?: string | null;
|
||||
fonts?: PdfJsonFont[] | null;
|
||||
pages?: PdfJsonPage[] | null;
|
||||
lazyImages?: boolean | null;
|
||||
}
|
||||
|
||||
export interface PdfJsonPageDimension {
|
||||
pageNumber?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
rotation?: number | null;
|
||||
}
|
||||
|
||||
export interface PdfJsonDocumentMetadata {
|
||||
metadata?: PdfJsonMetadata | null;
|
||||
xmpMetadata?: string | null;
|
||||
fonts?: PdfJsonFont[] | null;
|
||||
pageDimensions?: PdfJsonPageDimension[] | null;
|
||||
formFields?: unknown[] | null;
|
||||
lazyImages?: boolean | null;
|
||||
}
|
||||
|
||||
export interface BoundingBox {
|
||||
left: number;
|
||||
right: number;
|
||||
top: number;
|
||||
bottom: number;
|
||||
}
|
||||
|
||||
export interface TextGroup {
|
||||
id: string;
|
||||
pageIndex: number;
|
||||
fontId?: string | null;
|
||||
fontSize?: number | null;
|
||||
fontMatrixSize?: number | null;
|
||||
lineSpacing?: number | null;
|
||||
lineElementCounts?: number[] | null;
|
||||
color?: string | null;
|
||||
fontWeight?: number | 'normal' | 'bold' | null;
|
||||
rotation?: number | null;
|
||||
anchor?: { x: number; y: number } | null;
|
||||
baselineLength?: number | null;
|
||||
baseline?: number | null;
|
||||
elements: PdfJsonTextElement[];
|
||||
originalElements: PdfJsonTextElement[];
|
||||
text: string;
|
||||
originalText: string;
|
||||
bounds: BoundingBox;
|
||||
childLineGroups?: TextGroup[] | null;
|
||||
}
|
||||
|
||||
export const DEFAULT_PAGE_WIDTH = 612;
|
||||
export const DEFAULT_PAGE_HEIGHT = 792;
|
||||
|
||||
export interface ConversionProgress {
|
||||
percent: number;
|
||||
stage: string;
|
||||
message: string;
|
||||
current?: number;
|
||||
total?: number;
|
||||
}
|
||||
|
||||
export interface PdfTextEditorViewData {
|
||||
document: PdfJsonDocument | null;
|
||||
groupsByPage: TextGroup[][];
|
||||
imagesByPage: PdfJsonImageElement[][];
|
||||
pagePreviews: Map<number, string>;
|
||||
selectedPage: number;
|
||||
dirtyPages: boolean[];
|
||||
hasDocument: boolean;
|
||||
hasVectorPreview: boolean;
|
||||
fileName: string;
|
||||
errorMessage: string | null;
|
||||
isGeneratingPdf: boolean;
|
||||
isConverting: boolean;
|
||||
conversionProgress: ConversionProgress | null;
|
||||
hasChanges: boolean;
|
||||
forceSingleTextElement: boolean;
|
||||
groupingMode: 'auto' | 'paragraph' | 'singleLine';
|
||||
requestPagePreview: (pageIndex: number, scale: number) => void;
|
||||
onSelectPage: (pageIndex: number) => void;
|
||||
onGroupEdit: (pageIndex: number, groupId: string, value: string) => void;
|
||||
onGroupDelete: (pageIndex: number, groupId: string) => void;
|
||||
onImageTransform: (
|
||||
pageIndex: number,
|
||||
imageId: string,
|
||||
next: {
|
||||
left: number;
|
||||
bottom: number;
|
||||
width: number;
|
||||
height: number;
|
||||
transform: number[];
|
||||
},
|
||||
) => void;
|
||||
onImageReset: (pageIndex: number, imageId: string) => void;
|
||||
onReset: () => void;
|
||||
onDownloadJson: () => void;
|
||||
onGeneratePdf: () => void;
|
||||
onGeneratePdfForNavigation: () => Promise<void>;
|
||||
onForceSingleTextElementChange: (value: boolean) => void;
|
||||
onGroupingModeChange: (value: 'auto' | 'paragraph' | 'singleLine') => void;
|
||||
onMergeGroups: (pageIndex: number, groupIds: string[]) => boolean;
|
||||
onUngroupGroup: (pageIndex: number, groupId: string) => boolean;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user