У вас не мак, и вы опять обнаружили pages в входящем письме? Да что с ними не так?
Последний раз после тщательного поиска, нашлось простое и изящное решение. И, заметьте, это не бензопила со спутниковым наведением.
1. Переименовать расширение в .zip
2. Открыть полученный архив и найти в нём pdf файл
3. Profit!
Вы думаете мы остановились только на этом? Мы настолько упоролись вдохновились, что запилили сервис для автоматической конвертации, обдумываем схемы монетизации.
Под катом вас ждет подробное объяснение как это работает.
На самом деле 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