Часто зависая на Хабре и не только много раз ловил себя на мысли, что информация и статьи гораздо эффективнее воспринимаются с телефона или планшета, когда читаешь в удобной позе, или даже не дома — в транспорте, командировках, и т.п. Описание игр с напильником для оригинальной конвертации Хабрахабра в 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://selectpdf.com/save-as-pdf">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://mysite.ru/proxy.php?exp=1&q=$q", $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://mysite.ru/scripts/js/www_to_pdf.js"></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 или еще как-то различались названием. Надо почитать про ограничения длины доменных имен. Ну и в заголовках еще иногда ��одглючивает конвертатор (см.ниже). Но это мелочи.