mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-11-16 01:21:16 +01:00
rename tool
This commit is contained in:
parent
246d7a7b16
commit
aad1b06324
@ -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.
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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>
|
||||
@ -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;
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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;
|
||||
@ -1,4 +1,4 @@
|
||||
import { PdfJsonDocument, PdfJsonFont } from './pdfJsonEditorTypes';
|
||||
import { PdfJsonDocument, PdfJsonFont } from './pdfTextEditorTypes';
|
||||
|
||||
export type FontStatus = 'perfect' | 'embedded-subset' | 'system-fallback' | 'missing' | 'unknown';
|
||||
|
||||
@ -181,7 +181,7 @@ export interface ConversionProgress {
|
||||
total?: number;
|
||||
}
|
||||
|
||||
export interface PdfJsonEditorViewData {
|
||||
export interface PdfTextEditorViewData {
|
||||
document: PdfJsonDocument | null;
|
||||
groupsByPage: TextGroup[][];
|
||||
imagesByPage: PdfJsonImageElement[][];
|
||||
@ -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;
|
||||
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
export const PROPRIETARY_REGULAR_TOOL_IDS = [
|
||||
'pdfJsonEditor',
|
||||
'pdfTextEditor',
|
||||
] as const;
|
||||
|
||||
export const PROPRIETARY_SUPER_TOOL_IDS = [
|
||||
|
||||
Loading…
Reference in New Issue
Block a user