mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-15 19:10: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:
@@ -0,0 +1,286 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Accordion,
|
||||
Badge,
|
||||
Box,
|
||||
Code,
|
||||
Collapse,
|
||||
Group,
|
||||
List,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import ErrorIcon from '@mui/icons-material/Error';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
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/pdfTextEditor/pdfTextEditorTypes';
|
||||
import {
|
||||
analyzeDocumentFonts,
|
||||
DocumentFontAnalysis,
|
||||
FontAnalysis,
|
||||
getFontStatusColor,
|
||||
getFontStatusDescription,
|
||||
} from '@app/tools/pdfTextEditor/fontAnalysis';
|
||||
|
||||
interface FontStatusPanelProps {
|
||||
document: PdfJsonDocument | null;
|
||||
pageIndex?: number;
|
||||
}
|
||||
|
||||
const FontStatusBadge = ({ analysis }: { analysis: FontAnalysis }) => {
|
||||
const color = getFontStatusColor(analysis.status);
|
||||
const description = getFontStatusDescription(analysis.status);
|
||||
|
||||
const icon = useMemo(() => {
|
||||
switch (analysis.status) {
|
||||
case 'perfect':
|
||||
return <CheckCircleIcon sx={{ fontSize: 14 }} />;
|
||||
case 'embedded-subset':
|
||||
return <InfoIcon sx={{ fontSize: 14 }} />;
|
||||
case 'system-fallback':
|
||||
return <WarningIcon sx={{ fontSize: 14 }} />;
|
||||
case 'missing':
|
||||
return <ErrorIcon sx={{ fontSize: 14 }} />;
|
||||
default:
|
||||
return <InfoIcon sx={{ fontSize: 14 }} />;
|
||||
}
|
||||
}, [analysis.status]);
|
||||
|
||||
return (
|
||||
<Tooltip label={description} position="top" withArrow>
|
||||
<Badge
|
||||
size="xs"
|
||||
color={color}
|
||||
variant="light"
|
||||
leftSection={icon}
|
||||
style={{ cursor: 'help' }}
|
||||
>
|
||||
{analysis.status.replace('-', ' ')}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const FontDetailItem = ({ analysis }: { analysis: FontAnalysis }) => {
|
||||
const { t } = useTranslation();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<Paper withBorder p="xs" style={{ cursor: 'pointer' }} onClick={() => setExpanded(!expanded)}>
|
||||
<Stack gap={4}>
|
||||
<Group justify="space-between">
|
||||
<Group gap={4}>
|
||||
<FontDownloadIcon sx={{ fontSize: 16 }} />
|
||||
<Text size="xs" fw={500} lineClamp={1}>
|
||||
{analysis.baseName}
|
||||
</Text>
|
||||
{analysis.isSubset && (
|
||||
<Badge size="xs" color="gray" variant="outline">
|
||||
subset
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
<Group gap={4}>
|
||||
<FontStatusBadge analysis={analysis} />
|
||||
{expanded ? <ExpandLessIcon sx={{ fontSize: 16 }} /> : <ExpandMoreIcon sx={{ fontSize: 16 }} />}
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<Collapse in={expanded}>
|
||||
<Stack gap={4} mt={4}>
|
||||
{/* Font Details */}
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" mb={2}>
|
||||
{t('pdfTextEditor.fontAnalysis.details', 'Font Details')}:
|
||||
</Text>
|
||||
<Stack gap={2}>
|
||||
<Group gap={4}>
|
||||
<Text size="xs" c="dimmed">
|
||||
{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('pdfTextEditor.fontAnalysis.type', 'Type')}:
|
||||
</Text>
|
||||
<Code style={{ fontSize: '0.65rem', padding: '0 4px' }}>{analysis.subtype}</Code>
|
||||
</Group>
|
||||
)}
|
||||
{analysis.webFormat && (
|
||||
<Group gap={4}>
|
||||
<Text size="xs" c="dimmed">
|
||||
{t('pdfTextEditor.fontAnalysis.webFormat', 'Web Format')}:
|
||||
</Text>
|
||||
<Code style={{ fontSize: '0.65rem', padding: '0 4px' }}>{analysis.webFormat}</Code>
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* Warnings */}
|
||||
{analysis.warnings.length > 0 && (
|
||||
<Box>
|
||||
<Text size="xs" c="orange" fw={500}>
|
||||
{t('pdfTextEditor.fontAnalysis.warnings', 'Warnings')}:
|
||||
</Text>
|
||||
<List size="xs" spacing={2} withPadding>
|
||||
{analysis.warnings.map((warning, index) => (
|
||||
<List.Item key={index}>
|
||||
<Text size="xs">{warning}</Text>
|
||||
</List.Item>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Suggestions */}
|
||||
{analysis.suggestions.length > 0 && (
|
||||
<Box>
|
||||
<Text size="xs" c="blue" fw={500}>
|
||||
{t('pdfTextEditor.fontAnalysis.suggestions', 'Notes')}:
|
||||
</Text>
|
||||
<List size="xs" spacing={2} withPadding>
|
||||
{analysis.suggestions.map((suggestion, index) => (
|
||||
<List.Item key={index}>
|
||||
<Text size="xs">{suggestion}</Text>
|
||||
</List.Item>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Collapse>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
const FontStatusPanel: React.FC<FontStatusPanelProps> = ({ document, pageIndex }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const fontAnalysis: DocumentFontAnalysis = useMemo(
|
||||
() => analyzeDocumentFonts(document, pageIndex),
|
||||
[document, pageIndex]
|
||||
);
|
||||
|
||||
const { canReproducePerfectly, hasWarnings, summary, fonts } = fontAnalysis;
|
||||
|
||||
const statusIcon = useMemo(() => {
|
||||
if (canReproducePerfectly) {
|
||||
return <CheckCircleIcon sx={{ fontSize: 16 }} />;
|
||||
}
|
||||
if (hasWarnings) {
|
||||
return <WarningIcon sx={{ fontSize: 16 }} />;
|
||||
}
|
||||
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
|
||||
? t('pdfTextEditor.fontAnalysis.currentPageFonts', 'Fonts on this page')
|
||||
: t('pdfTextEditor.fontAnalysis.allFonts', 'All fonts');
|
||||
|
||||
return (
|
||||
<Accordion variant="contained" defaultValue={hasWarnings ? 'fonts' : undefined}>
|
||||
<Accordion.Item value="fonts">
|
||||
<Accordion.Control>
|
||||
<Group gap="xs" wrap="wrap" style={{ flex: 1 }}>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
{statusIcon}
|
||||
<Text size="sm" fw={500}>
|
||||
{pageLabel}
|
||||
</Text>
|
||||
<Badge size="xs" color={statusColor} variant="dot">
|
||||
{fonts.length}
|
||||
</Badge>
|
||||
</Group>
|
||||
|
||||
{/* Warning badges BEFORE expansion */}
|
||||
<Group gap={4} wrap="wrap">
|
||||
{summary.systemFallback > 0 && (
|
||||
<Badge size="xs" color="yellow" variant="filled" leftSection={<WarningIcon sx={{ fontSize: 12 }} />}>
|
||||
{summary.systemFallback} {t('pdfTextEditor.fontAnalysis.fallback', 'fallback')}
|
||||
</Badge>
|
||||
)}
|
||||
{summary.missing > 0 && (
|
||||
<Badge size="xs" color="red" variant="filled" leftSection={<ErrorIcon sx={{ fontSize: 12 }} />}>
|
||||
{summary.missing} {t('pdfTextEditor.fontAnalysis.missing', 'missing')}
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
</Group>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<Stack gap="xs">
|
||||
{/* Overall Status Message */}
|
||||
<Text size="xs" c="dimmed">
|
||||
{canReproducePerfectly
|
||||
? t(
|
||||
'pdfTextEditor.fontAnalysis.perfectMessage',
|
||||
'All fonts can be reproduced perfectly.'
|
||||
)
|
||||
: hasWarnings
|
||||
? t(
|
||||
'pdfTextEditor.fontAnalysis.warningMessage',
|
||||
'Some fonts may not render correctly.'
|
||||
)
|
||||
: t(
|
||||
'pdfTextEditor.fontAnalysis.infoMessage',
|
||||
'Font reproduction information available.'
|
||||
)}
|
||||
</Text>
|
||||
|
||||
{/* Summary Statistics */}
|
||||
<Group gap={4} wrap="wrap">
|
||||
{summary.perfect > 0 && (
|
||||
<Badge size="xs" color="green" variant="light" leftSection={<CheckCircleIcon sx={{ fontSize: 12 }} />}>
|
||||
{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('pdfTextEditor.fontAnalysis.subset', 'subset')}
|
||||
</Badge>
|
||||
)}
|
||||
{summary.systemFallback > 0 && (
|
||||
<Badge size="xs" color="yellow" variant="light" leftSection={<WarningIcon sx={{ fontSize: 12 }} />}>
|
||||
{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('pdfTextEditor.fontAnalysis.missing', 'missing')}
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
{/* Font List */}
|
||||
<Stack gap={4} mt="xs">
|
||||
{fonts.map((font, index) => (
|
||||
<FontDetailItem key={`${font.fontId}-${index}`} analysis={font} />
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
|
||||
export default FontStatusPanel;
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user