├── .eslintrc.json ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── api │ └── hello.js ├── guide.js └── index.js ├── postcss.config.js ├── public ├── darkNavLogo.png ├── lightNavLogo.png ├── logo.png ├── navLogo.png ├── note │ ├── note1.png │ ├── note2.png │ ├── note3.png │ ├── note4.png │ └── note5.png └── robots.txt ├── src ├── components │ ├── ExportPopup.js │ ├── FileUpload.js │ ├── Guide.js │ ├── Header.js │ ├── Icon.js │ ├── Loader.js │ └── SideBar.js └── context │ └── CanvasContext.js ├── styles ├── Home.module.css └── globals.css ├── tailwind.config.js └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. 16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | 19 | ## Code of Conduct 20 | 21 | ### Our Pledge 22 | 23 | In the interest of fostering an open and welcoming environment, we as 24 | contributors and maintainers pledge to making participation in our project and 25 | our community a harassment-free experience for everyone, regardless of age, body 26 | size, disability, ethnicity, gender identity and expression, level of experience, 27 | nationality, personal appearance, race, religion, or sexual identity and 28 | orientation. 29 | 30 | ### Our Standards 31 | 32 | Examples of behavior that contributes to creating a positive environment 33 | include: 34 | 35 | * Using welcoming and inclusive language 36 | * Being respectful of differing viewpoints and experiences 37 | * Gracefully accepting constructive criticism 38 | * Focusing on what is best for the community 39 | * Showing empathy towards other community members 40 | 41 | Examples of unacceptable behavior by participants include: 42 | 43 | * The use of sexualized language or imagery and unwelcome sexual attention or 44 | advances 45 | * Trolling, insulting/derogatory comments, and personal or political attacks 46 | * Public or private harassment 47 | * Publishing others' private information, such as a physical or electronic 48 | address, without explicit permission 49 | * Other conduct which could reasonably be considered inappropriate in a 50 | professional setting 51 | 52 | ### Our Responsibilities 53 | 54 | Project maintainers are responsible for clarifying the standards of acceptable 55 | behavior and are expected to take appropriate and fair corrective action in 56 | response to any instances of unacceptable behavior. 57 | 58 | Project maintainers have the right and responsibility to remove, edit, or 59 | reject comments, commits, code, wiki edits, issues, and other contributions 60 | that are not aligned to this Code of Conduct, or to ban temporarily or 61 | permanently any contributor for other behaviors that they deem inappropriate, 62 | threatening, offensive, or harmful. 63 | 64 | ### Scope 65 | 66 | This Code of Conduct applies both within project spaces and in public spaces 67 | when an individual is representing the project or its community. Examples of 68 | representing a project or community include using an official project e-mail 69 | address, posting via an official social media account, or acting as an appointed 70 | representative at an online or offline event. Representation of a project may be 71 | further defined and clarified by project maintainers. 72 | 73 | ### Enforcement 74 | 75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 76 | reported by contacting the project team at [divyakumarbaid.dkb.2001@gmail.com]. All 77 | complaints will be reviewed and investigated and will result in a response that 78 | is deemed necessary and appropriate to the circumstances. The project team is 79 | obligated to maintain confidentiality with regard to the reporter of an incident. 80 | Further details of specific enforcement policies may be posted separately. 81 | 82 | Project maintainers who do not follow or enforce the Code of Conduct in good 83 | faith may face temporary or permanent repercussions as determined by other 84 | members of the project's leadership. 85 | 86 | ### Attribution 87 | 88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 89 | available at [http://contributor-covenant.org/version/1/4][version] 90 | 91 | [homepage]: http://contributor-covenant.org 92 | [version]: http://contributor-covenant.org/version/1/4/ 93 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Divya Kumar Baid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |
6 |

DocType

Hussle Free PDF editor !

7 |
8 | 9 | [![GitHub license](https://img.shields.io/github/license/DivyaKumarBaid/DocType?color=e63946&logo=Big%20Cartel&logoColor=white&style=for-the-badge)](https://github.com/DivyaKumarBaid/DocType/blob/main/LICENSE)   [![GitHub forks](https://img.shields.io/github/forks/DivyaKumarBaid/DocType?logo=JFrog%20Bintray&logoColor=white&style=for-the-badge)](https://github.com/DivyaKumarBaid/DocType/network)   [![GitHub stars](https://img.shields.io/github/stars/DivyaKumarBaid/DocType?color=%23ffcb77&logo=Apache%20Spark&logoColor=yellow&style=for-the-badge)](https://github.com/DivyaKumarBaid/DocType/stargazers) 10 | 11 |

12 | 13 | ## Description 14 | 15 | A Hussle free solution to edit PDF. Easier to work with, this PDF editor is based on FabricJs built on React-pdf which would be a lot smoother working with day to day PDF. Download the edited PDF in an instant. Be it multipage, single page just edit and download the whole PDF or even a single page. 16 | 17 | --- 18 | 19 | ## Features 20 | 21 | 22 | 23 | | Sticky Notes | Add a sticky note to your pdf to know important topic | 24 | | ------------------ | ------------------------------------------------------ | 25 | | Shape Tool | Add elementry shapes such as reactangle and circle. | 26 | | Text Box | Add a TextBox in your PDF and take down extra notes. | 27 | | Image | Add an image to the PDF. | 28 | | Pencil | Lets you free draw on PDF. | 29 | | Highlighter | Highlight anything in your PDF. | 30 | | Delete | Delete the selected edited element. | 31 | | Clear Editor | Restor/Delete the whole edited part. | 32 | | Download Page | Download the current page with edited elements. | 33 | | Download PDF | Download the while PDF with edited elements. | 34 | | Color Picker | Pick a color for the included shapes and lines. | 35 | 36 | 37 | 38 | --- 39 | 40 | ## Forking and Hosting 41 | 42 | Fork the repository and host it in netlify. 43 | Currently I am having error while deploying the repository on vercel due to canvas issue with nextjs 44 | To run it locally, clone the repository and run ```npm i``` 45 | 46 | --- 47 | 48 | For any other Questions raise an issue and I will try to solve you problem. 49 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | swcMinify: true, 5 | } 6 | 7 | module.exports = nextConfig 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "doc", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@emotion/react": "^11.10.5", 13 | "@emotion/styled": "^11.10.5", 14 | "@headlessui/react": "^1.7.7", 15 | "@mui/material": "^5.11.6", 16 | "@next/font": "^13.1.6", 17 | "@react-18-pdf/renderer": "^2.3.4", 18 | "animejs": "^3.2.1", 19 | "fabric": "^5.3.0", 20 | "html2canvas": "^1.4.1", 21 | "jspdf": "^2.5.1", 22 | "next": "13.1.6", 23 | "react": "18.2.0", 24 | "react-color": "^2.19.3", 25 | "react-dom": "18.2.0", 26 | "react-dropzone": "^14.2.3", 27 | "react-icons": "^4.7.1", 28 | "react-pdf": "^6.2.2" 29 | }, 30 | "devDependencies": { 31 | "autoprefixer": "^10.4.13", 32 | "eslint": "8.33.0", 33 | "eslint-config-next": "13.1.6", 34 | "postcss": "^8.4.21", 35 | "tailwindcss": "^3.2.4" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import Header from '../src/components/Header' 2 | import { CanvasProvider } from '../src/context/CanvasContext' 3 | import '../styles/globals.css' 4 | 5 | function MyApp({ Component, pageProps }) { 6 | return ( 7 | <> 8 | 9 |
10 | 11 | 12 | 13 | ) 14 | } 15 | 16 | export default MyApp 17 | -------------------------------------------------------------------------------- /pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default function handler(req, res) { 4 | res.status(200).json({ name: 'John Doe' }) 5 | } 6 | -------------------------------------------------------------------------------- /pages/guide.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Guide from '../src/components/Guide' 3 | 4 | export default function GuidePage() { 5 | return ( 6 | 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import FileUpload from "../src/components/FileUpload"; 2 | import Head from 'next/head' 3 | import React from "react"; 4 | 5 | export default function Home() { 6 | 7 | return ( 8 |
9 | 10 | !DocType 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Doctype - Revolutionize Your Document Management 19 | 20 | {/* */} 21 | 22 | 23 | 24 | 25 | 26 | 27 | {/* */} 28 | 29 | 30 | 31 | 32 | 33 | 34 | {/* */} 35 | 36 | 37 | 38 | 39 | 40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/darkNavLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DivyaKumarBaid/DocType/b9b70860d9b00e9f68db1edadb1e9e817c8dc509/public/darkNavLogo.png -------------------------------------------------------------------------------- /public/lightNavLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DivyaKumarBaid/DocType/b9b70860d9b00e9f68db1edadb1e9e817c8dc509/public/lightNavLogo.png -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DivyaKumarBaid/DocType/b9b70860d9b00e9f68db1edadb1e9e817c8dc509/public/logo.png -------------------------------------------------------------------------------- /public/navLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DivyaKumarBaid/DocType/b9b70860d9b00e9f68db1edadb1e9e817c8dc509/public/navLogo.png -------------------------------------------------------------------------------- /public/note/note1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DivyaKumarBaid/DocType/b9b70860d9b00e9f68db1edadb1e9e817c8dc509/public/note/note1.png -------------------------------------------------------------------------------- /public/note/note2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DivyaKumarBaid/DocType/b9b70860d9b00e9f68db1edadb1e9e817c8dc509/public/note/note2.png -------------------------------------------------------------------------------- /public/note/note3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DivyaKumarBaid/DocType/b9b70860d9b00e9f68db1edadb1e9e817c8dc509/public/note/note3.png -------------------------------------------------------------------------------- /public/note/note4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DivyaKumarBaid/DocType/b9b70860d9b00e9f68db1edadb1e9e817c8dc509/public/note/note4.png -------------------------------------------------------------------------------- /public/note/note5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DivyaKumarBaid/DocType/b9b70860d9b00e9f68db1edadb1e9e817c8dc509/public/note/note5.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /src/components/ExportPopup.js: -------------------------------------------------------------------------------- 1 | import { Fragment } from 'react' 2 | import React, { useEffect, useRef, useState } from 'react'; 3 | import { Document, Page, pdfjs } from 'react-pdf'; 4 | import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; 5 | import 'react-pdf/dist/esm/Page/TextLayer.css'; 6 | import { fabric } from 'fabric'; 7 | import { Dialog, Transition } from '@headlessui/react' 8 | import { useButtons } from '../context/CanvasContext'; 9 | import html2canvas from 'html2canvas'; 10 | import jsPDF from 'jspdf'; 11 | import { Backdrop, CircularProgress } from '@mui/material'; 12 | import Loader from './Loader'; 13 | 14 | export default function ExportPopup(props) { 15 | 16 | const contextValues = useButtons(); 17 | const [exportCanvas, setExportCanvas] = React.useState(null); 18 | const [numPages, setNumPages] = React.useState(null); 19 | const [currPage, setCurrPage] = React.useState(1); 20 | const [isExporting, setExporting] = React.useState(false); 21 | 22 | useEffect(() => { 23 | if (exportCanvas) { 24 | contextValues.edits[currPage] && exportCanvas.loadFromJSON(contextValues.edits[currPage]); 25 | } 26 | }, [contextValues.edits, currPage, exportCanvas]) 27 | 28 | 29 | function onDocumentLoadSuccess({ numPages }) { 30 | setNumPages(numPages); 31 | setCurrPage(1); 32 | setExportCanvas(initCanvas()); 33 | } 34 | 35 | function changePage(offset) { 36 | const page = currPage; 37 | setCurrPage(page => page + offset); 38 | exportCanvas.clear() 39 | contextValues.edits[page + offset] && exportCanvas.loadFromJSON(contextValues.edits[page + offset], exportCanvas.renderAll.bind(exportCanvas)); 40 | } 41 | 42 | // fabric js 43 | const initCanvas = () => ( 44 | new fabric.StaticCanvas('canvas-export', { 45 | isDrawingMode: false, 46 | height: 842, 47 | width: 595, 48 | backgroundColor: 'rgba(0,0,0,0)' 49 | }) 50 | ) 51 | 52 | // fabric js 53 | 54 | React.useEffect(() => { 55 | pdfjs.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`; 56 | }, []) 57 | 58 | 59 | const onExport = () => { 60 | setCurrPage(1); 61 | setExporting(true) 62 | const docToExport = document.querySelector("#toExport"); 63 | const pdf = new jsPDF("p", "mm", "a4"); 64 | setTimeout(() => { 65 | let i = 0; 66 | let intervalId = setInterval(() => { 67 | html2canvas(docToExport) 68 | .then((canvas) => { 69 | const imgData = canvas.toDataURL('image/png'); 70 | pdf.addImage(imgData, 'PNG', 0, 0); 71 | }); 72 | i = i + 1; 73 | i <= numPages ? changePage(1) : stopInterval(); 74 | pdf.addPage(); 75 | pdf.setPage(i); 76 | }, 3000) 77 | 78 | const stopInterval = () => { 79 | clearInterval(intervalId); 80 | var pageCount = pdf.internal.getNumberOfPages(); 81 | pdf.deletePage(pageCount) 82 | pdf.save("Edge_lamp_editor.pdf"); 83 | setExporting(false); 84 | props.setOpen(false); 85 | } 86 | 87 | }, 1000) 88 | 89 | } 90 | 91 | return ( 92 | <> 93 | 94 | 95 | 104 |
105 | 106 | 107 |
108 |
109 | 118 | 119 | theme.zIndex.drawer + 1 }} 121 | open={isExporting} 122 | > 123 |
124 | 125 |
126 |
127 |
128 |
129 |
130 |
131 | {contextValues.selectedFile ? 132 |
133 |
134 | 135 | 136 |
137 | 138 |
139 | 140 | 141 | 142 |
143 |
144 |
145 | {currPage > 1 && } 146 |
Page {currPage} of {numPages}
147 | {currPage < numPages && } 148 |
149 |
150 | : null} 151 |
152 |
153 |
154 |
155 |
156 | 163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 | 171 | ) 172 | } 173 | -------------------------------------------------------------------------------- /src/components/FileUpload.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Document, Page, pdfjs } from 'react-pdf'; 3 | import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; 4 | import 'react-pdf/dist/esm/Page/TextLayer.css'; 5 | import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; 6 | import 'react-pdf/dist/esm/Page/TextLayer.css'; 7 | import { useDropzone } from 'react-dropzone'; 8 | import { fabric } from 'fabric'; 9 | import { useButtons } from '../context/CanvasContext'; 10 | import SideBar from './SideBar'; 11 | import { MdClose } from 'react-icons/md'; 12 | import Loader from './Loader'; 13 | import { Icon } from './Icon'; 14 | 15 | 16 | export default function FileUpload() { 17 | 18 | const contextValues = useButtons(); 19 | 20 | const [docIsLoading, setDocIsLoading] = React.useState(false); 21 | 22 | const { getRootProps, getInputProps } = useDropzone({ 23 | onDrop: files => { 24 | setDocIsLoading(true); 25 | contextValues.setFile(files[0]) 26 | } 27 | }) 28 | 29 | function onDocumentLoadSuccess({ numPages }) { 30 | contextValues.setEdits({}); 31 | contextValues.setNumPages(numPages); 32 | contextValues.setCurrPage(1); 33 | contextValues.setCanvas(initCanvas()); 34 | setTimeout(() => setDocIsLoading(false), 2000) 35 | } 36 | 37 | function changePage(offset) { 38 | const page = contextValues.currPage; 39 | contextValues.edits[page] = contextValues.canvas.toObject(); 40 | contextValues.setEdits(contextValues.edits); 41 | contextValues.setCurrPage(page => page + offset); 42 | contextValues.canvas.clear() 43 | contextValues.edits[page + offset] && contextValues.canvas.loadFromJSON(contextValues.edits[page + offset]); 44 | contextValues.canvas.renderAll(); 45 | } 46 | 47 | // fabric js 48 | const initCanvas = () => { 49 | return (new fabric.Canvas('canvas', { 50 | isDrawingMode: false, 51 | height: 842, 52 | width: 595, 53 | backgroundColor: 'rgba(0,0,0,0)' 54 | })) 55 | } 56 | 57 | // fabric js 58 | 59 | React.useEffect(() => { 60 | pdfjs.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`; 61 | }, []) 62 | 63 | return ( 64 |
65 | {contextValues.selectedFile && } 66 | {contextValues.selectedFile ? 67 |
68 |
contextValues.setFile(null)}> 69 | 70 |
71 | 72 |
73 | 74 |
75 | {docIsLoading && 76 | <> 77 |
78 |
79 | 80 |
81 | 82 | 83 | 84 | } 85 | 86 | 87 |
88 | 89 |
90 |
91 | 92 |
93 | 94 |
95 |
96 |
97 |
98 | {contextValues.currPage > 1 && } 99 |
Page {contextValues.currPage} of {contextValues.numPages}
100 | {contextValues.currPage < contextValues.numPages && } 101 |
102 |
103 | :
104 |
105 |
106 | 107 |
108 | 113 | 116 |

or drag and drop

117 |
118 |

PDF

119 |
120 |
121 |
} 122 |
123 | ); 124 | } -------------------------------------------------------------------------------- /src/components/Guide.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useButtons } from "../context/CanvasContext"; 3 | 4 | export default function Guide() { 5 | 6 | const contextValues = useButtons(); 7 | 8 | return ( 9 |
10 |

Useful Tools

11 |
    12 |
  1. Salient Features 13 |
      14 |
    • 15 | Sticky Notes 16 |
      17 |

      18 | - Allows users to add a Sticky Notes on any page of the PDF document. 19 |

      20 |
    • 21 |
    • Shape Tool

      - Allows users to add a rectangle or a circle on any page of the PDF document.

    • 22 |
    • Text Box

      - Allows users to add an editable text box on any page of the PDF document.

    • 23 |
    • Image

      - Allows users to add an image on any page of the PDF document.

    • 24 |
    • Pencil

      - Allows users to draw any shape or make freehand annotations on the PDF document.

    • 25 |
    • Highlighter

      - Allows users to highlight any text or image on any page of the PDF document.

    • 26 |
    • Delete

      - Allows users to remove unwanted annotations or shapes from the PDF document.

    • 27 |
    • Delete All

      - Allows users to clear all annotations or shapes from a specific page of the PDF document.

    • 28 |
    • Download PDF

      - Allows users to download the updated PDF document in .pdf format.

    • 29 |
    • Download Page

      - Allows users to download a specific page from the PDF document in .pdf format.

    • 30 |
    • Colour Picker

      - Allows users to pick a color of their choice.
      - The selected color will be applied to all subsequent annotations or shapes created with the other tools

    • 31 |
    32 |
  2. 33 |
34 |
35 | ) 36 | } 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import { AiFillGithub } from "react-icons/ai"; 2 | import { TiInfoLarge } from "react-icons/ti"; 3 | import Link from 'next/link' 4 | import { MdDarkMode } from "react-icons/md"; 5 | import { CiLight } from "react-icons/ci"; 6 | import { useButtons } from "../context/CanvasContext"; 7 | 8 | export default function Header() { 9 | const contextValues = useButtons(); 10 | 11 | return ( 12 |
13 | 14 | 15 |
16 | 17 | {!contextValues.theme ? contextValues.setTheme(prev => !prev)} /> : contextValues.setTheme(prev => !prev)} />} 18 | 19 |
20 | 21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Icon.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const Icon = () => { 4 | return ( 5 | 6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/components/Loader.js: -------------------------------------------------------------------------------- 1 | import anime from 'animejs'; 2 | import React from 'react' 3 | 4 | const Loader = ({ color, size, stokeWidth }) => { 5 | React.useEffect(() => { 6 | anime({ 7 | targets: '.p1', 8 | strokeDashoffset: [anime.setDashoffset, 0], 9 | easing: 'easeInOutSine', 10 | duration: 1800, 11 | delay: function (el, i) { return i * 150 }, 12 | direction: 'alternate', 13 | loop: true 14 | }); 15 | }, []) 16 | return ( 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | ) 32 | } 33 | export default Loader; 34 | -------------------------------------------------------------------------------- /src/components/SideBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { CgFormatText } from 'react-icons/cg' 3 | import { TbBookDownload } from 'react-icons/tb' 4 | import { BiHide, BiImageAdd, BiShow } from 'react-icons/bi' 5 | import { BsBorderWidth, BsCircle, BsSquare } from 'react-icons/bs' 6 | import { AiOutlineClear, AiOutlineDelete, AiOutlineHighlight } from 'react-icons/ai' 7 | import { HiPencil } from 'react-icons/hi' 8 | import { TfiNotepad } from 'react-icons/tfi' 9 | import { FiSave } from 'react-icons/fi' 10 | import { useButtons } from '../context/CanvasContext'; 11 | import { Popover, Slider } from '@mui/material' 12 | import Tooltip from '@mui/material/Tooltip'; 13 | import { SketchPicker } from 'react-color' 14 | import ExportPopup from './ExportPopup' 15 | 16 | export default function SideBar() { 17 | 18 | const contextValues = useButtons(); 19 | const [openColor, setOpenColor] = React.useState(false); 20 | const [openBorderColor, setOpenBorderColor] = React.useState(false); 21 | const [openStroke, setOpenStroke] = React.useState(false); 22 | const [openExporter, setOpenExporter] = React.useState(false); 23 | 24 | return ( 25 |
26 |
27 | 28 | 29 | 30 | 31 |
32 | contextValues.addNote(contextValues.canvas)} /> 33 |
34 |
35 | 36 | 37 |
38 | contextValues.addRect(contextValues.canvas)} /> 39 |
40 |
41 | 42 | 43 |
44 | contextValues.addCircle(contextValues.canvas)} /> 45 |
46 |
47 | 48 | 49 |
50 | contextValues.addText(contextValues.canvas)} /> 51 |
52 |
53 | 54 | 55 |
56 | 59 | contextValues.addImage(e, contextValues.canvas)} /> 60 |
61 |
62 | 63 | 64 |
65 | contextValues.toggleDraw(contextValues.canvas)} /> 66 |
67 |
68 | 69 | 70 |
71 | contextValues.addHighlight(contextValues.canvas)} /> 72 |
73 |
74 | 75 | 76 |
77 | contextValues.deleteBtn()} /> 78 |
79 |
80 | 81 | 82 |
83 | contextValues.canvas.clear()} /> 84 |
85 |
86 | 87 | 88 |
89 | contextValues.downloadPage()} /> 90 |
91 |
92 | 93 | 94 |
95 | { 96 | contextValues.edits[contextValues.currPage] = contextValues.canvas.toObject(); 97 | setOpenExporter(true); 98 | }} /> 99 |
100 |
101 | 102 | 103 |
setOpenBorderColor(e.currentTarget)}>
104 |
105 | setOpenBorderColor(null)} 110 | anchorOrigin={{ 111 | vertical: 'bottom', 112 | horizontal: 'left', 113 | }} 114 | > 115 | contextValues.setBorderColor(col.hex)} 118 | /> 119 | 120 | 121 | 122 |
setOpenColor(e.currentTarget)}>
123 |
124 | setOpenColor(null)} 129 | anchorOrigin={{ 130 | vertical: 'bottom', 131 | horizontal: 'left', 132 | }} 133 | > 134 | contextValues.setColor(col.hex)} 137 | /> 138 | 139 | 140 | 141 |
142 | setOpenStroke(e.currentTarget)} /> 143 |
144 |
145 | setOpenStroke(null)} 150 | anchorOrigin={{ 151 | vertical: 'bottom', 152 | horizontal: 'left', 153 | }} 154 | 155 | > 156 |
157 |
Stoke Width
158 | contextValues.setStrokeWidth(e.target.value)} 165 | valueLabelDisplay="auto" 166 | /> 167 |
168 |
169 | 170 | 171 |
contextValues.setHiddenCanvas(old => !old)}> 172 | {contextValues.hideCanvas ? : 173 | } 174 |
175 |
176 | 177 |
178 |
179 | ) 180 | } 181 | -------------------------------------------------------------------------------- /src/context/CanvasContext.js: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import { fabric } from 'fabric' 3 | import jsPDF from 'jspdf' 4 | import html2canvas from 'html2canvas' 5 | import { Roboto } from '@next/font/google' 6 | 7 | const funButtons = React.createContext() 8 | 9 | export const useButtons = () => { 10 | return React.useContext(funButtons) 11 | } 12 | 13 | const roboto = Roboto({ 14 | weight: ['400', '700'], 15 | style: ['normal', 'italic'], 16 | subsets: ['latin'], 17 | }) 18 | 19 | export const CanvasProvider = ({ children }) => { 20 | // file 21 | const [theme, setTheme] = React.useState(false); 22 | // false -> light mode , true -> dark mode 23 | const [numPages, setNumPages] = React.useState(null); 24 | const [currPage, setCurrPage] = React.useState(1); 25 | const [selectedFile, setFile] = React.useState(null); 26 | const [color, setColor] = React.useState("#000"); 27 | const [borderColor, setBorderColor] = React.useState("#f4a261"); 28 | const [strokeWidth, setStrokeWidth] = React.useState(1); 29 | const [canvas, setCanvas] = React.useState(''); 30 | const [isExporting, setExporting] = React.useState(false); 31 | const [hideCanvas, setHiddenCanvas] = React.useState(false); 32 | 33 | const exportPage = useRef(null); 34 | const [exportPages, setExportPages] = React.useState([]); 35 | // canvas edits 36 | const [edits, setEdits] = React.useState({}); 37 | // uploaded image 38 | 39 | React.useEffect(() => { 40 | if (document.getElementById("canvasWrapper")) 41 | document.getElementById("canvasWrapper").style.visibility = document.getElementById("canvasWrapper").style.visibility == "hidden" ? "visible" : "hidden"; 42 | }, [hideCanvas]) 43 | 44 | React.useEffect(() => { 45 | if (canvas != '') { 46 | var activeObject = canvas.getActiveObject(); 47 | if (activeObject) { 48 | activeObject.set("fill", color) 49 | canvas.renderAll() 50 | } 51 | } 52 | }, [color]) 53 | 54 | React.useEffect(() => { 55 | if (canvas.isDrawingMode) 56 | canvas.freeDrawingBrush.color = borderColor; 57 | if (canvas != '') { 58 | var activeObject = canvas.getActiveObject(); 59 | if (activeObject) { 60 | activeObject.set("stroke", borderColor) 61 | canvas.renderAll() 62 | } 63 | } 64 | }, [borderColor]) 65 | 66 | React.useEffect(() => { 67 | if (canvas.isDrawingMode) 68 | canvas.freeDrawingBrush.width = strokeWidth; 69 | if (canvas != '') { 70 | var activeObject = canvas.getActiveObject(); 71 | if (activeObject) { 72 | activeObject.set("strokeWidth", strokeWidth) 73 | canvas.renderAll() 74 | } 75 | } 76 | }, [strokeWidth]) 77 | 78 | const downloadPage = () => { 79 | setExporting(true); 80 | const doc = document.querySelector('#singlePageExport'); 81 | html2canvas(doc) 82 | .then((canvas) => { 83 | const imgData = canvas.toDataURL('image/png'); 84 | const pdf = new jsPDF(); 85 | pdf.addImage(imgData, 'PNG', 0, 0); 86 | pdf.save("edge_lamp_edited.pdf"); 87 | setExporting(false); 88 | }); 89 | } 90 | 91 | const addImage = (e, canvi) => { 92 | var file = e.target.files[0]; 93 | var reader = new FileReader(); 94 | reader.onload = function (f) { 95 | var data = f.target.result; 96 | fabric.Image.fromURL(data, function (img) { 97 | img.scaleToWidth(300); 98 | canvi.add(img).renderAll(); 99 | var dataURL = canvi.toDataURL({ format: 'png', quality: 0.8 }); 100 | }); 101 | } 102 | reader.readAsDataURL(file); 103 | canvi.isDrawingMode = false 104 | } 105 | 106 | const addNote = (canvi) => { 107 | fabric.Image.fromURL(`./note/note${(Math.floor(Math.random() * 10) % 4) + 1}.png`, function (img) { 108 | img.scaleToWidth(100); 109 | canvi.add(img).renderAll(); 110 | var dataURL = canvi.toDataURL({ format: 'png', quality: 0.8 }); 111 | }); 112 | canvi.isDrawingMode = false 113 | } 114 | 115 | const deleteBtn = () => { 116 | var activeObject = canvas.getActiveObject(); 117 | if (activeObject) { 118 | canvas.remove(activeObject); 119 | } 120 | } 121 | 122 | // add a rectangle 123 | const addRect = canvi => { 124 | const rect = new fabric.Rect({ 125 | height: 180, 126 | width: 200, 127 | fill: color, 128 | stroke: borderColor, 129 | strokeWidth: strokeWidth, 130 | cornerStyle: 'circle', 131 | editable: true 132 | }); 133 | canvi.add(rect); 134 | canvi.renderAll(); 135 | canvi.isDrawingMode = false 136 | } 137 | 138 | const addCircle = canvi => { 139 | const rect = new fabric.Circle({ 140 | radius: 100, 141 | fill: color, 142 | cornerStyle: 'circle', 143 | editable: true, 144 | stroke: borderColor, 145 | strokeWidth: 2, 146 | }); 147 | canvi.add(rect); 148 | canvi.renderAll(); 149 | canvi.isDrawingMode = false 150 | } 151 | 152 | // add highlight 153 | const addHighlight = canvi => { 154 | const rect = new fabric.Rect({ 155 | height: 20, 156 | width: 400, 157 | fill: color + '33', 158 | cornerStyle: 'circle', 159 | editable: true 160 | }); 161 | canvi.add(rect); 162 | canvi.renderAll(); 163 | canvi.isDrawingMode = false 164 | } 165 | 166 | // add text 167 | const addText = canvi => { 168 | const text = new fabric.Textbox("Type Here ...", { 169 | editable: true, 170 | }); 171 | // text.set({ fill: color }) 172 | text.set({ fill: color, fontFamily: roboto.style.fontFamily }) 173 | canvi.add(text); 174 | canvi.renderAll(); 175 | canvi.isDrawingMode = false 176 | } 177 | 178 | const toggleDraw = canvi => { 179 | canvi.isDrawingMode = !canvi.isDrawingMode; 180 | var brush = canvas.freeDrawingBrush; 181 | brush.color = borderColor; 182 | brush.strokeWidth = strokeWidth; 183 | } 184 | 185 | // add functions here 186 | const exportPdf = () => { 187 | setExportPages((prev) => ([...prev, exportPage.current])); 188 | console.log(exportPages) 189 | } 190 | 191 | return ( 192 | 193 | {children} 194 | 195 | ) 196 | } 197 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title a { 32 | color: #0070f3; 33 | text-decoration: none; 34 | } 35 | 36 | .title a:hover, 37 | .title a:focus, 38 | .title a:active { 39 | text-decoration: underline; 40 | } 41 | 42 | .title { 43 | margin: 0; 44 | line-height: 1.15; 45 | font-size: 4rem; 46 | } 47 | 48 | .title, 49 | .description { 50 | text-align: center; 51 | } 52 | 53 | .description { 54 | margin: 4rem 0; 55 | line-height: 1.5; 56 | font-size: 1.5rem; 57 | } 58 | 59 | .code { 60 | background: #fafafa; 61 | border-radius: 5px; 62 | padding: 0.75rem; 63 | font-size: 1.1rem; 64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 65 | Bitstream Vera Sans Mono, Courier New, monospace; 66 | } 67 | 68 | .grid { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | flex-wrap: wrap; 73 | max-width: 800px; 74 | } 75 | 76 | .card { 77 | margin: 1rem; 78 | padding: 1.5rem; 79 | text-align: left; 80 | color: inherit; 81 | text-decoration: none; 82 | border: 1px solid #eaeaea; 83 | border-radius: 10px; 84 | transition: color 0.15s ease, border-color 0.15s ease; 85 | max-width: 300px; 86 | } 87 | 88 | .card:hover, 89 | .card:focus, 90 | .card:active { 91 | color: #0070f3; 92 | border-color: #0070f3; 93 | } 94 | 95 | .card h2 { 96 | margin: 0 0 1rem 0; 97 | font-size: 1.5rem; 98 | } 99 | 100 | .card p { 101 | margin: 0; 102 | font-size: 1.25rem; 103 | line-height: 1.5; 104 | } 105 | 106 | .logo { 107 | height: 1em; 108 | margin-left: 0.5rem; 109 | } 110 | 111 | @media (max-width: 600px) { 112 | .grid { 113 | width: 100%; 114 | flex-direction: column; 115 | } 116 | } 117 | 118 | @media (prefers-color-scheme: dark) { 119 | .card, 120 | .footer { 121 | border-color: #222; 122 | } 123 | .code { 124 | background: #111; 125 | } 126 | .logo img { 127 | filter: invert(1); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@800&display=swap'); 2 | /* @import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap'); */ 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | .heading{ 8 | background-image: linear-gradient(60deg,#ff3a7c,#741eff); 9 | -webkit-background-clip: text; 10 | -webkit-text-fill-color: transparent; 11 | } 12 | /* scroll bar */ 13 | 14 | /* width */ 15 | ::-webkit-scrollbar { 16 | width: 4px; 17 | } 18 | 19 | /* Track */ 20 | ::-webkit-scrollbar-track { 21 | background: rgb(26, 26, 26); 22 | } 23 | 24 | /* Handle */ 25 | ::-webkit-scrollbar-thumb { 26 | background: grey; 27 | border-radius:8px; 28 | } 29 | 30 | /* Handle on hover */ 31 | ::-webkit-scrollbar-thumb:hover { 32 | background: #888; 33 | } 34 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./app/**/*.{js,ts,jsx,tsx}", 5 | "./pages/**/*.{js,ts,jsx,tsx}", 6 | "./components/**/*.{js,ts,jsx,tsx}", 7 | 8 | // Or if using `src` directory: 9 | "./src/**/*.{js,ts,jsx,tsx}", 10 | ], 11 | theme: { 12 | extend: {}, 13 | }, 14 | plugins: [], 15 | } --------------------------------------------------------------------------------