Invert colors (#4498)

This commit is contained in:
Anthony Stirling 2025-09-26 12:44:25 +01:00 committed by GitHub
parent 233b710b78
commit 18fa16f08e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 329 additions and 27 deletions

View File

@ -619,8 +619,7 @@
"title": "Auto Split by Size/Count",
"desc": "Automatically split PDFs by file size or page count"
},
"replaceColorPdf": {
"tags": "color,replace,invert",
"replaceColor": {
"title": "Replace & Invert Colour",
"desc": "Replace or invert colours in PDF documents"
},
@ -2533,25 +2532,48 @@
},
"selectCustomCert": "Custom Certificate File X.509 (Optional)"
},
"replace-color": {
"title": "Advanced Colour options",
"header": "Replace-Invert Colour PDF",
"selectText": {
"1": "Replace or Invert colour Options",
"2": "Default(Default high contrast colours)",
"3": "Custom(Customised colours)",
"4": "Full-Invert(Invert all colours)",
"5": "High contrast colour options",
"6": "white text on black background",
"7": "Black text on white background",
"8": "Yellow text on black background",
"9": "Green text on black background",
"10": "Choose text Colour",
"11": "Choose background Colour"
"replaceColor": {
"labels": {
"settings": "Settings",
"colourOperation": "Colour operation"
},
"submit": "Replace"
"options": {
"highContrast": "High contrast",
"invertAll": "Invert all colours",
"custom": "Custom"
},
"tooltip": {
"header": {
"title": "Replace & Invert Colour Settings Overview"
},
"description": {
"title": "Description",
"text": "Transform PDF colours to improve readability and accessibility. Choose from high contrast presets, invert all colours, or create custom colour schemes."
},
"highContrast": {
"title": "High Contrast",
"text": "Apply predefined high contrast colour combinations designed for better readability and accessibility compliance.",
"bullet1": "White text on black background - Classic dark mode",
"bullet2": "Black text on white background - Standard high contrast",
"bullet3": "Yellow text on black background - High visibility option",
"bullet4": "Green text on black background - Alternative high contrast"
},
"invertAll": {
"title": "Invert All Colours",
"text": "Completely invert all colours in the PDF, creating a negative-like effect. Useful for creating dark mode versions of documents or reducing eye strain in low-light conditions."
},
"custom": {
"title": "Custom Colours",
"text": "Define your own text and background colours using the colour pickers. Perfect for creating branded documents or specific accessibility requirements.",
"bullet1": "Text colour - Choose the colour for text elements",
"bullet2": "Background colour - Set the background colour for the document"
}
},
"error": {
"failed": "An error occurred while processing the colour replacement."
}
},
"replaceColorPdf": {
"replaceColor": {
"tags": "Replace Colour,Page operations,Back end,server side"
},
"login": {

View File

@ -0,0 +1,108 @@
import React from "react";
import { Stack, Text, Select, ColorInput } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { ReplaceColorParameters } from "../../../hooks/tools/replaceColor/useReplaceColorParameters";
interface ReplaceColorSettingsProps {
parameters: ReplaceColorParameters;
onParameterChange: <K extends keyof ReplaceColorParameters>(key: K, value: ReplaceColorParameters[K]) => void;
disabled?: boolean;
}
const ReplaceColorSettings = ({ parameters, onParameterChange, disabled = false }: ReplaceColorSettingsProps) => {
const { t } = useTranslation();
const replaceAndInvertOptions = [
{
value: 'HIGH_CONTRAST_COLOR',
label: t('replaceColor.options.highContrast', 'High contrast')
},
{
value: 'FULL_INVERSION',
label: t('replaceColor.options.invertAll', 'Invert all colours')
},
{
value: 'CUSTOM_COLOR',
label: t('replaceColor.options.custom', 'Custom')
}
];
const highContrastOptions = [
{
value: 'WHITE_TEXT_ON_BLACK',
label: t('replace-color.selectText.6', 'White text on black background')
},
{
value: 'BLACK_TEXT_ON_WHITE',
label: t('replace-color.selectText.7', 'Black text on white background')
},
{
value: 'YELLOW_TEXT_ON_BLACK',
label: t('replace-color.selectText.8', 'Yellow text on black background')
},
{
value: 'GREEN_TEXT_ON_BLACK',
label: t('replace-color.selectText.9', 'Green text on black background')
}
];
return (
<Stack gap="md">
<Stack gap="xs">
<Text size="sm" fw={500}>
{t('replaceColor.labels.colourOperation', 'Colour operation')}
</Text>
<Select
value={parameters.replaceAndInvertOption}
onChange={(value) => value && onParameterChange('replaceAndInvertOption', value as ReplaceColorParameters['replaceAndInvertOption'])}
data={replaceAndInvertOptions}
disabled={disabled}
/>
</Stack>
{parameters.replaceAndInvertOption === 'HIGH_CONTRAST_COLOR' && (
<Stack gap="xs">
<Text size="sm" fw={500}>
{t('replace-color.selectText.5', 'High contrast color options')}
</Text>
<Select
value={parameters.highContrastColorCombination}
onChange={(value) => value && onParameterChange('highContrastColorCombination', value as ReplaceColorParameters['highContrastColorCombination'])}
data={highContrastOptions}
disabled={disabled}
/>
</Stack>
)}
{parameters.replaceAndInvertOption === 'CUSTOM_COLOR' && (
<>
<Stack gap="xs">
<Text size="sm" fw={500}>
{t('replace-color.selectText.10', 'Choose text Color')}
</Text>
<ColorInput
value={parameters.textColor}
onChange={(value) => onParameterChange('textColor', value)}
format="hex"
disabled={disabled}
/>
</Stack>
<Stack gap="xs">
<Text size="sm" fw={500}>
{t('replace-color.selectText.11', 'Choose background Color')}
</Text>
<ColorInput
value={parameters.backGroundColor}
onChange={(value) => onParameterChange('backGroundColor', value)}
format="hex"
disabled={disabled}
/>
</Stack>
</>
)}
</Stack>
);
};
export default ReplaceColorSettings;

View File

@ -0,0 +1,40 @@
import { useTranslation } from 'react-i18next';
import { TooltipContent } from '../../types/tips';
export const useReplaceColorTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("replaceColor.tooltip.header.title", "Replace & Invert Colour Settings Overview")
},
tips: [
{
title: t("replaceColor.tooltip.description.title", "Description"),
description: t("replaceColor.tooltip.description.text", "Transform PDF colours to improve readability and accessibility. Choose from high contrast presets, invert all colours, or create custom colour schemes.")
},
{
title: t("replaceColor.tooltip.highContrast.title", "High Contrast"),
description: t("replaceColor.tooltip.highContrast.text", "Apply predefined high contrast colour combinations designed for better readability and accessibility compliance."),
bullets: [
t("replaceColor.tooltip.highContrast.bullet1", "White text on black background - Classic dark mode"),
t("replaceColor.tooltip.highContrast.bullet2", "Black text on white background - Standard high contrast"),
t("replaceColor.tooltip.highContrast.bullet3", "Yellow text on black background - High visibility option"),
t("replaceColor.tooltip.highContrast.bullet4", "Green text on black background - Alternative high contrast")
]
},
{
title: t("replaceColor.tooltip.invertAll.title", "Invert All Colours"),
description: t("replaceColor.tooltip.invertAll.text", "Completely invert all colours in the PDF, creating a negative-like effect. Useful for creating dark mode versions of documents or reducing eye strain in low-light conditions.")
},
{
title: t("replaceColor.tooltip.custom.title", "Custom Colours"),
description: t("replaceColor.tooltip.custom.text", "Define your own text and background colours using the colour pickers. Perfect for creating branded documents or specific accessibility requirements."),
bullets: [
t("replaceColor.tooltip.custom.bullet1", "Text colour - Choose the colour for text elements"),
t("replaceColor.tooltip.custom.bullet2", "Background colour - Set the background colour for the document")
]
}
]
};
};

View File

@ -50,6 +50,7 @@ import { redactOperationConfig } from "../hooks/tools/redact/useRedactOperation"
import { rotateOperationConfig } from "../hooks/tools/rotate/useRotateOperation";
import { changeMetadataOperationConfig } from "../hooks/tools/changeMetadata/useChangeMetadataOperation";
import { cropOperationConfig } from "../hooks/tools/crop/useCropOperation";
import { replaceColorOperationConfig } from "../hooks/tools/replaceColor/useReplaceColorOperation";
import CompressSettings from "../components/tools/compress/CompressSettings";
import SplitSettings from "../components/tools/split/SplitSettings";
import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings";
@ -68,6 +69,7 @@ import RedactSingleStepSettings from "../components/tools/redact/RedactSingleSte
import RotateSettings from "../components/tools/rotate/RotateSettings";
import Redact from "../tools/Redact";
import AdjustPageScale from "../tools/AdjustPageScale";
import ReplaceColor from "../tools/ReplaceColor";
import ScannerImageSplit from "../tools/ScannerImageSplit";
import { ToolId } from "../types/toolId";
import MergeSettings from '../components/tools/merge/MergeSettings';
@ -77,6 +79,7 @@ import AdjustPageScaleSettings from "../components/tools/adjustPageScale/AdjustP
import ScannerImageSplitSettings from "../components/tools/scannerImageSplit/ScannerImageSplitSettings";
import ChangeMetadataSingleStep from "../components/tools/changeMetadata/ChangeMetadataSingleStep";
import CropSettings from "../components/tools/crop/CropSettings";
import ReplaceColorSettings from "../components/tools/replaceColor/ReplaceColorSettings";
const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI
@ -646,14 +649,18 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
synonyms: getSynonyms(t, "overlayPdfs"),
},
replaceColorPdf: {
replaceColor: {
icon: <LocalIcon icon="format-color-fill-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.replaceColorPdf.title", "Replace & Invert Color"),
component: null,
description: t("home.replaceColorPdf.desc", "Replace or invert colors in PDF documents"),
name: t("home.replaceColor.title", "Replace & Invert Color"),
component: ReplaceColor,
description: t("home.replaceColor.desc", "Replace or invert colors in PDF documents"),
categoryId: ToolCategoryId.ADVANCED_TOOLS,
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
synonyms: getSynonyms(t, "replaceColorPdf"),
maxFiles: -1,
endpoints: ["replace-invert-pdf"],
operationConfig: replaceColorOperationConfig,
settingsComponent: ReplaceColorSettings,
synonyms: getSynonyms(t, "replaceColor"),
},
addImage: {
icon: <LocalIcon icon="image-rounded" width="1.5rem" height="1.5rem" />,

View File

@ -0,0 +1,38 @@
import { useTranslation } from 'react-i18next';
import { ToolType, useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { ReplaceColorParameters, defaultParameters } from './useReplaceColorParameters';
export const buildReplaceColorFormData = (parameters: ReplaceColorParameters, file: File): FormData => {
const formData = new FormData();
formData.append('fileInput', file);
formData.append('replaceAndInvertOption', parameters.replaceAndInvertOption);
if (parameters.replaceAndInvertOption === 'HIGH_CONTRAST_COLOR') {
formData.append('highContrastColorCombination', parameters.highContrastColorCombination);
} else if (parameters.replaceAndInvertOption === 'CUSTOM_COLOR') {
formData.append('textColor', parameters.textColor);
formData.append('backGroundColor', parameters.backGroundColor);
}
return formData;
};
export const replaceColorOperationConfig = {
toolType: ToolType.singleFile,
buildFormData: buildReplaceColorFormData,
operationType: 'replaceColor',
endpoint: '/api/v1/misc/replace-invert-pdf',
multiFileEndpoint: false,
defaultParameters,
} as const;
export const useReplaceColorOperation = () => {
const { t } = useTranslation();
return useToolOperation<ReplaceColorParameters>({
...replaceColorOperationConfig,
getErrorMessage: createStandardErrorHandler(t('replaceColor.error.failed', 'An error occurred while processing the color replacement.'))
});
};

View File

@ -0,0 +1,29 @@
import { BaseParameters } from '../../../types/parameters';
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
export interface ReplaceColorParameters extends BaseParameters {
replaceAndInvertOption: 'HIGH_CONTRAST_COLOR' | 'CUSTOM_COLOR' | 'FULL_INVERSION';
highContrastColorCombination: 'WHITE_TEXT_ON_BLACK' | 'BLACK_TEXT_ON_WHITE' | 'YELLOW_TEXT_ON_BLACK' | 'GREEN_TEXT_ON_BLACK';
textColor: string;
backGroundColor: string;
}
export const defaultParameters: ReplaceColorParameters = {
replaceAndInvertOption: 'HIGH_CONTRAST_COLOR',
highContrastColorCombination: 'WHITE_TEXT_ON_BLACK',
textColor: '#000000',
backGroundColor: '#ffffff',
};
export type ReplaceColorParametersHook = BaseParametersHook<ReplaceColorParameters>;
export const useReplaceColorParameters = (): ReplaceColorParametersHook => {
return useBaseParameters({
defaultParameters,
endpointName: 'replace-invert-pdf',
validateFn: () => {
// All parameters are always valid as they have defaults
return true;
},
});
};

View File

@ -0,0 +1,58 @@
import { useTranslation } from "react-i18next";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import ReplaceColorSettings from "../components/tools/replaceColor/ReplaceColorSettings";
import { useReplaceColorParameters } from "../hooks/tools/replaceColor/useReplaceColorParameters";
import { useReplaceColorOperation } from "../hooks/tools/replaceColor/useReplaceColorOperation";
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
import { BaseToolProps, ToolComponent } from "../types/tool";
import { useReplaceColorTips } from "../components/tooltips/useReplaceColorTips";
const ReplaceColor = (props: BaseToolProps) => {
const { t } = useTranslation();
const replaceColorTips = useReplaceColorTips();
const base = useBaseTool(
'replaceColor',
useReplaceColorParameters,
useReplaceColorOperation,
props
);
return createToolFlow({
files: {
selectedFiles: base.selectedFiles,
isCollapsed: base.hasResults,
},
steps: [
{
title: t("replaceColor.labels.settings", "Settings"),
isCollapsed: base.settingsCollapsed,
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
tooltip: replaceColorTips,
content: (
<ReplaceColorSettings
parameters={base.params.parameters}
onParameterChange={base.params.updateParameter}
disabled={base.endpointLoading}
/>
),
},
],
executeButton: {
text: t("replace-color.submit", "Replace"),
isVisible: !base.hasResults,
loadingText: t("loading"),
onClick: base.handleExecute,
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
},
review: {
isVisible: base.hasResults,
operation: base.operation,
title: t("replace-color.title", "Replace-Invert-Color"),
onFileClick: base.handleThumbnailClick,
onUndo: base.handleUndo,
},
});
};
export default ReplaceColor as ToolComponent;

View File

@ -49,7 +49,7 @@ export const TOOL_IDS = [
'validateSignature',
'read',
'automate',
'replaceColorPdf',
'replaceColor',
'showJS',
'devApi',
'devFolderScanning',

View File

@ -76,7 +76,7 @@ export const URL_TO_TOOL_MAP: Record<string, ToolId> = {
'/extract-images': 'extractImages',
'/adjust-contrast': 'adjustContrast',
'/fake-scan': 'fakeScan',
'/replace-color-pdf': 'replaceColorPdf',
'/replace-color-pdf': 'replaceColor',
// Metadata and info
'/change-metadata': 'changeMetadata',
@ -116,7 +116,7 @@ export const URL_TO_TOOL_MAP: Record<string, ToolId> = {
'/view-pdf': 'read',
'/get-info-on-pdf': 'getPdfInfo',
'/remove-image-pdf': 'removeImage',
'/replace-and-invert-color-pdf': 'replaceColorPdf',
'/replace-and-invert-color-pdf': 'replaceColor',
'/pipeline': 'automate',
'/extract-image-scans': 'scannerImageSplit',
'/show-javascript': 'showJS',