20101126

unplot.py: извлекаем табличные данные из графиков

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

Скачать: unplot.py.

Исходники лежат на bitbucket. Исправляйте и улучшайте.

Перед запуском программы нужно вначале определить координаты (в пикселях) той части графика, которую надо обработать. Это можно сделать в Gimp или в Geeqie.

Если на графике несколько линий, то нужную из них нужно покрасить отдельным цветом, отличным от всех других. Это тоже можно сделать в Gimp. Нужно записать HTML-код её цвета.

В общем, исходный график должен выглядеть примерно так:

colourized plot

После этого можно запустить и сам скрипт:

./unplot.py "#00ff00" 0 151 0 475 5.0 824 0.09 85 /путь/к/plot.png > /путь/к/data.txt


Первый параметр — это код цвета линии. Вторые два числа это значение и координата X левого нижнего угла. Потом ещё два числа, значение и координата Y того же угла. Потом ещё четыре числа — соответственно для правого верхнего угла. И в конце имя файла с графиком. Результаты (текстовую таблицу с данными) перенаправляем в файл. Можно ещё задавать «чувствительность» к цвету (параметр -s).

После применения скрипта полезно построить ещё раз график по полученным точкам и убедиться, что выбран правильный участок графика, указан правильный цвет и разумные числовые значения точек. Например, в этом случае я захватил слишком много справа, и зелёные буквы тоже были учтены как точки графика.



Обновление 20101203: теперь скрипт умеет обрабатывать графики с линией толще 1 пикселя; также в репозитории появился и небольшой скрипт для тестирования unplot.py на вменяемость.


Flattr this


По-английски: Unplot.py: from plots to tabular data.

20101012

Безболезненный upload с помощью Wondershaper

Наверное, многие замечали, особенно на домашнем интернете, что во время интенсивной закачивания данных в сеть (во время upload), соединение начинает «тормозить». Страницы сайтов подолгу не открываются, скачивание и медиапотоки подвисают на месте... Это легко измерить: у меня дома, например, пинг до гугла вырастает с 50 мс до порядка 1000 мс, при этом около 8% пакетов вообще теряются.

Причина этого явления (чаще всего) — загрузка в сеть занимает весь доступный исходящий канал, а соединения по протоколу TCP/IP требуют двухсторонней связи. Даже если мы пытаемся что-то просто скачать, машина должна посылать обратно пакеты подтверждения приёма (т.н. ACK). Если же исходящий канал весь занят, то и пакеты подтверждения, какими бы маленькими они не были, надолго задерживаются, и, соответственно, перестают приходить и входящие пакеты, даже если ширина входящего канала достаточна.

Решить эту проблему можно с помощью скрипта wondershaper (есть в составе большинства дистрибутивов). Он позволяет установить ограничения на скорость загрузки из сети и в сеть. Хитрость в том, чтобы вначале узнать свою реальную скорость соединения, а потом установить ограничения чуть-чуть ниже реальной доступной скорости. В этом случае будет всегда оставаться «запас» для своевременной передачи служебных пакетов, и задержки станут гораздо меньше.

Например, я хочу выложить кучу фотографий на яндекс-фотки. Иду на internet.yandex.ru и измеряю скорость соединения с Яндексом, сегодня вечером у меня получилось так:

Вниз: 4044 Кбит/с. Вверх: 490 Кбит/с.

Можно пойти на testspeed.net или другую измерительную страничку. Можно измерить вручную. Главное, получить реальные значения скорости в килобитах в секунду.

После этого надо включить wondershaper на используемом сетевом интерфейсе и указать ему значения немного меньше измеренных. Сетевой интерфейс — это обычно wlan0 в случае соединения по WiFi, или eth0 в случае соединения по локальной сети. Чтобы уточнить, можно выполнить команду route -n в терминале, имя используемого интерфейса искать в строке с флагами UG.

$ sudo wondershaper wlan0 3900 450


Готово. Запускаем загрузку в сеть (выкладываем фото, видео, раздаём торренты, и т.д.), измеряем время пинга. Чудесным образом, даже во время загрузки, пинг у меня упал до 61 мс, что не сильно больше его минимального значения при свободном канале, а веб-страницы стали скачиваться вполне шустро. Если не помогло с первого раза, можно попробовать ограничить скорость закачивания в сеть ещё сильнее.

Чтобы отключить wondershaper, выполнить:

$ sudo wondershaper clear интерфейс

20101001

Как аппликативные функторы с if-ами боролись

Maybe А и Maybe Б сидели на трубе,
А упало, Б пропало, что осталось на трубе?


На днях заметил, как полезны могут быть аппликативные функторы для избавления от леса вложенных условных конструкций (if-ов и case-ов). А именно, как удобно, что тип Maybe можно использовать как аппликативный функтор.

О функторах, пирогах и яблоках



Речь пойдёт о языке Хаскель, но постараюсь рассказать так, чтобы пользователям других языков было тоже понятно. Кто Хаскель знает, этот раздел может пропустить и сразу перейти к следующему.

Итак, для представления такого результата, которого может и не быть, в Хаскеле используется тип Maybe a, где a — любой тип. У Maybe a бывают значения двух видов: Just a («просто а», если значение есть) и Nothing («ничего», если значения нет). Maybe является функтором, то есть из любой функции из a в b (тип :: a -> b) можно сделать функцию из Maybe a в Maybe b (тип :: Maybe a -> Maybe b). В общем, функтором является любой тип f, для которого определена*:

fmap :: (a -> b) -> (f a -> f b)


* определение fmap должно удовлетворять при этом двум условиям: 1) fmap id = id, где id — функция, возвращаюая свой аргумент, 2) fmap должна быть дистрибутивна слева относительно композиции функций... [ссылка]


Например, пусть у нас есть типы Яблоко и Пирог и функция испечь :: Яблоко -> Пирог, превращающая одно в другое. Тогда мы легко можем взять яблоко, которого может и не быть, и испечь из него пирог, , которого может и не быть. То есть если будет просто яблоко, то выйдет просто пирог, а если ничего не было, то ничего и не выйдет. Делается это проще простого: fmap испечь.

data Яблоко = Яблоко deriving Show
data Пирог = Пирог deriving Show

испечь :: Яблоко -> Пирог
испечь Яблоко = Пирог

испечьЕслиЕсть :: (Maybe Яблоко) -> (Maybe Пирог)
испечьЕслиЕсть = fmap испечь


После этого мы можем печь пироги, которых может и не быть:

ghci> испечьЕслиЕсть (Just Яблоко)
Just Пирог
ghci> испечьЕслиЕсть Nothing
Nothing


А что делать, если самой функции из a в b может и не быть? Как применить Maybe (a -> b) к Maybe a? Такая операция определена для аппликативных функторов, Maybe — один из них. В Хаскеле для этого подключают модуль Control.Applicative, саму же операцию последовательного применения называют звучным словом (<*>).

Поясняю на яблоках. Что делать случае на входе у нас Maybe (Яблоко -> Пирог) — рецепт, которого может и не оказаться? Естественно, что пирог у нас получится тогда и только тогда, когда есть и яблоко, и сам рецепт. Если хоть что-то отсутствует, то и пирога не будет, будет Nothing. Итак, дано:

безРецепта = Nothing
сРецептом = Just испечь


Проверяем, как работает с такими «рецептами» аппликативный функтор Maybe:

ghci> :m + Control.Applicative
ghci> сРецептом <*> Just Яблоко
Just Пирог
ghci> сРецептом <*> Nothing
Nothing
ghci> безРецепта <*> Just Яблоко
Nothing
ghci> безРецепта <*> Nothing
Nothing


Это всё, что нам сейчас потребуется из «теории». И ещё пара замечаний: 1) с аппликативными функторами часто применяется ещё операция (<$>), которая является для них синонимом fmap и в инфиксной записи выглядит короче; 2) любая монада является также аппликативным функтором (обратное неверное), и для неё ap и (<*>) совпадают (об этом полезно знать, хотя нам сейчас и не потребуется).

Избавление от условных конструкций



Перехожу к практическим вопросам. На днях писал небольшой парсер для файлов формата Matrix Market. Это несложный формат, состоящий из заголовка в котором указывается, как хранится структура матрицы (покоординатно или сплошным массивом) и тип значений её элементов (целые, действительные или комплексные). Возможны разные комбинации.

Логику разбора такого формата на императивном псевдокоде можно описать так:

if (структура == координатная) {
if (тип == целые) {
читатьЦелыеПоКоординатам;
} else if (тип == действительные) {
читатьДействительныеПоКоординатам;
} else if (тип == комплексные) {
читатьКомплексныеПоКоординатам;
} else {
ошибка;
}
} else if (структура == массив) {
if (тип == целые) {
читатьМассивЦелых;
} else if (тип == действительные) {
читатьМассивДействительных;
} else if (тип == комплексные) {
читатьМассивКомплексных;
} else {
ошибка;
}
} else {
ошибка;
}


Естественно, что такого развесистого дерева выбора можно избежать, потому что выбор типа элементов и выбор структуры матрицы независимы друг от друга. В функциональном стиле можно просто составить «словарь» поддерживаемых функций для разбора структуры и отдельный «словарь» функций для чтения значений разных типов, выбирать подходящие, и потом передавать вторые в качестве параметра первым. Так я и сделал.

Опять псевдокод для пояснения этой идеи:

let читатьСтруктуру = lookup структура словарьФункцийРазбора
читатьЧисло = lookup тип словарьФункцийРазбораЧисла
in читатьСтруктуру читатьЧисло исходныеДанные


На практике дело обстоит немного сложнее. Операция поиска в словаре может ничего не найти. Как легко догадаться, lookup в Хаскеле возвращает Maybe a. Итак, у нас есть Maybe функция, Maybe аргумент, а мы хотим получить Maybe результат. Ничего не напоминает? Да-да, тут самое время применить (<*>).

Однако представим на минуту, как эта задача решается без операции последовательного применения Maybe a. И для функции, и для её аргумента нужно проверять являются ли они чем-то (Just a) или ничем (Nothing), и в результате получается тот же лес проверок, что и в императивном псевдокоде, только в этот раз с проверкой на результат поиска. Можно, правда, все проверки объединить в одной единственной условной конструкции на каждое применение функции, но все проверки надо всё равно записывать явно. То есть без (<*>) получается что-то такое:

let читатьСтруктуру = lookup структура словарьФункцийРазбора
читатьЧисло = lookup тип словарьФункцийРазбораЧисла
in case (читатьСтруктуру, читатьЧисло) of
(Just f, Just g) -> Just (f g исходныеДанные)
_ -> Nothing


Код склонен паталогически ветвиться, если таких Maybe-аргументов у функции больше одного, или если полученный Maybe-результат нужно использовать в качестве аргумента ещё одной функции (тут не обойтись без вложенных проверок).

К счастью, все эти проверки тривиальны, и код можно записать гораздо короче:

let читатьСтруктуру = lookup структура словарьФункцийРазбора 
читатьЧисло = lookup тип словарьФункцийРазбораЧисла
in читатьСтруктуру <*> читатьЧисло <*> (Just исходныеДанные)


Здесь нет ни одной явной условной конструкции, но код, тем не менее, делает все необходимые проверки, и вернёт Just результат только если нашлись обе функции.

У меня получилось немного длиннее, но суть та же:

let p = lookup fmt parsers        :: Maybe FormatReader
nr = lookup fieldq readers :: Maybe (Int, ValueReader)
sy = lookup symq symmetries :: Maybe Symmetry
p' = uncurry <$> p <*> nr :: Maybe ([String] -> Maybe MatrixData)
d = join $ p' <*> (Just toks) :: Maybe MatrixData
m = MM <$> d <*> sy :: Maybe Matrix
in ...


Что тут можно заметить. Во-первых, (<$>) практически так же часто используется, как и (<*>). Эта операция позволяет применять обычные функции к Maybe-значениям. Вместо (<$>) можно поднимать значения в функтор с помощью pure (для любых аппликативных функторов) или Just (конкретно для Maybe) и использовать только (<*>).

Во-вторых, если у нас кругом функции, возвращающие Maybe, и они сами оказываются внутри Maybe (см. p' в моём примере), то рано или поздно появляются «вложенные» значения типа Maybe (Maybe a). Тут помогает монадный join, превращающий их в просто Maybe a. join определён в Control.Monad. И кстати, это тоже помогает избавиться от вложенных тривиальных проверок (join (Just Nothing) даёт Nothing).

В-третьих, при таком подходе мы, естественно, теряем информацию о том, какое именно вычисление не дало результата (дало Nothing). Для передачи «исключения» по цепочке можно использовать другие типы, например Either e, определив для них Applicative.

Заключение



Мне такой способ комбинировать вычисления очень понравился. По-моему, случай, когда все условные проверки сводятся к «если в порядке, то считать дальше, а если нет, то прервать» — довольно распространённый. Тип Maybe с операцией последовательного применения (<*>) позволяет такие проверки, любой степени вложенности, не писать вообще.


Flattr this

20100913

Типографская раскладка в Линуксе

В блоге «Словомания» полезная заметка: Типографская раскладка в Линуксе. Оказывается, в свежих версиях есть уже готовая типографская раскладка, почти как раскладка Бирмана.

Типографская раскладка в Линуксе

Как включить: в параметрах раскладки клавиатуры включить «Клавишу для выбора 3-го уровня» (например, правый Alt). В списке «Разные параметры совместимости» — «Включить дополнительные типографские символы». Подробнее здесь.

По теме:

Ввод символов с акцентами в Linux
Как задавать произвольные Compose-последовательности

По-английски: Typography keyboard layout in Linux.

20100806

Удаление пыли на матрице в Gimp

Недавно нашёл лёгкий способ удалять пятна грязи с фотографий. Речь о пятнах пыли на матрице. Если на камере объективы сменные и иногда меняются, грязь на фоточувствительный элемент рано или поздно проберётся. И встроенная очистка справляется с ней с переменным успехом. Выглядят эти пятна примерно так:

Пятна от грязи на матрице

Их легко распознать, они появляются во всех снимках серии на одном и том же месте. Наиболее заметны они становятся на маленькой диафрагме, при съёмке более-менее однотонных объектов (небо, море, далёкие холмы). Поэтому можно поставить маленькую диафрагму (скажем, f/22 или меньше) и снять чистое небо. Если пятна сильно заметны, то матрицу придётся чистить.

Я же расскажу, что делать с фотками, на которых эти пятна уже есть. Использую для этих целей Gimp и плагин Resynthesizer. Конечно, можно было бы обойтись обычной клонирующей кистью, но вычищать несколько фотографий подряд, особенно если там по несколько пятен на каждой, утомительно. А Resynthesizer оказался неплохой автоматической «клонирующей кистью». Пользователи Ubuntu могут установить плагин пакетом gimp-resynthesizer.

Вначале пятна надо найти и выделить. Я обычно использую свободное выделение (лассо). Чтобы выделить сразу несколько пятен, удерживаю нажатой клавишу Shift. Важно захватить в выделение достаточное количество «чистых» пикселей на границе: они влияют на результат работы Resynthesizer.

Затем можно запустить сам плагин: Фильтры -> Карта -> Resynthesize.... В общем, параметры по-умолчанию работают обычно достаточно хорошо. Возможности периодического мощения (make ... tileable) нам в этом случае не нужны.



Плагин не очень шустрый. На большой фотке (10-15 мегапикселей) нужно будет несколько секунд подождать. Впрочем, это всё равно быстрее и легче, чем использовать клонирующую кисть.

Наконец, нужно осмотреть результат, нет ли никаках странностей. Скорее всего, всё будет нормально (пятна обычно заметны как раз на таких местах, которые Resynthesizer легко восстанавливает: небо, облака, море, сплошная растительность). Если выделение было слишком тесным, его граница прошла слишком близким к пятну, то на месте пятна может всё равно остаться размазанное потемнение. В этом случае нужно отменить исправление и повторить, выделив пятна по-другому. Однако обычно всё удаляется с первой попытки:

Пятна грязи на матрице удалены плагином Resynthesizer

В общем, чистого всем неба! Если полезно — Flattr this.

Riviera

Also in English: Cleaning sensor dust with Gimp.

20100322

Впечатления о Цюрихаке

Вчера вернулся с Цюрихака. Интересная была поездка. Программировать в компании 80 других фанатов очень даже занятно. Здорово, что можно прямо тут же что-то обсудить, наметить цели, поделить работу и её сделать. Приятно браться за незнакомый код и в сжатый срок добавлять к нему что-то полезное (а с Хаскелем такое по силам даже новичкам; и новичкам с готовностью помогали). Большинство участников поселились все вместе в одном хостеле, так что вечерами собирались большими компаниями, чтоб выпить кружечку пива, познакомиться, послушать рассказы других людей, поделиться своим опытом и впечатлениями. Ну и просто увидеть людей, имена которых были уже знакомы, тоже интересно. Вот они, цюрихакеры (я тут тоже есть):



20100129

Автоматический учёт времени: Arbtt macht frei!

В линуксе есть несколько разных программок для учёта времени, самая простая и незамысловатая, и при этом вполне функциональная — это, пожалуй, Hamster. С ней всё понятно: добавляем на панель, вбиваем новое дело всякий раз, когда за него берёмся. Главное, не забывать.

А вот есть программка похитрее: arbtt. Пользоваться ей, правда, легче. Она полностью автоматическая. Достаточно запустить arbtt-capture и заниматься своими делами*. arbtt-capture будет записывать когда и какие программы были запущены и какие у окон были заголовки.

* Автор arbtt рекомендует сразу добавить arbtt-capture в автоматически запускаемые приложения.


Чтобы увидеть необработанные сырые данные, можно выполнить arbtt-dump, но это не очень полезно. Для просмотра статистики удобнее использовать использовать утилитку arbtt-stats.

Чтобы arbtt-stats могла выдавать осмысленные результаты, нужно вначале задать свою классификацию запущенных программ. Эти правила вписываются в файл ~/.arbtt/categorize.cfg. Пример и описание формата правил есть в документации. Приведу свой (сокращённый) пример с комментариями по-русски:
-- правила имеют вид:
-- [условие ==>] tag [категория_тега:]тег,
-- в условиях и тегах можно использовать несколько специальных переменных,
-- почти все они встречаются в примерах ниже

-- Не учитывать время простоя
$idle > 60 ==> tag inactive,

-- Все записи за последние 24 часа пометить тегом last-day
$sampleage <= 24:00 ==> tag last-day,
-- Пометить тегом last-hour все записи за последний час
$sampleage <= 1:00 ==> tag last-hour,

-- Все типы окон Firefox учитывать в одном теге program:web (program — это категория тега)
current window $program == "Navigator" ==> tag program:web,
current window $program == "firefox-bin" ==> tag program:web,
current window $program == "gecko" ==> tag program:web,
-- Общий тег для всех видов терминалов (на будущее)
current window $program == "gnome-terminal" ==> tag program:terminal,
-- Пометить все остальные программы пометить тегами вида program:имя_программы
tag program:$current.program,

-- Классифицировать заголовки Firefox с помощью регулярных выражений. Тут у каждого будут свои шаблоны.
-- Присваивать теги категории web.
current window ($program == "Navigator" && $title =~ /^Gmail.*/) ==> tag web:Gmail,
current window ($program == "Navigator" && $title =~ /.*Google Search.*/) ==> tag web:Google,
current window ($program == "Navigator" && $title =~ /^Twitter.*/) ==> tag web:Twitter,
current window ($program == "Navigator" && $title =~ /.* on Twitter - Iceweasel$/) ==> tag web:Twitter,
current window ($program == "Navigator" && $title =~ /^Springer.*/) ==> tag web:Papers,
current window ($program == "Navigator" && $title =~ /^Wiki - Editing.*/) ==> tag web:Papers,
-- ...
--
current window $program == "Navigator" ==> tag web:$current.title,

-- Теги категории time-of-day для классификации по времени суток
$time >= 2:00 && $time < 8:00 ==> tag time-of-day:night,
$time >= 8:00 && $time < 12:00 ==> tag time-of-day:morning,
$time >= 12:00 && $time < 14:00 ==> tag time-of-day:lunchtime,
$time >= 14:00 && $time < 18:00 ==> tag time-of-day:afternoon,
$time >= 18:00 && $time < 22:00 ==> tag time-of-day:evening,
$time >= 22:00 || $time < 2:00 ==> tag time-of-day:late-evening,

-- Помечать над каким проектом работаю судя по заголовку окна.
-- Присваивать теги категории project.
current window $title =~ m!~/work/projectA! ==> tag project:projectA,
current window $title =~ m!~/work/projectB! ==> tag project:projectB,
-- ...
--

-- Помечать, какой тип текста я редактирую судя по заголовку окна.
-- Присваивать теги категории edit.
current window ($title =~ /^[^ ]+\.c .* - G?VIM.*$/) ==> tag edit:c,
current window ($title =~ /^[^ ]+\.py .* - G?VIM.*$/) ==> tag edit:python,
current window ($title =~ /^[^ ]+\.hs .* - G?VIM.*$/) ==> tag edit:haskell,
-- Когда использую suduedit
current window ($title =~ m!.*\(/var/tmp\) - G?VIM.*$!) ==> tag edit:config,
-- Когда редактирую что-то онлайн в Its All Text
current window ($title =~ m!.*/itsalltext\) - G?VIM.*!) ==> tag edit:itsalltext,

Для отчёта по определённой категории:
$ arbtt-stats -c имя_категории
Для просмотра отчётов по всем категориям:
$ arbtt-stats --each-category
Для ограничения выборки только записями с определённым тегом, например, last-hour, есть опция -o. Всё вместе:
$ arbtt-stats -o last-hour -c program -c edit
Statistics for category program
===============================
_____________Tag_|___Time_|_Percentage_
program:terminal | 29m00s | 48.33
program:gvim | 17m00s | 28.33
program:web | 13m00s | 21.67
program:Pidgin | 1m00s | 1.67

Statistics for category edit
============================
_____________Tag_|___Time_|_Percentage_
edit:itsalltext | 17m00s | 28.33
edit:haskell | 4m00s | 6.67
(unmatched time) | 39m00s | 65.00
В последнем примере я показал примерный вывод программы. Сразу видно, сколько времени за последний час я что-то редактировал и что именно и какие программы использовал. Писал эту заметку, в общем.

Кстати, arbtt есть не только в линуксовых репозиториях, но в скором времени (а может и уже) будет доступна и пользователям Windows.

Некоторые замеченные изъяны: arbtt-stats при печати портит заголовки окон с уникодом (патчем на 20 строк исправляется, должно быть ОК при сборке новым GHC), пока нельзя классифицировать по дням недели или по месяцам, сообщения о синтаксических ошибках в правилах очень невнятны.

Дополнение: замеченные недостатки, не без моего скромного участия, исправлены во время Хакатона в Цюрихе; используйте GHC 6.12 и устанавливайте новую версию 0.5; там всё ОК.

Приятных всем выходных!

20100124

Три способа отрезать поля у PDF-документа

Речь пойдёт о том, как отрезать поля (и вообще изменить размер страниц) в PDF-документе. После того, как у меня появилась читалка на электронных чернилах, делать это приходится довольно часто.

В чём проблема: большинство PDF* свёрстаны под печатную страницу формата A4 (29,7×21 см) или Letter, с полями, колонтитулами, всё как положено. А типичный экран читалки — 12×9 см с разрешением 800×600 точек. Даже если показывать по половине странице, на страницу приходится всего 1200×800 точек (и 18×12 см площади экрана). Значит, даже при просмотре страниц «половинками» буквы будут примерно в 1,65 раза мельче, вдобавок и разрешение при этом будет тоже как минимум раза в полтора ниже. Короче говоря, значительная доля PDF, свёрстанных под печать, на нынешних электронных читалка нечитаема.

Впрочем, во многих случаях можно легко из обычного PDF сделать PDF, легко читаемый на экране читалки. Дело в том, что значительную часть площади страницы обычно занимают поля. Они нужны для бумажной версии, но без них вполне можно обойтись на электронной читалке. И если обрезать поля (а в некоторых случаях можно обрезать и колонтитулы), то часто содержательная часть страницы будет выглядеть вполне читаемой и на маленьком экране читалки.

На сегодняшний день я нашёл и попробовал три способа обрезать поля у PDF-файла.

1. Обрезка полей с помощью pdfcrop



Есть скрипт pdfcrop на перле (не путать с одноимённым скриптом на питоне), который умеет обрезать поля автоматически. В Debian он входит в состав пакета texlive-extra-utils.

Использовать так:
$ pdfcrop --clip --margin 5 исходный.pdf обрезанный.pdf


Советую всегда всё равно оставлять небольшое поле (--margin 5), иначе касающиеся края буквы могут не отображаться на экране читалки.

В общем, всё просто. Преимущества: простой автоматический способ, по полученному таким способом PDF сохраняется возможность поиска. Недостатки такого способа: pdfcrop очень медленно работает с большими документами (сотни страниц), нельзя автоматически отрезать колонтитулы и заметки на полях (в некоторых случаях проще обойтись без номеров страниц и названия главы сверху, зато получить более крупное изображение основного текста), конкретно моя читалка иногда аварийно перегружается на полученных таким способом PDF, на некоторых файлах pdfcrop неправильно определяет границы текста, на некоторых портит шрифты.

2. Растеризация и обрезка страниц в ImageMagick



Пару раз мне пришлось прибегнуть к написанию самодельного скрипта, заточенного под определённый исходный PDF. Общая схема такая:

Исходный PDF → растеризованные изображения страниц (использую pdftoppm) → разрезание страниц на части и обрезка полей (использую convert из ImageMagick) → сборка нового PDF или DjVu из обрезанных страниц.

Вот пример такого скрипта, которым пользуюсь (он не только позволяет разрезать страницы на несколько колонок, но также отрезать поля и пережать, отбросив пустые страницы) — pdf-trim-to-djvu:



Как пользоваться — должно быть ясно из его справки:

Usage: pdf-trim-to-djvu [options] document.pdf

Options:
-f the first page to process [default: 1]
-t the last page to process
-d resolution in DPI [default: 150]
-c|--columns multi-column mode [default: 1]
--mono bitonal compression (black and white only)
--gray DjVuPhoto compression (shades of gray images) [default]
--color DjVuPhoto compression (color images)
-h|--help print this message


Автоматическая обрезка полей довольно хорошо реализована в команде -trim ImageMagick, но можно задать параметры обрезки и вручную (приходилось). Например, чтобы принудительно обрезать по 3% с каждой стороны, в опции convert можно вставить -shave 3%x3% +repage.

Если хочется не DjVu, а именно PDF, то собрать из изображений PDF можно так (о создании PDF с помощью IM см. здесь):
convert -define pdf:use-trimbox=true `ls -v *.ppm` -density разрешение_в_dpi книжка.pdf
Если страниц много, такой способ будет очень медленным (и прожорливым), лучше конвертировать каждую отдельно (можно тем же convert, если качество устраивает, можно специально для этих целей предназначенным sam2p), а потом объединять страницы вместе. Для объединения PDF-страниц в PDF-документ я использую pdftk:
$ pdftk *.pdf cat output книжка.pdf


Преимущества этого способа: можно разрезать и обрезать страницы именно так, как надо. Недостатки: возможность поиска по тексту безвозвратно теряется, размер файла обычно увеличивается, добиться нормальной растеризации шрифта трудно, ну и сам скрипт иногда приходится менять под конкретную книжку.

3. Изменение границ страницы в PDFedit



Наконец, есть ещё способ. Совмещающий и возможность указать вручную что именно следует отрезать, и сохраняющий PDF в почти исходном виде. Есть редактор для PDF-файлов — PDFedit. Однако хотя эта программа и с графическим интерфейсом, методы всё те же.

как обрезать поля страницы в pdfedit

Порядок действий:
  1. открываем копию PDF-файла в PDFedit и выбираем страницу, целиком заполненную текстом, чтобы было видно его границы;
  2. засекаем примерные численные координаты углов прямоугольника обрезки;
  3. в меню «Страница» выбираем «Изменить метрики страницы»; далее вводим новые параметры страницы цифрами, жмём «Изменить», чтобы проверить результат (такой вот GUI; что от чего отмеряется придётся познать на опыте), подобрав параметры страницы применяем обрезку ко всем с 1 по последнюю;
  4. сохраняем результат.

Преимущества: способ быстрый (даже если в документе несколько сотен страниц), возможность поиска по тексту сохраняется (да и вообще всё сохраняется), можно как угодно отрезать заметки на полях, номера страниц и колонтитулы. Недостатки: способ требует ручного подбора параметров, нельзя вырезать две страницы из одной (может можно, если дублировать страницы?), сам редактор PDFedit далеко не прост и полон сюрпризов.


Flattr this


* Вот, кстати, не пойму. Современные научные статьи распространяются почти исключительно в электронном виде (бумажные отпечатки — сувениры для авторов). За каждую операцию копирования файла издатели стараются взымать по 30 долларов (думаю, не бедствуют), а вот набирают статьи зачастую таким скупым кеглем, словно свою бумагу жалко. Сравните публикации XIX века и нынешнего. Отчего?

20100114

Кнопки твиттера, жуйки, я.ру и ЖЖ для Blogger

Приятно, когда на тебя ссылаются, поэтому любой заядлый блоггер любит всякие кнопки вроде «retweet» и «в делишез». «Retweet» особенно: в микроблогах ссылаются охотнее. Однако кроме твиттера, у нас есть ещё и ЖЖ, и Я.ру, и juick. А вот каким-нибудь stumbleupon никто не пользуется.

Никакие готовые кнопки мне не понравились: 1) большинство из них неправильно работает, если заметка находится на главной странице, 2) они почти все требуют установки чужих скриптов, часто тормозных 3) многие кнопки игнорируют популярные в России (и среди моих читателей) ЖЖ, Я.ру и juick. Вот и пришлось сделать самому.

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

Что получилось

Первая версия выглядит примерно так (это просто картинка, не кликать! — настоящие кнопки внизу заметки):

Кнопки подписал по-русски. Для надёжности расшифровал. Проверил, точно работают «пожужжать |жж», «двумЯ.РУками |я.ру» и «расчирикать |twi». Кнопка для жуйки по идее должна работать, только я её проверить не смог, потому что у меня обработчик XMPP URI не настроен. Пожалуйста, проверьте, кто пользуется, отпишитесь и скажите, как надо поправить, если что.

Как установить себе

В шаблоне блога «раскрыть виджеты», найти подходящее место (например, я выбрал post-footer-line-2) и добавить туда такой код:

<div class="sharemebuttons">
<a class="shareme" expr:href='"http://www.livejournal.com/update.bml?subject=Ссылка: "
+ data:post.title + "&amp;event=" + data:post.title + ": "
+ data:post.url'>пожужжать |жж</a>

<a class="shareme" expr:href='"http://my.ya.ru/posts_add_link.xml?title="
+ data:post.title + "&amp;URL=" + data:post.url'>двумЯ.РУками |я.ру</a>

<a class="shareme" expr:href='"xmpp:juick@juick.com?message;body="
+ data:post.title + " " + data:post.url'>перетереть |juick</a>

<a class="shareme" expr:href='"http://www.google.com/reader/link?url="
+ data:post.url + "&amp;title=" + data:post.title + "&amp;srcURL="
+ data:blog.homePageUrl + "&amp;srcTitle=" + data:title'>побузить |buzz</a>

<a class="shareme" expr:href='"http://twitter.com/home?status="
+ data:post.url + " " + data:post.title'>расчирикать |twi</a>
</div>

Для ЖЖ бы хорошо в event помещать уже HTML-код, но у меня пока не получилось.

Надо, наверное, добавить красивые графические иконки (не знаю, дойдут ли руки, но собственноручно нарисованный чижик для твиттера у меня уже есть; нет ничего для жуйки и жж). Пока довольствуюсь вот таким фрагментом CSS (тоже черновой вариант, вставлять в таблицу стилей в верху шаблона):
div.sharemebuttons {
margin-top: 1em;
margin-bottom: 1em;
}
a.shareme, a.shareme:visited {
text-decoration: none;
padding: 3px 8px;
margin: 0em 8px 0em 0px;
background-color: #94cc32;
color: white;
}
a.shareme:hover {
background-color: #aced3a;
color: white;
}

Всякие улучшения приветствуются. Ну и ссылки, конечно :-)

Доп. 1: добавил кнопку для «Я.ру».

Доп. 2: исправление для кнопки «Я.ру» — чтобы при нажатии на кнопку незалогинненному пользователю вначале предлагалось войти в «Я.ру», добавить в шаблон блога такой Javascript-код (например, перед </head>):
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$("a.shareme[href^='http://my.ya.ru']").attr("href", function() {
return "http://passport.yandex.ru/passport?mode=auth&amp;retpath=" + escape(this.href);
});
});
</script>
Без этого скрипта кнопка тоже работает, но только для залогинненых пользователей «Я.ру».

Доп. 3: добавил кнопку для Google Buzz (через Reader).