genode/repos/libports/src/app/pdf_view/main.cc

491 lines
12 KiB
C++

/*
* \brief MuPDF for Genode
* \author Norman Feske
* \date 2012-01-09
*/
/*
* Copyright (C) 2012-2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <base/env.h>
#include <libc/component.h>
#include <framebuffer_session/client.h>
#include <base/sleep.h>
#include <util/reconstructible.h>
#include <base/attached_rom_dataspace.h>
#include <util/geometry.h>
#include <input_session/client.h>
#include <input/event.h>
#include <input/keycodes.h>
#include <nitpicker_session/connection.h>
/* MuPDF includes */
extern "C" {
#include <fitz.h>
#include <mupdf.h>
#include <muxps.h>
#include <pdfapp.h>
}
/* libc includes */
#include <dirent.h>
#include <unistd.h>
/***************
** Dithering **
***************/
/*
* XXX blatantly copied from 'demo/src/app/backdrop/main.cc'
*
* We should factor-out the dithering support into a separate header file.
* But where is a good place to put it?
*/
enum { DITHER_SIZE = 16, DITHER_MASK = DITHER_SIZE - 1 };
static const int dither_matrix[DITHER_SIZE][DITHER_SIZE] = {
{ 0,192, 48,240, 12,204, 60,252, 3,195, 51,243, 15,207, 63,255 },
{ 128, 64,176,112,140, 76,188,124,131, 67,179,115,143, 79,191,127 },
{ 32,224, 16,208, 44,236, 28,220, 35,227, 19,211, 47,239, 31,223 },
{ 160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95 },
{ 8,200, 56,248, 4,196, 52,244, 11,203, 59,251, 7,199, 55,247 },
{ 136, 72,184,120,132, 68,180,116,139, 75,187,123,135, 71,183,119 },
{ 40,232, 24,216, 36,228, 20,212, 43,235, 27,219, 39,231, 23,215 },
{ 168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87 },
{ 2,194, 50,242, 14,206, 62,254, 1,193, 49,241, 13,205, 61,253 },
{ 130, 66,178,114,142, 78,190,126,129, 65,177,113,141, 77,189,125 },
{ 34,226, 18,210, 46,238, 30,222, 33,225, 17,209, 45,237, 29,221 },
{ 162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93 },
{ 10,202, 58,250, 6,198, 54,246, 9,201, 57,249, 5,197, 53,245 },
{ 138, 74,186,122,134, 70,182,118,137, 73,185,121,133, 69,181,117 },
{ 42,234, 26,218, 38,230, 22,214, 41,233, 25,217, 37,229, 21,213 },
{ 170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85 }
};
static inline uint16_t rgb565(int r, int g, int b)
{
enum {
R_MASK = 0xf800, R_LSHIFT = 8,
G_MASK = 0x07e0, G_LSHIFT = 3,
B_MASK = 0x001f, B_RSHIFT = 3
};
return ((r << R_LSHIFT) & R_MASK)
| ((g << G_LSHIFT) & G_MASK)
| ((b >> B_RSHIFT) & B_MASK);
}
static void convert_line_rgba_to_rgb565(const unsigned char *rgba_src,
uint16_t *dst, int num_pixels, int line)
{
using namespace Genode;
enum { CHANNEL_MAX = 255 };
int const *dm = dither_matrix[line & DITHER_MASK];
for (int i = 0; i < num_pixels; i++) {
int v = dm[i & DITHER_MASK] >> 5;
*dst++ = rgb565(min(v + (int)rgba_src[0], (int)CHANNEL_MAX),
min(v + (int)rgba_src[1], (int)CHANNEL_MAX),
min(v + (int)rgba_src[2], (int)CHANNEL_MAX));
/* we ignore the alpha channel */
rgba_src += 4; /* next pixel */
}
}
static int pdf_select(const struct dirent *d)
{
char const *name = d->d_name;
int n = strlen(name);
return (n > 4)
? (!strncmp(&name[n-4], ".pdf", 4))
: 0;
}
/**************
** PDF view **
**************/
class Pdf_view
{
public:
/**
* Exception types
*/
class Non_supported_framebuffer_mode { };
class Unexpected_document_color_depth { };
typedef uint16_t pixel_t;
typedef Framebuffer::Mode Mode;
private:
enum { NO_ALPHA = false };
Genode::Env &_env;
Nitpicker::Connection _nitpicker { _env };
Framebuffer::Session &_framebuffer = *_nitpicker.framebuffer();
Input::Session_client &_input = *_nitpicker.input();
Framebuffer::Mode _nit_mode = _nitpicker.mode();
Framebuffer::Mode _fb_mode {};
Nitpicker::Area _view_area { };
Genode::Constructible<Genode::Attached_dataspace> _fb_ds { };
Genode::Signal_handler<Pdf_view> _nit_mode_handler {
_env.ep(), *this, &Pdf_view::_handle_nit_mode };
Genode::Signal_handler<Pdf_view> _sync_handler {
_env.ep(), *this, &Pdf_view::_refresh };
Genode::Signal_handler<Pdf_view> _input_handler {
_env.ep(), *this, &Pdf_view::_handle_input_events };
Nitpicker::Session::View_handle _view = _nitpicker.create_view();
pdfapp_t _pdfapp { };
int _motion_x = 0;
int _motion_y = 0;
pixel_t *_fb_base() { return _fb_ds->local_addr<pixel_t>(); }
/**
* Replace the backing framebuffer
*
* The Nitpicker view is reduced if rebuffering
* is too expensive.
*/
void _rebuffer(int w, int h)
{
while (!_fb_ds.constructed()
|| w > _fb_mode.width()
|| h > _fb_mode.height())
{
try {
Mode new_mode(w, h, _nit_mode.format());
_nitpicker.buffer(new_mode, NO_ALPHA);
_fb_mode = new_mode;
if (_fb_ds.constructed())
_fb_ds.destruct();
_fb_ds.construct(_env.rm(), _framebuffer.dataspace());
break;
} catch (Genode::Out_of_ram) { }
w -= w >> 2;
h -= h >> 2;
}
_view_area = Nitpicker::Area(w, h);
}
void _resize(int w, int h)
{
/*
* XXX replace heuristics with a meaningful computation
*
* The magic values are hand-tweaked manually to accommodating the
* use case of showing slides.
*/
_pdfapp.resolution = Genode::min(_nit_mode.width()/5,
_nit_mode.height()/3.8);
_rebuffer(w, h);
_pdfapp.scrw = _view_area.w();
_pdfapp.scrh = _view_area.h();
pdfapp_onresize(&_pdfapp, _view_area.w(), _view_area.h());
using namespace Nitpicker;
typedef Nitpicker::Session::Command Command;
_nitpicker.enqueue<Command::Geometry>(
_view, Rect(Point(), _view_area));
_nitpicker.enqueue<Command::To_front>(_view, Nitpicker::Session::View_handle());
_nitpicker.execute();
}
void _handle_nit_mode()
{
_nit_mode = _nitpicker.mode();
_resize(_nit_mode.width(), _nit_mode.height());
}
void _handle_input_event(Input::Event const &ev)
{
using namespace Input;
ev.handle_relative_motion([&] (int x, int y) {
_motion_x += x;
_motion_y += y;
//pdfapp_onmouse(&_pdfapp, _motion_x, _motion_y, 0, 0, 0);
});
ev.handle_absolute_motion([&] (int x, int y) {
_motion_x = x;
_motion_y = y;
//pdfapp_onmouse(&_pdfapp, _motion_x, _motion_y, 0, 0, 0);
});
if (ev.key_press(BTN_LEFT))
pdfapp_onmouse(&_pdfapp, _motion_x, _motion_y, 1, 0, -1);
else
if (ev.key_release(BTN_LEFT))
pdfapp_onmouse(&_pdfapp, _motion_x, _motion_y, 1, 0, 1);
else
if (ev.key_press(KEY_PAGEDOWN) || ev.key_press(KEY_RIGHT))
pdfapp_onkey(&_pdfapp, '.');
else
if (ev.key_press(KEY_PAGEUP) || ev.key_press(KEY_LEFT))
pdfapp_onkey(&_pdfapp, ',');
else
if (ev.key_press(KEY_DOWN))
pdfapp_onkey(&_pdfapp, 'j');
else
if (ev.key_press(KEY_UP))
pdfapp_onkey(&_pdfapp, 'k');
ev.handle_press([&] (Keycode, Codepoint glyph) {
if ((glyph.value & 0x7f) && !(glyph.value & 0x80)) {
pdfapp_onkey(&_pdfapp, glyph.value);
}
});
/*
ev.handle_wheel([&] (int, int y) {
pdfapp_onmouse(
&_pdfapp, _motion_x, _motion_y,
y > 0 ? 4 : 5, 0, 1);
});
*/
}
void _handle_input_events()
{
Libc::with_libc([&] () {
_input.for_each_event([&] (Input::Event const &ev) {
_handle_input_event(ev); }); });
}
void _refresh()
{
_framebuffer.refresh(0, 0, _view_area.w(), _view_area.h());
/* handle one sync signal only */
_framebuffer.sync_sigh(Genode::Signal_context_capability());
}
public:
/**
* Constructor
*
* \throw Non_supported_framebuffer_mode
* \throw Unexpected_document_color_depth
*/
Pdf_view(Genode::Env &env) : _env(env)
{
_nitpicker.mode_sigh(_nit_mode_handler);
_input.sigh(_input_handler);
pdfapp_init(&_pdfapp);
_pdfapp.userdata = this;
_pdfapp.pageno = 0;
{
struct dirent **list = NULL;
if (scandir("/", &list, pdf_select, alphasort) > 0) {
char const *file_name = list[0]->d_name;
int fd = open(file_name, O_BINARY | O_RDONLY, 0666);
if (fd < 0) {
Genode::error("Could not open input file \"", file_name, "\", Exiting.");
exit(fd);
}
pdfapp_open(&_pdfapp, (char *)file_name, fd, 0);
} else {
Genode::error("failed to find a PDF to open");
exit(~0);
}
}
if (_pdfapp.image->n != 4) {
Genode::error("Unexpected color depth, expected 4, got ",
_pdfapp.image->n, ", Exiting.");
throw Unexpected_document_color_depth();
}
Genode::log(Genode::Cstring(pdfapp_version(&_pdfapp)));
}
void title(char const *msg)
{
typedef Nitpicker::Session::Command Command;
_nitpicker.enqueue<Command::Title>(_view, msg);
_nitpicker.execute();
}
void show();
void exit(int code = 0) { _env.parent().exit(code); }
};
void Pdf_view::show()
{
if (!_fb_ds.constructed())
_resize(_pdfapp.image->w, _pdfapp.image->h);
Genode::Area<> const fb_size(_fb_mode.width(), _fb_mode.height());
int const x_max = Genode::min((int)fb_size.w(), _pdfapp.image->w);
int const y_max = Genode::min((int)fb_size.h(), _pdfapp.image->h);
/* clear framebuffer */
memset(_fb_base(), 0, _fb_ds->size());
Genode::size_t src_line_bytes = _pdfapp.image->n * _pdfapp.image->w;
unsigned char *src_line = _pdfapp.image->samples;
Genode::size_t dst_line_width = fb_size.w(); /* in pixels */
pixel_t *dst_line = _fb_base();
/* skip first two lines as they contain white (XXX) */
src_line += 2*src_line_bytes;
dst_line += 2*dst_line_width;
int const tweaked_y_max = y_max - 2;
/* center vertically if the dst buffer is higher than the image */
if (_pdfapp.image->h < (int)_view_area.h()) {
dst_line += dst_line_width*((_view_area.h() - _pdfapp.image->h)/2);
} else {
auto n = src_line_bytes * Genode::min(_pdfapp.image->h - (int)_view_area.h(), -_pdfapp.pany);
src_line += n;
}
/* center horizontally if the dst buffer is wider than the image */
if (_pdfapp.image->w < (int)_view_area.w())
dst_line += (_view_area.w() - _pdfapp.image->w)/2;
for (int y = 0; y < tweaked_y_max; y++) {
convert_line_rgba_to_rgb565(src_line, dst_line, x_max, y);
src_line += src_line_bytes;
dst_line += dst_line_width;
}
/* refresh after the next sync signal */
_framebuffer.sync_sigh(_sync_handler);
}
extern "C" void _sigprocmask()
{
/* suppress debug message by default "not-implemented" implementation */
}
/**************************
** Called from pdfapp.c **
**************************/
void winrepaint(pdfapp_t *pdfapp)
{
Pdf_view *pdf_view = (Pdf_view *)pdfapp->userdata;
pdf_view->show();
}
void winrepaintsearch(pdfapp_t *pdfapp)
{
Pdf_view *pdf_view = (Pdf_view *)pdfapp->userdata;
pdf_view->show();
}
void wincursor(pdfapp_t *, int)
{
}
void windocopy(pdfapp_t*) { }
void winerror(pdfapp_t *pdfapp, fz_error error)
{
Genode::error("winerror: error=", error);
Pdf_view *pdf_view = (Pdf_view *)pdfapp->userdata;
pdf_view->exit();
}
void winwarn(pdfapp_t *, char *msg)
{
Genode::warning("MuPDF: ", Genode::Cstring(msg));
}
void winhelp(pdfapp_t *pdfapp)
{
Genode::log(Genode::Cstring(pdfapp_usage(pdfapp)));
}
char *winpassword(pdfapp_t *, char *)
{
Genode::warning(__func__, " not implemented");
return NULL;
}
void winopenuri(pdfapp_t*, char *s)
{
Genode::log(Genode::Cstring(s));
}
void winclose(pdfapp_t *pdfapp)
{
Pdf_view *pdf_view = (Pdf_view *)pdfapp->userdata;
pdf_view->exit();
}
void winreloadfile(pdfapp_t *)
{
Genode::warning(__func__, " not implemented");
}
void wintitle(pdfapp_t *pdfapp, char *s)
{
Pdf_view *pdf_view = (Pdf_view *)pdfapp->userdata;
pdf_view->title(s);
}
void winresize(pdfapp_t*, int, int)
{
}
void Libc::Component::construct(Libc::Env &env)
{
Libc::with_libc([&] () { static Pdf_view main(env); });
}