Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
# Changelog

## Unreleased

### Added

- Started the v2 cycle with a production Image category and local Image Converter.
- Added JPEG, PNG, and WebP input/output, lossy quality controls for JPEG/WebP, deterministic white JPEG transparency, metadata-stripping canvas re-encoding, collision-safe Unicode names, and ZIP batch output.
- Added per-file, queue, dimension, decoded-pixel, and 200-megapixel aggregate-work protections with recoverable errors.

### Changed

- Removed the standalone planned Rotate PDF card; page rotation remains available as part of PDF Organizer.
- Moved shared browser image validation and decoding into `tools/shared/image.js` for PDF and Image tools.

## 1.0.0

This section describes the planned v1.0.0 release candidate. The Git tag and GitHub release are intentionally separate release-manager actions and are not created by Sprint 11.
Secure Tools v1.0.0 was published on 2026-08-24 after the Sprint 11 release-candidate hardening and manual release process.

### PDF tools

Expand Down
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ Secure Tools is a privacy-first web hub for everyday file utilities. Production

The project uses a category-first architecture, a Pull Request/main CI gate, and static GitHub Pages deployment.

Release-candidate context is recorded in the [v1.0.0 changelog](./CHANGELOG.md) and [release QA checklist](./docs/release-qa.md). The v1.0.0 Git tag and GitHub release are deliberate release-manager actions after manual sign-off; Sprint 11 does not create them.
The v1 baseline is recorded in the [changelog](./CHANGELOG.md) and historical [v1.0.0 release QA checklist](./docs/release-qa.md). Sprint 12 begins the v2 cycle by expanding production tools into the Image category.

## Available tools

- [Image Converter](./tools/image/converter/) — convert batches of JPEG, PNG, and WebP images locally with predictable names and ZIP output.
- [Images to PDF](./tools/pdf/images-to-pdf/) — arrange JPEG, PNG, and WebP images and save them as one PDF.
- [Merge PDF](./tools/pdf/merge/) — validate, order, and combine PDF pages without rasterizing them.
- [Split PDF](./tools/pdf/split/) — extract ordered page ranges or create predictable per-page and fixed-interval archives.
Expand Down Expand Up @@ -75,6 +76,7 @@ The hub is a static site built with semantic HTML, CSS, and Vanilla JavaScript E
├── tools/
│ ├── shared/
│ │ ├── file.js
│ │ ├── image.js
│ │ ├── pdf.js
│ │ ├── tool.css
│ │ └── save.js
Expand All @@ -87,6 +89,8 @@ The hub is a static site built with semantic HTML, CSS, and Vanilla JavaScript E
│ │ ├── to-images/
│ │ └── metadata/
│ ├── image/
│ │ ├── index.html
│ │ └── converter/
│ ├── privacy/
│ ├── scan/
│ ├── media/
Expand All @@ -101,6 +105,7 @@ The hub is a static site built with semantic HTML, CSS, and Vanilla JavaScript E
├── ci-foundation.test.mjs
├── home-structure.test.mjs
├── image-to-pdf.test.mjs
├── image-converter.test.mjs
├── pdf-merge-and-categories.test.mjs
├── pdf-split.test.mjs
├── pdf-metadata.test.mjs
Expand All @@ -124,12 +129,26 @@ Categories are the stable navigation layer. Individual utilities can be added be

`/tools/image-to-pdf/` is a lightweight static migration page with a meta refresh and visible fallback link to `/tools/pdf/images-to-pdf/`. It does not use JavaScript to redirect.

## Image Converter

Image Converter is available at `/tools/image/converter/`.

- Accepts signature-validated JPEG, PNG, and WebP files and converts one or many sources sequentially with browser-native decode, canvas, and encode APIs.
- Supports JPEG, PNG, and WebP output. JPEG and WebP expose a 50–100% lossy quality control; PNG hides that irrelevant setting.
- Uses explicit browser orientation handling so normally displayed camera images are rendered in their intended orientation.
- Flattens transparent pixels onto white for JPEG while PNG and WebP keep the canvas alpha channel.
- Canvas re-encoding does not preserve EXIF or other embedded image metadata. The tool intentionally makes no configurable metadata-cleaning or preservation claim.
- Replaces supported source extensions, preserves Unicode, limits output bases to 120 characters and 180 UTF-8 bytes, and adds deterministic suffixes for collisions.
- Saves one converted image directly or packages multiple outputs into `converted_images.zip` with the existing same-origin JSZip build.
- Reuses the existing 50 MiB file, 100-file queue, 500 MiB queue, 16,384-pixel dimension, and 50-megapixel per-image limits, plus a 200-megapixel aggregate decoded-work limit per conversion.
- Keeps queued sources available after cancellation or recoverable decode, encode, archive, and save failures so the job can be retried without reloading.

## Images to PDF

Images to PDF is available at `/tools/pdf/images-to-pdf/`.

- `app.js` manages picker/drop input, duplicate-preserving queue state, ordering, removal, settings, progress, and save behavior.
- `image.js` validates and decodes JPEG, PNG, and WebP using browser-local APIs.
- `tools/shared/image.js` validates and decodes JPEG, PNG, and WebP using browser-local APIs shared with Image Converter.
- `pdf.js` owns page geometry, image placement, sequential preparation, and jsPDF generation.
- Preview object URLs and temporary download URLs are revoked when no longer needed.
- File admission validates JPEG, PNG, and WebP signatures before browser decoding; a supported signature may override a misleading MIME type or extension.
Expand Down Expand Up @@ -234,7 +253,7 @@ All processing libraries are pinned and served as same-origin static files. Prod
### JSZip

- Version: `3.10.1`
- Purpose: local multi-file PDF Split and PDF to Images archives
- Purpose: local multi-file Image Converter, PDF Split, and PDF to Images archives
- Package: `jszip@3.10.1` from npm
- License choice: MIT
- Details and hashes: [assets/vendor/jszip/README.md](./assets/vendor/jszip/README.md)
Expand Down Expand Up @@ -266,7 +285,7 @@ Run the complete local and CI validation entry point with:
node tests/run-all.mjs
```

It checks JavaScript syntax and runs Images to PDF, PDF Merge, PDF Split, PDF Organizer, PDF to Images, PDF Metadata, category-first homepage, system typography, CJK wrapping, long-copy layout, six-language catalog parity and placeholders, locale detection and persistence, static resource, privacy/network, security-hardening, dependency-integrity, save-path, ZIP, and CI workflow regression coverage. PDF fixtures are generated deterministically during tests; CI never processes real user files.
It checks JavaScript syntax and runs Image Converter, Images to PDF, PDF Merge, PDF Split, PDF Organizer, PDF to Images, PDF Metadata, category-first homepage, system typography, CJK wrapping, long-copy layout, six-language catalog parity and placeholders, locale detection and persistence, static resource, privacy/network, security-hardening, dependency-integrity, save-path, ZIP, and CI workflow regression coverage. Test fixtures are generated deterministically; CI never processes real user files.

## Production security controls

Expand Down Expand Up @@ -325,8 +344,8 @@ Production routes load only same-origin HTML, CSS, JavaScript, and vendored libr
The historical prototype is absent from the deployed tree. External URLs in documentation, licenses, and source links are informational or user-initiated; no production processing dependency is fetched remotely.
## Deferred work

- PDF rotate, compression, and encryption tools
- Image conversion, compression, and resizing
- Broader PDF modification, compression, and encryption workflows
- Image resizing, compression, and dedicated metadata inspection/cleaning
- Broader XMP and structural PDF metadata sanitization
- Scan/OCR and media tools
- Offline/PWA support
Expand Down
45 changes: 43 additions & 2 deletions js/locales/de.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export const de = {
"title": "PDF Metadata Inspector & Cleaner – Secure Tools",
"description": "Überprüfen und entfernen Sie unterstützte PDF-Metadaten lokal in Ihrem Browser."
},
"imageConverter": {
"title": "Bildkonverter – Secure Tools",
"description": "Konvertieren Sie JPEG-, PNG- und WebP-Bilder lokal in Ihrem Browser."
},
"pdfCategory": {
"title": "PDF-Tools – Secure Tools",
"description": "Erstellen und organisieren Sie PDF-Dokumente lokal in Ihrem Browser."
Expand Down Expand Up @@ -140,8 +144,6 @@ export const de = {
"imagesToPdf": "Ordnen Sie JPEG-, PNG- und WebP-Bilder in einem PDF an.",
"merge": "Kombinieren Sie PDF-Dateien in der von Ihnen gewählten Reihenfolge.",
"split": "Extrahieren Sie ausgewählte Seiten oder teilen Sie ein Dokument in lokale Dateien auf.",
"rotateTitle": "PDF drehen",
"rotate": "Korrigieren Sie die Seitenausrichtung, ohne die Dokumentqualität zu verändern.",
"organize": "Sehen Sie sich eine Vorschau an, ordnen Sie sie neu an, drehen Sie sie und entfernen Sie Seiten aus einer PDF-Datei.",
"toImages": "Konvertieren Sie geordnete PDF-Seiten in PNG-, JPEG- oder WebP-Bilder.",
"metadata": "Überprüfen Sie allgemeine Dokumentmetadaten und erstellen Sie eine verifizierte, bereinigte Kopie."
Expand Down Expand Up @@ -383,6 +385,45 @@ export const de = {
"save": "Der Speicherort konnte nicht geöffnet werden. Bitte versuchen Sie es erneut."
}
},
"imageConverter": {
"eyebrow": "Bildtool",
"title": "Bildkonverter",
"description": "Konvertieren Sie JPEG-, PNG- und WebP-Bilder lokal. Mehrere Dateien werden gemeinsam als ZIP gespeichert.",
"queue": { "meta": "{size} · Eintrag {position}" },
"settings": {
"title": "Konvertierungseinstellungen",
"format": "Ausgabeformat",
"jpeg": "JPEG",
"png": "PNG",
"webp": "WebP",
"quality": "Qualität",
"qualityHelp": "JPEG und WebP verwenden verlustbehaftete Qualität. PNG verwendet diese Einstellung nicht.",
"outputHelp": "Transparente Pixel werden in JPEG weiß. Die erneute Canvas-Kodierung entfernt eingebettete Bildmetadaten aus jeder Ausgabe.",
"imageFile": "Konvertiertes Bild",
"zipFile": "ZIP-Archiv"
},
"actions": { "convert": "Konvertieren und speichern" },
"status": {
"converting": "Bilder werden lokal konvertiert…",
"progress": "Bild {completed} von {total} wird konvertiert…",
"progressLabel": "Fortschritt der Bildkonvertierung",
"archive": "ZIP-Archiv wird vorbereitet…",
"saved": "{name} mit {count} konvertierten Bildern wurde gespeichert.",
"downloaded": "{name} mit {count} konvertierten Bildern wurde heruntergeladen.",
"saveCancelled": "Speichern abgebrochen. Es wurde keine Datei geschrieben."
},
"errors": {
"noFiles": "Fügen Sie vor der Konvertierung mindestens ein Bild hinzu.",
"format": "Wählen Sie JPEG, PNG oder WebP.",
"jobPixels": "Dieser Stapel überschreitet das kombinierte Limit von 200 Megapixeln für dekodierte Bilder. Konvertieren Sie weniger Bilder gleichzeitig.",
"canvas": "Dieser Browser kann die Bildzeichenfläche nicht erstellen.",
"encoder": "{name} konnte nicht in das ausgewählte Format kodiert werden.",
"archiveLibrary": "Die lokale Archivbibliothek konnte nicht geladen werden.",
"archive": "Das ZIP-Archiv konnte nicht erstellt werden.",
"failure": "{name} konnte nicht konvertiert werden. Das Bild ist möglicherweise beschädigt oder wird von diesem Browser nicht unterstützt.",
"save": "Der Speicherort konnte nicht geöffnet werden. Bitte versuchen Sie es erneut."
}
},
"imageToPdf": {
"eyebrow": "Bildtool",
"title": "Bilder in PDF",
Expand Down
26 changes: 25 additions & 1 deletion js/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const en = {
organizePdf: { title: "Organize PDF — Secure Tools", description: "Reorder, rotate, and remove PDF pages locally in your browser." },
pdfToImages: { title: "PDF to Images — Secure Tools", description: "Convert PDF pages to PNG, JPEG, or WebP images locally in your browser." },
pdfMetadata: { title: "PDF Metadata Inspector & Cleaner — Secure Tools", description: "Inspect and remove supported PDF metadata locally in your browser." },
imageConverter: { title: "Image Converter — Secure Tools", description: "Convert JPEG, PNG, and WebP images locally in your browser." },
pdfCategory: { title: "PDF tools — Secure Tools", description: "Create and organize PDF documents locally in your browser." },
imageCategory: { title: "Image tools — Secure Tools", description: "Private image tools designed to run locally in your browser." },
privacyCategory: { title: "Privacy tools — Secure Tools", description: "Inspect and remove hidden file details without uploading the source file." },
Expand Down Expand Up @@ -46,7 +47,7 @@ export const en = {
pdf: {
title: "PDF tools", description: "Create and organize PDF documents locally, without uploading private files.",
imagesToPdf: "Arrange JPEG, PNG, and WebP images into one PDF.", merge: "Combine PDF files in the order you choose.",
split: "Extract selected pages or divide a document into local files.", rotateTitle: "Rotate PDF", rotate: "Correct page orientation without changing document quality.",
split: "Extract selected pages or divide a document into local files.",
organize: "Preview, reorder, rotate, and remove pages from one PDF.",
toImages: "Convert ordered PDF pages to PNG, JPEG, or WebP images.",
metadata: "Inspect common document metadata and create a verified cleaned copy.",
Expand Down Expand Up @@ -154,6 +155,29 @@ export const en = {
library: "The local PDF library could not be loaded.", generation: "The PDF could not be merged. Try fewer or smaller files.", save: "The save location could not be opened. Please try again.",
},
},
imageConverter: {
eyebrow: "Image tool", title: "Image Converter", description: "Convert JPEG, PNG, and WebP images locally. Multiple files are saved together as one ZIP archive.",
queue: { meta: "{size} · item {position}" },
settings: {
title: "Conversion settings", format: "Output format", jpeg: "JPEG", png: "PNG", webp: "WebP", quality: "Quality",
qualityHelp: "JPEG and WebP use lossy quality. PNG does not use this setting.",
outputHelp: "Transparent pixels become white in JPEG. Canvas re-encoding removes embedded image metadata from every output.",
imageFile: "Converted image", zipFile: "ZIP archive",
},
actions: { convert: "Convert and save" },
status: {
converting: "Converting images locally…", progress: "Converting image {completed} of {total}…", progressLabel: "Image conversion progress",
archive: "Preparing the ZIP archive…", saved: "Saved {name} with {count} converted image(s).", downloaded: "Downloaded {name} with {count} converted image(s).",
saveCancelled: "Save cancelled. No file was written.",
},
errors: {
noFiles: "Add at least one image before converting.", format: "Choose JPEG, PNG, or WebP.",
jobPixels: "This batch exceeds the 200 megapixel combined decoded-work limit. Convert fewer images at once.",
canvas: "This browser cannot create the image canvas.", encoder: "{name} could not be encoded in the selected format.",
archiveLibrary: "The local archive library could not be loaded.", archive: "The ZIP archive could not be created.",
failure: "{name} could not be converted. The image may be damaged or unsupported by this browser.", save: "The save location could not be opened. Please try again.",
},
},
imageToPdf: {
eyebrow: "Image tool", title: "Images to PDF",
description: "Arrange JPEG, PNG, and WebP images and save them as one PDF. Everything happens on this device.",
Expand Down
45 changes: 43 additions & 2 deletions js/locales/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export const es = {
"title": "Metadatos del PDF — Secure Tools",
"description": "Inspeccione y elimine metadatos PDF compatibles localmente en su navegador."
},
"imageConverter": {
"title": "Convertidor de imágenes — Secure Tools",
"description": "Convierta imágenes JPEG, PNG y WebP localmente en su navegador."
},
"pdfCategory": {
"title": "Herramientas PDF — Secure Tools",
"description": "Cree y organice documentos PDF localmente en su navegador."
Expand Down Expand Up @@ -140,8 +144,6 @@ export const es = {
"imagesToPdf": "Organice imágenes JPEG, PNG y WebP en un PDF.",
"merge": "Combina archivos PDF en el orden que elijas.",
"split": "Extrae páginas seleccionadas o divide un documento en archivos locales.",
"rotateTitle": "Girar PDF",
"rotate": "Orientación correcta de la página sin cambiar la calidad del documento.",
"organize": "Obtenga una vista previa, reordene, rote y elimine páginas de un PDF.",
"toImages": "Convierte páginas PDF ordenadas a imágenes PNG, JPEG o WebP.",
"metadata": "Inspecciona los metadatos de documentos comunes y crea una copia limpia verificada."
Expand Down Expand Up @@ -383,6 +385,45 @@ export const es = {
"save": "No se pudo abrir la ubicación para guardar. Inténtelo de nuevo."
}
},
"imageConverter": {
"eyebrow": "Herramienta de imagen",
"title": "Convertidor de imágenes",
"description": "Convierta imágenes JPEG, PNG y WebP localmente. Varios archivos se guardan juntos en un único ZIP.",
"queue": { "meta": "{size} · elemento {position}" },
"settings": {
"title": "Ajustes de conversión",
"format": "Formato de salida",
"jpeg": "JPEG",
"png": "PNG",
"webp": "WebP",
"quality": "Calidad",
"qualityHelp": "JPEG y WebP usan calidad con pérdida. PNG no utiliza este ajuste.",
"outputHelp": "Los píxeles transparentes se vuelven blancos en JPEG. La recodificación con canvas elimina los metadatos de imagen de cada salida.",
"imageFile": "Imagen convertida",
"zipFile": "Archivo ZIP"
},
"actions": { "convert": "Convertir y guardar" },
"status": {
"converting": "Convirtiendo imágenes localmente…",
"progress": "Convirtiendo imagen {completed} de {total}…",
"progressLabel": "Progreso de conversión de imágenes",
"archive": "Preparando el archivo ZIP…",
"saved": "Se guardó {name} con {count} imagen(es) convertida(s).",
"downloaded": "Se descargó {name} con {count} imagen(es) convertida(s).",
"saveCancelled": "Guardado cancelado. No se escribió ningún archivo."
},
"errors": {
"noFiles": "Añada al menos una imagen antes de convertir.",
"format": "Elija JPEG, PNG o WebP.",
"jobPixels": "Este lote supera el límite combinado de 200 megapíxeles decodificados. Convierta menos imágenes a la vez.",
"canvas": "Este navegador no puede crear el lienzo de imagen.",
"encoder": "No se pudo codificar {name} en el formato seleccionado.",
"archiveLibrary": "No se pudo cargar la biblioteca de archivos local.",
"archive": "No se pudo crear el archivo ZIP.",
"failure": "No se pudo convertir {name}. La imagen puede estar dañada o no ser compatible con este navegador.",
"save": "No se pudo abrir la ubicación para guardar. Inténtelo de nuevo."
}
},
"imageToPdf": {
"eyebrow": "Herramienta de imagen",
"title": "Imágenes a PDF",
Expand Down
Loading
Loading