- PVSM.RU - https://www.pvsm.ru -

iPages в PDF или сервис за 2 часа

У вас не мак, и вы опять обнаружили pages в входящем письме? Да что с ними не так?
Последний раз после тщательного поиска, нашлось простое и изящное решение. И, заметьте, это не бензопила со спутниковым наведением.

iPages в PDF или сервис за 2 часа [1]

1. Переименовать расширение в .zip
2. Открыть полученный архив и найти в нём pdf файл
3. Profit!

Вы думаете мы остановились только на этом? Мы настолько упоролись вдохновились, что запилили сервис [1] для автоматической конвертации, обдумываем схемы монетизации.

Под катом вас ждет подробное объяснение как это работает.

На самом деле pages, как и некоторые форматы для MacOS, хранят в себе специальные данные для предпросмотра в формате PDF.

Чтобы было не скучно в реализации вместо обработки на бэкэнде мы решили поиграть мышцами на js.
Да, из-за этого конвертер не работает в ie9, потому что у него нет поддержки URL (все остальное можно сделать эмулированно). Зато ничего не нужно загружать на сервер, все работает на клиенте, а значит — мгновенно (а еще безопасно, вы же никуда ничего не отправляете).

Итак, как же это работает?

pages — это zip-контейнер(кстати, как и docx, xslx и прочие офисные форматы нового поколения).
Внутри у него лежат:
-index.xml — главный файл презентации
-buildVersionHistory.plist — файлик с метаданными, что он делает – понятно и по названию
-QuickLook/Thumbnail.jpg — картинка-минюатюра для предпросмотра внутри папки
-QuickLook/Preview.pdf — сам файл предпросмотра, который открывается в macOS по нажатию пробела.

Как мы получаем файлы драг-н-дропом или через инпуты – рассказывалось сотню раз, это неинтересно, давайте пропустим этот шаг.
Мы получили этот файл, и для того чтобы прочитать файл – нам нужно запустить FileReader.

Скрипты, работающие с файлами, имеют разный формат на входе – кто-то принимает Blob, кто-то – бинарную строку. Мы взяли js-unzip, одно из десятков легко нагугливающихся решений. Взяли за простоту и понятность.

Он требует на входе строку, поэтому мы запускаем FileReader в формате readAsBinaryString:

if (file.type === "application/x-iwork-pages-sffpages") {
        var reader = new FileReader();
        reader.onload = function (event) {
            processZip(event.target.result)
        };
        reader.readAsBinaryString(file);
}

Обратите внимание, в самом событии нет полезной информации, event.target на самом деле ссылается на reader, и можно было бы написать processZip(reader.result).
Почти все стандарты в браузерах очень похожи друг на друга по синтаксису, и FileReader сделан с оглядкой на XMLHttpRequest, так что все будет довольно знакомым.

Работу с zip-архивом мы тоже пропустим – библиотек в сети для этой задачи много, и у каждой свой синтаксис, тем более что в данном случае zip – это просто контейнер, и даже не пришлось подключать механизмы разархивирования.

Самое интересное происходит в конце (этот код немного не соответствует тому, что на сайте, ради читаемости):

var uintArray = new Uint8Array(dataString.length);
for (var i = 0; i < dataString.length; i++) {
    uintArray[i] = dataString.charCodeAt(i)
}
var blob = new Blob([uintArray], {type: 'application/pdf'});
gotLink(URL.createObjectURL(blob));

Что тут происходит:
Бинарная строка в текстовом выражении хранит в себе ascii-коды своих байтов. Мы создаем специальный типизированный массив(uint8array) из однобайтовых целочисленных без знака, тоже в диапазоне от 0 до 255, и побайтово переносим в него числовые значения символов строки.
Это нужно для того, чтобы blob-объект (бинарный объект в js) создался с учетом того, что каждое число хранит один байт – иначе символы могут быть интерпретированы иначе, и файл будет сгенерирован неправильно.
При этом сам Blob принимает на входе только массивы, поэтому нам дополнительно приходится оборачивать uintArray в обычный массив.

Так как выходная ссылка будет без формата – мы дополнительно указываем для blob-объекта mime-тип.
И самый большой кусок магии на сайте – с помощью функции

URL.createObjectURL(blob)

мы получаем ссылку на blob-объект в памяти. То есть буквально — как только мы закрываем документ-родитель, ссылка перестает работать.
Выглядит ссылка так:

blob:http://localhost:8005/4222c9ec-1c66-4143-96a8-4223482148f6

Вот так и можно достать отдельный файл из архива и отдать его обратно клиенту, не обращаясь к серверу.
К сожалению, если бы не было необходимости в ссылке от URL.createObjectURL – можно было бы опереться на сервер в чтении файла для ie9 – Blob.poly.js существует, и с ним можно работать, но base64 ссылка на выходе оказалась таких размеров, что браузер просто не хотел открывать ее в новом окне и повисал.

P.S. Если обнаружите баги (ОС, версия браузера) пишите в личку. Обещаю сразу пофиксить.

Автор: teolink

Источник [2]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/45487

Ссылки в тексте:

[1] Image: http://pages-pdf.com/

[2] Источник: http://habrahabr.ru/post/197220/