...

пятница, 8 мая 2015 г.

[Из песочницы] Habrahabr в PDF-варианте для электронной книги

Часто зависая на Хабре и не только много раз ловил себя на мысли, что информация и статьи гораздо эффективнее воспринимаются с телефона или планшета, когда читаешь в удобной позе, или даже не дома — в транспорте, командировках, и т.п. Описание игр с напильником для оригинальной конвертации Хабрахабра в PDF-вариант для комфортного оффлайн чтения на электронной книге — скорее любопытный вариант эксперимента, где задействовано сразу несколько интересных сервисов и известных всем технологий: PHP, CURL, ajax, js, css.

Итак, обо всем по-порядку.

Давно зрела мысль приобрести электронную книгу на e-ink чернилах. Всем известны доводы в пользу в этого: меньше нагрузка на глаза, дольше время работы на одной зарядке. Не приветствуя «комбайны» а-ля «все в одном», остановился на Amazon Kindle 6 версии, которая благодаря одной популярной доске объявлений досталась мне почти по цене как на ebay, зато сразу и с возможностью поторговаться и пощупать. Обзоров данной модели в вебе предостаточно, однако главная суть — в достаточном консерватизме производителя. Да, это электронная книга во всем ее проявлении. Тут нет mp3-плеера, приложений-игр и прочих фишек. Строго популярные мировые форматы «оцифровки макулатуры» и простенький браузер, не более того. Отмечу, что первоначальная ставка на встроенный браузер оказалась явно завышенной. И тот же Хабр открывался в очень мелком варианте шрифта, рендерил «хабровские» цвета заголовков #b5b5b5 в очень неконтрастный цвет. В общем, чтение напрямую, из браузера, немного напрягало.

К тому же, в отличие от скачанных книг, перелистывающихся на следующую страницу одним кликом, в браузере листать приходилось стандартным «телефонным» методом, проводя пальцем снизу-вверх. И если на каленом матером стекле телефона такие жесты уже как-то не замечаешь, то нежный, ранимый и слегка шершавый e-ink дисплей как-то не поднималась рука тереть несколько часов в день. К тому же, еще и вслепую, ведь картинка перерисовывалась только при отпускании пальца и глаз вынужден был искать место, откуда продолжать читать.

Тогда родилась идея запрограммировать некий «прокси» для удобного серфинга и экспорта наиболее интересного в PDF. Довольно быстро я написал PHP-прослойку, на вход которой в GET-параметре передавался url целевого сайта и которая через CURL запрашивала необходимый ресурс.

Первые наброски кода
$q = $_GET['q'];
if ( get_magic_quotes_gpc()) $q=addslashes(stripslashes(trim($q)));

if ($ch = curl_init()) { 
        // Инициализация параметров CURL
        curl_setopt($ch, CURLOPT_HEADER, false); 
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1); 
        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0');
        curl_setopt($ch, CURLOPT_URL, $q );
        $p = curl_exec($ch);
        // Вывод в браузер
        header("Content-Type: text/html; charset=utf-8");             
        echo $p;
} else echo 'Ошибка инициализации CURL!';



Такое простейшее решение было успешно опробовано на ПК и в книге. Разумеется, все css-стили и js-скрипты благополучно «потерялись», т.к. этот простейший код не учел относительных ссылок в html-коде сайта, подвергаемого парсингу.

Для исправления этой досадной оплошности мне пришлось внедрить

замену относительных ссылок на абсолютные:
$p = str_replace ('href=\'/',      'href=\'http://m.habrahabr.ru/' , $p);
$p = str_replace ('href="/',       'href="http://m.habrahabr.ru/' , $p);
$p = str_replace ('src=\'/',    'src=\'http://m.habrahabr.ru/' , $p);
$p = str_replace ('src="/',        'src="http://m.habrahabr.ru/' , $p);


Обращаю внимание на парные замены с одинарными и двойными кавычками — в коде Хабрастраниц встречались оба варианта написания.

Останавливаться уже не хотелось и я стал думать над усовершенствованием своей задумки.

Были сформулированы следующие пожелания к повышению юзабилити веб-серфинга:

  1. Масштаб (для комфортного чтения нужно было увеличивать размер шрифтов в 2 раза);
  2. Цвет (в силу особенностей цветопередачи e-ink все «модные» цвета нужно было сделать поконтрастнее);
  3. Удобный скролл страницы.

Все коррекции я решил производить заменой, аналогичной вышеприведенной манипуляции с подменой ссылок.
Внедрение дополнений к CSS
// Увеличиваем масштаб страницы в целом, и задаем черный цвет для наибольшей контрастности чтения
$p = str_replace ('</head>', '<style>body { zoom:2; } * {color: #000 !important; }</style></head>', $p);



Конструкторская мысль не стоит на месте — эксперименты с удобным скроллингом я решил начать с простейшей кнопки, которая будет жестко привязана к нижнему правому углу экрана. По задумке, клик на нее должен был вызвать простейшую функцию, которая через scrollBy двигала бы скролл на нужное значение вниз. Промежуточных опробованных кодов приводить не буду. Увы и ах, все эти примеры, блестяще работающие на обычном ПК, не были работоспособны в киндлобраузере… Все известные кроссбраузерные функции, хотя бы просто дающие значение текущего скролла, выдавали undefined или 0 при тестах на девайсе. Более того, даже сама кнопка, имеющая четко заданное позиционирование fixed и привязанная к низу экрана, в книге скроллилась вместе с сайтом. Потратив полчаса времени (и пропорциональное моему рейту количество нервов в час * 0.5), я решил пойти другим путем. Который, как оказалось, вполне работоспособен и удобен.

А именно — мне захотелось сделать удобную возможность экспорта в PDF, чтобы вообще не отвлекаться на особенности браузера книги — и спокойно читать, благо делать это из файла намного комфортнее. К тому же оффлайн чтение актуально там, где нет wi-fi.

Помониторив текущие ресурсы быстрого экспорта веб-страниц в PDF ( selectpdf.com и web2pdfconvert.com), я переназначил функцию своей fixed-кнопки на такой быстрый экспорт.

Внедрение кнопки для быстрого скачивания страницы в PDF
// Манипуляции со стилями
$p = str_replace ('</head>', '<style>body { zoom: 2; } * {color: #000 !important; } .fixed-buttons {position: fixed; right: 0; bottom: 0; margin-top: 0; background-color:gray; width:40px; height:40px;} </style></head>', $p); 
// Код кнопки (перед закрывающим тегом BODY)
$p = str_replace ('</body>', '<a class="fixed-buttons" href="http://ift.tt/1zJa6dt">PDF</a></body>', $p);



Да, это решение работало. Selectpdf.com по значению поля referer определяет, откуда пришел юзер, конвертирует эту страницу и отдает с нужным mime-заголовком. Для юзера это означает ровно то, что он вообще не подозревает о существовании selectpdf.com, а лишь жмакает на волшебную кнопку на моем сайте — и практически моментально скачивается pdf. Да, кстати, «моего сайта» — не совсем корректное выражение… Ведь в данном случае весь контент любезно предоставлен Хабром. Однако я не планирую выкладывать сервис в публичный доступ и думаю, что для моих личных целей такой парсинг CURL-ом не приносит никаких проблем самому Хабру в данном случае.

Итак, мы вроде бы близки к своей цели — прочесть страничку в волшебном скачанном pdf-варианте. Но Kindle снова заготовил нам сюрприз! Скачивание в браузере разрешено только в нескольких форматах — MOBI, TXT, и еще парочке. Не совсем понятное ограничение, ведь сама книга вполне справляется с большинством форматов, включая даже RTF и Word-овский DOC(X).

Ну что ж, тут включается ненормальное программирование. И азарт.

Я вспоминаю про одну любопытную возможность синхронизации документов, которую заложил Amazon в своем девайсе. А именно — у каждого авторизованного юзера есть личный кабинет и личный «киндловский» емайл, на который можно с белого списка ящиков посылать письма с документами и они волшебным образом появятся в книге. Некая пародия на dropbox и другие облачные системы хранения файлов. Но не использовать такую возможность было бы глупо. К тому же, проведенные тесты показали, что в таком сценарии «курьерской доставки документов на книгу» успешно загружается хоть PDF, хоть любой другой из списка поддерживаемых Киндлом.

Теперь о второй половинке «мостика», который мы будем дальше строить. И который именно как мостик срастался в моей голове во время анализа всех вариантов решения поставленной задачи. А именно — сервис PdfByEmail, предоставляемый одним из тех сервисов, которые я мониторил ранее. Суть — можно отправить целевой url порталу web2pdfconvert.com в письме, указав в теме письма ключевое слово Convert и в ответ (как обещают разработчики сервиса — в течении 1...3 мин) придет письмо с приаттаченным PDF-ником. Гениально! Для нашего случая самое то. Ведь мы можем сделать так, чтобы ответное письмо отсылалось прямо на книгу, т.е. на ее авторизованный email. Все, что нужно — это добавить email сервиса конвертации no-reply@web2pdfconvert.com в белый список личного кабинета Amazon.

Мне пришлось переписать функцию все той же fixed-кнопки внизу сайта. Теперь она запускает ajax-запрос к другой странице моего сайта, указывая, какой url требуется парсить. Т.к. не было уверенности, подключен ли jquery на всех сайтах, где я буду бродить, а подключать свой было бы громоздко, пришлось вспомнить давние времена, когда я делал ajax на нативном js, без фреймворков.

Связка js и php, отвечающая за отправку письма в сервис конвертации
Фронтэнд
// AJAX
function getXmlHttp() {
  var xmlhttp;
  try {xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");}
  catch (e) {
    try {xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");}
        catch (E) {xmlhttp = false;}
  }
  if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
    xmlhttp = new XMLHttpRequest();
  }
  return xmlhttp;
}

// ajax-отправка команды
function www_to_pdf(q) {
        var req = getXmlHttp();
        req.onreadystatechange = function() {  
                if (req.readyState == 4) { 
                        if(req.status == 200) { 
                                if (req.responseText == "ok") {
                                        alert('Запрос на конвертацию документа успешно отправлен!');
                                } else alert(req.responseText);
                        } else alert(req.status);
                }
        }
        req.open('GET', 'www_to_pdf.php?q=' + encodeURIComponent(q), true);
        req.send(null);  // отослать запрос
}


Бэкенд:
$q = $_GET['q'];
if ( get_magic_quotes_gpc()) $q=addslashes(stripslashes(trim($q)));

// Отправляем e-mail
$title = "convert"; 
$headers = "From: myamazonlogin<myamazonlogin@kindle.com>\r\nReply-To: myamazonlogin<myamazonlogin@kindle.com>\r\nContent-type: text/plain; charset=utf-8\r\n";
$ret = mail('submit@web2pdfconvert.com', $title, "http://ift.tt/1zJa8lz", $headers);
if ($ret == 1) echo 'ok';
else echo $ret;


Параметр exp=1 в отправляемой ссылке я добавил для чуть разного отображения веб-страницы для конвертора PDF и для моего просмотра.

Конвертор слишком «мельчил», и для него я делал тройной масштаб body в css. Кроме того, в экспортном документе не должно было быть видно моей fixed-кнопки. Этим и заправлял флаг exp=1.


Конечно, вначале я протестировал отправку на свой ящик, а уже потом решился запустить всю цепочку. Больше всего я боялся, что мое письмо, отправленное с домена, не совпадающего с доменом электронной почты для ответа kindle.com, будет забанено спам-фильтрами PDF-сервиса. Но все прошло успешно и заработало с первого раза. Ровно 2-3 минуты, как и утверждали разработчики web2pdfconvert — и файл валится в книгу с шильдиком NEW.

Подведу итоги. Теперь, благодаря самодельному прокси, я имею возможность серфить любимые сайты в книге в удобном масштабе, контрастности. А наткнувшись на интересную статью, одним кликом отправить ее на конвертацию, зная, что через три минуты она свалится мне в книгу в PDF-варианте. Грубо говоря, можно ежедневно отсматривать новостную ленту и накликать статьи, которые тебя заинтересовали. А потом уже читать их оффлайн и с удобным юзабилити. Кстати, как оказалось, web2pdfconvert сохраняет ссылки. Так что даже читая PDF я могу так же, как и раньше, открыть что-то по гиперссылке из документа. К примеру, раздел комментарии или «похожие статьи» внизу.

Кстати, мне пришлось внедрить еще небольшой хак касательно спойлеров в статьях Хабра. Ведь в PDF их уже не откроешь…

Проблема решилась еще парочкой корректирующих вставок
$p = str_replace ('</head>', '<style>body { zoom: ' . ( $exp ? 3 : 2 ) . '; } * {color: #000 !important; } .fixed-buttons {position: fixed; right: 0; bottom: 0; margin-top: 0; background-color:gray; width:40px; height:40px;} .spoiler_text {display:block;} </style><script type="text/javascript" src="http://ift.tt/1DW1N9z"></script></head>', $p); 
$p = str_replace ('class="spoiler_text"', 'class="spoiler_text" style="display:block; line-height: 120%;"', $p);


Обратите внимание на вышеупомянутый параметр $exp, который варьирует масштаб для конвертации и простого просмотра.

Финита ля комедия! Теперь чтение любимых статей, да еще и через самодельный костыль (ну и что, что костыль) — приносит еще большее наслаждение. Конечно, есть еще куда совершенствовать идею. К примеру, имена файлов, которые присылает конвертатор, равны mysyte-ru и не зависят от дальнейшей ссылки. Думаю, что можно решить через создание поддоменов. Чтобы хотя бы supernovost-mysyte-ru или еще как-то различались названием. Надо почитать про ограничения длины доменных имен. Ну и в заголовках еще иногда подглючивает конвертатор (см.ниже). Но это мелочи.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Комментариев нет:

Отправить комментарий