rename tool

This commit is contained in:
Anthony Stirling 2025-11-12 10:16:36 +00:00
parent 246d7a7b16
commit aad1b06324
11 changed files with 109 additions and 109 deletions

View File

@ -1,4 +1,4 @@
# PDF JSON Editor Backlog
# PDF Text Editor Backlog
- **Type3 Font Support (Text Additions)**
- Parse Type3 charprocs to extract glyph outlines, build a synthetic TrueType/OpenType font (FontTools, Ghostscript `ps2ttf`, etc.), and store it in `webProgram` / `pdfProgram` for client use.
@ -15,7 +15,7 @@
- **Editor UX Safeguards**
- Mark groups using fallback glyphs so the UI can warn about possible appearance shifts. Font family matching is now implemented (Liberation fonts), but weight matching is still TODO, so bold/italic text using fallbacks may appear lighter than original.
- Surface when Type3 conversion was downgraded (e.g., rasterized glyphs) and limit editing to operations that keep the PDF stable.
- Reference: `frontend/src/proprietary/components/tools/pdfJsonEditor/PdfJsonEditorView.tsx:1260-1287`
- Reference: `frontend/src/proprietary/components/tools/pdfTextEditor/PdfTextEditorView.tsx:1260-1287`
- **Canonical Font Sharing**
- Emit fonts once per unique embedded program. Add a `canonicalFonts` array containing the full payload (program, ToUnicode, metadata) and a compact `fontAliases` mapping `{pageNumber, fontId, canonicalUid}` so text elements can still reference per-page IDs.

View File

@ -2,7 +2,7 @@
## Overview
The PDF JSON editor needs to handle **Type3 fonts** - custom vector fonts embedded in PDFs that don't follow standard font formats. These are common in PDFs generated by Matplotlib, LaTeX, scientific papers, and presentation tools.
The PDF Text editor needs to handle **Type3 fonts** - custom vector fonts embedded in PDFs that don't follow standard font formats. These are common in PDFs generated by Matplotlib, LaTeX, scientific papers, and presentation tools.
When converting a PDF to JSON for editing, Type3 fonts present two challenges:
1. **No Unicode mapping** - Character codes don't map to standard Unicode characters
@ -430,8 +430,8 @@ Output shows all Type3 fonts with their signatures and glyph coverage.
- `PdfJsonFontType3Glyph.java` - Model for Type3 glyph data
### Frontend (TypeScript)
- `pdfJsonEditorTypes.ts` - Type definitions for JSON structure
- `pdfJsonEditorUtils.ts` - Font handling utilities
- `pdfTextEditorTypes.ts` - Type definitions for JSON structure
- `pdfTextEditorUtils.ts` - Font handling utilities
### Resources
- `type3/library/index.json` - Font library metadata

View File

@ -765,9 +765,9 @@
"title": "Automate",
"desc": "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."
},
"pdfJsonEditor": {
"pdfTextEditor": {
"tags": "edit,text,modify",
"title": "PDF Editor",
"title": "PDF Text Editor",
"desc": "Edit text content in PDF documents"
},
"mobile": {
@ -4455,8 +4455,8 @@
"startTour": "Start Tour",
"startTourDescription": "Take a guided tour of Stirling PDF's key features"
},
"pdfJsonEditor": {
"viewLabel": "PDF Editor",
"pdfTextEditor": {
"viewLabel": "PDF Text Editor",
"title": "PDF Editor",
"badges": {
"unsaved": "Unsaved changes",

View File

@ -21,14 +21,14 @@ import FontDownloadIcon from '@mui/icons-material/FontDownload';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import { PdfJsonDocument } from '@app/tools/pdfJsonEditor/pdfJsonEditorTypes';
import { PdfJsonDocument } from '@app/tools/pdfTextEditor/pdfTextEditorTypes';
import {
analyzeDocumentFonts,
DocumentFontAnalysis,
FontAnalysis,
getFontStatusColor,
getFontStatusDescription,
} from '@app/tools/pdfJsonEditor/fontAnalysis';
} from '@app/tools/pdfTextEditor/fontAnalysis';
interface FontStatusPanelProps {
document: PdfJsonDocument | null;
@ -99,19 +99,19 @@ const FontDetailItem = ({ analysis }: { analysis: FontAnalysis }) => {
{/* Font Details */}
<Box>
<Text size="xs" c="dimmed" mb={2}>
{t('pdfJsonEditor.fontAnalysis.details', 'Font Details')}:
{t('pdfTextEditor.fontAnalysis.details', 'Font Details')}:
</Text>
<Stack gap={2}>
<Group gap={4}>
<Text size="xs" c="dimmed">
{t('pdfJsonEditor.fontAnalysis.embedded', 'Embedded')}:
{t('pdfTextEditor.fontAnalysis.embedded', 'Embedded')}:
</Text>
<Code style={{ fontSize: '0.65rem', padding: '0 4px' }}>{analysis.embedded ? 'Yes' : 'No'}</Code>
</Group>
{analysis.subtype && (
<Group gap={4}>
<Text size="xs" c="dimmed">
{t('pdfJsonEditor.fontAnalysis.type', 'Type')}:
{t('pdfTextEditor.fontAnalysis.type', 'Type')}:
</Text>
<Code style={{ fontSize: '0.65rem', padding: '0 4px' }}>{analysis.subtype}</Code>
</Group>
@ -119,7 +119,7 @@ const FontDetailItem = ({ analysis }: { analysis: FontAnalysis }) => {
{analysis.webFormat && (
<Group gap={4}>
<Text size="xs" c="dimmed">
{t('pdfJsonEditor.fontAnalysis.webFormat', 'Web Format')}:
{t('pdfTextEditor.fontAnalysis.webFormat', 'Web Format')}:
</Text>
<Code style={{ fontSize: '0.65rem', padding: '0 4px' }}>{analysis.webFormat}</Code>
</Group>
@ -131,7 +131,7 @@ const FontDetailItem = ({ analysis }: { analysis: FontAnalysis }) => {
{analysis.warnings.length > 0 && (
<Box>
<Text size="xs" c="orange" fw={500}>
{t('pdfJsonEditor.fontAnalysis.warnings', 'Warnings')}:
{t('pdfTextEditor.fontAnalysis.warnings', 'Warnings')}:
</Text>
<List size="xs" spacing={2} withPadding>
{analysis.warnings.map((warning, index) => (
@ -147,7 +147,7 @@ const FontDetailItem = ({ analysis }: { analysis: FontAnalysis }) => {
{analysis.suggestions.length > 0 && (
<Box>
<Text size="xs" c="blue" fw={500}>
{t('pdfJsonEditor.fontAnalysis.suggestions', 'Notes')}:
{t('pdfTextEditor.fontAnalysis.suggestions', 'Notes')}:
</Text>
<List size="xs" spacing={2} withPadding>
{analysis.suggestions.map((suggestion, index) => (
@ -192,8 +192,8 @@ const FontStatusPanel: React.FC<FontStatusPanelProps> = ({ document, pageIndex }
const statusColor = canReproducePerfectly ? 'green' : hasWarnings ? 'yellow' : 'blue';
const pageLabel = pageIndex !== undefined
? t('pdfJsonEditor.fontAnalysis.currentPageFonts', 'Fonts on this page')
: t('pdfJsonEditor.fontAnalysis.allFonts', 'All fonts');
? t('pdfTextEditor.fontAnalysis.currentPageFonts', 'Fonts on this page')
: t('pdfTextEditor.fontAnalysis.allFonts', 'All fonts');
return (
<Accordion variant="contained" defaultValue={hasWarnings ? 'fonts' : undefined}>
@ -215,16 +215,16 @@ const FontStatusPanel: React.FC<FontStatusPanelProps> = ({ document, pageIndex }
<Text size="xs" c="dimmed">
{canReproducePerfectly
? t(
'pdfJsonEditor.fontAnalysis.perfectMessage',
'pdfTextEditor.fontAnalysis.perfectMessage',
'All fonts can be reproduced perfectly.'
)
: hasWarnings
? t(
'pdfJsonEditor.fontAnalysis.warningMessage',
'pdfTextEditor.fontAnalysis.warningMessage',
'Some fonts may not render correctly.'
)
: t(
'pdfJsonEditor.fontAnalysis.infoMessage',
'pdfTextEditor.fontAnalysis.infoMessage',
'Font reproduction information available.'
)}
</Text>
@ -233,22 +233,22 @@ const FontStatusPanel: React.FC<FontStatusPanelProps> = ({ document, pageIndex }
<Group gap={4} wrap="wrap">
{summary.perfect > 0 && (
<Badge size="xs" color="green" variant="light" leftSection={<CheckCircleIcon sx={{ fontSize: 12 }} />}>
{summary.perfect} {t('pdfJsonEditor.fontAnalysis.perfect', 'perfect')}
{summary.perfect} {t('pdfTextEditor.fontAnalysis.perfect', 'perfect')}
</Badge>
)}
{summary.embeddedSubset > 0 && (
<Badge size="xs" color="blue" variant="light" leftSection={<InfoIcon sx={{ fontSize: 12 }} />}>
{summary.embeddedSubset} {t('pdfJsonEditor.fontAnalysis.subset', 'subset')}
{summary.embeddedSubset} {t('pdfTextEditor.fontAnalysis.subset', 'subset')}
</Badge>
)}
{summary.systemFallback > 0 && (
<Badge size="xs" color="yellow" variant="light" leftSection={<WarningIcon sx={{ fontSize: 12 }} />}>
{summary.systemFallback} {t('pdfJsonEditor.fontAnalysis.fallback', 'fallback')}
{summary.systemFallback} {t('pdfTextEditor.fontAnalysis.fallback', 'fallback')}
</Badge>
)}
{summary.missing > 0 && (
<Badge size="xs" color="red" variant="light" leftSection={<ErrorIcon sx={{ fontSize: 12 }} />}>
{summary.missing} {t('pdfJsonEditor.fontAnalysis.missing', 'missing')}
{summary.missing} {t('pdfTextEditor.fontAnalysis.missing', 'missing')}
</Badge>
)}
</Group>

View File

@ -32,13 +32,13 @@ import CloseIcon from '@mui/icons-material/Close';
import { Rnd } from 'react-rnd';
import {
PdfJsonEditorViewData,
PdfTextEditorViewData,
PdfJsonFont,
PdfJsonPage,
ConversionProgress,
} from '@app/tools/pdfJsonEditor/pdfJsonEditorTypes';
import { getImageBounds, pageDimensions } from '@app/tools/pdfJsonEditor/pdfJsonEditorUtils';
import FontStatusPanel from '@app/components/tools/pdfJsonEditor/FontStatusPanel';
} from '@app/tools/pdfTextEditor/pdfTextEditorTypes';
import { getImageBounds, pageDimensions } from '@app/tools/pdfTextEditor/pdfTextEditorUtils';
import FontStatusPanel from '@app/components/tools/pdfTextEditor/FontStatusPanel';
const MAX_RENDER_WIDTH = 820;
const MIN_BOX_SIZE = 18;
@ -190,8 +190,8 @@ const extractTextWithSoftBreaks = (element: HTMLElement): { text: string; insert
};
};
interface PdfJsonEditorViewProps {
data: PdfJsonEditorViewData;
interface PdfTextEditorViewProps {
data: PdfTextEditorViewData;
}
const toCssBounds = (
@ -313,7 +313,7 @@ const analyzePageContentType = (groups: TextGroup[]): boolean => {
type GroupingMode = 'auto' | 'paragraph' | 'singleLine';
const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
const PdfTextEditorView = ({ data }: PdfTextEditorViewProps) => {
const { t } = useTranslation();
const [activeGroupId, setActiveGroupId] = useState<string | null>(null);
const [editingGroupId, setEditingGroupId] = useState<string | null>(null);
@ -1066,8 +1066,8 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
<Group justify="space-between" align="center">
<Group gap="xs" align="center">
<DescriptionIcon fontSize="small" />
<Title order={3}>{t('pdfJsonEditor.title', 'PDF JSON Editor')}</Title>
{hasChanges && <Badge color="orange" variant="light" size="sm">{t('pdfJsonEditor.badges.unsaved', 'Edited')}</Badge>}
<Title order={3}>{t('pdfTextEditor.title', 'PDF JSON Editor')}</Title>
{hasChanges && <Badge color="orange" variant="light" size="sm">{t('pdfTextEditor.badges.unsaved', 'Edited')}</Badge>}
</Group>
</Group>
@ -1081,7 +1081,7 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
fullWidth
{...props}
>
{t('pdfJsonEditor.actions.load', 'Load File')}
{t('pdfTextEditor.actions.load', 'Load File')}
</Button>
)}
</FileButton>
@ -1092,7 +1092,7 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
disabled={!hasDocument || isConverting}
fullWidth
>
{t('pdfJsonEditor.actions.reset', 'Reset Changes')}
{t('pdfTextEditor.actions.reset', 'Reset Changes')}
</Button>
<Button
variant="default"
@ -1101,7 +1101,7 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
disabled={!hasDocument || isConverting}
fullWidth
>
{t('pdfJsonEditor.actions.downloadJson', 'Download JSON')}
{t('pdfTextEditor.actions.downloadJson', 'Download JSON')}
</Button>
<Button
leftSection={<PictureAsPdfIcon fontSize="small" />}
@ -1110,13 +1110,13 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
disabled={!hasDocument || !hasChanges || isConverting}
fullWidth
>
{t('pdfJsonEditor.actions.generatePdf', 'Generate PDF')}
{t('pdfTextEditor.actions.generatePdf', 'Generate PDF')}
</Button>
</Stack>
{fileName && (
<Text size="sm" c="dimmed">
{t('pdfJsonEditor.currentFile', 'Current file: {{name}}', { name: fileName })}
{t('pdfTextEditor.currentFile', 'Current file: {{name}}', { name: fileName })}
</Text>
)}
@ -1125,11 +1125,11 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
<Group justify="space-between" align="center">
<div>
<Text fw={500} size="sm">
{t('pdfJsonEditor.options.autoScaleText.title', 'Auto-scale text to fit boxes')}
{t('pdfTextEditor.options.autoScaleText.title', 'Auto-scale text to fit boxes')}
</Text>
<Text size="xs" c="dimmed" mt={4}>
{t(
'pdfJsonEditor.options.autoScaleText.description',
'pdfTextEditor.options.autoScaleText.description',
'Automatically scales text horizontally to fit within its original bounding box when font rendering differs from PDF.'
)}
</Text>
@ -1144,32 +1144,32 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
<Stack gap="xs">
<Group gap={4} align="center">
<Text fw={500} size="sm">
{t('pdfJsonEditor.options.groupingMode.title', 'Text Grouping Mode')}
{t('pdfTextEditor.options.groupingMode.title', 'Text Grouping Mode')}
</Text>
{externalGroupingMode === 'auto' && isParagraphPage && (
<Badge size="xs" color="blue" variant="light">
{t('pdfJsonEditor.pageType.paragraph', 'Paragraph page')}
{t('pdfTextEditor.pageType.paragraph', 'Paragraph page')}
</Badge>
)}
{externalGroupingMode === 'auto' && !isParagraphPage && hasDocument && (
<Badge size="xs" color="gray" variant="light">
{t('pdfJsonEditor.pageType.sparse', 'Sparse text')}
{t('pdfTextEditor.pageType.sparse', 'Sparse text')}
</Badge>
)}
</Group>
<Text size="xs" c="dimmed">
{externalGroupingMode === 'auto'
? t(
'pdfJsonEditor.options.groupingMode.autoDescription',
'pdfTextEditor.options.groupingMode.autoDescription',
'Automatically detects page type and groups text appropriately.'
)
: externalGroupingMode === 'paragraph'
? t(
'pdfJsonEditor.options.groupingMode.paragraphDescription',
'pdfTextEditor.options.groupingMode.paragraphDescription',
'Groups aligned lines into multi-line paragraph text boxes.'
)
: t(
'pdfJsonEditor.options.groupingMode.singleLineDescription',
'pdfTextEditor.options.groupingMode.singleLineDescription',
'Keeps each PDF text line as a separate text box.'
)}
</Text>
@ -1177,9 +1177,9 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
value={externalGroupingMode}
onChange={(value) => handleModeChangeRequest(value as GroupingMode)}
data={[
{ label: t('pdfJsonEditor.groupingMode.auto', 'Auto'), value: 'auto' },
{ label: t('pdfJsonEditor.groupingMode.paragraph', 'Paragraph'), value: 'paragraph' },
{ label: t('pdfJsonEditor.groupingMode.singleLine', 'Single Line'), value: 'singleLine' },
{ label: t('pdfTextEditor.groupingMode.auto', 'Auto'), value: 'auto' },
{ label: t('pdfTextEditor.groupingMode.paragraph', 'Paragraph'), value: 'paragraph' },
{ label: t('pdfTextEditor.groupingMode.singleLine', 'Single Line'), value: 'singleLine' },
]}
fullWidth
/>
@ -1188,11 +1188,11 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
<Group justify="space-between" align="center">
<div>
<Text fw={500} size="sm">
{t('pdfJsonEditor.options.forceSingleElement.title', 'Lock edited text to a single PDF element')}
{t('pdfTextEditor.options.forceSingleElement.title', 'Lock edited text to a single PDF element')}
</Text>
<Text size="xs" c="dimmed" mt={4}>
{t(
'pdfJsonEditor.options.forceSingleElement.description',
'pdfTextEditor.options.forceSingleElement.description',
'When enabled, the editor exports each edited text box as one PDF text element to avoid overlapping glyphs or mixed fonts.'
)}
</Text>
@ -1212,7 +1212,7 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
<Group gap="xs" wrap="nowrap">
<InfoOutlinedIcon fontSize="small" />
<Text size="sm" fw={500}>
{t('pdfJsonEditor.disclaimer.heading', 'Preview Limitations')}
{t('pdfTextEditor.disclaimer.heading', 'Preview Limitations')}
</Text>
</Group>
</Accordion.Control>
@ -1220,19 +1220,19 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
<Stack gap={4}>
<Text size="xs">
{t(
'pdfJsonEditor.disclaimer.textFocus',
'pdfTextEditor.disclaimer.textFocus',
'This workspace focuses on editing text and repositioning embedded images. Complex page artwork, form widgets, and layered graphics are preserved for export but are not fully editable here.'
)}
</Text>
<Text size="xs">
{t(
'pdfJsonEditor.disclaimer.previewVariance',
'pdfTextEditor.disclaimer.previewVariance',
'Some visuals (such as table borders, shapes, or annotation appearances) may not display exactly in the preview. The exported PDF keeps the original drawing commands whenever possible.'
)}
</Text>
<Text size="xs">
{t(
'pdfJsonEditor.disclaimer.alpha',
'pdfTextEditor.disclaimer.alpha',
'This alpha viewer is still evolving—certain fonts, colours, transparency effects, and layout details may shift slightly. Please double-check the generated PDF before sharing.'
)}
</Text>
@ -1262,10 +1262,10 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
<Stack align="center" gap="md">
<DescriptionIcon sx={{ fontSize: 48 }} />
<Text size="lg" fw={600}>
{t('pdfJsonEditor.empty.title', 'No document loaded')}
{t('pdfTextEditor.empty.title', 'No document loaded')}
</Text>
<Text size="sm" c="dimmed" ta="center" maw={420}>
{t('pdfJsonEditor.empty.subtitle', 'Load a PDF or JSON file to begin editing text content.')}
{t('pdfTextEditor.empty.subtitle', 'Load a PDF or JSON file to begin editing text content.')}
</Text>
</Stack>
</Card>
@ -1279,12 +1279,12 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
<Text size="lg" fw={600} mb="xs">
{conversionProgress
? conversionProgress.message
: t('pdfJsonEditor.converting', 'Converting PDF to editable format...')}
: t('pdfTextEditor.converting', 'Converting PDF to editable format...')}
</Text>
{conversionProgress && (
<Group gap="xs">
<Text size="sm" c="dimmed" tt="capitalize">
{t(`pdfJsonEditor.stages.${conversionProgress.stage}`, conversionProgress.stage)}
{t(`pdfTextEditor.stages.${conversionProgress.stage}`, conversionProgress.stage)}
</Text>
{conversionProgress.current !== undefined &&
conversionProgress.total !== undefined && (
@ -1317,18 +1317,18 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
<Group justify="space-between" align="center">
<Group gap="sm">
<Text fw={500}>
{t('pdfJsonEditor.pageSummary', 'Page {{number}} of {{total}}', {
{t('pdfTextEditor.pageSummary', 'Page {{number}} of {{total}}', {
number: selectedPage + 1,
total: pages.length,
})}
</Text>
{dirtyPages[selectedPage] && (
<Badge color="yellow" size="xs">
{t('pdfJsonEditor.badges.modified', 'Edited')}
{t('pdfTextEditor.badges.modified', 'Edited')}
</Badge>
)}
<Badge color="blue" variant="dot" size="xs">
{t('pdfJsonEditor.badges.earlyAccess', 'Early Access')}
{t('pdfTextEditor.badges.earlyAccess', 'Early Access')}
</Badge>
</Group>
{pages.length > 1 && (
@ -1381,7 +1381,7 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
{pagePreview && (
<img
src={pagePreview}
alt={t('pdfJsonEditor.pagePreviewAlt', 'Page preview')}
alt={t('pdfTextEditor.pagePreviewAlt', 'Page preview')}
style={{
position: 'absolute',
inset: 0,
@ -1493,7 +1493,7 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
>
<img
src={src}
alt={t('pdfJsonEditor.imageLabel', 'Placed image')}
alt={t('pdfTextEditor.imageLabel', 'Placed image')}
style={{
width: '100%',
height: '100%',
@ -1510,7 +1510,7 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
<Group justify="center" align="center" style={{ height: '100%' }}>
<Stack gap={4} align="center">
<Text size="sm" c="dimmed">
{t('pdfJsonEditor.noTextOnPage', 'No editable text was detected on this page.')}
{t('pdfTextEditor.noTextOnPage', 'No editable text was detected on this page.')}
</Text>
</Stack>
</Group>
@ -1785,22 +1785,22 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
<Modal
opened={pendingModeChange !== null}
onClose={handleCancelModeChange}
title={t('pdfJsonEditor.modeChange.title', 'Confirm Mode Change')}
title={t('pdfTextEditor.modeChange.title', 'Confirm Mode Change')}
centered
>
<Stack gap="md">
<Text>
{t(
'pdfJsonEditor.modeChange.warning',
'pdfTextEditor.modeChange.warning',
'Changing the text grouping mode will reset all unsaved changes. Are you sure you want to continue?'
)}
</Text>
<Group justify="flex-end" gap="sm">
<Button variant="default" onClick={handleCancelModeChange}>
{t('pdfJsonEditor.modeChange.cancel', 'Cancel')}
{t('pdfTextEditor.modeChange.cancel', 'Cancel')}
</Button>
<Button color="red" onClick={handleConfirmModeChange}>
{t('pdfJsonEditor.modeChange.confirm', 'Reset and Change Mode')}
{t('pdfTextEditor.modeChange.confirm', 'Reset and Change Mode')}
</Button>
</Group>
</Stack>
@ -1809,4 +1809,4 @@ const PdfJsonEditorView = ({ data }: PdfJsonEditorViewProps) => {
);
};
export default PdfJsonEditorView;
export default PdfTextEditorView;

View File

@ -2,7 +2,7 @@ import { useMemo } from "react";
import LocalIcon from "@app/components/shared/LocalIcon";
import { useTranslation } from "react-i18next";
import { getSynonyms } from "@app/utils/toolSynonyms";
import PdfJsonEditor from "@app/tools/pdfJsonEditor/PdfJsonEditor";
import PdfTextEditor from "@app/tools/pdfTextEditor/PdfTextEditor";
import {
SubcategoryId,
ToolCategoryId,
@ -19,19 +19,19 @@ export function useProprietaryToolRegistry(): ProprietaryToolRegistry {
const { t } = useTranslation();
return useMemo<ProprietaryToolRegistry>(() => ({
pdfJsonEditor: {
pdfTextEditor: {
icon: <LocalIcon icon="code-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.pdfJsonEditor.title", "PDF JSON Editor"),
component: PdfJsonEditor,
name: t("home.pdfTextEditor.title", "PDF Text Editor"),
component: PdfTextEditor,
description: t(
"home.pdfJsonEditor.desc",
"home.pdfTextEditor.desc",
"Review and edit Stirling PDF JSON exports with grouped text editing and PDF regeneration"
),
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
subcategoryId: SubcategoryId.GENERAL,
workbench: "custom:pdfJsonEditor",
workbench: "custom:pdfTextEditor",
endpoints: ["json-pdf"],
synonyms: getSynonyms(t, "pdfJsonEditor"),
synonyms: getSynonyms(t, "pdfTextEditor"),
supportsAutomate: false,
automationSettings: null,
},

View File

@ -17,8 +17,8 @@ import {
PdfJsonImageElement,
PdfJsonPage,
TextGroup,
PdfJsonEditorViewData,
} from './pdfJsonEditorTypes';
PdfTextEditorViewData,
} from './pdfTextEditorTypes';
import {
deepCloneDocument,
getDirtyPages,
@ -27,12 +27,12 @@ import {
extractDocumentImages,
cloneImageElement,
valueOr,
} from './pdfJsonEditorUtils';
import PdfJsonEditorView from '@app/components/tools/pdfJsonEditor/PdfJsonEditorView';
} from './pdfTextEditorUtils';
import PdfTextEditorView from '@app/components/tools/pdfTextEditor/PdfTextEditorView';
import type { PDFDocumentProxy } from 'pdfjs-dist';
const VIEW_ID = 'pdfJsonEditorView';
const WORKBENCH_ID = 'custom:pdfJsonEditor' as const;
const VIEW_ID = 'pdfTextEditorView';
const WORKBENCH_ID = 'custom:pdfTextEditor' as const;
const sanitizeBaseName = (name?: string | null): string => {
if (!name || name.trim().length === 0) {
@ -52,7 +52,7 @@ const getAutoLoadKey = (file: File): string => {
return `${file.name}|${file.size}|${file.lastModified}`;
};
const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
const PdfTextEditor = ({ onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation();
const {
registerCustomWorkbenchView,
@ -136,7 +136,7 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
);
const hasChanges = useMemo(() => dirtyPages.some(Boolean), [dirtyPages]);
const hasDocument = loadedDocument !== null;
const viewLabel = useMemo(() => t('pdfJsonEditor.viewLabel', 'PDF Editor'), [t]);
const viewLabel = useMemo(() => t('pdfTextEditor.viewLabel', 'PDF Editor'), [t]);
const { selectedFiles } = useFileSelection();
const resetToDocument = useCallback((document: PdfJsonDocument | null, mode: 'auto' | 'paragraph' | 'singleLine') => {
@ -191,9 +191,9 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
if (!jobId) {
return;
}
console.log(`[PdfJsonEditor] Cleaning up cached document for jobId: ${jobId}`);
console.log(`[PdfTextEditor] Cleaning up cached document for jobId: ${jobId}`);
apiClient.post(`/api/v1/convert/pdf/json/clear-cache/${jobId}`).catch((error) => {
console.warn('[PdfJsonEditor] Failed to clear cache:', error);
console.warn('[PdfTextEditor] Failed to clear cache:', error);
});
}, []);
@ -227,7 +227,7 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
setHasVectorPreview(true);
} catch (error) {
if (previewRequestIdRef.current === requestId) {
console.warn('[PdfJsonEditor] Failed to initialise PDF preview:', error);
console.warn('[PdfTextEditor] Failed to initialise PDF preview:', error);
clearPdfPreview();
}
}
@ -522,7 +522,7 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
}
console.log(
`[PdfJsonEditor] Document loaded. Lazy image mode: ${shouldUseLazyMode}, Pages: ${
`[PdfTextEditor] Document loaded. Lazy image mode: ${shouldUseLazyMode}, Pages: ${
parsed.pages?.length || 0
}`,
);
@ -559,13 +559,13 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
if (isPdf) {
const errorMsg =
error?.message ||
t('pdfJsonEditor.conversionFailed', 'Failed to convert PDF. Please try again.');
t('pdfTextEditor.conversionFailed', 'Failed to convert PDF. Please try again.');
setErrorMessage(errorMsg);
console.error('Setting error message:', errorMsg);
} else {
setErrorMessage(
t(
'pdfJsonEditor.errors.invalidJson',
'pdfTextEditor.errors.invalidJson',
'Unable to read the JSON file. Ensure it was generated by the PDF to JSON tool.',
),
);
@ -891,7 +891,7 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
const message =
error?.response?.data ||
error?.message ||
t('pdfJsonEditor.errors.pdfConversion', 'Unable to convert the edited JSON back into a PDF.');
t('pdfTextEditor.errors.pdfConversion', 'Unable to convert the edited JSON back into a PDF.');
const msgString = typeof message === 'string' ? message : String(message);
setErrorMessage(msgString);
if (onError) {
@ -974,7 +974,7 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
}
context.restore();
} catch (textError) {
console.warn('[PdfJsonEditor] Failed to strip text from preview', textError);
console.warn('[PdfTextEditor] Failed to strip text from preview', textError);
}
const dataUrl = canvas.toDataURL('image/png');
page.cleanup();
@ -988,7 +988,7 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
return next;
});
} catch (error) {
console.warn('[PdfJsonEditor] Failed to render page preview', error);
console.warn('[PdfTextEditor] Failed to render page preview', error);
} finally {
previewRenderingRef.current.delete(pageIndex);
}
@ -1004,7 +1004,7 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
}
}, [groupingMode, resetToDocument]);
const viewData = useMemo<PdfJsonEditorViewData>(() => ({
const viewData = useMemo<PdfTextEditorViewData>(() => ({
document: loadedDocument,
groupsByPage,
imagesByPage,
@ -1063,7 +1063,7 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
setForceSingleTextElement,
]);
const latestViewDataRef = useRef<PdfJsonEditorViewData>(viewData);
const latestViewDataRef = useRef<PdfTextEditorViewData>(viewData);
latestViewDataRef.current = viewData;
// Trigger initial image loading in lazy mode
@ -1079,7 +1079,7 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
return;
}
if (navigationState.selectedTool !== 'pdfJsonEditor') {
if (navigationState.selectedTool !== 'pdfTextEditor') {
return;
}
@ -1103,7 +1103,7 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
workbenchId: WORKBENCH_ID,
label: viewLabel,
icon: <DescriptionIcon fontSize="small" />,
component: PdfJsonEditorView,
component: PdfTextEditorView,
});
setLeftPanelView('hidden');
setCustomWorkbenchViewData(VIEW_ID, latestViewDataRef.current);
@ -1127,14 +1127,14 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
useEffect(() => {
if (
navigationState.selectedTool === 'pdfJsonEditor' &&
navigationState.selectedTool === 'pdfTextEditor' &&
navigationState.workbench !== WORKBENCH_ID
) {
navigationActions.setWorkbench(WORKBENCH_ID);
}
}, [navigationActions, navigationState.selectedTool, navigationState.workbench]);
const lastSentViewDataRef = useRef<PdfJsonEditorViewData | null>(null);
const lastSentViewDataRef = useRef<PdfTextEditorViewData | null>(null);
useEffect(() => {
if (lastSentViewDataRef.current === viewData) {
@ -1148,12 +1148,12 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
return null;
};
(PdfJsonEditor as ToolComponent).tool = () => {
(PdfTextEditor as ToolComponent).tool = () => {
throw new Error('PDF JSON Editor does not support automation operations.');
};
(PdfJsonEditor as ToolComponent).getDefaultParameters = () => ({
(PdfTextEditor as ToolComponent).getDefaultParameters = () => ({
groups: [],
});
export default PdfJsonEditor as ToolComponent;
export default PdfTextEditor as ToolComponent;

View File

@ -1,4 +1,4 @@
import { PdfJsonDocument, PdfJsonFont } from './pdfJsonEditorTypes';
import { PdfJsonDocument, PdfJsonFont } from './pdfTextEditorTypes';
export type FontStatus = 'perfect' | 'embedded-subset' | 'system-fallback' | 'missing' | 'unknown';

View File

@ -181,7 +181,7 @@ export interface ConversionProgress {
total?: number;
}
export interface PdfJsonEditorViewData {
export interface PdfTextEditorViewData {
document: PdfJsonDocument | null;
groupsByPage: TextGroup[][];
imagesByPage: PdfJsonImageElement[][];

View File

@ -7,7 +7,7 @@ import {
TextGroup,
DEFAULT_PAGE_HEIGHT,
DEFAULT_PAGE_WIDTH,
} from './pdfJsonEditorTypes';
} from './pdfTextEditorTypes';
const LINE_TOLERANCE = 2;
const GAP_FACTOR = 0.6;

View File

@ -5,7 +5,7 @@
*/
export const PROPRIETARY_REGULAR_TOOL_IDS = [
'pdfJsonEditor',
'pdfTextEditor',
] as const;
export const PROPRIETARY_SUPER_TOOL_IDS = [