mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-23 07:59:31 +02:00
chore: pdf compression init
This commit is contained in:
161
.idea/workspace.xml
generated
161
.idea/workspace.xml
generated
@@ -4,33 +4,17 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="chore: compress video icon">
|
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="fix: stars button width for 1k+ 😊">
|
||||||
<change afterPath="$PROJECT_DIR$/@types/theme.d.ts" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/src/pages/tools/pdf/compress-pdf/index.tsx" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/public/assets/background-dark.png" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/src/pages/tools/pdf/compress-pdf/meta.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/pages/tools/pdf/compress-pdf/service.test.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/pages/tools/pdf/compress-pdf/service.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/pages/tools/pdf/compress-pdf/types.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/App.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/App.tsx" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/Hero.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/Hero.tsx" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/Navbar/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/Navbar/index.tsx" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/index.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/ToolHeader.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/ToolHeader.tsx" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/vite.config.ts" beforeDir="false" afterPath="$PROJECT_DIR$/vite.config.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/ToolLayout.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/ToolLayout.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/allTools/ToolCard.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/allTools/ToolCard.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/examples/ExampleCard.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/examples/ExampleCard.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/input/BaseFileInput.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/input/BaseFileInput.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/input/ToolFileInput.tsx" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/input/ToolTextInput.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/input/ToolTextInput.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/options/ColorSelector.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/ColorSelector.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/options/TextFieldWithDesc.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/TextFieldWithDesc.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/options/ToolOptions.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/ToolOptions.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/result/ToolFileResult.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/result/ToolFileResult.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/result/ToolTextResult.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/result/ToolTextResult.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/config/muiConfig.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/config/muiConfig.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/home/Categories.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/home/Categories.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/home/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/home/index.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools-by-category/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools-by-category/index.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/split-pdf/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/split-pdf/index.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/video/compress/service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/video/compress/service.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/video/gif/change-speed/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/video/gif/change-speed/index.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/tsconfig.json" beforeDir="false" afterPath="$PROJECT_DIR$/tsconfig.json" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -47,7 +31,7 @@
|
|||||||
<option name="PUSH_AUTO_UPDATE" value="true" />
|
<option name="PUSH_AUTO_UPDATE" value="true" />
|
||||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||||
<map>
|
<map>
|
||||||
<entry key="$PROJECT_DIR$" value="main" />
|
<entry key="$PROJECT_DIR$" value="chesterkxng" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
@@ -144,6 +128,13 @@
|
|||||||
"number": 76
|
"number": 76
|
||||||
},
|
},
|
||||||
"lastSeen": 1743352150953
|
"lastSeen": 1743352150953
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": {
|
||||||
|
"id": "PR_kwDOMJIfts6Q0JBe",
|
||||||
|
"number": 82
|
||||||
|
},
|
||||||
|
"lastSeen": 1743470267269
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}]]></component>
|
}]]></component>
|
||||||
@@ -199,7 +190,7 @@
|
|||||||
"Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run",
|
"Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run",
|
||||||
"Vitest.replaceText function.executor": "Run",
|
"Vitest.replaceText function.executor": "Run",
|
||||||
"Vitest.timeBetweenDates.executor": "Run",
|
"Vitest.timeBetweenDates.executor": "Run",
|
||||||
"git-widget-placeholder": "dark-mode",
|
"git-widget-placeholder": "main",
|
||||||
"ignore.virus.scanning.warn.message": "true",
|
"ignore.virus.scanning.warn.message": "true",
|
||||||
"kotlin-language-version-configured": "true",
|
"kotlin-language-version-configured": "true",
|
||||||
"last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/@types",
|
"last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/@types",
|
||||||
@@ -421,54 +412,8 @@
|
|||||||
<workItem from="1743047367993" duration="986000" />
|
<workItem from="1743047367993" duration="986000" />
|
||||||
<workItem from="1743103182313" duration="4264000" />
|
<workItem from="1743103182313" duration="4264000" />
|
||||||
<workItem from="1743348610793" duration="21855000" />
|
<workItem from="1743348610793" duration="21855000" />
|
||||||
</task>
|
<workItem from="1743397561176" duration="25000" />
|
||||||
<task id="LOCAL-00127" summary="chore: show tooloptions in example">
|
<workItem from="1743458576265" duration="13083000" />
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1740619610168</created>
|
|
||||||
<option name="number" value="00127" />
|
|
||||||
<option name="presentableId" value="LOCAL-00127" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1740619610169</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00128" summary="refact: examples">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1740620866551</created>
|
|
||||||
<option name="number" value="00128" />
|
|
||||||
<option name="presentableId" value="LOCAL-00128" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1740620866551</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00129" summary="feat: json pretty">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1740661424202</created>
|
|
||||||
<option name="number" value="00129" />
|
|
||||||
<option name="presentableId" value="LOCAL-00129" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1740661424202</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00130" summary="feat: json pretty">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1740661540908</created>
|
|
||||||
<option name="number" value="00130" />
|
|
||||||
<option name="presentableId" value="LOCAL-00130" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1740661540908</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00131" summary="style: tool categories">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1740661744828</created>
|
|
||||||
<option name="number" value="00131" />
|
|
||||||
<option name="presentableId" value="LOCAL-00131" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1740661744828</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00132" summary="chore: compute only if value">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1740661864615</created>
|
|
||||||
<option name="number" value="00132" />
|
|
||||||
<option name="presentableId" value="LOCAL-00132" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1740661864615</updated>
|
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00133" summary="chore: remove prettify test">
|
<task id="LOCAL-00133" summary="chore: remove prettify test">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -814,7 +759,55 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1743355166426</updated>
|
<updated>1743355166426</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="176" />
|
<task id="LOCAL-00176" summary="fix: gif speed">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1743385388051</created>
|
||||||
|
<option name="number" value="00176" />
|
||||||
|
<option name="presentableId" value="LOCAL-00176" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1743385388051</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00177" summary="fix: tsc">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1743385467178</created>
|
||||||
|
<option name="number" value="00177" />
|
||||||
|
<option name="presentableId" value="LOCAL-00177" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1743385467178</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00178" summary="fix: background color">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1743385898871</created>
|
||||||
|
<option name="number" value="00178" />
|
||||||
|
<option name="presentableId" value="LOCAL-00178" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1743385898871</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00179" summary="docs: github trendings">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1743459110471</created>
|
||||||
|
<option name="number" value="00179" />
|
||||||
|
<option name="presentableId" value="LOCAL-00179" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1743459110471</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00180" summary="docs: optimize">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1743459205311</created>
|
||||||
|
<option name="number" value="00180" />
|
||||||
|
<option name="presentableId" value="LOCAL-00180" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1743459205311</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00181" summary="fix: stars button width for 1k+ 😊">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1743470832619</created>
|
||||||
|
<option name="number" value="00181" />
|
||||||
|
<option name="presentableId" value="LOCAL-00181" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1743470832619</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="182" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -861,12 +854,6 @@
|
|||||||
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||||
<option name="CHECK_NEW_TODO" value="false" />
|
<option name="CHECK_NEW_TODO" value="false" />
|
||||||
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
|
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
|
||||||
<MESSAGE value="style: tools height" />
|
|
||||||
<MESSAGE value="chore: update meta" />
|
|
||||||
<MESSAGE value="feat: change pgn opacity" />
|
|
||||||
<MESSAGE value="feat: crop png" />
|
|
||||||
<MESSAGE value="chore: remove unnecessary files" />
|
|
||||||
<MESSAGE value="refactor: validateJson" />
|
|
||||||
<MESSAGE value="feat: missing tools" />
|
<MESSAGE value="feat: missing tools" />
|
||||||
<MESSAGE value="refactor: use ToolContent" />
|
<MESSAGE value="refactor: use ToolContent" />
|
||||||
<MESSAGE value="fix: prettify json" />
|
<MESSAGE value="fix: prettify json" />
|
||||||
@@ -886,7 +873,13 @@
|
|||||||
<MESSAGE value="fix: typos" />
|
<MESSAGE value="fix: typos" />
|
||||||
<MESSAGE value="feat: compress video" />
|
<MESSAGE value="feat: compress video" />
|
||||||
<MESSAGE value="chore: compress video icon" />
|
<MESSAGE value="chore: compress video icon" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="chore: compress video icon" />
|
<MESSAGE value="fix: gif speed" />
|
||||||
|
<MESSAGE value="fix: tsc" />
|
||||||
|
<MESSAGE value="fix: background color" />
|
||||||
|
<MESSAGE value="docs: github trendings" />
|
||||||
|
<MESSAGE value="docs: optimize" />
|
||||||
|
<MESSAGE value="fix: stars button width for 1k+ " />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="fix: stars button width for 1k+ " />
|
||||||
</component>
|
</component>
|
||||||
<component name="XSLT-Support.FileAssociations.UIState">
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
<expand />
|
<expand />
|
||||||
|
7
package-lock.json
generated
7
package-lock.json
generated
@@ -15,6 +15,7 @@
|
|||||||
"@ffmpeg/util": "^0.12.2",
|
"@ffmpeg/util": "^0.12.2",
|
||||||
"@imgly/background-removal": "^1.6.0",
|
"@imgly/background-removal": "^1.6.0",
|
||||||
"@jimp/types": "^1.6.0",
|
"@jimp/types": "^1.6.0",
|
||||||
|
"@jspawn/ghostscript-wasm": "^0.0.2",
|
||||||
"@mui/icons-material": "^5.15.20",
|
"@mui/icons-material": "^5.15.20",
|
||||||
"@mui/material": "^5.15.20",
|
"@mui/material": "^5.15.20",
|
||||||
"@playwright/test": "^1.45.0",
|
"@playwright/test": "^1.45.0",
|
||||||
@@ -2038,6 +2039,12 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@jspawn/ghostscript-wasm": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jspawn/ghostscript-wasm/-/ghostscript-wasm-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-IhGvfXNezc+V3jyJlmjz7oxrjWPqFPcz1gqRdo0Y7EkVyFuL1A+tCRnQXx/BHQZPRvBDA+Uf0EqkvXzfMzoDcw==",
|
||||||
|
"license": "AGPL-3.0"
|
||||||
|
},
|
||||||
"node_modules/@mui/base": {
|
"node_modules/@mui/base": {
|
||||||
"version": "5.0.0-beta.40",
|
"version": "5.0.0-beta.40",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz",
|
||||||
|
@@ -32,6 +32,7 @@
|
|||||||
"@ffmpeg/util": "^0.12.2",
|
"@ffmpeg/util": "^0.12.2",
|
||||||
"@imgly/background-removal": "^1.6.0",
|
"@imgly/background-removal": "^1.6.0",
|
||||||
"@jimp/types": "^1.6.0",
|
"@jimp/types": "^1.6.0",
|
||||||
|
"@jspawn/ghostscript-wasm": "^0.0.2",
|
||||||
"@mui/icons-material": "^5.15.20",
|
"@mui/icons-material": "^5.15.20",
|
||||||
"@mui/material": "^5.15.20",
|
"@mui/material": "^5.15.20",
|
||||||
"@playwright/test": "^1.45.0",
|
"@playwright/test": "^1.45.0",
|
||||||
|
235
src/pages/tools/pdf/compress-pdf/index.tsx
Normal file
235
src/pages/tools/pdf/compress-pdf/index.tsx
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
import { Box, Typography } from '@mui/material';
|
||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import ToolContent from '@components/ToolContent';
|
||||||
|
import { ToolComponentProps } from '@tools/defineTool';
|
||||||
|
import ToolPdfInput from '@components/input/ToolPdfInput';
|
||||||
|
import ToolFileResult from '@components/result/ToolFileResult';
|
||||||
|
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||||
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
import { CompressionLevel, InitialValuesType } from './types';
|
||||||
|
import { compressPdf } from './service';
|
||||||
|
import SimpleRadio from '@components/options/SimpleRadio';
|
||||||
|
import { CustomSnackBarContext } from '../../../../contexts/CustomSnackBarContext';
|
||||||
|
|
||||||
|
const initialValues: InitialValuesType = {
|
||||||
|
compressionLevel: 'medium'
|
||||||
|
};
|
||||||
|
|
||||||
|
const exampleCards: CardExampleType<InitialValuesType>[] = [
|
||||||
|
{
|
||||||
|
title: 'Low Compression',
|
||||||
|
description: 'Slightly reduce file size with minimal quality loss',
|
||||||
|
sampleText: '',
|
||||||
|
sampleResult: '',
|
||||||
|
sampleOptions: {
|
||||||
|
compressionLevel: 'low'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Medium Compression',
|
||||||
|
description: 'Balance between file size and quality',
|
||||||
|
sampleText: '',
|
||||||
|
sampleResult: '',
|
||||||
|
sampleOptions: {
|
||||||
|
compressionLevel: 'medium'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'High Compression',
|
||||||
|
description: 'Maximum file size reduction with some quality loss',
|
||||||
|
sampleText: '',
|
||||||
|
sampleResult: '',
|
||||||
|
sampleOptions: {
|
||||||
|
compressionLevel: 'high'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function CompressPdf({
|
||||||
|
title,
|
||||||
|
longDescription
|
||||||
|
}: ToolComponentProps) {
|
||||||
|
const [input, setInput] = useState<File | null>(null);
|
||||||
|
const [result, setResult] = useState<File | null>(null);
|
||||||
|
const [resultSize, setResultSize] = useState<string>('');
|
||||||
|
const [isProcessing, setIsProcessing] = useState<boolean>(false);
|
||||||
|
const [fileInfo, setFileInfo] = useState<{
|
||||||
|
size: string;
|
||||||
|
pages: number;
|
||||||
|
} | null>(null);
|
||||||
|
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||||
|
|
||||||
|
// Get the PDF info when a file is uploaded
|
||||||
|
useEffect(() => {
|
||||||
|
const getPdfInfo = async () => {
|
||||||
|
if (!input) {
|
||||||
|
setFileInfo(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const arrayBuffer = await input.arrayBuffer();
|
||||||
|
const pdf = await PDFDocument.load(arrayBuffer);
|
||||||
|
const pages = pdf.getPageCount();
|
||||||
|
const size = formatFileSize(input.size);
|
||||||
|
|
||||||
|
setFileInfo({ size, pages });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting PDF info:', error);
|
||||||
|
setFileInfo(null);
|
||||||
|
showSnackBar(
|
||||||
|
'Error reading PDF file. Please make sure it is a valid PDF.',
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getPdfInfo();
|
||||||
|
}, [input]);
|
||||||
|
|
||||||
|
const formatFileSize = (bytes: number): string => {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
const compute = async (values: InitialValuesType, input: File | null) => {
|
||||||
|
if (!input) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsProcessing(true);
|
||||||
|
const compressedPdf = await compressPdf(input, values);
|
||||||
|
setResult(compressedPdf);
|
||||||
|
|
||||||
|
// Log compression results
|
||||||
|
const compressionRatio = (compressedPdf.size / input.size) * 100;
|
||||||
|
console.log(`Compression Ratio: ${compressionRatio.toFixed(2)}%`);
|
||||||
|
setResultSize(formatFileSize(compressedPdf.size));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error compressing PDF:', error);
|
||||||
|
showSnackBar(
|
||||||
|
`Failed to compress PDF: ${
|
||||||
|
error instanceof Error ? error.message : String(error)
|
||||||
|
}`,
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
setResult(null);
|
||||||
|
} finally {
|
||||||
|
setIsProcessing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const compressionOptions: {
|
||||||
|
value: CompressionLevel;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
value: 'low',
|
||||||
|
label: 'Low Compression',
|
||||||
|
description: 'Slightly reduce file size with minimal quality loss'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'medium',
|
||||||
|
label: 'Medium Compression',
|
||||||
|
description: 'Balance between file size and quality'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'high',
|
||||||
|
label: 'High Compression',
|
||||||
|
description: 'Maximum file size reduction with some quality loss'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolContent
|
||||||
|
title={title}
|
||||||
|
input={input}
|
||||||
|
setInput={setInput}
|
||||||
|
initialValues={initialValues}
|
||||||
|
compute={compute}
|
||||||
|
exampleCards={exampleCards}
|
||||||
|
inputComponent={
|
||||||
|
<ToolPdfInput
|
||||||
|
value={input}
|
||||||
|
onChange={setInput}
|
||||||
|
accept={['application/pdf']}
|
||||||
|
title={'Input PDF'}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
resultComponent={
|
||||||
|
<ToolFileResult
|
||||||
|
title={'Compressed PDF'}
|
||||||
|
value={result}
|
||||||
|
extension={'pdf'}
|
||||||
|
loading={isProcessing}
|
||||||
|
loadingText={'Compressing PDF'}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
getGroups={({ values, updateField }) => [
|
||||||
|
{
|
||||||
|
title: 'Compression Settings',
|
||||||
|
component: (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="subtitle2" sx={{ mb: 1 }}>
|
||||||
|
Compression Level
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{compressionOptions.map((option) => (
|
||||||
|
<SimpleRadio
|
||||||
|
key={option.value}
|
||||||
|
title={option.label}
|
||||||
|
description={option.description}
|
||||||
|
checked={values.compressionLevel === option.value}
|
||||||
|
onClick={() => {
|
||||||
|
updateField('compressionLevel', option.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{fileInfo && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
mt: 2,
|
||||||
|
p: 2,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
borderRadius: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="body2">
|
||||||
|
File size: <strong>{fileInfo.size}</strong>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Pages: <strong>{fileInfo.pages}</strong>
|
||||||
|
</Typography>
|
||||||
|
{resultSize && (
|
||||||
|
<Typography variant="body2">
|
||||||
|
Compressed file size: <strong>{resultSize}</strong>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
toolInfo={{
|
||||||
|
title: 'How to Use the Compress PDF Tool',
|
||||||
|
description: `This tool allows you to compress PDF files to reduce their size while maintaining reasonable quality.
|
||||||
|
|
||||||
|
Choose a compression level:
|
||||||
|
- Low Compression: Slightly reduces file size with minimal quality loss
|
||||||
|
- Medium Compression: Balances between file size and quality
|
||||||
|
- High Compression: Maximum file size reduction with some quality loss
|
||||||
|
|
||||||
|
Note: The compression results may vary depending on the content of your PDF. Documents with many images will typically see greater size reduction than text-only documents.
|
||||||
|
|
||||||
|
${longDescription}`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
22
src/pages/tools/pdf/compress-pdf/meta.ts
Normal file
22
src/pages/tools/pdf/compress-pdf/meta.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { defineTool } from '@tools/defineTool';
|
||||||
|
import { lazy } from 'react';
|
||||||
|
|
||||||
|
export const tool = defineTool('pdf', {
|
||||||
|
name: 'Compress PDF',
|
||||||
|
path: 'compress-pdf',
|
||||||
|
icon: 'material-symbols:compress',
|
||||||
|
description: 'Reduce PDF file size while maintaining quality',
|
||||||
|
shortDescription: 'Compress PDF files to reduce size',
|
||||||
|
keywords: [
|
||||||
|
'pdf',
|
||||||
|
'compress',
|
||||||
|
'reduce',
|
||||||
|
'size',
|
||||||
|
'optimize',
|
||||||
|
'shrink',
|
||||||
|
'file size'
|
||||||
|
],
|
||||||
|
longDescription:
|
||||||
|
'Compress PDF files to reduce their size while maintaining reasonable quality. Useful for sharing documents via email, uploading to websites, or saving storage space.',
|
||||||
|
component: lazy(() => import('./index'))
|
||||||
|
});
|
107
src/pages/tools/pdf/compress-pdf/service.test.ts
Normal file
107
src/pages/tools/pdf/compress-pdf/service.test.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { compressPdf } from './service';
|
||||||
|
import { CompressionLevel } from './types';
|
||||||
|
|
||||||
|
// Mock the mupdf module
|
||||||
|
vi.mock('mupdf', () => {
|
||||||
|
return {
|
||||||
|
Document: {
|
||||||
|
openDocument: vi.fn(() => ({
|
||||||
|
countPages: vi.fn(() => 2),
|
||||||
|
loadPage: vi.fn(() => ({}))
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
PDFWriter: vi.fn(() => ({
|
||||||
|
addPage: vi.fn(),
|
||||||
|
asBuffer: vi.fn(() => Buffer.from('test'))
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock the pdf-lib module
|
||||||
|
vi.mock('pdf-lib', () => {
|
||||||
|
return {
|
||||||
|
PDFDocument: {
|
||||||
|
load: vi.fn(() => ({
|
||||||
|
getPageCount: vi.fn(() => 2)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compressPdf', () => {
|
||||||
|
it('should compress a PDF file with low compression', async () => {
|
||||||
|
// Create a mock File
|
||||||
|
const mockFile = new File(['test'], 'test.pdf', {
|
||||||
|
type: 'application/pdf'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock arrayBuffer method
|
||||||
|
mockFile.arrayBuffer = vi.fn().mockResolvedValue(new ArrayBuffer(4));
|
||||||
|
|
||||||
|
// Call the function with low compression
|
||||||
|
const result = await compressPdf(mockFile, {
|
||||||
|
compressionLevel: 'low' as CompressionLevel
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check the result
|
||||||
|
expect(result).toBeInstanceOf(File);
|
||||||
|
expect(result.name).toBe('test-compressed.pdf');
|
||||||
|
expect(result.type).toBe('application/pdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should compress a PDF file with medium compression', async () => {
|
||||||
|
// Create a mock File
|
||||||
|
const mockFile = new File(['test'], 'test.pdf', {
|
||||||
|
type: 'application/pdf'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock arrayBuffer method
|
||||||
|
mockFile.arrayBuffer = vi.fn().mockResolvedValue(new ArrayBuffer(4));
|
||||||
|
|
||||||
|
// Call the function with medium compression
|
||||||
|
const result = await compressPdf(mockFile, {
|
||||||
|
compressionLevel: 'medium' as CompressionLevel
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check the result
|
||||||
|
expect(result).toBeInstanceOf(File);
|
||||||
|
expect(result.name).toBe('test-compressed.pdf');
|
||||||
|
expect(result.type).toBe('application/pdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should compress a PDF file with high compression', async () => {
|
||||||
|
// Create a mock File
|
||||||
|
const mockFile = new File(['test'], 'test.pdf', {
|
||||||
|
type: 'application/pdf'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock arrayBuffer method
|
||||||
|
mockFile.arrayBuffer = vi.fn().mockResolvedValue(new ArrayBuffer(4));
|
||||||
|
|
||||||
|
// Call the function with high compression
|
||||||
|
const result = await compressPdf(mockFile, {
|
||||||
|
compressionLevel: 'high' as CompressionLevel
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check the result
|
||||||
|
expect(result).toBeInstanceOf(File);
|
||||||
|
expect(result.name).toBe('test-compressed.pdf');
|
||||||
|
expect(result.type).toBe('application/pdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle errors during compression', async () => {
|
||||||
|
// Create a mock File
|
||||||
|
const mockFile = new File(['test'], 'test.pdf', {
|
||||||
|
type: 'application/pdf'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock arrayBuffer method to throw an error
|
||||||
|
mockFile.arrayBuffer = vi.fn().mockRejectedValue(new Error('Test error'));
|
||||||
|
|
||||||
|
// Check that the function throws an error
|
||||||
|
await expect(
|
||||||
|
compressPdf(mockFile, { compressionLevel: 'medium' as CompressionLevel })
|
||||||
|
).rejects.toThrow('Failed to compress PDF: Test error');
|
||||||
|
});
|
||||||
|
});
|
59
src/pages/tools/pdf/compress-pdf/service.ts
Normal file
59
src/pages/tools/pdf/compress-pdf/service.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { CompressionLevel, InitialValuesType } from './types';
|
||||||
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
|
||||||
|
export async function compressPdf(
|
||||||
|
pdfFile: File,
|
||||||
|
options: InitialValuesType
|
||||||
|
): Promise<File> {
|
||||||
|
// Check if file is a PDF
|
||||||
|
if (pdfFile.type !== 'application/pdf') {
|
||||||
|
throw new Error('The provided file is not a PDF');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the file as an ArrayBuffer
|
||||||
|
const arrayBuffer = await pdfFile.arrayBuffer();
|
||||||
|
|
||||||
|
// Load PDF document using pdf-lib
|
||||||
|
const pdfDoc = await PDFDocument.load(arrayBuffer);
|
||||||
|
|
||||||
|
// Apply compression based on the selected level
|
||||||
|
const compressionOptions = getCompressionOptions(options.compressionLevel);
|
||||||
|
|
||||||
|
// pdf-lib has different compression approach than mupdf
|
||||||
|
// Compression is applied during the save operation
|
||||||
|
const compressedPdfBytes = await pdfDoc.save({
|
||||||
|
useObjectStreams: true, // More efficient storage
|
||||||
|
...compressionOptions
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a new File object with the compressed PDF
|
||||||
|
return new File([compressedPdfBytes], `compressed_${pdfFile.name}`, {
|
||||||
|
type: 'application/pdf'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to get compression options based on level
|
||||||
|
* @param level - Compression level (low, medium, or high)
|
||||||
|
* @returns Object with appropriate compression settings for pdf-lib
|
||||||
|
*/
|
||||||
|
function getCompressionOptions(level: CompressionLevel) {
|
||||||
|
switch (level) {
|
||||||
|
case 'low':
|
||||||
|
return {
|
||||||
|
addDefaultPage: false,
|
||||||
|
compress: true
|
||||||
|
};
|
||||||
|
case 'medium':
|
||||||
|
return {
|
||||||
|
addDefaultPage: false,
|
||||||
|
compress: true
|
||||||
|
};
|
||||||
|
case 'high':
|
||||||
|
return {
|
||||||
|
addDefaultPage: false,
|
||||||
|
compress: true,
|
||||||
|
objectsPerTick: 100 // Process more objects at once for higher compression
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
5
src/pages/tools/pdf/compress-pdf/types.ts
Normal file
5
src/pages/tools/pdf/compress-pdf/types.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export type CompressionLevel = 'low' | 'medium' | 'high';
|
||||||
|
|
||||||
|
export type InitialValuesType = {
|
||||||
|
compressionLevel: CompressionLevel;
|
||||||
|
};
|
@@ -1,5 +1,10 @@
|
|||||||
import { tool as pdfRotatePdf } from './rotate-pdf/meta';
|
import { tool as pdfRotatePdf } from './rotate-pdf/meta';
|
||||||
import { meta as splitPdfMeta } from './split-pdf/meta';
|
import { meta as splitPdfMeta } from './split-pdf/meta';
|
||||||
|
import { tool as compressPdfTool } from './compress-pdf/meta';
|
||||||
import { DefinedTool } from '@tools/defineTool';
|
import { DefinedTool } from '@tools/defineTool';
|
||||||
|
|
||||||
export const pdfTools: DefinedTool[] = [splitPdfMeta, pdfRotatePdf];
|
export const pdfTools: DefinedTool[] = [
|
||||||
|
splitPdfMeta,
|
||||||
|
pdfRotatePdf,
|
||||||
|
compressPdfTool
|
||||||
|
];
|
||||||
|
Reference in New Issue
Block a user