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:
Anthony Stirling 2025-12-03 12:55:34 +00:00 committed by GitHub
parent f902e8aca9
commit bdb3c887f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 33 additions and 41 deletions

View File

@ -31,12 +31,10 @@ import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.JobOwnershipService;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.WebResponseUtils;
import stirling.software.proprietary.security.config.PremiumEndpoint;
@Slf4j
@ConvertApi
@RequiredArgsConstructor
@PremiumEndpoint
public class ConvertPdfJsonController {
private final PdfJsonConversionService pdfJsonConversionService;

View File

@ -33,7 +33,7 @@ import MergeTypeIcon from '@mui/icons-material/MergeType';
import CallSplitIcon from '@mui/icons-material/CallSplit';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import { Rnd } from 'react-rnd';
import NavigationWarningModal from '@core/components/shared/NavigationWarningModal';
import NavigationWarningModal from '@app/components/shared/NavigationWarningModal';
import {
PdfTextEditorViewData,
@ -2147,14 +2147,20 @@ const selectionToolbarPosition = useMemo(() => {
// Determine text wrapping behavior based on whether text has been changed
const hasChanges = changed;
const widthExtended = resolvedWidth - baseWidth > 0.5;
const enableWrap = isParagraphLayout || widthExtended || isEditing || hasChanges;
// Only enable wrapping if:
// 1. It's paragraph layout (multi-line groups should wrap)
// 2. Width was manually extended (user explicitly made space for wrapping)
// 3. Has changes AND was already wrapping (preserve existing wrap state)
// DO NOT enable wrapping just because isEditing - text should only wrap when it actually overflows
const wasWrapping = isParagraphLayout || widthExtended;
const enableWrap = wasWrapping || (hasChanges && wasWrapping);
const whiteSpace = enableWrap ? 'pre-wrap' : 'pre';
const wordBreak = enableWrap ? 'break-word' : 'normal';
const overflowWrap = enableWrap ? 'break-word' : 'normal';
// For paragraph mode, allow height to grow to accommodate lines without wrapping
// For single-line mode, maintain fixed height based on PDF bounds
const useFlexibleHeight = isEditing || enableWrap || (isParagraphLayout && lineCount > 1);
const useFlexibleHeight = enableWrap || (isParagraphLayout && lineCount > 1);
// The renderGroupContainer wrapper adds 4px horizontal padding (2px left + 2px right)
// We need to add this to the container width to compensate, so the inner content

View File

@ -43,6 +43,7 @@ import CertSign from "@app/tools/CertSign";
import BookletImposition from "@app/tools/BookletImposition";
import Flatten from "@app/tools/Flatten";
import Rotate from "@app/tools/Rotate";
import PdfTextEditor from "@app/tools/pdfTextEditor/PdfTextEditor";
import ChangeMetadata from "@app/tools/ChangeMetadata";
import Crop from "@app/tools/Crop";
import Sign from "@app/tools/Sign";
@ -890,6 +891,23 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
automationSettings: RedactSingleStepSettings,
synonyms: getSynonyms(t, "redact")
},
pdfTextEditor: {
icon: <LocalIcon icon="edit-square-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.pdfTextEditor.title", "PDF Text Editor"),
component: PdfTextEditor,
description: t(
"home.pdfTextEditor.desc",
"Review and edit text and images in PDFs with grouped text editing and PDF regeneration"
),
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
subcategoryId: SubcategoryId.GENERAL,
maxFiles: 1,
endpoints: ["text-editor-pdf"],
synonyms: getSynonyms(t, "pdfTextEditor"),
supportsAutomate: false,
automationSettings: null,
versionStatus: "alpha",
},
};
const regularTools = {} as RegularToolRegistry;

View File

@ -10,7 +10,7 @@ import { CONVERSION_ENDPOINTS } from '@app/constants/convertConstants';
import apiClient from '@app/services/apiClient';
import { downloadBlob, downloadTextAsFile } from '@app/utils/downloadUtils';
import { getFilenameFromHeaders } from '@app/utils/fileResponseUtils';
import { pdfWorkerManager } from '@core/services/pdfWorkerManager';
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
import { Util } from 'pdfjs-dist/legacy/build/pdf.mjs';
import {
PdfJsonDocument,

View File

@ -54,6 +54,7 @@ export const CORE_REGULAR_TOOL_IDS = [
'replaceColor',
'showJS',
'bookletImposition',
'pdfTextEditor',
] as const;
export const CORE_SUPER_TOOL_IDS = [

View File

@ -97,6 +97,7 @@ export const URL_TO_TOOL_MAP: Record<string, ToolId> = {
'/automate': 'automate',
'/sign': 'sign',
'/add-text': 'addText',
'/pdf-text-editor': 'pdfTextEditor',
// Developer tools
'/dev-api': 'devApi',

View File

@ -1,13 +1,5 @@
import { useMemo } from "react";
import LocalIcon from "@app/components/shared/LocalIcon";
import { useTranslation } from "react-i18next";
import { getSynonyms } from "@app/utils/toolSynonyms";
import PdfTextEditor from "@app/tools/pdfTextEditor/PdfTextEditor";
import {
SubcategoryId,
ToolCategoryId,
type ProprietaryToolRegistry,
} from "@app/data/toolsTaxonomy";
import { type ProprietaryToolRegistry } from "@app/data/toolsTaxonomy";
/**
* Hook that provides the proprietary tool registry.
@ -16,26 +8,5 @@ import {
* and will be included in the main tool registry.
*/
export function useProprietaryToolRegistry(): ProprietaryToolRegistry {
const { t } = useTranslation();
return useMemo<ProprietaryToolRegistry>(() => ({
pdfTextEditor: {
icon: <LocalIcon icon="edit-square-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.pdfTextEditor.title", "PDF Text Editor"),
component: PdfTextEditor,
description: t(
"home.pdfTextEditor.desc",
"Review and edit text and images in PDFs with grouped text editing and PDF regeneration"
),
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
subcategoryId: SubcategoryId.GENERAL,
maxFiles: 1,
endpoints: ["text-editor-pdf"],
synonyms: getSynonyms(t, "pdfTextEditor"),
supportsAutomate: false,
automationSettings: null,
versionStatus: "alpha",
requiresPremium: true,
},
}), [t]);
return useMemo<ProprietaryToolRegistry>(() => ({}), []);
}

View File

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

View File

@ -8,9 +8,7 @@ import { ToolId } from '@app/types/toolId';
import { URL_TO_TOOL_MAP as CORE_URL_TO_TOOL_MAP } from '@core/utils/urlMapping';
// Proprietary URL mappings
const PROPRIETARY_URL_MAPPINGS: Record<string, ToolId> = {
'/pdf-text-editor': 'pdfTextEditor',
};
const PROPRIETARY_URL_MAPPINGS: Record<string, ToolId> = {};
// Merge core and proprietary mappings
export const URL_TO_TOOL_MAP: Record<string, ToolId> = {