PDF Text editor (#4724)

## Summary
- add a `PdfJsonConversionService` that serializes PDF text, fonts, and
metadata to JSON and rebuilds a PDF from the same structure
- expose REST endpoints for `/pdf/json` and `/json/pdf` conversions
using the existing convert API infrastructure
- define JSON model classes capturing document metadata, font
information, and positioned text elements

## Testing
- `./gradlew spotlessApply` *(fails: plugin
org.springframework.boot:3.5.4 unavailable in build environment)*
- `./gradlew build` *(fails: plugin org.springframework.boot:3.5.4
unavailable in build environment)*

------
https://chatgpt.com/codex/tasks/task_b_68f8e98d94ac8328a0e499e541528b6f

---------

Co-authored-by: EthanHealy01 <[email protected]>
This commit is contained in:
Anthony Stirling
2025-11-24 14:15:02 +00:00
committed by GitHub
co-authored by EthanHealy01
parent d42065e338
commit b0397da19e
253 changed files with 26069 additions and 111 deletions
@@ -121,10 +121,11 @@ export const NavigationProvider: React.FC<{
hasUnsavedChanges
});
// If we're leaving pageEditor or viewer workbench and have unsaved changes, request navigation
// If we're leaving pageEditor, viewer, or custom workbench and have unsaved changes, request navigation
const leavingWorkbenchWithChanges =
(state.workbench === 'pageEditor' && workbench !== 'pageEditor' && hasUnsavedChanges) ||
(state.workbench === 'viewer' && workbench !== 'viewer' && hasUnsavedChanges);
(state.workbench === 'viewer' && workbench !== 'viewer' && hasUnsavedChanges) ||
(state.workbench.startsWith('custom:') && workbench !== state.workbench && hasUnsavedChanges);
if (leavingWorkbenchWithChanges) {
// Update state to reflect unsaved changes so modal knows
@@ -132,7 +133,19 @@ export const NavigationProvider: React.FC<{
dispatch({ type: 'SET_UNSAVED_CHANGES', payload: { hasChanges: true } });
}
const performWorkbenchChange = () => {
dispatch({ type: 'SET_WORKBENCH', payload: { workbench } });
// When leaving a custom workbench, clear the selected tool
console.log('[NavigationContext] performWorkbenchChange executing', {
from: state.workbench,
to: workbench,
isCustom: state.workbench.startsWith('custom:')
});
if (state.workbench.startsWith('custom:')) {
console.log('[NavigationContext] Clearing tool and changing workbench to:', workbench);
dispatch({ type: 'SET_TOOL_AND_WORKBENCH', payload: { toolId: null, workbench } });
} else {
console.log('[NavigationContext] Just changing workbench to:', workbench);
dispatch({ type: 'SET_WORKBENCH', payload: { workbench } });
}
};
dispatch({ type: 'SET_PENDING_NAVIGATION', payload: { navigationFn: performWorkbenchChange } });
dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: { show: true } });
@@ -149,10 +162,11 @@ export const NavigationProvider: React.FC<{
// Check for unsaved changes using registered checker or state
const hasUnsavedChanges = unsavedChangesCheckerRef.current?.() || state.hasUnsavedChanges;
// If we're leaving pageEditor or viewer workbench and have unsaved changes, request navigation
// If we're leaving pageEditor, viewer, or custom workbench and have unsaved changes, request navigation
const leavingWorkbenchWithChanges =
(state.workbench === 'pageEditor' && workbench !== 'pageEditor' && hasUnsavedChanges) ||
(state.workbench === 'viewer' && workbench !== 'viewer' && hasUnsavedChanges);
(state.workbench === 'viewer' && workbench !== 'viewer' && hasUnsavedChanges) ||
(state.workbench.startsWith('custom:') && workbench !== state.workbench && hasUnsavedChanges);
if (leavingWorkbenchWithChanges) {
const performWorkbenchChange = () => {
@@ -192,13 +206,19 @@ export const NavigationProvider: React.FC<{
}, [state.hasUnsavedChanges]),
confirmNavigation: useCallback(() => {
console.log('[NavigationContext] confirmNavigation called', {
hasPendingNav: !!state.pendingNavigation,
currentWorkbench: state.workbench,
currentTool: state.selectedTool
});
if (state.pendingNavigation) {
state.pendingNavigation();
}
dispatch({ type: 'SET_PENDING_NAVIGATION', payload: { navigationFn: null } });
dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: { show: false } });
}, [state.pendingNavigation]),
console.log('[NavigationContext] confirmNavigation completed');
}, [state.pendingNavigation, state.workbench, state.selectedTool]),
cancelNavigation: useCallback(() => {
dispatch({ type: 'SET_PENDING_NAVIGATION', payload: { navigationFn: null } });