Не хочу грузить свои документы в онлайн-сервисы. Поэтому сжимаю PDF прямо в браузере

Нужно было загрузить на сайт посольства пакет документов одним PDF – свидетельство о рождении, дипломы, справки. Лимит – 1 МБ. Первое, что предлагает любой поисковик, – онлайн-сервисы «сжать PDF бесплатно». Загрузить туда сканы всех своих документов разом? Нет, спасибо.
Сжать PDF без загрузки на сервер: kompakt.xronocode.com/compress
TL;DR
Онлайн-компрессоры PDF почти всегда работают так: вы загружаете файл на чужой сервер. Что с ним происходит дальше – вы не знаете и проверить не можете.
Для личных документов и рабочих презентаций с финансовыми данными меня это не устраивает.
На компьютере альтернативы есть: Ghostscript, Preview, платные редакторы. На телефоне сжать PDF, не отправляя его никуда, почти нечем.
Сделал компрессор, который работает целиком в браузере – в том числе на iPhone и Android: файл не покидает устройство, после первого открытия всё работает даже без интернета. Проверить это можно самому за 30 секунд.
На сканах и фото он уменьшает файл на 66–97% в зависимости от выбранного качества. На текстовых PDF – проигрывает Ghostscript и честно об этом говорит.
Как всё началось
Пакет документов для посольства, многостраничный PDF, лимит формы – 1 МБ.
Онлайн-сервисы я отбросил сразу. Это сканы паспортных данных, свидетельства, дипломов – полный комплект для того, чтобы кто-то другой стал мной. Отдавать его сервису, о котором я знаю только то, что он «бесплатный», – странная идея. Даже если у сервиса написано «файлы удаляются через час», проверить это невозможно.
Платные редакторы на ноутбуке были. Но они файл не уменьшали, а один – тот самый, платный, официальный, на букву «А» – после «оптимизации» выдал файл больше исходного.
В итоге за вечер набросал скрипт вокруг Ghostscript – это gs -sDEVICE=pdfwrite -dPDFSETTINGS=/screen с обвязкой. Файл влез в лимит.
На этом история могла бы закончиться. Но.
Но
Та же ситуация потом повторялась постоянно, только с другими файлами. Презентация с финансовыми показателями, которую надо отправить, а почтовый сервер режет вложения. Отчёт со внутренними цифрами. Договор. Каждый раз – тот же выбор: либо ставить Ghostscript на очередной компьютер, либо грузить файл куда-то в интернет.
С чужого ноутбука или с телефона скрипт не запустишь. А грузить презентацию с финданными на непонятный сервер – это ровно то, за что потом бывает очень неловко, если файл где-нибудь всплывёт.
Поэтому захотелось вещь с тремя свойствами:
Файл не уходит с устройства. Вообще.
Работает в браузере, на любом компьютере и на телефоне, без установки.
Работает без интернета – открыл страницу один раз, дальше она доступна в самолёте, в поезде, где угодно.
Так появилась kompakt.xronocode.com/compress.
Главное – телефон
На компьютере вариантов на самом деле хватает. Ghostscript в командной строке, фильтр Quartz в Preview на Mac, платные редакторы, LibreOffice – можно сжать файл, никуда его не отправляя.
С телефоном всё иначе. А ведь документы чаще всего нужно отправить именно с него: сфотографировал справку, собрал PDF, а форма на сайте или почта не пропускает размер. Встроенной кнопки «сжать PDF» в «Файлах» на iPhone нет, на Android тоже. Приложения из сторов – в основном либо подписка, либо обработка на сервере разработчика: файл уходит туда же, куда я не хотел отдавать его на компьютере.
Поэтому мобильную версию я делал не «заодно», а как основной сценарий:
Открывается в Safari или Chrome, ничего не нужно ставить. Можно добавить на экран «Домой» – дальше работает как приложение и без интернета.
Не падает на больших файлах. Мобильный Safari перезагружает вкладку, если ей не хватило памяти. Поэтому на телефоне страница ограничена 4 мегапикселями – этого хватает, чтобы лист A4 оставался читаемым при печати.
Результат сразу можно отправить через системное меню «Поделиться» – в мессенджер или почту, не сохраняя файл вручную.
Как проверить, что файл никуда не уходит
Это главное, поэтому сразу – как убедиться самому, не веря мне на слово:
Откройте страницу, затем отключите интернет (режим полёта) и сожмите файл. Если бы он куда-то отправлялся – ничего бы не вышло.
Или откройте DevTools → вкладку Network, очистите список и сожмите файл. Единственные запросы, которые вы увидите, – к Google Analytics: анонимные события вроде «сжатие началось / завершилось», качество и размер в мегабайтах. Ни имени файла, ни его содержимого там нет – это видно прямо в параметрах запроса. Самого файла в запросах нет вообще.
Сжатие идёт внутри вкладки браузера. Сервер отдаёт саму страницу, а библиотека для чтения PDF загружается с CDN – и всё.
Как это устроено
Внутри нет никакой магии, и это скорее достоинство:
PDF ──► pdf.js рисует страницу ──► <canvas> ──► JPEG ──► новый PDF
pdf.js – движок, которым Firefox показывает PDF, – рисует страницу на canvas в нужном разрешении.
Браузер сам кодирует картинку в JPEG.
Маленький самописный модуль собирает из этих JPEG новый PDF.
Страницы обрабатываются по одной, поэтому даже файл на сотни страниц не забивает память. Для телефонов есть отдельный лимит размера страницы в пикселях – иначе мобильный Safari просто перезагружает вкладку.
Модуль, который собирает PDF, занимает около 90 строк. Каждая страница результата – это одна картинка на весь лист, поэтому сам PDF устроен предельно просто: каталог, список страниц, и для каждой страницы – описание листа, одна строка «нарисуй картинку на весь лист» и сама картинка. Готовые библиотеки вроде pdf-lib здесь избыточны: они держат весь документ в памяти, а нам нужно дописывать файл по мере обработки страниц.
Немного статистики
На странице стоит Google Analytics – только анонимные события: «выбран файл», «сжатие завершено», «файл сохранён», без имён и содержимого. Вот что набралось с апреля, когда страница появилась, по 9 октября:
Событие | Раз | Людей |
|---|---|---|
Выбрали файл | 73 | 17 |
Сжатие дало файл меньше исходного | 59 | 13 |
Сжатие дало бы файл больше – предложено оставить оригинал | 9 | 6 |
Сохранили результат | 46 | 11 |
Поделились результатом | 8 | 4 |
Сжимали из Бишкека, Алматы, Ташкента, Амстердама и Рио-де-Жанейро – без какой-либо рекламы.
Из этих цифр полезны две вещи. Примерно в каждом восьмом случае (9 из 68) браузерное сжатие сделало бы файл больше – и это ровно те текстовые PDF, о которых ниже. А из тех, кто получил сжатый файл, большинство его сохранили – значит, качество результата устраивает.
Где это работает, а где нет
Такой подход хорош не для всех PDF, и об этом лучше знать заранее. Я прогнал четыре разных документа через браузерный компрессор и через Ghostscript (средний пресет):
Документ | Было | Браузер | Ghostscript |
|---|---|---|---|
Сканы, 6 страниц | 2,9 МБ | 256 КБ (−91%) | 328 КБ (−89%) |
Презентация с фото, 8 слайдов | 4,0 МБ | 1,4 МБ (−66%) | 3,8 МБ (−5%) |
Текстовый отчёт, 245 страниц | 22,6 МБ | больше исходного | 7,8 МБ (−66%) |
Текстовый отчёт, 20 страниц | 96 КБ | больше исходного | 85 КБ (−11%) |
Что из этого следует:
Сканы и фото – то, для чего браузерное сжатие подходит идеально. Документы для посольства – как раз сканы.
Текстовые PDF – наоборот. Страница с текстом весит несколько килобайт, а та же страница в виде картинки – десятки. Компрессор это видит и предлагает оставить оригинал, а не отдаёт файл хуже исходного.
Текст после сжатия перестаёт быть текстом – его нельзя выделить и найти поиском. Для договоров, по которым потом нужно искать, лучше Ghostscript.
Кстати, возможно, именно поэтому тот редактор на букву «А» раздул мой файл: если инструмент растрирует страницы, а в документе много текста – результат будет больше. Как он устроен внутри, я не знаю, но поведение очень похожее.
Три бага, которые я нашёл и поправил, пока писал статью
Таблицу выше я делал для этой статьи – и попутно впервые посмотрел на выходные файлы не глазами, а утилитами. Нашлось три ошибки, которые жили в продакшене месяцами. Все три уже исправлены:
Разрешение было ниже заявленного. Надпись «150 dpi», а реально – 112. Перепутал систему координат: считал от 96 точек на дюйм, а pdf.js считает от 72.
Менялся размер страницы. Лист A4 после сжатия становился физически в полтора раза больше. На экране не видно, при печати – видно сразу.
Файлы были формально битыми – не хватало одного перевода строки. Просмотрщики такое молча чинят, а строгие системы приёма документов могут отвергнуть.
Забавно: инструмент, который родился из-за строгой формы загрузки на сайте посольства, сам выдавал файлы, которые строгая форма могла не принять.
Код открыт
Движок я вынес в отдельный репозиторий: github.com/xronocode/kompakt-web, лицензия MIT. Там же тесты и скрипт, которым получены цифры из таблицы – их можно перепроверить одной командой.
Алгоритм не уникальный, похожим образом работают и другие открытые проекты. Открыл его по простой причине: если вы, как и я, не хотите доверять свои файлы чужим серверам, то и мне на слово верить не обязаны. Код маленький, его можно прочитать целиком за вечер.
Пулреквесты и issues – велкам. Из того, что напрашивается само: перенос рендера в Web Worker через OffscreenCanvas, автотесты в Safari и Firefox, и – самое интересное – режим, который не растрирует страницы с текстом, а сжимает только картинки внутри них.
Сжать файл: kompakt.xronocode.com/compress
Подробнее про качество и пресеты (англ.): PDF compression vs quality
Если у вас есть PDF, на котором компрессор ведёт себя странно, – пишите в комментариях или присылайте в issues: добавлю в тестовый набор.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.