+
{children}
)
diff --git a/src/components/fileInfoDetails.tsx b/src/components/fileInfoDetails.tsx
index 3d2dc46..344f783 100644
--- a/src/components/fileInfoDetails.tsx
+++ b/src/components/fileInfoDetails.tsx
@@ -1,5 +1,4 @@
import React, { useState, useCallback } from 'react'
-import type { FileInfo } from 'utils/PdfCpu'
const FileInfoDetails = (
{ className, fileInfo }:
diff --git a/src/components/footer.tsx b/src/components/footer.tsx
index dc369a3..d5b3323 100644
--- a/src/components/footer.tsx
+++ b/src/components/footer.tsx
@@ -6,7 +6,7 @@ const Footer = () => {
diff --git a/src/components/header.tsx b/src/components/header.tsx
index 031c706..e083ab3 100644
--- a/src/components/header.tsx
+++ b/src/components/header.tsx
@@ -7,7 +7,7 @@ const Header = () => {
diff --git a/src/components/pdf/edit/EditableBox.tsx b/src/components/pdf/edit/EditableBox.tsx
new file mode 100644
index 0000000..bb64446
--- /dev/null
+++ b/src/components/pdf/edit/EditableBox.tsx
@@ -0,0 +1,51 @@
+import React, { useCallback } from 'react'
+import { DraggableData, Rnd } from 'react-rnd'
+import type { Props as RndProps } from 'react-rnd'
+
+const Dot = () => (
+
+)
+
+type Props = EditableBox & {
+ bounds: RndProps[`bounds`],
+ updateBoxSize: (id: EditableObject[`id`], { width, height }: EditableBox[`size`]) => void,
+ updateBoxPosition: (id: EditableObject[`id`], { x, y }: EditableBox[`position`]) => void,
+}
+
+const EditableBox: React.FC
= ({
+ id,
+ bounds,
+ size,
+ position,
+ updateBoxSize,
+ updateBoxPosition,
+ selected,
+}) => {
+ const onResizeStop = useCallback((_, __, ref: React.ElementRef<`div`>) => {
+ const { offsetWidth: width, offsetHeight: height } = ref
+ updateBoxSize(id, { height, width })
+ }, [id, updateBoxSize])
+ const onDragStop = useCallback((_, data: DraggableData) => {
+ const { x, y } = data
+ updateBoxPosition(id, { x, y })
+ }, [id, updateBoxPosition])
+
+ return (
+ ,
+ bottomRight: ,
+ topLeft: ,
+ topRight: ,
+ } : undefined}
+ />
+ )
+}
+
+export default EditableBox
\ No newline at end of file
diff --git a/src/components/PdfViewer.tsx b/src/components/pdf/edit/PdfViewer.tsx
similarity index 82%
rename from src/components/PdfViewer.tsx
rename to src/components/pdf/edit/PdfViewer.tsx
index 27941f3..aad2d43 100644
--- a/src/components/PdfViewer.tsx
+++ b/src/components/pdf/edit/PdfViewer.tsx
@@ -2,11 +2,13 @@ import React, { useCallback } from 'react'
import { Document } from 'react-pdf/dist/esm/entry.webpack5'
import usePdfViewerZoom from 'hooks/usePdfViewerZoom'
import type { DocumentProps, PDFPageProxy } from 'react-pdf'
-import MiniPage from 'components/pdf/edit/miniPage'
import MainPage from 'components/pdf/edit/mainPage'
import DocumentBottomBar from 'components/pdf/edit/documentBottomBar'
import usePdfViewerScroll from 'hooks/usePdfViewerScroll'
import LeftSideBar from 'components/pdf/edit/leftSidebar'
+import RightSideBar from './rightSidebar'
+import EditableContent from './editableContent'
+import useEditableContent from 'hooks/useEditableContent'
interface Props {
file: File,
@@ -22,6 +24,7 @@ interface Props {
documentRef: React.RefObject,
setPage: (pageIndex: number, action: (p: PageInfo) => PageInfo) => void,
zoomFullWidth: (pageWidth?: number) => void,
+ editableContentProps: ReturnType,
}
const PdfViewer: React.FC = ({
@@ -38,6 +41,7 @@ const PdfViewer: React.FC = ({
scale,
setPage,
zoomFullWidth,
+ editableContentProps: { addBox, ...editableContentProps},
}) => {
const {
zoom,
@@ -65,11 +69,12 @@ const PdfViewer: React.FC = ({
rotatePage={rotatePage}
numPages={numPages}
movePage={movePage}
+ addBox={addBox}
/>
-
+
= ({
})
)
}
+
= ({
zoomFullWidth={zoomFullWidth}
/>
-
- {
- (Array.isArray(pageOrder) && numPages > 0) && (
- pageOrder.map(({ pageNumber, rotation }, index) => {
- return (
-
- )
- })
- )
- }
-
+ pageIndex={pageIndex}
+ pageOrder={pageOrder}
+ selectPage={selectPage}
+ />
)
}
diff --git a/src/components/pdf/edit/editableContent.tsx b/src/components/pdf/edit/editableContent.tsx
new file mode 100644
index 0000000..a8b9ef0
--- /dev/null
+++ b/src/components/pdf/edit/editableContent.tsx
@@ -0,0 +1,42 @@
+import React from 'react'
+import type { Props as RndProps } from 'react-rnd'
+import EditableBox from './EditableBox'
+
+type Props = {
+ bounds?: RndProps[`bounds`],
+ editableContent: EditableContent[],
+ updateBoxSize: (id: EditableObject[`id`], { width, height }: EditableBox[`size`]) => void,
+ updateBoxPosition: (id: EditableObject[`id`], { x, y }: EditableBox[`position`]) => void,
+}
+
+const EditableContent: React.FC = ({
+ bounds,
+ editableContent,
+ updateBoxSize,
+ updateBoxPosition,
+}) => {
+
+ return (
+ <>
+ {
+ editableContent.map((data) => {
+ const { id, type } = data
+ if(type === `box`){
+ return (
+
+ )
+ }
+ return null
+ })
+ }
+ >
+ )
+}
+
+export default EditableContent
\ No newline at end of file
diff --git a/src/components/pdf/edit/leftSidebar.tsx b/src/components/pdf/edit/leftSidebar.tsx
index bbf7054..b358f7c 100644
--- a/src/components/pdf/edit/leftSidebar.tsx
+++ b/src/components/pdf/edit/leftSidebar.tsx
@@ -1,6 +1,6 @@
import React, { useCallback } from 'react'
import { PrimaryButton } from 'components/button'
-import OptimiseButton from 'components/pdf/view/optimiseButton'
+import OptimiseButton from 'components/pdf/edit/optimiseButton'
const SectionTitle = ({ children }) => {
return {children}
@@ -12,9 +12,11 @@ type Props = {
rotatePage: (pageIndex: number) => void,
removeCurrentPage: () => void,
movePage: (pageIndex: number, newPageIndex: number) => void,
+ addBox: () => void,
}
const LeftSideBar: React.FC = ({
+ addBox,
movePage,
numPages,
pageIndex,
@@ -47,18 +49,33 @@ const LeftSideBar: React.FC = ({
}, [pageIndex, numPages, movePage])
return (
-
+
Page
-
- 🔃 Rotate
+
+ 🔃 Rotate
+
+
+ 🗑️ Delete
+
+
+ ↕️ Move
+
+
+
+
+
+
Edit Content
+
+
+ ✒️ Text
-
- 🗑️ Delete
+
+ ⬜ Box
-
- ↕️ Move
+
+ 🖼️ Image
diff --git a/src/components/pdf/view/optimiseButton.tsx b/src/components/pdf/edit/optimiseButton.tsx
similarity index 97%
rename from src/components/pdf/view/optimiseButton.tsx
rename to src/components/pdf/edit/optimiseButton.tsx
index 250c618..b085c90 100644
--- a/src/components/pdf/view/optimiseButton.tsx
+++ b/src/components/pdf/edit/optimiseButton.tsx
@@ -26,7 +26,7 @@ const OptimiseButton: React.FC = () => {
}
title="Reduce the size of your document"
>
- 🪄 Optimise
+ 🪄 Reduce file size
)
}
diff --git a/src/components/pdf/edit/rightSidebar.tsx b/src/components/pdf/edit/rightSidebar.tsx
new file mode 100644
index 0000000..4152d56
--- /dev/null
+++ b/src/components/pdf/edit/rightSidebar.tsx
@@ -0,0 +1,38 @@
+import React from 'react'
+import { Document } from 'react-pdf/dist/esm/entry.webpack5'
+import MiniPage from 'components/pdf/edit/miniPage'
+
+type Props = {
+ file: File,
+ pageOrder: PageInfo[],
+ pageIndex: number,
+ selectPage: (pageIndex: number) => void,
+}
+
+const RightSideBar: React.FC
= ({ file, pageOrder, pageIndex, selectPage }) => {
+ const numPages = pageOrder.length
+ return (
+
+ {
+ (Array.isArray(pageOrder) && numPages > 0) && (
+ pageOrder.map(({ pageNumber, rotation }, index) => {
+ return (
+
+ )
+ })
+ )
+ }
+
+ )
+}
+
+export default RightSideBar
\ No newline at end of file
diff --git a/src/components/pdf/view/decryptButton.tsx b/src/components/pdf/view/decryptButton.tsx
index 989bb6c..5a16afe 100644
--- a/src/components/pdf/view/decryptButton.tsx
+++ b/src/components/pdf/view/decryptButton.tsx
@@ -2,7 +2,7 @@ import React from 'react'
import useDecryptPdf from 'hooks/useDecryptPdf'
import { PrimaryButton } from 'components/button'
import Spinner from 'components/spinner'
-import { FileInfo } from 'utils/PdfCpu'
+import { ErrorAdmonition } from 'components/admonition'
type Props = {
file: File,
@@ -12,38 +12,53 @@ type Props = {
const DecryptButton: React.FC = ({ file, fileInfo, reloadFile }) => {
const {
+ error,
decryptPdf,
decrypting,
- decryptedResult,
- } = useDecryptPdf({ file, reloadFile })
+ decryptResult,
+ } = useDecryptPdf({ file, fileInfo, reloadFile })
- if(fileInfo.encrypted === false){
+ if(fileInfo.encrypted === false && fileInfo.restricted === false){
return null
}
- if(decryptedResult === null) {
+ if(decryptResult === null) {
return (
-
- Decrypting...
- >
- }
- title="Remove edit restrictions"
- >
- 🔓 Remove restrictions
-
+ <>
+ {error.length > 0 ? (
+
+ ❌ Error
+ {error}
+
+ ) : null}
+
+
+ {fileInfo.encrypted === true
+ ? `Decrypting...`
+ : `Removing restrictions...`
+ }
+
+ >
+ }
+ title={fileInfo.encrypted === true ? `Decrypt PDF`: `Remove restrictions from PDF`}
+ >
+ 🔓 {fileInfo.encrypted === true ? `Decrypt` : `Remove restrictions`}
+
+ >
)
}
return (
-
+
💾
-
Save decrypted file
+
Save file
diff --git a/src/hooks/useDecryptPdf.ts b/src/hooks/useDecryptPdf.ts
index 77189dc..1a4bdec 100644
--- a/src/hooks/useDecryptPdf.ts
+++ b/src/hooks/useDecryptPdf.ts
@@ -1,43 +1,55 @@
import { useCallback, useState } from "react"
import Logger from "utils/Logger"
-import PdfCpu from 'utils/PdfCpu'
+import PdfCpu, { EncryptedPDFError, InvalidPDFError } from 'utils/PdfCpu'
import Storage from 'utils/Storage'
-interface DecryptedResult {
- url: string,
- fileName: string,
-}
-
-const useDecryptPdf = ({ file, reloadFile }: { file: File, reloadFile: () => void }) => {
+const useDecryptPdf = (
+ { file, fileInfo, reloadFile }:
+ { file: File, fileInfo: FileInfo, reloadFile: () => void }
+) => {
const [decrypting, setDecrypting] = useState(false)
- const [decryptedResult, setDecryptedResult] = useState(null as DecryptedResult)
+ const [decryptResult, setDecryptResult] = useState(null as ProcessFileResult)
+ const [error, setError] = useState(``)
+
const decryptPdf = useCallback(async () => {
setDecrypting(true)
const arrayBuffer = await file.arrayBuffer()
const filePath = `/${file.name}`
globalThis.fs.writeFileSync(filePath, Buffer.from(arrayBuffer))
+ let password
+ if(fileInfo.encrypted){
+ password = window.prompt(`This PDF is encrypted with a password. Please enter the password to decrypt it: `)
+ }
try {
- await PdfCpu.decrypt(filePath)
- const outBuffer: Buffer = globalThis.fs.readFileSync(filePath)
- const blob = new Blob([outBuffer])
- const f = new File([outBuffer], file.name)
- Storage.setFile(f)
- setDecryptedResult({
- fileName: file.name,
- url: URL.createObjectURL(blob),
- })
- reloadFile()
+ await PdfCpu.decrypt(filePath, password)
} catch (error) {
- Logger.error(error)
- } finally {
setDecrypting(false)
+ Logger.error(`decryptPdf`, error)
+ if (error instanceof EncryptedPDFError) {
+ return setError(`The password was incorrect or the PDF is invalid.`)
+ } else if(error instanceof InvalidPDFError) {
+ return setError(error.message)
+ } else {
+ return setError(`Sorry, something went wrong when processing the PDF`)
+ }
}
- }, [file, reloadFile])
+
+ const outBuffer: Buffer = globalThis.fs.readFileSync(filePath)
+ const blob = new Blob([outBuffer])
+ const f = new File([outBuffer], file.name)
+ Storage.setFile(f)
+ setDecryptResult({
+ fileName: file.name,
+ url: URL.createObjectURL(blob),
+ })
+ reloadFile()
+ }, [file, reloadFile, fileInfo])
return {
decryptPdf,
- decryptedResult,
+ decryptResult,
decrypting,
+ error,
}
}
diff --git a/src/hooks/useEditableContent.ts b/src/hooks/useEditableContent.ts
new file mode 100644
index 0000000..e0a4e1e
--- /dev/null
+++ b/src/hooks/useEditableContent.ts
@@ -0,0 +1,53 @@
+import { useCallback, useState } from 'react'
+import type { DraggableData } from 'react-rnd'
+import { getRandomID } from 'utils/Utils'
+
+const defaultBox: EditableBox = {
+ id: ``,
+ position: {
+ x: 0,
+ y: 0,
+ },
+ selected: false,
+ size: {
+ height: 100,
+ width: 100,
+ },
+ type: `box`,
+}
+
+const useEditableContent = () => {
+ const [editableContent, setEditableContent] = useState([])
+
+ const addBox = useCallback(() => {
+ setEditableContent(c => [
+ ...c,
+ {
+ ...defaultBox,
+ id: getRandomID(),
+ },
+ ])
+ }, [])
+
+ const updateBoxSize = useCallback((id: EditableObject[`id`], { width, height }: EditableBox[`size`]) => {
+ setEditableContent(c => c.map(box => (box.id === id ? { ...box, size: { ...box.size, height, width } } : box)))
+ }, [])
+
+ const updateBoxPosition = useCallback((id: EditableObject[`id`], { x, y }: DraggableData) => {
+ setEditableContent(c => c.map(box => (box.id === id ? { ...box, position: { x, y } } : box)))
+ }, [])
+
+ const selectBox = useCallback((id: EditableObject[`id`]) => {
+ setEditableContent(c => c.map(box => (box.id === id ? { ...box, selected: true } : box)))
+ }, [])
+
+ return {
+ addBox,
+ editableContent,
+ selectBox,
+ updateBoxPosition,
+ updateBoxSize,
+ }
+}
+
+export default useEditableContent
\ No newline at end of file
diff --git a/src/hooks/usePdfInfo.ts b/src/hooks/usePdfInfo.ts
index d60b53e..e6c013b 100644
--- a/src/hooks/usePdfInfo.ts
+++ b/src/hooks/usePdfInfo.ts
@@ -1,7 +1,6 @@
import { useEffect, useState, useCallback } from 'react'
import { useRouter } from 'next/router'
-import type { FileInfo } from 'utils/PdfCpu'
-import PdfCpu from 'utils/PdfCpu'
+import PdfCpu, { EncryptedPDFError, InvalidPDFError, RestrictedPDFError } from 'utils/PdfCpu'
import Storage from 'utils/Storage'
import { downloadBlob } from 'utils/Utils'
import Logger from 'utils/Logger'
@@ -37,10 +36,18 @@ const usePdfInfo = ({ file, wasmLoaded }: { file: File, wasmLoaded: boolean }) =
const result = await PdfCpu.getInfo(filePath)
setFileInfo(result)
} catch (error){
- if(error.message === `File is encrypted`){
- setFileInfo({
+ if(error instanceof EncryptedPDFError) {
+ setFileInfo(r => ({
+ ...r,
encrypted: true,
- })
+ }))
+ } else if (error instanceof RestrictedPDFError) {
+ setFileInfo(r => ({
+ ...r,
+ restricted: true,
+ }))
+ } else if(error instanceof InvalidPDFError) {
+ return setError(error.message)
} else {
Logger.error(error)
setError(`Something went wrong when processing your file`)
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index e85fb98..2ef6fb8 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -6,7 +6,7 @@ import PdfCpu from 'utils/PdfCpu'
import 'styles/globals.css'
-const DocsTogether = ({ Component, pageProps }: AppProps) => {
+const DocDocGoose = ({ Component, pageProps }: AppProps) => {
const [wasmLoaded, setWasmLoaded] = useState(false)
useEffect(() => {
@@ -18,7 +18,7 @@ const DocsTogether = ({ Component, pageProps }: AppProps) => {
return (
<>
- 📄 DocsTogether
+ 📄 DocDocGoose
{
>
)
}
-export default DocsTogether
+export default DocDocGoose
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index a063992..d5e90d0 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -7,12 +7,16 @@ import Storage from 'utils/Storage'
import { useRouter } from 'next/router'
import Spinner from 'components/spinner'
import { ContentTypes } from 'utils/Constants'
+import { GeneralAdmonition } from 'components/admonition'
+import SEO from 'components/SEO'
const Feature = ({ title, content }) => {
return (
{title}
- {content}
+ {typeof content === `string` ? (
+ {content}
+ ) : content}
)
}
@@ -47,43 +51,57 @@ const Home: NextPage = () => {
}, [router])
return (
-
-
-
-
-
-
Edit documents
directly in your browser
-
-
- {dropped ? (
-
-
Loading...
+ <>
+
+
+
+
+
+
+
Edit documents
directly in your browser
+
+
+ No password required.
+
+ - Remove restrictions on editing or highlighting in PDF or DOCX files
+ - Unlock track changes on DOCX files
+
+ >
+ }
+ />
+
+
+
- ) : (
-
- )}
+ {dropped ? (
+
+ Loading...
+
+ ) : (
+
+ )}
+
+
+
+ 💻 Private and secure
+
+ Your documents are processed on your computer, not on the cloud. Documents are never uploaded to a remote server. No information about your documents is stored or transferred outside of your computer.
+
+
+
+
-
-
-
+ >
)
}
diff --git a/src/pages/pdf/edit.tsx b/src/pages/pdf/edit.tsx
index db74896..3d18983 100644
--- a/src/pages/pdf/edit.tsx
+++ b/src/pages/pdf/edit.tsx
@@ -9,8 +9,9 @@ import useEditPdf from 'hooks/useEditPdf'
import usePdfViewerPage from 'hooks/usePdfViewerPage'
import Head from 'next/head'
import { useRouter } from 'next/router'
+import useEditableContent from 'hooks/useEditableContent'
-const PdfViewer = dynamic(() => import(`components/PdfViewer`), { ssr: false })
+const PdfViewer = dynamic(() => import(`components/pdf/edit/PdfViewer`), { ssr: false })
interface Props {
wasmLoaded: boolean
@@ -28,6 +29,7 @@ const Edit: NextPage
= () => {
const pageProps = usePdfViewerPage()
+ const editableContentProps = useEditableContent()
const {
saveFile,
editing,
@@ -36,7 +38,7 @@ const Edit: NextPage = () => {
return (
<>
- {file?.name} | 📄 DocsTogether
+ {file?.name} | 📄 DocDocGoose
@@ -68,6 +70,7 @@ const Edit: NextPage
= () => {
}
{...pageProps}
+ {...editableContentProps}
/>
>
diff --git a/src/pages/pdf/info.tsx b/src/pages/pdf/info.tsx
index 23218d9..098a023 100644
--- a/src/pages/pdf/info.tsx
+++ b/src/pages/pdf/info.tsx
@@ -32,7 +32,7 @@ const Info: NextPage = ({ wasmLoaded }) => {
} = usePdfInfo({ file, wasmLoaded })
useEffect(() => {
- if(fileInfo.encrypted){
+ if(fileInfo.encrypted || fileInfo.restricted){
router.replace(`/pdf/view`)
}
}, [fileInfo, router])
diff --git a/src/pages/pdf/view.tsx b/src/pages/pdf/view.tsx
index 84f31ab..2dcab9d 100644
--- a/src/pages/pdf/view.tsx
+++ b/src/pages/pdf/view.tsx
@@ -9,7 +9,7 @@ import { humanFileSize } from 'utils/Utils'
import usePdfInfo from 'hooks/usePdfInfo'
import FileInfoDetails from 'components/fileInfoDetails'
import Spinner from 'components/spinner'
-import { InfoAdmonition } from 'components/admonition'
+import { ErrorAdmonition, InfoAdmonition } from 'components/admonition'
import { useRouter } from 'next/router'
import DecryptButton from 'components/pdf/view/decryptButton'
import FileAttribute from 'components/FileAttribute'
@@ -36,7 +36,7 @@ const View: NextPage = ({ wasmLoaded }) => {
} = usePdfInfo({ file, wasmLoaded })
useEffect(() => {
- if(file && fileInfo.encrypted === false){
+ if(file && fileInfo.encrypted === false && fileInfo.restricted === false){
router.replace(`/pdf/edit`)
}
}, [file, fileInfo, router])
@@ -69,6 +69,8 @@ const View: NextPage = ({ wasmLoaded }) => {
{fileInfo.pageCount} pages}
{typeof fileInfo.encrypted !== `undefined` &&
{fileInfo.encrypted ? `Encrypted` : `Not encrypted`}}
+ {typeof fileInfo.restricted !== `undefined` &&
+ {fileInfo.restricted ? `Has restrictions` : `No restrictions`}}
@@ -76,19 +78,29 @@ const View: NextPage = ({ wasmLoaded }) => {
className="mt-2"
fileInfo={fileInfo}
/>
-
+ {error !== null ? (
+
+ ❌ Error
+ {error}
+
+ ) : (
-
+ )}
{
- fileInfo.encrypted !== false
+ fileInfo.encrypted === true
? (
+
+ 🔒 Encrypted
+ This PDF is encrypted and must be decrypted before it can be edited
+
+ ) : fileInfo.restricted === true ? (
🔒 Restrictions
- There are restrictions on this document that must be removed before the document can be edited.
+ There are restrictions on this PDF that must be removed before the document can be edited.
) : null
}
@@ -96,11 +108,6 @@ const View: NextPage = ({ wasmLoaded }) => {
) : (
Processing file
)}
- {error !== null ? (
-
- ❌ {error}
-
- ) : null}
diff --git a/src/types.d.ts b/src/types.d.ts
index 7d6bc16..a613655 100644
--- a/src/types.d.ts
+++ b/src/types.d.ts
@@ -8,4 +8,53 @@ type PageInfo = {
originalHeight: number,
height: number,
originalWidth: number,
-}
\ No newline at end of file
+}
+
+type EditableObject = {
+ id: string,
+ selected: boolean
+}
+
+type EditableBox = EditableObject & {
+ type: `box`,
+ size: {
+ height: number,
+ width: number,
+ },
+ position: {
+ x: number,
+ y: number,
+ },
+}
+
+type EditableContent = EditableBox
+
+type FileInfo = {
+ pdfVersion?: string,
+ pageCount?: number,
+ pageSize?: string,
+ title?: string,
+ author?: string,
+ subject?: string,
+ pdfProducer?: string,
+ contentCreator?: string,
+ creationDate?: string,
+ modificationDate?: string,
+ properties?: string,
+ tagged?: boolean,
+ hybrid?: boolean,
+ linearized?: boolean,
+ xrefStreams?: boolean,
+ objectStreams?: boolean,
+ watermarked?: boolean,
+ thumbnails?: boolean,
+ acroform?: boolean,
+ encrypted?: boolean,
+ restricted?: boolean,
+ permissions?: string,
+}
+
+type ProcessFileResult = {
+ url: string,
+ fileName: string,
+}
diff --git a/src/utils/PdfCpu.ts b/src/utils/PdfCpu.ts
index 3f0f21a..c87c8b3 100644
--- a/src/utils/PdfCpu.ts
+++ b/src/utils/PdfCpu.ts
@@ -167,30 +167,6 @@ const setGo = (value: any) => {
go = value
}
-export type FileInfo = {
- pdfVersion?: string,
- pageCount?: number,
- pageSize?: string,
- title?: string,
- author?: string,
- subject?: string,
- pdfProducer?: string,
- contentCreator?: string,
- creationDate?: string,
- modificationDate?: string,
- properties?: string,
- tagged?: boolean,
- hybrid?: boolean,
- linearized?: boolean,
- xrefStreams?: boolean,
- objectStreams?: boolean,
- watermarked?: boolean,
- thumbnails?: boolean,
- acroform?: boolean,
- encrypted?: boolean,
- permissions?: string,
-}
-
const PDF_PROPERTIES = {
acroform: `Acroform`,
author: `Author`,
@@ -215,6 +191,35 @@ const PDF_PROPERTIES = {
xrefStreams: `Using XRef streams`,
}
+export class EncryptedPDFError extends Error {
+ constructor(message: string) {
+ super(message)
+ this.name = `EncryptedPDFError`
+ }
+}
+
+export class RestrictedPDFError extends Error {
+ constructor(message: string) {
+ super(message)
+ this.name = `RestrictedPDFError`
+ }
+}
+
+export class InvalidPDFError extends Error {
+ constructor(message: string) {
+ super(message)
+ this.name = `InvalidPDFError`
+ }
+}
+
+const hasEncryptedPDFError = (stderr: string[]) => stderr.some(l =>
+ l.includes(`pdfcpu: please provide the correct password`)
+)
+
+const hasInvalidPDFError = (stderr: string[]) => stderr.some(l =>
+ l.includes(`Read: xRefTable failed: pdfcpu: headerVersion: corrupt pdf stream - no header version available`)
+)
+
const getInfo = async (filePath: string): Promise => {
const {
exitCode,
@@ -224,10 +229,16 @@ const getInfo = async (filePath: string): Promise => {
if(exitCode === 1 || exitCode === 2){
throw new Error(stderr.join(`\n`))
+ } else if (hasEncryptedPDFError(stderr)) {
+ throw new EncryptedPDFError(`File is encrypted with both owner and user password set`)
} else if (stderr.some(l => l.includes(`decryptAESBytes: Ciphertext not a multiple of block size`))){
- throw new Error(`File is encrypted`)
+ throw new RestrictedPDFError(`File is restricted`)
+ } else if (hasInvalidPDFError(stderr)) {
+ throw new InvalidPDFError(`PDF does not conform to the PDF specification and cannot be read. However, it may be viewable in other PDF viewers.`)
}
- let info = {} as FileInfo
+ let info = {
+ encrypted: false,
+ } as FileInfo
const booleanise = (val) => {
if(val === `Yes`){
@@ -343,7 +354,9 @@ const getInfo = async (filePath: string): Promise => {
break
}
case PDF_PROPERTIES.encrypted: {
- info.encrypted = booleanise(data)
+ // if it can be read, it is restricted not encrypted
+ // in DocDocGoose terminology
+ info.restricted = booleanise(data)
break
}
case PDF_PROPERTIES.permissions: {
@@ -387,9 +400,16 @@ const decrypt = async (filePath: string, userPassword?: string) => {
...(userPassword ? [`-upw`, userPassword] : []),
filePath,
])
+
if(exitCode === 1 || exitCode === 2){
throw new Error(stderr.join(`\n`))
}
+
+ if (hasEncryptedPDFError(stderr)) {
+ throw new EncryptedPDFError(`PDF remains encrypted, password provided may have been invalid`)
+ } else if (hasInvalidPDFError(stderr)) {
+ throw new InvalidPDFError(`This PDF does not conform to the PDF specification and cannot be read. However, it may be viewable in other PDF viewers.`)
+ }
return {
exitCode,
stderr,
diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts
index 5651c8f..d18ee94 100644
--- a/src/utils/Utils.ts
+++ b/src/utils/Utils.ts
@@ -42,4 +42,6 @@ export const moveArrayItem = (array: T[], fromIndex: number, toIndex: number)
copy.splice(fromIndex, 1)
copy.splice(toIndex, 0, el)
return copy
-}
\ No newline at end of file
+}
+
+export const getRandomID = () => `id${(new Date()).getTime()}${Math.random().toString(16).slice(2)}`
\ No newline at end of file