fix pagebox

This commit is contained in:
Anthony Stirling 2025-11-14 11:24:45 +00:00
parent a97f5e5257
commit 458cb7fab4
4 changed files with 200 additions and 129 deletions

View File

@ -415,9 +415,16 @@ public class PdfJsonConversionService {
for (PDPage page : document.getPages()) {
PdfJsonPageDimension dim = new PdfJsonPageDimension();
dim.setPageNumber(pageIndex + 1);
PDRectangle mediaBox = page.getMediaBox();
dim.setWidth(mediaBox.getWidth());
dim.setHeight(mediaBox.getHeight());
// Use CropBox if present (defines visible page area), otherwise fall back
// to MediaBox
PDRectangle pageBox = page.getCropBox();
if (pageBox == null
|| pageBox.getWidth() == 0
|| pageBox.getHeight() == 0) {
pageBox = page.getMediaBox();
}
dim.setWidth(pageBox.getWidth());
dim.setHeight(pageBox.getHeight());
dim.setRotation(page.getRotation());
pageDimensions.add(dim);
pageIndex++;
@ -1851,9 +1858,13 @@ public class PdfJsonConversionService {
for (PDPage page : document.getPages()) {
PdfJsonPage pageModel = new PdfJsonPage();
pageModel.setPageNumber(pageIndex + 1);
PDRectangle mediaBox = page.getMediaBox();
pageModel.setWidth(mediaBox.getWidth());
pageModel.setHeight(mediaBox.getHeight());
// Use CropBox if present (defines visible page area), otherwise fall back to MediaBox
PDRectangle pageBox = page.getCropBox();
if (pageBox == null || pageBox.getWidth() == 0 || pageBox.getHeight() == 0) {
pageBox = page.getMediaBox();
}
pageModel.setWidth(pageBox.getWidth());
pageModel.setHeight(pageBox.getHeight());
pageModel.setRotation(page.getRotation());
pageModel.setTextElements(textByPage.getOrDefault(pageIndex + 1, new ArrayList<>()));
pageModel.setImageElements(imagesByPage.getOrDefault(pageIndex + 1, new ArrayList<>()));

View File

@ -173,10 +173,6 @@ const FontStatusPanel: React.FC<FontStatusPanelProps> = ({ document, pageIndex }
[document, pageIndex]
);
if (!document || fontAnalysis.fonts.length === 0) {
return null;
}
const { canReproducePerfectly, hasWarnings, summary, fonts } = fontAnalysis;
const statusIcon = useMemo(() => {
@ -189,6 +185,11 @@ const FontStatusPanel: React.FC<FontStatusPanelProps> = ({ document, pageIndex }
return <InfoIcon sx={{ fontSize: 16 }} />;
}, [canReproducePerfectly, hasWarnings]);
// Early return AFTER all hooks are declared
if (!document || fontAnalysis.fonts.length === 0) {
return null;
}
const statusColor = canReproducePerfectly ? 'green' : hasWarnings ? 'yellow' : 'blue';
const pageLabel = pageIndex !== undefined

View File

@ -379,6 +379,30 @@ const PdfTextEditorView = ({ data }: PdfTextEditorViewProps) => {
onUngroupGroup,
} = data;
// Define derived variables immediately after props destructuring, before any hooks
const pages = pdfDocument?.pages ?? [];
const currentPage = pages[selectedPage] ?? null;
const pageGroups = groupsByPage[selectedPage] ?? [];
const pageImages = imagesByPage[selectedPage] ?? [];
const pagePreview = pagePreviews.get(selectedPage);
const { width: pageWidth, height: pageHeight } = pageDimensions(currentPage);
// Debug logging for page dimensions
console.log(`📐 [PdfTextEditor] Page ${selectedPage + 1} Dimensions:`, {
pageWidth,
pageHeight,
aspectRatio: pageHeight > 0 ? (pageWidth / pageHeight).toFixed(3) : 'N/A',
currentPage: currentPage ? {
mediaBox: currentPage.mediaBox,
cropBox: currentPage.cropBox,
rotation: currentPage.rotation,
} : null,
documentMetadata: pdfDocument?.metadata ? {
title: pdfDocument.metadata.title,
pageCount: pages.length,
} : null,
});
const handleModeChangeRequest = useCallback((newMode: GroupingMode) => {
if (hasChanges && newMode !== externalGroupingMode) {
// Show confirmation dialog
@ -409,7 +433,7 @@ const PdfTextEditorView = ({ data }: PdfTextEditorViewProps) => {
widthOverridesRef.current = widthOverrides;
}, [widthOverrides]);
const resolveFont = (fontId: string | null | undefined, pageIndex: number | null | undefined): PdfJsonFont | null => {
const resolveFont = useCallback((fontId: string | null | undefined, pageIndex: number | null | undefined): PdfJsonFont | null => {
if (!fontId || !pdfDocument?.fonts) {
return null;
}
@ -431,9 +455,9 @@ const PdfTextEditorView = ({ data }: PdfTextEditorViewProps) => {
return directUid;
}
return fonts.find((font) => font?.id === fontId) ?? null;
};
}, [pdfDocument?.fonts]);
const getFontFamily = (fontId: string | null | undefined, pageIndex: number | null | undefined): string => {
const getFontFamily = useCallback((fontId: string | null | undefined, pageIndex: number | null | undefined): string => {
if (!fontId) {
return 'sans-serif';
}
@ -464,116 +488,7 @@ const PdfTextEditorView = ({ data }: PdfTextEditorViewProps) => {
}
return 'Arial, Helvetica, sans-serif';
};
const getFontMetricsFor = (
fontId: string | null | undefined,
pageIndex: number | null | undefined,
): { unitsPerEm: number; ascent: number; descent: number } | undefined => {
if (!fontId) {
return undefined;
}
const font = resolveFont(fontId, pageIndex);
const lookupKeys = buildFontLookupKeys(fontId, font ?? undefined, pageIndex);
for (const key of lookupKeys) {
const metrics = fontMetrics.get(key);
if (metrics) {
return metrics;
}
}
return undefined;
};
const getLineHeightPx = (
fontId: string | null | undefined,
pageIndex: number | null | undefined,
fontSizePx: number,
): number => {
if (fontSizePx <= 0) {
return fontSizePx;
}
const metrics = getFontMetricsFor(fontId, pageIndex);
if (!metrics || metrics.unitsPerEm <= 0) {
return fontSizePx * 1.2;
}
const unitsPerEm = metrics.unitsPerEm > 0 ? metrics.unitsPerEm : 1000;
const ascentUnits = metrics.ascent ?? unitsPerEm;
const descentUnits = Math.abs(metrics.descent ?? -(unitsPerEm * 0.2));
const totalUnits = Math.max(unitsPerEm, ascentUnits + descentUnits);
if (totalUnits <= 0) {
return fontSizePx * 1.2;
}
const lineHeight = (totalUnits / unitsPerEm) * fontSizePx;
return Math.max(lineHeight, fontSizePx * 1.05);
};
const getFontGeometry = (
fontId: string | null | undefined,
pageIndex: number | null | undefined,
): {
unitsPerEm: number;
ascentUnits: number;
descentUnits: number;
totalUnits: number;
ascentRatio: number;
descentRatio: number;
} | undefined => {
const metrics = getFontMetricsFor(fontId, pageIndex);
if (!metrics) {
return undefined;
}
const unitsPerEm = metrics.unitsPerEm > 0 ? metrics.unitsPerEm : 1000;
const rawAscent = metrics.ascent ?? unitsPerEm;
const rawDescent = metrics.descent ?? -(unitsPerEm * 0.2);
const ascentUnits = Number.isFinite(rawAscent) ? rawAscent : unitsPerEm;
const descentUnits = Number.isFinite(rawDescent) ? Math.abs(rawDescent) : unitsPerEm * 0.2;
const totalUnits = Math.max(unitsPerEm, ascentUnits + descentUnits);
if (totalUnits <= 0 || !Number.isFinite(totalUnits)) {
return undefined;
}
return {
unitsPerEm,
ascentUnits,
descentUnits,
totalUnits,
ascentRatio: ascentUnits / totalUnits,
descentRatio: descentUnits / totalUnits,
};
};
const getFontWeight = (
fontId: string | null | undefined,
pageIndex: number | null | undefined,
): number | 'normal' | 'bold' => {
if (!fontId) {
return 'normal';
}
const font = resolveFont(fontId, pageIndex);
if (!font || !font.fontDescriptorFlags) {
return 'normal';
}
// PDF font descriptor flag bit 18 (value 262144 = 0x40000) indicates ForceBold
const FORCE_BOLD_FLAG = 262144;
if ((font.fontDescriptorFlags & FORCE_BOLD_FLAG) !== 0) {
return 'bold';
}
// Also check if font name contains "Bold"
const fontName = font.standard14Name || font.baseName || '';
if (fontName.toLowerCase().includes('bold')) {
return 'bold';
}
return 'normal';
};
const pages = pdfDocument?.pages ?? [];
const currentPage = pages[selectedPage] ?? null;
const pageGroups = groupsByPage[selectedPage] ?? [];
const pageImages = imagesByPage[selectedPage] ?? [];
const pagePreview = pagePreviews.get(selectedPage);
const { width: pageWidth, height: pageHeight } = pageDimensions(currentPage);
}, [resolveFont, fontFamilies]);
useEffect(() => {
clearSelection();
@ -929,6 +844,110 @@ const PdfTextEditorView = ({ data }: PdfTextEditorViewProps) => {
});
};
}, [pdfDocument?.fonts]);
// Define helper functions that depend on hooks AFTER all hook calls
const getFontMetricsFor = useCallback((
fontId: string | null | undefined,
pageIndex: number | null | undefined,
): { unitsPerEm: number; ascent: number; descent: number } | undefined => {
if (!fontId) {
return undefined;
}
const font = resolveFont(fontId, pageIndex);
const lookupKeys = buildFontLookupKeys(fontId, font ?? undefined, pageIndex);
for (const key of lookupKeys) {
const metrics = fontMetrics.get(key);
if (metrics) {
return metrics;
}
}
return undefined;
}, [resolveFont, fontMetrics]);
const getLineHeightPx = useCallback((
fontId: string | null | undefined,
pageIndex: number | null | undefined,
fontSizePx: number,
): number => {
if (fontSizePx <= 0) {
return fontSizePx;
}
const metrics = getFontMetricsFor(fontId, pageIndex);
if (!metrics || metrics.unitsPerEm <= 0) {
return fontSizePx * 1.2;
}
const unitsPerEm = metrics.unitsPerEm > 0 ? metrics.unitsPerEm : 1000;
const ascentUnits = metrics.ascent ?? unitsPerEm;
const descentUnits = Math.abs(metrics.descent ?? -(unitsPerEm * 0.2));
const totalUnits = Math.max(unitsPerEm, ascentUnits + descentUnits);
if (totalUnits <= 0) {
return fontSizePx * 1.2;
}
const lineHeight = (totalUnits / unitsPerEm) * fontSizePx;
return Math.max(lineHeight, fontSizePx * 1.05);
}, [getFontMetricsFor]);
const getFontGeometry = useCallback((
fontId: string | null | undefined,
pageIndex: number | null | undefined,
): {
unitsPerEm: number;
ascentUnits: number;
descentUnits: number;
totalUnits: number;
ascentRatio: number;
descentRatio: number;
} | undefined => {
const metrics = getFontMetricsFor(fontId, pageIndex);
if (!metrics) {
return undefined;
}
const unitsPerEm = metrics.unitsPerEm > 0 ? metrics.unitsPerEm : 1000;
const rawAscent = metrics.ascent ?? unitsPerEm;
const rawDescent = metrics.descent ?? -(unitsPerEm * 0.2);
const ascentUnits = Number.isFinite(rawAscent) ? rawAscent : unitsPerEm;
const descentUnits = Number.isFinite(rawDescent) ? Math.abs(rawDescent) : unitsPerEm * 0.2;
const totalUnits = Math.max(unitsPerEm, ascentUnits + descentUnits);
if (totalUnits <= 0 || !Number.isFinite(totalUnits)) {
return undefined;
}
return {
unitsPerEm,
ascentUnits,
descentUnits,
totalUnits,
ascentRatio: ascentUnits / totalUnits,
descentRatio: descentUnits / totalUnits,
};
}, [getFontMetricsFor]);
const getFontWeight = useCallback((
fontId: string | null | undefined,
pageIndex: number | null | undefined,
): number | 'normal' | 'bold' => {
if (!fontId) {
return 'normal';
}
const font = resolveFont(fontId, pageIndex);
if (!font || !font.fontDescriptorFlags) {
return 'normal';
}
// PDF font descriptor flag bit 18 (value 262144 = 0x40000) indicates ForceBold
const FORCE_BOLD_FLAG = 262144;
if ((font.fontDescriptorFlags & FORCE_BOLD_FLAG) !== 0) {
return 'bold';
}
// Also check if font name contains "Bold"
const fontName = font.standard14Name || font.baseName || '';
if (fontName.toLowerCase().includes('bold')) {
return 'bold';
}
return 'normal';
}, [resolveFont]);
const visibleGroups = useMemo(
() =>
pageGroups
@ -949,7 +968,18 @@ const orderedImages = useMemo(
),
[pageImages],
);
const scale = useMemo(() => Math.min(MAX_RENDER_WIDTH / pageWidth, 2.5), [pageWidth]);
const scale = useMemo(() => {
const calculatedScale = Math.min(MAX_RENDER_WIDTH / pageWidth, 2.5);
console.log(`🔍 [PdfTextEditor] Scale Calculation:`, {
MAX_RENDER_WIDTH,
pageWidth,
pageHeight,
calculatedScale: calculatedScale.toFixed(3),
scaledWidth: (pageWidth * calculatedScale).toFixed(2),
scaledHeight: (pageHeight * calculatedScale).toFixed(2),
});
return calculatedScale;
}, [pageWidth, pageHeight]);
const scaledWidth = pageWidth * scale;
const scaledHeight = pageHeight * scale;
const selectionToolbarPosition = useMemo(() => {
@ -1710,7 +1740,18 @@ const selectionToolbarPosition = useMemo(() => {
borderRadius: '0.5rem',
overflow: 'hidden',
}}
ref={containerRef}
ref={(node) => {
containerRef.current = node;
if (node) {
console.log(`🖼️ [PdfTextEditor] Canvas Rendered:`, {
renderedWidth: node.offsetWidth,
renderedHeight: node.offsetHeight,
styleWidth: scaledWidth,
styleHeight: scaledHeight,
pageNumber: selectedPage + 1,
});
}
}}
>
{pagePreview && (
<img

View File

@ -984,10 +984,28 @@ export const deepCloneDocument = (document: PdfJsonDocument): PdfJsonDocument =>
};
export const pageDimensions = (page: PdfJsonPage | null | undefined): { width: number; height: number } => {
return {
width: valueOr(page?.width, DEFAULT_PAGE_WIDTH),
height: valueOr(page?.height, DEFAULT_PAGE_HEIGHT),
};
const width = valueOr(page?.width, DEFAULT_PAGE_WIDTH);
const height = valueOr(page?.height, DEFAULT_PAGE_HEIGHT);
console.log(`📏 [pageDimensions] Calculating page size:`, {
hasPage: !!page,
rawWidth: page?.width,
rawHeight: page?.height,
mediaBox: page?.mediaBox,
cropBox: page?.cropBox,
rotation: page?.rotation,
calculatedWidth: width,
calculatedHeight: height,
DEFAULT_PAGE_WIDTH,
DEFAULT_PAGE_HEIGHT,
commonFormats: {
'US Letter': '612 × 792 pt',
'A4': '595 × 842 pt',
'Legal': '612 × 1008 pt',
},
});
return { width, height };
};
export const createMergedElement = (group: TextGroup): PdfJsonTextElement => {