Ubuntu系统的默认PDF阅读软件
ubuntu操作系统自带了Evince-PDF阅读器,其优点是简单便捷,不过与其他常用的PDF阅读器相比,缺少很多定制化的选项,例如为避免眼睛疲劳的替换阅读界面背景颜色的功能。使用GNU/Linux操作系统的一个优势是,可以获取从内核至应用各个层面的软件源码,并加以修改后重新编译,可以方便地实现所需的新功能。笔者在该文章中记录了为Evince
阅读器添加灰色背景的操作过程。作为对比,在添加该功能之前,笔者打开SystemTap教程文档的背景如下:
修改之后,在Evince
的选项中使能Night Mode
,阅读的背景会被修改为:
获取Evince的源代码
笔者使用的Ubuntu
系统版本为20.04
。获取Evince
源码并能够编译,需要做一些准备工作。首先,将/etc/apt/sources.list
中的# deb-src
前面的#
号删除:
sudo sed -e 's/# deb-src/deb-src/g' -i /etc/apt/sources.list
sudo apt-get update
之后,安装重新编译Evince
所需的依赖和系统软件:
sudo apt-get install build-essential libtool autoconf make pbuilder
sudo apt-get build-dep evince
最后,下载Evince
的源码包:
mkdir ~/evince-rebuild
cd ~/evince-rebuild
apt-get source evince
修改Evince源码以加载动态库
Evince
有一个Night Mode
的选项,可以将界面的亮度反转。其实现比较简单,是使用到了“差异算子”,将界面与最大亮度值求差:
/* libdocument/ev-document-misc.c */
void ev_document_misc_invert_surface (cairo_surface_t *surface) {
cairo_t *cr;
cr = cairo_create (surface);
/* white + DIFFERENCE -> invert */
cairo_set_operator (cr, CAIRO_OPERATOR_DIFFERENCE);
cairo_set_source_rgb (cr, 1., 1., 1.);
cairo_paint(cr);
cairo_destroy (cr);
}
笔者在实现该功能时,考虑到替换该函数的实现,就可以修改阅读界面的背景。不过,笔者没有在该函数中直接操作cairo_surface_t
对象,而是通过加载动态库实现的,这样Evince
只需被重新编译一次。笔者对该代码的修改如下:
diff --git a/libdocument/ev-document-misc.c b/libdocument/ev-document-misc.c
index 1d0acc5..da6c6ad 100644
--- a/libdocument/ev-document-misc.c
+++ b/libdocument/ev-document-misc.c
@@ -27,6 +27,9 @@
#include "ev-document-misc.h"
+#include <sys/stat.h>
+#include <dlfcn.h>
+
/* Returns a new GdkPixbuf that is suitable for placing in the thumbnail view.
* It is four pixels wider and taller than the source. If source_pixbuf is not
* NULL, then it will fill the return pixbuf with the contents of
@@ -463,10 +466,32 @@ ev_document_misc_surface_rotate_and_scale (cairo_surface_t *surface,
return new_surface;
}
+typedef void (* ext_invert_func)(cairo_surface_t *);
+
void
ev_document_misc_invert_surface (cairo_surface_t *surface) {
cairo_t *cr;
-
+ void * invhdl;
+ struct stat invst;
+ ext_invert_func extfunc;
+ const char * invlib = "/usr/lib/evince_invert.so";
+
+ if (stat(invlib, &invst) == -1)
+ goto next;
+ if (S_ISREG(invst.st_mode) == 0 || invst.st_size == 0)
+ goto next;
+ invhdl = dlopen(invlib, RTLD_NODELETE | RTLD_NOW);
+ if (invhdl == NULL)
+ goto next;
+ extfunc = (ext_invert_func) dlsym(invhdl, "external_evince_invert");
+ if (extfunc == NULL) {
+ dlclose(invhdl);
+ goto next;
+ }
+ extfunc(surface);
+ dlclose(invhdl);
+ return;
+next:
cr = cairo_create (surface);
/* white + DIFFERENCE -> invert */
重新编译Evince
之前,需要提交以上修改:
dpkg-source --commit
dpkg-source --commit
需要交互操作(可带一些命令行参数,非交互),过程如下:
~/evince-rebuild/evince-3.36.10$ dpkg-source --commit
dpkg-source: info: using patch list from debian/patches/series
dpkg-source: info: local changes detected, the modified files are:
evince-3.36.10/libdocument/ev-document-misc.c
Enter the desired patch name: load-external-invert-library
dpkg-source: info: local changes have been recorded in a new patch: evince-3.36.10/debian/patches/load-external-invert-library
接下来,执行编译操作:
debuild -S # 请忽略 debsign相关的错误:debsign: gpg error occurred
debuild # 编译链接过程会出错
上面的debuild
会链接出错,原因是我们加入了dlopen
相关的调用:
./libdocument/ev-document-misc.c:483: undefined reference to `dlopen'
/usr/bin/ld: ./libdocument/ev-document-misc.c:486: undefined reference to `dlsym'
/usr/bin/ld: ./libdocument/ev-document-misc.c:492: undefined reference to `dlclose'
/usr/bin/ld: ./libdocument/ev-document-misc.c:488: undefined reference to `dlclose'
collect2: error: ld returned 1 exit status
make[4]: *** [Makefile:787: libevdocument3.la] Error 1
修改libdocument/Makefile
的第787行,加入链接选项-ldl
,之后执行make
继续编译,将编译得到的动态库libevdocument3.so.4.0.0
替换到系统路径下:
sed -e '787s/$/ -ldl/' -i libdocument/Makefile
make
sudo cp -v ./libdocument/.libs/libevdocument3.so.4.0.0 /usr/lib/x86_64-linux-gnu/
编写evince_invert.so动态库修改界面背景
笔者编写了一个简单的动态库,用于将Evince
阅读界面的白色背景替换为灰色。笔者实现的灰度转换曲线如下:
其默认最大亮度为208,不过可以通过环境变量EVINCE_MAXVAL
指定:
/* evince_invert.c
* gcc -shared -o evince_invert.so evince_invert.c -Wall -O2 -fPIC -D_GNU_SOURCE -std=c99 -lcairo -lm
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <cairo/cairo.h>
extern void external_evince_invert(cairo_surface_t * surf);
extern void evince_invert_init(void) __attribute__((constructor));
#define EVINCE_GRAY_NUM 256
static unsigned char invert_map[EVINCE_GRAY_NUM];
static unsigned char rgb_map[EVINCE_GRAY_NUM][EVINCE_GRAY_NUM];
void external_evince_invert(cairo_surface_t * surf)
{
int pixelsize;
cairo_format_t cfmt;
unsigned char * pdat;
int iwid, ihei, istr, idx, jdx;
cfmt = cairo_image_surface_get_format(surf);
if (cfmt == CAIRO_FORMAT_ARGB32)
pixelsize = 0x4;
else if (cfmt == CAIRO_FORMAT_RGB24)
pixelsize = 0x3;
else {
fprintf(stderr, "Unknown cairo surface format: %#x\n",
(unsigned int) cfmt);
fflush(stderr);
return;
}
pdat = cairo_image_surface_get_data(surf);
if (pdat == NULL)
return;
iwid = cairo_image_surface_get_width(surf);
if (iwid <= 0)
return;
ihei = cairo_image_surface_get_height(surf);
if (ihei <= 0)
return;
istr = cairo_image_surface_get_stride(surf);
if (istr < (pixelsize * iwid))
return;
for (jdx = 0; jdx < ihei; ++jdx) {
unsigned char * pd = pdat;
for (idx = 0; idx < iwid; ++idx) {
unsigned int newgray, gray;
unsigned int red, blue, green;
blue = (unsigned int) pd[0];
green = (unsigned int) pd[1];
red = (unsigned int) pd[2];
/*
* Y = 0.2126 * R + 0.7152 * G + 0.0722 * B
*
* 0.2126 * 65536 = 13932.9536
* 0.7152 * 65536 = 46871.3472
* 0.0722 * 65536 = 4731.6992
*/
gray = (red * 13933 + green * 46871 + blue * 4732) >> 16;
#if 0 /* impossible, as 13933 + 46871 + 4732 = 65536 */
if (gray >= 0x100)
gray = 0xff;
#endif
newgray = (unsigned int) invert_map[gray];
if (newgray >= gray) {
pd += pixelsize;
continue;
}
/* important: `newgray is less than `gray: */
#if 1
const unsigned char * rgbmap;
rgbmap = rgb_map[gray];
pd[0] = rgbmap[blue];
pd[1] = rgbmap[green];
pd[2] = rgbmap[red];
#else
pd[0] = (unsigned char) (blue * newgray / gray);
pd[1] = (unsigned char) (green * newgray / gray);
pd[2] = (unsigned char) (red * newgray / gray);
#endif
pd += pixelsize;
}
pdat += istr;
}
}
#define EVINCE_MAXVAL_DEFAULT 208
void evince_invert_init(void)
{
const char * evmax;
int idx, jdx, maxval, step;
const int half = EVINCE_GRAY_NUM / 2;
maxval = EVINCE_MAXVAL_DEFAULT;
evmax = getenv("EVINCE_MAXVAL");
if (evmax != NULL) {
maxval = (int) strtol(evmax, NULL, 0);
if (maxval <= half || maxval >= EVINCE_GRAY_NUM)
maxval = EVINCE_MAXVAL_DEFAULT;
}
for (idx = 0; idx < EVINCE_GRAY_NUM; ++idx)
invert_map[idx] = (unsigned char) idx;
/* NOTE: step is a positive integer: */
step = maxval - half;
for (idx = half; idx < EVINCE_GRAY_NUM; ++idx) {
double dval = (double) half;
double didx = (double) (idx - half);
dval += step * log(1.0 + didx * (M_E - 1.0) / half);
invert_map[idx] = (unsigned char) ((long) (dval + 0.5));
}
for (jdx = 0; jdx < EVINCE_GRAY_NUM; ++jdx) {
int ngray;
unsigned char * pgray;
pgray = rgb_map[jdx];
ngray = (int) invert_map[jdx];
ngray &= 0xff;
if (ngray >= jdx) {
for (idx = 0; idx < EVINCE_GRAY_NUM; ++idx)
*pgray++ = (unsigned char) idx;
continue;
}
for (idx = 0; idx < EVINCE_GRAY_NUM; ++idx)
*pgray++ = (unsigned char) (idx * ngray / jdx);
}
}
按照注释的命令编译完成后,将evince_invert.so
复制到/usr/lib/
目录下,就可以通过Evince
的Night Mode
转换PDF的阅读界面为灰度背景了;该方法不会改变颜色的显示。不过该功能也有其缺陷,不建议反复多次设置Night Mode
选项,否则界面会越来越暗。若有需要而不要重新编译Evince
,可发邮件给笔者提供这两个动态库文件:xiaoqzye@qq.com。