updf v1.5:

- continuous scrolling through pages
- add scroll-bar
- drag page by holding right mouse button
- select and copy text in UTF-8
- colored icons
This commit is contained in:
2026-07-06 06:08:52 +00:00
committed by Burer
parent 91f5df9c53
commit b765aa62bf
9 changed files with 1733 additions and 1072 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+9
View File
@@ -512,3 +512,12 @@ int kos_get_key()
asm volatile("int $0x40":"=a"(__ret):"0"(2));
if(!(__ret & 0xFF)) return (__ret>>8)&0xFF; else return 0;
}
// Raw result of fn 2: bits 0-7 = status (0 = key present),
// bits 8-15 = ASCII code, bits 16-23 = layout-independent scancode.
unsigned kos_get_key_full()
{
unsigned __ret;
asm volatile("int $0x40":"=a"(__ret):"0"(2));
return __ret;
}
+1
View File
@@ -182,4 +182,5 @@ int kos_random(int num);
int kos_get_mouse_wheels(void);
void kos_screen_max(int* x, int* y);
int kos_get_key();
unsigned kos_get_key_full();
void kos_text(int x, int y, int color, const char* text, int len);
+644 -120
View File
@@ -20,7 +20,50 @@ struct proc_info Form;
#define DOCUMENT_BORDER 0x979797
#define DOCUMENT_BG 0xABABAB
#define SCROLL_H 25
#define SCROLL_STEP 50 // pixels moved per scroll gesture (arrow key / wheel notch)
#define PAGE_GAP 14 // gray gap between pages, like in a word processor
// keyboard scancodes (layout-independent, from fn 2 bits 16-23)
#define SC_ESC 0x01
#define SC_MINUS 0x0C // main row -
#define SC_EQUAL 0x0D // main row =/+
#define SC_R 0x13
#define SC_LBRACKET 0x1A // [
#define SC_RBRACKET 0x1B // ]
#define SC_G 0x22
#define SC_L 0x26
#define SC_NUM_MINUS 0x4A // keypad -
#define SC_NUM_PLUS 0x4E // keypad +
#define SC_HOME 0x47
#define SC_UP 0x48
#define SC_PGUP 0x49
#define SC_END 0x4F
#define SC_DOWN 0x50
#define SC_PGDN 0x51
// vertical scrollbar in the WebView style: flat box_lib type 2, no arrows
#define SCROLL_W 15
#define SB_BG_COL 0xEEEEEE // track
#define SB_FRONT_COL 0xBBBBBB // runner
#define SB_MIN_RUN 10 // minimal runner height, as in box_lib
static int sb_run_y, sb_run_h; // runner rect (client coords), for hit testing
static char sb_active; // the document is longer than the viewport
static char sb_drag, sb_lmb_prev;
static int sb_drag_off; // pointer offset inside the runner while dragging
// mouse interaction on the page: left = select text, right = drag (pan)
static char sel_dragging; // left button held, building a selection
static char sel_has; // a highlighted selection is on screen
static int sel_x0, sel_y0; // selection anchor, device coordinates
static char pan_dragging; // right button held, panning the page
static int pan_last_my; // last pointer y while panning
// look-ahead cache of rendered neighbour pages for continuous scrolling
#define PAGE_CACHE_N 3
static fz_pixmap *cache_pix[PAGE_CACHE_N];
static int cache_no[PAGE_CACHE_N];
static int cache_res = -1, cache_rot = -1, cache_gray = -1;
short show_area_w = 65;
short show_area_x;
@@ -28,21 +71,19 @@ short show_area_x;
char key_mode_enter_page_number;
int new_page_number;
static short window_center, draw_h, draw_w;
const char *help[] = {
"Keys:",
" ",
"PageUp - go to previous page",
"PageDown - go to next page",
"Home - go to first page",
"End - go to last page",
"Down arrow - scroll current page down",
"Up arrow - scroll current page up",
"+/- - zoom in/out",
"[ or l - rotate page 90 deg to the left",
"] or r - rotate page 90 deg to the right",
"g - grayscale on/off",
" ",
"PageUp - go to previous page",
"PageDown - go to next page",
"Home - go to first page",
"End - go to last page",
"Down arrow - scroll down",
"Up arrow - scroll up",
"+/- - zoom in/out",
"[ or l - rotate page 90 deg to the left",
"] or r - rotate page 90 deg to the right",
"g - grayscale on/off",
" ",
"Press Escape to hide help",
0
@@ -56,6 +97,23 @@ void DrawPagination(void);
void HandleNewPageNumber(unsigned char key);
void ApplyNewPageNumber(void);
void DrawMainWindow(void);
void FlushPageCache(void);
void SyncCacheParams(void);
void StashPixmap(int pageno, fz_pixmap *pix);
fz_pixmap *GetPageImage(int pageno);
int PageCenterX(fz_pixmap *pix);
void BlitPageSlice(fz_pixmap *pix, int vy, int srcy, int h);
void GoToPage(int n, int new_pany);
void NormalizeScrollPos(void);
int ViewW(void);
void GetDocMetrics(int *total, int *pos);
void DrawScrollbar(void);
void ScrollToDocPos(int pos);
void ScrollbarMouse(void);
void ClearSelection(void);
int ClientToPageDev(int mx, int my, int *dx, int *dy);
void CopySelectionToClipboard(void);
void ProcessPageMouse(void);
// not implemented yet
@@ -120,59 +178,505 @@ void winrepaint(pdfapp_t *app)
}
/* == continuous scroll: page cache =============================== */
void FlushPageCache(void)
{
int i;
for (i = 0; i < PAGE_CACHE_N; i++)
{
if (cache_pix[i]) fz_drop_pixmap(cache_pix[i]);
cache_pix[i] = NULL;
cache_no[i] = 0;
}
}
// drop all cached pages if the view parameters have changed
void SyncCacheParams(void)
{
if (cache_res != gapp.resolution || cache_rot != gapp.rotate || cache_gray != gapp.grayscale)
{
FlushPageCache();
cache_res = gapp.resolution;
cache_rot = gapp.rotate;
cache_gray = gapp.grayscale;
}
}
// keep a reference to an already rendered page (e.g. the outgoing current
// page on a flip) so scrolling back to it is instant
void StashPixmap(int pageno, fz_pixmap *pix)
{
int i, slot = -1;
if (!pix) return;
SyncCacheParams(); // the pixmap is rendered with the current params
for (i = 0; i < PAGE_CACHE_N; i++)
if (cache_pix[i] && cache_no[i] == pageno) return; // already cached
for (i = 0; i < PAGE_CACHE_N; i++)
if (!cache_pix[i]) { slot = i; break; }
if (slot < 0) // evict the entry farthest from this page
{
slot = 0;
for (i = 1; i < PAGE_CACHE_N; i++)
if (ABS(cache_no[i] - pageno) > ABS(cache_no[slot] - pageno)) slot = i;
fz_drop_pixmap(cache_pix[slot]);
}
cache_pix[slot] = fz_keep_pixmap(pix);
cache_no[slot] = pageno;
}
// pixmap of any page: the current one comes from pdfapp, neighbours
// are rendered on demand and cached
fz_pixmap *GetPageImage(int pageno)
{
int i, slot;
if (pageno == gapp.pageno) return gapp.image;
if (pageno < 1 || pageno > gapp.pagecount) return NULL;
SyncCacheParams();
for (i = 0; i < PAGE_CACHE_N; i++)
if (cache_pix[i] && cache_no[i] == pageno) return cache_pix[i];
slot = -1;
for (i = 0; i < PAGE_CACHE_N; i++)
if (!cache_pix[i]) { slot = i; break; }
if (slot < 0) // evict the entry farthest from the current page
{
slot = 0;
for (i = 1; i < PAGE_CACHE_N; i++)
if (ABS(cache_no[i] - gapp.pageno) > ABS(cache_no[slot] - gapp.pageno)) slot = i;
fz_drop_pixmap(cache_pix[slot]);
cache_pix[slot] = NULL;
}
cache_pix[slot] = pdfapp_renderpage(&gapp, pageno);
cache_no[slot] = cache_pix[slot] ? pageno : 0;
return cache_pix[slot];
}
/* == continuous scroll: view composition ========================= */
// width of the page area: the client width minus the scrollbar strip
int ViewW(void)
{
return Form.cwidth - SCROLL_W;
}
int PageCenterX(fz_pixmap *pix)
{
if (ViewW() > pix->w) return (ViewW() - pix->w) / 2;
return 0;
}
// draw a horizontal slice of a page at viewport line vy:
// side backgrounds, 1px borders and the page image itself
void BlitPageSlice(fz_pixmap *pix, int vy, int srcy, int h)
{
int wc = PageCenterX(pix);
if (wc > 0)
{
kol_paint_bar(0, TOOLBAR_HEIGHT + vy, wc - 1, h, DOCUMENT_BG);
kol_paint_bar(wc - 1, TOOLBAR_HEIGHT + vy, 1, h, DOCUMENT_BORDER);
kol_paint_bar(wc + pix->w, TOOLBAR_HEIGHT + vy, 1, h, DOCUMENT_BORDER);
if (ViewW() > wc + pix->w + 1)
kol_paint_bar(wc + pix->w + 1, TOOLBAR_HEIGHT + vy,
ViewW() - wc - pix->w - 1, h, DOCUMENT_BG);
}
kos_blit(wc + Form.cleft,
Form.ctop + TOOLBAR_HEIGHT + vy,
ViewW() - wc,
h,
0,
srcy,
pix->w,
pix->h,
pix->w * pix->n, // stride
pix->samples // image
);
}
// compose the viewport: tail of the current page, the gap, the head of
// the next page(s) - like the continuous page view in a word processor
void winblit(pdfapp_t *app)
{
int vh, vy, page_top, k, wc;
fz_pixmap *pix;
if (do_not_blit) return;
if (Form.cwidth == 0) return; // window is not drawn yet
if (key_mode_enter_page_number==1) HandleNewPageNumber(0); else DrawPagination();
if (Form.cwidth > gapp.image->w) window_center = (Form.cwidth - gapp.image->w) / 2; else window_center = 0;
gapp.panx = 0;
kos_blit(window_center + Form.cleft,
Form.ctop + TOOLBAR_HEIGHT,
Form.cwidth,
Form.cheight - TOOLBAR_HEIGHT,
gapp.panx,
gapp.pany,
gapp.image->w,
gapp.image->h,
gapp.image->w * gapp.image->n, // stride
gapp.image->samples // image
);
/*
void kos_blit(int dstx, int dsty, int w, int h, int srcx, int srcy, int srcw, int srch, int stride, char *d)
*/
vh = Form.cheight - TOOLBAR_HEIGHT;
if (vh <= 0) return;
vy = 0; // how much of the viewport is painted already
page_top = -gapp.pany; // viewport y of the current page top
k = gapp.pageno;
while (vy < vh && (pix = GetPageImage(k)) != NULL)
{
int page_bot = page_top + pix->h;
wc = PageCenterX(pix);
// border above the page, on the last row of the gap
if (page_top > 0 && page_top - 1 < vh)
kol_paint_bar(wc ? wc - 1 : 0, TOOLBAR_HEIGHT + page_top - 1,
MIN(pix->w + 2, ViewW()), 1, DOCUMENT_BORDER);
if (page_bot > vy)
{
int h = MIN(page_bot, vh) - vy;
BlitPageSlice(pix, vy, vy - page_top, h);
vy += h;
}
if (vy >= vh) break;
page_top = page_bot;
if (k < gapp.pagecount) // inter-page gap
{
int gap_bot = page_bot + PAGE_GAP;
if (gap_bot > vy)
{
kol_paint_bar(0, TOOLBAR_HEIGHT + vy, ViewW(),
MIN(gap_bot, vh) - vy, DOCUMENT_BG);
// border under the page
if (page_bot >= 0 && page_bot < vh)
kol_paint_bar(wc ? wc - 1 : 0, TOOLBAR_HEIGHT + page_bot,
MIN(pix->w + 2, ViewW()), 1, DOCUMENT_BORDER);
vy = MIN(gap_bot, vh);
}
page_top = gap_bot;
}
k++;
}
if (vy < vh) // area below the last page
kol_paint_bar(0, TOOLBAR_HEIGHT + vy, ViewW(), vh - vy, DOCUMENT_BG);
DrawScrollbar();
}
/* == vertical scrollbar (WebView / box_lib type 2 style) ========= */
void DrawPageSides(void)
{
if (gapp.image->h < Form.cheight - TOOLBAR_HEIGHT) {
draw_h = gapp.image->h - gapp.pany;
} else {
draw_h = Form.cheight - TOOLBAR_HEIGHT;
// document height and current position in pixels; pages are assumed
// to be as tall as the current one (true for typical PDFs)
void GetDocMetrics(int *total, int *pos)
{
int unit = gapp.image->h + PAGE_GAP;
*total = gapp.pagecount * unit - PAGE_GAP;
*pos = (gapp.pageno - 1) * unit + gapp.pany;
}
void DrawScrollbar(void)
{
int VH = Form.cheight - TOOLBAR_HEIGHT;
int x0 = ViewW();
int total, pos, run, pos2;
GetDocMetrics(&total, &pos);
kol_paint_bar(x0, TOOLBAR_HEIGHT, SCROLL_W, VH, SB_BG_COL); // track
if (total <= VH)
{
sb_active = 0;
return;
}
if (gapp.image->w < Form.cwidth) {
window_center = (Form.cwidth - gapp.image->w) / 2;
draw_w = gapp.image->w + 2;
kol_paint_bar(0, TOOLBAR_HEIGHT, window_center-1, Form.cheight - TOOLBAR_HEIGHT, DOCUMENT_BG);
kol_paint_bar(window_center-1, TOOLBAR_HEIGHT, 1, draw_h, DOCUMENT_BORDER);
kol_paint_bar(window_center + gapp.image->w, TOOLBAR_HEIGHT, 1, draw_h, DOCUMENT_BORDER);
kol_paint_bar(window_center + gapp.image->w+1, TOOLBAR_HEIGHT, Form.cwidth - window_center - gapp.image->w - 1, Form.cheight - TOOLBAR_HEIGHT, DOCUMENT_BG);
} else {
window_center = 1;
draw_w = Form.cwidth;
// box_lib formulas: runner size and offset inside the track
run = (int)((long long)VH * VH / total);
if (run < SB_MIN_RUN) run = SB_MIN_RUN;
if (run > VH) run = VH;
pos2 = (int)((long long)(VH - run) * pos / (total - VH));
if (pos2 > VH - run) pos2 = VH - run;
if (pos2 < 0) pos2 = 0;
// flat runner: 1px inset on the left, 1px track-colored lines
// on top and bottom (line_col == bckg_col in the WebView scheme)
kol_paint_bar(x0 + 1, TOOLBAR_HEIGHT + pos2 + 1, SCROLL_W - 1, run - 2, SB_FRONT_COL);
sb_active = 1;
sb_run_y = TOOLBAR_HEIGHT + pos2;
sb_run_h = run;
}
// jump so that the viewport top lands at the given document position
void ScrollToDocPos(int pos)
{
int unit = gapp.image->h + PAGE_GAP;
int page;
ClearSelection();
if (pos < 0) pos = 0;
page = pos / unit + 1;
if (page > gapp.pagecount) page = gapp.pagecount;
if (page != gapp.pageno)
GoToPage(page, pos - (page - 1) * unit);
else
gapp.pany = pos - (page - 1) * unit;
NormalizeScrollPos();
winblit(&gapp);
}
void ScrollbarMouse(void)
{
int mp, mx, my, lmb, VH, total, pos;
// fn 37/1 returns coordinates relative to the client area,
// signed 16-bit (negative when the cursor is above/left of it)
mp = kol_mouse_posw();
mx = (short)(mp >> 16);
my = (short)(mp & 0xFFFF);
lmb = kol_mouse_btn() & 1;
VH = Form.cheight - TOOLBAR_HEIGHT;
if (!sb_active || !lmb)
{
sb_drag = 0;
sb_lmb_prev = lmb;
return;
}
kol_paint_bar(window_center - 1, gapp.image->h - gapp.pany + TOOLBAR_HEIGHT, draw_w, 1, DOCUMENT_BORDER);
kol_paint_bar(window_center - 1, gapp.image->h - gapp.pany + TOOLBAR_HEIGHT + 1,
draw_w, Form.cheight - gapp.image->h - TOOLBAR_HEIGHT + gapp.pany - 1, DOCUMENT_BG);
GetDocMetrics(&total, &pos);
if (sb_drag)
{
// runner follows the pointer
int pos2 = my - sb_drag_off - TOOLBAR_HEIGHT;
int space = VH - sb_run_h;
if (space > 0)
{
int newpos = (int)((long long)pos2 * (total - VH) / space);
if (newpos > total - VH) newpos = total - VH;
ScrollToDocPos(newpos);
}
}
else if (!sb_lmb_prev
&& mx >= ViewW() && mx < Form.cwidth
&& my >= TOOLBAR_HEIGHT && my < Form.cheight)
{
if (my >= sb_run_y && my < sb_run_y + sb_run_h)
{
sb_drag = 1;
sb_drag_off = my - sb_run_y;
}
else if (my < sb_run_y) ScrollToDocPos(pos - VH); // page up
else ScrollToDocPos(pos + VH); // page down
}
sb_lmb_prev = lmb;
}
/* == text selection & drag-to-pan =============================== */
// restore the pixels under the current highlight (if any); the caller
// must repaint. Called before any scroll/zoom/rotate/page flip so the
// pixmap is never stashed or re-rendered with inverted pixels in it.
void ClearSelection(void)
{
if (sel_has)
{
pdfapp_invertselection(&gapp);
sel_has = 0;
}
sel_dragging = 0;
}
// map a client point to device coordinates inside the current page.
// Coordinates are clamped to the page; the return value tells whether
// the original point was actually within the page rectangle.
int ClientToPageDev(int mx, int my, int *dx, int *dy)
{
int wc = PageCenterX(gapp.image);
int px = mx - wc; // pixel x within the page
int py = my - TOOLBAR_HEIGHT + gapp.pany; // pixel y within the page
int inside = (px >= 0 && px < gapp.image->w && py >= 0 && py < gapp.image->h);
if (px < 0) px = 0; else if (px > gapp.image->w) px = gapp.image->w;
if (py < 0) py = 0; else if (py > gapp.image->h) py = gapp.image->h;
*dx = px + gapp.image->x;
*dy = py + gapp.image->y;
return inside;
}
// UTF-8 encode a single code point (<= 0xFFFF); returns bytes written
static int utf8_put(unsigned c, char *out)
{
if (c < 0x80) { out[0] = c; return 1; }
if (c < 0x800) { out[0] = 0xC0 | (c >> 6); out[1] = 0x80 | (c & 0x3F); return 2; }
out[0] = 0xE0 | (c >> 12);
out[1] = 0x80 | ((c >> 6) & 0x3F);
out[2] = 0x80 | (c & 0x3F);
return 3;
}
// extract the selected text and push it to the KolibriOS clipboard
// (fn 54: 12-byte header {size, type=text, encoding=UTF-8} + data)
void CopySelectionToClipboard(void)
{
static unsigned short ucs[4096];
static char clip[12 + 4096 * 3 + 1];
int i, n = 0;
int total;
pdfapp_oncopy(&gapp, ucs, 4096);
for (i = 0; ucs[i]; i++)
n += utf8_put(ucs[i], clip + 12 + n);
if (n == 0)
return;
total = 12 + n;
// 12-byte header, little-endian, written byte-wise to avoid aliasing
clip[0] = total; clip[1] = total >> 8; clip[2] = total >> 16; clip[3] = total >> 24;
clip[4] = clip[5] = clip[6] = clip[7] = 0; // type: text
clip[8] = clip[9] = clip[10] = clip[11] = 0; // encoding: 0 = UTF-8
kol_clip_set(total, clip);
}
// left button = select text, right button = grab & drag the page.
// Called on every mouse event unless the scrollbar owns the drag.
void ProcessPageMouse(void)
{
int mp = kol_mouse_posw();
int mx = (short)(mp >> 16);
int my = (short)(mp & 0xFFFF);
int btn = kol_mouse_btn();
int left = btn & 1, right = btn & 2;
int dx, dy;
/* --- right button: grab & drag (hand tool) --- */
if (right)
{
if (!pan_dragging)
{
if (my >= TOOLBAR_HEIGHT && mx >= 0 && mx < ViewW())
{
ClearSelection();
winblit(&gapp);
pan_dragging = 1;
pan_last_my = my;
}
}
else
{
int d = my - pan_last_my;
pan_last_my = my;
if (d)
{
gapp.pany -= d; // content follows the cursor
NormalizeScrollPos();
winblit(&gapp);
}
}
return;
}
pan_dragging = 0;
/* --- left button: text selection --- */
if (left && mx >= 0 && mx < ViewW() && my >= TOOLBAR_HEIGHT)
{
int inside = ClientToPageDev(mx, my, &dx, &dy);
if (!sel_dragging)
{
if (!inside) return; // start only on the page itself
ClearSelection();
winblit(&gapp);
sel_x0 = dx;
sel_y0 = dy;
sel_dragging = 1;
}
else
{
if (sel_has) pdfapp_invertselection(&gapp); // lift old highlight
gapp.selr.x0 = MIN(sel_x0, dx);
gapp.selr.x1 = MAX(sel_x0, dx);
gapp.selr.y0 = MIN(sel_y0, dy);
gapp.selr.y1 = MAX(sel_y0, dy);
pdfapp_invertselection(&gapp);
sel_has = 1;
winblit(&gapp);
}
}
else if (sel_dragging) // left released
{
sel_dragging = 0;
if (sel_has) CopySelectionToClipboard();
}
}
/* == continuous scroll: position management ====================== */
// switch the current page keeping the given scroll offset;
// reuses a cached pixmap when possible to avoid re-rendering
void GoToPage(int n, int new_pany)
{
fz_pixmap *cached = NULL;
int i;
// keep the outgoing page: scrolling back to it will be instant
StashPixmap(gapp.pageno, gapp.image);
for (i = 0; i < PAGE_CACHE_N; i++)
if (cache_pix[i] && cache_no[i] == n)
{
cached = cache_pix[i];
cache_pix[i] = NULL;
cache_no[i] = 0;
break;
}
gapp.pageno = n;
gapp.pany = new_pany;
if (cached)
{
if (gapp.image) fz_drop_pixmap(gapp.image);
gapp.image = cached;
pdfapp_showpage(&gapp, 1, 0, 0); // reload page_list/text/links only
}
else
{
pdfapp_showpage(&gapp, 1, 1, 0); // full render, pany is kept
}
}
// bring (pageno, pany) back into range, flipping pages when the
// viewport top has crossed a page boundary
void NormalizeScrollPos(void)
{
int maxs, rem;
// crossed the gap downwards: the next page becomes current
while (gapp.pageno < gapp.pagecount && gapp.pany >= gapp.image->h + PAGE_GAP)
GoToPage(gapp.pageno + 1, gapp.pany - gapp.image->h - PAGE_GAP);
// scrolled above the page top: the previous page becomes current
while (gapp.pany < 0)
{
if (gapp.pageno <= 1) { gapp.pany = 0; break; }
rem = gapp.pany;
GoToPage(gapp.pageno - 1, 0);
gapp.pany = rem + gapp.image->h + PAGE_GAP;
}
// do not scroll past the bottom of the last page
if (gapp.pageno == gapp.pagecount)
{
maxs = gapp.image->h - (Form.cheight - TOOLBAR_HEIGHT);
if (maxs < 0) maxs = 0;
if (gapp.pany > maxs) gapp.pany = maxs;
}
if (gapp.pany < 0) gapp.pany = 0;
}
@@ -252,8 +756,9 @@ void DrawMainWindow(void)
kol_btn_define(show_area_x-1, 5, show_area_w+1, 23, 20 + BT_HIDE, 0xA4A4A4);
kol_paint_bar(show_area_x, 5, show_area_w, 1, 0xA4A4A4);
kol_paint_bar(show_area_x, 28, show_area_w, 1, 0xA4A4A4);
ClearSelection();
NormalizeScrollPos();
winblit(&gapp);
DrawPageSides();
}
@@ -261,39 +766,28 @@ void DrawMainWindow(void)
void PageScrollDown(void)
{
//pdfapp_onkey(&gapp, 'k'); //move down
if (gapp.image->h - gapp.pany - SCROLL_H < Form.cheight - TOOLBAR_HEIGHT)
{
pdfapp_onkey(&gapp, '.');
}
else {
gapp.pany += SCROLL_H;
winblit(&gapp);
}
gapp.pany += SCROLL_STEP;
NormalizeScrollPos();
winblit(&gapp);
}
void PageScrollUp(void)
{
//pdfapp_onkey(&gapp, 'j'); //move up
if (gapp.pany >= SCROLL_H) {
gapp.pany -= SCROLL_H;
winblit(&gapp);
}
else {
//not very nice way of using do_not_blit, but it simple
if (gapp.pageno == 1) return;
do_not_blit = 1;
pdfapp_onkey(&gapp, ',');
do_not_blit = 0;
gapp.pany = gapp.image->h - SCROLL_H - Form.cheight + TOOLBAR_HEIGHT;
if (gapp.pany < 0) gapp.pany = 0;
//sprintf (debugstr, "gapp.pany: %d \n", gapp.pany);
//kol_board_puts(debugstr);
winblit(&gapp);
}
gapp.pany -= SCROLL_STEP;
NormalizeScrollPos();
winblit(&gapp);
}
void PageScroll(signed int delta)
{
ClearSelection();
gapp.pany += delta;
NormalizeScrollPos();
winblit(&gapp);
}
void RunApp(char app[], char param[])
{
kol_struct70 r;
@@ -309,27 +803,50 @@ void RunApp(char app[], char param[])
void PageZoomIn(void)
{
int oldh = gapp.image->h;
ClearSelection();
do_not_blit = 1;
pdfapp_onkey(&gapp, '+');
DrawPageSides();
do_not_blit = 0;
// keep the same relative position on the page
gapp.pany = (int)((long long)gapp.pany * gapp.image->h / oldh);
NormalizeScrollPos();
winblit(&gapp);
}
void PageZoomOut(void)
{
pdfapp_onkey(&gapp, '-');
DrawPageSides();
int oldh = gapp.image->h;
ClearSelection();
do_not_blit = 1;
pdfapp_onkey(&gapp, '-');
do_not_blit = 0;
gapp.pany = (int)((long long)gapp.pany * gapp.image->h / oldh);
NormalizeScrollPos();
winblit(&gapp);
}
void PageRotateLeft(void)
{
ClearSelection();
do_not_blit = 1;
pdfapp_onkey(&gapp, 'L');
DrawPageSides();
do_not_blit = 0;
gapp.pany = 0;
NormalizeScrollPos();
winblit(&gapp);
}
void PageRotateRight(void)
{
ClearSelection();
do_not_blit = 1;
pdfapp_onkey(&gapp, 'R');
DrawPageSides();
do_not_blit = 0;
gapp.pany = 0;
NormalizeScrollPos();
winblit(&gapp);
}
int main (int argc, char* argv[])
@@ -344,34 +861,26 @@ int main (int argc, char* argv[])
}
if (argc == 1) {
kol_board_puts("uPDF: no param set, showing OpenDialog...\n");
RunOpenApp(argv[0]);
exit(0);
}
kol_board_puts(full_argv);
kol_board_puts("\n");
char buf[128];
int resolution = 72;
int pageno = 1;
fz_accelerate();
kol_board_puts("PDF init...\n");
pdfapp_init(&gapp);
gapp.scrw = 600;
gapp.scrh = 400;
gapp.resolution = resolution;
gapp.pageno = pageno;
kol_board_puts("PDF Open...\n");
pdfapp_open(&gapp, full_argv, 0, 0);
kol_board_puts("PDF Opened!\n");
wintitle(&gapp, 0, full_argv);
kol_board_puts("Inital paint\n");
int butt, key, screen_max_x, screen_max_y;
int butt, key, scan, ascii, screen_max_x, screen_max_y;
kos_screen_max(&screen_max_x, &screen_max_y);
kol_event_mask(EVENT_REDRAW+EVENT_KEY+EVENT_BUTTON+EVENT_MOUSE_CHANGE);
// mouse events only for the active window, as WebView does (EVM_MOUSE_FILTER)
kol_event_mask(EVENT_REDRAW+EVENT_KEY+EVENT_BUTTON+EVENT_MOUSE_CHANGE+EVENT_MOUSE_WINDOW_MASK);
for(;;)
{
@@ -388,45 +897,61 @@ int main (int argc, char* argv[])
if (Form.window_state & 4) continue; // if Rolled-up
// Minimal size (700x600)
if (Form.width < 700) kol_wnd_change(-1, -1, 700, -1);
if (Form.height < 600) kol_wnd_change(-1, -1, -1, 600);
// Minimal size (640x480)
if (Form.width < 640) kol_wnd_change(-1, -1, 640, -1);
if (Form.height < 480) kol_wnd_change(-1, -1, -1, 480);
DrawMainWindow();
break;
case evKey:
key = kos_get_key();
key = kos_get_key_full();
if (key & 0xFF) break; // no key in buffer
scan = (key >> 16) & 0xFF; // layout-independent scancode
ascii = (key >> 8) & 0xFF; // ASCII, for digit entry only
// page-number entry still needs literal digits/enter/bs/esc
if (key_mode_enter_page_number)
{
HandleNewPageNumber(key);
HandleNewPageNumber(ascii);
break;
}
if (key==ASCII_KEY_ESC) DrawMainWindow(); //close help
if (key==ASCII_KEY_PGDN) pdfapp_onkey(&gapp, ']');
if (key==ASCII_KEY_PGUP) pdfapp_onkey(&gapp, '[');
if (key==ASCII_KEY_HOME) pdfapp_onkey(&gapp, 'g');
if (key==ASCII_KEY_END ) pdfapp_onkey(&gapp, 'G');
if (key=='g' ) pdfapp_onkey(&gapp, 'c');
if ((key=='[' ) || (key=='l')) PageRotateLeft();
if ((key==']' ) || (key=='r')) PageRotateRight();
if (key==ASCII_KEY_DOWN ) PageScrollDown();
if (key==ASCII_KEY_UP ) PageScrollUp();
if (key=='-') PageZoomOut();
if ((key=='=') || (key=='+')) PageZoomIn();
ClearSelection(); // any hotkey drops the current selection
switch (scan)
{
case SC_ESC: DrawMainWindow(); break; // close help
case SC_PGDN: PageScroll(Form.cheight - TOOLBAR_HEIGHT - SCROLL_STEP); break;
case SC_PGUP: PageScroll(-Form.cheight + TOOLBAR_HEIGHT + SCROLL_STEP); break;
case SC_HOME: pdfapp_onkey(&gapp, 'g'); break; // first page
case SC_END: pdfapp_onkey(&gapp, 'G'); break; // last page
case SC_G: pdfapp_onkey(&gapp, 'c'); break; // grayscale on/off
case SC_L:
case SC_LBRACKET: PageRotateLeft(); break;
case SC_R:
case SC_RBRACKET: PageRotateRight(); break;
case SC_DOWN: PageScroll(SCROLL_STEP); break;
case SC_UP: PageScroll(-SCROLL_STEP); break;
case SC_MINUS:
case SC_NUM_MINUS: PageZoomOut(); break;
case SC_EQUAL:
case SC_NUM_PLUS: PageZoomIn(); break;
}
break;
case evButton:
butt = kol_btn_get();
if(butt==1) exit(0);
if (butt!=13) ClearSelection(); // any toolbar action but help drops selection
if(butt==10) RunOpenApp(argv[0]);
if(butt==11) PageZoomOut(); //magnify -
if(butt==12) PageZoomIn(); //magnify +
if(butt==13) //show help
{
kol_paint_bar(0, TOOLBAR_HEIGHT, Form.cwidth, Form.cheight - TOOLBAR_HEIGHT, 0xF2F2F2);
kos_text(20, TOOLBAR_HEIGHT + 20 , 0x90000000, "uPDF for KolibriOS v1.2", 0);
kos_text(21, TOOLBAR_HEIGHT + 20 , 0x90000000, "uPDF for KolibriOS v1.2", 0);
kos_text(20, TOOLBAR_HEIGHT + 20, 0x90000000, "uPDF for KolibriOS v1.5", 0);
kos_text(21, TOOLBAR_HEIGHT + 20, 0x90000000, "uPDF for KolibriOS v1.5", 0);
for (ii=0; help[ii]!=0; ii++) {
kos_text(20, TOOLBAR_HEIGHT + 60 + ii * 15, 0x80000000, help[ii], 0);
}
@@ -441,12 +966,11 @@ int main (int argc, char* argv[])
case evMouse:
if (mouse_wheels_state = kos_get_mouse_wheels())
{
if (mouse_wheels_state==1) { PageScrollDown(); PageScrollDown(); }
if (mouse_wheels_state==-1) { PageScrollUp(); PageScrollUp(); }
if (mouse_wheels_state>0) PageScroll(SCROLL_STEP);
if (mouse_wheels_state<0) PageScroll(-SCROLL_STEP);
}
//sprintf (debugstr, "mouse_wheels_state: %d \n", mouse_wheels_state);
//kol_board_puts(debugstr);
//pdfapp_onmouse(&gapp, int x, int y, int btn, int modifiers, int state)
ScrollbarMouse();
if (!sb_drag) ProcessPageMouse(); // page area: select / drag
break;
}
}
+124 -53
View File
@@ -15,8 +15,7 @@ enum panning
PAN_TO_BOTTOM
};
void DrawPageSides(void);
static void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage, int repaint);
/* pdfapp_showpage and pdfapp_renderpage are declared in pdfapp.h */
static void pdfapp_warn(pdfapp_t *app, const char *fmt, ...)
{
@@ -111,12 +110,9 @@ static void pdfapp_open_pdf(pdfapp_t *app, char *filename, int fd)
/*
* Open PDF and load xref table
*/
kol_board_puts("FZ OPEN\n");
//file = fz_open_fd(fd);
kol_board_puts("FZ ready\n");
error = pdf_open_xref(&app->xref, filename, NULL);
if (error){
kol_board_puts("FZ can't open\n");
pdfapp_error(app, fz_rethrow(error, "cannot open document '%s'", filename));}
fz_close(file);
@@ -162,16 +158,12 @@ kol_board_puts("FZ OPEN\n");
/*
* Start at first page
*/
kol_board_puts("Start at first page\n");
error = pdf_load_page_tree(app->xref);
if (error) {
kol_board_puts("Can't load tree\n");
pdfapp_error(app, fz_rethrow(error, "cannot load page tree"));}
kol_board_puts("Page counter\n");
app->pagecount = pdf_count_pages(app->xref);
kol_board_puts("All is set!\n");
}
void pdfapp_open(pdfapp_t *app, char *filename, int fd, int reload)
@@ -242,39 +234,13 @@ static fz_matrix pdfapp_viewctm(pdfapp_t *app)
static void pdfapp_panview(pdfapp_t *app, int newx, int newy)
{
/* vertical position is managed by the continuous scroll code in kos_main.c,
here we only forbid negative offsets */
if (newx < 0)
newx = 0;
if (newy < 0)
newy = 0;
if (newx + app->image->w < app->winw)
newx = app->winw - app->image->w;
if (newy + app->image->h < app->winh)
newy = app->winh - app->image->h;
if (app->winw >= app->image->w)
newx = (app->winw - app->image->w) / 2;
if (app->winh >= app->image->h)
newy = (app->winh - app->image->h) / 2;
if (newx != app->panx || newy != app->pany)
winrepaint(app);
if (newy > app->image->h) {
app->pageno++;
if (app->pageno > app->pagecount)
app->pageno = app->pagecount;
newy = 0;
app->pany = newy;
pdfapp_showpage(app, 1, 1, 1);
}
app->panx = newx;
app->pany = newy;
}
@@ -310,7 +276,92 @@ static void pdfapp_loadpage_pdf(pdfapp_t *app)
pdf_age_store(app->xref->store, 3);
}
static void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage, int repaint)
/*
* The KolibriOS blitter (syscall 73) always treats the image as 32-bit
* BGRA, so we render in colour (n=4) and, when grayscale is requested,
* convert each pixel to its luminance in place. Rendering into an
* fz_device_gray pixmap instead gives a 2-channel (gray+alpha) buffer
* that the blitter misreads - green tint and a horizontally doubled page.
*/
static void pdfapp_desaturate(fz_pixmap *pix)
{
unsigned char *p = pix->samples;
int i, n, y;
if (pix->n != 4)
return;
n = pix->w * pix->h;
for (i = 0; i < n; i++, p += 4)
{
/* bgr order: p[0]=B, p[1]=G, p[2]=R, p[3]=A */
y = (p[2] * 77 + p[1] * 150 + p[0] * 29) >> 8;
p[0] = p[1] = p[2] = y;
}
}
/*
* Render any page into a fresh pixmap using the current view parameters
* (resolution, rotation, grayscale) without touching the state of the
* currently displayed page. Used by the continuous-scroll composer to
* draw neighbour pages. Returns NULL on failure.
*/
fz_pixmap *pdfapp_renderpage(pdfapp_t *app, int pageno)
{
pdf_page *page;
fz_error error;
fz_device *dev;
fz_display_list *list;
fz_matrix ctm;
fz_bbox bbox;
fz_colorspace *colorspace;
fz_pixmap *pix;
if (pageno < 1 || pageno > app->pagecount)
return NULL;
error = pdf_load_page(&page, app->xref, pageno - 1);
if (error)
return NULL;
list = fz_new_display_list();
dev = fz_new_list_device(list);
error = pdf_run_page(app->xref, page, dev, fz_identity);
fz_free_device(dev);
if (error)
{
fz_free_display_list(list);
pdf_free_page(page);
return NULL;
}
/* same transform as pdfapp_viewctm, but for this page's box and rotation */
ctm = fz_identity;
ctm = fz_concat(ctm, fz_translate(0, -page->mediabox.y1));
ctm = fz_concat(ctm, fz_scale(app->resolution/72.0f, -app->resolution/72.0f));
ctm = fz_concat(ctm, fz_rotate(app->rotate + page->rotate));
bbox = fz_round_rect(fz_transform_rect(ctm, page->mediabox));
/* always render 32-bit BGRA for the blitter, then desaturate if needed */
colorspace = fz_device_bgr;
pix = fz_new_pixmap_with_rect(colorspace, bbox);
fz_clear_pixmap_with_color(pix, 255);
dev = fz_new_draw_device(app->cache, pix);
fz_execute_display_list(list, dev, ctm, bbox);
fz_free_device(dev);
if (app->grayscale)
pdfapp_desaturate(pix);
fz_free_display_list(list);
pdf_free_page(page);
pdf_age_store(app->xref->store, 3);
return pix;
}
void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage, int repaint)
{
char buf[256];
fz_device *idev;
@@ -356,23 +407,17 @@ static void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage, int repai
/* Draw */
if (app->image)
fz_drop_pixmap(app->image);
if (app->grayscale)
colorspace = fz_device_gray;
else
/*
#ifdef _WIN32
colorspace = fz_device_bgr;
#else
colorspace = fz_device_rgb;
#endif
*/
colorspace = fz_device_bgr;
/* always render 32-bit BGRA for the blitter, then desaturate if needed */
colorspace = fz_device_bgr;
app->image = fz_new_pixmap_with_rect(colorspace, bbox);
fz_clear_pixmap_with_color(app->image, 255);
idev = fz_new_draw_device(app->cache, app->image);
fz_execute_display_list(app->page_list, idev, ctm, bbox);
fz_free_device(idev);
if (app->grayscale)
pdfapp_desaturate(app->image);
}
if (repaint)
@@ -381,7 +426,6 @@ static void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage, int repai
if (app->shrinkwrap)
{
kol_board_puts ("SHRINK\n");
int w = app->image->w;
int h = app->image->h;
if (app->winw == w)
@@ -402,8 +446,6 @@ static void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage, int repai
}
fz_flush_warnings();
DrawPageSides();
}
static void pdfapp_gotouri(pdfapp_t *app, fz_obj *uri)
@@ -484,6 +526,35 @@ void pdfapp_inverthit(pdfapp_t *app)
pdfapp_invert(app, fz_transform_bbox(ctm, hitbox));
}
/*
* Invert on screen every character whose box lies inside app->selr
* (device coordinates). Inversion is its own inverse, so calling this
* twice with the same selr restores the original pixels - the caller
* uses that to move/clear the highlight without re-rendering the page.
*/
void pdfapp_invertselection(pdfapp_t *app)
{
fz_bbox hitbox;
fz_matrix ctm;
fz_text_span *span;
int i;
int x0 = app->selr.x0, x1 = app->selr.x1;
int y0 = app->selr.y0, y1 = app->selr.y1;
if (!app->image || !app->page_text)
return;
ctm = pdfapp_viewctm(app);
for (span = app->page_text; span; span = span->next)
for (i = 0; i < span->len; i++)
{
hitbox = fz_transform_bbox(ctm, span->text[i].bbox);
if (hitbox.x1 >= x0 && hitbox.x0 <= x1 &&
hitbox.y1 >= y0 && hitbox.y0 <= y1)
pdfapp_invert(app, hitbox);
}
}
static inline int charat(fz_text_span *span, int idx)
{
int ofs = 0;
+4
View File
@@ -95,6 +95,9 @@ void pdfapp_close(pdfapp_t *app);
char *pdfapp_version(pdfapp_t *app);
char *pdfapp_usage(pdfapp_t *app);
void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage, int repaint);
fz_pixmap *pdfapp_renderpage(pdfapp_t *app, int pageno);
void pdfapp_onkey(pdfapp_t *app, int c);
void pdfapp_onmouse(pdfapp_t *app, int x, int y, int btn, int modifiers, int state);
void pdfapp_oncopy(pdfapp_t *app, unsigned short *ucsbuf, int ucslen);
@@ -102,3 +105,4 @@ void pdfapp_onresize(pdfapp_t *app, int w, int h);
void pdfapp_invert(pdfapp_t *app, fz_bbox rect);
void pdfapp_inverthit(pdfapp_t *app);
void pdfapp_invertselection(pdfapp_t *app);
+73 -21
View File
@@ -1,29 +1,81 @@
# BUILD ONLY LIBRARIES
#!/bin/bash
# Build uPDF for KolibriOS: third-party libs from the SDK, the MuPDF
# libraries (fitz, pdf, draw) and the application itself.
# Needs only the kos32-gcc toolchain, no make.
#
# Usage: ./build_libs.sh - build missing libraries, then the app
# ./build_libs.sh all - force-rebuild everything
mkdir lib
set -e
cd "$(dirname "$0")"
SDK=$(cd ../../sdk && pwd)
cd SYSCALL/src
make
cd ../..
# locate the kos32 toolchain
if ! command -v kos32-gcc >/dev/null 2>&1; then
for p in /home/autobuild/tools/win32/bin \
/c/MinGW/msys/1.0/home/autobuild/tools/win32/bin; do
if [ -x "$p/kos32-gcc" ] || [ -x "$p/kos32-gcc.exe" ]; then
export PATH="$p:$PATH"
break
fi
done
fi
command -v kos32-gcc >/dev/null 2>&1 || { echo "error: kos32-gcc not found"; exit 1; }
TOOLLIB="$(dirname "$(command -v kos32-gcc)")/../lib"
cd fitz
make
cd ..
CFLAGS="-c -fno-ident -O2 -fomit-frame-pointer -U__WIN32__ -U_Win32 -U_WIN32 -U__MINGW32__ -UWIN32"
NEWLIB_INC="-I $SDK/sources/newlib/libc/include"
cd pdf
make
cd ..
FORCE=0
[ "$1" = "all" ] && FORCE=1
cd libopenjpeg
make
cd ..
build_lib() { # <src dir> <lib name> <dest dir> <cflags...>
local dir="$1" lib="$2" dest="$3"
shift 3
if [ $FORCE = 0 ] && [ -f "$dest/$lib" ]; then
echo "skip $lib (already in $dest)"
return
fi
echo "building $lib..."
( cd "$dir"
rm -f *.o
for f in *.c; do
kos32-gcc $CFLAGS "$@" -o "${f%.c}.o" "$f"
done
kos32-ar rcs "$lib" *.o
rm -f *.o
mkdir -p "$dest"
mv -f "$lib" "$dest/" )
}
cd libjbig2dec
make
cd ..
build_lib "$SDK/sources/libjbig2dec" libjbig2dec.a "$SDK/lib" \
-DHAVE_CONFIG_H $NEWLIB_INC -I "$SDK/sources/freetype/include" \
-I "$SDK/sources/libpng" -I "$SDK/sources/zlib" -I .
cd draw
make
cd ..
build_lib "$SDK/sources/libopenjpeg" libopenjpeg.a "$SDK/lib" \
$NEWLIB_INC -I "$SDK/sources/freetype/include" -I "$SDK/sources/zlib" -I .
sleep 100
build_lib fitz libfitz.a "$PWD/lib" \
$NEWLIB_INC -I "$SDK/sources/freetype/include" -I "$SDK/sources/libjpeg" \
-I "$SDK/sources/zlib" -I "$SDK/sources/libopenjpeg" -I "$SDK/sources/libjbig2dec"
build_lib pdf libmupdf.a "$PWD/lib" \
$NEWLIB_INC -I "$PWD/fitz" -I "$SDK/sources/freetype/include"
build_lib draw libdraw.a "$PWD/lib" \
$NEWLIB_INC -I "$PWD/fitz"
echo "building updf..."
cd apps
rm -f *.o updf
for f in kolibri.c kos_main.c pdfapp.c; do
kos32-gcc $CFLAGS $NEWLIB_INC -I "$SDK/sources/freetype/include" \
-I "$SDK/sources/zlib" -I ../fitz -I ../pdf -o "${f%.c}.o" "$f"
done
kos32-ld -static -nostdlib -T "$SDK/sources/newlib/app.lds" --image-base 0 \
-L "$SDK/lib" -L "$TOOLLIB" -L ../lib --subsystem native \
-o updf kolibri.o pdfapp.o kos_main.o \
-lmupdf -lfitz -lgcc -lfitz -ldraw -ljpeg -ljbig2dec -lfreetype -lopenjpeg -lz.dll -lc.dll
kos32-objcopy updf -O binary
rm -f *.o
echo "done: apps/updf"
Binary file not shown.