Compare commits

...
11 Commits
Author SHA1 Message Date
abdullahalam123 e4bd206d17 Release v1.3.0 2025-11-08 13:24:59 +05:30
abdullahalam123 05b5181b3e refactor(toc): clean up HTML structure and comment out status message
- Simplified the HTML structure of the table of contents page by removing unnecessary comments and whitespace.
- Commented out the status message display in the TypeScript logic for file selection.
- Enhanced readability and maintainability of the code.
2025-11-08 13:23:57 +05:30
abdullahalam123 bc181c8c96 chore: update image sources and version bump to 1.2.0
- Changed image sources for GDPR, CCPA, and HIPAA compliance logos to local paths.
- Updated package version to 1.2.0 in package-lock.json.
- Enhanced README and SIMPLE_MODE documentation with Docker usage instructions.
- Improved table-of-contents worker and related TypeScript definitions for better clarity and functionality.
- Refactored CSS styles for consistency and improved UI elements.
2025-11-08 13:17:29 +05:30
abdullahalam123 661c030ae1 feat(toc): implement table of contents generation feature
- Added a new page for generating a table of contents from PDF bookmarks.
- Introduced a web worker to handle the TOC generation process in the background.
- Updated TypeScript definitions for coherentpdf integration.
- Enhanced the Vite configuration to include necessary headers and exclude specific dependencies.
- Modified tsconfig to include WebWorker library and updated file inclusion paths.
- Added a new script for handling TOC logic and user interactions.
2025-11-08 12:37:10 +05:30
Alam 9befab7758 Merge pull request #149 from Erwan-loot/patch-1
Add image source label to Dockerfile
2025-11-08 00:13:50 +05:30
Alam 925eb5db28 Merge pull request #150 from luna-cant-code/main
Update unraid_bentopdf.xml
2025-11-08 00:13:28 +05:30
luna-cant-code 2de6cdc7c6 Update unraid_bentopdf.xml
See Issue #147
This should fix it for new users

https://github.com/alam00000/bentopdf/issues/147
2025-11-07 19:35:08 +01:00
Erwan Hervé 0ab113f8ba Add image source label to Dockerfile 2025-11-07 18:48:39 +01:00
Alam e7e0160fe9 Merge pull request #126 from LokiSalmonNeko/main
Enhance README with Zeabur deploy button
2025-11-07 14:20:16 +05:30
abdullahalam123 f0c6b1e562 refactor(styles): move inline css to separate bookmark.css file 2025-11-07 12:30:26 +05:30
Loki 1707b293a8 Enhance README with Zeabur deploy button
Added a deployment button for Zeabur.
2025-10-30 05:02:25 +08:00
22 changed files with 1134 additions and 198 deletions

No files matched your search

+2
View File
@@ -14,6 +14,8 @@ RUN npm run build -- --mode production
# Production stage # Production stage
FROM nginxinc/nginx-unprivileged:stable-alpine-slim FROM nginxinc/nginx-unprivileged:stable-alpine-slim
LABEL org.opencontainers.image.source="https://github.com/alam00000/bentopdf"
COPY --chown=nginx:nginx --from=builder /app/dist /usr/share/nginx/html COPY --chown=nginx:nginx --from=builder /app/dist /usr/share/nginx/html
COPY --chown=nginx:nginx nginx.conf /etc/nginx/nginx.conf COPY --chown=nginx:nginx nginx.conf /etc/nginx/nginx.conf
RUN mkdir -p /etc/nginx/tmp && chown -R nginx:nginx /etc/nginx/tmp RUN mkdir -p /etc/nginx/tmp && chown -R nginx:nginx /etc/nginx/tmp
+6
View File
@@ -102,17 +102,21 @@ You can run BentoPDF locally for development or personal use.
### 🚀 Quick Start with Docker ### 🚀 Quick Start with Docker
[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/LWO8I0?referralCode=LokiSalmonNeko)
You can run BentoPDF directly from Docker Hub or GitHub Container Registry without cloning the repository: You can run BentoPDF directly from Docker Hub or GitHub Container Registry without cloning the repository:
You can also watch the video on how to set it up 👉 You can also watch the video on how to set it up 👉
[BentoPDF Docker Setup](https://drive.google.com/file/d/1C4eJ2nqeaH__1Tlad-xuBHaF2Ha4fSBf/view?usp=drive_link) [BentoPDF Docker Setup](https://drive.google.com/file/d/1C4eJ2nqeaH__1Tlad-xuBHaF2Ha4fSBf/view?usp=drive_link)
**Using Docker Hub:** **Using Docker Hub:**
```bash ```bash
docker run -p 3000:8080 bentopdf/bentopdf:latest docker run -p 3000:8080 bentopdf/bentopdf:latest
``` ```
**Using GitHub Container Registry:** **Using GitHub Container Registry:**
```bash ```bash
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
``` ```
@@ -180,11 +184,13 @@ For detailed security configuration, see [SECURITY.md](SECURITY.md).
BentoPDF supports semantic versioning with multiple Docker tags available on both Docker Hub and GitHub Container Registry: BentoPDF supports semantic versioning with multiple Docker tags available on both Docker Hub and GitHub Container Registry:
**Docker Hub:** **Docker Hub:**
- **Latest**: `bentopdf/bentopdf:latest` - **Latest**: `bentopdf/bentopdf:latest`
- **Specific Version**: `bentopdf/bentopdf:1.0.0` - **Specific Version**: `bentopdf/bentopdf:1.0.0`
- **Version with Prefix**: `bentopdf/bentopdf:v1.0.0` - **Version with Prefix**: `bentopdf/bentopdf:v1.0.0`
**GitHub Container Registry:** **GitHub Container Registry:**
- **Latest**: `ghcr.io/alam00000/bentopdf:latest` - **Latest**: `ghcr.io/alam00000/bentopdf:latest`
- **Specific Version**: `ghcr.io/alam00000/bentopdf:1.0.0` - **Specific Version**: `ghcr.io/alam00000/bentopdf:1.0.0`
- **Version with Prefix**: `ghcr.io/alam00000/bentopdf:v1.0.0` - **Version with Prefix**: `ghcr.io/alam00000/bentopdf:v1.0.0`
+7
View File
@@ -24,14 +24,17 @@ When enabled, Simple Mode will:
Use the pre-built Simple Mode image directly: Use the pre-built Simple Mode image directly:
**Using Docker Hub:** **Using Docker Hub:**
```bash ```bash
docker run -p 3000:8080 bentopdf/bentopdf-simple:latest docker run -p 3000:8080 bentopdf/bentopdf-simple:latest
``` ```
**Using GitHub Container Registry:** **Using GitHub Container Registry:**
```bash ```bash
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
``` ```
Or with Docker Compose: Or with Docker Compose:
```yaml ```yaml
@@ -151,20 +154,24 @@ When Simple Mode is working correctly, you should see:
### Normal Mode (Full Branding) ### Normal Mode (Full Branding)
**Docker Hub:** **Docker Hub:**
- `bentopdf/bentopdf:latest` - `bentopdf/bentopdf:latest`
- `bentopdf/bentopdf:v1.0.0` (versioned) - `bentopdf/bentopdf:v1.0.0` (versioned)
**GitHub Container Registry:** **GitHub Container Registry:**
- `ghcr.io/alam00000/bentopdf:latest` - `ghcr.io/alam00000/bentopdf:latest`
- `ghcr.io/alam00000/bentopdf:v1.0.0` (versioned) - `ghcr.io/alam00000/bentopdf:v1.0.0` (versioned)
### Simple Mode (Clean Interface) ### Simple Mode (Clean Interface)
**Docker Hub:** **Docker Hub:**
- `bentopdf/bentopdf-simple:latest` - `bentopdf/bentopdf-simple:latest`
- `bentopdf/bentopdf-simple:v1.0.0` (versioned) - `bentopdf/bentopdf-simple:v1.0.0` (versioned)
**GitHub Container Registry:** **GitHub Container Registry:**
- `ghcr.io/alam00000/bentopdf-simple:latest` - `ghcr.io/alam00000/bentopdf-simple:latest`
- `ghcr.io/alam00000/bentopdf-simple:v1.0.0` (versioned) - `ghcr.io/alam00000/bentopdf-simple:v1.0.0` (versioned)
+3 -3
View File
@@ -375,7 +375,7 @@
class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4" class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4"
> >
<img <img
src="https://mkt-cf.pdffiller.com/mrk/350/images/_global/logos/security-badges/logo-gdpr.svg" src="/images/gdpr.svg"
alt="GDPR compliance" alt="GDPR compliance"
class="w-full h-full" class="w-full h-full"
/> />
@@ -396,7 +396,7 @@
class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4" class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4"
> >
<img <img
src="https://mkt-cf.pdffiller.com/mrk/350/images/_global/logos/security-badges/logo-ccpa.svg" src="/images/ccpa.svg"
alt="CCPA compliance" alt="CCPA compliance"
class="w-full h-full" class="w-full h-full"
/> />
@@ -417,7 +417,7 @@
class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4" class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4"
> >
<img <img
src="https://mkt-cf.pdffiller.com/mrk/350/images/_global/logos/security-badges/logo-hipaa.svg" src="/images/hipaa.svg"
alt="HIPAA compliance" alt="HIPAA compliance"
class="w-full h-full" class="w-full h-full"
/> />
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "bento-pdf", "name": "bento-pdf",
"version": "1.1.1", "version": "1.2.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bento-pdf", "name": "bento-pdf",
"version": "1.1.1", "version": "1.2.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@fontsource/cedarville-cursive": "^5.2.7", "@fontsource/cedarville-cursive": "^5.2.7",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "bento-pdf", "name": "bento-pdf",
"private": true, "private": true,
"version": "1.2.0", "version": "1.3.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
+10
View File
@@ -0,0 +1,10 @@
/*
* This file contains a modified version of cpdf.js from the CoherentPDF project.
* Original source: https://www.coherentpdf.com
* Portions © Coherent Graphics Ltd. (CoherentPDF project)
* Copyright © 2025 BentoPDF
*
* The original CoherentPDF code is licensed under the GNU Affero General Public License v3.0 (AGPLv3),
* and this modified version is distributed under the same license.
*/
!function(t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).coherentpdf=t()}(function(){return function n(i,a,f){function o(e,t){if(!a[e]){if(!i[e]){var r="function"==typeof require&&require;if(!t&&r)return r(e,!0);if(u)return u(e,!0);throw(t=new Error("Cannot find module '"+e+"'")).code="MODULE_NOT_FOUND",t}r=a[e]={exports:{}},i[e][0].call(r.exports,function(t){return o(i[e][1][t]||t)},r,r.exports,n,i,a,f)}return a[e].exports}for(var u="function"==typeof require&&require,t=0;t<f.length;t++)o(f[t]);return o}({1:[function(xzt,Pzt,t){!function(Azt){!function(){var t;!function(e){function t(){var t=this||self;t.globalThis=t,delete e.prototype._T_}"object"!=typeof globalThis&&(this?t():(e.defineProperty(e.prototype,"_T_",{configurable:!0,get:t}),_T_))}(Object),(t=globalThis).cpdf_zlib=xzt("zlib"),t.cpdf_crypto=xzt("crypto"),t.cpdf_sjcl=xzt("./sjcl.js"),function(o){"use strict";var i="object"==typeof Pzt&&Pzt.exports||o,u=83809507,e="/RunLengthDecode",r="/Oslash",a="addContent",p=192,f="add",B=365180284,s="/scaron",c="EMFILE",l="noHqPrint",d="textToPDFMemory",b="leftJustify",v="/h",m="cpdfutil.ml",_="/asciicircum",G=72,k=2147483645,S="sc",E="setAuthorXMP",D=163,N="Pdfflate.Error",F="title",j="/Encrypt",L="/numbersign",U=122,z="PMAXY",q="upright",W=181,H="/bullet",K="endstream",Ht=128,X="difference",Z="/twosuperior",$="/U",Q=45,J="stampExtended",tt="timesBold",et="ptOfIn",rt=719944127,nt="squeezeInMemory",it=1027,at=297,ft="/OCGs",ot="/acute",ht="EINPROGRESS",lt="BDC",dt="/ViewerPreferences",pt="/Ucircumflex",bt="combinePages",vt="/daggerdbl",mt="fromJSON",yt="enumeratePDFsInfo",gt="/Trapped",wt="DP",_t="/Ecircumflex",kt="/Ascent",St="/FontFile",Et="False",Mt="endGetOCGList",At="Q Deficit was nonzero. Fixing. %i\n%!",xt="/yen",Rt="numberFonts",Kt="/Scaron",Xt="UseThumbs",Zt="\nendobj\n",$t="/A",Yt="/Agrave",Vt=-43,Qt="squeeze_form_xobject",Jt="Bad Op_gs",te="getProducerXMP",ee="getPageLabelStyle",re="setMetadataFromByteArray",ne="/yacute",ie="twoPageRight",ae="getCropBox",fe="a3landscape",oe="/AE",ue="attachFileToPageFromMemory",se="/Contents",ce=100,he=111,le=180,de="setAuthor",pe="Keywords",be="/CPDFJSONmajorpdfversion",ve=-100,me="/questiondown",ye=229,ge="/FontMatrix",we="@])",_e="scaleToFit",ke="ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ\0\0\0\0ÿÿ\0\0\0†\0ÿÿ\0ÿÿ†\0E’9\0ÿÿE’ÿÿÿÿÿÿÿÿ}\0Š\0ÿÿ\0\0ÿÿ\0\0\0©\0†\0®\0ÿÿ\0\0\nE’\f\0\0\n\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0s\0\0\0}\0\0\0ìˆ\0ÿ&\0ÿÿ\n\0ˆ\0f\0:\0\0\0k\0f\0ÿÿ\v\0\0\0\v\0\v\0\v\0\v\0\v\0\v\0\v\0\v\0\v\0\v\0\0&\0\0\0o\0Ð\0é\0ÿ\0\f\0\0<\0\v\0\n\0\0\0\0\0\0 \0\"\0\0\0\0\0\0\0!\0\0\0\0\0\0\0#\0(\0$\0&\0\0\0)\0*\0+\0,\0-\0.\0:\0R\0\v\0\r\0\r\0\r\0\r\0\r\0\r\0\r\0\r\0\r\0\r\0'\0?\0'\0'\0'\0'\0'\0'\0'\0'\0'\0'\0U\0Œ\0<\0\r\0\0\0‘\x000\0“\x000\0”\0'\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x001\x001\x001\x001\x001\x001\x001\x001\x001\x001\x002\x002\x002\x002\x002\x002\x002\x002\x002\x002\0A\0'\0•\0–\0œ\0?\0\x003\0ž\x003\0Ÿ\x002\x003\x003\x003\x003\x003\x003\x003\x003\x003\x003\x004\x004\x004\x004\x004\x004\x004\x004\x004\x004\x005\x005\x005\x005\x005\x005\x005\x005\x005\x005\0›\x002\x006\x006\x006\x006\x006\x006\x006\x006\x006\x006\0¡\0¢\0›\0[\0A\0\0\x007\x007\x007\x007\x007\x007\x007\x007\x007\x007\x009\0D\0f\0k\0s\0ƒ\0…\0…\0}\0Š\0…\0£\0^\0¥\0D\0¦\0§\0¨\0«\0o\0¬\0­\0Î\0Ë\0Ï\0Ò\0Ó\0:\0R\0…\0Ô\0Õ\0Ö\0×\0Ù\0Œ\0Ú\0a\0Û\0Ü\0w\0Ý\0Þ\0ß\0…\0[\0Ë\0\">é\0˜\0P÷\0<\0û\x006:QD\0)RSD\0w\0D\0^\0TUVGXD\0Ë\x002GD\0YD\0D\0G\0G\0G\0G\0G\0G\0G\0G\0G\0G\0a\0Lw\0Z?\0\\G\0G\0G\0G\0G\0G\0N\0N\0N\0N\0N\0N\0N\0N\0N\0N\0˜\0L]_ab-N\0N\0N\0N\0N\0N\0c˜\0dG\0G\0G\0G\0G\0G\0´\0´\0´\0´\0´\0´\0´\0´\0´\0´\0LA\0efhN\0N\0N\0N\0N\0N\0O\0O\0O\0O\0O\0O\0O\0O\0O\0O\0ij-$klmO\0O\0O\0O\0O\0O\0P\0P\0P\0P\0P\0P\0P\0P\0P\0P\0nyžP\0P\0P\0P\0P\0P\0[\0Ÿ>O\0O\0O\0O\0O\0O\0÷\0 û\0¡:D\0V\0V\0V\0V\0V\0V\0V\0V\0V\0V\0^\0P\0P\0P\0P\0P\0P\0V\0V\0V\0V\0V\0V\0W\0W\0W\0W\0W\0W\0W\0W\0W\0W\0$)a\0¢£w\0W\0W\0W\0W\0W\0W\0¥6˜\0V\0V\0V\0V\0V\0V\0¦§¨©X\0X\0X\0X\0X\0X\0X\0X\0X\0X\x002W\0W\0W\0W\0W\0W\0X\0X\0X\0X\0X\0X\0Y\0Y\0Y\0Y\0Y\0Y\0Y\0Y\0Y\0Y\0_\0…ª«š…¬Y\0Y\0Y\0Y\0Y\0Y\0_\0°\0­X\0X\0X\0X\0X\0X\0-®¯°\0°š°\0°\0°\0°\0°\0°\0°\0°\0°\0°\0zY\0Y\0Y\0Y\0Y\0Y\0”±²b\0”³´µ·ØÁ_\0šØÍb\0Þ_\0Íå_\0Ú$××Ú×_\0Ø_\0_\0_\0`\0`\0`\0`\0`\0`\0`\0`\0`\0`\0×z\b\t\n\v`\0`\0`\0`\0`\0`\0b\0\f×÷\rb\0}€b\0Üûb\0yÂb\0b\0b\0`\0`\0`\0`\0`\0`\0c\0c\0c\0c\0c\0c\0c\0c\0c\0c\0çîûîc\0c\0c\0c\0c\0c\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0ó}€àÅd\0d\0d\0d\0d\0d\0Âc\0c\0c\Line truncated
+6
View File
@@ -0,0 +1,6 @@
<svg width="52" height="52" viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 26C0 11.6406 11.6406 0 26 0C40.3594 0 52 11.6406 52 26C52 40.3594 40.3594 52 26 52C11.6406 52 0 40.3594 0 26Z" fill="#0C4776"/>
<path d="M26 46C37.0457 46 46 37.0457 46 26C46 14.9543 37.0457 6 26 6C14.9543 6 6 14.9543 6 26C6 37.0457 14.9543 46 26 46Z" fill="white" stroke="#007A7F" stroke-width="2"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.9398 16.7218L24.2279 18.9983L31.7583 11.5073L34.6951 14.4291L24.2279 24.8407L19.043 19.683L21.9398 16.7218Z" fill="#007A7F"/>
<path d="M18.1522 35.9595C19.0079 35.9595 19.6653 35.8029 19.9574 35.6568L19.7174 34.4151C19.4044 34.5403 18.8722 34.6447 18.3922 34.6447C16.9731 34.6447 16.1383 33.7577 16.1383 32.349C16.1383 30.7838 17.1192 30.0221 18.3818 30.0221C18.9453 30.0221 19.394 30.1473 19.7174 30.2829L20.0409 29.0203C19.7592 28.8742 19.1331 28.7073 18.3087 28.7073C16.1801 28.7073 14.4688 30.0429 14.4688 32.4429C14.4688 34.4464 15.7209 35.9595 18.1522 35.9595ZM23.8989 35.9595C24.7545 35.9595 25.4119 35.8029 25.7041 35.6568L25.4641 34.4151C25.1511 34.5403 24.6189 34.6447 24.1389 34.6447C22.7198 34.6447 21.885 33.7577 21.885 32.349C21.885 30.7838 22.8658 30.0221 24.1285 30.0221C24.6919 30.0221 25.1406 30.1473 25.4641 30.2829L25.7876 29.0203C25.5059 28.8742 24.8798 28.7073 24.0554 28.7073C21.9267 28.7073 20.2154 30.0429 20.2154 32.4429C20.2154 34.4464 21.4676 35.9595 23.8989 35.9595ZM28.1743 35.8551V33.3403C28.3203 33.3612 28.5082 33.3716 28.7169 33.3716C29.656 33.3716 30.4595 33.1421 31.0021 32.6308C31.4195 32.2342 31.649 31.6499 31.649 30.9612C31.649 30.2725 31.3464 29.6881 30.8977 29.3334C30.4282 28.9577 29.729 28.7699 28.7482 28.7699C27.7777 28.7699 27.089 28.8325 26.5986 28.916V35.8551H28.1743ZM28.7064 32.1508C28.4769 32.1508 28.3099 32.1403 28.1743 32.109V30.0325C28.289 30.0012 28.5082 29.9699 28.8316 29.9699C29.6247 29.9699 30.0734 30.356 30.0734 31.0029C30.0734 31.7229 29.5516 32.1508 28.7064 32.1508ZM33.0235 35.8551L33.5244 34.0499H35.5383L36.0809 35.8551H37.7922L35.6114 28.8221H33.5244L31.3748 35.8551H33.0235ZM35.3087 32.8603H33.754L34.1714 31.3681C34.2861 30.9612 34.3905 30.429 34.4948 30.0116H34.5157C34.6201 30.429 34.7453 30.9508 34.8705 31.3681L35.3087 32.8603Z" fill="#0C4776"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg width="52" height="52" viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 26C0 11.6406 11.6406 0 26 0C40.3594 0 52 11.6406 52 26C52 40.3594 40.3594 52 26 52C11.6406 52 0 40.3594 0 26Z" fill="#0047BD"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.3149 11.2327L26.0039 9.98946L27.693 11.2327L27.0509 9.23793L28.733 7.99473H26.653L26.0109 6L25.3618 7.99473H23.2749L24.957 9.23793L24.3149 11.2327ZM34.6372 12.3214L32.9412 13.5646L33.5903 11.5698L31.9012 10.3266H33.9951L34.6372 8.33189L35.2863 10.3266H37.3593L35.6842 11.5698L36.3263 13.5646L34.6372 12.3214ZM39.2647 19.928L40.9607 18.6918L42.6498 19.928L42.0076 17.9332L43.6827 16.6971H41.6098L40.9607 14.7023L40.3186 16.6971H38.2247L39.9138 17.9332L39.2647 19.928ZM7.03298 28.6093L8.72902 27.3731L10.4181 28.6093L9.77596 26.6216L11.4511 25.3784H9.37812L8.736 23.3836L8.0869 25.3784H6L7.68208 26.6216L7.03298 28.6093ZM9.34325 37.3046L11.0393 36.0684L12.7283 37.3046L12.0862 35.3099L13.7683 34.0737H11.6884L11.0393 32.079L10.3972 34.0737H8.31027L9.99235 35.3099L9.34325 37.3046ZM17.3702 42.4249L15.6811 43.6681L16.3233 41.6734L14.6412 40.4302H16.7281L17.3772 38.4355L18.0193 40.4302H20.0992L18.4171 41.6734L19.0593 43.6681L17.3702 42.4249ZM24.3149 46L26.0039 44.7568L27.693 46L27.0509 44.0052L28.733 42.7691H26.653L26.0109 40.7743L25.3618 42.7691H23.2749L24.957 44.0052L24.3149 46ZM34.6372 42.4249L32.9412 43.6681L33.5903 41.6734L31.9012 40.4302H33.9951L34.6372 38.4355L35.2863 40.4302H37.3593L35.6842 41.6734L36.3263 43.6681L34.6372 42.4249ZM39.2647 37.3046L40.9607 36.0684L42.6498 37.3046L42.0076 35.3099L43.6827 34.0737H41.6098L40.9607 32.079L40.3186 34.0737H38.2247L39.9138 35.3099L39.2647 37.3046ZM43.2779 27.3731L41.5819 28.6093L42.231 26.6216L40.5489 25.3784H42.6358L43.2849 23.3836L43.927 25.3784H46L44.3249 26.6216L44.967 28.6093L43.2779 27.3731ZM17.3702 12.3214L15.6811 13.5646L16.3233 11.5698L14.6412 10.3266H16.7281L17.3772 8.33189L18.0193 10.3266H20.0992L18.4171 11.5698L19.0593 13.5646L17.3702 12.3214ZM9.34325 19.928L11.0393 18.6918L12.7283 19.928L12.0862 17.9332L13.7683 16.6971H11.6884L11.0393 14.7023L10.3972 16.6971H8.31027L9.99235 17.9332L9.34325 19.928Z" fill="#FFCC00"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.691 26.8367V25.8391H21.2723V28.0538C21.2723 29.1711 20.6973 29.8096 19.5883 29.8096C18.4793 29.8096 17.9043 29.1711 17.9043 28.0538V24.4225C17.9043 23.3052 18.4793 22.6667 19.5883 22.6667C20.6973 22.6667 21.2723 23.3052 21.2723 24.4225V25.1009H20.2044V24.3527C20.2044 23.8539 19.9785 23.6644 19.6191 23.6644C19.2597 23.6644 19.0338 23.8539 19.0338 24.3527V28.1237C19.0338 28.6225 19.2597 28.802 19.6191 28.802C19.9785 28.802 20.2044 28.6225 20.2044 28.1237V26.8367H19.691ZM22.3165 29.7297V22.7465H24.1032C25.2327 22.7465 25.7872 23.355 25.7872 24.4724V28.0039C25.7872 29.1212 25.2327 29.7297 24.1032 29.7297H22.3165ZM24.0832 23.7441H23.4465V28.7321H24.0832C24.4425 28.7321 24.6582 28.5526 24.6582 28.0538V24.4225C24.6582 23.9237 24.4425 23.7441 24.0832 23.7441ZM28.4852 22.7465C29.6147 22.7465 30.1692 23.355 30.1692 24.4724V25.3802C30.1692 26.4975 29.6147 27.106 28.4852 27.106H27.9512V29.7297H26.8217V22.7465H28.4852ZM28.4848 23.7441H27.9508V26.1084H28.4848C28.8441 26.1084 29.0392 25.9488 29.0392 25.45V24.4025C29.0392 23.9037 28.8441 23.7441 28.4848 23.7441ZM34.5708 29.7297H33.4208C33.3592 29.5502 33.3181 29.4404 33.3181 28.8718V27.7744C33.3181 27.126 33.0922 26.8866 32.5788 26.8866H32.1886V29.7297H31.0591V22.7465H32.7636C33.9342 22.7465 34.4373 23.2752 34.4373 24.3526V24.9013C34.4373 25.6196 34.2012 26.0885 33.698 26.3179C34.2628 26.5474 34.4476 27.0761 34.4476 27.8044V28.8818C34.4476 29.221 34.4579 29.4704 34.5708 29.7297ZM32.7333 23.7441H32.1891V25.889H32.6306C33.0516 25.889 33.3083 25.7094 33.3083 25.1507V24.4624C33.3083 23.9636 33.1338 23.7441 32.7333 23.7441Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

+9
View File
@@ -0,0 +1,9 @@
<svg width="52" height="52" viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 26C0 11.6406 11.6406 0 26 0C40.3594 0 52 11.6406 52 26C52 40.3594 40.3594 52 26 52C11.6406 52 0 40.3594 0 26Z" fill="#3D539E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.1434 9V42.8939C22.9916 42.8918 22.8392 42.8939 22.6672 42.8939C13.4707 42.8939 6 35.3065 6 26.0655C6 16.5874 13.4707 9 22.6672 9C22.8392 9 22.9916 9.00209 23.1434 9Z" fill="white"/>
<path d="M23.1419 15.9558L20.886 17.5326L21.6983 15.0027L19.5703 13.4259H22.2105L23.1419 10.896V15.9558Z" fill="#3D539E"/>
<path d="M23.1433 15.9558L25.3992 17.5326L24.5868 15.0027L26.7148 13.4259H24.0747L23.1433 10.896V15.9558Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.2154 18.2045C41.1503 18.4463 43.26 18.5724 43.26 18.5724C43.26 18.5724 42.1094 19.5149 40.9533 19.5149C39.2311 19.5149 37.81 18.9534 37.1019 18.6736C36.9994 18.6331 36.9119 18.5985 36.8405 18.5724C35.6593 18.1398 34.3512 17.4197 33.3753 16.6066C32.3995 15.7934 31.0775 15.0338 29.9711 14.8321C28.8647 14.6304 28.1509 15.1874 27.6137 15.6688C27.3371 15.9168 27.2526 16.1548 27.109 16.5592C26.9738 16.94 26.7862 17.4684 26.3364 18.2916C25.409 19.9889 23.147 20.0365 23.147 20.0365V19.5149C23.147 19.5149 23.7836 19.5013 24.719 19.005C25.6544 18.5087 26.0527 16.942 26.2371 15.8959C26.4214 14.8498 27.1245 13.4088 28.9889 13.1967C30.8533 12.9846 32.7231 14.4067 34.3187 15.6688C35.1339 16.3136 37.1489 17.4111 40.2154 18.2045ZM28.1848 29.7639H30.3448V30.8349C30.3448 31.2061 30.3356 31.4424 30.3173 31.544C30.299 31.6455 30.2558 31.7203 30.1877 31.7683C30.1195 31.8163 29.998 31.8404 29.8231 31.8404H29.6797V31.991H31.5621V31.8404H31.4675C31.2824 31.8404 31.1563 31.8142 31.0892 31.7618C31.0221 31.7093 30.9809 31.6291 30.9656 31.521C30.9504 31.4129 30.9427 31.1842 30.9427 30.8349V28.6569C30.9427 28.2879 30.9534 28.0542 30.9748 27.956C30.9961 27.8577 31.0383 27.7906 31.1014 27.7546C31.1644 27.7185 31.2977 27.7005 31.501 27.7005H31.5621V27.5499H29.6797V27.7005H29.7895C29.999 27.7005 30.1348 27.7202 30.1968 27.7595C30.2589 27.7988 30.299 27.8697 30.3173 27.9724C30.3356 28.075 30.3448 28.3031 30.3448 28.6569V29.5379H28.1848V28.6569C28.1848 28.2879 28.1955 28.0542 28.2168 27.956C28.2382 27.8577 28.2804 27.7906 28.3434 27.7546C28.4065 27.7185 28.5387 27.7005 28.7401 27.7005H28.8163V27.5499H26.9553V27.7005H27.0316C27.2411 27.7005 27.3768 27.7202 27.4389 27.7595C27.5009 27.7988 27.5416 27.8697 27.5609 27.9724C27.5802 28.075 27.5899 28.3031 27.5899 28.6569V30.8349C27.5899 31.2061 27.5802 31.4424 27.5609 31.544C27.5416 31.6455 27.4979 31.7203 27.4297 31.7683C27.3616 31.8163 27.239 31.8404 27.0621 31.8404H26.9553V31.991H28.8163V31.8404H28.7095C28.5245 31.8404 28.3989 31.8142 28.3328 31.7618C28.2667 31.7093 28.2255 31.6291 28.2092 31.521C28.1929 31.4129 28.1848 31.1842 28.1848 30.8349V29.7639ZM31.9338 27.5499V27.7005H32.0223C32.2318 27.7005 32.3681 27.7202 32.4311 27.7595C32.4942 27.7988 32.5348 27.8697 32.5532 27.9724C32.5715 28.075 32.5806 28.3031 32.5806 28.6569V30.8349C32.5806 31.2061 32.5715 31.4424 32.5532 31.544C32.5348 31.6455 32.4916 31.7203 32.4235 31.7683C32.3554 31.8163 32.2328 31.8404 32.0559 31.8404H31.9338V31.991H33.8253V31.8404H33.7033C33.5162 31.8404 33.3896 31.8142 33.3235 31.7618C33.2574 31.7093 33.2167 31.6291 33.2015 31.521C33.1862 31.4129 33.1786 31.1842 33.1786 30.8349V28.6569C33.1786 28.2879 33.1893 28.0542 33.2106 27.956C33.232 27.8577 33.2742 27.7906 33.3372 27.7546C33.4003 27.7185 33.5335 27.7005 33.7369 27.7005H33.8253V27.5499H31.9338ZM35.4222 30.8349V29.9014H35.8768C36.3507 29.9014 36.6817 29.8534 36.8698 29.7573C37.058 29.6613 37.2024 29.5204 37.3031 29.3348C37.4037 29.1492 37.4541 28.9353 37.4541 28.6929C37.4541 28.4134 37.3804 28.182 37.2329 27.9986C37.0854 27.8151 36.8963 27.6874 36.6654 27.6154C36.4346 27.5433 36.1056 27.5073 35.6785 27.5073C35.4466 27.5073 35.1233 27.5149 34.7083 27.5302L34.1897 27.5499V27.7005H34.266C34.4755 27.7005 34.6117 27.7202 34.6748 27.7595C34.7378 27.7988 34.7785 27.8697 34.7968 27.9724C34.8151 28.075 34.8243 28.3031 34.8243 28.6569V30.8349C34.8243 31.2061 34.8151 31.4424 34.7968 31.544C34.7785 31.6455 34.7353 31.7203 34.6672 31.7683C34.599 31.8163 34.4765 31.8404 34.2995 31.8404H34.1897V31.991H36.0843V31.8404H35.947C35.7619 31.8404 35.6358 31.8142 35.5687 31.7618C35.5015 31.7093 35.4604 31.6291 35.4451 31.521C35.4299 31.4129 35.4222 31.1842 35.4222 30.8349ZM35.718 29.7376H35.4221V27.6808C35.4953 27.672 35.6011 27.6677 35.7394 27.6677C36.1157 27.6677 36.3877 27.7534 36.5555 27.9248C36.7233 28.0962 36.8072 28.3653 36.8072 28.7321C36.8072 28.9723 36.7584 29.1743 36.6608 29.338C36.5631 29.5018 36.4411 29.6093 36.2947 29.6606C36.1482 29.7119 35.956 29.7376 35.718 29.7376ZM38.6249 30.3535H40.1961L40.5134 31.1755C40.6069 31.4135 40.6537 31.5566 40.6537 31.6046C40.6537 31.6985 40.6186 31.7613 40.5484 31.7929C40.4783 31.8246 40.3435 31.8404 40.1442 31.8404V31.9911H41.7346V31.8404L41.7488 31.8379L41.7703 31.8404V31.9911H42.9641V31.8404C42.8319 31.8404 42.7236 31.806 42.6392 31.7372C42.5548 31.6685 42.5126 31.5675 42.5126 31.4343C42.5126 31.3098 42.5919 31.0435 42.7505 30.6351L42.8573 30.3535H44.4285L44.7458 31.1755C44.8393 31.4135 44.8861 31.5566 44.8861 31.6046C44.8861 31.6985 44.851 31.7613 44.7809 31.7929C44.7107 31.8246 44.576 31.8404 44.3766 31.8404V31.9911H46.0027V31.8404C45.8583 31.8251 45.75 31.7875 45.6778 31.7274C45.6056 31.6674 45.5481 31.5849 45.5054 31.4801L45.3163 31.0151L43.9343 27.4877H43.8153L42.6133 30.5696C42.5258 30.7902 42.477 30.9168 42.4668 30.9496C42.3407 31.2967 42.2329 31.5244 42.1434 31.6324C42.0563 31.7377 41.9247 31.8062 41.7488 31.8379C41.6151 31.8213 41.514 31.7845 41.4454 31.7274C41.3732 31.6674 41.31Line truncated
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.14677 17.538C8.14677 17.538 10.3838 16.9304 11.9794 15.6683C13.5751 14.4062 15.4448 12.9841 17.3092 13.1962C19.1736 13.4083 19.8767 14.8493 20.0611 15.8954C20.2454 16.9415 20.6438 18.5082 21.5791 19.0045C22.5145 19.5008 23.143 19.5145 23.143 19.5145V20.036C23.143 20.036 20.8891 19.9884 19.9617 18.2911C19.0343 16.5938 19.2216 16.1498 18.6844 15.6683C18.1472 15.1869 17.4334 14.6299 16.327 14.8316C15.2206 15.0334 13.8986 15.7929 12.9228 16.6061C11.947 17.4192 10.6388 18.1393 9.45762 18.5719C8.2764 19.0045 7.1875 19.2289 7.1875 19.2289L8.14677 17.538Z" fill="#3D539E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.9916 42.9998C22.9916 42.9998 22.6486 42.5607 22.4081 42.3683C21.6318 41.7472 20.9942 41.1623 20.9942 40.4901C20.9942 39.3267 22.1425 39.3243 22.0677 38.7345C21.9929 38.1446 20.1119 37.5482 20.1119 35.8112C20.1119 34.3119 21.9326 33.7941 21.9326 33.1336C21.9326 32.4731 19.5805 31.9959 19.4809 30.3178C19.3714 28.4741 21.8048 27.9755 21.8048 27.5349C21.8048 26.87 17.3652 26.3046 17.3652 23.127C17.3652 21.7545 18.3777 20.9619 19.3595 20.9619C21.4626 20.9619 21.6711 22.3361 22.1444 22.6194C22.3444 22.739 22.6614 22.9048 22.8323 23.127C23.066 23.4309 23.13 23.9395 22.8323 24.2709C22.3169 24.8447 21.4177 24.334 21.3232 24.179C21.2288 24.024 20.999 23.3056 20.7376 23.0223C20.4761 22.739 19.7252 22.5377 19.3595 22.6194C18.9937 22.701 18.6295 23.127 19.0332 24.179C19.493 25.3772 23.1409 26.0546 23.1409 27.4516C23.1409 28.8486 20.5666 29.2169 20.7376 30.3932C20.9086 31.5695 23.1409 32.0379 23.1409 33.1336C23.1409 34.2293 21.2069 34.9776 21.2069 35.8895C21.2069 36.8014 23.1409 37.5954 23.1409 38.7345C23.1409 39.8736 21.9326 39.8736 21.9326 40.4901C21.9326 40.9372 22.4027 41.4877 22.7468 42.126C22.8956 42.402 22.9916 42.9998 22.9916 42.9998Z" fill="#3D539E"/>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

+22
View File
@@ -0,0 +1,22 @@
declare const coherentpdf: typeof import('../../src/types/coherentpdf.global').coherentpdf;
interface GenerateTOCMessage {
command: 'generate-toc';
pdfData: ArrayBuffer;
title: string;
fontSize: number;
fontFamily: number;
addBookmark: boolean;
}
interface TOCSuccessResponse {
status: 'success';
pdfBytes: ArrayBuffer;
}
interface TOCErrorResponse {
status: 'error';
message: string;
}
type TOCResponse = TOCSuccessResponse | TOCErrorResponse;
@@ -0,0 +1,61 @@
self.importScripts('/coherentpdf.browser.min.js');
function generateTableOfContentsInWorker(
pdfData,
title,
fontSize,
fontFamily,
addBookmark
) {
try {
const uint8Array = new Uint8Array(pdfData);
const pdf = coherentpdf.fromMemory(uint8Array, '');
coherentpdf.startGetBookmarkInfo(pdf);
const bookmarkCount = coherentpdf.numberBookmarks();
coherentpdf.endGetBookmarkInfo();
if (bookmarkCount === 0) {
coherentpdf.deletePdf(pdf);
self.postMessage({
status: 'error',
message:
'This PDF does not have any bookmarks. Please add bookmarks first using the Bookmark tool.',
});
return;
}
coherentpdf.tableOfContents(pdf, fontFamily, fontSize, title, addBookmark);
const outputBytes = coherentpdf.toMemory(pdf, false, false);
const outputBytesBuffer = outputBytes.buffer;
coherentpdf.deletePdf(pdf);
self.postMessage(
{
status: 'success',
pdfBytes: outputBytesBuffer,
},
[outputBytesBuffer]
);
} catch (error) {
self.postMessage({
status: 'error',
message:
error instanceof Error
? error.message
: 'Unknown error occurred during table of contents generation.',
});
}
}
self.onmessage = (e) => {
if (e.data.command === 'generate-toc') {
generateTableOfContentsInWorker(
e.data.pdfData,
e.data.title,
e.data.fontSize,
e.data.fontFamily,
e.data.addBookmark
);
}
};
+131
View File
@@ -0,0 +1,131 @@
body {
color: #374151;
}
.sortable-ghost {
opacity: 0.4;
border: 2px dashed #2b7fff;
}
.sortable-drag {
cursor: move;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 1rem;
}
.modal-content {
background: white;
border-radius: 0.5rem;
max-width: 500px;
width: 100%;
max-height: 90vh;
overflow-y: auto;
}
.modal-minimized {
position: fixed;
top: 1rem;
right: 1rem;
max-width: 350px;
max-height: none;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
z-index: 1001;
}
.child-container {
position: relative;
padding-left: 1.5rem;
margin-top: 0.5rem;
}
.child-container::before {
content: '';
position: absolute;
left: 0.75rem;
top: 0;
bottom: 0;
width: 2px;
background: #e5e7eb;
}
.child-container > li {
position: relative;
}
.child-container > li::before {
content: '';
position: absolute;
left: -0.75rem;
top: 1.25rem;
width: 0.75rem;
height: 2px;
background: #e5e7eb;
}
.style-preview {
padding: 0.5rem;
border: 1px solid #e5e7eb;
border-radius: 0.25rem;
min-height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
}
.destination-marker {
position: absolute;
width: 30px;
height: 30px;
margin-left: -15px;
margin-top: -15px;
pointer-events: none;
z-index: 10;
}
.destination-marker svg {
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
}
.pdf-canvas-wrapper {
position: relative;
display: inline-block;
}
.pdf-canvas-wrapper.picking-mode {
cursor: crosshair;
}
.coordinate-tooltip {
position: absolute;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
pointer-events: none;
z-index: 20;
white-space: nowrap;
}
.destination-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(59, 130, 246, 0.1);
border: 2px solid #3b82f6;
pointer-events: none;
z-index: 5;
}
+6
View File
@@ -83,6 +83,12 @@ export const categories = [
icon: 'bookmark', icon: 'bookmark',
subtitle: 'Add, edit, import, delete and extract PDF bookmarks.', subtitle: 'Add, edit, import, delete and extract PDF bookmarks.',
}, },
{
href: '/src/pages/table-of-contents.html',
name: 'Table of Contents',
icon: 'list',
subtitle: 'Generate a table of contents page from PDF bookmarks.',
},
{ {
id: 'add-page-numbers', id: 'add-page-numbers',
name: 'Page Numbers', name: 'Page Numbers',
+116 -48
View File
@@ -5,6 +5,7 @@ import { PDFDocument, PDFName, PDFString, PDFNumber, PDFArray } from 'pdf-lib';
import * as pdfjsLib from 'pdfjs-dist'; import * as pdfjsLib from 'pdfjs-dist';
import Sortable from 'sortablejs'; import Sortable from 'sortablejs';
import { createIcons, icons } from 'lucide'; import { createIcons, icons } from 'lucide';
import '../../css/bookmark.css';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs', 'pdfjs-dist/build/pdf.worker.min.mjs',
@@ -245,10 +246,10 @@ function showInputModal(title, fields = [], defaultValues = {}) {
// Store modal references // Store modal references
savedModalOverlay = overlay; savedModalOverlay = overlay;
savedModal = modal; savedModal = modal;
// Hide modal completely // Hide modal completely
overlay.style.display = 'none'; overlay.style.display = 'none';
startDestinationPicking((page, pdfX, pdfY) => { startDestinationPicking((page, pdfX, pdfY) => {
const destPageInput = modal.querySelector('#modal-dest-page'); const destPageInput = modal.querySelector('#modal-dest-page');
const destXInput = modal.querySelector('#modal-dest-x'); const destXInput = modal.querySelector('#modal-dest-x');
@@ -257,10 +258,10 @@ function showInputModal(title, fields = [], defaultValues = {}) {
if (destPageInput) destPageInput.value = page; if (destPageInput) destPageInput.value = page;
if (destXInput) destXInput.value = Math.round(pdfX); if (destXInput) destXInput.value = Math.round(pdfX);
if (destYInput) destYInput.value = Math.round(pdfY); if (destYInput) destYInput.value = Math.round(pdfY);
// Restore modal // Restore modal
overlay.style.display = ''; overlay.style.display = '';
// Update preview to show the destination after a short delay to ensure modal is visible // Update preview to show the destination after a short delay to ensure modal is visible
setTimeout(() => { setTimeout(() => {
updateDestinationPreview(); updateDestinationPreview();
@@ -268,7 +269,7 @@ function showInputModal(title, fields = [], defaultValues = {}) {
}); });
}); });
} }
// Add validation for page input // Add validation for page input
const destPageInput = modal.querySelector('#modal-dest-page'); const destPageInput = modal.querySelector('#modal-dest-page');
if (destPageInput) { if (destPageInput) {
@@ -284,7 +285,7 @@ function showInputModal(title, fields = [], defaultValues = {}) {
} }
updateDestinationPreview(); updateDestinationPreview();
}); });
destPageInput.addEventListener('blur', (e) => { destPageInput.addEventListener('blur', (e) => {
const value = parseInt(e.target.value); const value = parseInt(e.target.value);
const maxPages = parseInt(e.target.max) || 1; const maxPages = parseInt(e.target.max) || 1;
@@ -298,32 +299,34 @@ function showInputModal(title, fields = [], defaultValues = {}) {
updateDestinationPreview(); updateDestinationPreview();
}); });
} }
// Function to update destination preview // Function to update destination preview
function updateDestinationPreview() { function updateDestinationPreview() {
if (!pdfJsDoc) return; if (!pdfJsDoc) return;
const destPageInput = modal.querySelector('#modal-dest-page'); const destPageInput = modal.querySelector('#modal-dest-page');
const destXInput = modal.querySelector('#modal-dest-x'); const destXInput = modal.querySelector('#modal-dest-x');
const destYInput = modal.querySelector('#modal-dest-y'); const destYInput = modal.querySelector('#modal-dest-y');
const destZoomSelect = modal.querySelector('#modal-dest-zoom'); const destZoomSelect = modal.querySelector('#modal-dest-zoom');
const pageNum = destPageInput ? parseInt(destPageInput.value) : currentPage; const pageNum = destPageInput
? parseInt(destPageInput.value)
: currentPage;
const x = destXInput ? parseFloat(destXInput.value) : null; const x = destXInput ? parseFloat(destXInput.value) : null;
const y = destYInput ? parseFloat(destYInput.value) : null; const y = destYInput ? parseFloat(destYInput.value) : null;
const zoom = destZoomSelect ? destZoomSelect.value : null; const zoom = destZoomSelect ? destZoomSelect.value : null;
if (pageNum >= 1 && pageNum <= pdfJsDoc.numPages) { if (pageNum >= 1 && pageNum <= pdfJsDoc.numPages) {
// Render the page with zoom if specified // Render the page with zoom if specified
renderPageWithDestination(pageNum, x, y, zoom); renderPageWithDestination(pageNum, x, y, zoom);
} }
} }
// Add listeners for X, Y, and zoom changes // Add listeners for X, Y, and zoom changes
const destXInput = modal.querySelector('#modal-dest-x'); const destXInput = modal.querySelector('#modal-dest-x');
const destYInput = modal.querySelector('#modal-dest-y'); const destYInput = modal.querySelector('#modal-dest-y');
const destZoomSelect = modal.querySelector('#modal-dest-zoom'); const destZoomSelect = modal.querySelector('#modal-dest-zoom');
if (destXInput) { if (destXInput) {
destXInput.addEventListener('input', updateDestinationPreview); destXInput.addEventListener('input', updateDestinationPreview);
} }
@@ -433,7 +436,7 @@ function cancelDestinationPicking() {
destinationMarker.remove(); destinationMarker.remove();
destinationMarker = null; destinationMarker = null;
} }
// Remove coordinate display // Remove coordinate display
const coordDisplay = document.getElementById('destination-coord-display'); const coordDisplay = document.getElementById('destination-coord-display');
if (coordDisplay) { if (coordDisplay) {
@@ -496,20 +499,22 @@ document.addEventListener('DOMContentLoaded', () => {
const page = await pdfJsDoc.getPage(currentPage); const page = await pdfJsDoc.getPage(currentPage);
viewport = page.getViewport({ scale: currentZoom }); viewport = page.getViewport({ scale: currentZoom });
} }
// Convert canvas pixel coordinates to PDF coordinates // Convert canvas pixel coordinates to PDF coordinates
// The canvas CSS size matches viewport dimensions, so coordinates map directly // The canvas CSS size matches viewport dimensions, so coordinates map directly
// PDF uses bottom-left origin, canvas uses top-left // PDF uses bottom-left origin, canvas uses top-left
const scaleX = viewport.width / rect.width; const scaleX = viewport.width / rect.width;
const scaleY = viewport.height / rect.height; const scaleY = viewport.height / rect.height;
const pdfX = canvasX * scaleX; const pdfX = canvasX * scaleX;
const pdfY = viewport.height - (canvasY * scaleY); const pdfY = viewport.height - canvasY * scaleY;
// Remove old marker and coordinate display // Remove old marker and coordinate display
if (destinationMarker) { if (destinationMarker) {
destinationMarker.remove(); destinationMarker.remove();
} }
const oldCoordDisplay = document.getElementById('destination-coord-display'); const oldCoordDisplay = document.getElementById(
'destination-coord-display'
);
if (oldCoordDisplay) { if (oldCoordDisplay) {
oldCoordDisplay.remove(); oldCoordDisplay.remove();
} }
@@ -527,16 +532,21 @@ document.addEventListener('DOMContentLoaded', () => {
const canvasRect = canvas.getBoundingClientRect(); const canvasRect = canvas.getBoundingClientRect();
const wrapperRect = canvasWrapper.getBoundingClientRect(); const wrapperRect = canvasWrapper.getBoundingClientRect();
destinationMarker.style.position = 'absolute'; destinationMarker.style.position = 'absolute';
destinationMarker.style.left = (canvasX + canvasRect.left - wrapperRect.left) + 'px'; destinationMarker.style.left =
destinationMarker.style.top = (canvasY + canvasRect.top - wrapperRect.top) + 'px'; canvasX + canvasRect.left - wrapperRect.left + 'px';
destinationMarker.style.top =
canvasY + canvasRect.top - wrapperRect.top + 'px';
canvasWrapper.appendChild(destinationMarker); canvasWrapper.appendChild(destinationMarker);
// Create persistent coordinate display // Create persistent coordinate display
const coordDisplay = document.createElement('div'); const coordDisplay = document.createElement('div');
coordDisplay.id = 'destination-coord-display'; coordDisplay.id = 'destination-coord-display';
coordDisplay.className = 'absolute bg-blue-500 text-white px-2 py-1 rounded text-xs font-mono z-50 pointer-events-none'; coordDisplay.className =
coordDisplay.style.left = (canvasX + canvasRect.left - wrapperRect.left + 20) + 'px'; 'absolute bg-blue-500 text-white px-2 py-1 rounded text-xs font-mono z-50 pointer-events-none';
coordDisplay.style.top = (canvasY + canvasRect.top - wrapperRect.top - 30) + 'px'; coordDisplay.style.left =
canvasX + canvasRect.left - wrapperRect.left + 20 + 'px';
coordDisplay.style.top =
canvasY + canvasRect.top - wrapperRect.top - 30 + 'px';
coordDisplay.textContent = `X: ${Math.round(pdfX)}, Y: ${Math.round(pdfY)}`; coordDisplay.textContent = `X: ${Math.round(pdfX)}, Y: ${Math.round(pdfY)}`;
canvasWrapper.appendChild(coordDisplay); canvasWrapper.appendChild(coordDisplay);
@@ -638,6 +648,12 @@ const jsonInput = document.getElementById('json-input');
const autoExtractCheckbox = document.getElementById('auto-extract-checkbox'); const autoExtractCheckbox = document.getElementById('auto-extract-checkbox');
const appEl = document.getElementById('app'); const appEl = document.getElementById('app');
const uploaderEl = document.getElementById('uploader'); const uploaderEl = document.getElementById('uploader');
const fileDisplayArea = document.getElementById(
'file-display-area'
) as HTMLElement;
const backToToolsBtn = document.getElementById(
'back-to-tools'
) as HTMLButtonElement;
const canvas = document.getElementById('pdf-canvas'); const canvas = document.getElementById('pdf-canvas');
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
const pageIndicator = document.getElementById('page-indicator'); const pageIndicator = document.getElementById('page-indicator');
@@ -973,6 +989,37 @@ collapseAllBtn.addEventListener('click', () => {
renderBookmarkTree(); renderBookmarkTree();
}); });
// Format bytes helper
function formatBytes(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 Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
}
// Render file display
function renderFileDisplay(file: File) {
if (!fileDisplayArea) return;
fileDisplayArea.innerHTML = '';
fileDisplayArea.classList.remove('hidden');
const fileDiv = document.createElement('div');
fileDiv.className =
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
const nameSpan = document.createElement('span');
nameSpan.className = 'truncate font-medium text-gray-200';
nameSpan.textContent = file.name;
const sizeSpan = document.createElement('span');
sizeSpan.className = 'flex-shrink-0 ml-4 text-gray-400';
sizeSpan.textContent = formatBytes(file.size);
fileDiv.append(nameSpan, sizeSpan);
fileDisplayArea.appendChild(fileDiv);
}
fileInput.addEventListener('change', loadPDF); fileInput.addEventListener('change', loadPDF);
async function loadPDF(e) { async function loadPDF(e) {
@@ -981,6 +1028,7 @@ async function loadPDF(e) {
originalFileName = file.name.replace('.pdf', ''); originalFileName = file.name.replace('.pdf', '');
filenameDisplay.textContent = originalFileName; filenameDisplay.textContent = originalFileName;
renderFileDisplay(file);
const arrayBuffer = await file.arrayBuffer(); const arrayBuffer = await file.arrayBuffer();
currentPage = 1; currentPage = 1;
@@ -1056,47 +1104,47 @@ async function renderPage(num, zoom = null, destX = null, destY = null) {
if (!pdfJsDoc) return; if (!pdfJsDoc) return;
const page = await pdfJsDoc.getPage(num); const page = await pdfJsDoc.getPage(num);
let zoomScale = currentZoom; let zoomScale = currentZoom;
if (zoom !== null && zoom !== '' && zoom !== '0') { if (zoom !== null && zoom !== '' && zoom !== '0') {
zoomScale = parseFloat(zoom) / 100; zoomScale = parseFloat(zoom) / 100;
} }
const dpr = window.devicePixelRatio || 1; const dpr = window.devicePixelRatio || 1;
let viewport = page.getViewport({ scale: zoomScale }); let viewport = page.getViewport({ scale: zoomScale });
currentViewport = viewport; currentViewport = viewport;
canvas.height = viewport.height * dpr; canvas.height = viewport.height * dpr;
canvas.width = viewport.width * dpr; canvas.width = viewport.width * dpr;
// Set CSS size to maintain aspect ratio (this is what the browser displays) // Set CSS size to maintain aspect ratio (this is what the browser displays)
canvas.style.width = viewport.width + 'px'; canvas.style.width = viewport.width + 'px';
canvas.style.height = viewport.height + 'px'; canvas.style.height = viewport.height + 'px';
// Scale the canvas context to match device pixel ratio // Scale the canvas context to match device pixel ratio
ctx.scale(dpr, dpr); ctx.scale(dpr, dpr);
await page.render({ canvasContext: ctx, viewport: viewport }).promise; await page.render({ canvasContext: ctx, viewport: viewport }).promise;
// Draw destination marker if coordinates are provided // Draw destination marker if coordinates are provided
if (destX !== null && destY !== null) { if (destX !== null && destY !== null) {
const canvasX = destX; const canvasX = destX;
const canvasY = viewport.height - destY; // Flip Y axis (PDF bottom-left, canvas top-left) const canvasY = viewport.height - destY; // Flip Y axis (PDF bottom-left, canvas top-left)
// Draw marker on canvas with animation effect // Draw marker on canvas with animation effect
ctx.save(); ctx.save();
ctx.strokeStyle = '#3b82f6'; ctx.strokeStyle = '#3b82f6';
ctx.fillStyle = '#3b82f6'; ctx.fillStyle = '#3b82f6';
ctx.lineWidth = 3; ctx.lineWidth = 3;
ctx.shadowBlur = 10; ctx.shadowBlur = 10;
ctx.shadowColor = 'rgba(59, 130, 246, 0.5)'; ctx.shadowColor = 'rgba(59, 130, 246, 0.5)';
ctx.beginPath(); ctx.beginPath();
ctx.arc(canvasX, canvasY, 12, 0, 2 * Math.PI); ctx.arc(canvasX, canvasY, 12, 0, 2 * Math.PI);
ctx.fill(); ctx.fill();
ctx.shadowBlur = 0; ctx.shadowBlur = 0;
// Draw crosshair // Draw crosshair
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(canvasX - 15, canvasY); ctx.moveTo(canvasX - 15, canvasY);
@@ -1104,26 +1152,26 @@ async function renderPage(num, zoom = null, destX = null, destY = null) {
ctx.moveTo(canvasX, canvasY - 15); ctx.moveTo(canvasX, canvasY - 15);
ctx.lineTo(canvasX, canvasY + 15); ctx.lineTo(canvasX, canvasY + 15);
ctx.stroke(); ctx.stroke();
// Draw inner circle // Draw inner circle
ctx.beginPath(); ctx.beginPath();
ctx.arc(canvasX, canvasY, 6, 0, 2 * Math.PI); ctx.arc(canvasX, canvasY, 6, 0, 2 * Math.PI);
ctx.fill(); ctx.fill();
ctx.stroke(); ctx.stroke();
// Draw coordinate text background // Draw coordinate text background
const text = `X: ${Math.round(destX)}, Y: ${Math.round(destY)}`; const text = `X: ${Math.round(destX)}, Y: ${Math.round(destY)}`;
ctx.font = 'bold 12px monospace'; ctx.font = 'bold 12px monospace';
const textMetrics = ctx.measureText(text); const textMetrics = ctx.measureText(text);
const textWidth = textMetrics.width; const textWidth = textMetrics.width;
const textHeight = 18; const textHeight = 18;
ctx.fillStyle = 'rgba(59, 130, 246, 0.95)'; ctx.fillStyle = 'rgba(59, 130, 246, 0.95)';
ctx.fillRect(canvasX + 18, canvasY - 25, textWidth + 10, textHeight); ctx.fillRect(canvasX + 18, canvasY - 25, textWidth + 10, textHeight);
ctx.fillStyle = 'white'; ctx.fillStyle = 'white';
ctx.fillText(text, canvasX + 23, canvasY - 10); ctx.fillText(text, canvasX + 23, canvasY - 10);
ctx.restore(); ctx.restore();
} }
@@ -1423,8 +1471,13 @@ function createNodeElement(node, level = 0) {
// Check if bookmark has a custom destination // Check if bookmark has a custom destination
if (node.destX !== null || node.destY !== null || node.zoom !== null) { if (node.destX !== null || node.destY !== null || node.zoom !== null) {
// Render page with destination highlighted and zoom applied // Render page with destination highlighted and zoom applied
await renderPageWithDestination(node.page, node.destX, node.destY, node.zoom); await renderPageWithDestination(
node.page,
node.destX,
node.destY,
node.zoom
);
// Highlight the destination briefly (2 seconds) // Highlight the destination briefly (2 seconds)
setTimeout(() => { setTimeout(() => {
// Re-render without highlight but keep the zoom if it was set // Re-render without highlight but keep the zoom if it was set
@@ -1768,7 +1821,6 @@ extractExistingBtn.addEventListener('click', async () => {
} }
}); });
async function extractExistingBookmarks(doc) { async function extractExistingBookmarks(doc) {
try { try {
const outlines = doc.catalog.lookup(PDFName.of('Outlines')); const outlines = doc.catalog.lookup(PDFName.of('Outlines'));
@@ -1791,7 +1843,9 @@ async function extractExistingBookmarks(doc) {
try { try {
function addNamePair(nameObj, destObj) { function addNamePair(nameObj, destObj) {
try { try {
const key = nameObj.decodeText ? nameObj.decodeText() : String(nameObj); const key = nameObj.decodeText
? nameObj.decodeText()
: String(nameObj);
namedDests.set(key, resolveRef(destObj)); namedDests.set(key, resolveRef(destObj));
} catch (_) { } catch (_) {
// ignore malformed entry // ignore malformed entry
@@ -1803,7 +1857,9 @@ async function extractExistingBookmarks(doc) {
node = resolveRef(node); node = resolveRef(node);
if (!node) return; if (!node) return;
const namesArray = node.lookup ? node.lookup(PDFName.of('Names')) : null; const namesArray = node.lookup
? node.lookup(PDFName.of('Names'))
: null;
if (namesArray && namesArray.array) { if (namesArray && namesArray.array) {
for (let i = 0; i < namesArray.array.length; i += 2) { for (let i = 0; i < namesArray.array.length; i += 2) {
const n = namesArray.array[i]; const n = namesArray.array[i];
@@ -1847,7 +1903,8 @@ async function extractExistingBookmarks(doc) {
if (pageRef.numberValue !== undefined) { if (pageRef.numberValue !== undefined) {
const numericIndex = pageRef.numberValue | 0; const numericIndex = pageRef.numberValue | 0;
if (numericIndex >= 0 && numericIndex < pages.length) return numericIndex; if (numericIndex >= 0 && numericIndex < pages.length)
return numericIndex;
} }
if (pageRef.objectNumber !== undefined) { if (pageRef.objectNumber !== undefined) {
@@ -1859,7 +1916,9 @@ async function extractExistingBookmarks(doc) {
if (pageRef.toString) { if (pageRef.toString) {
const target = pageRef.toString(); const target = pageRef.toString();
const idxByString = pages.findIndex((p) => p.ref.toString() === target); const idxByString = pages.findIndex(
(p) => p.ref.toString() === target
);
if (idxByString !== -1) return idxByString; if (idxByString !== -1) return idxByString;
} }
@@ -1883,7 +1942,7 @@ async function extractExistingBookmarks(doc) {
// Try Dest entry first // Try Dest entry first
let dest = item.lookup(PDFName.of('Dest')); let dest = item.lookup(PDFName.of('Dest'));
// If no Dest, try Action/D // If no Dest, try Action/D
if (!dest) { if (!dest) {
const action = resolveRef(item.lookup(PDFName.of('A'))); const action = resolveRef(item.lookup(PDFName.of('A')));
@@ -1901,7 +1960,10 @@ async function extractExistingBookmarks(doc) {
} else if (dest.lookup) { } else if (dest.lookup) {
// Some named destinations resolve to a dictionary with 'D' entry // Some named destinations resolve to a dictionary with 'D' entry
const maybeDict = resolveRef(dest); const maybeDict = resolveRef(dest);
const dictD = maybeDict && maybeDict.lookup ? maybeDict.lookup(PDFName.of('D')) : null; const dictD =
maybeDict && maybeDict.lookup
? maybeDict.lookup(PDFName.of('D'))
: null;
if (dictD) dest = resolveRef(dictD); if (dictD) dest = resolveRef(dictD);
} }
} catch (_) { } catch (_) {
@@ -1974,7 +2036,7 @@ async function extractExistingBookmarks(doc) {
style, style,
destX, destX,
destY, destY,
zoom zoom,
}; };
// Process children (make sure to resolve refs) // Process children (make sure to resolve refs)
@@ -1985,7 +2047,6 @@ async function extractExistingBookmarks(doc) {
child = resolveRef(child.lookup(PDFName.of('Next'))); child = resolveRef(child.lookup(PDFName.of('Next')));
} }
if (pageIndex === 0 && bookmark.children.length > 0) { if (pageIndex === 0 && bookmark.children.length > 0) {
const firstChild = bookmark.children[0]; const firstChild = bookmark.children[0];
if (firstChild) { if (firstChild) {
@@ -2014,6 +2075,13 @@ async function extractExistingBookmarks(doc) {
} }
} }
// Back to tools button
if (backToToolsBtn) {
backToToolsBtn.addEventListener('click', () => {
window.location.href = '../../index.html#tools-header';
});
}
downloadBtn.addEventListener('click', async () => { downloadBtn.addEventListener('click', async () => {
const pages = pdfLibDoc.getPages(); const pages = pdfLibDoc.getPages();
const outlinesDict = pdfLibDoc.context.obj({}); const outlinesDict = pdfLibDoc.context.obj({});
+229
View File
@@ -0,0 +1,229 @@
const worker = new Worker('/workers/table-of-contents.worker.js');
let pdfFile: File | null = null;
const dropZone = document.getElementById('drop-zone') as HTMLElement;
const fileInput = document.getElementById('file-input') as HTMLInputElement;
const generateBtn = document.getElementById(
'generate-btn'
) as HTMLButtonElement;
const tocTitleInput = document.getElementById('toc-title') as HTMLInputElement;
const fontSizeSelect = document.getElementById(
'font-size'
) as HTMLSelectElement;
const fontFamilySelect = document.getElementById(
'font-family'
) as HTMLSelectElement;
const addBookmarkCheckbox = document.getElementById(
'add-bookmark'
) as HTMLInputElement;
const statusMessage = document.getElementById('status-message') as HTMLElement;
const fileDisplayArea = document.getElementById(
'file-display-area'
) as HTMLElement;
const backToToolsBtn = document.getElementById(
'back-to-tools'
) as HTMLButtonElement;
interface GenerateTOCMessage {
command: 'generate-toc';
pdfData: ArrayBuffer;
title: string;
fontSize: number;
fontFamily: number;
addBookmark: boolean;
}
interface TOCSuccessResponse {
status: 'success';
pdfBytes: ArrayBuffer;
}
interface TOCErrorResponse {
status: 'error';
message: string;
}
type TOCWorkerResponse = TOCSuccessResponse | TOCErrorResponse;
// Show status message
function showStatus(
message: string,
type: 'success' | 'error' | 'info' = 'info'
) {
statusMessage.textContent = message;
statusMessage.className = `mt-4 p-3 rounded-lg text-sm ${
type === 'success'
? 'bg-green-900 text-green-200'
: type === 'error'
? 'bg-red-900 text-red-200'
: 'bg-blue-900 text-blue-200'
}`;
statusMessage.classList.remove('hidden');
}
// Hide status message
function hideStatus() {
statusMessage.classList.add('hidden');
}
// Format bytes helper
function formatBytes(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 Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
}
// Render file display
function renderFileDisplay(file: File) {
fileDisplayArea.innerHTML = '';
fileDisplayArea.classList.remove('hidden');
const fileDiv = document.createElement('div');
fileDiv.className =
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
const nameSpan = document.createElement('span');
nameSpan.className = 'truncate font-medium text-gray-200';
nameSpan.textContent = file.name;
const sizeSpan = document.createElement('span');
sizeSpan.className = 'flex-shrink-0 ml-4 text-gray-400';
sizeSpan.textContent = formatBytes(file.size);
fileDiv.append(nameSpan, sizeSpan);
fileDisplayArea.appendChild(fileDiv);
}
// Handle file selection
function handleFileSelect(file: File) {
if (file.type !== 'application/pdf') {
showStatus('Please select a PDF file.', 'error');
return;
}
pdfFile = file;
generateBtn.disabled = false;
renderFileDisplay(file);
// showStatus(`File selected: ${file.name}`, 'success');
}
// Drag and drop handlers
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('border-blue-500');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('border-blue-500');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('border-blue-500');
const file = e.dataTransfer?.files[0];
if (file) {
handleFileSelect(file);
}
});
fileInput.addEventListener('change', (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
handleFileSelect(file);
}
});
// Generate table of contents
async function generateTableOfContents() {
if (!pdfFile) {
showStatus('Please select a PDF file first.', 'error');
return;
}
try {
generateBtn.disabled = true;
showStatus('Reading file (Main Thread)...', 'info');
const arrayBuffer = await pdfFile.arrayBuffer();
showStatus('Generating table of contents...', 'info');
const title = tocTitleInput.value || 'Table of Contents';
const fontSize = parseInt(fontSizeSelect.value, 10);
const fontFamily = parseInt(fontFamilySelect.value, 10);
const addBookmark = addBookmarkCheckbox.checked;
const message: GenerateTOCMessage = {
command: 'generate-toc',
pdfData: arrayBuffer,
title,
fontSize,
fontFamily,
addBookmark,
};
worker.postMessage(message, [arrayBuffer]);
} catch (error) {
console.error('Error reading file:', error);
showStatus(
`Error reading file: ${error instanceof Error ? error.message : 'Unknown error occurred'}`,
'error'
);
generateBtn.disabled = false;
}
}
// Handle messages from worker
worker.onmessage = (e: MessageEvent<TOCWorkerResponse>) => {
generateBtn.disabled = false;
if (e.data.status === 'success') {
const pdfBytesBuffer = e.data.pdfBytes;
const pdfBytes = new Uint8Array(pdfBytesBuffer);
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download =
pdfFile?.name.replace('.pdf', '_with_toc.pdf') || 'output_with_toc.pdf';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showStatus(
'Table of contents generated successfully! Download started.',
'success'
);
hideStatus();
pdfFile = null;
fileInput.value = '';
fileDisplayArea.innerHTML = '';
fileDisplayArea.classList.add('hidden');
generateBtn.disabled = true;
} else if (e.data.status === 'error') {
const errorMessage = e.data.message || 'Unknown error occurred in worker.';
console.error('Worker Error:', errorMessage);
showStatus(`Error: ${errorMessage}`, 'error');
}
};
worker.onerror = (error) => {
console.error('Worker error:', error);
showStatus('Worker error occurred. Check console for details.', 'error');
generateBtn.disabled = false;
};
// Back to tools button
if (backToToolsBtn) {
backToToolsBtn.addEventListener('click', () => {
window.location.href = '../../index.html#tools-header';
});
}
generateBtn.addEventListener('click', generateTableOfContents);
+17 -140
View File
@@ -6,139 +6,7 @@
<title>Advanced PDF Bookmark Tool - BentoPDF</title> <title>Advanced PDF Bookmark Tool - BentoPDF</title>
<link rel="icon" type="image/png" href="../../images/favicon.svg" /> <link rel="icon" type="image/png" href="../../images/favicon.svg" />
<link href="../../src/css/styles.css" rel="stylesheet" /> <link href="../../src/css/styles.css" rel="stylesheet" />
<style> <link href="../../src/css/bookmark.css" rel="stylesheet" />
body {
color: #374151;
}
.sortable-ghost {
opacity: 0.4;
border: 2px dashed #2b7fff;
}
.sortable-drag {
cursor: move;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 1rem;
}
.modal-content {
background: white;
border-radius: 0.5rem;
max-width: 500px;
width: 100%;
max-height: 90vh;
overflow-y: auto;
}
.modal-minimized {
position: fixed;
top: 1rem;
right: 1rem;
max-width: 350px;
max-height: none;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
z-index: 1001;
}
.child-container {
position: relative;
padding-left: 1.5rem;
margin-top: 0.5rem;
}
.child-container::before {
content: '';
position: absolute;
left: 0.75rem;
top: 0;
bottom: 0;
width: 2px;
background: #e5e7eb;
}
.child-container > li {
position: relative;
}
.child-container > li::before {
content: '';
position: absolute;
left: -0.75rem;
top: 1.25rem;
width: 0.75rem;
height: 2px;
background: #e5e7eb;
}
.style-preview {
padding: 0.5rem;
border: 1px solid #e5e7eb;
border-radius: 0.25rem;
min-height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
}
.destination-marker {
position: absolute;
width: 30px;
height: 30px;
margin-left: -15px;
margin-top: -15px;
pointer-events: none;
z-index: 10;
}
.destination-marker svg {
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
}
.pdf-canvas-wrapper {
position: relative;
display: inline-block;
}
.pdf-canvas-wrapper.picking-mode {
cursor: crosshair;
}
.coordinate-tooltip {
position: absolute;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
pointer-events: none;
z-index: 20;
white-space: nowrap;
}
.destination-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(59, 130, 246, 0.1);
border: 2px solid #3b82f6;
pointer-events: none;
z-index: 5;
}
</style>
</head> </head>
<body class="antialiased bg-gray-900"> <body class="antialiased bg-gray-900">
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30"> <nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30">
@@ -235,8 +103,15 @@
class="min-h-screen flex items-center justify-center p-4 bg-gray-900" class="min-h-screen flex items-center justify-center p-4 bg-gray-900"
> >
<div <div
class="bg-gray-900 rounded-xl shadow-xl p-8 max-w-md w-full text-gray-200" class="bg-gray-800 rounded-xl shadow-xl p-8 max-w-md w-full text-gray-200 border border-gray-700"
> >
<button
id="back-to-tools"
class="flex items-center gap-2 text-indigo-400 hover:text-indigo-300 mb-6 font-semibold"
>
<i data-lucide="arrow-left" class="cursor-pointer"></i>
<span class="cursor-pointer"> Back to Tools </span>
</button>
<p class="text-gray-400 mb-6"> <p class="text-gray-400 mb-6">
Upload a PDF to begin editing bookmarks Upload a PDF to begin editing bookmarks
</p> </p>
@@ -244,7 +119,7 @@
<!-- Drop Zone for Main PDF Upload --> <!-- Drop Zone for Main PDF Upload -->
<div <div
id="drop-zone" id="drop-zone"
class="relative flex flex-col items-center justify-center w-full h-48 border-2 border-dashed border-gray-600 rounded-xl cursor-pointer bg-gray-800 hover:bg-gray-700 transition-colors duration-300" class="relative flex flex-col items-center justify-center w-full h-48 border-2 border-dashed border-gray-600 rounded-xl cursor-pointer bg-gray-700 hover:bg-gray-600 transition-colors duration-300"
> >
<div class="flex flex-col items-center justify-center pt-5 pb-6"> <div class="flex flex-col items-center justify-center pt-5 pb-6">
<i <i
@@ -268,6 +143,9 @@
/> />
</div> </div>
<!-- File Display Area -->
<div id="file-display-area" class="mt-4 hidden"></div>
<!-- Auto Extract Checkbox --> <!-- Auto Extract Checkbox -->
<div class="mt-4"> <div class="mt-4">
<label class="flex items-center gap-2 text-sm text-gray-300"> <label class="flex items-center gap-2 text-sm text-gray-300">
@@ -295,10 +173,7 @@
class="relative flex flex-col items-center justify-center w-full h-32 mt-2 border-2 border-dashed border-gray-600 rounded-xl cursor-pointer bg-gray-800 hover:bg-gray-700 transition-colors duration-300" class="relative flex flex-col items-center justify-center w-full h-32 mt-2 border-2 border-dashed border-gray-600 rounded-xl cursor-pointer bg-gray-800 hover:bg-gray-700 transition-colors duration-300"
> >
<div class="flex flex-col items-center justify-center pt-4 pb-4"> <div class="flex flex-col items-center justify-center pt-4 pb-4">
<i <i data-lucide="sheet" class="w-8 h-8 mb-2 text-gray-400"></i>
data-lucide="sheet"
class="w-8 h-8 mb-2 text-gray-400"
></i>
<p class="text-xs text-gray-400"> <p class="text-xs text-gray-400">
<span class="font-semibold">Click to upload CSV</span> or drag <span class="font-semibold">Click to upload CSV</span> or drag
here here
@@ -339,7 +214,9 @@
</div> </div>
<div id="app" class="hidden bg-gray-900 min-h-screen"> <div id="app" class="hidden bg-gray-900 min-h-screen">
<header class="bg-gray-800 border-b border-gray-700 shadow-sm sticky top-0 z-50"> <header
class="bg-gray-800 border-b border-gray-700 shadow-sm sticky top-0 z-50"
>
<div class="max-w-7xl mx-auto px-4 py-3"> <div class="max-w-7xl mx-auto px-4 py-3">
<div class="flex items-center justify-between flex-wrap gap-2"> <div class="flex items-center justify-between flex-wrap gap-2">
<h1 class="text-xl font-bold text-white" id="filename-display"> <h1 class="text-xl font-bold text-white" id="filename-display">
+255
View File
@@ -0,0 +1,255 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Generate Table of Contents - BentoPDF</title>
<link rel="icon" type="image/png" href="../../images/favicon.svg" />
<link href="../../src/css/styles.css" rel="stylesheet" />
</head>
<body class="antialiased bg-gray-900">
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30">
<div class="container mx-auto px-4">
<div class="flex justify-between items-center h-16">
<div class="flex-shrink-0 flex items-center cursor-pointer" id="home-logo">
<img src="../../public/images/favicon.svg" alt="Bento PDF Logo" class="h-8 w-8" />
<span class="text-white font-bold text-xl ml-2">
<a href="../../index.html">BentoPDF</a>
</span>
</div>
<!-- Desktop Navigation -->
<div class="hidden md:flex items-center space-x-8 text-white">
<a href="../../index.html" class="nav-link">Home</a>
<a href="../../about.html" class="nav-link">About</a>
<a href="../../contact.html" class="nav-link">Contact</a>
<a href="../../index.html#tools-header" class="nav-link">All Tools</a>
</div>
<!-- Mobile Hamburger Button -->
<div class="md:hidden flex items-center">
<button id="mobile-menu-button" type="button"
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 transition-colors"
aria-controls="mobile-menu" aria-expanded="false">
<span class="sr-only">Open main menu</span>
<!-- Hamburger Icon -->
<svg id="menu-icon" class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
<!-- Close Icon -->
<svg id="close-icon" class="hidden h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<!-- Mobile Menu Dropdown -->
<div id="mobile-menu" class="hidden md:hidden bg-gray-800 border-t border-gray-700">
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
<a href="../../index.html" class="mobile-nav-link">Home</a>
<a href="../../about.html" class="mobile-nav-link">About</a>
<a href="../../contact.html" class="mobile-nav-link">Contact</a>
<a href="../../index.html#tools-header" class="mobile-nav-link">All Tools</a>
</div>
</div>
</nav>
<div id="uploader" class="min-h-screen flex items-center justify-center p-4 bg-gray-900">
<div class="bg-gray-800 rounded-xl shadow-xl p-8 max-w-2xl w-full text-gray-200 border border-gray-700">
<button id="back-to-tools"
class="flex items-center gap-2 text-indigo-400 hover:text-indigo-300 mb-6 font-semibold">
<i data-lucide="arrow-left" class="cursor-pointer"></i>
<span class="cursor-pointer"> Back to Tools </span>
</button>
<h1 class="text-2xl font-bold text-white mb-2">
Generate Table of Contents
</h1>
<p class="text-gray-400 mb-6">
Upload a PDF with bookmarks to generate a table of contents page
</p>
<!-- Drop Zone for Main PDF Upload -->
<div id="drop-zone"
class="relative flex flex-col items-center justify-center w-full h-48 border-2 border-dashed border-gray-600 rounded-xl cursor-pointer bg-gray-700 hover:bg-gray-600 transition-colors duration-300">
<div class="flex flex-col items-center justify-center pt-5 pb-6">
<i data-lucide="upload-cloud" class="w-10 h-10 mb-3 text-gray-400"></i>
<p class="mb-2 text-sm text-gray-300">
<span class="font-semibold">Click to select a file</span> or drag
and drop
</p>
<p class="text-xs text-gray-500">A single PDF file</p>
<p class="text-xs text-gray-500">
Your files never leave your device.
</p>
</div>
<input type="file" id="file-input" accept=".pdf"
class="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer" />
</div>
<!-- File Display Area -->
<div id="file-display-area" class="mt-4 hidden"></div>
<!-- Options Section -->
<div class="mt-6 space-y-4">
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">
TOC Title
</label>
<input type="text" id="toc-title" value="Table of Contents"
class="w-full px-4 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Table of Contents" />
</div>
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">
Font Size
</label>
<select id="font-size"
class="w-full px-4 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="10">10pt</option>
<option value="11">11pt</option>
<option value="12" selected>12pt</option>
<option value="14">14pt</option>
<option value="16">16pt</option>
<option value="18">18pt</option>
<option value="20">20pt</option>
<option value="22">22pt</option>
<option value="24">24pt</option>
<option value="26">26pt</option>
<option value="30">30pt</option>
<option value="36">36pt</option>
<option value="42">42pt</option>
<option value="48">48pt</option>
<option value="60">60pt</option>
<option value="72">72pt</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">
Font Family
</label>
<select id="font-family"
class="w-full px-4 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="0">Times Roman</option>
<option value="1">Times Bold</option>
<option value="2">Times Italic</option>
<option value="3">Times Bold Italic</option>
<option value="4" selected>Helvetica</option>
<option value="5">Helvetica Bold</option>
<option value="6">Helvetica Oblique</option>
<option value="7">Helvetica Bold Oblique</option>
<option value="8">Courier</option>
<option value="9">Courier Bold</option>
<option value="10">Courier Oblique</option>
<option value="11">Courier Bold Oblique</option>
</select>
</div>
<div class="flex items-center gap-2">
<input type="checkbox" id="add-bookmark" checked class="w-4 h-4 accent-blue-500" />
<label for="add-bookmark" class="text-sm text-gray-300">
Add bookmark for TOC page
</label>
</div>
</div>
<!-- Generate Button -->
<button id="generate-btn" disabled class="btn-gradient w-full mt-6">
<span id="generate-btn-text">Generate Table of Contents</span>
</button>
<!-- Status Message -->
<div id="status-message" class="mt-4 hidden p-3 rounded-lg text-sm"></div>
</div>
</div>
<footer class="mt-16 border-t-2 border-gray-700 py-8 bg-[#111827]">
<div class="container mx-auto px-4">
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left">
<div class="mb-8 md:mb-0">
<div class="flex items-center justify-center md:justify-start mb-4">
<img src="../../public/images/favicon.svg" alt="Bento PDF Logo" class="h-10 w-10 mr-3" />
<span class="text-xl font-bold text-white">BentoPDF</span>
</div>
<p class="text-gray-400 text-sm">
&copy; 2025 BentoPDF. All rights reserved.
</p>
</div>
<div>
<h3 class="font-bold text-white mb-4">Company</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a href="../../about.html" class="hover:text-indigo-400">About Us</a>
</li>
<li>
<a href="../../faq.html" class="hover:text-indigo-400">FAQ</a>
</li>
<li>
<a href="../../contact.html" class="hover:text-indigo-400">Contact Us</a>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4">Legal</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a href="../../terms.html" class="hover:text-indigo-400">Terms and Conditions</a>
</li>
<li>
<a href="../../privacy.html" class="hover:text-indigo-400">Privacy Policy</a>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4">Follow Us</h3>
<div class="flex justify-center md:justify-start space-x-4">
<a href="https://github.com/alam00000/bentopdf" target="_blank" rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400" title="GitHub">
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd" />
</svg>
</a>
<a href="https://discord.gg/q42xWQmJ" target="_blank" rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400" title="Discord">
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z" />
</svg>
</a>
<a href="https://www.instagram.com/thebentopdf/" class="text-gray-400 hover:text-indigo-400"
title="Instagram">
<i data-lucide="instagram"></i>
</a>
<a href="https://www.linkedin.com/company/bentopdf/" class="text-gray-400 hover:text-indigo-400"
title="LinkedIn">
<i data-lucide="linkedin"></i>
</a>
<a href="https://x.com/BentoPDF" class="text-gray-400 hover:text-indigo-400" title="X (Twitter)">
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
</a>
</div>
</div>
</div>
</div>
</footer>
<script type="module" src="../js/utils/lucide-init.ts"></script>
<script type="module" src="../js/logic/table-of-contents.ts"></script>
<script type="module" src="../js/mobileMenu.ts"></script>
</body>
</html>
+231
View File
@@ -0,0 +1,231 @@
/*
* coherentpdf.global.d.ts — TypeScript type definitions for BentoPDF / CoherentPDF integration
*
* These type definitions were written by Alam for use in the BentoPDF project.
* They describe APIs provided by the CoherentPDF library (cpdf.js) but are original
* work created for type safety and integration.
*
* Copyright © 2025 BentoPDF
* Licensed under the GNU Affero General Public License v3.0 or later (AGPLv3+).
*/
declare global {
/** Opaque type representing a loaded PDF document instance. */
type CoherentPdf = object;
/** Represents a page range, which is an array of page numbers. */
type CpdfPageRange = number[];
// --- Type Aliases from Constants ---
type CpdfPermission = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
type CpdfEncryptionMethod = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
type CpdfPaperSize =
| 0
| 1
| 2
| 3
| 4
| 5
| 6
| 7
| 8
| 9
| 10
| 11
| 12
| 13
| 14
| 15;
type CpdfPositionAnchor =
| 0
| 1
| 2
| 3
| 4
| 5
| 6
| 7
| 8
| 9
| 10
| 11
| 12;
type CpdfFont = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11;
type CpdfJustification = 0 | 1 | 2;
type CpdfLayout = 0 | 1 | 2 | 3 | 4 | 5;
type CpdfMode = 0 | 1 | 2 | 3 | 4;
type CpdfLabelStyle = 0 | 1 | 2 | 3 | 4;
}
// This tells TypeScript that a variable named 'coherentpdf'
// exists in the global scope and has this shape.
declare const coherentpdf: {
/**
* Returns a string giving the version number of the CPDF library.
* @returns The version number.
*/
version(): string;
/**
* Sets the global operation mode to 'fast'. The default is 'slow' mode, which works
* even on old-fashioned files.
*/
setFast(): void;
/**
* Sets the global operation mode to 'slow'. The default is 'slow' mode, which works
* even on old-fashioned files.
*/
setSlow(): void;
/**
* Delete a PDF so the memory representing it may be recovered. Must be called for every loaded PDF.
* @param pdf PDF document to delete.
*/
deletePdf(pdf: CoherentPdf): void;
/**
* A debug function which prints some information about resource usage.
* Can be used to detect if PDFs or ranges are being deallocated properly.
*/
onexit(): void;
/**
* Loads a PDF file from a given filename. Supply a user password (possibly blank)
* if the file is encrypted.
* @param filename File name.
* @param userpw User password, or blank if none.
* @returns The loaded PDF document instance.
*/
fromFile(filename: string, userpw: string): CoherentPdf;
/**
* Loads a PDF from a file, doing only minimal parsing (lazily).
* @param filename File name.
* @param userpw User password, or blank if none.
* @returns The loaded PDF document instance.
*/
fromFileLazy(filename: string, userpw: string): CoherentPdf;
/**
* Loads a file from memory given any user password.
* @param data PDF document as an array of bytes.
* @param userpw User password, or blank if none.
* @returns The loaded PDF document instance.
*/
fromMemory(data: Uint8Array, userpw: string): CoherentPdf;
/**
* Loads a file from memory, but lazily (minimal parsing).
* @param data PDF document as an array of bytes.
* @param userpw User password, or blank if none.
* @returns The loaded PDF document instance.
*/
fromMemoryLazy(data: Uint8Array, userpw: string): CoherentPdf;
/**
* Writes the PDF document to memory as a byte array.
* @param pdf The PDF document.
* @param linearize If true, linearizes the PDF.
* @param make_id If true, generates a new `/ID`.
* @returns The PDF document as a byte array.
*/
toMemory(pdf: CoherentPdf, linearize: boolean, make_id: boolean): Uint8Array;
/**
* Returns the total number of pages in the PDF document.
* @param pdf The PDF document.
* @returns The number of pages.
*/
pages(pdf: CoherentPdf): number;
/**
* Starts the process of retrieving bookmark information from a PDF.
* Follow with `numberBookmarks` and accessor functions.
* @param pdf The PDF document.
*/
startGetBookmarkInfo(pdf: CoherentPdf): void;
/**
* Gets the total number of bookmarks available after calling `startGetBookmarkInfo`.
* @returns The number of bookmarks.
*/
numberBookmarks(): number;
/**
* Gets the nesting level (0-based) for the bookmark at index `n`.
* @param n The bookmark index (0-based).
* @returns The bookmark level.
*/
getBookmarkLevel(n: number): number;
/**
* Gets the target page number (1-based) for the bookmark at index `n`.
* @param pdf The PDF document.
* @param n The bookmark index (0-based).
* @returns The target page number.
*/
getBookmarkPage(pdf: CoherentPdf, n: number): number;
/**
* Returns the text title of the bookmark at index `n`.
* @param n The bookmark index (0-based).
* @returns The bookmark text.
*/
getBookmarkText(n: number): string;
/**
* Returns the open/closed status for the bookmark at index `n`.
* @param n The bookmark index (0-based).
* @returns True if the bookmark is open.
*/
getBookmarkOpenStatus(n: number): boolean;
/**
* Ends the bookmark retrieval process and cleans up resources.
*/
endGetBookmarkInfo(): void;
/**
* Typesets a Table of Contents (TOC) page based on existing bookmarks and prepends it to the document.
* @param pdf The PDF document.
* @param font The font constant for the TOC text.
* @param fontsize The font size for the TOC text.
* @param title The title for the TOC page.
* @param bookmark If true, the TOC page itself gets a bookmark.
*/
tableOfContents(
pdf: CoherentPdf,
font: CpdfFont,
fontsize: number,
title: string,
bookmark: boolean
): void;
/** Times Roman font constant (0) */
readonly timesRoman: CpdfFont;
/** Times Bold font constant (1) */
readonly timesBold: CpdfFont;
/** Times Italic font constant (2) */
readonly timesItalic: CpdfFont;
/** Times Bold Italic font constant (3) */
readonly timesBoldItalic: CpdfFont;
/** Helvetica font constant (4) */
readonly helvetica: CpdfFont;
/** Helvetica Bold font constant (5) */
readonly helveticaBold: CpdfFont;
/** Helvetica Oblique font constant (6) */
readonly helveticaOblique: CpdfFont;
/** Helvetica Bold Oblique font constant (7) */
readonly helveticaBoldOblique: CpdfFont;
/** Courier font constant (8) */
readonly courier: CpdfFont;
/** Courier Bold font constant (9) */
readonly courierBold: CpdfFont;
/** Courier Oblique font constant (10) */
readonly courierOblique: CpdfFont;
/** Courier Bold Oblique font constant (11) */
readonly courierBoldOblique: CpdfFont;
};
export { coherentpdf };
+2 -2
View File
@@ -29,7 +29,7 @@
"compilerOptions": { "compilerOptions": {
"target": "ES2022", "target": "ES2022",
"module": "ESNext", "module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"], "lib": ["ES2022", "DOM", "DOM.Iterable", "WebWorker"],
"types": ["vite/client", "vitest/globals"], // Added vitest/globals "types": ["vite/client", "vitest/globals"], // Added vitest/globals
/* Easier module handling for Vite */ /* Easier module handling for Vite */
@@ -61,6 +61,6 @@
"@/*": ["./src/*"] "@/*": ["./src/*"]
} }
}, },
"include": ["src", "src/**/*.ts"], // Updated to include all TS files "include": ["src", "src/**/*.ts", "public/workers"], // Updated to include all TS files
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist"]
} }
+2 -2
View File
@@ -14,7 +14,7 @@ BentoPDF is a privacy first PDF Toolkit&#13;
&#13; &#13;
https://hub.docker.com/r/bentopdf/bentopdf/</Overview> https://hub.docker.com/r/bentopdf/bentopdf/</Overview>
<Category>Tools:</Category> <Category>Tools:</Category>
<WebUI>http://[IP]:[PORT:80]/</WebUI> <WebUI>http://[IP]:[PORT:8080]/</WebUI>
<TemplateURL/> <TemplateURL/>
<Icon>https://raw.githubusercontent.com/alam00000/bentopdf/refs/heads/main/public/images/favicon.svg</Icon> <Icon>https://raw.githubusercontent.com/alam00000/bentopdf/refs/heads/main/public/images/favicon.svg</Icon>
<ExtraParams/> <ExtraParams/>
@@ -24,7 +24,7 @@ https://hub.docker.com/r/bentopdf/bentopdf/</Overview>
<DonateText/> <DonateText/>
<DonateLink/> <DonateLink/>
<Requires/> <Requires/>
<Config Name="Web Port for GUI" Target="3000" Default="80" Mode="tcp" Description="Port used for BentoPDF's web GUI" Type="Port" Display="always" Required="false" Mask="false">80</Config> <Config Name="Web Port for Nginx and GUI" Target="8080" Default="8080" Mode="tcp" Description="Port used for BentoPDF's web GUI" Type="Port" Display="always" Required="false" Mask="false">80</Config>
<TailscaleStateDir/> <TailscaleStateDir/>
</Container> </Container>
+11
View File
@@ -26,6 +26,13 @@ export default defineConfig(({ mode }) => ({
}, },
optimizeDeps: { optimizeDeps: {
include: ['pdfkit', 'blob-stream'], include: ['pdfkit', 'blob-stream'],
exclude: ['coherentpdf'],
},
server: {
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
},
}, },
build: { build: {
rollupOptions: { rollupOptions: {
@@ -37,6 +44,10 @@ export default defineConfig(({ mode }) => ({
privacy: resolve(__dirname, 'privacy.html'), privacy: resolve(__dirname, 'privacy.html'),
terms: resolve(__dirname, 'terms.html'), terms: resolve(__dirname, 'terms.html'),
bookmark: resolve(__dirname, 'src/pages/bookmark.html'), bookmark: resolve(__dirname, 'src/pages/bookmark.html'),
'table-of-contents': resolve(
__dirname,
'src/pages/table-of-contents.html'
),
}, },
}, },
}, },