From 6d239974429e3c0630f3471f54181ba77abdf50f Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Wed, 10 Oct 2012 13:09:25 +0100 Subject: [PATCH 01/70] Add simple scaling support. Conflicts: pic.c --- pic.c | 314 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 pic.c diff --git a/pic.c b/pic.c new file mode 100644 index 000000000..c5ffad667 --- /dev/null +++ b/pic.c @@ -0,0 +1,314 @@ +/* + KindlePDFViewer: Picture viewer abstraction for Lua + Copyright (C) 2012 Tigran Aivazian + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include + +#include "jpeglib.h" +#include "blitbuffer.h" +#include "drawcontext.h" +#include "pic.h" + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +typedef struct PicDocument { + int width; + int height; + int components; + uint8_t *image; +} PicDocument; + +typedef struct PicPage { + int width, height; + PicDocument *doc; +} PicPage; + +struct my_error_mgr { + struct jpeg_error_mgr pub; + jmp_buf setjmp_buffer; +}; + +typedef struct my_error_mgr *my_error_ptr; + +METHODDEF(void) my_error_exit(j_common_ptr cinfo) +{ + my_error_ptr myerr = (my_error_ptr) cinfo->err; + (*cinfo->err->output_message) (cinfo); + longjmp(myerr->setjmp_buffer, 1); +} + +uint8_t *readJPEG(const char *fname, int *width, int *height, int *components) +{ + struct jpeg_decompress_struct cinfo; + struct my_error_mgr jerr; + FILE *infile; + JSAMPARRAY buffer; + int row_stride; + long cont; + JSAMPLE *image_buffer; + + if ((infile = fopen(fname, "r")) == NULL) return NULL; + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = my_error_exit; + if (setjmp(jerr.setjmp_buffer)) { + jpeg_destroy_decompress(&cinfo); + fclose(infile); + return NULL; + } + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo, infile); + (void) jpeg_read_header(&cinfo, TRUE); + (void) jpeg_start_decompress(&cinfo); + row_stride = cinfo.output_width * cinfo.output_components; + buffer = (*cinfo.mem->alloc_sarray) + ((j_common_ptr) & cinfo, JPOOL_IMAGE, row_stride, 1); + + image_buffer = (JSAMPLE *) malloc(cinfo.image_width*cinfo.image_height*cinfo.output_components); + if (image_buffer == NULL) return NULL; + *width = cinfo.image_width; + *height = cinfo.image_height; + + //cont = cinfo.output_height - 1; + cont = 0; + while (cinfo.output_scanline < cinfo.output_height) { + (void) jpeg_read_scanlines(&cinfo, buffer, 1); + memcpy(image_buffer + cinfo.image_width * cinfo.output_components * cont, buffer[0], row_stride); + cont++; + } + + (void) jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + fclose(infile); + *components = cinfo.output_components; + return (uint8_t *)image_buffer; +} + +static int openDocument(lua_State *L) { + int width, height, components; + const char *filename = luaL_checkstring(L, 1); + + PicDocument *doc = (PicDocument*) lua_newuserdata(L, sizeof(PicDocument)); + luaL_getmetatable(L, "picdocument"); + lua_setmetatable(L, -2); + + unsigned char *image = readJPEG(filename, &width, &height, &components); + if (!image) + return luaL_error(L, "cannot open jpeg file"); + + doc->image = image; + doc->width = width; + doc->height = height; + doc->components = components; + //printf("openDocument(%s) decoded image: %dx%dx%d\n", filename, width, height, components); + return 1; +} + +static int openPage(lua_State *L) { + PicDocument *doc = (PicDocument*) luaL_checkudata(L, 1, "picdocument"); + int pageno = luaL_checkint(L, 2); + + PicPage *page = (PicPage*) lua_newuserdata(L, sizeof(PicPage)); + luaL_getmetatable(L, "picpage"); + lua_setmetatable(L, -2); + page->width = doc->width; + page->height = doc->height; + page->doc = doc; + + return 1; +} + +static int getNumberOfPages(lua_State *L) { + lua_pushinteger(L, 1); + return 1; +} + +static int getOriginalPageSize(lua_State *L) { + PicDocument *doc = (PicDocument*) luaL_checkudata(L, 1, "picdocument"); + lua_pushnumber(L, doc->width); + lua_pushnumber(L, doc->height); + return 2; +} + +static int closeDocument(lua_State *L) { + PicDocument *doc = (PicDocument*) luaL_checkudata(L, 1, "picdocument"); + if (doc->image != NULL) + free(doc->image); + return 0; +} + +static void scaleImage(uint8_t *result, uint8_t *image, int width, int height, int components, int new_width, int new_height) +{ + int x, y; + + for (x = 0; xoffset_x); + int y_offset = MAX(0, dc->offset_y); + int x, y; + int img_width = page->doc->width; + int img_height = page->doc->height; + int img_components = page->doc->components; + int img_new_width = bb->w; + int img_new_height = bb->h; + unsigned char adjusted_low[16], adjusted_high[16]; + int i, adjust_pixels = 0; + + /* prepare the tables for adjusting the intensity of pixels */ + if (dc->gamma != -1.0) { + for (i=0; i<16; i++) { + adjusted_low[i] = MIN(15, (unsigned char)floorf(dc->gamma * (float)i)); + adjusted_high[i] = adjusted_low[i] << 4; + } + adjust_pixels = 1; + } + + uint8_t *scaled_image = malloc(img_new_width*img_new_height+1); + if (!scaled_image) + return 0; + + scaleImage(scaled_image, page->doc->image, img_width, img_height, img_components, img_new_width, img_new_height); + + uint8_t *bbptr = bb->data; + uint8_t *pmptr = scaled_image; + bbptr += bb->pitch * y_offset; + for(y = y_offset; y < img_new_height; y++) { + for(x = x_offset/2; x < (img_new_width / 2); x++) { + int p = x*2 - x_offset; + unsigned char low, high; + if (img_components == 1) { + low = 15 - (pmptr[p + 1] >> 4); + high = 15 - (pmptr[p] >> 4); + } else if (img_components == 3) { + low = 15 - (pmptr[p + 3] >> 4); + high = 15 - (pmptr[p] >> 4); + } else { + fprintf(stderr, "pic.c:drawPage(): unsupported image format\n"); + return 0; + } + if (adjust_pixels) + bbptr[x] = adjusted_high[high] | adjusted_low[low]; + else + bbptr[x] = (high << 4) | low; + } + if (img_new_width & 1) + bbptr[x] = 255 - (pmptr[x*2] & 0xF0); + bbptr += bb->pitch; + pmptr += img_new_width; + } + + free(scaled_image); + return 0; +} + +static int getCacheSize(lua_State *L) { + PicDocument *doc = (PicDocument*) luaL_checkudata(L, 1, "picdocument"); + lua_pushnumber(L, doc->width * doc->height * doc->components); + return 1; +} + +static int cleanCache(lua_State *L) { + return 0; +} + +static int getPageSize(lua_State *L) { + PicPage *page = (PicPage*) luaL_checkudata(L, 1, "picpage"); + DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); + + lua_pushnumber(L, dc->zoom * page->width); + lua_pushnumber(L, dc->zoom * page->height); + + return 2; +} + + +static int closePage(lua_State *L) { + PicPage *page = (PicPage*) luaL_checkudata(L, 1, "picpage"); + return 0; +} + +/* unsupported so fake it */ +static int getUsedBBox(lua_State *L) { + lua_pushnumber(L, (double)0.01); + lua_pushnumber(L, (double)0.01); + lua_pushnumber(L, (double)-0.01); + lua_pushnumber(L, (double)-0.01); + return 4; +} + +static int getTableOfContent(lua_State *L) { + lua_newtable(L); + return 1; +} + +static const struct luaL_Reg pic_func[] = { + {"openDocument", openDocument}, + {NULL, NULL} +}; + +static const struct luaL_Reg picdocument_meth[] = { + {"openPage", openPage}, + {"getPages", getNumberOfPages}, + {"getToc", getTableOfContent}, + {"getOriginalPageSize", getOriginalPageSize}, + {"getCacheSize", getCacheSize}, + {"close", closeDocument}, + {"cleanCache", cleanCache}, + {"__gc", closeDocument}, + {NULL, NULL} +}; + + +static const struct luaL_Reg picpage_meth[] = { + {"getSize", getPageSize}, + {"getUsedBBox", getUsedBBox}, + {"close", closePage}, + {"__gc", closePage}, + {"draw", drawPage}, + {NULL, NULL} +}; + + +int luaopen_pic(lua_State *L) { + luaL_newmetatable(L, "picdocument"); + lua_pushstring(L, "__index"); + lua_pushvalue(L, -2); + lua_settable(L, -3); + luaL_register(L, NULL, picdocument_meth); + lua_pop(L, 1); + + luaL_newmetatable(L, "picpage"); + lua_pushstring(L, "__index"); + lua_pushvalue(L, -2); + lua_settable(L, -3); + luaL_register(L, NULL, picpage_meth); + lua_pop(L, 1); + + luaL_register(L, "pic", pic_func); + return 1; +} From b56f4bf9584ccadba8442b8827b93ae132996bbc Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Wed, 10 Oct 2012 13:15:16 +0100 Subject: [PATCH 02/70] Support only grayscale images for now. --- pic.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/pic.c b/pic.c index c5ffad667..447840154 100644 --- a/pic.c +++ b/pic.c @@ -114,6 +114,9 @@ static int openDocument(lua_State *L) { if (!image) return luaL_error(L, "cannot open jpeg file"); + if (components != 1) + return luaL_error(L, "Unsupported image format"); + doc->image = image; doc->width = width; doc->height = height; @@ -155,7 +158,7 @@ static int closeDocument(lua_State *L) { return 0; } -static void scaleImage(uint8_t *result, uint8_t *image, int width, int height, int components, int new_width, int new_height) +static void scaleImage(uint8_t *result, uint8_t *image, int width, int height, int new_width, int new_height) { int x, y; @@ -173,7 +176,6 @@ static int drawPage(lua_State *L) { int x, y; int img_width = page->doc->width; int img_height = page->doc->height; - int img_components = page->doc->components; int img_new_width = bb->w; int img_new_height = bb->h; unsigned char adjusted_low[16], adjusted_high[16]; @@ -192,7 +194,7 @@ static int drawPage(lua_State *L) { if (!scaled_image) return 0; - scaleImage(scaled_image, page->doc->image, img_width, img_height, img_components, img_new_width, img_new_height); + scaleImage(scaled_image, page->doc->image, img_width, img_height, img_new_width, img_new_height); uint8_t *bbptr = bb->data; uint8_t *pmptr = scaled_image; @@ -200,17 +202,8 @@ static int drawPage(lua_State *L) { for(y = y_offset; y < img_new_height; y++) { for(x = x_offset/2; x < (img_new_width / 2); x++) { int p = x*2 - x_offset; - unsigned char low, high; - if (img_components == 1) { - low = 15 - (pmptr[p + 1] >> 4); - high = 15 - (pmptr[p] >> 4); - } else if (img_components == 3) { - low = 15 - (pmptr[p + 3] >> 4); - high = 15 - (pmptr[p] >> 4); - } else { - fprintf(stderr, "pic.c:drawPage(): unsupported image format\n"); - return 0; - } + unsigned char low = 15 - (pmptr[p + 1] >> 4); + unsigned char high = 15 - (pmptr[p] >> 4); if (adjust_pixels) bbptr[x] = adjusted_high[high] | adjusted_low[low]; else From cf38d02e80539d734a55adbb67b93011417c204d Mon Sep 17 00:00:00 2001 From: HW Date: Wed, 10 Oct 2012 16:59:07 +0200 Subject: [PATCH 03/70] added framework to check the involved pointers in blitbuffer operations blitbuffer operations in 4bpp can be a nasty experience, easy to get the pointers wrong. so a macro can check them now. Conflicts: blitbuffer.c --- blitbuffer.c | 143 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 109 insertions(+), 34 deletions(-) diff --git a/blitbuffer.c b/blitbuffer.c index 0e4d30b69..670c5f4be 100644 --- a/blitbuffer.c +++ b/blitbuffer.c @@ -20,14 +20,19 @@ #include #include "blitbuffer.h" - -inline int setPixel(lua_State *L, BlitBuffer *bb, int x, int y, int c) { - uint8_t *dstptr = (uint8_t*)(bb->data) + (y * bb->pitch) + (x / 2); -#ifndef NO_CHECK_BOUNDS - if(x < 0 || x >= bb->w || y < 0 || y >= bb->h) { - return luaL_error(L, "out of bounds in blitbuffer.setPixel()"); +/* debugging statements, switch as needed */ +#ifdef DEBUG +#define ASSERT_BLITBUFFER_BOUNDARIES(bb,bb_ptr) \ + if((bb_ptr < bb->data) || (bb_ptr >= (bb->data + bb->pitch * bb->h))) { \ + fprintf(stderr, "violated blitbuffer constraints in file %s, line %d!\r\n", __FILE__, __LINE__); exit(1); \ } -#endif +#else // DEBUG +#define ASSERT_BLITBUFFER_BOUNDARIES(bb,bb_ptr) {} +#endif // DEBUG + +inline int setPixel(BlitBuffer *bb, int x, int y, int c) { + uint8_t *dstptr = (uint8_t*)(bb->data) + (y * bb->pitch) + (x / 2); + ASSERT_BLITBUFFER_BOUNDARIES(bb, dstptr); if(x % 2 == 0) { *dstptr &= 0x0F; @@ -173,6 +178,8 @@ static int blitToBuffer(lua_State *L) { xoffs / 2 ); if(xoffs & 1) { for(y = 0; y < h; y++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); + ASSERT_BLITBUFFER_BOUNDARIES(src, srcptr); *dstptr &= 0xF0; *dstptr |= *srcptr & 0x0F; dstptr += dst->pitch; @@ -180,6 +187,8 @@ static int blitToBuffer(lua_State *L) { } } else { for(y = 0; y < h; y++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); + ASSERT_BLITBUFFER_BOUNDARIES(src, srcptr); *dstptr &= 0xF0; *dstptr |= *srcptr >> 4; dstptr += dst->pitch; @@ -200,6 +209,8 @@ static int blitToBuffer(lua_State *L) { if(xoffs & 1) { for(y = 0; y < h; y++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); + ASSERT_BLITBUFFER_BOUNDARIES(src, srcptr); for(x = 0; x < (w / 2); x++) { dstptr[x] = (srcptr[x] << 4) | (srcptr[x+1] >> 4); } @@ -212,6 +223,8 @@ static int blitToBuffer(lua_State *L) { } } else { for(y = 0; y < h; y++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); + ASSERT_BLITBUFFER_BOUNDARIES(src, srcptr); memcpy(dstptr, srcptr, w / 2); if(w & 1) { dstptr[w/2] &= 0x0F; @@ -270,6 +283,8 @@ static int addblitToBuffer(lua_State *L) { xoffs / 2 ); if(xoffs & 1) { for(y = 0; y < h; y++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); + ASSERT_BLITBUFFER_BOUNDARIES(src, srcptr); uint8_t v = (*dstptr & 0x0F) + (*srcptr & 0x0F); *dstptr = (*dstptr & 0xF0) | (v < 0x0F ? v : 0x0F); dstptr += dst->pitch; @@ -277,6 +292,8 @@ static int addblitToBuffer(lua_State *L) { } } else { for(y = 0; y < h; y++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); + ASSERT_BLITBUFFER_BOUNDARIES(src, srcptr); uint8_t v = (*dstptr & 0x0F) + (*srcptr >> 4); *dstptr = (*dstptr & 0xF0) | (v < 0x0F ? v : 0x0F); dstptr += dst->pitch; @@ -298,11 +315,15 @@ static int addblitToBuffer(lua_State *L) { if(xoffs & 1) { for(y = 0; y < h; y++) { for(x = 0; x < (w / 2); x++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); + ASSERT_BLITBUFFER_BOUNDARIES(src, srcptr); uint16_t v1 = (dstptr[x] & 0xF0) + ((srcptr[x] & 0x0F) << 4); uint8_t v2 = (dstptr[x] & 0x0F) + (srcptr[x+1] >> 4); dstptr[x] = (v1 < 0xF0 ? v1 : 0xF0) | (v2 < 0x0F ? v2 : 0x0F); } if(w & 1) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); + ASSERT_BLITBUFFER_BOUNDARIES(src, srcptr); uint16_t v1 = (dstptr[x] & 0xF0) + ((srcptr[x] & 0x0F) << 4); dstptr[x] = (dstptr[x] & 0x0F) | (v1 < 0xF0 ? v1 : 0xF0); } @@ -312,11 +333,15 @@ static int addblitToBuffer(lua_State *L) { } else { for(y = 0; y < h; y++) { for(x = 0; x < (w / 2); x++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); + ASSERT_BLITBUFFER_BOUNDARIES(src, srcptr); uint16_t v1 = (dstptr[x] & 0xF0) + (srcptr[x] & 0xF0); uint8_t v2 = (dstptr[x] & 0x0F) + (srcptr[x] & 0x0F); dstptr[x] = (v1 < 0xF0 ? v1 : 0xF0) | (v2 < 0x0F ? v2 : 0x0F); } if(w & 1) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); + ASSERT_BLITBUFFER_BOUNDARIES(src, srcptr); uint16_t v1 = (dstptr[x] & 0xF0) + (srcptr[x] & 0xF0); dstptr[x] = (dstptr[x] & 0x0F) | (v1 < 0xF0 ? v1 : 0xF0); } @@ -337,9 +362,25 @@ static int paintRect(lua_State *L) { uint8_t *dstptr; int cy; - if(w <= 0 || h <= 0 || x >= dst->w || y >= dst->h) { - return 0; + + if(x < 0) { + if (x+w > 0) { + w += x; + x = 0; + } else { + return 0; + } } + + if(y < 0) { + if (y+h > 0) { + h += y; + y = 0; + } else { + return 0; + } + } + if(x + w > dst->w) { w = dst->w - x; } @@ -347,6 +388,10 @@ static int paintRect(lua_State *L) { h = dst->h - y; } + if(w <= 0 || h <= 0 || x >= dst->w || y >= dst->h) { + return 0; + } + if(x & 1) { /* This will render the leftmost column * in the case when x is odd. After this, @@ -355,6 +400,7 @@ static int paintRect(lua_State *L) { y * dst->pitch + x / 2); for(cy = 0; cy < h; cy++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); *dstptr &= 0xF0; *dstptr |= c; dstptr += dst->pitch; @@ -366,6 +412,7 @@ static int paintRect(lua_State *L) { y * dst->pitch + x / 2); for(cy = 0; cy < h; cy++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); memset(dstptr, c | (c << 4), w / 2); dstptr += dst->pitch; } @@ -377,6 +424,7 @@ static int paintRect(lua_State *L) { y * dst->pitch + (x + w) / 2); for(cy = 0; cy < h; cy++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); *dstptr &= 0x0F; *dstptr |= (c << 4); dstptr += dst->pitch; @@ -435,6 +483,7 @@ static int invertRect(lua_State *L) { y * dst->pitch + x / 2); for(cy = 0; cy < h; cy++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); *dstptr ^= 0x0F; dstptr += dst->pitch; } @@ -446,6 +495,7 @@ static int invertRect(lua_State *L) { x / 2); for(cy = 0; cy < h; cy++) { for(cx = 0; cx < w/2; cx++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, (dstptr+cx)); *(dstptr+cx) ^= 0xFF; } dstptr += dst->pitch; @@ -458,6 +508,7 @@ static int invertRect(lua_State *L) { y * dst->pitch + (x + w) / 2); for(cy = 0; cy < h; cy++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); *dstptr ^= 0xF0; dstptr += dst->pitch; } @@ -474,6 +525,27 @@ static int dimRect(lua_State *L) { uint8_t *dstptr; int cy, cx; + + if (x < 0) { + if ( x + w > 0 ) { + w = w + x; + x = 0; + } else { + //printf("## invertRect x out of bound\n"); + return 0; + } + } + + if (y < 0) { + if ( y + h > 0 ) { + h = h + y; + y = 0; + } else { + //printf("## invertRect y out of bound\n"); + return 0; + } + } + if(w <= 0 || h <= 0 || x >= dst->w || y >= dst->h) { return 0; } @@ -492,6 +564,7 @@ static int dimRect(lua_State *L) { y * dst->pitch + x / 2); for(cy = 0; cy < h; cy++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); int px = *dstptr & 0x0F; *dstptr &= 0xF0 | px >> 1; dstptr += dst->pitch; @@ -504,6 +577,7 @@ static int dimRect(lua_State *L) { x / 2); for(cy = 0; cy < h; cy++) { for(cx = 0; cx < w/2; cx++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, (dstptr+cx)); *(dstptr+cx) = ( *(dstptr+cx) >> 1 ) & 0xF0 | ( *(dstptr+cx) & 0x0F ) >> 1; @@ -518,6 +592,7 @@ static int dimRect(lua_State *L) { y * dst->pitch + (x + w) / 2); for(cy = 0; cy < h; cy++) { + ASSERT_BLITBUFFER_BOUNDARIES(dst, dstptr); int px = *dstptr & 0xF0; *dstptr &= 0x0F | ( px >> 1 & 0xF0 ); dstptr += dst->pitch; @@ -560,10 +635,10 @@ static int paintCircle(lua_State *L) { /* draw two axles */ for(tmp_y = r; tmp_y > r2; tmp_y--) { - setPixel(L, dst, center_x+0, center_y+tmp_y, c); - setPixel(L, dst, center_x-0, center_y-tmp_y, c); - setPixel(L, dst, center_x+tmp_y, center_y+0, c); - setPixel(L, dst, center_x-tmp_y, center_y-0, c); + setPixel(dst, center_x+0, center_y+tmp_y, c); + setPixel(dst, center_x-0, center_y-tmp_y, c); + setPixel(dst, center_x+tmp_y, center_y+0, c); + setPixel(dst, center_x-tmp_y, center_y-0, c); } while(x < y) { @@ -591,21 +666,21 @@ static int paintCircle(lua_State *L) { } for(tmp_y = y; tmp_y > y2; tmp_y--) { - setPixel(L, dst, center_x+x, center_y+tmp_y, c); - setPixel(L, dst, center_x+tmp_y, center_y+x, c); + setPixel(dst, center_x+x, center_y+tmp_y, c); + setPixel(dst, center_x+tmp_y, center_y+x, c); - setPixel(L, dst, center_x+tmp_y, center_y-x, c); - setPixel(L, dst, center_x+x, center_y-tmp_y, c); + setPixel(dst, center_x+tmp_y, center_y-x, c); + setPixel(dst, center_x+x, center_y-tmp_y, c); - setPixel(L, dst, center_x-x, center_y-tmp_y, c); - setPixel(L, dst, center_x-tmp_y, center_y-x, c); + setPixel(dst, center_x-x, center_y-tmp_y, c); + setPixel(dst, center_x-tmp_y, center_y-x, c); - setPixel(L, dst, center_x-tmp_y, center_y+x, c); - setPixel(L, dst, center_x-x, center_y+tmp_y, c); + setPixel(dst, center_x-tmp_y, center_y+x, c); + setPixel(dst, center_x-x, center_y+tmp_y, c); } } if(r == w) { - setPixel(L, dst, center_x, center_y, c); + setPixel(dst, center_x, center_y, c); } return 0; } @@ -645,10 +720,10 @@ static int paintRoundedCorner(lua_State *L) { /* draw two axles */ /*for(tmp_y = r; tmp_y > r2; tmp_y--) {*/ - /*setPixel(L, dst, (w-r)+off_x+0, (h-r)+off_y+tmp_y-1, c);*/ - /*setPixel(L, dst, (w-r)+off_x-0, (r)+off_y-tmp_y, c);*/ - /*setPixel(L, dst, (w-r)+off_x+tmp_y, (h-r)+off_y+0, c);*/ - /*setPixel(L, dst, (r)+off_x-tmp_y, (h-r)+off_y-0-1, c);*/ + /*setPixel(dst, (w-r)+off_x+0, (h-r)+off_y+tmp_y-1, c);*/ + /*setPixel(dst, (w-r)+off_x-0, (r)+off_y-tmp_y, c);*/ + /*setPixel(dst, (w-r)+off_x+tmp_y, (h-r)+off_y+0, c);*/ + /*setPixel(dst, (r)+off_x-tmp_y, (h-r)+off_y-0-1, c);*/ /*}*/ while(x < y) { @@ -676,17 +751,17 @@ static int paintRoundedCorner(lua_State *L) { } for(tmp_y = y; tmp_y > y2; tmp_y--) { - setPixel(L, dst, (w-r)+off_x+x-1, (h-r)+off_y+tmp_y-1, c); - setPixel(L, dst, (w-r)+off_x+tmp_y-1, (h-r)+off_y+x-1, c); + setPixel(dst, (w-r)+off_x+x-1, (h-r)+off_y+tmp_y-1, c); + setPixel(dst, (w-r)+off_x+tmp_y-1, (h-r)+off_y+x-1, c); - setPixel(L, dst, (w-r)+off_x+tmp_y-1, (r)+off_y-x, c); - setPixel(L, dst, (w-r)+off_x+x-1, (r)+off_y-tmp_y, c); + setPixel(dst, (w-r)+off_x+tmp_y-1, (r)+off_y-x, c); + setPixel(dst, (w-r)+off_x+x-1, (r)+off_y-tmp_y, c); - setPixel(L, dst, (r)+off_x-x, (r)+off_y-tmp_y, c); - setPixel(L, dst, (r)+off_x-tmp_y, (r)+off_y-x, c); + setPixel(dst, (r)+off_x-x, (r)+off_y-tmp_y, c); + setPixel(dst, (r)+off_x-tmp_y, (r)+off_y-x, c); - setPixel(L, dst, (r)+off_x-tmp_y, (h-r)+off_y+x-1, c); - setPixel(L, dst, (r)+off_x-x, (h-r)+off_y+tmp_y-1, c); + setPixel(dst, (r)+off_x-tmp_y, (h-r)+off_y+x-1, c); + setPixel(dst, (r)+off_x-x, (h-r)+off_y+tmp_y-1, c); } } return 0; From 4b2a9237c9cc9e7262eec9d10e183da056db3f28 Mon Sep 17 00:00:00 2001 From: HW Date: Wed, 10 Oct 2012 17:58:31 +0200 Subject: [PATCH 04/70] factored out blitting constraints check in a seperate function the checking of the boundaries when blitting was incomplete for the addblitToBuffer() function when compared with the blitToBuffer() function. Factored out the redundant checking procedure into a new function. Should fix another bug leading to issue #401 and probably more cases for specifying boundary boxes. --- blitbuffer.c | 134 ++++++++++++++++++++++++--------------------------- 1 file changed, 63 insertions(+), 71 deletions(-) diff --git a/blitbuffer.c b/blitbuffer.c index 670c5f4be..df0a4f65d 100644 --- a/blitbuffer.c +++ b/blitbuffer.c @@ -105,6 +105,63 @@ static int blitFullToBuffer(lua_State *L) { return 0; } +/** +* check/adapt boundaries for blitting operations +* +* @return 0 if no blitting is needed, 1 otherwise +*/ +int fitBlitBufferBoundaries(BlitBuffer* src, BlitBuffer* dst, int* xdest, int* ydest, int* xoffs, int* yoffs, int* w, int* h) { + // check bounds + if(*ydest < 0) { + // negative ydest, try to compensate + if(*ydest + *h > 0) { + // shrink h by negative dest offset + *h += *ydest; + // extend source offset + *yoffs += -(*ydest); + *ydest = 0; + } else { + // effectively no height + return 0; + } + } else if(*ydest >= dst->h) { + // we're told to paint to off-bound target coords + return 0; + } + if(*ydest + *h > dst->h) { + // clamp height if too large for target size + *h = dst->h - *ydest; + } + if(*yoffs >= src->h) { + // recalculated source offset is out of bounds + return 0; + } else if(*yoffs + *h > src->h) { + // clamp height if too large for source size + *h = src->h - *yoffs; + } + // same stuff for x coords: + if(*xdest < 0) { + if(*xdest + *w > 0) { + *w += *xdest; + *xoffs += -(*xdest); + *xdest = 0; + } else { + return 0; + } + } else if(*xdest >= dst->w) { + return 0; + } + if(*xdest + *w > dst->w) { + *w = dst->w - *xdest; + } + if(*xoffs >= src->w) { + return 0; + } else if(*xoffs + *w > src->w) { + *w = src->w - *xoffs; + } + return 1; // continue processing +} + static int blitToBuffer(lua_State *L) { BlitBuffer *dst = (BlitBuffer*) luaL_checkudata(L, 1, "blitbuffer"); BlitBuffer *src = (BlitBuffer*) luaL_checkudata(L, 2, "blitbuffer"); @@ -116,58 +173,12 @@ static int blitToBuffer(lua_State *L) { int h = luaL_checkint(L, 8); int x, y; - // check bounds - if(ydest < 0) { - // negative ydest, try to compensate - if(ydest + h > 0) { - // shrink h by negative dest offset - h += ydest; - // extend source offset - yoffs += -ydest; - ydest = 0; - } else { - // effectively no height - return 0; - } - } else if(ydest >= dst->h) { - // we're told to paint to off-bound target coords - return 0; - } - if(ydest + h > dst->h) { - // clamp height if too large for target size - h = dst->h - ydest; - } - if(yoffs >= src->h) { - // recalculated source offset is out of bounds - return 0; - } else if(yoffs + h > src->h) { - // clamp height if too large for source size - h = src->h - yoffs; - } - // same stuff for x coords: - if(xdest < 0) { - if(xdest + w > 0) { - w += xdest; - xoffs += -xdest; - xdest = 0; - } else { - return 0; - } - } else if(xdest >= dst->w) { - return 0; - } - if(xdest + w > dst->w) { - w = dst->w - xdest; - } - if(xoffs >= src->w) { - return 0; - } else if(xoffs + w > src->w) { - w = src->w - xoffs; - } - uint8_t *dstptr; uint8_t *srcptr; + if(!fitBlitBufferBoundaries(src, dst, &xdest, &ydest, &xoffs, &yoffs, &w, &h)) + return 0; + if(xdest & 1) { /* this will render the leftmost column */ dstptr = (uint8_t*)(dst->data + @@ -248,31 +259,12 @@ static int addblitToBuffer(lua_State *L) { int h = luaL_checkint(L, 8); int x, y; - // check bounds - if(yoffs >= src->h) { - return 0; - } else if(yoffs + h > src->h) { - h = src->h - yoffs; - } - if(ydest >= dst->h) { - return 0; - } else if(ydest + h > dst->h) { - h = dst->h - ydest; - } - if(xoffs >= src->w) { - return 0; - } else if(xoffs + w > src->w) { - w = src->w - xoffs; - } - if(xdest >= dst->w) { - return 0; - } else if(xdest + w > dst->w) { - w = dst->w - xdest; - } - uint8_t *dstptr; uint8_t *srcptr; + if(!fitBlitBufferBoundaries(src, dst, &xdest, &ydest, &xoffs, &yoffs, &w, &h)) + return 0; + if(xdest & 1) { /* this will render the leftmost column */ dstptr = (uint8_t*)(dst->data + From b053667d9e425d75bddf7a862cd32c01ac056d95 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Wed, 10 Oct 2012 19:49:50 +0100 Subject: [PATCH 05/70] Add git-rev file. Conflicts: .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 9ff0fb666..115c9b5c2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,11 @@ lua-* .reader.kpdfview.lua mupdf-thirdparty.zip djvulibre* +<<<<<<< HEAD +======= +crash.log +.vimrc +git-rev kpdfview slider_watcher *.o From 879c937762f128b9672527a84c7ab42530d23c09 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Wed, 10 Oct 2012 21:04:47 +0100 Subject: [PATCH 06/70] Force the use of bash. On some distributions (e.g. Ubuntu) the shell used for scripts is dash, not bash and we explicitly rely on bash's features in the Makefile, so we must guarantee that we are running with bash (not dash or anything else). --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 3789f8461..a996ea0c8 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ TTF_FONTS_DIR=$(MUPDFDIR)/fonts # set this to your ARM cross compiler: +SHELL:=/bin/bash CHOST?=arm-none-linux-gnueabi CC:=$(CHOST)-gcc CXX:=$(CHOST)-g++ From f6c73c33d33088aa2c71e6e2c5825b4862eb269a Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Thu, 11 Oct 2012 11:49:38 +0100 Subject: [PATCH 07/70] Commented out debug-only function readable_fs(). --- pdf.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pdf.c b/pdf.c index dd0de744b..fdb64b674 100644 --- a/pdf.c +++ b/pdf.c @@ -58,6 +58,7 @@ static size_t msize_min; static size_t msize_iniz; static int is_realloc=0; +#if 0 char* readable_fs(double size/*in bytes*/, char *buf) { int i = 0; const char* units[] = {"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}; @@ -68,6 +69,7 @@ char* readable_fs(double size/*in bytes*/, char *buf) { sprintf(buf, "%.*f %s", i, size, units[i]); return buf; } +#endif static void resetMsize(){ msize_iniz = msize; From 60edf54e2fefe206b5a1c6afc1328f984f1890a1 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Thu, 11 Oct 2012 19:25:24 +0200 Subject: [PATCH 08/70] getPageLinks implementation for crengine formats Conflicts: cre.cpp crereader.lua unireader.lua --- cre.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/cre.cpp b/cre.cpp index 6277233f4..d7af01dbc 100644 --- a/cre.cpp +++ b/cre.cpp @@ -157,7 +157,7 @@ static int getCurrentPercent(lua_State *L) { return 1; } -static int getCurrentXPointer(lua_State *L) { +static int getXPointer(lua_State *L) { CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument"); ldomXPointer xp = doc->text_view->getBookmark(); @@ -410,7 +410,73 @@ static int cursorRight(lua_State *L) { return 0; } -static int drawCurrentView(lua_State *L) { +static int getPageLinks(lua_State *L) { + CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument"); + + lua_newtable(L); // all links + + int pos = doc->text_view->GetPos(); + + printf("## pos=%d\n", pos); + + ldomXRangeList links; + ldomXRangeList & sel = doc->text_view->getDocument()->getSelections(); + doc->text_view->getCurrentPageLinks( links ); + int linkCount = links.length(); + if ( linkCount ) { + sel.clear(); + for ( int i=0; igetRangeText(); + lString8 txt8 = UnicodeToLocal( txt ); + + lString16 link = links[i]->getHRef(); + lString8 link8 = UnicodeToLocal( link ); + + ldomXRange currSel; + currSel = *links[i]; + + lvPoint start_pt ( currSel.getStart().toPoint() ); + lvPoint end_pt ( currSel.getEnd().toPoint() ); + + printf("# link %d start %d %d end %d %d '%s' %s\n", i, + start_pt.x, start_pt.y, end_pt.x, end_pt.y, + txt8.c_str(), link8.c_str() + ); + + lua_newtable(L); // new link + + lua_pushstring(L, "start_x"); + lua_pushinteger(L, start_pt.x); + lua_settable(L, -3); + lua_pushstring(L, "start_y"); + lua_pushinteger(L, start_pt.y); + lua_settable(L, -3); + lua_pushstring(L, "end_x"); + lua_pushinteger(L, end_pt.x); + lua_settable(L, -3); + lua_pushstring(L, "end_y"); + lua_pushinteger(L, end_pt.y); + lua_settable(L, -3); + + const char * link_to = link8.c_str(); + + lua_pushstring(L, "section"); + lua_pushstring(L, link_to); + lua_settable(L, -3); + + lua_rawseti(L, -2, i + 1); + + } + doc->text_view->updateSelections(); + } + + return 1; +} + + +static int drawCurrentPage(lua_State *L) { CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument"); BlitBuffer *bb = (BlitBuffer*) luaL_checkudata(L, 2, "blitbuffer"); @@ -555,8 +621,9 @@ static const struct luaL_Reg credocument_meth[] = { {"toggleFontBolder", toggleFontBolder}, //{"cursorLeft", cursorLeft}, //{"cursorRight", cursorRight}, - {"drawCurrentView", drawCurrentView}, + {"drawCurrentPage", drawCurrentPage}, {"findText", findText}, + {"getPageLinks", getPageLinks}, {"close", closeDocument}, {"__gc", closeDocument}, {NULL, NULL} From bc4d2e7a49ec9d057f17c565d303ae15eac50ccc Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Tue, 9 Oct 2012 23:51:53 +0200 Subject: [PATCH 09/70] added gotoLink Conflicts: unireader.lua --- cre.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cre.cpp b/cre.cpp index d7af01dbc..ea36e83e7 100644 --- a/cre.cpp +++ b/cre.cpp @@ -475,6 +475,15 @@ static int getPageLinks(lua_State *L) { return 1; } +static int gotoLink(lua_State *L) { + CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument"); + const char *pos = luaL_checkstring(L, 2); + + doc->text_view->goLink(lString16(pos), true); + + return 0; +} + static int drawCurrentPage(lua_State *L) { CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument"); @@ -624,6 +633,7 @@ static const struct luaL_Reg credocument_meth[] = { {"drawCurrentPage", drawCurrentPage}, {"findText", findText}, {"getPageLinks", getPageLinks}, + {"gotoLink", gotoLink}, {"close", closeDocument}, {"__gc", closeDocument}, {NULL, NULL} From f2933a32c0e8224a0f59544c8e243736e90b9d7f Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Wed, 10 Oct 2012 00:18:11 +0200 Subject: [PATCH 10/70] added clearSelection Conflicts: unireader.lua --- cre.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cre.cpp b/cre.cpp index ea36e83e7..7497fb806 100644 --- a/cre.cpp +++ b/cre.cpp @@ -484,6 +484,13 @@ static int gotoLink(lua_State *L) { return 0; } +static int clearSelection(lua_State *L) { + CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument"); + + doc->text_view->clearSelection(); + + return 0; +} static int drawCurrentPage(lua_State *L) { CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument"); @@ -634,6 +641,7 @@ static const struct luaL_Reg credocument_meth[] = { {"findText", findText}, {"getPageLinks", getPageLinks}, {"gotoLink", gotoLink}, + {"clearSelection", clearSelection}, {"close", closeDocument}, {"__gc", closeDocument}, {NULL, NULL} From 2103461a432a12eadb89f753c79eec5a042f0997 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Wed, 10 Oct 2012 00:48:48 +0200 Subject: [PATCH 11/70] return section separate from url and fix highlight --- cre.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/cre.cpp b/cre.cpp index 7497fb806..93e0f113d 100644 --- a/cre.cpp +++ b/cre.cpp @@ -426,8 +426,6 @@ static int getPageLinks(lua_State *L) { if ( linkCount ) { sel.clear(); for ( int i=0; igetRangeText(); lString8 txt8 = UnicodeToLocal( txt ); @@ -462,9 +460,17 @@ static int getPageLinks(lua_State *L) { const char * link_to = link8.c_str(); - lua_pushstring(L, "section"); - lua_pushstring(L, link_to); - lua_settable(L, -3); + if ( link_to[0] == '#' ) { + lua_pushstring(L, "section"); + lua_pushstring(L, link_to); + lua_settable(L, -3); + + sel.add( new ldomXRange(*links[i]) ); // highlight + } else { + lua_pushstring(L, "uri"); + lua_pushstring(L, link_to); + lua_settable(L, -3); + } lua_rawseti(L, -2, i + 1); From cac63cd3908d27c9e2d30293fb6e2bf357c36bae Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Thu, 11 Oct 2012 22:01:26 +0100 Subject: [PATCH 12/70] Add support for colour JPEG images. I used the luminance match algorithm for converting RGB colour images to grayscale as it gives the best visual results (but at the cost of speed). --- pic.c | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/pic.c b/pic.c index 447840154..d11dd4d9b 100644 --- a/pic.c +++ b/pic.c @@ -102,6 +102,25 @@ uint8_t *readJPEG(const char *fname, int *width, int *height, int *components) return (uint8_t *)image_buffer; } +/* Uses luminance match for approximating the human perception of colour, + * as per http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale + * L = 0.299*Red + 0.587*Green + 0.114*Blue */ +static uint8_t *rgbToGrayscale(uint8_t *image, int width, int height) +{ + int x, y; + uint8_t *buf = malloc(width*height+1); + + if (!buf) return NULL; + + for (x = 0; ximage = raw_image; + else if (components == 3) { + uint8_t *gray_image = rgbToGrayscale(raw_image, width, height); + if (!gray_image) { + free(raw_image); + return luaL_error(L, "Cannot convert to grayscale"); + } else { + free(raw_image); + doc->image = gray_image; + } + } else return luaL_error(L, "Unsupported image format"); - doc->image = image; doc->width = width; doc->height = height; doc->components = components; - //printf("openDocument(%s) decoded image: %dx%dx%d\n", filename, width, height, components); return 1; } @@ -132,6 +160,7 @@ static int openPage(lua_State *L) { PicPage *page = (PicPage*) lua_newuserdata(L, sizeof(PicPage)); luaL_getmetatable(L, "picpage"); lua_setmetatable(L, -2); + page->width = doc->width; page->height = doc->height; page->doc = doc; @@ -158,6 +187,7 @@ static int closeDocument(lua_State *L) { return 0; } +/* uses very simple nearest neighbour scaling */ static void scaleImage(uint8_t *result, uint8_t *image, int width, int height, int new_width, int new_height) { int x, y; From 7609fc2dd0b296a147a28abb06cabbcc4f36b9a2 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Thu, 11 Oct 2012 22:10:02 +0100 Subject: [PATCH 13/70] Slight tidy-up of the code. --- pic.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pic.c b/pic.c index d11dd4d9b..7e8875d12 100644 --- a/pic.c +++ b/pic.c @@ -137,13 +137,11 @@ static int openDocument(lua_State *L) { doc->image = raw_image; else if (components == 3) { uint8_t *gray_image = rgbToGrayscale(raw_image, width, height); - if (!gray_image) { - free(raw_image); + free(raw_image); + if (!gray_image) return luaL_error(L, "Cannot convert to grayscale"); - } else { - free(raw_image); + else doc->image = gray_image; - } } else return luaL_error(L, "Unsupported image format"); From ee59fac32887941f404ae69d1e904579d42282f1 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Thu, 11 Oct 2012 22:19:33 +0100 Subject: [PATCH 14/70] Make closeDocument() re-entrant. --- pic.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pic.c b/pic.c index 7e8875d12..51b65bbcc 100644 --- a/pic.c +++ b/pic.c @@ -178,10 +178,13 @@ static int getOriginalPageSize(lua_State *L) { return 2; } +/* re-entrant */ static int closeDocument(lua_State *L) { PicDocument *doc = (PicDocument*) luaL_checkudata(L, 1, "picdocument"); - if (doc->image != NULL) + if (doc->image != NULL) { free(doc->image); + doc->image = NULL; + } return 0; } From 74a60eeea2da2c87c4f2f504d0bdc8373e30eddc Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Thu, 11 Oct 2012 22:22:45 +0100 Subject: [PATCH 15/70] Show the number of colour components via Menu. Conflicts: picviewer.lua --- pic.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pic.c b/pic.c index 51b65bbcc..bad4bfe2d 100644 --- a/pic.c +++ b/pic.c @@ -175,7 +175,8 @@ static int getOriginalPageSize(lua_State *L) { PicDocument *doc = (PicDocument*) luaL_checkudata(L, 1, "picdocument"); lua_pushnumber(L, doc->width); lua_pushnumber(L, doc->height); - return 2; + lua_pushnumber(L, doc->components); + return 3; } /* re-entrant */ From 01ce094adb49ed129305bb3fd72c3d35f5f53fc9 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Thu, 11 Oct 2012 22:34:18 +0100 Subject: [PATCH 16/70] Free raw_image if it is unsupported. If the number of colour components is neither 1 nor 3 we should remember to free the buffer holding the raw decoded image before returning the error to Lua. --- pic.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pic.c b/pic.c index bad4bfe2d..c0ac8a1fa 100644 --- a/pic.c +++ b/pic.c @@ -142,8 +142,10 @@ static int openDocument(lua_State *L) { return luaL_error(L, "Cannot convert to grayscale"); else doc->image = gray_image; - } else + } else { + free(raw_image); return luaL_error(L, "Unsupported image format"); + } doc->width = width; doc->height = height; From 8c6fd357895ad6e08f0cca2c83139aa36c819ae7 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Thu, 11 Oct 2012 22:54:04 +0100 Subject: [PATCH 17/70] Initialize doc->image in openDocument This is needed to protect against double free() in closeDocument(). --- pic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/pic.c b/pic.c index c0ac8a1fa..0934c76fd 100644 --- a/pic.c +++ b/pic.c @@ -133,6 +133,7 @@ static int openDocument(lua_State *L) { if (!raw_image) return luaL_error(L, "Cannot open jpeg file"); + doc->image = NULL; if (components == 1) doc->image = raw_image; else if (components == 3) { From 6c9a3c3dc5cbc72f329d55e5c7e2a33a29ba8c57 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Fri, 12 Oct 2012 09:36:08 +0100 Subject: [PATCH 18/70] Factor out JPEG support into a module This is to make adding support for new formats easier. See the file pic_jpeg.h for the description of the interface that each image format must support. Conflicts: Makefile --- Makefile | 11 ++++++- pic.c | 65 ++---------------------------------------- pic_jpeg.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ pic_jpeg.h | 30 +++++++++++++++++++ 4 files changed, 126 insertions(+), 64 deletions(-) create mode 100644 pic_jpeg.c create mode 100644 pic_jpeg.h diff --git a/Makefile b/Makefile index a996ea0c8..1eecadaa2 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ KPVCRLIBDIR=kpvcrlib CRENGINEDIR=$(KPVCRLIBDIR)/crengine FREETYPEDIR=$(MUPDFDIR)/thirdparty/freetype-2.4.10 +JPEGDIR=$(MUPDFDIR)/thirdparty/jpeg-9 LFSDIR=luafilesystem POPENNSDIR=popen-noshell @@ -111,7 +112,7 @@ POPENNSLIB := $(POPENNSDIR)/libpopen_noshell.a all: kpdfview VERSION?=$(shell git describe HEAD) -kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o $(POPENNSLIB) util.o ft.o lfs.o mupdfimg.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) djvu.o $(DJVULIBS) cre.o $(CRENGINELIBS) +kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o $(POPENNSLIB) util.o ft.o lfs.o mupdfimg.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) djvu.o $(DJVULIBS) cre.o $(CRENGINELIBS) pic.o pic_jpeg.o echo $(VERSION) > git-rev $(CC) \ $(CFLAGS) \ @@ -131,6 +132,8 @@ kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o $(POPENNS $(LUALIB) \ djvu.o \ $(DJVULIBS) \ + pic.o \ + pic_jpeg.o \ cre.o \ $(CRENGINELIBS) \ $(STATICLIBSTDCPP) \ @@ -155,6 +158,12 @@ kpdfview.o pdf.o blitbuffer.o util.o drawcontext.o einkfb.o input.o mupdfimg.o: djvu.o: %.o: %.c $(CC) -c $(KPDFREADER_CFLAGS) -I$(DJVUDIR)/ $< -o $@ +pic.o: %.o: %.c + $(CC) -c $(KPDFREADER_CFLAGS) $< -o $@ + +pic_jpeg.o: %.o: %.c + $(CC) -c $(KPDFREADER_CFLAGS) -I$(JPEGDIR)/ -I$(MUPDFDIR)/scripts/ $< -o $@ + cre.o: %.o: %.cpp $(CC) -c $(CFLAGS) -I$(CRENGINEDIR)/crengine/include/ -I$(LUADIR)/src $< -o $@ diff --git a/pic.c b/pic.c index 0934c76fd..9e3d1d2c4 100644 --- a/pic.c +++ b/pic.c @@ -19,13 +19,12 @@ #include #include #include -#include #include -#include "jpeglib.h" #include "blitbuffer.h" #include "drawcontext.h" #include "pic.h" +#include "pic_jpeg.h" #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) @@ -42,66 +41,6 @@ typedef struct PicPage { PicDocument *doc; } PicPage; -struct my_error_mgr { - struct jpeg_error_mgr pub; - jmp_buf setjmp_buffer; -}; - -typedef struct my_error_mgr *my_error_ptr; - -METHODDEF(void) my_error_exit(j_common_ptr cinfo) -{ - my_error_ptr myerr = (my_error_ptr) cinfo->err; - (*cinfo->err->output_message) (cinfo); - longjmp(myerr->setjmp_buffer, 1); -} - -uint8_t *readJPEG(const char *fname, int *width, int *height, int *components) -{ - struct jpeg_decompress_struct cinfo; - struct my_error_mgr jerr; - FILE *infile; - JSAMPARRAY buffer; - int row_stride; - long cont; - JSAMPLE *image_buffer; - - if ((infile = fopen(fname, "r")) == NULL) return NULL; - cinfo.err = jpeg_std_error(&jerr.pub); - jerr.pub.error_exit = my_error_exit; - if (setjmp(jerr.setjmp_buffer)) { - jpeg_destroy_decompress(&cinfo); - fclose(infile); - return NULL; - } - jpeg_create_decompress(&cinfo); - jpeg_stdio_src(&cinfo, infile); - (void) jpeg_read_header(&cinfo, TRUE); - (void) jpeg_start_decompress(&cinfo); - row_stride = cinfo.output_width * cinfo.output_components; - buffer = (*cinfo.mem->alloc_sarray) - ((j_common_ptr) & cinfo, JPOOL_IMAGE, row_stride, 1); - - image_buffer = (JSAMPLE *) malloc(cinfo.image_width*cinfo.image_height*cinfo.output_components); - if (image_buffer == NULL) return NULL; - *width = cinfo.image_width; - *height = cinfo.image_height; - - //cont = cinfo.output_height - 1; - cont = 0; - while (cinfo.output_scanline < cinfo.output_height) { - (void) jpeg_read_scanlines(&cinfo, buffer, 1); - memcpy(image_buffer + cinfo.image_width * cinfo.output_components * cont, buffer[0], row_stride); - cont++; - } - - (void) jpeg_finish_decompress(&cinfo); - jpeg_destroy_decompress(&cinfo); - fclose(infile); - *components = cinfo.output_components; - return (uint8_t *)image_buffer; -} - /* Uses luminance match for approximating the human perception of colour, * as per http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale * L = 0.299*Red + 0.587*Green + 0.114*Blue */ @@ -129,7 +68,7 @@ static int openDocument(lua_State *L) { luaL_getmetatable(L, "picdocument"); lua_setmetatable(L, -2); - uint8_t *raw_image = readJPEG(filename, &width, &height, &components); + uint8_t *raw_image = jpegLoadFile(filename, &width, &height, &components); if (!raw_image) return luaL_error(L, "Cannot open jpeg file"); diff --git a/pic_jpeg.c b/pic_jpeg.c new file mode 100644 index 000000000..18e5eab38 --- /dev/null +++ b/pic_jpeg.c @@ -0,0 +1,84 @@ +/* + KindlePDFViewer: JPEG support for Picture Viewer module + Copyright (C) 2012 Tigran Aivazian + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include + +#include "jpeglib.h" + +struct my_error_mgr { + struct jpeg_error_mgr pub; + jmp_buf setjmp_buffer; +}; + +typedef struct my_error_mgr *my_error_ptr; + +METHODDEF(void) my_error_exit(j_common_ptr cinfo) +{ + my_error_ptr myerr = (my_error_ptr) cinfo->err; + (*cinfo->err->output_message) (cinfo); + longjmp(myerr->setjmp_buffer, 1); +} + +uint8_t *jpegLoadFile(const char *fname, int *width, int *height, int *components) +{ + struct jpeg_decompress_struct cinfo; + struct my_error_mgr jerr; + FILE *infile; + JSAMPARRAY buffer; + int row_stride; + long cont; + JSAMPLE *image_buffer; + + if ((infile = fopen(fname, "r")) == NULL) return NULL; + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = my_error_exit; + if (setjmp(jerr.setjmp_buffer)) { + jpeg_destroy_decompress(&cinfo); + fclose(infile); + return NULL; + } + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo, infile); + (void) jpeg_read_header(&cinfo, TRUE); + (void) jpeg_start_decompress(&cinfo); + row_stride = cinfo.output_width * cinfo.output_components; + buffer = (*cinfo.mem->alloc_sarray) + ((j_common_ptr) & cinfo, JPOOL_IMAGE, row_stride, 1); + + image_buffer = (JSAMPLE *) malloc(cinfo.image_width*cinfo.image_height*cinfo.output_components); + if (image_buffer == NULL) return NULL; + *width = cinfo.image_width; + *height = cinfo.image_height; + + //cont = cinfo.output_height - 1; + cont = 0; + while (cinfo.output_scanline < cinfo.output_height) { + (void) jpeg_read_scanlines(&cinfo, buffer, 1); + memcpy(image_buffer + cinfo.image_width * cinfo.output_components * cont, buffer[0], row_stride); + cont++; + } + + (void) jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + fclose(infile); + *components = cinfo.output_components; + return (uint8_t *)image_buffer; +} diff --git a/pic_jpeg.h b/pic_jpeg.h new file mode 100644 index 000000000..22be93d45 --- /dev/null +++ b/pic_jpeg.h @@ -0,0 +1,30 @@ +/* + KindlePDFViewer: Interface to JPEG module for picture viewer + Copyright (C) 2012 Tigran Aivazian + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef _PIC_JPEG_H +#define _PIC_JPEG_H + +/* each new image format must provide fmtLoadFile() function which + * performs the following: + * 1. Opens the file 'filename' + * 2. Reads the image data from it into a buffer allocated with malloc() + * 3. Fills in the image *width, *height and *components (number of bytes per pixel) + * 4. Closes the file + * 5. Returns the pointer to the image data + */ +extern uint8_t *jpegLoadFile(const char *fname, int *width, int *height, int *components); +#endif From ebc029dbbd7d30d5744d706673b7421fa7e385d4 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Fri, 12 Oct 2012 09:44:19 +0100 Subject: [PATCH 19/70] Recognize the files with ".jpeg" extension Conflicts: extentions.lua --- resources/jpeg.png | Bin 0 -> 1117 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/jpeg.png diff --git a/resources/jpeg.png b/resources/jpeg.png new file mode 100644 index 0000000000000000000000000000000000000000..f038383226f839382308b40df1bf3d5866fdc1d1 GIT binary patch literal 1117 zcmaJ;SxggA6ul~_REQfwaLfFN3$4zSN?=?)>@;2k`B}9w9@v~>C_e%LMn#My>D-Q)KFhMJCiGB^-DB) zS&CYo{S=KfWTYLdIr5;SxHhQN9~U3% zHEQ$NC#)q^nY7K9q(@fE*qqTLS7x!*%SX39ZCrz%IZDs8w^;7#_j`N7cloPLmTS9i z9^JX8?)&A7`L;K0(MEr5Q&Dg5eAA0iC~`kN)M?K>*V7d_Uc9n|)TwqG17Cl3Z`m;6 z^Xtof{-1p%iOOjMKZ-tn>z?wYIyq<<7+>+DxiPIxZrUhQwWNglzjQUUHgCwk6*Qda zSQ6;z?rtBGe9(~I(Oh(AdqhdBtIyY?$R%HX<{o3)sqBm47kRfU)io^-Rk?EcLf1sF zh3yBc?wqW^!^uVMMv+A>7v~mXxx>j3e!(G%oSP^uC)8F{$`M985|P)AJoSWXKI#9q z`4HC4l5|!^Z{NDmGTDKVs zQez!h+J*!dBZ9-l@Z5r}9*%!*>Aa8ei4rd5G>e=_@Ol^)yO8_VpT!{Y2lO&7kJI7i z6*h(ykpRkoQceXZ>F9gwZpOKu_>i;)PqQpGlNEa;a}*S%AeBe`@)?*rU3&K~SI03{ z{5S`z5RZrzMr~p1g41=Fmo}WYUf?-5TMlmI<~l$0lwrnXi6K~RwAv|xjF7(AJ?Z Date: Fri, 12 Oct 2012 10:55:46 +0100 Subject: [PATCH 20/70] Comment out [currently] unused variables. --- pic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pic.c b/pic.c index 9e3d1d2c4..eb4340ceb 100644 --- a/pic.c +++ b/pic.c @@ -95,7 +95,7 @@ static int openDocument(lua_State *L) { static int openPage(lua_State *L) { PicDocument *doc = (PicDocument*) luaL_checkudata(L, 1, "picdocument"); - int pageno = luaL_checkint(L, 2); + //int pageno = luaL_checkint(L, 2); PicPage *page = (PicPage*) lua_newuserdata(L, sizeof(PicPage)); luaL_getmetatable(L, "picpage"); @@ -215,7 +215,7 @@ static int getPageSize(lua_State *L) { static int closePage(lua_State *L) { - PicPage *page = (PicPage*) luaL_checkudata(L, 1, "picpage"); + //PicPage *page = (PicPage*) luaL_checkudata(L, 1, "picpage"); return 0; } From 9c4a7ab860ac47282cd057d4d04d0d06e07aaa81 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Sat, 13 Oct 2012 09:39:34 +0100 Subject: [PATCH 21/70] Cleanup of pic.c 1. Report the true size of the document cache (0). 2. Cut down the number of pointer dereferences in drawPage(). --- pic.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pic.c b/pic.c index eb4340ceb..4035fdd3f 100644 --- a/pic.c +++ b/pic.c @@ -37,7 +37,9 @@ typedef struct PicDocument { } PicDocument; typedef struct PicPage { - int width, height; + int width; + int height; + uint8_t *image; PicDocument *doc; } PicPage; @@ -103,6 +105,7 @@ static int openPage(lua_State *L) { page->width = doc->width; page->height = doc->height; + page->image = doc->image; page->doc = doc; return 1; @@ -148,8 +151,8 @@ static int drawPage(lua_State *L) { int x_offset = MAX(0, dc->offset_x); int y_offset = MAX(0, dc->offset_y); int x, y; - int img_width = page->doc->width; - int img_height = page->doc->height; + int img_width = page->width; + int img_height = page->height; int img_new_width = bb->w; int img_new_height = bb->h; unsigned char adjusted_low[16], adjusted_high[16]; @@ -168,7 +171,7 @@ static int drawPage(lua_State *L) { if (!scaled_image) return 0; - scaleImage(scaled_image, page->doc->image, img_width, img_height, img_new_width, img_new_height); + scaleImage(scaled_image, page->image, img_width, img_height, img_new_width, img_new_height); uint8_t *bbptr = bb->data; uint8_t *pmptr = scaled_image; @@ -195,7 +198,7 @@ static int drawPage(lua_State *L) { static int getCacheSize(lua_State *L) { PicDocument *doc = (PicDocument*) luaL_checkudata(L, 1, "picdocument"); - lua_pushnumber(L, doc->width * doc->height * doc->components); + lua_pushnumber(L, 0); return 1; } From 24d4b82f2d4a52c0875faed8ced15a341ab052a1 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 13 Oct 2012 13:47:15 +0200 Subject: [PATCH 22/70] getSelections can return empty ldomXRangeList --- cre.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cre.cpp b/cre.cpp index 93e0f113d..485d54f3b 100644 --- a/cre.cpp +++ b/cre.cpp @@ -421,6 +421,11 @@ static int getPageLinks(lua_State *L) { ldomXRangeList links; ldomXRangeList & sel = doc->text_view->getDocument()->getSelections(); + + if ( sel.length() == 0 ) { + return 0; + } + doc->text_view->getCurrentPageLinks( links ); int linkCount = links.length(); if ( linkCount ) { From 08d26596b803d4658c513b203d8464eb5ebd8e91 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 13 Oct 2012 19:28:56 +0200 Subject: [PATCH 23/70] use CRLog::debug instead of printf, removed unused code --- cre.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cre.cpp b/cre.cpp index 485d54f3b..ec0a74a6b 100644 --- a/cre.cpp +++ b/cre.cpp @@ -415,10 +415,6 @@ static int getPageLinks(lua_State *L) { lua_newtable(L); // all links - int pos = doc->text_view->GetPos(); - - printf("## pos=%d\n", pos); - ldomXRangeList links; ldomXRangeList & sel = doc->text_view->getDocument()->getSelections(); @@ -443,7 +439,7 @@ static int getPageLinks(lua_State *L) { lvPoint start_pt ( currSel.getStart().toPoint() ); lvPoint end_pt ( currSel.getEnd().toPoint() ); - printf("# link %d start %d %d end %d %d '%s' %s\n", i, + CRLog::debug("# link %d start %d %d end %d %d '%s' %s\n", i, start_pt.x, start_pt.y, end_pt.x, end_pt.y, txt8.c_str(), link8.c_str() ); From 484d85ab0f970d2818d500533e1f1aedeb828890 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 13 Oct 2012 19:33:20 +0200 Subject: [PATCH 24/70] getSelections can be 0 This depends on visible selections and not on content of file. So if we have search results on screen this will be more than 0, but if we don't we won't get any link shortcuts --- cre.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cre.cpp b/cre.cpp index ec0a74a6b..fd9c6213d 100644 --- a/cre.cpp +++ b/cre.cpp @@ -418,10 +418,6 @@ static int getPageLinks(lua_State *L) { ldomXRangeList links; ldomXRangeList & sel = doc->text_view->getDocument()->getSelections(); - if ( sel.length() == 0 ) { - return 0; - } - doc->text_view->getCurrentPageLinks( links ); int linkCount = links.length(); if ( linkCount ) { From 95f6ed5bd6263d8ed6a2ecdea9c5c6bae0e0ac71 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 13 Oct 2012 20:49:29 +0200 Subject: [PATCH 25/70] fix getCurrentPageLinks segfault on links without target --- Makefile | 3 ++- kpvcrlib/lvdocview-getCurrentPageLinks.patch | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 kpvcrlib/lvdocview-getCurrentPageLinks.patch diff --git a/Makefile b/Makefile index 1eecadaa2..eed248c57 100644 --- a/Makefile +++ b/Makefile @@ -185,7 +185,8 @@ fetchthirdparty: # CREngine patch: change child nodes' type face # @TODO replace this dirty hack 24.04 2012 (houqp) cd kpvcrlib/crengine/crengine/src && \ - patch -N -p0 < ../../../lvrend_node_type_face.patch || true + patch -N -p0 < ../../../lvrend_node_type_face.patch && \ + patch -N -p3 < ../../../lvdocview-getCurrentPageLinks.patch || true unzip mupdf-thirdparty.zip -d mupdf # check mupdf's thirdparty libs' version, if not matched, remove the old one # run make fetchthirdparty again to get the latest thirdparty libs. diff --git a/kpvcrlib/lvdocview-getCurrentPageLinks.patch b/kpvcrlib/lvdocview-getCurrentPageLinks.patch new file mode 100644 index 000000000..168b2e1f9 --- /dev/null +++ b/kpvcrlib/lvdocview-getCurrentPageLinks.patch @@ -0,0 +1,14 @@ +diff --git a/crengine/src/lvdocview.cpp b/crengine/src/lvdocview.cpp +index e7a355a..e1178de 100755 +--- a/crengine/src/lvdocview.cpp ++++ b/crengine/src/lvdocview.cpp +@@ -4539,7 +4539,8 @@ void LVDocView::getCurrentPageLinks(ldomXRangeList & list) { + if (_list[i]->getStart().getNode() == elem) + return true; // don't add, duplicate found! + } +- _list.add(new ldomXRange(elem->getChildNode(0))); ++ ldomNode * node = elem->getChildNode(0); ++ if ( node ) _list.add(new ldomXRange(node)); + } + return true; + } From 3c193651455a0fc4361122839d8d6b1b896230b6 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 13 Oct 2012 20:52:46 +0200 Subject: [PATCH 26/70] use DEBUG_CRENGINE from enviroment if available This allows to specify DEBUG_CRENGINE=1 while compiling without editing source code --- cre.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cre.cpp b/cre.cpp index fd9c6213d..0bf1ceb0c 100644 --- a/cre.cpp +++ b/cre.cpp @@ -17,7 +17,9 @@ along with this program. If not, see . */ +#ifndef DEBUG_CRENGINE #define DEBUG_CRENGINE 0 +#endif extern "C" { #include "blitbuffer.h" From 89c5aad77fddd50759cfe17ef76a0e50d31c37c5 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Mon, 15 Oct 2012 12:05:40 +0100 Subject: [PATCH 27/70] Mention SDL/SDL-devel requirement on Fedora --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e23f0b3a0..b89609f35 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,8 @@ for this. It allows to develop on a standard PC and saves precious development time. It might also compose the most unfriendly desktop PDF reader, depending on your view. -If you are using Ubuntu, simply install `libsdl-dev1.2` package. +If you are using Fedora Core Linux, do `yum install SDL SDL-devel`. +If you are using Ubuntu, install `libsdl-dev1.2` package. To build in "emulation mode", you need to run make like this: make clean cleanthirdparty From f3f97b6223ef71428d21ae5bc07af2aed694554e Mon Sep 17 00:00:00 2001 From: chrox Date: Tue, 16 Oct 2012 10:02:38 +0800 Subject: [PATCH 28/70] add pdf page reflow Conflicts: pdfreader.lua --- Makefile | 6 +- k2pdfopt.c | 6338 ++++++++++++++++++++++++++++++++++++++++++++++++++++ k2pdfopt.h | 33 + pdf.c | 93 + 4 files changed, 6469 insertions(+), 1 deletion(-) create mode 100644 k2pdfopt.c create mode 100644 k2pdfopt.h diff --git a/Makefile b/Makefile index eed248c57..9f2c372fd 100644 --- a/Makefile +++ b/Makefile @@ -112,13 +112,14 @@ POPENNSLIB := $(POPENNSDIR)/libpopen_noshell.a all: kpdfview VERSION?=$(shell git describe HEAD) -kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o $(POPENNSLIB) util.o ft.o lfs.o mupdfimg.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) djvu.o $(DJVULIBS) cre.o $(CRENGINELIBS) pic.o pic_jpeg.o +kpdfview: kpdfview.o einkfb.o pdf.o k2pdfopt.o blitbuffer.o drawcontext.o input.o $(POPENNSLIB) util.o ft.o lfs.o mupdfimg.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) djvu.o $(DJVULIBS) cre.o $(CRENGINELIBS) pic.o pic_jpeg.o echo $(VERSION) > git-rev $(CC) \ $(CFLAGS) \ kpdfview.o \ einkfb.o \ pdf.o \ + k2pdfopt.o \ blitbuffer.o \ drawcontext.o \ input.o \ @@ -155,6 +156,9 @@ ft.o: %.o: %.c $(THIRDPARTYLIBS) kpdfview.o pdf.o blitbuffer.o util.o drawcontext.o einkfb.o input.o mupdfimg.o: %.o: %.c $(CC) -c $(KPDFREADER_CFLAGS) $(EMU_CFLAGS) -I$(LFSDIR)/src $< -o $@ +k2pdfopt.o: %.o: %.c + $(CC) -c -I$(MUPDFDIR)/ $(CFLAGS) $< -o $@ + djvu.o: %.o: %.c $(CC) -c $(KPDFREADER_CFLAGS) -I$(DJVUDIR)/ $< -o $@ diff --git a/k2pdfopt.c b/k2pdfopt.c new file mode 100644 index 000000000..d982d6f9f --- /dev/null +++ b/k2pdfopt.c @@ -0,0 +1,6338 @@ +/* + ** k2pdfopt.c K2pdfopt optimizes PDF/DJVU files for mobile e-readers + ** (e.g. the Kindle) and smartphones. It works well on + ** multi-column PDF/DJVU files. K2pdfopt is freeware. + ** + ** Copyright (C) 2012 http://willus.com + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU Affero General Public License as + ** published by the Free Software Foundation, either version 3 of the + ** License, or (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU Affero General Public License for more details. + ** + ** You should have received a copy of the GNU Affero General Public License + ** along with this program. If not, see . + ** + /* + ** WILLUSDEBUGX flags: + ** 1 = Generic + ** 2 = breakinfo row analysis + ** 4 = word wrapping + ** 8 = word wrapping II + ** 16 = hyphens + ** 32 = OCR + ** + */ +// #define WILLUSDEBUGX 32 +// #define WILLUSDEBUG +#include "k2pdfopt.h" +#include +#include +#include +#include +#include +#include + +#define HAVE_MUPDF + +#define VERSION "v1.51" +#define GRAYLEVEL(r,g,b) ((int)(((r)*0.3+(g)*0.59+(b)*0.11)*1.002)) +#if (defined(WIN32) || defined(WIN64)) +#define TTEXT_BOLD ANSI_WHITE +#define TTEXT_NORMAL ANSI_NORMAL +#define TTEXT_BOLD2 ANSI_YELLOW +#define TTEXT_INPUT ANSI_GREEN +#define TTEXT_WARN ANSI_RED +#define TTEXT_HEADER ANSI_CYAN +#define TTEXT_MAGENTA ANSI_MAGENTA +#else +#define TTEXT_BOLD "\x1b[0m\x1b[34m" +#define TTEXT_NORMAL "\x1b[0m" +#define TTEXT_BOLD2 "\x1b[0m\x1b[33m" +#define TTEXT_INPUT "\x1b[0m\x1b[32m" +#define TTEXT_WARN "\x1b[0m\x1b[31m" +#define TTEXT_HEADER "\x1b[0m\x1b[36m" +#define TTEXT_MAGENTA "\x1b[0m\x1b[35m" +#endif + +#ifndef __ANSI_H__ +#define ANSI_RED "\x1b[1m\x1b[31m" +#define ANSI_GREEN "\x1b[1m\x1b[32m" +#define ANSI_YELLOW "\x1b[1m\x1b[33m" +#define ANSI_BROWN "\x1b[0m\x1b[33m" +#define ANSI_BLUE "\x1b[1m\x1b[34m" +#define ANSI_MAGENTA "\x1b[1m\x1b[35m" +#define ANSI_CYAN "\x1b[1m\x1b[36m" +#define ANSI_WHITE "\x1b[1m\x1b[37m" +#define ANSI_NORMAL "\x1b[0m\x1b[37m" +#define ANSI_SAVE_CURSOR "\x1b[s" +#define ANSI_RESTORE_CURSOR "\x1b[u" +#define ANSI_CLEAR_TO_END "\x1b[K" +#define ANSI_BEGIN_LINE "\x1b[80D" +#define ANSI_UP_ONE_LINE "\x1b[1A" +#define ANSI_HOME "\x1b[2J\x1b[0;0;H" +#define __ANSI_H__ +#endif + +/* bmp.c */ +#define WILLUSBITMAP_TYPE_NATIVE 0 +#define WILLUSBITMAP_TYPE_WIN32 1 + +#ifdef PI +#undef PI +#endif +/* + ** Constants from the front of the CRC standard math tables + ** (Accuracy = 50 digits) + */ +/* The 50 digits cause problems with MPW's Mr. C on the Macintosh, */ +/* so I've truncated to 20 digits. */ +/* + #define PI 3.14159265358979323846264338327950288419716939937511 + #define SQRT2 1.41421356237309504880168872420969807856967187537695 + #define SQRT3 1.73205080756887729352744634150587236694280525381039 + #define LOG10E 0.43429448190325182765112891891660508229439700580367 + */ +#define PI 3.1415926535897932384 +#define SQRT2 1.4142135623730950488 +#define SQRT3 1.7320508075688772935 +#define LOG10E 0.4342944819032518276 +#define DBPERNEP (20.*LOG10E) + +#define SRC_TYPE_PDF 1 +#define SRC_TYPE_DJVU 2 +#define SRC_TYPE_OTHER 3 + +/* DATA STRUCTURES */ + +typedef struct { + int page; /* Source page */ + double rot_deg; /* Source rotation (happens first) */ + double x0, y0; /* x0,y0, in points, of lower left point on rectangle */ + double w, h; /* width and height of rectangle in points */ + double scale; /* Scale rectangle by this factor on destination page */ + double x1, y1; /* (x,y) position of lower left point on destination page, in points */ +} PDFBOX; + +typedef struct { + PDFBOX *box; + int n; + int na; +} PDFBOXES; + +typedef struct { + int pageno; /* Source page number */ + double page_rot_deg; /* Source page rotation */ + PDFBOXES boxes; +} PAGEINFO; + +typedef struct { + int ch; /* Hyphen starting point -- < 0 for no hyphen */ + int c2; /* End of end region if hyphen is erased */ + int r1; /* Top of hyphen */ + int r2; /* Bottom of hyphen */ +} HYPHENINFO; + +typedef struct { + int c1, c2; /* Left and right columns */ + int r1, r2; /* Top and bottom of region in pixels */ + int rowbase; /* Baseline of row */ + int gap; /* Gap to next region in pixels */ + int rowheight; /* text + gap */ + int capheight; + int h5050; + int lcheight; + HYPHENINFO hyphen; +} TEXTROW; + +typedef struct { + TEXTROW *textrow; + int rhmean_pixels; /* Mean row height (text) */ + int centered; /* Is this set of rows centered? */ + int n, na; +} BREAKINFO; + +typedef struct { + int red[256]; + int green[256]; + int blue[256]; + unsigned char *data; /* Top to bottom in native type, bottom to */ + /* top in Win32 type. */ + int width; /* Width of image in pixels */ + int height; /* Height of image in pixels */ + int bpp; /* Bits per pixel (only 8 or 24 allowed) */ + int size_allocated; + int type; /* See defines above for WILLUSBITMAP_TYPE_... */ +} WILLUSBITMAP; + +typedef struct { + int r1, r2; /* row position from top of bmp, inclusive */ + int c1, c2; /* column positions, inclusive */ + int rowbase; /* Baseline of text row */ + int capheight; /* capital letter height */ + int h5050; + int lcheight; /* lower-case letter height */ + int bgcolor; /* 0 - 255 */ + HYPHENINFO hyphen; + WILLUSBITMAP *bmp; + WILLUSBITMAP *bmp8; + WILLUSBITMAP *marked; +} BMPREGION; + +typedef struct { + WILLUSBITMAP bmp; + int rows; + int published_pages; + int bgcolor; + int fit_to_page; + int wordcount; + char debugfolder[256]; +} MASTERINFO; + +static int verbose = 0; +static int debug = 0; + +#define DEFAULT_WIDTH 600 +#define DEFAULT_HEIGHT 800 +#define MIN_REGION_WIDTH_INCHES 1.0 +#define SRCROT_AUTO -999. +#define SRCROT_AUTOEP -998. + +/* + ** Blank Area Threshold Widths--average black pixel width, in inches, that + ** prevents a region from being determined as "blank" or clear. + */ +static double gtc_in = .005; // detecting gap between columns +static double gtr_in = .006; // detecting gap between rows +static double gtw_in = .0015; // detecting gap between words +// static double gtm_in=.005; // detecting margins for trimming +static int src_left_to_right = 1; +static int src_whitethresh = -1; +static int dst_dpi = 167; +static int fit_columns = 1; +static int src_dpi = 300; +static int dst_width = DEFAULT_WIDTH; /* Full device width in pixels */ +static int dst_height = DEFAULT_HEIGHT; +static int dst_userwidth = DEFAULT_WIDTH; +static int dst_userheight = DEFAULT_HEIGHT; +static int dst_justify = -1; // 0 = left, 1 = center +static int dst_figure_justify = -1; // -1 = same as dst_justify. 0=left 1=center 2=right +static double dst_min_figure_height_in = 0.75; +static int dst_fulljustify = -1; // 0 = no, 1 = yes +static int dst_color = 0; +static int dst_landscape = 0; +static double dst_mar = 0.02; +static double dst_martop = -1.0; +static double dst_marbot = -1.0; +static double dst_marleft = -1.0; +static double dst_marright = -1.0; +static double min_column_gap_inches = 0.1; +static double max_column_gap_inches = 1.5; // max gap between columns +static double min_column_height_inches = 1.5; +static double mar_top = -1.0; +static double mar_bot = -1.0; +static double mar_left = -1.0; +static double mar_right = -1.0; +static double max_region_width_inches = 3.6; /* Max viewable width (device width minus margins) */ +static int max_columns = 2; +static double column_gap_range = 0.33; +static double column_offset_max = 0.2; +static double column_row_gap_height_in = 1. / 72.; +static int text_wrap = 1; +static double word_spacing = 0.375; +static double display_width_inches = 3.6; /* Device width = dst_width / dst_dpi */ +static int column_fitted = 0; +static double lm_org, bm_org, tm_org, rm_org, dpi_org; +static double contrast_max = 2.0; +static int show_marked_source = 0; +static double defect_size_pts = 1.0; +static double max_vertical_gap_inches = 0.25; +static double vertical_multiplier = 1.0; +static double vertical_line_spacing = -1.2; +static double vertical_break_threshold = 1.75; +static int erase_vertical_lines = 0; +static int k2_hyphen_detect = 1; +static int dst_fit_to_page = 0; +/* + ** Undocumented cmd-line args + */ +static double no_wrap_ar_limit = 0.2; /* -arlim */ +static double no_wrap_height_limit_inches = 0.55; /* -whmax */ +static double little_piece_threshold_inches = 0.5; /* -rwmin */ +/* + ** Keeping track of vertical gaps + */ +static double last_scale_factor_internal = -1.0; +/* indicates desired vert. gap before next region is added. */ +static int last_rowbase_internal; /* Pixels between last text row baseline and current end */ +/* of destination bitmap. */ +static int beginning_gap_internal = -1; +static int last_h5050_internal = -1; +static int just_flushed_internal = 0; +static int gap_override_internal; /* If > 0, apply this gap in wrapbmp_flush() and then reset. */ + +void adjust_params_init(void); +void set_region_widths(void); +static void mark_source_page(BMPREGION *region, int caller_id, int mark_flags); +static void fit_column_to_screen(double column_width_inches); +static void restore_output_dpi(void); +void adjust_contrast(WILLUSBITMAP *src, WILLUSBITMAP *srcgrey, int *white); +static int bmpregion_row_black_count(BMPREGION *region, int r0); +static void bmpregion_row_histogram(BMPREGION *region); +static int bmpregion_find_multicolumn_divider(BMPREGION *region, + int *row_black_count, BMPREGION *pageregion, int *npr, int *colcount, + int *rowcount); +static int bmpregion_column_height_and_gap_test(BMPREGION *column, + BMPREGION *region, int r1, int r2, int cmid, int *colcount, + int *rowcount); +static int bmpregion_is_clear(BMPREGION *region, int *row_is_clear, + double gt_in); +void bmpregion_multicolumn_add(BMPREGION *region, MASTERINFO *masterinfo, + int level, PAGEINFO *pageinfo, int colgap0_pixels); +static void bmpregion_vertically_break(BMPREGION *region, + MASTERINFO *masterinfo, int allow_text_wrapping, double force_scale, + int *colcount, int *rowcount, PAGEINFO *pageinfo, int colgap_pixels, + int ncols); +static void bmpregion_add(BMPREGION *region, BREAKINFO *breakinfo, + MASTERINFO *masterinfo, int allow_text_wrapping, int trim_flags, + int allow_vertical_breaks, double force_scale, int justify_flags, + int caller_id, int *colcount, int *rowcount, PAGEINFO *pageinfo, + int mark_flags, int rowbase_delta); +static void dst_add_gap_src_pixels(char *caller, MASTERINFO *masterinfo, + int pixels); +static void dst_add_gap(MASTERINFO *masterinfo, double inches); +static void bmp_src_to_dst(MASTERINFO *masterinfo, WILLUSBITMAP *src, + int justification_flags, int whitethresh, int nocr, int dpi); +static void bmp_fully_justify(WILLUSBITMAP *jbmp, WILLUSBITMAP *src, int nocr, + int whitethresh, int just); +#ifdef HAVE_OCR +static void ocrwords_fill_in(OCRWORDS *words,WILLUSBITMAP *src,int whitethresh,int dpi); +#endif +static void bmpregion_trim_margins(BMPREGION *region, int *colcount0, + int *rowcount0, int flags); +static void bmpregion_hyphen_detect(BMPREGION *region); +#if (WILLUSDEBUGX & 6) +static void breakinfo_echo(BREAKINFO *bi); +#endif +#if (defined(WILLUSDEBUGX) || defined(WILLUSDEBUG)) +static void bmpregion_write(BMPREGION *region,char *filename); +#endif +static int height2_calc(int *rc, int n); +static void trim_to(int *count, int *i1, int i2, double gaplen); +static void bmpregion_analyze_justification_and_line_spacing(BMPREGION *region, + BREAKINFO *breakinfo, MASTERINFO *masterinfo, int *colcount, + int *rowcount, PAGEINFO *pageinfo, int allow_text_wrapping, + double force_scale); +static int bmpregion_is_centered(BMPREGION *region, BREAKINFO *breakinfo, + int i1, int i2, int *textheight); +static double median_val(double *x, int n); +static void bmpregion_find_vertical_breaks(BMPREGION *region, + BREAKINFO *breakinfo, int *colcount, int *rowcount, double apsize_in); +static void textrow_assign_bmpregion(TEXTROW *textrow, BMPREGION *region); +static void breakinfo_compute_row_gaps(BREAKINFO *breakinfo, int r2); +static void breakinfo_compute_col_gaps(BREAKINFO *breakinfo, int c2); +static void breakinfo_remove_small_col_gaps(BREAKINFO *breakinfo, int lcheight, + double mingap); +static void breakinfo_remove_small_rows(BREAKINFO *breakinfo, double fracrh, + double fracgap, BMPREGION *region, int *colcount, int *rowcount); +static void breakinfo_alloc(int index, BREAKINFO *breakinfo, int nrows); +static void breakinfo_free(int index, BREAKINFO *breakinfo); +static void breakinfo_sort_by_gap(BREAKINFO *breakinfo); +static void breakinfo_sort_by_row_position(BREAKINFO *breakinfo); +static void bmpregion_one_row_find_breaks(BMPREGION *region, + BREAKINFO *breakinfo, int *colcount, int *rowcount, int add_to_dbase); +void wrapbmp_init(void); +static int wrapbmp_ends_in_hyphen(void); +static void wrapbmp_set_color(int is_color); +static void wrapbmp_free(void); +static void wrapbmp_set_maxgap(int value); +static int wrapbmp_width(void); +static int wrapbmp_remaining(void); +static void wrapbmp_add(BMPREGION *region, int gap, int line_spacing, int rbase, + int gio, int justification_flags); +static void wrapbmp_flush(MASTERINFO *masterinfo, int allow_full_justify, + PAGEINFO *pageinfo, int use_bgi); +static void wrapbmp_hyphen_erase(void); +static void bmpregion_one_row_wrap_and_add(BMPREGION *region, + BREAKINFO *breakinfo, int index, int i0, int i1, MASTERINFO *masterinfo, + int justflags, int *colcount, int *rowcount, PAGEINFO *pageinfo, + int rheight, int mean_row_gap, int rowbase, int marking_flags, int pi); +static void white_margins(WILLUSBITMAP *src, WILLUSBITMAP *srcgrey); +static void get_white_margins(BMPREGION *region); +/* Bitmap orientation detection functions */ +static double bitmap_orientation(WILLUSBITMAP *bmp); +static double bmp_inflections_vertical(WILLUSBITMAP *srcgrey, int ndivisions, + int delta, int *wthresh); +static double bmp_inflections_horizontal(WILLUSBITMAP *srcgrey, int ndivisions, + int delta, int *wthresh); +static int inflection_count(double *x, int n, int delta, int *wthresh); +static void pdfboxes_init(PDFBOXES *boxes); +static void pdfboxes_free(PDFBOXES *boxes); +/* + static void pdfboxes_add_box(PDFBOXES *boxes,PDFBOX *box); + static void pdfboxes_delete(PDFBOXES *boxes,int n); + */ +static void word_gaps_add(BREAKINFO *breakinfo, int lcheight, + double *median_gap); +static void bmp_detect_vertical_lines(WILLUSBITMAP *bmp, WILLUSBITMAP *cbmp, + double dpi, double minwidth_in, double maxwidth_in, double minheight_in, + double anglemax_deg, int white_thresh); +static int vert_line_erase(WILLUSBITMAP *bmp, WILLUSBITMAP *cbmp, + WILLUSBITMAP *tmp, int row0, int col0, double tanthx, + double minheight_in, double minwidth_in, double maxwidth_in, + int white_thresh); +static void willus_dmem_alloc_warn(int index, void **ptr, int size, + char *funcname, int exitcode); +static void willus_dmem_free(int index, double **ptr, char *funcname); +static int willus_mem_alloc_warn(void **ptr, int size, char *name, int exitcode); +static void willus_mem_free(double **ptr, char *name); +static void sortd(double *x, int n); +static void sorti(int *x, int n); +static void bmp_init(WILLUSBITMAP *bmap); +static int bmp_alloc(WILLUSBITMAP *bmap); +static void bmp_free(WILLUSBITMAP *bmap); +static int bmp_copy(WILLUSBITMAP *dest, WILLUSBITMAP *src); +static void bmp_fill(WILLUSBITMAP *bmp,int r,int g,int b); +static int bmp_bytewidth(WILLUSBITMAP *bmp); +static unsigned char *bmp_rowptr_from_top(WILLUSBITMAP *bmp, int row); +static void bmp_more_rows(WILLUSBITMAP *bmp, double ratio, int pixval); +static int bmp_is_grayscale(WILLUSBITMAP *bmp); +static int bmp_resample(WILLUSBITMAP *dest, WILLUSBITMAP *src, double x1, + double y1, double x2, double y2, int newwidth, int newheight); +static void bmp_contrast_adjust(WILLUSBITMAP *dest,WILLUSBITMAP *src,double contrast); +static void bmp_convert_to_greyscale_ex(WILLUSBITMAP *dst, WILLUSBITMAP *src); +static int bmpmupdf_pixmap_to_bmp(WILLUSBITMAP *bmp, fz_context *ctx, + fz_pixmap *pixmap); + +static MASTERINFO _masterinfo, *masterinfo; +static WILLUSBITMAP _bmp, *bmp; +static int master_bmp_inited = 0; +static int master_bmp_width = 0; +static int master_bmp_height = 0; + +void k2pdfopt_mupdf_reflow_bmp(fz_context *ctx, fz_pixmap *pix, double rot_deg) { + PAGEINFO _pageinfo, *pageinfo; + WILLUSBITMAP _src, *src; + WILLUSBITMAP _srcgrey, *srcgrey; + int i, status, white, pw, np, src_type, or_detect, orep_detect, + second_time_through; + int pagecount, pagestep, pages_done, is_gray, dpi; + double size, area_ratio, bormean; + + masterinfo = &_masterinfo; + masterinfo->debugfolder[0] = '\0'; + second_time_through = 0; + white = src_whitethresh; /* Will be set by adjust_contrast() or set to src_whitethresh */ + dpi = src_dpi; + adjust_params_init(); + set_region_widths(); + + bmp = &_bmp; + src = &_src; + srcgrey = &_srcgrey; + + if (master_bmp_inited == 0) { + bmp_init(&masterinfo->bmp); + master_bmp_inited = 1; + } + // free last used master bmp + bmp_free(&masterinfo->bmp); + + bmp_init(&masterinfo->bmp); + bmp_init(src); + bmp_init(srcgrey); + + wrapbmp_init(); + + int ii; + masterinfo->bmp.bpp = 8; + for (ii = 0; ii < 256; ii++) + masterinfo->bmp.red[ii] = masterinfo->bmp.blue[ii] = + masterinfo->bmp.green[ii] = ii; + masterinfo->rows = 0; + masterinfo->bmp.width = dst_width; + area_ratio = 8.5 * 11.0 * dst_dpi * dst_dpi / (dst_width * dst_height); + masterinfo->bmp.height = dst_height * area_ratio * 1.5; + bmp_alloc(&masterinfo->bmp); + bmp_fill(&masterinfo->bmp, 255, 255, 255); + + BMPREGION region; + + status = bmpmupdf_pixmap_to_bmp(src, ctx, pix); + bmp_copy(srcgrey, src); + adjust_contrast(src, srcgrey, &white); + white_margins(src, srcgrey); + + region.r1 = 0; + region.r2 = srcgrey->height - 1; + region.c1 = 0; + region.c2 = srcgrey->width - 1; + region.bgcolor = white; + region.bmp = src; + region.bmp8 = srcgrey; + + masterinfo->bgcolor = white; + masterinfo->fit_to_page = dst_fit_to_page; + /* Check to see if master bitmap might need more room */ + bmpregion_multicolumn_add(®ion, masterinfo, 1, pageinfo, + pages_done == 0. ? 0. : (int) (0.25 * src_dpi + .5)); + + master_bmp_width = masterinfo->bmp.width; + master_bmp_height = masterinfo->rows; + + bmp_free(srcgrey); + bmp_free(src); +} + +void k2pdfopt_mupdf_rfbmp_size(int *width, int *height) { + *width = master_bmp_width; + *height = master_bmp_height; +} + +void k2pdfopt_mupdf_rfbmp_ptr(unsigned char** bmp_ptr_ptr) { + *bmp_ptr_ptr = masterinfo->bmp.data; +} + +/* ansi.c */ +#define MAXSIZE 8000 + +static int ansi_on=1; +static char ansi_buffer[MAXSIZE]; + +int avprintf(FILE *f, char *fmt, va_list args) + +{ + int status; + { + if (!ansi_on) { + status = vsprintf(ansi_buffer, fmt, args); + ansi_parse(f, ansi_buffer); + } else + status = vfprintf(f, fmt, args); + } + return (status); +} + +int aprintf(char *fmt, ...) + +{ + va_list args; + int status; + + va_start(args, fmt); + status = avprintf(stdout, fmt, args); + va_end(args); + return (status); +} + +/* + ** Ensure that max_region_width_inches will be > MIN_REGION_WIDTH_INCHES + ** + ** Should only be called once, after all params are set. + ** + */ +void adjust_params_init(void) + +{ + if (dst_landscape) { + dst_width = dst_userheight; + dst_height = dst_userwidth; + } else { + dst_width = dst_userwidth; + dst_height = dst_userheight; + } + if (dst_mar < 0.) + dst_mar = 0.02; + if (dst_martop < 0.) + dst_martop = dst_mar; + if (dst_marbot < 0.) + dst_marbot = dst_mar; + if (dst_marleft < 0.) + dst_marleft = dst_mar; + if (dst_marright < 0.) + dst_marright = dst_mar; + if ((double) dst_width / dst_dpi - dst_marleft + - dst_marright< MIN_REGION_WIDTH_INCHES) { + int olddpi; + olddpi = dst_dpi; + dst_dpi = (int) ((double) dst_width + / (MIN_REGION_WIDTH_INCHES + dst_marleft + dst_marright)); + aprintf( + TTEXT_BOLD2 "Output DPI of %d is too large. Reduced to %d." TTEXT_NORMAL "\n\n", + olddpi, dst_dpi); + } +} + +void set_region_widths(void) + +{ + max_region_width_inches = display_width_inches = (double) dst_width + / dst_dpi; + max_region_width_inches -= (dst_marleft + dst_marright); + /* This is ensured by adjust_dst_dpi() as of v1.17 */ + /* + if (max_region_width_inches < MIN_REGION_WIDTH_INCHES) + max_region_width_inches = MIN_REGION_WIDTH_INCHES; + */ +} + +/* + ** Process full source page bitmap into rectangular regions and add + ** to the destination bitmap. Start by looking for columns. + ** + ** level = recursion level. First call = 1, then 2, ... + ** + */ +void bmpregion_multicolumn_add(BMPREGION *region, MASTERINFO *masterinfo, + int level, PAGEINFO *pageinfo, int colgap0_pixels) + +{ + static char *funcname = "bmpregion_multicolumn_add"; + int *row_black_count; + int r2, rh, r0, cgr, maxlevel; + BMPREGION *srcregion, _srcregion; + BMPREGION *newregion, _newregion; + BMPREGION *pageregion; + double minh; + int ipr, npr, na; + int *colcount, *rowcount; + + willus_dmem_alloc_warn(1, (void **) &colcount, + sizeof(int) * (region->c2 + 1), funcname, 10); + willus_dmem_alloc_warn(2, (void **) &rowcount, + sizeof(int) * (region->r2 + 1), funcname, 10); + maxlevel = max_columns / 2; + if (debug) + printf("@bmpregion_multicolumn_add (%d,%d) - (%d,%d) lev=%d\n", + region->c1, region->r1, region->c2, region->r2, level); + newregion = &_newregion; + (*newregion) = (*region); + /* Establish colcount, rowcount arrays */ + bmpregion_trim_margins(newregion, colcount, rowcount, 0xf); + (*newregion) = (*region); + srcregion = &_srcregion; + (*srcregion) = (*region); + /* How many page regions do we need? */ + minh = min_column_height_inches; + if (minh < .01) + minh = .1; + na = (srcregion->r2 - srcregion->r1 + 1) / src_dpi / minh; + if (na < 1) + na = 1; + na += 16; + /* Allocate page regions */ + willus_dmem_alloc_warn(3, (void **) &pageregion, sizeof(BMPREGION) * na, + funcname, 10); +#ifdef COMMENT + mindr=src_dpi*.045; /* src->height/250; */ + if (mindr<1) + mindr=1; +#endif +// white=250; +// for (i=0;iwidth;i++) +// colcount[i]=0; + if (debug) + bmpregion_row_histogram(region); + + /* + ** Store information about which rows are mostly clear for future + ** processing (saves processing time). + */ + willus_dmem_alloc_warn(4, (void **) &row_black_count, + region->bmp8->height * sizeof(int), funcname, 10); + for (cgr = 0, r0 = 0; r0 < region->bmp8->height; r0++) { + row_black_count[r0] = bmpregion_row_black_count(region, r0); + if (row_black_count[r0] == 0) + cgr++; + /* + int dr; + dr=mindr; + if (r0+dr>region->bmp8->height) + dr=region->bmp8->height-r0; + if ((row_is_clear[r0]=bmpregion_row_mostly_white(region,r0,dr))!=0) + cgr++; + */ +// printf("row_is_clear[%d]=%d\n",r0,row_is_clear[r0]); + } + if (verbose) + printf("%d clear rows.\n", cgr); + + if (max_columns == 1) { + pageregion[0] = (*srcregion); + /* Set c1 negative to indicate full span */ + pageregion[0].c1 = -1 - pageregion[0].c1; + npr = 1; + } else + /* Find all column dividers in source region and store sequentially in pageregion[] array */ + for (npr = 0, rh = 0; srcregion->r1 <= srcregion->r2; srcregion->r1 += + rh) { + static char *ierr = + TTEXT_WARN "\n\aInternal error--not enough allocated regions.\n" + "Please inform the developer at willus.com.\n\n" TTEXT_NORMAL; + if (npr >= na - 3) { + aprintf("%s", ierr); + break; + } + rh = bmpregion_find_multicolumn_divider(srcregion, row_black_count, + pageregion, &npr, colcount, rowcount); + if (verbose) + printf("rh=%d/%d\n", rh, region->r2 - region->r1 + 1); + } + + /* Process page regions by column */ + if (debug) + printf("Page regions: %d\n", npr); + r2 = -1; + for (ipr = 0; ipr < npr;) { + int r20, jpr, colnum, colgap_pixels; + + for (colnum = 1; colnum <= 2; colnum++) { + if (debug) { + printf("ipr = %d of %d...\n", ipr, npr); + printf("COLUMN %d...\n", colnum); + } + r20 = r2; + for (jpr = ipr; jpr < npr; jpr += 2) { + /* If we get to a page region that spans the entire source, stop */ + if (pageregion[jpr].c1 < 0) + break; + /* See if we should suspend this column and start displaying the next one */ + if (jpr > ipr) { + double cpdiff, cdiv1, cdiv2, rowgap1_in, rowgap2_in; + + if (column_offset_max < 0.) + break; + /* Did column divider move too much? */ + cdiv1 = (pageregion[jpr].c2 + pageregion[jpr + 1].c1) / 2.; + cdiv2 = (pageregion[jpr - 2].c2 + pageregion[jpr - 1].c1) + / 2.; + cpdiff = fabs( + (double) (cdiv1 - cdiv2) + / (srcregion->c2 - srcregion->c1 + 1)); + if (cpdiff > column_offset_max) + break; + /* Is gap between this column region and next column region too big? */ + rowgap1_in = (double) (pageregion[jpr].r1 + - pageregion[jpr - 2].r2) / src_dpi; + rowgap2_in = (double) (pageregion[jpr + 1].r1 + - pageregion[jpr - 1].r2) / src_dpi; + if (rowgap1_in > 0.28 && rowgap2_in > 0.28) + break; + } + (*newregion) = pageregion[ + src_left_to_right ? + jpr + colnum - 1 : jpr + (2 - colnum)]; + /* Preserve vertical gap between this region and last region */ + if (r20 >= 0 && newregion->r1 - r20 >= 0) + colgap_pixels = newregion->r1 - r20; + else + colgap_pixels = colgap0_pixels; + if (level < maxlevel) + bmpregion_multicolumn_add(newregion, masterinfo, level + 1, + pageinfo, colgap_pixels); + else { + bmpregion_vertically_break(newregion, masterinfo, text_wrap, + fit_columns ? -2.0 : -1.0, colcount, rowcount, + pageinfo, colgap_pixels, 2 * level); + } + r20 = newregion->r2; + } + if (r20 > r2) + r2 = r20; + if (jpr == ipr) + break; + } + if (jpr < npr && pageregion[jpr].c1 < 0) { + if (debug) + printf("SINGLE COLUMN REGION...\n"); + (*newregion) = pageregion[jpr]; + newregion->c1 = -1 - newregion->c1; + /* dst_add_gap_src_pixels("Col level",masterinfo,newregion->r1-r2); */ + colgap_pixels = newregion->r1 - r2; + bmpregion_vertically_break(newregion, masterinfo, text_wrap, + (fit_columns && (level > 1)) ? -2.0 : -1.0, colcount, + rowcount, pageinfo, colgap_pixels, level); + r2 = newregion->r2; + jpr++; + } + ipr = jpr; + } + willus_dmem_free(4, (double **) &row_black_count, funcname); + willus_dmem_free(3, (double **) &pageregion, funcname); + willus_dmem_free(2, (double **) &rowcount, funcname); + willus_dmem_free(1, (double **) &colcount, funcname); +} + +static void fit_column_to_screen(double column_width_inches) + +{ + double text_width_pixels, lm_pixels, rm_pixels, tm_pixels, bm_pixels; + + if (!column_fitted) { + dpi_org = dst_dpi; + lm_org = dst_marleft; + rm_org = dst_marright; + tm_org = dst_martop; + bm_org = dst_marbot; + } + text_width_pixels = max_region_width_inches * dst_dpi; + lm_pixels = dst_marleft * dst_dpi; + rm_pixels = dst_marright * dst_dpi; + tm_pixels = dst_martop * dst_dpi; + bm_pixels = dst_marbot * dst_dpi; + dst_dpi = text_width_pixels / column_width_inches; + dst_marleft = lm_pixels / dst_dpi; + dst_marright = rm_pixels / dst_dpi; + dst_martop = tm_pixels / dst_dpi; + dst_marbot = bm_pixels / dst_dpi; + set_region_widths(); + column_fitted = 1; +} + +static void restore_output_dpi(void) + +{ + if (column_fitted) { + dst_dpi = dpi_org; + dst_marleft = lm_org; + dst_marright = rm_org; + dst_martop = tm_org; + dst_marbot = bm_org; + set_region_widths(); + } + column_fitted = 0; +} + +void adjust_contrast(WILLUSBITMAP *src, WILLUSBITMAP *srcgrey, int *white) + +{ + int i, j, tries, wc, tc, hist[256]; + double contrast, rat0; + WILLUSBITMAP *dst, _dst; + + if (debug && verbose) + printf("\nAt adjust_contrast.\n"); + if ((*white) <= 0) + (*white) = 192; + /* If contrast_max negative, use it as fixed contrast adjustment. */ + if (contrast_max < 0.) { + bmp_contrast_adjust(srcgrey, srcgrey, -contrast_max); + if (dst_color && fabs(contrast_max + 1.0) > 1e-4) + bmp_contrast_adjust(src, src, -contrast_max); + return; + } + dst = &_dst; + bmp_init(dst); + wc = 0; /* Avoid compiler warning */ + tc = srcgrey->width * srcgrey->height; + rat0 = 0.5; /* Avoid compiler warning */ + for (contrast = 1.0, tries = 0; contrast < contrast_max + .01; tries++) { + if (fabs(contrast - 1.0) > 1e-4) + bmp_contrast_adjust(dst, srcgrey, contrast); + else + bmp_copy(dst, srcgrey); + /*Get bitmap histogram */ + for (i = 0; i < 256; i++) + hist[i] = 0; + for (j = 0; j < dst->height; j++) { + unsigned char *p; + p = bmp_rowptr_from_top(dst, j); + for (i = 0; i < dst->width; i++, p++) + hist[p[0]]++; + } + if (tries == 0) { + int h1; + for (h1 = 0, j = (*white); j < 256; j++) + h1 += hist[j]; + rat0 = (double) h1 / tc; + if (debug && verbose) + printf(" rat0 = rat[%d-255]=%.4f\n", (*white), rat0); + } + + /* Find white ratio */ + /* + for (wc=hist[254],j=253;j>=252;j--) + if (hist[j]>wc1) + wc1=hist[j]; + */ + for (wc = 0, j = 252; j <= 255; j++) + wc += hist[j]; + /* + if ((double)wc/tc >= rat0*0.7 && (double)hist[255]/wc > 0.995) + break; + */ + if (debug && verbose) + printf(" %2d. Contrast=%7.2f, rat[252-255]/rat0=%.4f\n", + tries + 1, contrast, (double) wc / tc / rat0); + if ((double) wc / tc >= rat0 * 0.94) + break; + contrast *= 1.05; + } + if (debug) + printf("Contrast=%7.2f, rat[252-255]/rat0=%.4f\n", contrast, + (double) wc / tc / rat0); + /* + bmp_write(dst,"outc.png",stdout,100); + wfile_written_info("outc.png",stdout); + exit(10); + */ + bmp_copy(srcgrey, dst); + /* Maybe don't adjust the contrast for the color bitmap? */ + if (dst_color && fabs(contrast - 1.0) > 1e-4) + bmp_contrast_adjust(src, src, contrast); + bmp_free(dst); +} + +static int bmpregion_row_black_count(BMPREGION *region, int r0) + +{ + unsigned char *p; + int i, nc, c; + + p = bmp_rowptr_from_top(region->bmp8, r0) + region->c1; + nc = region->c2 - region->c1 + 1; + for (c = i = 0; i < nc; i++, p++) + if (p[0] < region->bgcolor) + c++; + return (c); +} + +/* + ** Returns height of region found and divider position in (*divider_column). + ** (*divider_column) is absolute position on source bitmap. + ** + */ +static int bmpregion_find_multicolumn_divider(BMPREGION *region, + int *row_black_count, BMPREGION *pageregion, int *npr, int *colcount, + int *rowcount) + +{ + int itop, i, dm, middle, divider_column, min_height_pixels, mhp2, + min_col_gap_pixels; + BMPREGION _newregion, *newregion, column[2]; + BREAKINFO *breakinfo, _breakinfo; + int *rowmin, *rowmax; + static char *funcname = "bmpregion_find_multicolumn_divider"; + + if (debug) + printf("@bmpregion_find_multicolumn_divider(%d,%d)-(%d,%d)\n", + region->c1, region->r1, region->c2, region->r2); + breakinfo = &_breakinfo; + breakinfo->textrow = NULL; + breakinfo_alloc(101, breakinfo, region->r2 - region->r1 + 1); + bmpregion_find_vertical_breaks(region, breakinfo, colcount, rowcount, + column_row_gap_height_in); + /* + { + printf("region (%d,%d)-(%d,%d) has %d breaks:\n",region->c1,region->r1,region->c2,region->r2,breakinfo->n); + for (i=0;in;i++) + printf(" Rows %d - %d\n",breakinfo->textrow[i].r1,breakinfo->textrow[i].r2); + } + */ + newregion = &_newregion; + (*newregion) = (*region); + min_height_pixels = min_column_height_inches * src_dpi; /* src->height/15; */ + mhp2 = min_height_pixels - 1; + if (mhp2 < 0) + mhp2 = 0; + dm = 1 + (region->c2 - region->c1 + 1) * column_gap_range / 2.; + middle = (region->c2 - region->c1 + 1) / 2; + min_col_gap_pixels = (int) (min_column_gap_inches * src_dpi + .5); + if (verbose) { + printf("(dm=%d, width=%d, min_gap=%d)\n", dm, + region->c2 - region->c1 + 1, min_col_gap_pixels); + printf("Checking regions (r1=%d, r2=%d, minrh=%d)..", region->r1, + region->r2, min_height_pixels); + fflush(stdout); + } + breakinfo_sort_by_row_position(breakinfo); + willus_dmem_alloc_warn(5, (void **) &rowmin, + (region->c2 + 10) * 2 * sizeof(int), funcname, 10); + rowmax = &rowmin[region->c2 + 10]; + for (i = 0; i < region->c2 + 2; i++) { + rowmin[i] = region->r2 + 2; + rowmax[i] = -1; + } + + /* Start with top-most and bottom-most regions, look for column dividers */ + for (itop = 0; + itop < breakinfo->n + && breakinfo->textrow[itop].r1 + < region->r2 + 1 - min_height_pixels; itop++) { + int ibottom; + + for (ibottom = breakinfo->n - 1; + ibottom >= itop + && breakinfo->textrow[ibottom].r2 + - breakinfo->textrow[itop].r1 + >= min_height_pixels; ibottom--) { + /* + ** Look for vertical shaft of clear space that clearly demarcates + ** two columns + */ + for (i = 0; i < dm; i++) { + int foundgap, ii, c1, c2, iiopt, status; + + newregion->c1 = region->c1 + middle - i; + /* If we've effectively already checked this shaft, move on */ + if (itop >= rowmin[newregion->c1] + && ibottom <= rowmax[newregion->c1]) + continue; + newregion->c2 = newregion->c1 + min_col_gap_pixels - 1; + newregion->r1 = breakinfo->textrow[itop].r1; + newregion->r2 = breakinfo->textrow[ibottom].r2; + foundgap = bmpregion_is_clear(newregion, row_black_count, + gtc_in); + if (!foundgap && i > 0) { + newregion->c1 = region->c1 + middle + i; + newregion->c2 = newregion->c1 + min_col_gap_pixels - 1; + foundgap = bmpregion_is_clear(newregion, row_black_count, + gtc_in); + } + if (!foundgap) + continue; + /* Found a gap, but look for a better gap nearby */ + c1 = newregion->c1; + c2 = newregion->c2; + for (iiopt = 0, ii = -min_col_gap_pixels; + ii <= min_col_gap_pixels; ii++) { + int newgap; + newregion->c1 = c1 + ii; + newregion->c2 = c2 + ii; + newgap = bmpregion_is_clear(newregion, row_black_count, + gtc_in); + if (newgap > 0 && newgap < foundgap) { + iiopt = ii; + foundgap = newgap; + if (newgap == 1) + break; + } + } + newregion->c1 = c1 + iiopt; + /* If we've effectively already checked this shaft, move on */ + if (itop >= rowmin[newregion->c1] + && ibottom <= rowmax[newregion->c1]) + continue; + newregion->c2 = c2 + iiopt; + divider_column = newregion->c1 + min_col_gap_pixels / 2; + status = bmpregion_column_height_and_gap_test(column, region, + breakinfo->textrow[itop].r1, + breakinfo->textrow[ibottom].r2, divider_column, + colcount, rowcount); + /* If fails column height or gap test, mark as bad */ + if (status) { + if (itop < rowmin[newregion->c1]) + rowmin[newregion->c1] = itop; + if (ibottom > rowmax[newregion->c1]) + rowmax[newregion->c1] = ibottom; + } + /* If right column too short, stop looking */ + if (status & 2) + break; + if (!status) { + int colheight; + + /* printf(" GOT COLUMN DIVIDER AT x=%d.\n",(*divider_column)); */ + if (verbose) { + printf("\n GOOD REGION: col gap=(%d,%d) - (%d,%d)\n" + " r1=%d, r2=%d\n", + newregion->c1, newregion->r1, newregion->c2, + newregion->r2, breakinfo->textrow[itop].r1, + breakinfo->textrow[ibottom].r2); + } + if (itop > 0) { + /* add 1-column region */ + pageregion[(*npr)] = (*region); + pageregion[(*npr)].r2 = breakinfo->textrow[itop - 1].r2; + if (pageregion[(*npr)].r2 + > pageregion[(*npr)].bmp8->height - 1) + pageregion[(*npr)].r2 = + pageregion[(*npr)].bmp8->height - 1; + bmpregion_trim_margins(&pageregion[(*npr)], colcount, + rowcount, 0xf); + /* Special flag to indicate full-width region */ + pageregion[(*npr)].c1 = -1 - pageregion[(*npr)].c1; + (*npr) = (*npr) + 1; + } + pageregion[(*npr)] = column[0]; + (*npr) = (*npr) + 1; + pageregion[(*npr)] = column[1]; + (*npr) = (*npr) + 1; + colheight = breakinfo->textrow[ibottom].r2 - region->r1 + 1; + breakinfo_free(101, breakinfo); + /* + printf("Returning %d divider column = %d - %d\n",region->r2-region->r1+1,newregion->c1,newregion->c2); + */ + return (colheight); + } + } + } + } + if (verbose) + printf("NO GOOD REGION FOUND.\n"); + pageregion[(*npr)] = (*region); + bmpregion_trim_margins(&pageregion[(*npr)], colcount, rowcount, 0xf); + /* Special flag to indicate full-width region */ + pageregion[(*npr)].c1 = -1 - pageregion[(*npr)].c1; + (*npr) = (*npr) + 1; + /* (*divider_column)=region->c2+1; */ + willus_dmem_free(5, (double **) &rowmin, funcname); + breakinfo_free(101, breakinfo); + /* + printf("Returning %d\n",region->r2-region->r1+1); + */ + return (region->r2 - region->r1 + 1); +} + +/* + ** 1 = column 1 too short + ** 2 = column 2 too short + ** 3 = both too short + ** 0 = both okay + ** Both columns must pass height requirement. + ** + ** Also, if gap between columns > max_column_gap_inches, fails test. (8-31-12) + ** + */ +static int bmpregion_column_height_and_gap_test(BMPREGION *column, + BMPREGION *region, int r1, int r2, int cmid, int *colcount, + int *rowcount) + +{ + int min_height_pixels, status; + + status = 0; + min_height_pixels = min_column_height_inches * src_dpi; + column[0] = (*region); + column[0].r1 = r1; + column[0].r2 = r2; + column[0].c2 = cmid - 1; + bmpregion_trim_margins(&column[0], colcount, rowcount, 0xf); + /* + printf(" COL1: pix=%d (%d - %d)\n",newregion->r2-newregion->r1+1,newregion->r1,newregion->r2); + */ + if (column[0].r2 - column[0].r1 + 1 < min_height_pixels) + status |= 1; + column[1] = (*region); + column[1].r1 = r1; + column[1].r2 = r2; + column[1].c1 = cmid; + column[1].c2 = region->c2; + bmpregion_trim_margins(&column[1], colcount, rowcount, 0xf); + /* + printf(" COL2: pix=%d (%d - %d)\n",newregion->r2-newregion->r1+1,newregion->r1,newregion->r2); + */ + if (column[1].r2 - column[1].r1 + 1 < min_height_pixels) + status |= 2; + /* Make sure gap between columns is not too large */ + if (max_column_gap_inches >= 0. + && column[1].c1 - column[0].c2 - 1 + > max_column_gap_inches * src_dpi) + status |= 4; + return (status); +} + +/* + ** Return 0 if there are dark pixels in the region. NZ otherwise. + */ +static int bmpregion_is_clear(BMPREGION *region, int *row_black_count, + double gt_in) + +{ + int r, c, nc, pt; + + /* + ** row_black_count[] doesn't necessarily match up to this particular region's columns. + ** So if row_black_count[] == 0, the row is clear, otherwise it has to be counted. + ** because the columns are a subset. + */ + /* nr=region->r2-region->r1+1; */ + nc = region->c2 - region->c1 + 1; + pt = (int) (gt_in * src_dpi * nc + .5); + if (pt < 0) + pt = 0; + for (c = 0, r = region->r1; r <= region->r2; r++) { + if (r < 0 || r >= region->bmp8->height) + continue; + if (row_black_count[r] == 0) + continue; + c += bmpregion_row_black_count(region, r); + if (c > pt) + return (0); + } + /* + printf("(%d,%d)-(%d,%d): c=%d, pt=%d (gt_in=%g)\n", + region->c1,region->r1,region->c2,region->r2,c,pt,gt_in); + */ + return (1 + (int) 10 * c / pt); +} + +static void bmpregion_row_histogram(BMPREGION *region) + +{ + static char *funcname = "bmpregion_row_histogram"; + WILLUSBITMAP *src; + FILE *out; + static int *rowcount; + static int *hist; + int i, j, nn; + + willus_dmem_alloc_warn(6, (void **) &rowcount, + (region->r2 - region->r1 + 1) * sizeof(int), funcname, 10); + willus_dmem_alloc_warn(7, (void **) &hist, + (region->c2 - region->c1 + 1) * sizeof(int), funcname, 10); + src = region->bmp8; + for (j = region->r1; j <= region->r2; j++) { + unsigned char *p; + p = bmp_rowptr_from_top(src, j) + region->c1; + rowcount[j - region->r1] = 0; + for (i = region->c1; i <= region->c2; i++, p++) + if (p[0] < region->bgcolor) + rowcount[j - region->r1]++; + } + for (i = region->c1; i <= region->c2; i++) + hist[i - region->c1] = 0; + for (i = region->r1; i <= region->r2; i++) + hist[rowcount[i - region->r1]]++; + for (i = region->c2 - region->c1 + 1; i >= 0; i--) + if (hist[i] > 0) + break; + nn = i; + out = fopen("hist.ep", "w"); + for (i = 0; i <= nn; i++) + fprintf(out, "%5d %5d\n", i, hist[i]); + fclose(out); + out = fopen("rowcount.ep", "w"); + for (i = 0; i < region->r2 - region->r1 + 1; i++) + fprintf(out, "%5d %5d\n", i, rowcount[i]); + fclose(out); + willus_dmem_free(7, (double **) &hist, funcname); + willus_dmem_free(6, (double **) &rowcount, funcname); +} + +/* + ** Mark the region + ** mark_flags & 1 : Mark top + ** mark_flags & 2 : Mark bottom + ** mark_flags & 4 : Mark left + ** mark_flags & 8 : Mark right + ** + */ +static void mark_source_page(BMPREGION *region0, int caller_id, int mark_flags) + +{ + static int display_order = 0; + int i, n, nn, fontsize, r, g, b, shownum; + char num[16]; + BMPREGION *region, _region; + BMPREGION *clip, _clip; + + if (!show_marked_source) + return; + + if (region0 == NULL) { + display_order = 0; + return; + } + + region = &_region; + (*region) = (*region0); + + /* Clip the region w/ignored margins */ + clip = &_clip; + clip->bmp = region0->bmp; + get_white_margins(clip); + if (region->c1 < clip->c1) + region->c1 = clip->c1; + if (region->c2 > clip->c2) + region->c2 = clip->c2; + if (region->r1 < clip->r1) + region->r1 = clip->r1; + if (region->r2 > clip->r2) + region->r2 = clip->r2; + if (region->r2 <= region->r1 || region->c2 <= region->c1) + return; + + /* printf("@mark_source_page(display_order=%d)\n",display_order); */ + if (caller_id == 1) { + display_order++; + shownum = 1; + n = (int) (src_dpi / 60. + 0.5); + if (n < 5) + n = 5; + r = 255; + g = b = 0; + } else if (caller_id == 2) { + shownum = 0; + n = 2; + r = 0; + g = 0; + b = 255; + } else if (caller_id == 3) { + shownum = 0; + n = (int) (src_dpi / 80. + 0.5); + if (n < 4) + n = 4; + r = 0; + g = 255; + b = 0; + } else if (caller_id == 4) { + shownum = 0; + n = 2; + r = 255; + g = 0; + b = 255; + } else { + shownum = 0; + n = 2; + r = 140; + g = 140; + b = 140; + } + if (n < 2) + n = 2; + nn = (region->c2 + 1 - region->c1) / 2; + if (n > nn) + n = nn; + nn = (region->r2 + 1 - region->r1) / 2; + if (n > nn) + n = nn; + if (n < 1) + n = 1; + for (i = 0; i < n; i++) { + int j; + unsigned char *p; + if (mark_flags & 1) { + p = bmp_rowptr_from_top(region->marked, region->r1 + i) + + region->c1 * 3; + for (j = region->c1; j <= region->c2; j++, p += 3) { + p[0] = r; + p[1] = g; + p[2] = b; + } + } + if (mark_flags & 2) { + p = bmp_rowptr_from_top(region->marked, region->r2 - i) + + region->c1 * 3; + for (j = region->c1; j <= region->c2; j++, p += 3) { + p[0] = r; + p[1] = g; + p[2] = b; + } + } + if (mark_flags & 16) /* rowbase */ + { + p = bmp_rowptr_from_top(region->marked, region->rowbase - i) + + region->c1 * 3; + for (j = region->c1; j <= region->c2; j++, p += 3) { + p[0] = r; + p[1] = g; + p[2] = b; + } + } + if (mark_flags & 4) + for (j = region->r1; j <= region->r2; j++) { + p = bmp_rowptr_from_top(region->marked, j) + + (region->c1 + i) * 3; + p[0] = r; + p[1] = g; + p[2] = b; + } + if (mark_flags & 8) + for (j = region->r1; j <= region->r2; j++) { + p = bmp_rowptr_from_top(region->marked, j) + + (region->c2 - i) * 3; + p[0] = r; + p[1] = g; + p[2] = b; + } + } + if (!shownum) + return; + fontsize = region->c2 - region->c1 + 1; + if (fontsize > region->r2 - region->r1 + 1) + fontsize = region->r2 - region->r1 + 1; + fontsize /= 2; + if (fontsize > src_dpi) + fontsize = src_dpi; + if (fontsize < 5) + return; + fontrender_set_typeface("helvetica-bold"); + fontrender_set_fgcolor(r, g, b); + fontrender_set_bgcolor(255, 255, 255); + fontrender_set_pixel_size(fontsize); + fontrender_set_justification(4); + fontrender_set_or(1); + sprintf(num, "%d", display_order); + fontrender_render(region->marked, (double) (region->c1 + region->c2) / 2., + (double) (region->marked->height - ((region->r1 + region->r2) / 2.)), + num, 0, NULL); + /* printf(" done mark_source_page.\n"); */ +} + +/* + ** Input: A generic rectangular region from the source file. It will not + ** be checked for multiple columns, but the text may be wrapped + ** (controlled by allow_text_wrapping input). + ** + ** force_scale == -2 : Use same scale for entire column--fit to device + ** + ** This function looks for vertical gaps in the region and breaks it at + ** the widest ones (if there are significantly wider ones). + ** + */ +static void bmpregion_vertically_break(BMPREGION *region, + MASTERINFO *masterinfo, int allow_text_wrapping, double force_scale, + int *colcount, int *rowcount, PAGEINFO *pageinfo, int colgap_pixels, + int ncols) + +{ + static int ncols_last = -1; + int regcount, i, i1, biggap, revert, trim_flags, allow_vertical_breaks; + int justification_flags, caller_id, marking_flags, rbdelta; + // int trim_left_and_right; + BMPREGION *bregion, _bregion; + BREAKINFO *breakinfo, _breakinfo; + double region_width_inches, region_height_inches; + +#if (WILLUSDEBUGX & 1) + printf("\n\n@bmpregion_vertically_break. colgap_pixels=%d\n\n",colgap_pixels); +#endif + trim_flags = 0xf; + allow_vertical_breaks = 1; + justification_flags = 0x8f; /* Don't know region justification status yet. Use user settings. */ + rbdelta = -1; + breakinfo = &_breakinfo; + breakinfo->textrow = NULL; + breakinfo_alloc(102, breakinfo, region->r2 - region->r1 + 1); + bmpregion_find_vertical_breaks(region, breakinfo, colcount, rowcount, -1.0); + /* Should there be a check for breakinfo->n==0 here? */ + /* Don't think it breaks anything to let it go. -- 6-11-12 */ +#if (WILLUSDEBUGX & 2) + breakinfo_echo(breakinfo); +#endif + breakinfo_remove_small_rows(breakinfo, 0.25, 0.5, region, colcount, + rowcount); +#if (WILLUSDEBUGX & 2) + breakinfo_echo(breakinfo); +#endif + breakinfo->centered = bmpregion_is_centered(region, breakinfo, 0, + breakinfo->n - 1, NULL); +#if (WILLUSDEBUGX & 2) + breakinfo_echo(breakinfo); +#endif + /* + newregion=&_newregion; + for (i=0;in;i++) + { + (*newregion)=(*region); + newregion->r1=breakinfo->textrow[i].r1; + newregion->r2=breakinfo->textrow[i].r2; + bmpregion_add(newregion,breakinfo,masterinfo,allow_text_wrapping,force_scale,0,1, + colcount,rowcount,pageinfo,0,0xf); + } + breakinfo_free(breakinfo); + return; + */ + /* + if (!vertical_breaks) + { + caller_id=100; + marking_flags=0; + bmpregion_add(region,breakinfo,masterinfo,allow_text_wrapping,trim_flags, + allow_vertical_breaks,force_scale,justification_flags, + caller_id,colcount,rowcount,pageinfo,marking_flags,rbdelta); + breakinfo_free(breakinfo); + return; + } + */ + /* Red, numbered region */ + mark_source_page(region, 1, 0xf); + bregion = &_bregion; + if (debug) { + if (!allow_text_wrapping) + printf( + "@bmpregion_vertically_break (no break) (%d,%d) - (%d,%d) (scale=%g)\n", + region->c1, region->r1, region->c2, region->r2, + force_scale); + else + printf( + "@bmpregion_vertically_break (allow break) (%d,%d) - (%d,%d) (scale=%g)\n", + region->c1, region->r1, region->c2, region->r2, + force_scale); + } + /* + ** Tag blank rows and columns + */ + if (vertical_break_threshold < 0. || breakinfo->n < 6) + biggap = -1.; + else { + int gap_median; + /* + int rowheight_median; + + breakinfo_sort_by_rowheight(breakinfo); + rowheight_median = breakinfo->textrow[breakinfo->n/2].rowheight; + */ +#ifdef WILLUSDEBUG + for (i=0;in;i++) + printf(" gap[%d]=%d\n",i,breakinfo->textrow[i].gap); +#endif + breakinfo_sort_by_gap(breakinfo); + gap_median = breakinfo->textrow[breakinfo->n / 2].gap; +#ifdef WILLUSDEBUG + printf(" median=%d\n",gap_median); +#endif + biggap = gap_median * vertical_break_threshold; + breakinfo_sort_by_row_position(breakinfo); + } +#ifdef WILLUSDEBUG + printf(" biggap=%d\n",biggap); +#endif + region_width_inches = (double) (region->c2 - region->c1 + 1) / src_dpi; + region_height_inches = (double) (region->r2 - region->r1 + 1) / src_dpi; + /* + trim_left_and_right = 1; + if (region_width_inches <= max_region_width_inches) + trim_left_and_right = 0; + */ + /* + printf("force_scale=%g, rwi = %g, rwi/mrwi = %g, rhi = %g\n", + force_scale, + region_width_inches, + region_width_inches / max_region_width_inches, + region_height_inches); + */ + if (force_scale < -1.5 && region_width_inches > MIN_REGION_WIDTH_INCHES + && region_width_inches / max_region_width_inches < 1.25 + && region_height_inches > 0.5) { + revert = 1; + force_scale = -1.0; + fit_column_to_screen(region_width_inches); + // trim_left_and_right = 0; + allow_text_wrapping = 0; + } else + revert = 0; + /* Add the regions (broken vertically) */ + caller_id = 1; + /* + if (trim_left_and_right) + trim_flags=0xf; + else + trim_flags=0xc; + */ + trim_flags = 0xf; + for (regcount = i1 = i = 0; i1 < breakinfo->n; i++) { + int i2; + + i2 = i < breakinfo->n ? i : breakinfo->n - 1; + if (i >= breakinfo->n + || (biggap > 0. && breakinfo->textrow[i2].gap >= biggap)) { + int j, c1, c2, nc, nowrap; + double regwidth, ar1, rh1; + +// printf("CALLER 1: i1=%d, i2=%d (breakinfo->n=%d)\n",i1,i2,breakinfo->n); + (*bregion) = (*region); + bregion->r1 = breakinfo->textrow[i1].r1; + bregion->r2 = breakinfo->textrow[i2].r2; + c1 = breakinfo->textrow[i1].c1; + c2 = breakinfo->textrow[i1].c2; + nc = c2 - c1 + 1; + if (nc <= 0) + nc = 1; + rh1 = (double) (breakinfo->textrow[i1].r2 + - breakinfo->textrow[i1].r1 + 1) / src_dpi; + ar1 = (double) (breakinfo->textrow[i1].r2 + - breakinfo->textrow[i1].r1 + 1) / nc; + for (j = i1 + 1; j <= i2; j++) { + if (c1 > breakinfo->textrow[j].c1) + c1 = breakinfo->textrow[j].c1; + if (c2 < breakinfo->textrow[j].c2) + c2 = breakinfo->textrow[j].c2; + } + regwidth = (double) (c2 - c1 + 1) / src_dpi; + marking_flags = (i1 == 0 ? 0 : 1) + | (i2 == breakinfo->n - 1 ? 0 : 2); + /* Green */ + mark_source_page(bregion, 3, marking_flags); + nowrap = ((regwidth <= max_region_width_inches + && allow_text_wrapping < 2) + || (ar1 > no_wrap_ar_limit + && rh1 > no_wrap_height_limit_inches)); + /* + ** If between regions, or if the next region isn't going to be + ** wrapped, or if the next region starts a different number of + ** columns than before, then "flush and gap." + */ + if (regcount > 0 || just_flushed_internal || nowrap + || (ncols_last > 0 && ncols_last != ncols)) { + int gap; +#ifdef WILLUSDEBUG + printf("wrapflush1\n"); +#endif + if (!just_flushed_internal) + wrapbmp_flush(masterinfo, 0, pageinfo, 0); + gap = regcount == 0 ? + colgap_pixels : breakinfo->textrow[i1 - 1].gap; + if (regcount == 0 && beginning_gap_internal > 0) { + if (last_h5050_internal > 0) { + if (fabs( + 1. + - (double) breakinfo->textrow[i1].h5050 + / last_h5050_internal) > .1) + dst_add_gap_src_pixels("Col/Page break", masterinfo, + colgap_pixels); + last_h5050_internal = -1; + } + gap = beginning_gap_internal; + beginning_gap_internal = -1; + } + dst_add_gap_src_pixels("Vert break", masterinfo, gap); + } else { + if (regcount == 0 && beginning_gap_internal < 0) + beginning_gap_internal = colgap_pixels; + } + bmpregion_add(bregion, breakinfo, masterinfo, allow_text_wrapping, + trim_flags, allow_vertical_breaks, force_scale, + justification_flags, caller_id, colcount, rowcount, + pageinfo, marking_flags, rbdelta); + regcount++; + i1 = i2 + 1; + } + } + ncols_last = ncols; + if (revert) + restore_output_dpi(); + breakinfo_free(102, breakinfo); +} + +/* + ** + ** MAIN BITMAP REGION ADDING FUNCTION + ** + ** NOTE: This function calls itself recursively! + ** + ** Input: A generic rectangular region from the source file. It will not + ** be checked for multiple columns, but the text may be wrapped + ** (controlled by allow_text_wrapping input). + ** + ** First, excess margins are trimmed off of the region. + ** + ** Then, if the resulting trimmed region is wider than the max desirable width + ** and allow_text_wrapping is non-zero, then the + ** bmpregion_analyze_justification_and_line_spacing() function is called. + ** Otherwise the region is scaled to fit and added to the master set of pages. + ** + ** justification_flags + ** Bits 6-7: 0 = document is not fully justified + ** 1 = document is fully justified + ** 2 = don't know document justification yet + ** Bits 4-5: 0 = Use user settings + ** 1 = fully justify + ** 2 = do not fully justify + ** Bits 2-3: 0 = document is left justified + ** 1 = document is centered + ** 2 = document is right justified + ** 3 = don't know document justification yet + ** Bits 0-1: 0 = left justify document + ** 1 = center document + ** 2 = right justify document + ** 3 = Use user settings + ** + ** force_scale = -2.0 : Fit column width to display width + ** force_scale = -1.0 : Use output dpi unless the region doesn't fit. + ** In that case, scale it down until it fits. + ** force_scale > 0.0 : Scale region by force_scale. + ** + ** mark_flags & 1 : Mark top + ** mark_flags & 2 : Mark bottom + ** mark_flags & 4 : Mark left + ** mark_flags & 8 : Mark right + ** + ** trim_flags & 0x80 : Do NOT re-trim no matter what. + ** + */ +static void bmpregion_add(BMPREGION *region, BREAKINFO *breakinfo, + MASTERINFO *masterinfo, int allow_text_wrapping, int trim_flags, + int allow_vertical_breaks, double force_scale, int justification_flags, + int caller_id, int *colcount, int *rowcount, PAGEINFO *pageinfo, + int mark_flags, int rowbase_delta) + +{ + int w, wmax, i, nc, nr, h, bpp, tall_region; + double region_width_inches; + WILLUSBITMAP *bmp, _bmp; + BMPREGION *newregion, _newregion; + + newregion = &_newregion; + (*newregion) = (*region); +#if (WILLUSDEBUGX & 1) + printf("@bmpregion_add (%d,%d) - (%d,%d)\n",region->c1,region->r1,region->c2,region->r2); + printf(" trimflags = %X\n",trim_flags); +#endif + if (debug) { + if (!allow_text_wrapping) + printf("@bmpregion_add (no break) (%d,%d) - (%d,%d) (scale=%g)\n", + region->c1, region->r1, region->c2, region->r2, + force_scale); + else + printf( + "@bmpregion_add (allow break) (%d,%d) - (%d,%d) (scale=%g)\n", + region->c1, region->r1, region->c2, region->r2, + force_scale); + } + /* + ** Tag blank rows and columns and trim the blank margins off + ** trimflags = 0xf for all margin trim. + ** trimflags = 0xc for just top and bottom margins. + */ + bmpregion_trim_margins(newregion, colcount, rowcount, trim_flags); +#if (WILLUSDEBUGX & 1) + printf(" After trim: (%d,%d) - (%d,%d)\n",newregion->c1,newregion->r1,newregion->c2,newregion->r2); +#endif + nc = newregion->c2 - newregion->c1 + 1; + nr = newregion->r2 - newregion->r1 + 1; +// printf("nc=%d, nr=%d\n",nc,nr); + if (verbose) { + printf(" row range adjusted to %d - %d\n", newregion->r1, + newregion->r2); + printf(" col range adjusted to %d - %d\n", newregion->c1, + newregion->c2); + } + if (nc <= 5 || nr <= 1) + return; + region_width_inches = (double) nc / src_dpi; +// printf("regwidth = %g in\n",region_width_inches); + /* Use untrimmed region left/right if possible */ + if (caller_id == 1 && region_width_inches <= max_region_width_inches) { + int trimleft, trimright; + int maxpix, dpix; + + maxpix = (int) (max_region_width_inches * src_dpi + .5); +#if (WILLUSDEBUGX & 1) + printf(" Trimming. C's = %4d %4d %4d %4d\n",region->c1,newregion->c1,newregion->c2,region->c2); + printf(" maxpix = %d, regwidth = %d\n",maxpix,region->c2-region->c1+1); +#endif + if (maxpix > (region->c2 - region->c1 + 1)) + maxpix = region->c2 - region->c1 + 1; +// printf(" maxpix = %d\n",maxpix); + dpix = (region->c2 - region->c1 + 1 - maxpix) / 2; +// printf(" dpix = %d\n",dpix); + trimright = region->c2 - newregion->c2; + trimleft = newregion->c1 - region->c1; + if (trimleft < trimright) { + if (trimleft > dpix) + newregion->c1 = region->c1 + dpix; + newregion->c2 = newregion->c1 + maxpix - 1; + } else { + if (trimright > dpix) + newregion->c2 = region->c2 - dpix; + newregion->c1 = newregion->c2 - maxpix + 1; + } + if (newregion->c1 < region->c1) + newregion->c1 = region->c1; + if (newregion->c2 > region->c2) + newregion->c2 = region->c2; + nc = newregion->c2 - newregion->c1 + 1; +#if (WILLUSDEBUGX & 1) + printf(" Post Trim. C's = %4d %4d %4d %4d\n",region->c1,newregion->c1,newregion->c2,region->c2); +#endif + region_width_inches = (double) nc / src_dpi; + } + + /* + ** Try breaking the region into smaller horizontal pieces (wrap text lines) + */ + /* + printf("allow_text_wrapping=%d, region_width_inches=%g, max_region_width_inches=%g\n", + allow_text_wrapping,region_width_inches,max_region_width_inches); + */ + /* New in v1.50, if allow_text_wrapping==2, unwrap short lines. */ + if (allow_text_wrapping == 2 + || (allow_text_wrapping == 1 + && region_width_inches > max_region_width_inches)) { + bmpregion_analyze_justification_and_line_spacing(newregion, breakinfo, + masterinfo, colcount, rowcount, pageinfo, 1, force_scale); + return; + } + + /* + ** If allowed, re-submit each vertical region individually + */ + if (allow_vertical_breaks) { + bmpregion_analyze_justification_and_line_spacing(newregion, breakinfo, + masterinfo, colcount, rowcount, pageinfo, 0, force_scale); + return; + } + + /* AT THIS POINT, BITMAP IS NOT TO BE BROKEN UP HORIZONTALLY OR VERTICALLY */ + /* (IT CAN STILL BE FULLY JUSTIFIED IF ALLOWED.) */ + + /* + ** Scale region to fit the destination device width and add to the master bitmap. + ** + ** + ** Start by copying source region to new bitmap + ** + */ +// printf("c1=%d\n",newregion->c1); + /* Is it a figure? */ + tall_region = (double) (newregion->r2 - newregion->r1 + 1) / src_dpi + >= dst_min_figure_height_in; + /* Re-trim left and right? */ + if ((trim_flags & 0x80) == 0) { + /* If tall region and figure justification turned on ... */ + if ((tall_region && dst_figure_justify >= 0) + /* ... or if centered region ... */ + || ((trim_flags & 3) != 3 + && ((justification_flags & 3) == 1 + || ((justification_flags & 3) == 3 + && (dst_justify == 1 + || (dst_justify < 0 + && (justification_flags + & 0xc) == 4)))))) { + bmpregion_trim_margins(newregion, colcount, rowcount, 0x3); + nc = newregion->c2 - newregion->c1 + 1; + region_width_inches = (double) nc / src_dpi; + } + } +#if (WILLUSDEBUGX & 1) + aprintf("atomic region: " ANSI_CYAN "%.2f x %.2f in" ANSI_NORMAL " c1=%d, (%d x %d) (rbdel=%d) just=0x%02X\n", + (double)(newregion->c2-newregion->c1+1)/src_dpi, + (double)(newregion->r2-newregion->r1+1)/src_dpi, + newregion->c1, + (newregion->c2-newregion->c1+1), + (newregion->r2-newregion->r1+1), + rowbase_delta,justification_flags); +#endif + /* Copy atomic region into bmp */ + bmp = &_bmp; + bmp_init(bmp); + bmp->width = nc; + bmp->height = nr; + if (dst_color) + bmp->bpp = 24; + else { + bmp->bpp = 8; + for (i = 0; i < 256; i++) + bmp->red[i] = bmp->blue[i] = bmp->green[i] = i; + } + bmp_alloc(bmp); + bpp = dst_color ? 3 : 1; +// printf("r1=%d, r2=%d\n",newregion->r1,newregion->r2); + for (i = newregion->r1; i <= newregion->r2; i++) { + unsigned char *psrc, *pdst; + + pdst = bmp_rowptr_from_top(bmp, i - newregion->r1); + psrc = bmp_rowptr_from_top(dst_color ? newregion->bmp : newregion->bmp8, + i) + bpp * newregion->c1; + memcpy(pdst, psrc, nc * bpp); + } + /* + ** Now scale to appropriate destination size. + ** + ** force_scale is used to maintain uniform scaling so that + ** most of the regions are scaled at the same value. + ** + ** force_scale = -2.0 : Fit column width to display width + ** force_scale = -1.0 : Use output dpi unless the region doesn't fit. + ** In that case, scale it down until it fits. + ** force_scale > 0.0 : Scale region by force_scale. + ** + */ + /* Max viewable pixel width on device screen */ + wmax = (int) (masterinfo->bmp.width - (dst_marleft + dst_marright) * dst_dpi + + 0.5); + if (force_scale > 0.) + w = (int) (force_scale * bmp->width + 0.5); + else { + if (region_width_inches < max_region_width_inches) + w = (int) (region_width_inches * dst_dpi + .5); + else + w = wmax; + } + /* Special processing for tall regions (likely figures) */ + if (tall_region && w < wmax && dst_fit_to_page != 0) { + if (dst_fit_to_page < 0) + w = wmax; + else { + w = (int) (w * (1. + (double) dst_fit_to_page / 100.) + 0.5); + if (w > wmax) + w = wmax; + } + } + h = (int) (((double) w / bmp->width) * bmp->height + .5); + + /* + ** If scaled dimensions are finite, add to master bitmap. + */ + if (w > 0 && h > 0) { + WILLUSBITMAP *tmp, _tmp; + int nocr; + + last_scale_factor_internal = (double) w / bmp->width; +#ifdef HAVE_OCR + if (dst_ocr) + { + nocr=(int)((double)bmp->width/w+0.5); + if (nocr < 1) + nocr=1; + if (nocr > 10) + nocr=10; + w *= nocr; + h *= nocr; + } + else +#endif + nocr = 1; + tmp = &_tmp; + bmp_init(tmp); + bmp_resample(tmp, bmp, (double) 0., (double) 0., (double) bmp->width, + (double) bmp->height, w, h); + bmp_free(bmp); + /* + { + static int nn=0; + char filename[256]; + sprintf(filename,"xxx%02d.png",nn++); + bmp_write(tmp,filename,stdout,100); + } + */ + /* + ** Add scaled bitmap to destination. + */ + /* Allocate more rows if necessary */ + while (masterinfo->rows + tmp->height / nocr > masterinfo->bmp.height) + bmp_more_rows(&masterinfo->bmp, 1.4, 255); + /* Check special justification for tall regions */ + if (tall_region && dst_figure_justify >= 0) + justification_flags = dst_figure_justify; + bmp_src_to_dst(masterinfo, tmp, justification_flags, region->bgcolor, + nocr, (int) ((double) src_dpi * tmp->width / bmp->width + .5)); + bmp_free(tmp); + } + + /* Store delta to base of text row (used by wrapbmp_flush()) */ + last_rowbase_internal = rowbase_delta; + /* .05 was .072 in v1.35 */ + /* dst_add_gap(&masterinfo->bmp,&masterinfo->rows,0.05); */ + /* + if (revert) + restore_output_dpi(); + */ +} + +static void dst_add_gap_src_pixels(char *caller, MASTERINFO *masterinfo, + int pixels) + +{ + double gap_inches; + + /* + aprintf("%s " ANSI_GREEN "dst_add" ANSI_NORMAL " %.3f in (%d pix)\n",caller,(double)pixels/src_dpi,pixels); + */ + if (last_scale_factor_internal < 0.) + gap_inches = (double) pixels / src_dpi; + else + gap_inches = (double) pixels * last_scale_factor_internal / dst_dpi; + gap_inches *= vertical_multiplier; + if (gap_inches > max_vertical_gap_inches) + gap_inches = max_vertical_gap_inches; + dst_add_gap(masterinfo, gap_inches); +} + +static void dst_add_gap(MASTERINFO *masterinfo, double inches) + +{ + int n, bw; + unsigned char *p; + + n = (int) (inches * dst_dpi + .5); + if (n < 1) + n = 1; + while (masterinfo->rows + n > masterinfo->bmp.height) + bmp_more_rows(&masterinfo->bmp, 1.4, 255); + bw = bmp_bytewidth(&masterinfo->bmp) * n; + p = bmp_rowptr_from_top(&masterinfo->bmp, masterinfo->rows); + memset(p, 255, bw); + masterinfo->rows += n; +} + +/* + ** + ** Add already-scaled source bmp to destination bmp. + ** Source bmp may be narrower than destination--if so, it may be fully justifed. + ** dst = destination bitmap + ** src = source bitmap + ** dst and src bpp must match! + ** All rows of src are applied to masterinfo->bmp starting at row masterinfo->rows + ** Full justification is done if requested. + ** + */ +static void bmp_src_to_dst(MASTERINFO *masterinfo, WILLUSBITMAP *src, + int justification_flags, int whitethresh, int nocr, int dpi) + +{ + WILLUSBITMAP *src1, _src1; + WILLUSBITMAP *tmp; +#ifdef HAVE_OCR + WILLUSBITMAP _tmp; + OCRWORDS _words,*words; +#endif + int dw, dw2; + int i, srcbytespp, srcbytewidth, go_full; + int destwidth, destx0, just; + + if (src->width <= 0 || src->height <= 0) + return; + /* + printf("@bmp_src_to_dst. dst->bpp=%d, src->bpp=%d, src=%d x %d\n",masterinfo->bmp.bpp,src->bpp,src->width,src->height); + */ + /* + { + static int count=0; + static char filename[256]; + + printf(" @bmp_src_to_dst...\n"); + sprintf(filename,"src%05d.png",count++); + bmp_write(src,filename,stdout,100); + } + */ + /* + if (fulljust && dst_fulljustify) + printf("srcbytespp=%d, srcbytewidth=%d, destwidth=%d, destx0=%d, destbytewidth=%d\n", + srcbytespp,srcbytewidth,destwidth,destx0,dstbytewidth); + */ + + /* Determine what justification to use */ + /* Left? */ + if ((justification_flags & 3) == 0 /* Mandatory left just */ + || ((justification_flags & 3) == 3 /* Use user settings */ + && (dst_justify == 0 + || (dst_justify < 0 + && (justification_flags & 0xc) == 0)))) + just = 0; + else if ((justification_flags & 3) == 2 + || ((justification_flags & 3) == 3 + && (dst_justify == 2 + || (dst_justify < 0 + && (justification_flags & 0xc) == 8)))) + just = 2; + else + just = 1; + + /* Full justification? */ + destwidth = (int) (masterinfo->bmp.width + - (dst_marleft + dst_marright) * dst_dpi + .5); + go_full = (destwidth * nocr > src->width + && (((justification_flags & 0x30) == 0x10) + || ((justification_flags & 0x30) == 0 // Use user settings + && (dst_fulljustify == 1 + || (dst_fulljustify < 0 + && (justification_flags & 0xc0) + == 0x40))))); + + /* Put fully justified text into src1 bitmap */ + if (go_full) { + src1 = &_src1; + bmp_init(src1); + bmp_fully_justify(src1, src, nocr * destwidth, whitethresh, just); + } else + src1 = src; + +#if (WILLUSDEBUGX & 1) + printf("@bmp_src_to_dst: jflags=0x%02X just=%d, go_full=%d\n",justification_flags,just,go_full); + printf(" destx0=%d, destwidth=%d, src->width=%d\n",destx0,destwidth,src->width); +#endif +#ifdef HAVE_OCR + if (dst_ocr) + { + /* Run OCR on the bitmap */ + words=&_words; + ocrwords_init(words); + ocrwords_fill_in(words,src1,whitethresh,dpi); + /* Scale bitmap and word positions to destination size */ + if (nocr>1) + { + tmp=&_tmp; + bmp_init(tmp); + bmp_integer_resample(tmp,src1,nocr); + ocrwords_int_scale(words,nocr); + } + else + tmp=src1; + } + else +#endif + tmp = src1; + /* + printf("writing...\n"); + ocrwords_box(words,tmp); + bmp_write(tmp,"out.png",stdout,100); + exit(10); + */ + destx0 = (int) (dst_marleft * dst_dpi + .5); + if (just == 0) + dw = destx0; + else if (just == 1) + dw = destx0 + (destwidth - tmp->width) / 2; + else + dw = destx0 + destwidth - tmp->width; + if (dw < 0) + dw = 0; + /* Add OCR words to destination list */ +#ifdef HAVE_OCR + if (dst_ocr) + { + ocrwords_offset(words,dw,masterinfo->rows); + ocrwords_concatenate(dst_ocrwords,words); + ocrwords_free(words); + } +#endif + + /* Add tmp bitmap to dst */ + srcbytespp = tmp->bpp == 24 ? 3 : 1; + srcbytewidth = tmp->width * srcbytespp; + dw2 = masterinfo->bmp.width - tmp->width - dw; + dw *= srcbytespp; + dw2 *= srcbytespp; + for (i = 0; i < tmp->height; i++, masterinfo->rows++) { + unsigned char *pdst, *psrc; + + psrc = bmp_rowptr_from_top(tmp, i); + pdst = bmp_rowptr_from_top(&masterinfo->bmp, masterinfo->rows); + memset(pdst, 255, dw); + pdst += dw; + memcpy(pdst, psrc, srcbytewidth); + pdst += srcbytewidth; + memset(pdst, 255, dw2); + } + +#ifdef HAVE_OCR + if (dst_ocr && nocr>1) + bmp_free(tmp); +#endif + if (go_full) + bmp_free(src1); +} + +/* + ** Spread words out in src and put into jbmp at scaling nocr + ** In case the text can't be expanded enough, + ** just=0 (left justify), 1 (center), 2 (right justify) + */ +static void bmp_fully_justify(WILLUSBITMAP *jbmp, WILLUSBITMAP *src, + int jbmpwidth, int whitethresh, int just) + +{ + BMPREGION srcregion; + BREAKINFO *colbreaks, _colbreaks; + WILLUSBITMAP gray; + int *gappos, *gapsize; + int i, srcbytespp, srcbytewidth, jbmpbytewidth, newwidth, destx0, ng; + static char *funcname = "bmp_fully_justify"; + + /* + { + char filename[256]; + count++; + sprintf(filename,"out%03d.png",count); + bmp_write(src,filename,stdout,100); + } + */ + /* Init/allocate destination bitmap */ + jbmp->width = jbmpwidth; + jbmp->height = src->height; + jbmp->bpp = src->bpp; + if (jbmp->bpp == 8) + for (i = 0; i < 256; i++) + jbmp->red[i] = jbmp->green[i] = jbmp->blue[i] = i; + bmp_alloc(jbmp); + + /* Find breaks in the text row */ + colbreaks = &_colbreaks; + colbreaks->textrow = NULL; + srcregion.bgcolor = whitethresh; + srcregion.c1 = 0; + srcregion.c2 = src->width - 1; + srcregion.r1 = 0; + srcregion.r2 = src->height - 1; + srcbytespp = src->bpp == 24 ? 3 : 1; + if (srcbytespp == 3) { + srcregion.bmp = src; + srcregion.bmp8 = &gray; + bmp_init(srcregion.bmp8); + bmp_convert_to_greyscale_ex(srcregion.bmp8, src); + } else { + srcregion.bmp = src; + srcregion.bmp8 = src; + } + breakinfo_alloc(103, colbreaks, src->width); + { + int *colcount, *rowcount; + + colcount = rowcount = NULL; + willus_dmem_alloc_warn(8, (void **) &colcount, + sizeof(int) * (src->width + src->height), funcname, 10); + rowcount = &colcount[src->width]; + bmpregion_one_row_find_breaks(&srcregion, colbreaks, colcount, rowcount, + 1); + willus_dmem_free(8, (double **) &colcount, funcname); + } + if (srcbytespp == 3) + bmp_free(srcregion.bmp8); + ng = colbreaks->n - 1; + gappos = NULL; + if (ng > 0) { + int maxsize, ms2, mingap, j; + + willus_dmem_alloc_warn(9, (void **) &gappos, (2 * sizeof(int)) * ng, + funcname, 10); + gapsize = &gappos[ng]; + for (i = 0; i < ng; i++) { + gappos[i] = colbreaks->textrow[i].c2 + 1; + gapsize[i] = colbreaks->textrow[i].gap; + } + + /* Take only the largest group of gaps */ + for (maxsize = i = 0; i < ng; i++) + if (maxsize < gapsize[i]) + maxsize = gapsize[i]; + mingap = srcregion.lcheight * word_spacing; + if (mingap < 2) + mingap = 2; + if (maxsize > mingap) + maxsize = mingap; + ms2 = maxsize / 2; + for (i = j = 0; i < ng; i++) + if (gapsize[i] > ms2) { + if (j != i) { + gapsize[j] = gapsize[i]; + gappos[j] = gappos[i]; + } + j++; + } + ng = j; + + /* Figure out total pixel expansion */ + newwidth = src->width * 1.25; + if (newwidth > jbmp->width) + newwidth = jbmp->width; + } else + newwidth = src->width; + breakinfo_free(103, colbreaks); + + /* Starting column in destination bitmap */ + if (just == 1) + destx0 = (jbmp->width - newwidth) / 2; + else if (just == 2) + destx0 = (jbmp->width - newwidth); + else + destx0 = 0; + + jbmpbytewidth = bmp_bytewidth(jbmp); + srcbytewidth = bmp_bytewidth(src); + + /* Clear entire fully justified bitmap */ + memset(bmp_rowptr_from_top(jbmp, 0), 255, jbmpbytewidth * jbmp->height); + + /* Spread out source pieces to fully justify them */ + for (i = 0; i <= ng; i++) { + int j, dx0, dx, sx0; + unsigned char *pdst, *psrc; + + dx = i < ng ? + (i > 0 ? gappos[i] - gappos[i - 1] : gappos[i] + 1) : + (i > 0 ? src->width - (gappos[i - 1] + 1) : src->width); + dx *= srcbytespp; + sx0 = i == 0 ? 0 : (gappos[i - 1] + 1); + dx0 = destx0 + sx0 + (i == 0 ? 0 : (newwidth - src->width) * i / ng); + psrc = bmp_rowptr_from_top(src, 0) + sx0 * srcbytespp; + pdst = bmp_rowptr_from_top(jbmp, 0) + dx0 * srcbytespp; + for (j = 0; j < src->height; j++, pdst += jbmpbytewidth, psrc += + srcbytewidth) + memcpy(pdst, psrc, dx); + } + if (gappos != NULL) + willus_dmem_free(9, (double **) &gappos, funcname); +} + +/* + ** flags&1 : trim c1 + ** flags&2 : trim c2 + ** flags&4 : trim r1 + ** flags&8 : trim r2 + ** flags&16 : Find rowbase, font size, etc. + ** + ** Row base is where row dist crosses 50% on r2 side. + ** Font size is where row dist crosses 5% on other side (r1 side). + ** Lowercase font size is where row dist crosses 50% on r1 side. + ** + ** For 12 pt font: + ** Single spacing is 14.66 pts (Calibri), 13.82 pts (Times), 13.81 pts (Arial) + ** Size of cap letter is 7.7 pts (Calibri), 8.1 pts (Times), 8.7 pts (Arial) + ** Size of small letter is 5.7 pts (Calibri), 5.6 pts (Times), 6.5 pts (Arial) + ** Mean line spacing = 1.15 - 1.22 (~1.16) + ** Mean cap height = 0.68 + ** Mean small letter height = 0.49 + ** + */ +static void bmpregion_trim_margins(BMPREGION *region, int *colcount0, + int *rowcount0, int flags) + +{ + int i, j, n; /* ,r1,r2,dr1,dr2,dr,vtrim,vspace; */ + int *colcount, *rowcount; + static char *funcname = "bmpregion_trim_margins"; + + /* To detect a hyphen, we need to trim and calc text base row */ + if (flags & 32) + flags |= 0x1f; + if (colcount0 == NULL) + willus_dmem_alloc_warn(10, (void **) &colcount, + sizeof(int) * (region->c2 + 1), funcname, 10); + else + colcount = colcount0; + if (rowcount0 == NULL) + willus_dmem_alloc_warn(11, (void **) &rowcount, + sizeof(int) * (region->r2 + 1), funcname, 10); + else + rowcount = rowcount0; + n = region->c2 - region->c1 + 1; + /* + printf("Trim: reg=(%d,%d) - (%d,%d)\n",region->c1,region->r1,region->c2,region->r2); + if (region->c2+1 > cca || region->r2+1 > rca) + { + printf("A ha 0!\n"); + exit(10); + } + */ + memset(colcount, 0, (region->c2 + 1) * sizeof(int)); + memset(rowcount, 0, (region->r2 + 1) * sizeof(int)); + for (j = region->r1; j <= region->r2; j++) { + unsigned char *p; + p = bmp_rowptr_from_top(region->bmp8, j) + region->c1; + for (i = 0; i < n; i++, p++) + if (p[0] < region->bgcolor) { + rowcount[j]++; + colcount[i + region->c1]++; + } + } + /* + ** Trim excess margins + */ + if (flags & 1) + trim_to(colcount, ®ion->c1, region->c2, + src_left_to_right ? 2.0 : 4.0); + if (flags & 2) + trim_to(colcount, ®ion->c2, region->c1, + src_left_to_right ? 4.0 : 2.0); + if (colcount0 == NULL) + willus_dmem_free(10, (double **) &colcount, funcname); + if (flags & 4) + trim_to(rowcount, ®ion->r1, region->r2, 4.0); + if (flags & 8) + trim_to(rowcount, ®ion->r2, region->r1, 4.0); + if (flags & 16) { + int maxcount, mc2, h2; + double f; + + maxcount = 0; + for (i = region->r1; i <= region->r2; i++) + if (rowcount[i] > maxcount) + maxcount = rowcount[i]; + mc2 = maxcount / 2; + for (i = region->r2; i >= region->r1; i--) + if (rowcount[i] > mc2) + break; + region->rowbase = i; + for (i = region->r1; i <= region->r2; i++) + if (rowcount[i] > mc2) + break; + region->h5050 = region->lcheight = region->rowbase - i + 1; + mc2 = maxcount / 20; + for (i = region->r1; i <= region->r2; i++) + if (rowcount[i] > mc2) + break; + region->capheight = region->rowbase - i + 1; + /* + ** Sanity check capheight and lcheight + */ + h2 = height2_calc(&rowcount[region->r1], region->r2 - region->r1 + 1); +#if (WILLUSDEBUGX & 8) + if (region->c2-region->c1 > 1500) + printf("reg %d x %d (%d,%d) - (%d,%d) h2=%d ch/h2=%g\n",region->c2-region->c1+1,region->r2-region->r1+1,region->c1,region->r1,region->c2,region->r2,h2,(double)region->capheight/h2); +#endif + if (region->capheight < h2 * 0.75) + region->capheight = h2; + f = (double) region->lcheight / region->capheight; + if (f < 0.55) + region->lcheight = (int) (0.72 * region->capheight + .5); + else if (f > 0.85) + region->lcheight = (int) (0.72 * region->capheight + .5); +#if (WILLUSDEBUGX & 8) + if (region->c2-region->c1 > 1500) + printf(" lcheight final = %d\n",region->lcheight); +#endif +#if (WILLUSDEBUGX & 10) + if (region->c2-region->c1 > 1500 && region->r2-region->r1 < 100) + { + static int append=0; + FILE *f; + int i; + f=fopen("textrows.ep",append==0?"w":"a"); + append=1; + for (i=region->r1;i<=region->r2;i++) + fprintf(f,"%d %g\n",region->rowbase-i,(double)rowcount[i]/maxcount); + fprintf(f,"//nc\n"); + fclose(f); + } +#endif + } else { + region->h5050 = region->r2 - region->r1 + 1; + region->capheight = 0.68 * (region->r2 - region->r1 + 1); + region->lcheight = 0.5 * (region->r2 - region->r1 + 1); + region->rowbase = region->r2; + } +#if (WILLUSDEBUGX & 2) + printf("trim:\n reg->c1=%d, reg->c2=%d\n",region->c1,region->c2); + printf(" reg->r1=%d, reg->r2=%d, reg->rowbase=%d\n\n",region->r1,region->r2,region->rowbase); +#endif + if (rowcount0 == NULL) + willus_dmem_free(11, (double **) &rowcount, funcname); +} + +/* + ** Does region end in a hyphen? If so, fill in HYPHENINFO structure. + */ +static void bmpregion_hyphen_detect(BMPREGION *region) + +{ + int i, j; /* ,r1,r2,dr1,dr2,dr,vtrim,vspace; */ + int width; + int *r0, *r1, *r2, *r3; + int rmin, rmax, rowbytes, nrmid, rsum; + int cstart, cend, cdir; + unsigned char *p; + static char *funcname = "bmpregion_hyphen_detect"; + +#if (WILLUSDEBUGX & 16) + static int count=0; + char pngfile[256]; + FILE *out; + + count++; + printf("@bmpregion_hyphen_detect count=%d\n",count); + sprintf(pngfile,"word%04d.png",count); + bmpregion_write(region,pngfile); + sprintf(pngfile,"word%04d.txt",count); + out=fopen(pngfile,"w"); + fprintf(out,"c1=%d, c2=%d, r1=%d, r2=%d\n",region->c1,region->c2,region->r1,region->r2); + fprintf(out,"lcheight=%d\n",region->lcheight); +#endif + + region->hyphen.ch = -1; + region->hyphen.c2 = -1; + if (!k2_hyphen_detect) + return; + width = region->c2 - region->c1 + 1; + if (width < 2) + return; + willus_dmem_alloc_warn(27, (void **) &r0, sizeof(int) * 4 * width, funcname, + 10); + r1 = &r0[width]; + r2 = &r1[width]; + r3 = &r2[width]; + for (i = 0; i < width; i++) + r0[i] = r1[i] = r2[i] = r3[i] = -1; + rmin = region->rowbase - region->capheight - region->lcheight * .04; + if (rmin < region->r1) + rmin = region->r1; + rmax = region->rowbase + region->lcheight * .04; + if (rmax > region->r2) + rmax = region->r2; + rowbytes = bmp_bytewidth(region->bmp8); + p = bmp_rowptr_from_top(region->bmp8, 0); + nrmid = rsum = 0; + if (src_left_to_right) { + cstart = region->c2; + cend = region->c1 - 1; + cdir = -1; + } else { + cstart = region->c1; + cend = region->c2 + 1; + cdir = 1; + } +#if (WILLUSDEBUGX & 16) + fprintf(out," j r0 r1 r2 r3\n"); +#endif + for (j = cstart; j != cend; j += cdir) { + int r, rmid, dr, drmax; + +// printf("j=%d\n",j); + rmid = (rmin + rmax) / 2; +// printf(" rmid=%d\n",rmid); + drmax = region->r2 + 1 - rmid > rmid - region->r1 + 1 ? + region->r2 + 1 - rmid : rmid - region->r1 + 1; + /* Find dark region closest to center line */ + for (dr = 0; dr < drmax; dr++) { + if (rmid + dr <= region->r2 + && p[(rmid + dr) * rowbytes + j] < region->bgcolor) + break; + if (rmid - dr >= region->r1 + && p[(rmid - dr) * rowbytes + j] < region->bgcolor) { + dr = -dr; + break; + } + } +#if (WILLUSDEBUGX & 16) + fprintf(out," dr=%d/%d, rmid+dr=%d, rmin=%d, rmax=%d, nrmid=%d\n",dr,drmax,rmid+dr,rmin,rmax,nrmid); +#endif + /* No dark detected or mark is outside hyphen region? */ + /* Termination criterion #1 */ + if (dr >= drmax + || (nrmid > 2 && (double) nrmid / region->lcheight > .1 + && (rmid + dr < rmin || rmid + dr > rmax))) { + if (region->hyphen.ch >= 0 && dr >= drmax) + continue; + if (nrmid > 2 && (double) nrmid / region->lcheight > .35) { + region->hyphen.ch = j - cdir; + region->hyphen.r1 = rmin; + region->hyphen.r2 = rmax; + } + if (dr < drmax) { + region->hyphen.c2 = j; + break; + } + continue; + } + if (region->hyphen.ch >= 0) { + region->hyphen.c2 = j; + break; + } + nrmid++; + rmid += dr; + /* Dark spot is outside expected hyphen area */ + /* + if (rmidrmax) + { + if (nrmid>0) + break; + continue; + } + */ + for (r = rmid; r >= region->r1; r--) + if (p[r * rowbytes + j] >= region->bgcolor) + break; + r1[j - region->c1] = r + 1; + r0[j - region->c1] = -1; + if (r >= region->r1) { + for (; r >= region->r1; r--) + if (p[r * rowbytes + j] < region->bgcolor) + break; + if (r >= region->r1) + r0[j - region->c1] = r; + } + for (r = rmid; r <= region->r2; r++) + if (p[r * rowbytes + j] >= region->bgcolor) + break; + r2[j - region->c1] = r - 1; + r3[j - region->c1] = -1; + if (r <= region->r2) { + for (; r <= region->r2; r++) + if (p[r * rowbytes + j] < region->bgcolor) + break; + if (r <= region->r2) + r3[j - region->c1] = r; + } +#if (WILLUSDEBUGX & 16) + fprintf(out," %4d %4d %4d %4d %4d\n",j,r0[j-region->c1],r1[j-region->c1],r2[j-region->c1],r3[j-region->c1]); +#endif + if (region->hyphen.c2 < 0 + && (r0[j - region->c1] >= 0 || r3[j - region->c1] >= 0)) + region->hyphen.c2 = j; + /* Termination criterion #2 */ + if (nrmid > 2 && (double) nrmid / region->lcheight > .35 + && (r1[j - region->c1] > rmax || r2[j - region->c1] < rmin)) { + region->hyphen.ch = j - cdir; + region->hyphen.r1 = rmin; + region->hyphen.r2 = rmax; + if (region->hyphen.c2 < 0) + region->hyphen.c2 = j; + break; + } + // rc=(r1[j-region->c1]+r2[j-region->c1])/2; + /* DQ possible hyphen if r1/r2 out of range */ + if (nrmid > 1) { + /* Too far away from last values? */ + if ((double) (rmin - r1[j - region->c1]) / region->lcheight > .1 + || (double) (r2[j - region->c1] - rmax) / region->lcheight + > .1) + break; + if ((double) nrmid / region->lcheight > .1 && nrmid > 1) { + if ((double) fabs(rmin - r1[j - region->c1]) / region->lcheight + > .1 + || (double) (rmax - r2[j - region->c1]) + / region->lcheight > .1) + break; + } + } + if (nrmid == 1 || r1[j - region->c1] < rmin) + rmin = r1[j - region->c1]; + if (nrmid == 1 || r2[j - region->c1] > rmax) + rmax = r2[j - region->c1]; + if ((double) nrmid / region->lcheight > .1 && nrmid > 1) { + double rmean; + + /* Can't be too thick */ + if ((double) (rmax - rmin) / region->lcheight > .55 + || (double) (rmax - rmin) / region->lcheight < .08) + break; + /* Must be reasonably well centered above baseline */ + rmean = (double) (rmax + rmin) / 2; + if ((double) (region->rowbase - rmean) / region->lcheight < 0.35 + || (double) (region->rowbase - rmean) / region->lcheight + > 0.85) + break; + if ((double) (region->rowbase - rmax) / region->lcheight < 0.2 + || (double) (region->rowbase - rmin) / region->lcheight + > 0.92) + break; + } + } +#if (WILLUSDEBUGX & 16) + fprintf(out," ch=%d, c2=%d, r1=%d, r2=%d\n",region->hyphen.ch,region->hyphen.c2,region->hyphen.r1,region->hyphen.r2); + fclose(out); +#endif + /* More sanity checks--better to miss a hyphen than falsely detect it. */ + if (region->hyphen.ch >= 0) { + double ar; + /* If it's only a hyphen, then it's probably actually a dash--don't detect it. */ + if (region->hyphen.c2 < 0) + region->hyphen.ch = -1; + /* Check aspect ratio */ + ar = (double) (region->hyphen.r2 - region->hyphen.r1) / nrmid; + if (ar < 0.08 || ar > 0.75) + region->hyphen.ch = -1; + } + willus_dmem_free(27, (double **) &r0, funcname); +#if (WILLUSDEBUGX & 16) + if (region->hyphen.ch>=0) + printf("\n\n GOT HYPHEN.\n\n"); + printf(" Exiting bmpregion_hyphen_detect\n"); +#endif +} + +#if (defined(WILLUSDEBUGX) || defined(WILLUSDEBUG)) +static void bmpregion_write(BMPREGION *region,char *filename) + +{ + int i,bpp; + WILLUSBITMAP *bmp,_bmp; + + bmp=&_bmp; + bmp_init(bmp); + bmp->width=region->c2-region->c1+1; + bmp->height=region->r2-region->r1+1; + bmp->bpp=region->bmp->bpp; + bpp=bmp->bpp==8?1:3; + bmp_alloc(bmp); + for (i=0;i<256;i++) + bmp->red[i]=bmp->green[i]=bmp->blue[i]=i; + for (i=0;iheight;i++) + { + unsigned char *s,*d; + s=bmp_rowptr_from_top(region->bmp,region->r1+i)+region->c1*bpp; + d=bmp_rowptr_from_top(bmp,i); + memcpy(d,s,bmp->width*bpp); + } + bmp_write(bmp,filename,stdout,97); + bmp_free(bmp); +} +#endif + +#if (WILLUSDEBUGX & 6) +static void breakinfo_echo(BREAKINFO *breakinfo) + +{ + int i; + printf("@breakinfo_echo...\n"); + for (i=0;in;i++) + printf(" %2d. r1=%4d, rowbase=%4d, r2=%4d, c1=%4d, c2=%4d\n", + i+1,breakinfo->textrow[i].r1, + breakinfo->textrow[i].rowbase, + breakinfo->textrow[i].r2, + breakinfo->textrow[i].c1, + breakinfo->textrow[i].c2); +} +#endif + +/* + ** Calculate weighted height of a rectangular region. + ** This weighted height is intended to be close to the height of + ** a capital letter, or the height of the majority of the region. + ** + */ +static int height2_calc(int *rc, int n) + +{ + int i, thresh, i1, h2; + int *c; + static char *funcname = "height2_calc"; +#if (WILLUSDEBUGX & 8) + int cmax; +#endif + + if (n <= 0) + return (1); + willus_dmem_alloc_warn(12, (void **) &c, sizeof(int) * n, funcname, 10); + memcpy(c, rc, n * sizeof(int)); + sorti(c, n); +#if (WILLUSDEBUGX & 8) + cmax=c[n-1]; +#endif + for (i = 0; i < n - 1 && c[i] == 0; i++) + ; + thresh = c[(i + n) / 3]; + willus_dmem_free(12, (double **) &c, funcname); + for (i = 0; i < n - 1; i++) + if (rc[i] >= thresh) + break; + i1 = i; + for (i = n - 1; i > i1; i--) + if (rc[i] >= thresh) + break; +#if (WILLUSDEBUGX & 8) +// printf("thresh = %g, i1=%d, i2=%d\n",(double)thresh/cmax,i1,i); +#endif + h2 = i - i1 + 1; /* Guaranteed to be >=1 */ + return (h2); +} + +static void trim_to(int *count, int *i1, int i2, double gaplen) + +{ + int del, dcount, igaplen, clevel, dlevel, defect_start, last_defect; + + igaplen = (int) (gaplen * src_dpi / 72.); + if (igaplen < 1) + igaplen = 1; + /* clevel=(int)(defect_size_pts*src_dpi/72./3.); */ + clevel = 0; + dlevel = (int) (pow(defect_size_pts * src_dpi / 72., 2.) * PI / 4. + .5); + del = i2 > (*i1) ? 1 : -1; + defect_start = -1; + last_defect = -1; + dcount = 0; + for (; (*i1) != i2; (*i1) = (*i1) + del) { + if (count[(*i1)] <= clevel) { + dcount = 0; /* Reset defect size */ + continue; + } + /* Mark found */ + if (dcount == 0) { + if (defect_start >= 0) + last_defect = defect_start; + defect_start = (*i1); + } + dcount += count[(*i1)]; + if (dcount >= dlevel) { + if (last_defect >= 0 && abs(defect_start - last_defect) <= igaplen) + (*i1) = last_defect; + else + (*i1) = defect_start; + return; + } + } + if (defect_start < 0) + return; + if (last_defect < 0) { + (*i1) = defect_start; + return; + } + if (abs(defect_start - last_defect) <= igaplen) + (*i1) = last_defect; + else + (*i1) = defect_start; +} + +/* + ** A region that needs its line spacing and justification analyzed. + ** + ** The region may be wider than the max desirable region width. + ** + ** Input: breakinfo should be valid row-break information for the region. + ** + ** Calls bmpregion_one_row_wrap_and_add() for each text row from the + ** breakinfo structure that is within the region. + ** + */ +static void bmpregion_analyze_justification_and_line_spacing(BMPREGION *region, + BREAKINFO *breakinfo, MASTERINFO *masterinfo, int *colcount, + int *rowcount, PAGEINFO *pageinfo, int allow_text_wrapping, + double force_scale) + +{ + int i, i1, i2, ntr, mean_row_gap, maxgap, line_spacing, nls, nch; + BMPREGION *newregion, _newregion; + double *id, *c1, *c2, *ch, *lch, *ls; + int *just, *indented, *short_line; + double capheight, lcheight, fontsize; + int textheight, ragged_right, src_line_spacing; + static char *funcname = "bmpregion_analyze_justification_and_line_spacing"; + +#if (WILLUSDEBUGX & 1) + printf("@bmpregion_analyze_justification_and_line_spacing"); + printf(" (%d,%d) - (%d,%d)\n",region->c1,region->r1,region->c2,region->r2); + printf(" centering = %d\n",breakinfo->centered); +#endif +#if (WILLUSDEBUGX & 2) + breakinfo_echo(breakinfo); +#endif + + /* Locate the vertical part indices in the breakinfo structure */ + newregion = &_newregion; + breakinfo_sort_by_row_position(breakinfo); + for (i = 0; i < breakinfo->n; i++) { + TEXTROW *textrow; + textrow = &breakinfo->textrow[i]; + if ((textrow->r1 + textrow->r2) / 2 >= region->r1) + break; + } + if (i >= breakinfo->n) + return; + i1 = i; + for (; i < breakinfo->n; i++) { + TEXTROW *textrow; + textrow = &breakinfo->textrow[i]; + if ((textrow->r1 + textrow->r2) / 2 > region->r2) + break; + } + i2 = i - 1; + if (i2 < i1) + return; + ntr = i2 - i1 + 1; +#if (WILLUSDEBUGX & 1) + printf(" i1=%d, i2=%d, ntr=%d\n",i1,i2,ntr); +#endif + + willus_dmem_alloc_warn(13, (void **) &c1, sizeof(double) * 6 * ntr, + funcname, 10); + willus_dmem_alloc_warn(14, (void **) &just, sizeof(int) * 3 * ntr, funcname, + 10); + c2 = &c1[ntr]; + ch = &c2[ntr]; + lch = &ch[ntr]; + ls = &lch[ntr]; + id = &ls[ntr]; + indented = &just[ntr]; + short_line = &indented[ntr]; + for (i = 0; i < ntr; i++) + id[i] = i; + + /* Find baselines / font size */ + capheight = lcheight = 0.; + maxgap = -1; + for (nch = nls = 0, i = i1; i <= i2; i++) { + TEXTROW *textrow; + double ar, rh; + int marking_flags; + + textrow = &breakinfo->textrow[i]; + c1[i - i1] = (double) textrow->c1; + c2[i - i1] = (double) textrow->c2; + if (i < i2 && maxgap < textrow->gap) { + maxgap = textrow->gap; + if (maxgap < 2) + maxgap = 2; + } + if (textrow->c2 < textrow->c1) + ar = 100.; + else + ar = (double) (textrow->r2 - textrow->r1 + 1) + / (double) (textrow->c2 - textrow->c1 + 1); + rh = (double) (textrow->r2 - textrow->r1 + 1) / src_dpi; + if (i < i2 && ar <= no_wrap_ar_limit + && rh <= no_wrap_height_limit_inches) + ls[nls++] = breakinfo->textrow[i + 1].r1 - textrow->r1; + if (ar <= no_wrap_ar_limit && rh <= no_wrap_height_limit_inches) { + ch[nch] = textrow->capheight; + lch[nch] = textrow->lcheight; + nch++; + } + + /* Mark region w/gray, mark rowbase also */ + marking_flags = (i == i1 ? 0 : 1) | (i == i2 ? 0 : 2); + if (i < i2 || textrow->r2 - textrow->rowbase > 1) + marking_flags |= 0x10; + (*newregion) = (*region); + newregion->r1 = textrow->r1; + newregion->r2 = textrow->r2; + newregion->c1 = textrow->c1; + newregion->c2 = textrow->c2; + newregion->rowbase = textrow->rowbase; + mark_source_page(newregion, 5, marking_flags); +#if (WILLUSDEBUGX & 1) + printf(" Row %2d: (%4d,%4d) - (%4d,%4d) rowbase=%4d, lch=%d, h5050=%d, rh=%d\n",i-i1+1,textrow->c1,textrow->r1,textrow->c2,textrow->r2,textrow->rowbase,textrow->lcheight,textrow->h5050,textrow->rowheight); +#endif + } + wrapbmp_set_maxgap(maxgap); + if (nch < 1) + capheight = lcheight = 2; // Err on the side of too small + else { + capheight = median_val(ch, nch); + lcheight = median_val(lch, nch); + } +// printf("capheight = %g, lcheight = %g\n",capheight,lcheight); + bmpregion_is_centered(region, breakinfo, i1, i2, &textheight); + /* + ** For 12 pt font: + ** Single spacing is 14.66 pts (Calibri), 13.82 pts (Times), 13.81 pts (Arial) + ** Size of cap letter is 7.7 pts (Calibri), 8.1 pts (Times), 8.7 pts (Arial) + ** Size of small letter is 5.7 pts (Calibri), 5.6 pts (Times), 6.5 pts (Arial) + ** Mean line spacing = 1.15 - 1.22 (~1.16) + ** Mean cap height = 0.68 + ** Mean small letter height = 0.49 + */ + fontsize = (capheight + lcheight) / 1.17; +// printf("font size = %g pts.\n",(fontsize/src_dpi)*72.); + /* + ** Set line spacing for this region + */ + if (nls > 0) + src_line_spacing = median_val(ls, nls); + else + src_line_spacing = fontsize * 1.2; + if (vertical_line_spacing < 0 + && src_line_spacing + <= fabs(vertical_line_spacing) * fontsize * 1.16) + line_spacing = src_line_spacing; + else + line_spacing = fabs(vertical_line_spacing) * fontsize * 1.16; +#if (WILLUSDEBUGX & 1) + printf(" font size = %.2f pts = %d pixels\n",(fontsize/src_dpi)*72.,(int)(fontsize+.5)); + printf(" src_line_spacing = %d, line_spacing = %d\n",src_line_spacing,line_spacing); +#endif + /* + if (ntr==1) + rheight= (int)((breakinfo->textrow[i1].r2 - breakinfo->textrow[i1].r1)*1.25+.5); + else + rheight = (int)((double)(breakinfo->textrow[i2].rowbase - breakinfo->textrow[i1].rowbase)/(ntr-1)+.5); + */ + mean_row_gap = line_spacing - textheight; + if (mean_row_gap <= 1) + mean_row_gap = 1; + + /* Try to figure out if we have a ragged right edge */ + if (ntr < 3) + ragged_right = 1; + else { + int flushcount; + + if (src_left_to_right) { + for (flushcount = i = 0; i < ntr; i++) { +#if (WILLUSDEBUGX & 1) + printf(" flush_factors[%d] = %g (<.5), %g in (<.1)\n", + i,(double)(region->c2-c2[i])/textheight,(double)(region->c2-c2[i])/src_dpi); +#endif + if ((double) (region->c2 - c2[i]) / textheight < 0.5 + && (double) (region->c2 - c2[i]) / src_dpi < 0.1) + flushcount++; + } + } else { + for (flushcount = i = 0; i < ntr; i++) { +#if (WILLUSDEBUGX & 1) + printf(" flush_factors[%d] = %g (<.5), %g in (<.1)\n", + i,(double)(c1[i]-region->c1)/textheight,(double)(c1[i]-region->c1)/src_dpi); +#endif + if ((double) (c1[i] - region->c1) / textheight < 0.5 + && (double) (c1[i] - region->c1) / src_dpi < 0.1) + flushcount++; + } + } + ragged_right = (flushcount <= ntr / 2); + /* + if (src_left_to_right) + { + sortxyd(c2,id,ntr); + del = region->c2 - c2[ntr-1-ntr/3]; + sortxyd(id,c2,ntr); + } + else + { + sortxyd(c1,id,ntr); + del = c1[ntr/3] - region->c1; + sortxyd(id,c1,ntr); + } + del /= textheight; + printf("del=%g\n",del); + ragged_right = (del > 0.5); + */ + } +#if (WILLUSDEBUGX & 1) + printf("ragged_right=%d\n",ragged_right); +#endif + + /* Store justification and other info line by line */ + for (i = i1; i <= i2; i++) { + double indent1, del; + double i1f, ilfi, i2f, ilf, ifmin, dif; + int centered; + + TEXTROW *textrow; + textrow = &breakinfo->textrow[i]; + i1f = (double) (c1[i - i1] - region->c1) + / (region->c2 - region->c1 + 1); + i2f = (double) (region->c2 - c2[i - i1]) + / (region->c2 - region->c1 + 1); + ilf = src_left_to_right ? i1f : i2f; + ilfi = ilf * (region->c2 - region->c1 + 1) / src_dpi; /* Indent in inches */ + ifmin = i1f < i2f ? i1f : i2f; + dif = fabs(i1f - i2f); + if (ifmin < .01) + ifmin = 0.01; + if (src_left_to_right) + indent1 = (double) (c1[i - i1] - region->c1) / textheight; + else + indent1 = (double) (region->c2 - c2[i - i1]) / textheight; +// printf(" row %2d: indent1=%g\n",i-i1,indent1); + if (!breakinfo->centered) { + indented[i - i1] = (indent1 > 0.5 && ilfi < 1.2 && ilf < .25); + centered = + (!indented[i - i1] && indent1 > 1.0 && dif / ifmin < 0.5); + } else { + centered = (dif < 0.1 || dif / ifmin < 0.5); + indented[i - i1] = (indent1 > 0.5 && ilfi < 1.2 && ilf < .25 + && !centered); + } +#if (WILLUSDEBUGX & 1) + printf("Indent %d: %d. indent1=%g, ilf=%g, centered=%d\n",i-i1+1,indented[i-i1],indent1,ilf,centered); + printf(" indent1=%g, i1f=%g, i2f=%g\n",indent1,i1f,i2f); +#endif + if (centered) + just[i - i1] = 4; + else { + /* + ** The .01 favors left justification over right justification in + ** close cases. + */ + if (src_left_to_right) + just[i - i1] = indented[i - i1] || (i1f < i2f + .01) ? 0 : 8; + else + just[i - i1] = indented[i - i1] || (i2f < i1f + .01) ? 8 : 0; + } + if (src_left_to_right) + del = (double) (region->c2 - textrow->c2); + else + del = (double) (textrow->c1 - region->c1); + /* Should we keep wrapping after this line? */ + if (!ragged_right) + short_line[i - i1] = (del / textheight > 0.5); + else + short_line[i - i1] = (del / (region->c2 - region->c1) > 0.25); + /* If this row is a bigger/smaller row (font) than the next row, don't wrap. */ + if (!short_line[i - i1] && i < i2) { + TEXTROW *t1; + t1 = &breakinfo->textrow[i + 1]; + if ((textrow->h5050 > t1->h5050 * 1.5 + || textrow->h5050 * 1.5 < t1->h5050) + && (i == 0 + || (i > 0 + && (textrow->rowheight > t1->rowheight * 1.5 + || textrow->rowheight * 1.5 + < t1->rowheight)))) + short_line[i - i1] = 1; + } + if (!ragged_right) + just[i - i1] |= 0x40; +#if (WILLUSDEBUGX & 1) + printf(" just[%d]=0x%02X, shortline[%d]=%d\n",i-i1,just[i-i1],i-i1,short_line[i-i1]); + printf(" textrow->c2=%d, region->c2=%d, del=%g, textheight=%d\n",textrow->c2,region->c2,del,textheight); +#endif + /* If short line, it should still be fully justified if it is wrapped. */ + /* + if (short_line[i-i1]) + just[i-i1] = (just[i-i1]&0xf)|0x60; + */ + } + /* + { + double mean1,mean2,stdev1,stdev2; + array_mean(c1,ntr,&mean1,&stdev1); + array_mean(c2,ntr,&mean2,&stdev2); + printf("Mean c1, c2 = %g, %g; stddevs = %g, %g\n",mean1,mean2,stdev1,stdev2); + printf("textheight = %d, line_spacing = %d\n",textheight,line_spacing); + } + */ + for (i = i1; i <= i2; i++) { + TEXTROW *textrow; + int justflags, trimflags, centered, marking_flags, gap; + +#if (WILLUSDEBUGX & 1) + aprintf("Row " ANSI_YELLOW "%d of %d" ANSI_NORMAL " (wrap=%d)\n",i-i1+1,i2-i1+1,allow_text_wrapping); +#endif + textrow = &breakinfo->textrow[i]; + (*newregion) = (*region); + newregion->r1 = textrow->r1; + newregion->r2 = textrow->r2; + + /* The |3 tells it to use the user settings for left/right/center */ + justflags = just[i - i1] | 0x3; + centered = ((justflags & 0xc) == 4); +#if (WILLUSDEBUGX & 1) + printf(" justflags[%d]=0x%2X, centered=%d, indented=%d\n",i-i1,justflags,centered,indented[i-i1]); +#endif + if (allow_text_wrapping) { + /* If this line is indented or if the justification has changed, */ + /* then start a new line. */ + if (centered || indented[i - i1] + || (i > i1 + && (just[i - i1] & 0xc) != (just[i - i1 - 1] & 0xc))) { +#ifdef WILLUSDEBUG + printf("wrapflush4\n"); +#endif + wrapbmp_flush(masterinfo, 0, pageinfo, 1); + } +#ifdef WILLUSDEBUG + printf(" c1=%d, c2=%d\n",newregion->c1,newregion->c2); +#endif + marking_flags = 0xc | (i == i1 ? 0 : 1) | (i == i2 ? 0 : 2); + bmpregion_one_row_wrap_and_add(newregion, breakinfo, i, i1, i2, + masterinfo, justflags, colcount, rowcount, pageinfo, + line_spacing, mean_row_gap, textrow->rowbase, marking_flags, + indented[i - i1]); + if (centered || short_line[i - i1]) { +#ifdef WILLUSDEBUG + printf("wrapflush5\n"); +#endif + wrapbmp_flush(masterinfo, 0, pageinfo, 2); + } + continue; + } +#ifdef WILLUSDEBUG + printf("wrapflush5a\n"); +#endif + wrapbmp_flush(masterinfo, 0, pageinfo, 1); + /* If default justifications, ignore all analysis and just center it. */ + if (dst_justify < 0 && dst_fulljustify < 0) { + newregion->c1 = region->c1; + newregion->c2 = region->c2; + justflags = 0xad; /* Force centered region, no justification */ + trimflags = 0x80; + } else + trimflags = 0; + /* No wrapping: text wrap, trim flags, vert breaks, fscale, just */ + bmpregion_add(newregion, breakinfo, masterinfo, 0, trimflags, 0, + force_scale, justflags, 5, colcount, rowcount, pageinfo, 0, + textrow->r2 - textrow->rowbase); + if (vertical_line_spacing < 0) { + int gap1; + gap1 = line_spacing - (textrow->r2 - textrow->r1 + 1); + if (i < i2) + gap = textrow->gap > gap1 ? gap1 : textrow->gap; + else { + gap = textrow->rowheight + - (textrow->rowbase + last_rowbase_internal); + if (gap < mean_row_gap / 2.) + gap = mean_row_gap; + } + } else { + gap = line_spacing - (textrow->r2 - textrow->r1 + 1); + if (gap < mean_row_gap / 2.) + gap = mean_row_gap; + } + if (i < i2) + dst_add_gap_src_pixels("No-wrap line", masterinfo, gap); + else { + last_h5050_internal = textrow->h5050; + beginning_gap_internal = gap; + } + } + willus_dmem_free(14, (double **) &just, funcname); + willus_dmem_free(13, (double **) &c1, funcname); +#ifdef WILLUSDEBUG + printf("Done wrap_and_add.\n"); +#endif +} + +static int bmpregion_is_centered(BMPREGION *region, BREAKINFO *breakinfo, + int i1, int i2, int *th) + +{ + int j, i, cc, n1, ntr; + int textheight; + +#if (WILLUSDEBUGX & 1) + printf("@bmpregion_is_centered: region=(%d,%d) - (%d,%d)\n",region->c1,region->r1,region->c2,region->r2); + printf(" nrows = %d\n",i2-i1+1); +#endif + ntr = i2 - i1 + 1; + for (j = 0; j < 3; j++) { + for (n1 = textheight = 0, i = i1; i <= i2; i++) { + TEXTROW *textrow; + double ar, rh; + + textrow = &breakinfo->textrow[i]; + if (textrow->c2 < textrow->c1) + ar = 100.; + else + ar = (double) (textrow->r2 - textrow->r1 + 1) + / (double) (textrow->c2 - textrow->c1 + 1); + rh = (double) (textrow->r2 - textrow->r1 + 1) / src_dpi; + if (j == 2 || (j >= 1 && rh <= no_wrap_height_limit_inches) + || (j == 0 && rh <= no_wrap_height_limit_inches + && ar <= no_wrap_ar_limit)) { + textheight += textrow->rowbase - textrow->r1 + 1; + n1++; + } + } + if (n1 > 0) + break; + } + textheight = (int) ((double) textheight / n1 + .5); + if (th != NULL) { + (*th) = textheight; +#if (WILLUSDEBUGX & 1) + printf(" textheight assigned (%d)\n",textheight); +#endif + return (breakinfo->centered); + } + + /* + ** Does region appear to be centered? + */ + for (cc = 0, i = i1; i <= i2; i++) { + double indent1, indent2; + +#if (WILLUSDEBUGX & 1) + printf(" tr[%d].c1,c2 = %d, %d\n",i,breakinfo->textrow[i].c1,breakinfo->textrow[i].c2); +#endif + indent1 = (double) (breakinfo->textrow[i].c1 - region->c1) / textheight; + indent2 = (double) (region->c2 - breakinfo->textrow[i].c2) / textheight; +#if (WILLUSDEBUGX & 1) + printf(" tr[%d].indent1,2 = %g, %g\n",i,indent1,indent2); +#endif + /* If only one line and it spans the entire region, call it centered */ + /* Sometimes this won't be the right thing to to. */ + if (i1 == i2 && indent1 < .5 && indent2 < .5) { +#if (WILLUSDEBUGX & 1) + printf(" One line default to bigger region (%s).\n",breakinfo->centered?"not centered":"centered"); +#endif + return (1); + } + if (fabs(indent1 - indent2) > 1.5) { +#if (WILLUSDEBUGX & 1) + printf(" Region not centered.\n"); +#endif + return (0); + } + if (indent1 > 1.0) + cc++; + } +#if (WILLUSDEBUGX & 1) + printf("Region centering: i=%d, i2=%d, cc=%d, ntr=%d\n",i,i2,cc,ntr); +#endif + if (cc > ntr / 2) { +#if (WILLUSDEBUGX & 1) + printf(" Region is centered (enough obviously centered lines).\n"); +#endif + return (1); + } +#if (WILLUSDEBUGX & 1) + printf(" Not centered (not enough obviously centered lines).\n"); +#endif + return (0); +} + +/* array.c */ +/* + ** + ** Compute mean and standard deviation + ** + */ +double array_mean(double *a, int n, double *mean, double *stddev) + +{ + int i; + double sum, avg, sum_sq; + + if (n < 1) + return (0.); + for (sum = sum_sq = i = 0; i < n; i++) + sum += a[i]; + avg = sum / n; + if (mean != NULL) + (*mean) = avg; + if (stddev != NULL) { + double sum_sq; + + for (sum_sq = i = 0; i < n; i++) + sum_sq += (a[i] - avg) * (a[i] - avg); + (*stddev) = sqrt(sum_sq / n); + } + return (avg); +} + +/* + ** CAUTION: This function re-orders the x[] array! + */ +static double median_val(double *x, int n) + +{ + int i1, n1; + + if (n < 4) + return (array_mean(x, n, NULL, NULL)); + sortd(x, n); + if (n == 4) { + n1 = 2; + i1 = 1; + } else if (n == 5) { + n1 = 3; + i1 = 1; + } else { + n1 = n / 3; + i1 = (n - n1) / 2; + } + return (array_mean(&x[i1], n1, NULL, NULL)); +} + +/* + ** + ** Searches the region for vertical break points and stores them into + ** the BREAKINFO structure. + ** + ** apsize_in = averaging aperture size in inches. Use -1 for dynamic aperture. + ** + */ +static void bmpregion_find_vertical_breaks(BMPREGION *region, + BREAKINFO *breakinfo, int *colcount, int *rowcount, double apsize_in) + +{ + static char *funcname = "bmpregion_find_vertical_breaks"; + int nr, i, brc, brcmin, dtrc, trc, aperture, aperturemax, figrow, labelrow; + int ntr, rhmin_pix; + BMPREGION *newregion, _newregion; + int *rowthresh; + double min_fig_height, max_fig_gap, max_label_height; + + min_fig_height = dst_min_figure_height_in; + max_fig_gap = 0.16; + max_label_height = 0.5; + /* Trim region and populate colcount/rowcount arrays */ + bmpregion_trim_margins(region, colcount, rowcount, 0xf); + newregion = &_newregion; + (*newregion) = (*region); + if (debug) + printf("@bmpregion_find_vertical_breaks: (%d,%d) - (%d,%d)\n", + region->c1, region->r1, region->c2, region->r2); + /* + ** brc = consecutive blank pixel rows + ** trc = consecutive non-blank pixel rows + ** dtrc = number of non blank pixel rows since last dump + */ + nr = region->r2 - region->r1 + 1; + willus_dmem_alloc_warn(15, (void **) &rowthresh, sizeof(int) * nr, funcname, + 10); + brcmin = max_vertical_gap_inches * src_dpi; + aperturemax = (int) (src_dpi / 72. + .5); + if (aperturemax < 2) + aperturemax = 2; + aperture = (int) (src_dpi * apsize_in + .5); + /* + for (i=region->r1;i<=region->r2;i++) + printf("rowcount[%d]=%d\n",i,rowcount[i]); + */ + breakinfo->rhmean_pixels = 0; // Mean text row height + ntr = 0; // Number of text rows + /* Fill rowthresh[] array */ + for (dtrc = 0, i = region->r1; i <= region->r2; i++) { + int ii, i1, i2, sum, pt; + + if (apsize_in < 0.) { + aperture = (int) (dtrc / 13.7 + .5); + if (aperture > aperturemax) + aperture = aperturemax; + if (aperture < 2) + aperture = 2; + } + i1 = i - aperture / 2; + i2 = i1 + aperture - 1; + if (i1 < region->r1) + i1 = region->r1; + if (i2 > region->r2) + i2 = region->r2; + pt = (int) ((i2 - i1 + 1) * gtr_in * src_dpi + .5); /* pixel count threshold */ + if (pt < 1) + pt = 1; + /* Sum over row aperture */ + for (sum = 0, ii = i1; ii <= i2; sum += rowcount[ii], ii++) + ; + /* Does row have few enough black pixels to be considered blank? */ + if ((rowthresh[i - region->r1] = 10 * sum / pt) <= 40) { + if (dtrc > 0) { + breakinfo->rhmean_pixels += dtrc; + ntr++; + } + dtrc = 0; + } else + dtrc++; + } + if (dtrc > 0) { + breakinfo->rhmean_pixels += dtrc; + ntr++; + } + if (ntr > 0) + breakinfo->rhmean_pixels /= ntr; + /* + printf("rhmean=%d (ntr=%d)\n",breakinfo->rhmean_pixels,ntr); + { + FILE *f; + static int count=0; + f=fopen("rthresh.ep",count==0?"w":"a"); + count++; + for (i=region->r1;i<=region->r2;i++) + nprintf(f,"%d\n",rowthresh[i-region->r1]); + nprintf(f,"//nc\n"); + fclose(f); + } + */ + /* Minimum text row height required (pixels) */ + rhmin_pix = breakinfo->rhmean_pixels / 3; + if (rhmin_pix < .04 * src_dpi) + rhmin_pix = .04 * src_dpi; + if (rhmin_pix > .13 * src_dpi) + rhmin_pix = .13 * src_dpi; + if (rhmin_pix < 1) + rhmin_pix = 1; + /* + for (rmax=region->r2;rmax>region->r1;rmax--) + if (rowthresh[rmax-region->r1]>10) + break; + */ + /* Look for "row" gaps in the region so that it can be broken into */ + /* multiple "rows". */ + breakinfo->n = 0; + for (labelrow = figrow = -1, dtrc = trc = brc = 0, i = region->r1; + i <= region->r2; i++) { + /* Does row have few enough black pixels to be considered blank? */ + if (rowthresh[i - region->r1] <= 10) { + trc = 0; + brc++; + /* + ** Max allowed white space between rows = max_vertical_gap_inches + */ + if (dtrc == 0) { + if (brc > brcmin) + newregion->r1++; + continue; + } + /* + ** Big enough blank gap, so add one row / line + */ + if (dtrc + brc >= rhmin_pix) { + int i0, iopt; + double region_height_inches; + double gap_inches; + + if (dtrc < src_dpi * 0.02) + dtrc = src_dpi * 0.02; + if (dtrc < 2) + dtrc = 2; + /* Look for more optimum point */ + for (i0 = iopt = i; i <= region->r2 && i - i0 < dtrc; i++) { + if (rowthresh[i - region->r1] + < rowthresh[iopt - region->r1]) { + iopt = i; + if (rowthresh[i - region->r1] == 0) + break; + } + if (rowthresh[i - region->r1] > 100) + break; + } + /* If at end of region and haven't found perfect break, stay at end */ + if (i > region->r2 && rowthresh[iopt - region->r1] > 0) + i = region->r2; + else + i = iopt; + newregion->r2 = i - 1; + region_height_inches = (double) (newregion->r2 - newregion->r1 + + 1) / src_dpi; + + /* Could this region be a figure? */ + if (figrow < 0 && region_height_inches >= min_fig_height) { + /* If so, set figrow and don't process it yet. */ + figrow = newregion->r1; + labelrow = -1; + newregion->r1 = i; + dtrc = trc = 0; + brc = 1; + continue; + } + /* Are we processing a figure? */ + if (figrow >= 0) { + /* Compute most recent gap */ + if (labelrow >= 0) + gap_inches = (double) (labelrow - newregion->r1) + / src_dpi; + else + gap_inches = -1.; + /* If gap and region height are small enough, tack them on to the figure. */ + if (region_height_inches < max_label_height + && gap_inches > 0. && gap_inches < max_fig_gap) + newregion->r1 = figrow; + else { + /* Not small enough--dump the previous figure. */ + newregion->r2 = newregion->r1 - 1; + newregion->r1 = figrow; + newregion->c1 = region->c1; + newregion->c2 = region->c2; + bmpregion_trim_margins(newregion, colcount, rowcount, + 0x1f); + if (newregion->r2 > newregion->r1) + textrow_assign_bmpregion( + &breakinfo->textrow[breakinfo->n++], + newregion); + if (gap_inches > 0. && gap_inches < max_fig_gap) { + /* This new region might be a figure--set it as the new figure */ + /* and don't dump it yet. */ + figrow = newregion->r2 + 1; + labelrow = -1; + newregion->r1 = i; + dtrc = trc = 0; + brc = 1; + continue; + } else { + newregion->r1 = newregion->r2 + 1; + newregion->r2 = i - 1; + } + } + /* Cancel figure processing */ + figrow = -1; + labelrow = -1; + } + /* + if (newregion->r2 >= rmax) + i=newregion->r2=region->r2; + */ + newregion->c1 = region->c1; + newregion->c2 = region->c2; + bmpregion_trim_margins(newregion, colcount, rowcount, 0x1f); + if (newregion->r2 > newregion->r1) + textrow_assign_bmpregion( + &breakinfo->textrow[breakinfo->n++], newregion); + newregion->r1 = i; + dtrc = trc = 0; + brc = 1; + } + } else { + if (figrow >= 0 && labelrow < 0) + labelrow = i; + dtrc++; + trc++; + brc = 0; + } + } + newregion->r2 = region->r2; + if (dtrc > 0 && newregion->r2 - newregion->r1 + 1 > 0) { + /* If we were processing a figure, include it. */ + if (figrow >= 0) + newregion->r1 = figrow; + newregion->c1 = region->c1; + newregion->c2 = region->c2; + bmpregion_trim_margins(newregion, colcount, rowcount, 0x1f); + if (newregion->r2 > newregion->r1) + textrow_assign_bmpregion(&breakinfo->textrow[breakinfo->n++], + newregion); + } + /* Compute gaps between rows and row heights */ + breakinfo_compute_row_gaps(breakinfo, region->r2); + willus_dmem_free(15, (double **) &rowthresh, funcname); +} + +static void textrow_assign_bmpregion(TEXTROW *textrow, BMPREGION *region) + +{ + textrow->r1 = region->r1; + textrow->r2 = region->r2; + textrow->c1 = region->c1; + textrow->c2 = region->c2; + textrow->rowbase = region->rowbase; + textrow->lcheight = region->lcheight; + textrow->capheight = region->capheight; + textrow->h5050 = region->h5050; +} + +static void breakinfo_compute_row_gaps(BREAKINFO *breakinfo, int r2) + +{ + int i, n; + + n = breakinfo->n; + if (n <= 0) + return; + breakinfo->textrow[0].rowheight = breakinfo->textrow[0].r2 + - breakinfo->textrow[0].r1; + for (i = 0; i < n - 1; i++) + breakinfo->textrow[i].gap = breakinfo->textrow[i + 1].r1 + - breakinfo->textrow[i].rowbase - 1; + /* + breakinfo->textrow[i].rowheight = breakinfo->textrow[i+1].r1 - breakinfo->textrow[i].r1; + */ + for (i = 1; i < n; i++) + breakinfo->textrow[i].rowheight = breakinfo->textrow[i].rowbase + - breakinfo->textrow[i - 1].rowbase; + breakinfo->textrow[n - 1].gap = r2 - breakinfo->textrow[n - 1].rowbase; +} + +static void breakinfo_compute_col_gaps(BREAKINFO *breakinfo, int c2) + +{ + int i, n; + + n = breakinfo->n; + if (n <= 0) + return; + for (i = 0; i < n - 1; i++) { + breakinfo->textrow[i].gap = breakinfo->textrow[i + 1].c1 + - breakinfo->textrow[i].c2 - 1; + breakinfo->textrow[i].rowheight = breakinfo->textrow[i + 1].c1 + - breakinfo->textrow[i].c1; + } + breakinfo->textrow[n - 1].gap = c2 - breakinfo->textrow[n - 1].c2; + breakinfo->textrow[n - 1].rowheight = breakinfo->textrow[n - 1].c2 + - breakinfo->textrow[n - 1].c1; +} + +static void breakinfo_remove_small_col_gaps(BREAKINFO *breakinfo, int lcheight, + double mingap) + +{ + int i, j; + + if (mingap < word_spacing) + mingap = word_spacing; + for (i = 0; i < breakinfo->n - 1; i++) { + double gap; + + gap = (double) breakinfo->textrow[i].gap / lcheight; + if (gap >= mingap) + continue; + breakinfo->textrow[i].c2 = breakinfo->textrow[i + 1].c2; + breakinfo->textrow[i].gap = breakinfo->textrow[i + 1].gap; + if (breakinfo->textrow[i + 1].r1 < breakinfo->textrow[i].r1) + breakinfo->textrow[i].r1 = breakinfo->textrow[i + 1].r1; + if (breakinfo->textrow[i + 1].r2 > breakinfo->textrow[i].r2) + breakinfo->textrow[i].r2 = breakinfo->textrow[i + 1].r2; + for (j = i + 1; j < breakinfo->n - 1; j++) + breakinfo->textrow[j] = breakinfo->textrow[j + 1]; + breakinfo->n--; + i--; + } +} + +static void breakinfo_remove_small_rows(BREAKINFO *breakinfo, double fracrh, + double fracgap, BMPREGION *region, int *colcount, int *rowcount) + +{ + int i, j, mg, mh, mg0, mg1; + int c1, c2, nc; + int *rh, *gap; + static char *funcname = "breakinfo_remove_small_rows"; + +#if (WILLUSDEBUGX & 2) + printf("@breakinfo_remove_small_rows(fracrh=%g,fracgap=%g)\n",fracrh,fracgap); +#endif + if (breakinfo->n < 2) + return; + c1 = region->c1; + c2 = region->c2; + nc = c2 - c1 + 1; + willus_dmem_alloc_warn(16, (void **) &rh, 2 * sizeof(int) * breakinfo->n, + funcname, 10); + gap = &rh[breakinfo->n]; + for (i = 0; i < breakinfo->n; i++) { + rh[i] = breakinfo->textrow[i].r2 - breakinfo->textrow[i].r1 + 1; + if (i < breakinfo->n - 1) + gap[i] = breakinfo->textrow[i].gap; + } + sorti(rh, breakinfo->n); + sorti(gap, breakinfo->n - 1); + mh = rh[breakinfo->n / 2]; + mh *= fracrh; + if (mh < 1) + mh = 1; + mg0 = gap[(breakinfo->n - 1) / 2]; + mg = mg0 * fracgap; + mg1 = mg0 * 0.7; + if (mg < 1) + mg = 1; +#if (WILLUSDEBUGX & 2) + printf("mh = %d x %g = %d\n",rh[breakinfo->n/2],fracrh,mh); + printf("mg = %d x %g = %d\n",gap[breakinfo->n/2],fracgap,mg); +#endif + for (i = 0; i < breakinfo->n; i++) { + TEXTROW *textrow; + int trh, gs1, gs2, g1, g2, gap_is_big, row_too_small; + double m1, m2, row_width_inches; + + textrow = &breakinfo->textrow[i]; + trh = textrow->r2 - textrow->r1 + 1; + if (i == 0) { + g1 = mg0 + 1; + gs1 = mg + 1; + } else { + g1 = textrow->r1 - breakinfo->textrow[i - 1].r2 - 1; + gs1 = breakinfo->textrow[i - 1].gap; + } + if (i == breakinfo->n - 1) { + g2 = mg0 + 1; + gs2 = mg + 1; + } else { + g2 = breakinfo->textrow[i + 1].r1 - textrow->r2 - 1; + gs2 = breakinfo->textrow[i].gap; + } +#if (WILLUSDEBUGX & 2) + printf(" rowheight[%d] = %d, mh=%d, gs1=%d, gs2=%d\n",i,trh,gs1,gs2); +#endif + gap_is_big = (trh >= mh || (gs1 >= mg && gs2 >= mg)); + /* + ** Is the row width small and centered? If so, it should probably + ** be attached to its nearest neighbor--it's usually a fragment of + ** an equation or a table/figure. + */ + row_width_inches = (double) (textrow->c2 - textrow->c1 + 1) / src_dpi; + m1 = fabs(textrow->c1 - c1) / nc; + m2 = fabs(textrow->c2 - c2) / nc; + row_too_small = m1 > 0.1 && m2 > 0.1 + && row_width_inches < little_piece_threshold_inches + && (g1 <= mg1 || g2 <= mg1); +#if (WILLUSDEBUGX & 2) + printf(" m1=%g, m2=%g, rwi=%g, g1=%d, g2=%d, mg0=%d\n",m1,m2,row_width_inches,g1,g2,mg0); +#endif + if (gap_is_big && !row_too_small) + continue; +#if (WILLUSDEBUGX & 2) + printf(" row[%d] to be combined w/next row.\n",i); +#endif + if (row_too_small) { + if (g1 < g2) + i--; + } else { + if (gs1 < gs2) + i--; + } + /* + printf("Removing row. nrows=%d, rh=%d, gs1=%d, gs2=%d\n",breakinfo->n,trh,gs1,gs2); + printf(" mh = %d, mg = %d\n",rh[breakinfo->n/2],gap[(breakinfo->n-1)/2]); + */ + breakinfo->textrow[i].r2 = breakinfo->textrow[i + 1].r2; + if (breakinfo->textrow[i + 1].c2 > breakinfo->textrow[i].c2) + breakinfo->textrow[i].c2 = breakinfo->textrow[i + 1].c2; + if (breakinfo->textrow[i + 1].c1 < breakinfo->textrow[i].c1) + breakinfo->textrow[i].c1 = breakinfo->textrow[i + 1].c1; + /* Re-compute rowbase, capheight, lcheight */ + { + BMPREGION newregion; + newregion = (*region); + newregion.c1 = breakinfo->textrow[i].c1; + newregion.c2 = breakinfo->textrow[i].c2; + newregion.r1 = breakinfo->textrow[i].r1; + newregion.r2 = breakinfo->textrow[i].r2; + bmpregion_trim_margins(&newregion, colcount, rowcount, 0x1f); + newregion.c1 = breakinfo->textrow[i].c1; + newregion.c2 = breakinfo->textrow[i].c2; + newregion.r1 = breakinfo->textrow[i].r1; + newregion.r2 = breakinfo->textrow[i].r2; + textrow_assign_bmpregion(&breakinfo->textrow[i], &newregion); + } + for (j = i + 1; j < breakinfo->n - 1; j++) + breakinfo->textrow[j] = breakinfo->textrow[j + 1]; + breakinfo->n--; + i--; + } + willus_dmem_free(16, (double **) &rh, funcname); +} + +static void breakinfo_alloc(int index, BREAKINFO *breakinfo, int nrows) + +{ + static char *funcname = "breakinfo_alloc"; + + willus_dmem_alloc_warn(index, (void **) &breakinfo->textrow, + sizeof(TEXTROW) * (nrows / 2 + 2), funcname, 10); +} + +static void breakinfo_free(int index, BREAKINFO *breakinfo) + +{ + static char *funcname = "breakinfo_free"; + + willus_dmem_free(index, (double **) &breakinfo->textrow, funcname); +} + +static void breakinfo_sort_by_gap(BREAKINFO *breakinfo) + +{ + int n, top, n1; + TEXTROW *x, x0; + + x = breakinfo->textrow; + n = breakinfo->n; + if (n < 2) + return; + top = n / 2; + n1 = n - 1; + while (1) { + if (top > 0) { + top--; + x0 = x[top]; + } else { + x0 = x[n1]; + x[n1] = x[0]; + n1--; + if (!n1) { + x[0] = x0; + return; + } + } + { + int parent, child; + + parent = top; + child = top * 2 + 1; + while (child <= n1) { + if (child < n1 && x[child].gap < x[child + 1].gap) + child++; + if (x0.gap < x[child].gap) { + x[parent] = x[child]; + parent = child; + child += (parent + 1); + } else + break; + } + x[parent] = x0; + } + } +} + +static void breakinfo_sort_by_row_position(BREAKINFO *breakinfo) + +{ + int n, top, n1; + TEXTROW *x, x0; + + x = breakinfo->textrow; + n = breakinfo->n; + if (n < 2) + return; + top = n / 2; + n1 = n - 1; + while (1) { + if (top > 0) { + top--; + x0 = x[top]; + } else { + x0 = x[n1]; + x[n1] = x[0]; + n1--; + if (!n1) { + x[0] = x0; + return; + } + } + { + int parent, child; + + parent = top; + child = top * 2 + 1; + while (child <= n1) { + if (child < n1 && x[child].r1 < x[child + 1].r1) + child++; + if (x0.r1 < x[child].r1) { + x[parent] = x[child]; + parent = child; + child += (parent + 1); + } else + break; + } + x[parent] = x0; + } + } +} + +/* + ** Add a vertically-contiguous rectangular region to the destination bitmap. + ** The rectangular region may be broken up horizontally (wrapped). + */ +static void bmpregion_one_row_find_breaks(BMPREGION *region, + BREAKINFO *breakinfo, int *colcount, int *rowcount, int add_to_dbase) + +{ + int nc, i, mingap, col0, dr, thlow, thhigh; + int *bp; + BMPREGION *newregion, _newregion; + static char *funcname = "bmpregion_one_row_find_breaks"; + + if (debug) + printf("@bmpregion_one_row_find_breaks(%d,%d)-(%d,%d)\n", region->c1, + region->r1, region->c2, region->r2); + newregion = &_newregion; + (*newregion) = (*region); + bmpregion_trim_margins(newregion, colcount, rowcount, 0x1f); + region->lcheight = newregion->lcheight; + region->capheight = newregion->capheight; + region->rowbase = newregion->rowbase; + region->h5050 = newregion->h5050; + nc = newregion->c2 - newregion->c1 + 1; + breakinfo->n = 0; + if (nc < 6) + return; + /* + ** Look for "space-sized" gaps, i.e. gaps that would occur between words. + ** Use this as pixel counting aperture. + */ + dr = newregion->lcheight; + mingap = dr * word_spacing * 0.8; + if (mingap < 2) + mingap = 2; + + /* + ** Find places where there are gaps (store in bp array) + ** Could do this more intelligently--maybe calculate a histogram? + */ + willus_dmem_alloc_warn(18, (void **) &bp, sizeof(int) * nc, funcname, 10); + for (i = 0; i < nc; i++) + bp[i] = 0; + if (src_left_to_right) { + for (i = newregion->c1; i <= newregion->c2; i++) { + int i1, i2, pt, sum, ii; + i1 = i - mingap / 2; + i2 = i1 + mingap - 1; + if (i1 < newregion->c1) + i1 = newregion->c1; + if (i2 > newregion->c2) + i2 = newregion->c2; + pt = (int) ((i2 - i1 + 1) * gtw_in * src_dpi + .5); + if (pt < 1) + pt = 1; + for (sum = 0, ii = i1; ii <= i2; ii++, sum += colcount[ii]) + ; + bp[i - newregion->c1] = 10 * sum / pt; + } + } else { + for (i = newregion->c2; i >= newregion->c1; i--) { + int i1, i2, pt, sum, ii; + i1 = i - mingap / 2; + i2 = i1 + mingap - 1; + if (i1 < newregion->c1) + i1 = newregion->c1; + if (i2 > newregion->c2) + i2 = newregion->c2; + pt = (int) ((i2 - i1 + 1) * gtw_in * src_dpi + .5); + if (pt < 1) + pt = 1; + for (sum = 0, ii = i1; ii <= i2; ii++, sum += colcount[ii]) + ; + bp[i - newregion->c1] = 10 * sum / pt; + } + } +#if (WILLUSDEBUGX & 4) + if (region->r1 > 3699 && region->r1<3750) + { + static int a=0; + FILE *f; + f=fopen("outbp.ep",a==0?"w":"a"); + a++; + fprintf(f,"/sa l \"(%d,%d)-(%d,%d) lch=%d\" 2\n",region->c1,region->r1,region->c2,region->r2,region->lcheight); + for (i=0;ic1; col0 <= newregion->c2; col0++) { + int copt, c0; + BMPREGION xregion; + + xregion = (*newregion); + xregion.c1 = col0; + for (; col0 <= newregion->c2; col0++) + if (bp[col0 - newregion->c1] >= thhigh) + break; + if (col0 > newregion->c2) + break; + for (col0++; col0 <= newregion->c2; col0++) + if (bp[col0 - newregion->c1] < thlow) + break; + for (copt = c0 = col0; col0 <= newregion->c2 && col0 - c0 <= dr; + col0++) { + if (bp[col0 - newregion->c1] < bp[copt - newregion->c1]) + copt = col0; + if (bp[col0 - newregion->c1] > thhigh) + break; + } + if (copt > newregion->c2) + copt = newregion->c2; + xregion.c2 = copt; + if (xregion.c2 - xregion.c1 < 2) + continue; + bmpregion_trim_margins(&xregion, colcount, rowcount, 0x1f); + textrow_assign_bmpregion(&breakinfo->textrow[breakinfo->n++], &xregion); + col0 = copt; + if (copt == newregion->c2) + break; + } + breakinfo_compute_col_gaps(breakinfo, newregion->c2); + willus_dmem_free(18, (double **) &bp, funcname); + + /* Remove small gaps */ + { + double median_gap; + word_gaps_add(add_to_dbase ? breakinfo : NULL, region->lcheight, + &median_gap); + breakinfo_remove_small_col_gaps(breakinfo, region->lcheight, + median_gap / 1.9); + } +} + +/* + ** pi = preserve indentation + */ +static void bmpregion_one_row_wrap_and_add(BMPREGION *region, + BREAKINFO *rowbreakinfo, int index, int i1, int i2, + MASTERINFO *masterinfo, int justflags, int *colcount, int *rowcount, + PAGEINFO *pageinfo, int line_spacing, int mean_row_gap, int rowbase, + int marking_flags, int pi) + +{ + int nc, nr, i, i0, gappix; + double aspect_ratio, region_height; + BREAKINFO *colbreaks, _colbreaks; + BMPREGION *newregion, _newregion; + +#if (WILLUSDEBUGX & 4) + printf("@bmpregion_one_row_wrap_and_add, index=%d, i1=%d, i2=%d\n",index,i1,i2); +#endif + newregion = &_newregion; + (*newregion) = (*region); + bmpregion_trim_margins(newregion, colcount, rowcount, 0xf); + nc = newregion->c2 - newregion->c1 + 1; + nr = newregion->r2 - newregion->r1 + 1; + if (nc < 6) + return; + aspect_ratio = (double) nr / nc; + region_height = (double) nr / src_dpi; + if (aspect_ratio > no_wrap_ar_limit + && region_height > no_wrap_height_limit_inches) { + newregion->r1 = region->r1; + newregion->r2 = region->r2; +#ifdef WILLUSDEBUG + printf("wrapflush6\n"); +#endif + wrapbmp_flush(masterinfo, 0, pageinfo, 1); + if (index > i1) + dst_add_gap_src_pixels("Tall region", masterinfo, + rowbreakinfo->textrow[index - 1].gap); + bmpregion_add(newregion, rowbreakinfo, masterinfo, 0, 0xf, 0, -1.0, 0, + 2, colcount, rowcount, pageinfo, 0xf, + rowbreakinfo->textrow[index].r2 + - rowbreakinfo->textrow[index].rowbase); + if (index < i2) + gap_override_internal = rowbreakinfo->textrow[index].gap; + return; + } + colbreaks = &_colbreaks; + colbreaks->textrow = NULL; + breakinfo_alloc(106, colbreaks, newregion->c2 - newregion->c1 + 1); + bmpregion_one_row_find_breaks(newregion, colbreaks, colcount, rowcount, 1); + if (pi && colbreaks->n > 0) { + if (src_left_to_right) + colbreaks->textrow[0].c1 = region->c1; + else + colbreaks->textrow[colbreaks->n - 1].c2 = region->c2; + } + /* + hs=0.; + for (i=0;in;i++) + hs += (colbreaks->textrow[i].r2-colbreaks->textrow[i].r1); + hs /= colbreaks->n; + */ + /* + ** Find appropriate letter height to use for word spacing + */ + { + double median_gap; + word_gaps_add(NULL, newregion->lcheight, &median_gap); + gappix = (int) (median_gap * newregion->lcheight + .5); + } +#if (WILLUSDEBUGX & 4) + printf("Before small gap removal, column breaks:\n"); + breakinfo_echo(colbreaks); +#endif +#if (WILLUSDEBUGX & 4) + printf("After small gap removal, column breaks:\n"); + breakinfo_echo(colbreaks); +#endif + if (show_marked_source) + for (i = 0; i < colbreaks->n; i++) { + BMPREGION xregion; + xregion = (*newregion); + xregion.c1 = colbreaks->textrow[i].c1; + xregion.c2 = colbreaks->textrow[i].c2; + mark_source_page(&xregion, 2, marking_flags); + } +#if (WILLUSDEBUGX & 4) + for (i=0;in;i++) + printf(" colbreak[%d] = %d - %d\n",i,colbreaks->textrow[i].c1,colbreaks->textrow[i].c2); +#endif + /* Maybe skip gaps < 0.5*median_gap or collect gap/rowheight ratios and skip small gaps */ + /* (Could be thrown off by full-justified articles where some lines have big gaps.) */ + /* Need do call a separate function that removes these gaps. */ + for (i0 = 0; i0 < colbreaks->n;) { + int i1, i2, toolong, rw, remaining_width_pixels; + BMPREGION reg; + + toolong = 0; /* Avoid compiler warning */ + for (i = i0; i < colbreaks->n; i++) { + int wordgap; + + wordgap = wrapbmp_ends_in_hyphen() ? 0 : gappix; + i1 = src_left_to_right ? i0 : colbreaks->n - 1 - i; + i2 = src_left_to_right ? i : colbreaks->n - 1 - i0; + rw = (colbreaks->textrow[i2].c2 - colbreaks->textrow[i1].c1 + 1); + remaining_width_pixels = wrapbmp_remaining(); + toolong = (rw + wordgap > remaining_width_pixels); +#if (WILLUSDEBUGX & 4) + printf(" i1=%d, i2=%d, rw=%d, rw+gap=%d, remainder=%d, toolong=%d\n",i1,i2,rw,rw+wordgap,remaining_width_pixels,toolong); +#endif + /* + ** If we're too long with just one word and there is already + ** stuff on the queue, then flush it and re-evaluate. + */ + if (i == i0 && toolong && wrapbmp_width() > 0) { +#ifdef WILLUSDEBUG + printf("wrapflush8\n"); +#endif + wrapbmp_flush(masterinfo, 1, pageinfo, 0); + i--; + continue; + } + /* + ** If we're not too long and we're not done yet, add another word. + */ + if (i < colbreaks->n - 1 && !toolong) + continue; + /* + ** Add the regions from i0 to i (or i0 to i-1) + */ + break; + } + if (i > i0 && toolong) + i--; + i1 = src_left_to_right ? i0 : colbreaks->n - 1 - i; + i2 = src_left_to_right ? i : colbreaks->n - 1 - i0; + reg = (*newregion); + reg.c1 = colbreaks->textrow[i1].c1; + reg.c2 = colbreaks->textrow[i2].c2; +#if (WILLUSDEBUGX & 4) + printf(" Adding i1=%d to i2=%d\n",i1,i2); +#endif + /* Trim the word top/bottom */ + bmpregion_trim_margins(®, colcount, rowcount, 0xc); + reg.c1 = colbreaks->textrow[i1].c1; + reg.c2 = colbreaks->textrow[i2].c2; + reg.lcheight = newregion->lcheight; + reg.capheight = newregion->capheight; + reg.rowbase = newregion->rowbase; + reg.h5050 = newregion->h5050; + if (reg.r1 > reg.rowbase) + reg.r1 = reg.rowbase; + if (reg.r2 < reg.rowbase) + reg.r2 = reg.rowbase; + /* Add it to the existing line queue */ + wrapbmp_add(®, gappix, line_spacing, rowbase, mean_row_gap, + justflags); + if (toolong) { +#ifdef WILLUSDEBUG + printf("wrapflush7\n"); +#endif + wrapbmp_flush(masterinfo, 1, pageinfo, 0); + } + i0 = i + 1; + } + breakinfo_free(106, colbreaks); +} + +static WILLUSBITMAP _wrapbmp, *wrapbmp; +static int wrapbmp_base; +static int wrapbmp_line_spacing; +static int wrapbmp_gap; +static int wrapbmp_bgcolor; +static int wrapbmp_just; +static int wrapbmp_rhmax; +static int wrapbmp_thmax; +static int wrapbmp_maxgap = 2; +static int wrapbmp_height_extended; +static HYPHENINFO wrapbmp_hyphen; + +void wrapbmp_init(void) + +{ + wrapbmp = &_wrapbmp; + bmp_init(wrapbmp); + wrapbmp_set_color(dst_color); + wrapbmp->width = 0; + wrapbmp->height = 0; + wrapbmp_base = 0; + wrapbmp_line_spacing = -1; + wrapbmp_gap = -1; + wrapbmp_bgcolor = -1; + wrapbmp_height_extended = 0; + wrapbmp_just = 0x8f; + wrapbmp_rhmax = -1; + wrapbmp_thmax = -1; + wrapbmp_hyphen.ch = -1; + just_flushed_internal = 0; + beginning_gap_internal = -1; + last_h5050_internal = -1; +} + +static int wrapbmp_ends_in_hyphen(void) + +{ + return (wrapbmp_hyphen.ch >= 0); +} + +static void wrapbmp_set_color(int is_color) + +{ + if (is_color) + wrapbmp->bpp = 24; + else { + int i; + + wrapbmp->bpp = 8; + for (i = 0; i < 256; i++) + wrapbmp->red[i] = wrapbmp->blue[i] = wrapbmp->green[i] = i; + } +} + +static void wrapbmp_free(void) + +{ + bmp_free(wrapbmp); +} + +static void wrapbmp_set_maxgap(int value) + +{ + wrapbmp_maxgap = value; +} + +static int wrapbmp_width(void) + +{ + return (wrapbmp->width); +} + +static int wrapbmp_remaining(void) + +{ + int maxpix, w; + maxpix = max_region_width_inches * src_dpi; + /* Don't include hyphen if wrapbmp ends in a hyphen */ + if (wrapbmp_hyphen.ch < 0) + w = wrapbmp->width; + else if (src_left_to_right) + w = wrapbmp_hyphen.c2 + 1; + else + w = wrapbmp->width - wrapbmp_hyphen.c2; + return (maxpix - w); +} + +/* + ** region = bitmap region to add to line + ** gap = horizontal pixel gap between existing region and region being added + ** line_spacing = desired spacing between lines of text (pixels) + ** rbase = position of baseline in region + ** gio = gap if over--gap above top of text if it goes over line_spacing. + */ +// static int bcount=0; +static void wrapbmp_add(BMPREGION *region, int gap, int line_spacing, int rbase, + int gio, int just_flags) + +{ + WILLUSBITMAP *tmp, _tmp; + int i, rh, th, bw, new_base, h2, bpp, width0; +// static char filename[256]; + +#ifdef WILLUSDEBUG + printf("@wrapbmp_add %d x %d (w=%d).\n",region->c2-region->c1+1,region->r2-region->r1+1,wrapbmp->width); +#endif + bmpregion_hyphen_detect(region); /* Figure out if what we're adding ends in a hyphen */ + if (wrapbmp_ends_in_hyphen()) + gap = 0; + wrapbmp_hyphen_erase(); + just_flushed_internal = 0; // Reset "just flushed" flag + beginning_gap_internal = -1; // Reset top-of-page or top-of-column gap + last_h5050_internal = -1; // Reset last row font size + if (line_spacing > wrapbmp_line_spacing) + wrapbmp_line_spacing = line_spacing; + if (gio > wrapbmp_gap) + wrapbmp_gap = gio; + wrapbmp_bgcolor = region->bgcolor; + wrapbmp_just = just_flags; + /* + printf(" c1=%d, c2=%d, r1=%d, r2=%d\n",region->c1,region->c2,region->r1,region->r2); + printf(" gap=%d, line_spacing=%d, rbase=%d, gio=%d\n",gap,line_spacing,rbase,gio); + */ + bpp = dst_color ? 3 : 1; + rh = rbase - region->r1 + 1; + if (rh > wrapbmp_rhmax) + wrapbmp_rhmax = rh; + th = rh + (region->r2 - rbase); + if (th > wrapbmp_thmax) + wrapbmp_thmax = th; + /* + { + WILLUSBITMAP *bmp,_bmp; + + bmp=&_bmp; + bmp_init(bmp); + bmp->height=region->r2-region->r1+1; + bmp->width=region->c2-region->c1+1; + bmp->bpp=bpp*8; + if (bpp==1) + for (i=0;i<256;i++) + bmp->red[i]=bmp->blue[i]=bmp->green[i]=i; + bmp_alloc(bmp); + bw=bmp_bytewidth(bmp); + memset(bmp_rowptr_from_top(bmp,0),255,bw*bmp->height); + for (i=region->r1;i<=region->r2;i++) + { + unsigned char *d,*s; + d=bmp_rowptr_from_top(bmp,i-region->r1); + s=bmp_rowptr_from_top(dst_color?region->bmp:region->bmp8,i)+bpp*region->c1; + if (i==rbase) + memset(d,0,bw); + else + memcpy(d,s,bw); + } + sprintf(filename,"out%05d.png",bcount++); + bmp_write(bmp,filename,stdout,100); + bmp_free(bmp); + } + */ + if (wrapbmp->width == 0) { + /* Put appropriate gap in */ + if (last_rowbase_internal >= 0 + && rh < wrapbmp_line_spacing - last_rowbase_internal) { + rh = wrapbmp_line_spacing - last_rowbase_internal; + if (rh < 2) + rh = 2; + th = rh + (region->r2 - rbase); + wrapbmp_height_extended = 0; + } else + wrapbmp_height_extended = (last_rowbase_internal >= 0); + wrapbmp_base = rh - 1; + wrapbmp->height = th; +#ifdef WILLUSDEBUG + printf("@wrapbmp_add: bmpheight set to %d (wls=%d, lrbi=%d)\n",wrapbmp->height,wrapbmp_line_spacing,last_rowbase_internal); +#endif + wrapbmp->width = region->c2 - region->c1 + 1; + bmp_alloc(wrapbmp); + bw = bmp_bytewidth(wrapbmp); + memset(bmp_rowptr_from_top(wrapbmp, 0), 255, bw * wrapbmp->height); + for (i = region->r1; i <= region->r2; i++) { + unsigned char *d, *s; + d = bmp_rowptr_from_top(wrapbmp, wrapbmp_base + (i - rbase)); + s = bmp_rowptr_from_top(dst_color ? region->bmp : region->bmp8, i) + + bpp * region->c1; + memcpy(d, s, bw); + } +#ifdef WILLUSDEBUG + if (wrapbmp->height<=wrapbmp_base) + { + printf("1. SCREEECH!\n"); + printf("wrapbmp = %d x %d, base=%d\n",wrapbmp->width,wrapbmp->height,wrapbmp_base); + exit(10); + } +#endif + /* Copy hyphen info from added region */ + wrapbmp_hyphen = region->hyphen; + if (wrapbmp_ends_in_hyphen()) { + wrapbmp_hyphen.r1 += (wrapbmp_base - rbase); + wrapbmp_hyphen.r2 += (wrapbmp_base - rbase); + wrapbmp_hyphen.ch -= region->c1; + wrapbmp_hyphen.c2 -= region->c1; + } + return; + } + width0 = wrapbmp->width; /* Starting wrapbmp width */ + tmp = &_tmp; + bmp_init(tmp); + bmp_copy(tmp, wrapbmp); + tmp->width += gap + region->c2 - region->c1 + 1; + if (rh > wrapbmp_base) { + wrapbmp_height_extended = 1; + new_base = rh - 1; + } else + new_base = wrapbmp_base; + if (region->r2 - rbase > wrapbmp->height - 1 - wrapbmp_base) + h2 = region->r2 - rbase; + else + h2 = wrapbmp->height - 1 - wrapbmp_base; + tmp->height = new_base + h2 + 1; + bmp_alloc(tmp); + bw = bmp_bytewidth(tmp); + memset(bmp_rowptr_from_top(tmp, 0), 255, bw * tmp->height); + bw = bmp_bytewidth(wrapbmp); + /* + printf("3. wbh=%d x %d, tmp=%d x %d x %d, new_base=%d, wbbase=%d\n",wrapbmp->width,wrapbmp->height,tmp->width,tmp->height,tmp->bpp,new_base,wrapbmp_base); + */ + for (i = 0; i < wrapbmp->height; i++) { + unsigned char *d, *s; + d = bmp_rowptr_from_top(tmp, i + new_base - wrapbmp_base) + + (src_left_to_right ? 0 : tmp->width - 1 - wrapbmp->width) + * bpp; + s = bmp_rowptr_from_top(wrapbmp, i); + memcpy(d, s, bw); + } + bw = bpp * (region->c2 - region->c1 + 1); + if (region->r1 + new_base - rbase < 0 + || region->r2 + new_base - rbase > tmp->height - 1) { + aprintf(ANSI_YELLOW "INTERNAL ERROR--TMP NOT DIMENSIONED PROPERLY.\n"); + aprintf("(%d-%d), tmp->height=%d\n" ANSI_NORMAL, + region->r1 + new_base - rbase, region->r2 + new_base - rbase, + tmp->height); + exit(10); + } + for (i = region->r1; i <= region->r2; i++) { + unsigned char *d, *s; + + d = bmp_rowptr_from_top(tmp, i + new_base - rbase) + + (src_left_to_right ? wrapbmp->width + gap : 0) * bpp; + s = bmp_rowptr_from_top(dst_color ? region->bmp : region->bmp8, i) + + bpp * region->c1; + memcpy(d, s, bw); + } + bmp_copy(wrapbmp, tmp); + bmp_free(tmp); + /* Copy region's hyphen info */ + wrapbmp_hyphen = region->hyphen; + if (wrapbmp_ends_in_hyphen()) { + wrapbmp_hyphen.r1 += (new_base - rbase); + wrapbmp_hyphen.r2 += (new_base - rbase); + if (src_left_to_right) { + wrapbmp_hyphen.ch += width0 + gap - region->c1; + wrapbmp_hyphen.c2 += width0 + gap - region->c1; + } else { + wrapbmp_hyphen.ch -= region->c1; + wrapbmp_hyphen.c2 -= region->c1; + } + } + wrapbmp_base = new_base; +#ifdef WILLUSDEBUG + if (wrapbmp->height<=wrapbmp_base) + { + printf("2. SCREEECH!\n"); + printf("wrapbmp = %d x %d, base=%d\n",wrapbmp->width,wrapbmp->height,wrapbmp_base); + exit(10); + } +#endif +} + +static void wrapbmp_flush(MASTERINFO *masterinfo, int allow_full_justification, + PAGEINFO *pageinfo, int use_bgi) + +{ + BMPREGION region; + WILLUSBITMAP *bmp8, _bmp8; + int gap, just, nomss, dh; + int *colcount, *rowcount; + static char *funcname = "wrapbmp_flush"; +// char filename[256]; + + if (wrapbmp->width <= 0) { + if (use_bgi == 1 && beginning_gap_internal > 0) + dst_add_gap_src_pixels("wrapbmp_bgi0", masterinfo, + beginning_gap_internal); + beginning_gap_internal = -1; + last_h5050_internal = -1; + if (use_bgi) + just_flushed_internal = 1; + return; + } +#ifdef WILLUSDEBUG + printf("@wrapbmp_flush()\n"); +#endif + /* + { + char filename[256]; + int i; + static int bcount=0; + for (i=0;iheight;i++) + { + unsigned char *p; + int j; + p=bmp_rowptr_from_top(wrapbmp,i); + for (j=0;jwidth;j++) + if (p[j]>240) + p[j]=192; + } + sprintf(filename,"out%05d.png",bcount++); + bmp_write(wrapbmp,filename,stdout,100); + } + */ + colcount = rowcount = NULL; + willus_dmem_alloc_warn(19, (void **) &colcount, + (wrapbmp->width + 16) * sizeof(int), funcname, 10); + willus_dmem_alloc_warn(20, (void **) &rowcount, + (wrapbmp->height + 16) * sizeof(int), funcname, 10); + region.c1 = 0; + region.c2 = wrapbmp->width - 1; + region.r1 = 0; + region.r2 = wrapbmp->height - 1; + region.rowbase = wrapbmp_base; + region.bmp = wrapbmp; + region.bgcolor = wrapbmp_bgcolor; +#ifdef WILLUSDEBUG + printf("Bitmap is %d x %d (baseline=%d)\n",wrapbmp->width,wrapbmp->height,wrapbmp_base); +#endif + + /* Sanity check on row spacing -- don't let it be too large. */ + nomss = wrapbmp_rhmax * 1.7; /* Nominal single-spaced height for this row */ + if (last_rowbase_internal < 0) + dh = 0; + else { + dh = (int) (wrapbmp_line_spacing - last_rowbase_internal + - 1.2 * fabs(vertical_line_spacing) * nomss + .5); + if (vertical_line_spacing < 0.) { + int dh1; + if (wrapbmp_maxgap > 0) + dh1 = region.rowbase + 1 - wrapbmp_rhmax - wrapbmp_maxgap; + else + dh1 = (int) (wrapbmp_line_spacing - last_rowbase_internal + - 1.2 * nomss + .5); + if (dh1 > dh) + dh = dh1; + } + } + if (dh > 0) { +#ifdef WILLUSDEBUG + aprintf(ANSI_YELLOW "dh > 0 = %d" ANSI_NORMAL "\n",dh); + printf(" wrapbmp_line_spacing=%d\n",wrapbmp_line_spacing); + printf(" nomss = %d\n",nomss); + printf(" vls = %g\n",vertical_line_spacing); + printf(" lrbi=%d\n",last_rowbase_internal); + printf(" wrapbmp_maxgap=%d\n",wrapbmp_maxgap); + printf(" wrapbmp_rhmax=%d\n",wrapbmp_rhmax); +#endif + region.r1 = dh; + /* + if (dh>200) + { + bmp_write(wrapbmp,"out.png",stdout,100); + exit(10); + } + */ + } + if (wrapbmp->bpp == 24) { + bmp8 = &_bmp8; + bmp_init(bmp8); + bmp_convert_to_greyscale_ex(bmp8, wrapbmp); + region.bmp8 = bmp8; + } else + region.bmp8 = wrapbmp; + if (gap_override_internal > 0) { + region.r1 = wrapbmp_base - wrapbmp_rhmax + 1; + if (region.r1 < 0) + region.r1 = 0; + if (region.r1 > wrapbmp_base) + region.r1 = wrapbmp_base; + gap = gap_override_internal; + gap_override_internal = -1; + } else { + if (wrapbmp_height_extended) + gap = wrapbmp_gap; + else + gap = 0; + } +#ifdef WILLUSDEBUG + printf("wf: gap=%d\n",gap); +#endif + if (gap > 0) + dst_add_gap_src_pixels("wrapbmp", masterinfo, gap); + if (!allow_full_justification) + just = (wrapbmp_just & 0xcf) | 0x20; + else + just = wrapbmp_just; + bmpregion_add(®ion, NULL, masterinfo, 0, 0, 0, -1.0, just, 2, colcount, + rowcount, pageinfo, 0xf, wrapbmp->height - 1 - wrapbmp_base); + if (wrapbmp->bpp == 24) + bmp_free(bmp8); + willus_dmem_free(20, (double **) &rowcount, funcname); + willus_dmem_free(19, (double **) &colcount, funcname); + wrapbmp->width = 0; + wrapbmp->height = 0; + wrapbmp_line_spacing = -1; + wrapbmp_gap = -1; + wrapbmp_rhmax = -1; + wrapbmp_thmax = -1; + wrapbmp_hyphen.ch = -1; + if (use_bgi == 1 && beginning_gap_internal > 0) + dst_add_gap_src_pixels("wrapbmp_bgi1", masterinfo, + beginning_gap_internal); + beginning_gap_internal = -1; + last_h5050_internal = -1; + if (use_bgi) + just_flushed_internal = 1; +} + +static void wrapbmp_hyphen_erase(void) + +{ + WILLUSBITMAP *bmp, _bmp; + int bw, bpp, c0, c1, c2, i; + + if (wrapbmp_hyphen.ch < 0) + return; +#if (WILLUSDEBUGX & 16) + printf("@hyphen_erase, bmp=%d x %d x %d\n",wrapbmp->width,wrapbmp->height,wrapbmp->bpp); + printf(" ch=%d, c2=%d, r1=%d, r2=%d\n",wrapbmp_hyphen.ch,wrapbmp_hyphen.c2,wrapbmp_hyphen.r1,wrapbmp_hyphen.r2); +#endif + bmp = &_bmp; + bmp_init(bmp); + bmp->bpp = wrapbmp->bpp; + if (bmp->bpp == 8) + for (i = 0; i < 256; i++) + bmp->red[i] = bmp->blue[i] = bmp->green[i] = i; + bmp->height = wrapbmp->height; + if (src_left_to_right) { + bmp->width = wrapbmp_hyphen.c2 + 1; + c0 = 0; + c1 = wrapbmp_hyphen.ch; + c2 = bmp->width - 1; + } else { + bmp->width = wrapbmp->width - wrapbmp_hyphen.c2; + c0 = wrapbmp_hyphen.c2; + c1 = 0; + c2 = wrapbmp_hyphen.ch - wrapbmp_hyphen.c2; + } + bmp_alloc(bmp); + bpp = bmp->bpp == 24 ? 3 : 1; + bw = bpp * bmp->width; + for (i = 0; i < bmp->height; i++) + memcpy(bmp_rowptr_from_top(bmp, i), + bmp_rowptr_from_top(wrapbmp, i) + bpp * c0, bw); + bw = (c2 - c1 + 1) * bpp; + if (bw > 0) + for (i = wrapbmp_hyphen.r1; i <= wrapbmp_hyphen.r2; i++) + memset(bmp_rowptr_from_top(bmp, i) + bpp * c1, 255, bw); +#if (WILLUSDEBUGX & 16) + { + static int count=1; + char filename[256]; + sprintf(filename,"be%04d.png",count); + bmp_write(wrapbmp,filename,stdout,100); + sprintf(filename,"ae%04d.png",count); + bmp_write(bmp,filename,stdout,100); + count++; + } +#endif + bmp_copy(wrapbmp, bmp); + bmp_free(bmp); +} + +/* + ** src is only allocated if dst_color != 0 + */ +static void white_margins(WILLUSBITMAP *src, WILLUSBITMAP *srcgrey) + +{ + int i, n; + BMPREGION *region, _region; + + region = &_region; + region->bmp = srcgrey; + get_white_margins(region); + n = region->c1; + for (i = 0; i < srcgrey->height; i++) { + unsigned char *p; + if (dst_color) { + p = bmp_rowptr_from_top(src, i); + memset(p, 255, n * 3); + } + p = bmp_rowptr_from_top(srcgrey, i); + memset(p, 255, n); + } + n = srcgrey->width - 1 - region->c2; + for (i = 0; i < srcgrey->height; i++) { + unsigned char *p; + if (dst_color) { + p = bmp_rowptr_from_top(src, i) + 3 * (src->width - n); + memset(p, 255, n * 3); + } + p = bmp_rowptr_from_top(srcgrey, i) + srcgrey->width - n; + memset(p, 255, n); + } + n = region->r1; + for (i = 0; i < n; i++) { + unsigned char *p; + if (dst_color) { + p = bmp_rowptr_from_top(src, i); + memset(p, 255, src->width * 3); + } + p = bmp_rowptr_from_top(srcgrey, i); + memset(p, 255, srcgrey->width); + } + n = srcgrey->height - 1 - region->r2; + for (i = srcgrey->height - n; i < srcgrey->height; i++) { + unsigned char *p; + if (dst_color) { + p = bmp_rowptr_from_top(src, i); + memset(p, 255, src->width * 3); + } + p = bmp_rowptr_from_top(srcgrey, i); + memset(p, 255, srcgrey->width); + } +} + +static void get_white_margins(BMPREGION *region) + +{ + int n; + double defval; + + defval = 0.25; + if (mar_left < 0.) + mar_left = defval; + n = (int) (0.5 + mar_left * src_dpi); + if (n > region->bmp->width) + n = region->bmp->width; + region->c1 = n; + if (mar_right < 0.) + mar_right = defval; + n = (int) (0.5 + mar_right * src_dpi); + if (n > region->bmp->width) + n = region->bmp->width; + region->c2 = region->bmp->width - 1 - n; + if (mar_top < 0.) + mar_top = defval; + n = (int) (0.5 + mar_top * src_dpi); + if (n > region->bmp->height) + n = region->bmp->height; + region->r1 = n; + if (mar_bot < 0.) + mar_bot = defval; + n = (int) (0.5 + mar_bot * src_dpi); + if (n > region->bmp->height) + n = region->bmp->height; + region->r2 = region->bmp->height - 1 - n; +} + +/* + ** bitmap_orientation() + ** + ** 1.0 means neutral + ** + ** >> 1.0 means document is likely portrait (no rotation necessary) + ** (max is 100.) + ** + ** << 1.0 means document is likely landscape (need to rotate it) + ** (min is 0.01) + ** + */ +static double bitmap_orientation(WILLUSBITMAP *bmp) + +{ + int i, ic, wtcalc; + double hsum, vsum, rat; + + wtcalc = -1; + for (vsum = 0., hsum = 0., ic = 0, i = 20; i <= 85; i += 5, ic++) { + double nv, nh; + int wth, wtv; + +#ifdef DEBUG + printf("h %d:\n",i); +#endif + if (ic == 0) + wth = -1; + else + wth = wtcalc; + wth = -1; + nh = bmp_inflections_horizontal(bmp, 8, i, &wth); +#ifdef DEBUG + { + FILE *f; + f=fopen("inf.ep","a"); + fprintf(f,"/ag\n"); + fclose(f); + } + printf("v %d:\n",i); +#endif + if (ic == 0) + wtv = -1; + else + wtv = wtcalc; + wtv = -1; + nv = bmp_inflections_vertical(bmp, 8, i, &wtv); + if (ic == 0) { + if (wtv > wth) + wtcalc = wtv; + else + wtcalc = wth; + continue; + } +// exit(10); + hsum += nh * i * i * i; + vsum += nv * i * i * i; + } + if (vsum == 0. && hsum == 0.) + rat = 1.0; + else if (hsum < vsum && hsum / vsum < .01) + rat = 100.; + else + rat = vsum / hsum; + if (rat < .01) + rat = .01; + // printf(" page %2d: %8.4f\n",pagenum,rat); + // fprintf(out,"\t%8.4f",vsum/hsum); + // fprintf(out,"\n"); + return (rat); +} + +static double bmp_inflections_vertical(WILLUSBITMAP *srcgrey, int ndivisions, + int delta, int *wthresh) + +{ + int y0, y1, ny, i, nw, nisum, ni, wt, wtmax; + double *g; + char *funcname = "bmp_inflections_vertical"; + + nw = srcgrey->width / ndivisions; + y0 = srcgrey->height / 6; + y1 = srcgrey->height - y0; + ny = y1 - y0; + willus_dmem_alloc_warn(21, (void **) &g, ny * sizeof(double), funcname, 10); + wtmax = -1; + for (nisum = 0, i = 0; i < 10; i++) { + int x0, x1, nx, j; + + x0 = (srcgrey->width - nw) * (i + 2) / 13; + x1 = x0 + nw; + if (x1 > srcgrey->width) + x1 = srcgrey->width; + nx = x1 - x0; + for (j = y0; j < y1; j++) { + int k, rsum; + unsigned char *p; + + p = bmp_rowptr_from_top(srcgrey, j) + x0; + for (rsum = k = 0; k < nx; k++, p++) + rsum += p[0]; + g[j - y0] = (double) rsum / nx; + } + wt = (*wthresh); + ni = inflection_count(g, ny, delta, &wt); + if ((*wthresh) < 0 && ni >= 3 && wt > wtmax) + wtmax = wt; + if (ni > nisum) + nisum = ni; + } + willus_dmem_free(21, &g, funcname); + if ((*wthresh) < 0) + (*wthresh) = wtmax; + return (nisum); +} + +static double bmp_inflections_horizontal(WILLUSBITMAP *srcgrey, int ndivisions, + int delta, int *wthresh) + +{ + int x0, x1, nx, bw, i, nh, nisum, ni, wt, wtmax; + double *g; + char *funcname = "bmp_inflections_vertical"; + + nh = srcgrey->height / ndivisions; + x0 = srcgrey->width / 6; + x1 = srcgrey->width - x0; + nx = x1 - x0; + bw = bmp_bytewidth(srcgrey); + willus_dmem_alloc_warn(22, (void **) &g, nx * sizeof(double), funcname, 10); + wtmax = -1; + for (nisum = 0, i = 0; i < 10; i++) { + int y0, y1, ny, j; + + y0 = (srcgrey->height - nh) * (i + 2) / 13; + y1 = y0 + nh; + if (y1 > srcgrey->height) + y1 = srcgrey->height; + ny = y1 - y0; + for (j = x0; j < x1; j++) { + int k, rsum; + unsigned char *p; + + p = bmp_rowptr_from_top(srcgrey, y0) + j; + for (rsum = k = 0; k < ny; k++, p += bw) + rsum += p[0]; + g[j - x0] = (double) rsum / ny; + } + wt = (*wthresh); + ni = inflection_count(g, nx, delta, &wt); + if ((*wthresh) < 0 && ni >= 3 && wt > wtmax) + wtmax = wt; + if (ni > nisum) + nisum = ni; + } + willus_dmem_free(22, &g, funcname); + if ((*wthresh) < 0) + (*wthresh) = wtmax; + return (nisum); +} + +static int inflection_count(double *x, int n, int delta, int *wthresh) + +{ + int i, i0, ni, ww, c, ct, wt, mode; + double meandi, meandisq, f1, f2, stdev; + double *xs; + static int hist[256]; + static char *funcname = "inflection_count"; + + /* Find threshold white value that peaks must exceed */ + if ((*wthresh) < 0) { + for (i = 0; i < 256; i++) + hist[i] = 0; + for (i = 0; i < n; i++) { + i0 = floor(x[i]); + if (i0 > 255) + i0 = 255; + hist[i0]++; + } + ct = n * .15; + for (c = 0, i = 255; i >= 0; i--) { + c += hist[i]; + if (c > ct) + break; + } + wt = i - 10; + if (wt < 192) + wt = 192; +#ifdef DEBUG + printf("wt=%d\n",wt); +#endif + (*wthresh) = wt; + } else + wt = (*wthresh); + ww = n / 150; + if (ww < 1) + ww = 1; + willus_dmem_alloc_warn(23, (void **) &xs, sizeof(double) * n, funcname, 10); + for (i = 0; i < n - ww; i++) { + int j; + for (xs[i] = 0., j = 0; j < ww; j++, xs[i] += x[i + j]) + ; + xs[i] /= ww; + } + meandi = meandisq = 0.; + if (xs[0] <= wt - delta) + mode = 1; + else if (xs[0] >= wt) + mode = -1; + else + mode = 0; + for (i0 = 0, ni = 0, i = 1; i < n - ww; i++) { + if (mode == 1 && xs[i] >= wt) { + if (i0 > 0) { + meandi += i - i0; + meandisq += (i - i0) * (i - i0); + ni++; + } + i0 = i; + mode = -1; + continue; + } + if (xs[i] <= wt - delta) + mode = 1; + } + stdev = 1.0; /* Avoid compiler warning */ + if (ni > 0) { + meandi /= ni; + meandisq /= ni; + stdev = sqrt(fabs(meandi * meandi - meandisq)); + } + f1 = meandi / n; + if (f1 > .15) + f1 = .15; + if (ni > 2) { + if (stdev / meandi < .05) + f2 = 20.; + else + f2 = meandi / stdev; + } else + f2 = 1.; +#ifdef DEBUG + printf(" ni=%3d, f1=%8.4f, f2=%8.4f, f1*f2*ni=%8.4f\n",ni,f1,f2,f1*f2*ni); + { + static int count=0; + FILE *f; + int i; + f=fopen("inf.ep",count==0?"w":"a"); + count++; + fprintf(f,"/sa l \"%d\" 1\n",ni); + for (i=0;in = boxes->na = 0; + boxes->box = NULL; +} + +static void pdfboxes_free(PDFBOXES *boxes) + +{ + static char *funcname = "pdfboxes_free"; + willus_dmem_free(24, (double **) &boxes->box, funcname); +} + +#ifdef COMMENT +static void pdfboxes_add_box(PDFBOXES *boxes,PDFBOX *box) + +{ + static char *funcname="pdfboxes_add_box"; + + if (boxes->n>=boxes->na) + { + int newsize; + + newsize = boxes->na < 1024 ? 2048 : boxes->na*2; + /* Just calls willus_mem_alloc if oldsize==0 */ + willus_mem_realloc_robust_warn((void **)&boxes->box,newsize*sizeof(PDFBOX), + boxes->na*sizeof(PDFBOX),funcname,10); + boxes->na=newsize; + } + boxes->box[boxes->n++]=(*box); +} + +static void pdfboxes_delete(PDFBOXES *boxes,int n) + +{ + if (n>0 && nn) + { + int i; + for (i=0;in-n;i++) + boxes->box[i]=boxes->box[i+n]; + } + boxes->n -= n; + if (boxes->n < 0) + boxes->n = 0; +} +#endif + +/* + ** Track gaps between words so that we can tell when one is out of family. + ** lcheight = height of a lowercase letter. + */ +static void word_gaps_add(BREAKINFO *breakinfo, int lcheight, + double *median_gap) + +{ + static int nn = 0; + static double gap[1024]; + static char *funcname = "word_gaps_add"; + + if (breakinfo != NULL && breakinfo->n > 1) { + int i; + + for (i = 0; i < breakinfo->n - 1; i++) { + double g; + g = (double) breakinfo->textrow[i].gap / lcheight; + if (g >= word_spacing) { + gap[nn & 0x3ff] = g; + nn++; + } + } + } + if (median_gap != NULL) { + if (nn > 0) { + int n; + static double *gap_sorted; + + n = (nn > 1024) ? 1024 : nn; + willus_dmem_alloc_warn(28, (void **) &gap_sorted, + sizeof(double) * n, funcname, 10); + memcpy(gap_sorted, gap, n * sizeof(double)); + sortd(gap_sorted, n); + (*median_gap) = gap_sorted[n / 2]; + willus_dmem_free(28, &gap_sorted, funcname); + } else + (*median_gap) = 0.7; + } +} + +/* + ** bmp must be grayscale! (cbmp = color, can be null) + */ +static void bmp_detect_vertical_lines(WILLUSBITMAP *bmp, WILLUSBITMAP *cbmp, + double dpi, double minwidth_in, double maxwidth_in, double minheight_in, + double anglemax_deg, int white_thresh) + +{ + int tc, iangle, irow, icol; + int rowstep, na, angle_sign, ccthresh; + int pixmin, halfwidth, bytewidth; + int bs1, nrsteps, dp; + double anglestep; + WILLUSBITMAP *tmp, _tmp; + unsigned char *p0; + + if (debug) + printf("At bmp_detect_vertical_lines...\n"); + if (!bmp_is_grayscale(bmp)) { + printf( + "Internal error. bmp_detect_vertical_lines passed a non-grayscale bitmap.\n"); + exit(10); + } + tmp = &_tmp; + bmp_init(tmp); + bmp_copy(tmp, bmp); + dp = bmp_rowptr_from_top(tmp, 0) - bmp_rowptr_from_top(bmp, 0); + bytewidth = bmp_bytewidth(bmp); + pixmin = (int) (minwidth_in * dpi + .5); + if (pixmin < 1) + pixmin = 1; + halfwidth = pixmin / 4; + if (halfwidth < 1) + halfwidth = 1; + anglestep = atan2((double) halfwidth / dpi, minheight_in); + na = (int) ((anglemax_deg * PI / 180.) / anglestep + .5); + if (na < 1) + na = 1; + rowstep = (int) (dpi / 40. + .5); + if (rowstep < 2) + rowstep = 2; + nrsteps = bmp->height / rowstep; + bs1 = bytewidth * rowstep; + ccthresh = (int) (minheight_in * dpi / rowstep + .5); + if (ccthresh < 2) + ccthresh = 2; + if (debug && verbose) + printf( + " na = %d, rowstep = %d, ccthresh = %d, white_thresh = %d, nrsteps=%d\n", + na, rowstep, ccthresh, white_thresh, nrsteps); + /* + bmp_write(bmp,"out.png",stdout,97); + wfile_written_info("out.png",stdout); + */ + p0 = bmp_rowptr_from_top(bmp, 0); + for (tc = 0; tc < 100; tc++) { + int ccmax, ic0max, ir0max; + double tanthmax; + + ccmax = -1; + ic0max = ir0max = 0; + tanthmax = 0.; + for (iangle = 0; iangle <= na; iangle++) { + for (angle_sign = 1; angle_sign >= -1; angle_sign -= 2) { + double th, tanth, tanthx; + int ic1, ic2; + + if (iangle == 0 && angle_sign == -1) + continue; + th = (PI / 180.) * iangle * angle_sign * fabs(anglemax_deg) + / na; + tanth = tan(th); + tanthx = tanth * rowstep; + if (angle_sign == 1) { + ic1 = -(int) (bmp->height * tanth + 1.); + ic2 = bmp->width - 1; + } else { + ic1 = (int) (-bmp->height * tanth + 1.); + ic2 = bmp->width - 1 + (int) (-bmp->height * tanth + 1.); + } +// printf("iangle=%2d, angle_sign=%2d, ic1=%4d, ic2=%4d\n",iangle,angle_sign,ic1,ic2); + for (icol = ic1; icol <= ic2; icol++) { + unsigned char *p; + int cc, ic0, ir0; + p = p0; + if (icol < 0 || icol > bmp->width - 1) + for (irow = 0; irow < nrsteps; irow++, p += bs1) { + int ic; + ic = icol + irow * tanthx; + if (ic >= 0 && ic < bmp->width) + break; + } + else + irow = 0; + for (ir0 = ic0 = cc = 0; irow < nrsteps; irow++, p += bs1) { + int ic; + ic = icol + irow * tanthx; + if (ic < 0 || ic >= bmp->width) + break; + if ((p[ic] < white_thresh + || p[ic + bytewidth] < white_thresh) + && (p[ic + dp] < white_thresh + || p[ic + bytewidth + dp] < white_thresh)) { + if (cc == 0) { + ic0 = ic; + ir0 = irow * rowstep; + } + cc++; + if (cc > ccmax) { + ccmax = cc; + tanthmax = tanth; + ic0max = ic0; + ir0max = ir0; + } + } else + cc = 0; + } + } + } + } + if (ccmax < ccthresh) + break; + if (debug) + printf( + " Vert line detected: ccmax=%d (pix=%d), tanthmax=%g, ic0max=%d, ir0max=%d\n", + ccmax, ccmax * rowstep, tanthmax, ic0max, ir0max); + if (!vert_line_erase(bmp, cbmp, tmp, ir0max, ic0max, tanthmax, + minheight_in, minwidth_in, maxwidth_in, white_thresh)) + break; + } + /* + bmp_write(tmp,"outt.png",stdout,95); + wfile_written_info("outt.png",stdout); + bmp_write(bmp,"out2.png",stdout,95); + wfile_written_info("out2.png",stdout); + exit(10); + */ +} + +/* + ** Calculate max vert line length. Line is terminated by nw consecutive white pixels + ** on either side. + */ +static int vert_line_erase(WILLUSBITMAP *bmp, WILLUSBITMAP *cbmp, + WILLUSBITMAP *tmp, int row0, int col0, double tanth, + double minheight_in, double minwidth_in, double maxwidth_in, + int white_thresh) + +{ + int lw, cc, maxdev, nw, dir, i, n; + int *c1, *c2, *w; + static char *funcname = "vert_line_erase"; + + willus_dmem_alloc_warn(26, (void **) &c1, sizeof(int) * 3 * bmp->height, + funcname, 10); + c2 = &c1[bmp->height]; + w = &c2[bmp->height]; + /* + maxdev = (int)((double)bmp->height / minheight_in +.5); + if (maxdev < 3) + maxdev=3; + */ + nw = (int) ((double) src_dpi / 100. + .5); + if (nw < 2) + nw = 2; + maxdev = nw; + for (i = 0; i < bmp->height; i++) + c1[i] = c2[i] = -1; + n = 0; + for (dir = -1; dir <= 1; dir += 2) { + int del, brc; + + brc = 0; + for (del = (dir == -1) ? 0 : 1; 1; del++) { + int r, c; + unsigned char *p; + + r = row0 + dir * del; + if (r < 0 || r > bmp->height - 1) + break; + c = col0 + (r - row0) * tanth; + if (c < 0 || c > bmp->width - 1) + break; + p = bmp_rowptr_from_top(bmp, r); + for (i = c; i <= c + maxdev && i < bmp->width; i++) + if (p[i] < white_thresh) + break; + if (i > c + maxdev || i >= bmp->width) { + for (i = c - 1; i >= c - maxdev && i >= 0; i--) + if (p[i] < white_thresh) + break; + if (i < c - maxdev || i < 0) { + brc++; + if (brc >= nw) + break; + continue; + } + } + brc = 0; + for (c = i, cc = 0; i < bmp->width; i++) + if (p[i] < white_thresh) + cc = 0; + else { + cc++; + if (cc >= nw) + break; + } + c2[r] = i - cc; + if (c2[r] > bmp->width - 1) + c2[r] = bmp->width - 1; + for (cc = 0, i = c; i >= 0; i--) + if (p[i] < white_thresh) + cc = 0; + else { + cc++; + if (cc >= nw) + break; + } + c1[r] = i + cc; + if (c1[r] < 0) + c1[r] = 0; + w[n++] = c2[r] - c1[r] + 1; + c1[r] -= cc; + if (c1[r] < 0) + c1[r] = 0; + c2[r] += cc; + if (c2[r] > bmp->width - 1) + c2[r] = bmp->width - 1; + } + } + if (n > 1) + sorti(w, n); + if (n < 10 || n < minheight_in * src_dpi || w[n / 4] < minwidth_in * src_dpi + || w[3 * n / 4] > maxwidth_in * src_dpi + || (erase_vertical_lines == 1 && w[n - 1] > maxwidth_in * src_dpi)) { + /* Erase area in temp bitmap */ + for (i = 0; i < bmp->height; i++) { + unsigned char *p; + int cmax; + + if (c1[i] < 0 || c2[i] < 0) + continue; + cmax = (c2[i] - c1[i]) + 1; + p = bmp_rowptr_from_top(tmp, i) + c1[i]; + for (; cmax > 0; cmax--, p++) + (*p) = 255; + } + } else { + /* Erase line width in source bitmap */ + lw = w[3 * n / 4] + nw * 2; + if (lw > maxwidth_in * src_dpi / 2) + lw = maxwidth_in * src_dpi / 2; + for (i = 0; i < bmp->height; i++) { + unsigned char *p; + int c0, cmin, cmax, count, white; + + if (c1[i] < 0 || c2[i] < 0) + continue; + c0 = col0 + (i - row0) * tanth; + cmin = c0 - lw - 1; + if (cmin < c1[i]) + cmin = c1[i]; + cmax = c0 + lw + 1; + if (cmax > c2[i]) + cmax = c2[i]; + p = bmp_rowptr_from_top(bmp, i); + c0 = (p[cmin] > p[cmax]) ? cmin : cmax; + white = p[c0]; + if (white <= white_thresh) + white = white_thresh + 1; + if (white > 255) + white = 255; + count = (cmax - cmin) + 1; + p = &p[cmin]; + for (; count > 0; count--, p++) + (*p) = white; + if (cbmp != NULL) { + unsigned char *p0; + p = bmp_rowptr_from_top(cbmp, i); + p0 = p + c0 * 3; + p = p + cmin * 3; + count = (cmax - cmin) + 1; + for (; count > 0; count--, p += 3) { + p[0] = p0[0]; + p[1] = p0[1]; + p[2] = p0[2]; + } + } + } + } + willus_dmem_free(26, (double **) &c1, funcname); + return (1); +} + +/* + ** mem_index... controls which memory allocactions get a protective margin + ** around them. + */ +static int mem_index_min = 999; +static int mem_index_max = 999; +static void willus_dmem_alloc_warn(int index, void **ptr, int size, + char *funcname, int exitcode) + +{ + if (index >= mem_index_min && index <= mem_index_max) { + char *ptr1; + void *x; + willus_mem_alloc_warn((void **) &ptr1, size + 2048, funcname, exitcode); + ptr1 += 1024; + x = (void *) ptr1; + (*ptr) = x; + } else + willus_mem_alloc_warn(ptr, size, funcname, exitcode); +} + +static void willus_dmem_free(int index, double **ptr, char *funcname) + +{ + if ((*ptr) == NULL) + return; + if (index >= mem_index_min && index <= mem_index_max) { + double *x; + char *ptr1; + x = (*ptr); + ptr1 = (char *) x; + ptr1 -= 1024; + x = (double *) ptr1; + willus_mem_free(&x, funcname); + (*ptr) = NULL; + } else + willus_mem_free(ptr, funcname); +} + +/* mem.c */ +/* +** The reason I don't simply use malloc is because I want to allocate +** memory using type long instead of type size_t. On some compilers, +** like gcc, these are the same, so it doesn't matter. On other +** compilers, like Turbo C, these are different. +** +*/ +static int willus_mem_alloc(double **ptr,long size,char *name) + + { +#if (defined(WIN32) && !defined(__DMC__)) + unsigned long memsize; + memsize = (unsigned long)size; +#ifdef USEGLOBAL + (*ptr) = (memsize==size) ? (double *)GlobalAlloc(GPTR,memsize) : NULL; +#else + (*ptr) = (memsize==size) ? (double *)CoTaskMemAlloc(memsize) : NULL; +#endif +#else + size_t memsize; + memsize=(size_t)size; + (*ptr) = (memsize==size) ? (double *)malloc(memsize) : NULL; +#endif +/* +{ +f=fopen("mem.dat","a"); +fprintf(f,"willus_mem_alloc(%d,%s)\n",size,name); +fclose(f); +} +*/ + return((*ptr)!=NULL); + } + +/* +** Prints an integer to 's' with commas separating every three digits. +** E.g. 45,399,350 +** Correctly handles negative values. +*/ +static void comma_print(char *s,long size) + + { + int i,m,neg; + char tbuf[80]; + + if (!size) + { + s[0]='0'; + s[1]='\0'; + return; + } + s[0]='\0'; + neg=0; + if (size<0) + { + size=-size; + neg=1; + } + for (i=0,m=size%1000;size;i++,size=(size-m)/1000,m=size%1000) + { + sprintf(tbuf,m==size ? "%d%s":"%03d%s",m,i>0 ? "," : ""); + strcat(tbuf,s); + strcpy(s,tbuf); + } + if (neg) + { + strcpy(tbuf,"-"); + strcat(tbuf,s); + strcpy(s,tbuf); + } + } + + +static void mem_warn(char *name,int size,int exitcode) + + { + static char buf[128]; + + aprintf("\n" ANSI_RED "\aCannot allocate enough memory for " + "function %s." ANSI_NORMAL "\n",name); + comma_print(buf,size); + aprintf(" " ANSI_RED "(Needed %s bytes.)" ANSI_NORMAL "\n\n",buf); + if (exitcode!=0) + { + aprintf(" " ANSI_RED "Program terminated." ANSI_NORMAL "\n\n"); + exit(exitcode); + } + } + +static int willus_mem_alloc_warn(void **ptr, int size, char *name, int exitcode) + +{ + int status; + + status = willus_mem_alloc((double **) ptr, (long) size, name); + if (!status) + mem_warn(name, size, exitcode); + return (status); +} + +static void willus_mem_free(double **ptr, char *name) + +{ + if ((*ptr) != NULL) { +#if (defined(WIN32) && !defined(__DMC__)) +#ifdef USEGLOBAL + GlobalFree((void *)(*ptr)); +#else + CoTaskMemFree((void *)(*ptr)); +#endif +#else + free((void *) (*ptr)); +#endif + (*ptr) = NULL; + } +} + +static int willus_mem_realloc_robust(double **ptr,long newsize,long oldsize,char *name) + + { +#if (defined(WIN32) && !defined(__DMC__)) + unsigned long memsize; + void *newptr; +#else + size_t memsize; + void *newptr; +#endif + +#if (defined(WIN32) && !defined(__DMC__)) + memsize=(unsigned long)newsize; +#else + memsize=(size_t)newsize; +#endif + if (memsize!=newsize) + return(0); + if ((*ptr)==NULL || oldsize<=0) + return(willus_mem_alloc(ptr,newsize,name)); +#if (defined(WIN32) && !defined(__DMC__)) +#ifdef USEGLOBAL + newptr = (void *)GlobalReAlloc((void *)(*ptr),memsize,GMEM_MOVEABLE); +#else + newptr = (void *)CoTaskMemRealloc((void *)(*ptr),memsize); +#endif +#else + newptr = realloc((void *)(*ptr),memsize); +#endif + if (newptr==NULL && willus_mem_alloc((double **)&newptr,newsize,name)) + { + memcpy(newptr,(*ptr),oldsize); + willus_mem_free(ptr,name); + } + if (newptr==NULL) + return(0); + + (*ptr) = newptr; + return(1); + } + + +static int willus_mem_realloc_robust_warn(void **ptr,int newsize,int oldsize,char *name, + int exitcode) + + { + int status; + + status = willus_mem_realloc_robust((double **)ptr,newsize,oldsize,name); + if (!status) + mem_warn(name,newsize,exitcode); + return(status); + } + +/* math.c */ +static void sortd(double *x, int n) + +{ + int top, n1; + double x0; + + if (n < 2) + return; + top = n / 2; + n1 = n - 1; + while (1) { + if (top > 0) { + top--; + x0 = x[top]; + } else { + x0 = x[n1]; + x[n1] = x[0]; + n1--; + if (!n1) { + x[0] = x0; + return; + } + } + { + int parent, child; + + parent = top; + child = top * 2 + 1; + while (child <= n1) { + if (child < n1 && x[child] < x[child + 1]) + child++; + if (x0 < x[child]) { + x[parent] = x[child]; + parent = child; + child += (parent + 1); + } else + break; + } + x[parent] = x0; + } + } +} + +static void sorti(int *x, int n) + +{ + int top, n1; + int x0; + + if (n < 2) + return; + top = n / 2; + n1 = n - 1; + while (1) { + if (top > 0) { + top--; + x0 = x[top]; + } else { + x0 = x[n1]; + x[n1] = x[0]; + n1--; + if (!n1) { + x[0] = x0; + return; + } + } + { + int parent, child; + + parent = top; + child = top * 2 + 1; + while (child <= n1) { + if (child < n1 && x[child] < x[child + 1]) + child++; + if (x0 < x[child]) { + x[parent] = x[child]; + parent = child; + child += (parent + 1); + } else + break; + } + x[parent] = x0; + } + } +} + +/* bmp.c */ +/* + ** Should call bmp_set_type() right after this to set the bitmap type. + */ + +#define RGBSET24(bmp,ptr,r,g,b) \ + if (bmp->type==WILLUSBITMAP_TYPE_NATIVE) \ + { \ + ptr[0]=r; \ + ptr[1]=g; \ + ptr[2]=b; \ + } \ + else \ + { \ + ptr[2]=r; \ + ptr[1]=g; \ + ptr[0]=b; \ + } + +#define RGBGET(bmp,ptr,r,g,b) \ + if (bmp->bpp==8) \ + { \ + r=bmp->red[ptr[0]]; \ + g=bmp->green[ptr[0]]; \ + b=bmp->blue[ptr[0]]; \ + } \ + else if (bmp->type==WILLUSBITMAP_TYPE_NATIVE) \ + { \ + r=ptr[0]; \ + g=ptr[1]; \ + b=ptr[2]; \ + } \ + else \ + { \ + r=ptr[2]; \ + g=ptr[1]; \ + b=ptr[0]; \ + } + +#define RGBGETINCPTR(bmp,ptr,r,g,b) \ + if (bmp->bpp==8) \ + { \ + r=bmp->red[ptr[0]]; \ + g=bmp->green[ptr[0]]; \ + b=bmp->blue[ptr[0]]; \ + ptr++; \ + } \ + else if (bmp->type==WILLUSBITMAP_TYPE_NATIVE) \ + { \ + r=ptr[0]; \ + g=ptr[1]; \ + b=ptr[2]; \ + ptr+=3; \ + } \ + else \ + { \ + r=ptr[2]; \ + g=ptr[1]; \ + b=ptr[0]; \ + ptr+=3; \ + } + +static void bmp_init(WILLUSBITMAP *bmap) + +{ + bmap->data = NULL; + bmap->size_allocated = 0; + bmap->type = WILLUSBITMAP_TYPE_NATIVE; +} + +static int bmp_bytewidth_win32(WILLUSBITMAP *bmp) + + { + return(((bmp->bpp==24 ? bmp->width*3 : bmp->width)+3)&(~0x3)); + } + +/* + ** The width, height, and bpp parameters of the WILLUSBITMAP structure + ** should be set before calling this function. + */ +static int bmp_alloc(WILLUSBITMAP *bmap) + +{ + int size; + static char *funcname = "bmp_alloc"; + + if (bmap->bpp != 8 && bmap->bpp != 24) { + printf("Internal error: call to bmp_alloc has bpp!=8 and bpp!=24!\n"); + exit(10); + } + /* Choose the max size even if not WIN32 to avoid memory faults */ + /* and to allow the possibility of changing the "type" of the */ + /* bitmap without reallocating memory. */ + size = bmp_bytewidth_win32(bmap) * bmap->height; + if (bmap->data != NULL && bmap->size_allocated >= size) + return (1); + if (bmap->data != NULL) + willus_mem_realloc_robust_warn((void **) &bmap->data, size, + bmap->size_allocated, funcname, 10); + else + willus_mem_alloc_warn((void **) &bmap->data, size, funcname, 10); + bmap->size_allocated = size; + return (1); +} + +static void bmp_free(WILLUSBITMAP *bmap) + + { + if (bmap->data!=NULL) + { + willus_mem_free((double **)&bmap->data,"bmp_free"); + bmap->data=NULL; + bmap->size_allocated=0; + } + } + +/* +** If 8-bit, the bitmap is filled with . +** If 24-bit, it gets , , values. +*/ +static void bmp_fill(WILLUSBITMAP *bmp,int r,int g,int b) + + { + int y,n; + + if (bmp->bpp==8 || (r==g && r==b)) + { + memset(bmp->data,r,bmp->size_allocated); + return; + } + if (bmp->type==WILLUSBITMAP_TYPE_WIN32 && bmp->bpp==24) + { + y=r; + r=b; + b=y; + } + for (y=bmp->height-1;y>=0;y--) + { + unsigned char *p; + + p=bmp_rowptr_from_top(bmp,y); + for (n=bmp->width-1;n>=0;n--) + { + (*p)=r; + p++; + (*p)=g; + p++; + (*p)=b; + p++; + } + } + } + + +static int bmp_copy(WILLUSBITMAP *dest, WILLUSBITMAP *src) + +{ + dest->width = src->width; + dest->height = src->height; + dest->bpp = src->bpp; + dest->type = src->type; + if (!bmp_alloc(dest)) + return (0); + memcpy(dest->data, src->data, src->height * bmp_bytewidth(src)); + memcpy(dest->red, src->red, sizeof(int) * 256); + memcpy(dest->green, src->green, sizeof(int) * 256); + memcpy(dest->blue, src->blue, sizeof(int) * 256); + return (1); +} + +static int bmp_bytewidth(WILLUSBITMAP *bmp) { + return (bmp->bpp == 24 ? bmp->width * 3 : bmp->width); +} + +/* + ** row==0 ==> top row of bitmap + ** row==bmp->height-1 ==> bottom row of bitmap + ** (regardless of bitmap type) + */ +static unsigned char *bmp_rowptr_from_top(WILLUSBITMAP *bmp, int row) + +{ + if (bmp->type == WILLUSBITMAP_TYPE_WIN32) + return (&bmp->data[bmp_bytewidth(bmp) * (bmp->height - 1 - row)]); + else + return (&bmp->data[bmp_bytewidth(bmp) * row]); +} + +/* + ** Allocate more bitmap rows. + ** ratio typically something like 1.5 or 2.0 + */ +static void bmp_more_rows(WILLUSBITMAP *bmp, double ratio, int pixval) + +{ + int new_height, new_bytes, bw; + static char *funcname = "bmp_more_rows"; + + new_height = (int) (bmp->height * ratio + .5); + if (new_height <= bmp->height) + return; + bw = bmp_bytewidth(bmp); + new_bytes = bw * new_height; + if (new_bytes > bmp->size_allocated) { + willus_mem_realloc_robust_warn((void **) &bmp->data, new_bytes, + bmp->size_allocated, funcname, 10); + bmp->size_allocated = new_bytes; + } + /* Fill in */ + memset(bmp_rowptr_from_top(bmp, bmp->height), pixval, + (new_height - bmp->height) * bw); + bmp->height = new_height; +} + +static double resample_single(double *y,double x1,double x2) + + { + int i,i1,i2; + double dx,dx1,dx2,sum; + + i1=floor(x1); + i2=floor(x2); + if (i1==i2) + return(y[i1]); + dx=x2-x1; + if (dx>1.) + dx=1.; + dx1= 1.-(x1-i1); + dx2= x2-i2; + sum=0.; + if (dx1 > 1e-8*dx) + sum += dx1*y[i1]; + if (dx2 > 1e-8*dx) + sum += dx2*y[i2]; + for (i=i1+1;i<=i2-1;sum+=y[i],i++); + return(sum/(x2-x1)); + } + +/* +** Resample src[] into dst[]. +** Examples: resample_1d(dst,src,0.,5.,5) would simply copy the +** first five elements of src[] to dst[]. +** +** resample_1d(dst,src,0.,5.,10) would work as follows: +** dst[0] and dst[1] would get src[0]. +** dst[2] and dst[3] would get src[1]. +** and so on. +** +*/ +static void resample_1d(double *dst,double *src,double x1,double x2, + int n) + + { + int i; + double new,last; + + last=x1; + for (i=0;itype==WILLUSBITMAP_TYPE_WIN32 && color>=0) + color=2-color; + for (row=0;rowbpp==8) + { + switch (color) + { + case -1: + for (col=0,p+=x0;colred[p[0]]; + break; + case 1: + for (col=0,p+=x0;colgreen[p[0]]; + break; + case 2: + for (col=0,p+=x0;colblue[p[0]]; + break; + } + } + else + { + p+=color; + for (col=0,p+=3*x0;colwidth (x-coord), and top to bottom go from + ** 0.0 to src->height (y-coord). + ** The cropped rectangle (x1,y1) to (x2,y2) is placed into + ** the destination bitmap, which need not be allocated yet. + ** + ** The destination bitmap will be 8-bit grayscale if the source bitmap + ** passes the bmp_is_grayscale() function. Otherwise it will be 24-bit. + ** + ** Returns 0 for okay. + ** -1 for not enough memory. + ** -2 for bad cropping area or destination bitmap size + */ +static int bmp_resample(WILLUSBITMAP *dest, WILLUSBITMAP *src, double x1, + double y1, double x2, double y2, int newwidth, int newheight) + +{ + int gray, maxlen, colorplanes; + double t; + double *tempbmp; + double *temprow; + int color, hmax, row, col, dy; + static char *funcname = "bmp_resample"; + + /* Clip and sort x1,y1 and x2,y2 */ + if (x1 > src->width) + x1 = src->width; + else if (x1 < 0.) + x1 = 0.; + if (x2 > src->width) + x2 = src->width; + else if (x2 < 0.) + x2 = 0.; + if (y1 > src->height) + y1 = src->height; + else if (y1 < 0.) + y1 = 0.; + if (y2 > src->height) + y2 = src->height; + else if (y2 < 0.) + y2 = 0.; + if (x2 < x1) { + t = x2; + x2 = x1; + x1 = t; + } + if (y2 < y1) { + t = y2; + y2 = y1; + y1 = t; + } + dy = y2 - y1; + dy += 2; + if (x2 - x1 == 0. || y2 - y1 == 0.) + return (-2); + + /* Allocate temp storage */ + maxlen = x2 - x1 > dy + newheight ? (int) (x2 - x1) : dy + newheight; + maxlen += 16; + hmax = newheight > dy ? newheight : dy; + if (!willus_mem_alloc(&temprow, maxlen * sizeof(double), funcname)) + return (-1); + if (!willus_mem_alloc(&tempbmp, hmax * newwidth * sizeof(double), + funcname)) { + willus_mem_free(&temprow, funcname); + return (-1); + } + if ((gray = bmp_is_grayscale(src)) != 0) { + int i; + dest->bpp = 8; + for (i = 0; i < 256; i++) + dest->red[i] = dest->blue[i] = dest->green[i] = i; + } else + dest->bpp = 24; + dest->width = newwidth; + dest->height = newheight; + dest->type = WILLUSBITMAP_TYPE_NATIVE; + if (!bmp_alloc(dest)) { + willus_mem_free(&tempbmp, funcname); + willus_mem_free(&temprow, funcname); + return (-1); + } + colorplanes = gray ? 1 : 3; + for (color = 0; color < colorplanes; color++) { + bmp_resample_1(tempbmp, src, x1, y1, x2, y2, newwidth, newheight, + temprow, gray ? -1 : color); + for (row = 0; row < newheight; row++) { + unsigned char *p; + double *s; + p = bmp_rowptr_from_top(dest, row) + color; + s = &tempbmp[row * newwidth]; + if (colorplanes == 1) + for (col = 0; col < newwidth; + p[0] = (int) (s[0] + .5), col++, s++, p++) + ; + else + for (col = 0; col < newwidth; + p[0] = (int) (s[0] + .5), col++, s++, p += colorplanes) + ; + } + } + willus_mem_free(&tempbmp, funcname); + willus_mem_free(&temprow, funcname); + return (0); +} + +static int bmp8_greylevel_convert(int r,int g,int b) + + { + return((int)((r*0.3+g*0.59+b*0.11)*1.002)); + } + +/* +** One of dest or src can be NULL, which is the +** same as setting them equal to each other, but +** in this case, the bitmap must be 24-bit! +*/ +static int bmp_is_grayscale(WILLUSBITMAP *bmp) + + { + int i; + if (bmp->bpp!=8) + return(0); + for (i=0;i<256;i++) + if (bmp->red[i]!=i || bmp->green[i]!=i || bmp->blue[i]!=i) + return(0); + return(1); + } + +static void bmp_color_xform8(WILLUSBITMAP *dest,WILLUSBITMAP *src,unsigned char *newval) + + { + int i,ir; + + if (src==NULL) + src=dest; + if (dest==NULL) + dest=src; + if (dest!=src) + { + dest->width = src->width; + dest->height = src->height; + dest->bpp = 8; + for (i=0;i<256;i++) + dest->red[i]=dest->green[i]=dest->blue[i]=i; + bmp_alloc(dest); + } + for (ir=0;irheight;ir++) + { + unsigned char *sp,*dp; + sp=bmp_rowptr_from_top(src,ir); + dp=bmp_rowptr_from_top(dest,ir); + for (i=0;iwidth;i++) + dp[i]=newval[sp[i]]; + } + } + +/* +** One of dest or src can be NULL, which is the +** same as setting them equal to each other, but +** in this case, the bitmap must be 24-bit! +*/ +static void bmp_color_xform(WILLUSBITMAP *dest,WILLUSBITMAP *src,unsigned char *newval) + + { + int ir,ic; + + if (src==NULL) + src=dest; + if (dest==NULL) + dest=src; + if (bmp_is_grayscale(src)) + { + bmp_color_xform8(dest,src,newval); + return; + } + if (dest!=src) + { + dest->width = src->width; + dest->height = src->height; + dest->bpp = 24; + bmp_alloc(dest); + } + for (ir=0;irheight;ir++) + { + unsigned char *sp,*dp; + sp=bmp_rowptr_from_top(src,ir); + dp=bmp_rowptr_from_top(dest,ir); + for (ic=0;icwidth;ic++,dp+=3) + { + int r,g,b; + + RGBGETINCPTR(src,sp,r,g,b); + r=newval[r]; + g=newval[g]; + b=newval[b]; + RGBSET24(dest,dp,r,g,b); + } + } + } + +/* +** One of dest or src can be NULL, which is the +** same as setting them equal to each other, but +** in this case, the bitmap must be 24-bit! +** Note: contrast > 1 will increase the contrast. +** contrast < 1 will decrease the contrast. +** contrast of 0 will make all pixels the same value. +** contrast of 1 will not change the image. +*/ +static void bmp_contrast_adjust(WILLUSBITMAP *dest,WILLUSBITMAP *src,double contrast) + + { + int i; + static unsigned char newval[256]; + + for (i=0;i<256;i++) + { + double x,y; + int sgn,v; + x=(i-127.5)/127.5; + sgn = x<0 ? -1 : 1; + if (contrast<0) + sgn = -sgn; + x=fabs(x); + if (fabs(contrast)>1.5) + y=x<.99999 ? 1-exp(fabs(contrast)*x/(x-1)) : 1.; + else + { + y=fabs(contrast)*x; + if (y>1.) + y=1.; + } + y = 127.5+y*sgn*127.5; + v = (int)(y+.5); + if (v<0) + v=0; + if (v>255) + v=255; + newval[i] = v; + } + bmp_color_xform(dest,src,newval); + } + +/* + ** Convert bitmap to grey-scale in-situ + */ +static void bmp_convert_to_greyscale_ex(WILLUSBITMAP *dst, WILLUSBITMAP *src) + +{ + int oldbpr, newbpr, bpp, dp, rownum, colnum, i; + + oldbpr = bmp_bytewidth(src); + dp = src->bpp == 8 ? 1 : 3; + bpp = src->bpp; + dst->bpp = 8; + for (i = 0; i < 256; i++) + dst->red[i] = dst->green[i] = dst->blue[i] = i; + if (dst != src) { + dst->width = src->width; + dst->height = src->height; + bmp_alloc(dst); + } + newbpr = bmp_bytewidth(dst); + /* Possibly restore src->bpp to 24 so RGBGET works right (src & dst may be the same) */ + src->bpp = bpp; + for (rownum = 0; rownum < src->height; rownum++) { + unsigned char *oldp, *newp; + oldp = &src->data[oldbpr * rownum]; + newp = &dst->data[newbpr * rownum]; + for (colnum = 0; colnum < src->width; colnum++, oldp += dp, newp++) { + int r, g, b; + RGBGET(src, oldp, r, g, b); + (*newp) = bmp8_greylevel_convert(r, g, b); + } + } + dst->bpp = 8; /* Possibly restore dst->bpp to 8 since src & dst may be the same. */ +} + +/* bmpmupdf.c */ +static int bmpmupdf_pixmap_to_bmp(WILLUSBITMAP *bmp, fz_context *ctx, + fz_pixmap *pixmap) + +{ + unsigned char *p; + int ncomp, i, row, col; + + bmp->width = fz_pixmap_width(ctx, pixmap); + bmp->height = fz_pixmap_height(ctx, pixmap); + ncomp = fz_pixmap_components(ctx, pixmap); + /* Has to be 8-bit or RGB */ + if (ncomp != 2 && ncomp != 4) + return (-1); + bmp->bpp = (ncomp == 2) ? 8 : 24; + bmp_alloc(bmp); + if (ncomp == 2) + for (i = 0; i < 256; i++) + bmp->red[i] = bmp->green[i] = bmp->blue[i] = i; + p = fz_pixmap_samples(ctx, pixmap); + if (ncomp == 1) + for (row = 0; row < bmp->height; row++) { + unsigned char *dest; + dest = bmp_rowptr_from_top(bmp, row); + memcpy(dest, p, bmp->width); + p += bmp->width; + } + else if (ncomp == 2) + for (row = 0; row < bmp->height; row++) { + unsigned char *dest; + dest = bmp_rowptr_from_top(bmp, row); + for (col = 0; col < bmp->width; col++, dest++, p += 2) + dest[0] = p[0]; + } + else + for (row = 0; row < bmp->height; row++) { + unsigned char *dest; + dest = bmp_rowptr_from_top(bmp, row); + for (col = 0; col < bmp->width; + col++, dest += ncomp - 1, p += ncomp) + memcpy(dest, p, ncomp - 1); + } + return (0); +} diff --git a/k2pdfopt.h b/k2pdfopt.h new file mode 100644 index 000000000..6067ff165 --- /dev/null +++ b/k2pdfopt.h @@ -0,0 +1,33 @@ +/* + ** k2pdfopt.h K2pdfopt optimizes PDF/DJVU files for mobile e-readers + ** (e.g. the Kindle) and smartphones. It works well on + ** multi-column PDF/DJVU files. K2pdfopt is freeware. + ** + ** Copyright (C) 2012 http://willus.com + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU Affero General Public License as + ** published by the Free Software Foundation, either version 3 of the + ** License, or (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU Affero General Public License for more details. + ** + ** You should have received a copy of the GNU Affero General Public License + ** along with this program. If not, see . + ** + */ + +#ifndef _K2PDFOPT_H +#define _K2PDFOPT_H + +#include + +void k2pdfopt_mupdf_reflow_bmp(fz_context *ctx, fz_pixmap *pix, double rot_deg); +void k2pdfopt_mupdf_rfbmp_size(int *width, int *height); +void k2pdfopt_mupdf_rfbmp_ptr(unsigned char** bmp_ptr_ptr); + +#endif + diff --git a/pdf.c b/pdf.c index fdb64b674..97ddd7c4e 100644 --- a/pdf.c +++ b/pdf.c @@ -20,6 +20,7 @@ #include "blitbuffer.h" #include "drawcontext.h" #include "pdf.h" +#include "k2pdfopt.h" #include #include #include @@ -511,6 +512,96 @@ static int closePage(lua_State *L) { return 0; } +static int reflowPage(lua_State *L) { + fz_context *ctx; + fz_device *dev; + fz_pixmap *pix; + fz_rect bounds,bounds2; + fz_matrix ctm; + fz_bbox bbox; + + PdfPage *page = (PdfPage*) luaL_checkudata(L, 1, "pdfpage"); + DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); + + double dpi = 200; + double dpp; + dpp = dpi / 72.; + pix = NULL; + fz_var(pix); + bounds = fz_bound_page(page->doc->xref, page->page); + ctm = fz_scale(dpp, dpp); + // ctm=fz_concat(ctm,fz_rotate(rotation)); + bounds2 = fz_transform_rect(ctm, bounds); + bbox = fz_round_rect(bounds2); + // ctm=fz_translate(0,-page->mediabox.y1); + // ctm=fz_concat(ctm,fz_scale(dpp,-dpp)); + // ctm=fz_concat(ctm,fz_rotate(page->rotate)); + // ctm=fz_concat(ctm,fz_rotate(0)); + // bbox=fz_round_rect(fz_transform_rect(ctm,page->mediabox)); + // pix=fz_new_pixmap_with_rect(colorspace,bbox); + pix = fz_new_pixmap_with_bbox(page->doc->context, fz_device_gray, bbox); + printf("bbox:%d,%d,%d,%d\n",bbox.x0,bbox.y0,bbox.x1,bbox.y1); + fz_clear_pixmap_with_value(page->doc->context, pix, 0xff); + dev = fz_new_draw_device(page->doc->context, pix); +#ifdef MUPDF_TRACE + fz_device *tdev; + fz_try(page->doc->context) { + tdev = fz_new_trace_device(page->doc->context); + fz_run_page(page->doc->xref, page->page, tdev, ctm, NULL); + } + fz_always(page->doc->context) { + fz_free_device(tdev); + } +#endif + fz_run_page(page->doc->xref, page->page, dev, ctm, NULL); + fz_free_device(dev); + + if(dc->gamma >= 0.0) { + fz_gamma_pixmap(page->doc->context, pix, dc->gamma); + } + int width, height; + k2pdfopt_mupdf_reflow_bmp(page->doc->context, pix, 0); + k2pdfopt_mupdf_rfbmp_size(&width, &height); + + lua_pushnumber(L, (double)width); + lua_pushnumber(L, (double)height); + + fz_drop_pixmap(page->doc->context, pix); + + return 2; +} + +static int drawReflowedPage(lua_State *L) { + static unsigned char *bmptr = NULL; + + PdfPage *page = (PdfPage*) luaL_checkudata(L, 1, "pdfpage"); + DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); + BlitBuffer *bb = (BlitBuffer*) luaL_checkudata(L, 3, "blitbuffer"); + k2pdfopt_mupdf_rfbmp_ptr(&bmptr); + + uint8_t *bbptr = (uint8_t*)bb->data; + uint8_t *pmptr = (uint8_t*)bmptr; + + int x_offset = 0; + int y_offset = 0; + + bbptr += bb->pitch * y_offset; + int x, y; + for(y = y_offset; y < bb->h; y++) { + for(x = x_offset/2; x < (bb->w/2); x++) { + int p = x*2 - x_offset; + bbptr[x] = (((pmptr[p + 1] & 0xF0) >> 4) | (pmptr[p] & 0xF0)) ^ 0xFF; + } + bbptr += bb->pitch; + pmptr += bb->w; + if (bb->w & 1) { + bbptr[x] = 255 - (pmptr[x*2] & 0xF0); + } + } + + return 0; +} + static int drawPage(lua_State *L) { fz_pixmap *pix; fz_device *dev; @@ -657,6 +748,8 @@ static const struct luaL_Reg pdfpage_meth[] = { {"getPageLinks", getPageLinks}, {"close", closePage}, {"__gc", closePage}, + {"reflow", reflowPage}, + {"rfdraw", drawReflowedPage}, {"draw", drawPage}, {NULL, NULL} }; From da88ca7a092fdbe22514362fddd9a82e193cc5b3 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Tue, 16 Oct 2012 12:50:02 +0100 Subject: [PATCH 29/70] Get rid of unused variable in getCacheSize() --- pic.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pic.c b/pic.c index 4035fdd3f..aaec55744 100644 --- a/pic.c +++ b/pic.c @@ -53,7 +53,7 @@ static uint8_t *rgbToGrayscale(uint8_t *image, int width, int height) if (!buf) return NULL; - for (x = 0; x Date: Tue, 16 Oct 2012 23:31:18 +0800 Subject: [PATCH 30/70] add zoom in/out in reflow mode && PGBCK/PGFWD now work as expect Conflicts: pdfreader.lua --- pdf.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pdf.c b/pdf.c index 97ddd7c4e..fa363e612 100644 --- a/pdf.c +++ b/pdf.c @@ -522,8 +522,9 @@ static int reflowPage(lua_State *L) { PdfPage *page = (PdfPage*) luaL_checkudata(L, 1, "pdfpage"); DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); + double zoom = (double) luaL_checknumber(L, 3); - double dpi = 200; + double dpi = 250*zoom; double dpp; dpp = dpi / 72.; pix = NULL; @@ -572,15 +573,14 @@ static int reflowPage(lua_State *L) { } static int drawReflowedPage(lua_State *L) { - static unsigned char *bmptr = NULL; + uint8_t *pmptr = NULL; PdfPage *page = (PdfPage*) luaL_checkudata(L, 1, "pdfpage"); DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); BlitBuffer *bb = (BlitBuffer*) luaL_checkudata(L, 3, "blitbuffer"); - k2pdfopt_mupdf_rfbmp_ptr(&bmptr); - uint8_t *bbptr = (uint8_t*)bb->data; - uint8_t *pmptr = (uint8_t*)bmptr; + uint8_t *bbptr = bb->data; + k2pdfopt_mupdf_rfbmp_ptr(&pmptr); int x_offset = 0; int y_offset = 0; From 503a76db9ac0b5da096a7b3215c865aff595864a Mon Sep 17 00:00:00 2001 From: chrox Date: Thu, 18 Oct 2012 01:10:26 +0800 Subject: [PATCH 31/70] Cleanup: pass globalzoom to reflow in dc Conflicts: pdfreader.lua --- pdf.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pdf.c b/pdf.c index fa363e612..ea6981758 100644 --- a/pdf.c +++ b/pdf.c @@ -522,9 +522,8 @@ static int reflowPage(lua_State *L) { PdfPage *page = (PdfPage*) luaL_checkudata(L, 1, "pdfpage"); DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); - double zoom = (double) luaL_checknumber(L, 3); - double dpi = 250*zoom; + double dpi = 250*(dc->zoom); double dpp; dpp = dpi / 72.; pix = NULL; From 441d06166417c65be90a7e11d995656ee1ddd385 Mon Sep 17 00:00:00 2001 From: chrox Date: Fri, 19 Oct 2012 01:20:48 +0800 Subject: [PATCH 32/70] add DJVU reflow Conflicts: djvu.c koptreader.lua reader.lua --- Makefile | 2 +- djvu.c | 60 ++++++++++++++++++++++++----- k2pdfopt.c | 108 ++++++++++++++++++++++++++++++++++++++++++----------- k2pdfopt.h | 9 +++-- pdf.c | 7 ++-- 5 files changed, 146 insertions(+), 40 deletions(-) diff --git a/Makefile b/Makefile index 9f2c372fd..b911d4c86 100644 --- a/Makefile +++ b/Makefile @@ -157,7 +157,7 @@ kpdfview.o pdf.o blitbuffer.o util.o drawcontext.o einkfb.o input.o mupdfimg.o: $(CC) -c $(KPDFREADER_CFLAGS) $(EMU_CFLAGS) -I$(LFSDIR)/src $< -o $@ k2pdfopt.o: %.o: %.c - $(CC) -c -I$(MUPDFDIR)/ $(CFLAGS) $< -o $@ + $(CC) -c -I$(MUPDFDIR)/ -I$(DJVUDIR)/ $(CFLAGS) $< -o $@ djvu.o: %.o: %.c $(CC) -c $(KPDFREADER_CFLAGS) -I$(DJVUDIR)/ $< -o $@ diff --git a/djvu.c b/djvu.c index c2ba5a5ca..535e366fe 100644 --- a/djvu.c +++ b/djvu.c @@ -470,16 +470,54 @@ static int closePage(lua_State *L) { return 0; } -/* draw part of the page to bb. - * - * @page: DjvuPage user data - * @dc: DrawContext user data - * @bb: BlitBuffer user data - * @x: x offset within zoomed page - * @y: y offset within zoomed page - * - * width and height for the visible_area is obtained from bb. - */ +static int reflowPage(lua_State *L) { + + DjvuPage *page = (DjvuPage*) luaL_checkudata(L, 1, "djvupage"); + DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); + ddjvu_render_mode_t mode = (int) luaL_checkint(L, 3); + + double dpi = 250*(dc->zoom); + + int width, height; + k2pdfopt_djvu_reflow(page->page_ref, page->doc->context, mode, page->doc->pixelformat, dpi); + k2pdfopt_rfbmp_size(&width, &height); + + lua_pushnumber(L, (double)width); + lua_pushnumber(L, (double)height); + + return 2; +} + +static int drawReflowedPage(lua_State *L) { + uint8_t *pmptr = NULL; + + DjvuPage *page = (DjvuPage*) luaL_checkudata(L, 1, "djvupage"); + DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); + BlitBuffer *bb = (BlitBuffer*) luaL_checkudata(L, 3, "blitbuffer"); + + uint8_t *bbptr = bb->data; + k2pdfopt_rfbmp_ptr(&pmptr); + + int x_offset = 0; + int y_offset = 0; + + bbptr += bb->pitch * y_offset; + int x, y; + for(y = y_offset; y < bb->h; y++) { + for(x = x_offset/2; x < (bb->w/2); x++) { + int p = x*2 - x_offset; + bbptr[x] = (((pmptr[p + 1] & 0xF0) >> 4) | (pmptr[p] & 0xF0)) ^ 0xFF; + } + bbptr += bb->pitch; + pmptr += bb->w; + if (bb->w & 1) { + bbptr[x] = 255 - (pmptr[x*2] & 0xF0); + } + } + + return 0; +} + static int drawPage(lua_State *L) { DjvuPage *page = (DjvuPage*) luaL_checkudata(L, 1, "djvupage"); DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); @@ -605,6 +643,8 @@ static const struct luaL_Reg djvupage_meth[] = { {"getUsedBBox", getUsedBBox}, {"close", closePage}, {"__gc", closePage}, + {"reflow", reflowPage}, + {"rfdraw", drawReflowedPage}, {"draw", drawPage}, {NULL, NULL} }; diff --git a/k2pdfopt.c b/k2pdfopt.c index d982d6f9f..aff875d4c 100644 --- a/k2pdfopt.c +++ b/k2pdfopt.c @@ -30,13 +30,13 @@ */ // #define WILLUSDEBUGX 32 // #define WILLUSDEBUG -#include "k2pdfopt.h" #include #include #include #include #include #include +#include "k2pdfopt.h" #define HAVE_MUPDF @@ -408,43 +408,33 @@ static void bmp_contrast_adjust(WILLUSBITMAP *dest,WILLUSBITMAP *src,double cont static void bmp_convert_to_greyscale_ex(WILLUSBITMAP *dst, WILLUSBITMAP *src); static int bmpmupdf_pixmap_to_bmp(WILLUSBITMAP *bmp, fz_context *ctx, fz_pixmap *pixmap); +static void handle(int wait, ddjvu_context_t *ctx); static MASTERINFO _masterinfo, *masterinfo; -static WILLUSBITMAP _bmp, *bmp; static int master_bmp_inited = 0; static int master_bmp_width = 0; static int master_bmp_height = 0; -void k2pdfopt_mupdf_reflow_bmp(fz_context *ctx, fz_pixmap *pix, double rot_deg) { +static void k2pdfopt_reflow_bmp(MASTERINFO *masterinfo, WILLUSBITMAP *src) { PAGEINFO _pageinfo, *pageinfo; - WILLUSBITMAP _src, *src; WILLUSBITMAP _srcgrey, *srcgrey; - int i, status, white, pw, np, src_type, or_detect, orep_detect, - second_time_through; - int pagecount, pagestep, pages_done, is_gray, dpi; - double size, area_ratio, bormean; + int i, white, dpi; + double area_ratio; - masterinfo = &_masterinfo; masterinfo->debugfolder[0] = '\0'; - second_time_through = 0; white = src_whitethresh; /* Will be set by adjust_contrast() or set to src_whitethresh */ dpi = src_dpi; adjust_params_init(); set_region_widths(); - bmp = &_bmp; - src = &_src; srcgrey = &_srcgrey; - if (master_bmp_inited == 0) { bmp_init(&masterinfo->bmp); master_bmp_inited = 1; } - // free last used master bmp - bmp_free(&masterinfo->bmp); + bmp_free(&masterinfo->bmp); bmp_init(&masterinfo->bmp); - bmp_init(src); bmp_init(srcgrey); wrapbmp_init(); @@ -462,8 +452,6 @@ void k2pdfopt_mupdf_reflow_bmp(fz_context *ctx, fz_pixmap *pix, double rot_deg) bmp_fill(&masterinfo->bmp, 255, 255, 255); BMPREGION region; - - status = bmpmupdf_pixmap_to_bmp(src, ctx, pix); bmp_copy(srcgrey, src); adjust_contrast(src, srcgrey, &white); white_margins(src, srcgrey); @@ -479,22 +467,72 @@ void k2pdfopt_mupdf_reflow_bmp(fz_context *ctx, fz_pixmap *pix, double rot_deg) masterinfo->bgcolor = white; masterinfo->fit_to_page = dst_fit_to_page; /* Check to see if master bitmap might need more room */ - bmpregion_multicolumn_add(®ion, masterinfo, 1, pageinfo, - pages_done == 0. ? 0. : (int) (0.25 * src_dpi + .5)); + bmpregion_multicolumn_add(®ion, masterinfo, 1, pageinfo, (int) (0.25 * src_dpi + .5)); master_bmp_width = masterinfo->bmp.width; master_bmp_height = masterinfo->rows; bmp_free(srcgrey); +} + +void k2pdfopt_mupdf_reflow(fz_context *ctx, fz_pixmap *pix, double rot_deg) { + WILLUSBITMAP _src, *src; + src = &_src; + masterinfo = &_masterinfo; + bmp_init(src); + int status = bmpmupdf_pixmap_to_bmp(src, ctx, pix); + k2pdfopt_reflow_bmp(masterinfo, src); bmp_free(src); } -void k2pdfopt_mupdf_rfbmp_size(int *width, int *height) { +void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ + ddjvu_render_mode_t mode, ddjvu_format_t *fmt, double dpi) { + WILLUSBITMAP _src, *src; + ddjvu_rect_t prect; + ddjvu_rect_t rrect; + int i, iw, ih, idpi, status; + + while (!ddjvu_page_decoding_done(page)) + handle(1, ctx); + + iw = ddjvu_page_get_width(page); + ih = ddjvu_page_get_height(page); + idpi = ddjvu_page_get_resolution(page); + prect.x = prect.y = 0; + prect.w = iw * dpi / idpi; + prect.h = ih * dpi / idpi; + rrect = prect; + + src = &_src; + masterinfo = &_masterinfo; + bmp_init(src); + + src->width = prect.w = iw * dpi / idpi; + src->height = prect.h = ih * dpi / idpi; + src->bpp = 8; + rrect = prect; + bmp_alloc(src); + if (src->bpp == 8) { + int ii; + for (ii = 0; ii < 256; ii++) + src->red[ii] = src->blue[ii] = src->green[ii] = ii; + } + + ddjvu_format_set_row_order(fmt, 1); + + status = ddjvu_page_render(page, mode, &prect, &rrect, fmt, + bmp_bytewidth(src), (char *) src->data); + + k2pdfopt_reflow_bmp(masterinfo, src); + bmp_free(src); +} + +void k2pdfopt_rfbmp_size(int *width, int *height) { *width = master_bmp_width; *height = master_bmp_height; } -void k2pdfopt_mupdf_rfbmp_ptr(unsigned char** bmp_ptr_ptr) { +void k2pdfopt_rfbmp_ptr(unsigned char** bmp_ptr_ptr) { *bmp_ptr_ptr = masterinfo->bmp.data; } @@ -6336,3 +6374,29 @@ static int bmpmupdf_pixmap_to_bmp(WILLUSBITMAP *bmp, fz_context *ctx, } return (0); } + +static void handle(int wait, ddjvu_context_t *ctx) + { + const ddjvu_message_t *msg; + + if (!ctx) + return; + if (wait) + msg = ddjvu_message_wait(ctx); + while ((msg = ddjvu_message_peek(ctx))) + { + switch(msg->m_any.tag) + { + case DDJVU_ERROR: + fprintf(stderr,"ddjvu: %s\n", msg->m_error.message); + if (msg->m_error.filename) + fprintf(stderr,"ddjvu: '%s:%d'\n", + msg->m_error.filename, msg->m_error.lineno); + exit(10); + default: + break; + } + } + ddjvu_message_pop(ctx); +} + diff --git a/k2pdfopt.h b/k2pdfopt.h index 6067ff165..ae46de0ed 100644 --- a/k2pdfopt.h +++ b/k2pdfopt.h @@ -24,10 +24,13 @@ #define _K2PDFOPT_H #include +#include -void k2pdfopt_mupdf_reflow_bmp(fz_context *ctx, fz_pixmap *pix, double rot_deg); -void k2pdfopt_mupdf_rfbmp_size(int *width, int *height); -void k2pdfopt_mupdf_rfbmp_ptr(unsigned char** bmp_ptr_ptr); +void k2pdfopt_mupdf_reflow(fz_context *ctx, fz_pixmap *pix, double rot_deg); +void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ + ddjvu_render_mode_t mode, ddjvu_format_t *fmt, double dpi); +void k2pdfopt_rfbmp_size(int *width, int *height); +void k2pdfopt_rfbmp_ptr(unsigned char** bmp_ptr_ptr); #endif diff --git a/pdf.c b/pdf.c index ea6981758..314ce1c83 100644 --- a/pdf.c +++ b/pdf.c @@ -20,7 +20,6 @@ #include "blitbuffer.h" #include "drawcontext.h" #include "pdf.h" -#include "k2pdfopt.h" #include #include #include @@ -560,8 +559,8 @@ static int reflowPage(lua_State *L) { fz_gamma_pixmap(page->doc->context, pix, dc->gamma); } int width, height; - k2pdfopt_mupdf_reflow_bmp(page->doc->context, pix, 0); - k2pdfopt_mupdf_rfbmp_size(&width, &height); + k2pdfopt_mupdf_reflow(page->doc->context, pix, 0); + k2pdfopt_rfbmp_size(&width, &height); lua_pushnumber(L, (double)width); lua_pushnumber(L, (double)height); @@ -579,7 +578,7 @@ static int drawReflowedPage(lua_State *L) { BlitBuffer *bb = (BlitBuffer*) luaL_checkudata(L, 3, "blitbuffer"); uint8_t *bbptr = bb->data; - k2pdfopt_mupdf_rfbmp_ptr(&pmptr); + k2pdfopt_rfbmp_ptr(&pmptr); int x_offset = 0; int y_offset = 0; From 9e8a92862c3cfb2b1f8737c29c64502c8873dccc Mon Sep 17 00:00:00 2001 From: chrox Date: Fri, 19 Oct 2012 01:47:41 +0800 Subject: [PATCH 33/70] add koptreader.lua file in customupdate Conflicts: Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b911d4c86..afaea7bd5 100644 --- a/Makefile +++ b/Makefile @@ -140,7 +140,7 @@ kpdfview: kpdfview.o einkfb.o pdf.o k2pdfopt.o blitbuffer.o drawcontext.o input. $(STATICLIBSTDCPP) \ $(LDFLAGS) \ -o $@ \ - -lm -ldl -lpthread \ + -lm -ldl -lpthread -ljpeg -L$(MUPDFLIBDIR) \ $(EMU_LDFLAGS) \ $(DYNAMICLIBSTDCPP) From 7fb6f165521873ca621a2b9d4f6ef45d669872ba Mon Sep 17 00:00:00 2001 From: chrox Date: Sat, 20 Oct 2012 16:43:34 +0800 Subject: [PATCH 34/70] add page size limit in reading pdf/djvu files --- k2pdfopt.c | 77 +++++++++++++++++++++++++++++++++++++++++++----------- k2pdfopt.h | 3 ++- pdf.c | 44 +------------------------------ 3 files changed, 65 insertions(+), 59 deletions(-) diff --git a/k2pdfopt.c b/k2pdfopt.c index aff875d4c..24146abe9 100644 --- a/k2pdfopt.c +++ b/k2pdfopt.c @@ -90,18 +90,10 @@ ** Constants from the front of the CRC standard math tables ** (Accuracy = 50 digits) */ -/* The 50 digits cause problems with MPW's Mr. C on the Macintosh, */ -/* so I've truncated to 20 digits. */ -/* - #define PI 3.14159265358979323846264338327950288419716939937511 - #define SQRT2 1.41421356237309504880168872420969807856967187537695 - #define SQRT3 1.73205080756887729352744634150587236694280525381039 - #define LOG10E 0.43429448190325182765112891891660508229439700580367 - */ -#define PI 3.1415926535897932384 -#define SQRT2 1.4142135623730950488 -#define SQRT3 1.7320508075688772935 -#define LOG10E 0.4342944819032518276 +#define PI 3.14159265358979323846264338327950288419716939937511 +#define SQRT2 1.41421356237309504880168872420969807856967187537695 +#define SQRT3 1.73205080756887729352744634150587236694280525381039 +#define LOG10E 0.43429448190325182765112891891660508229439700580367 #define DBPERNEP (20.*LOG10E) #define SRC_TYPE_PDF 1 @@ -414,6 +406,9 @@ static MASTERINFO _masterinfo, *masterinfo; static int master_bmp_inited = 0; static int master_bmp_width = 0; static int master_bmp_height = 0; +static int max_page_width_pix = 3000; +static int max_page_height_pix = 4000; +static double shrink_factor = 0.9; static void k2pdfopt_reflow_bmp(MASTERINFO *masterinfo, WILLUSBITMAP *src) { PAGEINFO _pageinfo, *pageinfo; @@ -475,14 +470,62 @@ static void k2pdfopt_reflow_bmp(MASTERINFO *masterinfo, WILLUSBITMAP *src) { bmp_free(srcgrey); } -void k2pdfopt_mupdf_reflow(fz_context *ctx, fz_pixmap *pix, double rot_deg) { +void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ + double dpi, double gamma, double rot_deg) { + fz_device *dev; + fz_pixmap *pix; + fz_rect bounds,bounds2; + fz_matrix ctm; + fz_bbox bbox; WILLUSBITMAP _src, *src; + + double dpp; + do { + dpp = dpi / 72.; + pix = NULL; + fz_var(pix); + bounds = fz_bound_page(doc, page); + ctm = fz_scale(dpp, dpp); + // ctm=fz_concat(ctm,fz_rotate(rotation)); + bounds2 = fz_transform_rect(ctm, bounds); + bbox = fz_round_rect(bounds2); + printf("reading page:%d,%d,%d,%d dpi:%.0f\n",bbox.x0,bbox.y0,bbox.x1,bbox.y1,dpi); + dpi = dpi*shrink_factor; + } while (bbox.x1 > max_page_width_pix | bbox.y1 > max_page_height_pix); + // ctm=fz_translate(0,-page->mediabox.y1); + // ctm=fz_concat(ctm,fz_scale(dpp,-dpp)); + // ctm=fz_concat(ctm,fz_rotate(page->rotate)); + // ctm=fz_concat(ctm,fz_rotate(0)); + // bbox=fz_round_rect(fz_transform_rect(ctm,page->mediabox)); + // pix=fz_new_pixmap_with_rect(colorspace,bbox); + pix = fz_new_pixmap_with_bbox(ctx, fz_device_gray, bbox); + fz_clear_pixmap_with_value(ctx, pix, 0xff); + dev = fz_new_draw_device(ctx, pix); +#ifdef MUPDF_TRACE + fz_device *tdev; + fz_try(ctx) { + tdev = fz_new_trace_device(ctx); + fz_run_page(doc, page, tdev, ctm, NULL); + } + fz_always(ctx) { + fz_free_device(tdev); + } +#endif + fz_run_page(doc, page, dev, ctm, NULL); + fz_free_device(dev); + + if(gamma >= 0.0) { + fz_gamma_pixmap(ctx, pix, gamma); + } + src = &_src; masterinfo = &_masterinfo; bmp_init(src); int status = bmpmupdf_pixmap_to_bmp(src, ctx, pix); k2pdfopt_reflow_bmp(masterinfo, src); bmp_free(src); + + fz_drop_pixmap(ctx, pix); } void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ @@ -499,8 +542,12 @@ void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ ih = ddjvu_page_get_height(page); idpi = ddjvu_page_get_resolution(page); prect.x = prect.y = 0; - prect.w = iw * dpi / idpi; - prect.h = ih * dpi / idpi; + do { + prect.w = iw * dpi / idpi; + prect.h = ih * dpi / idpi; + printf("reading page:%d,%d,%d,%d dpi:%.0f\n",prect.x,prect.y,prect.w,prect.h,dpi); + dpi = dpi*shrink_factor; + } while (prect.w > max_page_width_pix | prect.h > max_page_height_pix); rrect = prect; src = &_src; diff --git a/k2pdfopt.h b/k2pdfopt.h index ae46de0ed..61ec5c9bc 100644 --- a/k2pdfopt.h +++ b/k2pdfopt.h @@ -26,7 +26,8 @@ #include #include -void k2pdfopt_mupdf_reflow(fz_context *ctx, fz_pixmap *pix, double rot_deg); +void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ + double dpi, double gamma, double rot_deg); void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ ddjvu_render_mode_t mode, ddjvu_format_t *fmt, double dpi); void k2pdfopt_rfbmp_size(int *width, int *height); diff --git a/pdf.c b/pdf.c index 314ce1c83..a55556e8c 100644 --- a/pdf.c +++ b/pdf.c @@ -512,61 +512,19 @@ static int closePage(lua_State *L) { } static int reflowPage(lua_State *L) { - fz_context *ctx; - fz_device *dev; - fz_pixmap *pix; - fz_rect bounds,bounds2; - fz_matrix ctm; - fz_bbox bbox; PdfPage *page = (PdfPage*) luaL_checkudata(L, 1, "pdfpage"); DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); double dpi = 250*(dc->zoom); - double dpp; - dpp = dpi / 72.; - pix = NULL; - fz_var(pix); - bounds = fz_bound_page(page->doc->xref, page->page); - ctm = fz_scale(dpp, dpp); - // ctm=fz_concat(ctm,fz_rotate(rotation)); - bounds2 = fz_transform_rect(ctm, bounds); - bbox = fz_round_rect(bounds2); - // ctm=fz_translate(0,-page->mediabox.y1); - // ctm=fz_concat(ctm,fz_scale(dpp,-dpp)); - // ctm=fz_concat(ctm,fz_rotate(page->rotate)); - // ctm=fz_concat(ctm,fz_rotate(0)); - // bbox=fz_round_rect(fz_transform_rect(ctm,page->mediabox)); - // pix=fz_new_pixmap_with_rect(colorspace,bbox); - pix = fz_new_pixmap_with_bbox(page->doc->context, fz_device_gray, bbox); - printf("bbox:%d,%d,%d,%d\n",bbox.x0,bbox.y0,bbox.x1,bbox.y1); - fz_clear_pixmap_with_value(page->doc->context, pix, 0xff); - dev = fz_new_draw_device(page->doc->context, pix); -#ifdef MUPDF_TRACE - fz_device *tdev; - fz_try(page->doc->context) { - tdev = fz_new_trace_device(page->doc->context); - fz_run_page(page->doc->xref, page->page, tdev, ctm, NULL); - } - fz_always(page->doc->context) { - fz_free_device(tdev); - } -#endif - fz_run_page(page->doc->xref, page->page, dev, ctm, NULL); - fz_free_device(dev); - if(dc->gamma >= 0.0) { - fz_gamma_pixmap(page->doc->context, pix, dc->gamma); - } int width, height; - k2pdfopt_mupdf_reflow(page->doc->context, pix, 0); + k2pdfopt_mupdf_reflow(page->doc->xref, page->page, page->doc->context, dpi, dc->gamma, 0); k2pdfopt_rfbmp_size(&width, &height); lua_pushnumber(L, (double)width); lua_pushnumber(L, (double)height); - fz_drop_pixmap(page->doc->context, pix); - return 2; } From 7f4be723a04d8dfee0f71849813e85e46c386a62 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Sat, 20 Oct 2012 10:10:39 +0100 Subject: [PATCH 35/70] LuaJIT: make distclean -> make clean Two reasons for this change: 1. The latest LuaJIT has no "distclean" target 2. The "distclean" is MUCH slower --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index afaea7bd5..081f825a0 100644 --- a/Makefile +++ b/Makefile @@ -210,7 +210,7 @@ clean: rm -f *.o kpdfview slider_watcher cleanthirdparty: - $(MAKE) -C $(LUADIR) CC="$(HOSTCC)" CFLAGS="$(BASE_CFLAGS)" distclean + $(MAKE) -C $(LUADIR) CC="$(HOSTCC)" CFLAGS="$(BASE_CFLAGS)" clean $(MAKE) -C $(MUPDFDIR) build="release" clean $(MAKE) -C $(CRENGINEDIR)/thirdparty/antiword clean test -d $(CRENGINEDIR)/thirdparty/chmlib && $(MAKE) -C $(CRENGINEDIR)/thirdparty/chmlib clean || echo warn: chmlib folder not found From db3796cd24371867828135969d77a3d8fd4a1111 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Sun, 21 Oct 2012 12:15:56 +0100 Subject: [PATCH 36/70] Remove alt_getopt.lua module altogether. Conflicts: Makefile frontend/alt_getopt.lua --- frontend/alt_getopt.lua | 166 ---------------------------------------- 1 file changed, 166 deletions(-) delete mode 100644 frontend/alt_getopt.lua diff --git a/frontend/alt_getopt.lua b/frontend/alt_getopt.lua deleted file mode 100644 index 7a6591a49..000000000 --- a/frontend/alt_getopt.lua +++ /dev/null @@ -1,166 +0,0 @@ --- Copyright (c) 2009 Aleksey Cheusov --- --- Permission is hereby granted, free of charge, to any person obtaining --- a copy of this software and associated documentation files (the --- "Software"), to deal in the Software without restriction, including --- without limitation the rights to use, copy, modify, merge, publish, --- distribute, sublicense, and/or sell copies of the Software, and to --- permit persons to whom the Software is furnished to do so, subject to --- the following conditions: --- --- The above copyright notice and this permission notice shall be --- included in all copies or substantial portions of the Software. --- --- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, --- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF --- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND --- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE --- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION --- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION --- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -local type, pairs, ipairs, io, os = type, pairs, ipairs, io, os - -module ("alt_getopt") - -local function convert_short2long (opts) - local i = 1 - local len = #opts - local ret = {} - - for short_opt, accept_arg in opts:gmatch("(%w)(:?)") do - ret[short_opt]=#accept_arg - end - - return ret -end - -local function exit_with_error (msg, exit_status) - io.stderr:write (msg) - os.exit (exit_status) -end - -local function err_unknown_opt (opt) - exit_with_error ("Unknown option `-" .. - (#opt > 1 and "-" or "") .. opt .. "'\n", 1) -end - -local function canonize (options, opt) - if not options [opt] then - err_unknown_opt (opt) - end - - while type (options [opt]) == "string" do - opt = options [opt] - - if not options [opt] then - err_unknown_opt (opt) - end - end - - return opt -end - -function get_ordered_opts (arg, sh_opts, long_opts) - local i = 1 - local count = 1 - local opts = {} - local optarg = {} - - local options = convert_short2long (sh_opts) - for k,v in pairs (long_opts) do - options [k] = v - end - - while i <= #arg do - local a = arg [i] - - if a == "--" then - i = i + 1 - break - - elseif a == "-" then - break - - elseif a:sub (1, 2) == "--" then - local pos = a:find ("=", 1, true) - - if pos then - local opt = a:sub (3, pos-1) - - opt = canonize (options, opt) - - if options [opt] == 0 then - exit_with_error ("Bad usage of option `" .. a .. "'\n", 1) - end - - optarg [count] = a:sub (pos+1) - opts [count] = opt - else - local opt = a:sub (3) - - opt = canonize (options, opt) - - if options [opt] == 0 then - opts [count] = opt - else - if i == #arg then - exit_with_error ("Missed value for option `" .. a .. "'\n", 1) - end - - optarg [count] = arg [i+1] - opts [count] = opt - i = i + 1 - end - end - count = count + 1 - - elseif a:sub (1, 1) == "-" then - local j - for j=2,a:len () do - local opt = canonize (options, a:sub (j, j)) - - if options [opt] == 0 then - opts [count] = opt - count = count + 1 - elseif a:len () == j then - if i == #arg then - exit_with_error ("Missed value for option `-" .. opt .. "'\n", 1) - end - - optarg [count] = arg [i+1] - opts [count] = opt - i = i + 1 - count = count + 1 - break - else - optarg [count] = a:sub (j+1) - opts [count] = opt - count = count + 1 - break - end - end - else - break - end - - i = i + 1 - end - - return opts,i,optarg -end - -function get_opts (arg, sh_opts, long_opts) - local ret = {} - - local opts,optind,optarg = get_ordered_opts (arg, sh_opts, long_opts) - for i,v in ipairs (opts) do - if optarg [i] then - ret [v] = optarg [i] - else - ret [v] = 1 - end - end - - return ret,optind -end From f70dc5486485e101938e87664d7dcbb8b828787c Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 31 Oct 2012 22:01:52 -0400 Subject: [PATCH 37/70] update getXPointer method definition --- cre.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cre.cpp b/cre.cpp index 0bf1ceb0c..8df2584e7 100644 --- a/cre.cpp +++ b/cre.cpp @@ -623,7 +623,7 @@ static const struct luaL_Reg credocument_meth[] = { {"getPosFromXPointer", getPosFromXPointer}, {"getCurrentPos", getCurrentPos}, {"getCurrentPercent", getCurrentPercent}, - {"getCurrentXPointer", getCurrentXPointer}, + {"getXPointer", getXPointer}, {"getFullHeight", getFullHeight}, {"getFontSize", getFontSize}, {"getFontFace", getFontFace}, From 2b185961e8dff79a4248b98c4c6345daa827d96a Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 31 Oct 2012 22:02:53 -0400 Subject: [PATCH 38/70] remove getopt lua module in reader.lua --- reader.lua | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/reader.lua b/reader.lua index d6ae51c9a..3b6eae487 100755 --- a/reader.lua +++ b/reader.lua @@ -74,11 +74,8 @@ function showusage() print("usage: ./reader.lua [OPTION] ... path") print("Read all the books on your E-Ink reader") print("") - print("-p, --password=PASSWORD set password for reading PDF document") - print("-G, --gamma=GAMMA set gamma correction") - print(" (floating point notation, e.g. \"1.5\")") - print("-d, --debug start in debug mode") - print("-h, --help show this usage help") + print("-d start in debug mode") + print("-h show this usage help") print("") print("If you give the name of a directory instead of a file path, a file") print("chooser will show up and let you select a file") @@ -92,19 +89,17 @@ end optarg, optind = alt_getopt.get_opts(ARGV, "p:G:hg:dg:", longopts) -if optarg["h"] then +if ARGV[1] == "-h" then return showusage() end -if not optarg["d"] then +local argidx = 1 +if ARGV[1] == "-d" then + argidx = argidx + 1 +else DEBUG = function() end end -if optarg["G"] ~= nil then - globalgamma = optarg["G"] -end - - if Device.isKindle4() then -- remove menu item shortcut for K4 Menu.is_enable_shortcut = false @@ -125,11 +120,11 @@ Screen.native_rotation_mode = Screen.cur_rotation_mode --87712cf0e43fed624f8a9f610be42b1fe174b9fe -if ARGV[optind] then - if lfs.attributes(ARGV[optind], "mode") == "directory" then - showFileManager(ARGV[optind]) - elseif lfs.attributes(ARGV[optind], "mode") == "file" then - showReader(ARGV[optind], optarg["p"]) +if ARGV[argidx] then + if lfs.attributes(ARGV[argidx], "mode") == "directory" then + showFileManager(ARGV[argidx]) + elseif lfs.attributes(ARGV[argidx], "mode") == "file" then + showReader(ARGV[argidx], optarg["p"]) end UIManager:run() elseif last_file and lfs.attributes(last_file, "mode") == "file" then From 503c9b077d546ec8d33d09461a46a271bb48b1b9 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Mon, 8 Oct 2012 16:39:03 +0100 Subject: [PATCH 39/70] Initial commit of picviewer. Conflicts: Makefile extentions.lua pic.c reader.lua --- kpdfview.c | 2 ++ pic.c | 26 -------------------------- pic.h | 27 +++++++++++++++++++++++++++ picviewer.lua | 11 +++++++++++ resources/jpg.png | Bin 0 -> 1117 bytes 5 files changed, 40 insertions(+), 26 deletions(-) create mode 100644 pic.h create mode 100644 picviewer.lua create mode 100644 resources/jpg.png diff --git a/kpdfview.c b/kpdfview.c index 94b33ff1a..4a3bd7b33 100644 --- a/kpdfview.c +++ b/kpdfview.c @@ -28,6 +28,7 @@ #include "pdf.h" #include "mupdfimg.h" #include "djvu.h" +#include "pic.h" #include "cre.h" #include "einkfb.h" #include "input.h" @@ -96,6 +97,7 @@ int main(int argc, char **argv) { luaopen_einkfb(L); luaopen_pdf(L); luaopen_djvu(L); + luaopen_pic(L); luaopen_cre(L); luaopen_input(L); luaopen_util(L); diff --git a/pic.c b/pic.c index aaec55744..b40e47752 100644 --- a/pic.c +++ b/pic.c @@ -20,7 +20,6 @@ #include #include #include - #include "blitbuffer.h" #include "drawcontext.h" #include "pic.h" @@ -87,11 +86,6 @@ static int openDocument(lua_State *L) { } else { free(raw_image); return luaL_error(L, "Unsupported image format"); - } - - doc->width = width; - doc->height = height; - doc->components = components; return 1; } @@ -171,26 +165,6 @@ static int drawPage(lua_State *L) { scaleImage(scaled_image, page->image, img_width, img_height, img_new_width, img_new_height); - uint8_t *bbptr = bb->data; - uint8_t *pmptr = scaled_image; - bbptr += bb->pitch * y_offset; - for(y = y_offset; y < img_new_height; y++) { - for(x = x_offset/2; x < (img_new_width / 2); x++) { - int p = x*2 - x_offset; - unsigned char low = 15 - (pmptr[p + 1] >> 4); - unsigned char high = 15 - (pmptr[p] >> 4); - if (adjust_pixels) - bbptr[x] = adjusted_high[high] | adjusted_low[low]; - else - bbptr[x] = (high << 4) | low; - } - if (img_new_width & 1) - bbptr[x] = 255 - (pmptr[x*2] & 0xF0); - bbptr += bb->pitch; - pmptr += img_new_width; - } - - free(scaled_image); return 0; } diff --git a/pic.h b/pic.h new file mode 100644 index 000000000..ee6e099a5 --- /dev/null +++ b/pic.h @@ -0,0 +1,27 @@ +/* + KindlePDFViewer: JPEG Picture viewer abstraction for Lua + Copyright (C) 2012 Tigran Aivazian + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef _JPG_H +#define _JPG_H + +#include +#include +#include + +int luaopen_pic(lua_State *L); + +#endif diff --git a/picviewer.lua b/picviewer.lua new file mode 100644 index 000000000..cc3edbd99 --- /dev/null +++ b/picviewer.lua @@ -0,0 +1,11 @@ +require "unireader" + +PICViewer = UniReader:new{} + +function PICViewer:open(filename) + ok, self.doc = pcall(pic.openDocument, filename) + if not ok then + return ok, self.doc + end + return ok +end diff --git a/resources/jpg.png b/resources/jpg.png new file mode 100644 index 0000000000000000000000000000000000000000..f038383226f839382308b40df1bf3d5866fdc1d1 GIT binary patch literal 1117 zcmaJ;SxggA6ul~_REQfwaLfFN3$4zSN?=?)>@;2k`B}9w9@v~>C_e%LMn#My>D-Q)KFhMJCiGB^-DB) zS&CYo{S=KfWTYLdIr5;SxHhQN9~U3% zHEQ$NC#)q^nY7K9q(@fE*qqTLS7x!*%SX39ZCrz%IZDs8w^;7#_j`N7cloPLmTS9i z9^JX8?)&A7`L;K0(MEr5Q&Dg5eAA0iC~`kN)M?K>*V7d_Uc9n|)TwqG17Cl3Z`m;6 z^Xtof{-1p%iOOjMKZ-tn>z?wYIyq<<7+>+DxiPIxZrUhQwWNglzjQUUHgCwk6*Qda zSQ6;z?rtBGe9(~I(Oh(AdqhdBtIyY?$R%HX<{o3)sqBm47kRfU)io^-Rk?EcLf1sF zh3yBc?wqW^!^uVMMv+A>7v~mXxx>j3e!(G%oSP^uC)8F{$`M985|P)AJoSWXKI#9q z`4HC4l5|!^Z{NDmGTDKVs zQez!h+J*!dBZ9-l@Z5r}9*%!*>Aa8ei4rd5G>e=_@Ol^)yO8_VpT!{Y2lO&7kJI7i z6*h(ykpRkoQceXZ>F9gwZpOKu_>i;)PqQpGlNEa;a}*S%AeBe`@)?*rU3&K~SI03{ z{5S`z5RZrzMr~p1g41=Fmo}WYUf?-5TMlmI<~l$0lwrnXi6K~RwAv|xjF7(AJ?Z Date: Mon, 8 Oct 2012 23:00:28 +0100 Subject: [PATCH 40/70] Fix the leftover from the old name jpg.h --- pic.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pic.h b/pic.h index ee6e099a5..b7a611d5b 100644 --- a/pic.h +++ b/pic.h @@ -15,8 +15,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#ifndef _JPG_H -#define _JPG_H +#ifndef _PIC_H +#define _PIC_H #include #include From 75f2c2774fd24edbe305a1a61199f460bcd9a4a2 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 31 Oct 2012 22:15:02 -0400 Subject: [PATCH 41/70] fix getopt module clearance in reader.lua --- reader.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/reader.lua b/reader.lua index 3b6eae487..0e82245e0 100755 --- a/reader.lua +++ b/reader.lua @@ -6,7 +6,6 @@ require "ui/readerui" require "ui/filechooser" require "ui/infomessage" require "document/document" -require "alt_getopt" function showReader(file, pass) local document = DocumentRegistry:openDocument(file) @@ -87,8 +86,6 @@ function showusage() return end -optarg, optind = alt_getopt.get_opts(ARGV, "p:G:hg:dg:", longopts) - if ARGV[1] == "-h" then return showusage() end From fc20c0af26d0db47c158b22594c85858c1c75d05 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Wed, 24 Oct 2012 11:24:05 +0100 Subject: [PATCH 42/70] Support for extracting attachments from PDF file This commit adds Alt-S command which invokes the external utility "extr" passing it the full pathname of the open PDF file and the current page number. The utility extracts all attachments on this page (if there are any) and saves them in the same directory as the PDF file. The file names given to attachments are decoded from within the PDF file itself, i.e. they are the same as the original file names of the files embedded in the PDF. Conflicts: pdfreader.lua --- Makefile | 17 ++++++--- extr.c | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 extr.c diff --git a/Makefile b/Makefile index 081f825a0..b92c0c7e9 100644 --- a/Makefile +++ b/Makefile @@ -109,7 +109,7 @@ LUALIB := $(LUADIR)/src/libluajit.a POPENNSLIB := $(POPENNSDIR)/libpopen_noshell.a -all: kpdfview +all: kpdfview extr VERSION?=$(shell git describe HEAD) kpdfview: kpdfview.o einkfb.o pdf.o k2pdfopt.o blitbuffer.o drawcontext.o input.o $(POPENNSLIB) util.o ft.o lfs.o mupdfimg.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) djvu.o $(DJVULIBS) cre.o $(CRENGINELIBS) pic.o pic_jpeg.o @@ -144,6 +144,12 @@ kpdfview: kpdfview.o einkfb.o pdf.o k2pdfopt.o blitbuffer.o drawcontext.o input. $(EMU_LDFLAGS) \ $(DYNAMICLIBSTDCPP) +extr: extr.o + $(CC) $(CFLAGS) extr.o $(MUPDFLIBS) $(THIRDPARTYLIBS) -lm -o extr + +extr.o: %.o: %.c + $(CC) -c -I$(MUPDFDIR)/pdf -I$(MUPDFDIR)/fitz $< -o $@ + slider_watcher.o: %.o: %.c $(CC) -c $(CFLAGS) $< -o $@ @@ -207,7 +213,7 @@ fetchthirdparty: cd popen-noshell && test -f Makefile || patch -N -p0 < popen_noshell-buildfix.patch clean: - rm -f *.o kpdfview slider_watcher + rm -f *.o kpdfview slider_watcher extr cleanthirdparty: $(MAKE) -C $(LUADIR) CC="$(HOSTCC)" CFLAGS="$(BASE_CFLAGS)" clean @@ -268,13 +274,14 @@ INSTALL_DIR=kindlepdfviewer LUA_FILES=reader.lua customupdate: all - # ensure that build binary is for ARM + # ensure that the binaries were built for ARM file kpdfview | grep ARM || exit 1 - $(STRIP) --strip-unneeded kpdfview + file extr | grep ARM || exit 1 + $(STRIP) --strip-unneeded kpdfview extr rm -f kindlepdfviewer-$(VERSION).zip rm -rf $(INSTALL_DIR) mkdir -p $(INSTALL_DIR)/{history,screenshots} - cp -p README.md COPYING kpdfview kpdf.sh $(LUA_FILES) $(INSTALL_DIR) + cp -p README.md COPYING kpdfview extr kpdf.sh $(LUA_FILES) $(INSTALL_DIR) mkdir $(INSTALL_DIR)/data cp -rpL data/*.css $(INSTALL_DIR)/data cp -rpL fonts $(INSTALL_DIR) diff --git a/extr.c b/extr.c new file mode 100644 index 000000000..c41debee6 --- /dev/null +++ b/extr.c @@ -0,0 +1,106 @@ +/* + extr: Extract attachments from PDF file + + Usage: extr /dir/file.pdf pageno + Returns 0 if one or more attachments saved, otherwise returns non-zero. + Prints the number of saved attachments on stdout. + Attachments are saved in /dir directory with the appropriate filenames. + + Copyright (C) 2012 Tigran Aivazian + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "mupdf-internal.h" +#include + +/* doc is shared between save_attachments() and dump_stream() */ +static pdf_document *doc; + +void dump_stream(int i, FILE *fout) +{ + fz_stream *stm = pdf_open_stream(doc, i, 0); + static unsigned char buf[8192]; + while (1) { + int n = fz_read(stm, buf, sizeof buf); + if (n == 0) break; + fwrite(buf, 1, n, fout); + } + fz_close(stm); +} + +/* returns the number of attachments saved */ +int save_attachments(int pageno, char *targetdir) +{ + pdf_page *page = pdf_load_page(doc, pageno-1); + pdf_annot *annot; + int saved_count = 0; + + for (annot = page->annots; annot ; annot = annot->next) { + pdf_obj *fs_obj = pdf_dict_gets(annot->obj, "FS"); + if (fs_obj) { + pdf_obj *ef_obj; + char *name = pdf_to_str_buf(pdf_dict_gets(fs_obj, "F")); + ef_obj = pdf_dict_gets(fs_obj, "EF"); + if (ef_obj) { + pdf_obj *f_obj = pdf_dict_gets(ef_obj, "F"); + if (f_obj && pdf_is_indirect(f_obj)) { + static char pathname[PATH_MAX]; + sprintf(pathname, "%s/%s", targetdir, name); + FILE *fout = fopen(pathname, "w"); + if (!fout) { + fprintf(stderr, "extr: cannot write to file %s\n", name); + exit(1); + } + dump_stream(pdf_to_num(f_obj), fout); + fclose(fout); + saved_count++; + } + } + } + } + return saved_count; +} + +int main(int argc, char *argv[]) +{ + int saved = 0; + + if (argc != 3) { + printf("Usage: extr file.pdf pageno\n"); + exit(1); + } + + char *filename = strdup(argv[1]); + char *dir = dirname(strdup(filename)); + int pageno = atoi(argv[2]); + + fz_context *ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED); + if (!ctx) { + fprintf(stderr, "extr: cannot create context\n"); + exit(1); + } + + fz_var(doc); + fz_try(ctx) { + doc = pdf_open_document(ctx, filename); + saved = save_attachments(pageno, dir); + } + fz_catch(ctx) + { + } + + printf("%d\n", saved); + return 0; +} From ff2391694062940649365d0550a05c16924ff027 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Wed, 24 Oct 2012 11:48:20 +0100 Subject: [PATCH 43/70] Correct the comment. The global variable "doc" is actually shared by ALL functions, so the old comment was incorrect. --- extr.c | 1 - 1 file changed, 1 deletion(-) diff --git a/extr.c b/extr.c index c41debee6..2bd69e4dc 100644 --- a/extr.c +++ b/extr.c @@ -25,7 +25,6 @@ #include "mupdf-internal.h" #include -/* doc is shared between save_attachments() and dump_stream() */ static pdf_document *doc; void dump_stream(int i, FILE *fout) From d90b01dbfd723c20aa61f692001d146d49d0324b Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Wed, 24 Oct 2012 14:13:12 +0100 Subject: [PATCH 44/70] Handle absolute attachment pathnames We must use only the basename part of the embedded attachment filename, because the directories used in the path on the machine where the attachments were created may not even exist on the machine where they are extracted. --- extr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extr.c b/extr.c index 2bd69e4dc..c73f86ce5 100644 --- a/extr.c +++ b/extr.c @@ -50,7 +50,7 @@ int save_attachments(int pageno, char *targetdir) pdf_obj *fs_obj = pdf_dict_gets(annot->obj, "FS"); if (fs_obj) { pdf_obj *ef_obj; - char *name = pdf_to_str_buf(pdf_dict_gets(fs_obj, "F")); + char *name = basename(strdup(pdf_to_str_buf(pdf_dict_gets(fs_obj, "F")))); ef_obj = pdf_dict_gets(fs_obj, "EF"); if (ef_obj) { pdf_obj *f_obj = pdf_dict_gets(ef_obj, "F"); From 622e2959b37242a2ea35bbeae9653027ad26423b Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Wed, 24 Oct 2012 14:28:43 +0100 Subject: [PATCH 45/70] Utility for attaching files inside PDF This utility creates a PDF file containing specified files embedded into it as attachments. The attachments can be extracted from the PDF file by using extr utility. I placed this in a separate "utils" directory to signify that this is NOT intended to run on a Kindle (it requires pdfLaTeX to generate the PDF file) but only on the desktop Linux workstation. --- utils/pdfattach | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100755 utils/pdfattach diff --git a/utils/pdfattach b/utils/pdfattach new file mode 100755 index 000000000..10a729513 --- /dev/null +++ b/utils/pdfattach @@ -0,0 +1,76 @@ +#!/bin/bash + +# +# pdfattach --- embed specified file(s) in a specified PDF file +# Requires pdfLaTeX and attachfile.sty package to run +# Returns 0 on success or >0 on error +# +# Written by Tigran Aivazian +# + +progname=$(basename $0) + +function usage() +{ + echo "Usage: $progname -o file.pdf -i file1.djvu [-i file2.mp3] ..." + exit 1 +} + +if (! getopts ":o:i:" opt); then + echo "$progname: Missing options." >&2 + usage +fi + +declare outfile="" +declare -a infiles=() +declare -i infcount=0 outfcount=0 + +while getopts ":i:o:" opt; do + case $opt in + o) + outfile="$OPTARG" + outbase=$(basename $outfile | cut -d'.' -f1) + targetdir=$(dirname $outfile) + ((outfcount++)) + ;; + i) + infiles[$infcount]=$(readlink -f "$OPTARG") + ((infcount++)) + ;; + \?) + echo "$progname: Invalid option: -$OPTARG" >&2 + usage + ;; + :) + echo "$progname: Option -$OPTARG requires an argument." >&2 + usage + ;; + esac +done + +if ((infcount == 0)) ; then + echo "$progname: No input file(s) specified." >&2 + usage +fi + +if ((outfcount != 1)) ; then + echo "$progname: One (and only one) output file must be specified." >&2 + usage +fi + + +workdir=$(mktemp --tmpdir -d pdfattach.XXXXXX) +cd $workdir +> ${outbase}.tex +echo -E "\documentclass{book}" >> ${outbase}.tex +echo -E "\usepackage{attachfile}" >> ${outbase}.tex +echo -E "\begin{document}" >> ${outbase}.tex +echo -E "\pagestyle{empty}" >> ${outbase}.tex +for ((i = 0 ; i < ${#infiles[*]} ; i++)); +do + echo "\attachfile{${infiles[$i]}}" >> ${outbase}.tex +done +echo -E "\end{document}" >> ${outbase}.tex +pdflatex -halt-on-error ${outbase} > /dev/null && mv ${outbase}.pdf $targetdir +cd - > /dev/null +rm -rf $workdir From ae950ccf3283c29910e0ea22c53703e984593677 Mon Sep 17 00:00:00 2001 From: chrox Date: Wed, 24 Oct 2012 22:01:36 +0800 Subject: [PATCH 46/70] feedback zoom value used by page reflow So that when zoom value exceed the upper limit of reflowable page size user will notice that zoom value cannot be increased. Conflicts: koptreader.lua --- djvu.c | 8 ++++---- k2pdfopt.c | 19 +++++++++++++++---- k2pdfopt.h | 1 + pdf.c | 8 ++++---- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/djvu.c b/djvu.c index 535e366fe..7c75158b8 100644 --- a/djvu.c +++ b/djvu.c @@ -476,16 +476,16 @@ static int reflowPage(lua_State *L) { DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); ddjvu_render_mode_t mode = (int) luaL_checkint(L, 3); - double dpi = 250*(dc->zoom); - int width, height; - k2pdfopt_djvu_reflow(page->page_ref, page->doc->context, mode, page->doc->pixelformat, dpi); + k2pdfopt_djvu_reflow(page->page_ref, page->doc->context, mode, page->doc->pixelformat, dc->zoom); k2pdfopt_rfbmp_size(&width, &height); + k2pdfopt_rfbmp_zoom(&dc->zoom); lua_pushnumber(L, (double)width); lua_pushnumber(L, (double)height); + lua_pushnumber(L, (double)dc->zoom); - return 2; + return 3; } static int drawReflowedPage(lua_State *L) { diff --git a/k2pdfopt.c b/k2pdfopt.c index 24146abe9..df1bfe35e 100644 --- a/k2pdfopt.c +++ b/k2pdfopt.c @@ -409,6 +409,7 @@ static int master_bmp_height = 0; static int max_page_width_pix = 3000; static int max_page_height_pix = 4000; static double shrink_factor = 0.9; +static double zoom_value = 1.0; static void k2pdfopt_reflow_bmp(MASTERINFO *masterinfo, WILLUSBITMAP *src) { PAGEINFO _pageinfo, *pageinfo; @@ -471,7 +472,7 @@ static void k2pdfopt_reflow_bmp(MASTERINFO *masterinfo, WILLUSBITMAP *src) { } void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ - double dpi, double gamma, double rot_deg) { + double zoom, double gamma, double rot_deg) { fz_device *dev; fz_pixmap *pix; fz_rect bounds,bounds2; @@ -480,6 +481,7 @@ void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ WILLUSBITMAP _src, *src; double dpp; + double dpi = 250*zoom; do { dpp = dpi / 72.; pix = NULL; @@ -490,7 +492,9 @@ void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ bounds2 = fz_transform_rect(ctm, bounds); bbox = fz_round_rect(bounds2); printf("reading page:%d,%d,%d,%d dpi:%.0f\n",bbox.x0,bbox.y0,bbox.x1,bbox.y1,dpi); - dpi = dpi*shrink_factor; + zoom_value = zoom; + zoom *= shrink_factor; + dpi *= zoom; } while (bbox.x1 > max_page_width_pix | bbox.y1 > max_page_height_pix); // ctm=fz_translate(0,-page->mediabox.y1); // ctm=fz_concat(ctm,fz_scale(dpp,-dpp)); @@ -529,11 +533,12 @@ void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ } void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ - ddjvu_render_mode_t mode, ddjvu_format_t *fmt, double dpi) { + ddjvu_render_mode_t mode, ddjvu_format_t *fmt, double zoom) { WILLUSBITMAP _src, *src; ddjvu_rect_t prect; ddjvu_rect_t rrect; int i, iw, ih, idpi, status; + double dpi = 250*zoom; while (!ddjvu_page_decoding_done(page)) handle(1, ctx); @@ -546,7 +551,9 @@ void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ prect.w = iw * dpi / idpi; prect.h = ih * dpi / idpi; printf("reading page:%d,%d,%d,%d dpi:%.0f\n",prect.x,prect.y,prect.w,prect.h,dpi); - dpi = dpi*shrink_factor; + zoom_value = zoom; + zoom *= shrink_factor; + dpi *= zoom; } while (prect.w > max_page_width_pix | prect.h > max_page_height_pix); rrect = prect; @@ -583,6 +590,10 @@ void k2pdfopt_rfbmp_ptr(unsigned char** bmp_ptr_ptr) { *bmp_ptr_ptr = masterinfo->bmp.data; } +void k2pdfopt_rfbmp_zoom(double *zoom) { + *zoom = zoom_value; +} + /* ansi.c */ #define MAXSIZE 8000 diff --git a/k2pdfopt.h b/k2pdfopt.h index 61ec5c9bc..1fef6d24d 100644 --- a/k2pdfopt.h +++ b/k2pdfopt.h @@ -32,6 +32,7 @@ void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ ddjvu_render_mode_t mode, ddjvu_format_t *fmt, double dpi); void k2pdfopt_rfbmp_size(int *width, int *height); void k2pdfopt_rfbmp_ptr(unsigned char** bmp_ptr_ptr); +void k2pdfopt_rfbmp_zoom(double *zoom); #endif diff --git a/pdf.c b/pdf.c index a55556e8c..b51d6a73a 100644 --- a/pdf.c +++ b/pdf.c @@ -516,16 +516,16 @@ static int reflowPage(lua_State *L) { PdfPage *page = (PdfPage*) luaL_checkudata(L, 1, "pdfpage"); DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); - double dpi = 250*(dc->zoom); - int width, height; - k2pdfopt_mupdf_reflow(page->doc->xref, page->page, page->doc->context, dpi, dc->gamma, 0); + k2pdfopt_mupdf_reflow(page->doc->xref, page->page, page->doc->context, dc->zoom, dc->gamma, 0); k2pdfopt_rfbmp_size(&width, &height); + k2pdfopt_rfbmp_zoom(&dc->zoom); lua_pushnumber(L, (double)width); lua_pushnumber(L, (double)height); + lua_pushnumber(L, (double)dc->zoom); - return 2; + return 3; } static int drawReflowedPage(lua_State *L) { From 543e7cc98e0422d4bdba6e6c7c73d16d8fab15ea Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Wed, 24 Oct 2012 15:57:42 +0100 Subject: [PATCH 47/70] Make the pdfattach utility more robust 1. Handle filenames with spaces 2. Check if pdflatex and attachfile packages are installed before trying to use them. 3. Use a more intuitive interface "pdfattach -o file.pdf file1.djvu [file2.djvu]..." instead of forcing the user to specify "-i" option for every input file. --- utils/pdfattach | 56 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/utils/pdfattach b/utils/pdfattach index 10a729513..6af91c98b 100755 --- a/utils/pdfattach +++ b/utils/pdfattach @@ -12,31 +12,35 @@ progname=$(basename $0) function usage() { - echo "Usage: $progname -o file.pdf -i file1.djvu [-i file2.mp3] ..." + echo "Usage: $progname -o file.pdf file1.djvu [file2.mp3] ..." exit 1 } -if (! getopts ":o:i:" opt); then +if (! getopts ":o:" opt); then echo "$progname: Missing options." >&2 usage fi +if ! type pdflatex > /dev/null 2>&1 ; then + echo "$progname: pdfLaTeX program is required." >&2 + exit 1 +fi + +if ! kpsewhich attachfile.sty > /dev/null 2>&1 ; then + echo "$progname: attachfile.sty package is required." >&2 + exit 1 +fi + declare outfile="" declare -a infiles=() declare -i infcount=0 outfcount=0 -while getopts ":i:o:" opt; do +while getopts ":o:" opt; do case $opt in o) - outfile="$OPTARG" - outbase=$(basename $outfile | cut -d'.' -f1) - targetdir=$(dirname $outfile) + outfile=$(readlink -f "$OPTARG") ((outfcount++)) ;; - i) - infiles[$infcount]=$(readlink -f "$OPTARG") - ((infcount++)) - ;; \?) echo "$progname: Invalid option: -$OPTARG" >&2 usage @@ -48,6 +52,21 @@ while getopts ":i:o:" opt; do esac done +shift $((OPTIND-1)) + +numargs=$# +for ((i=1 ; i <= $numargs ; i++)) +do + fullname=$(readlink -f "$1") + if [ ! -f "$fullname" ] ; then + echo "$progname: file \"$fullname\" does not exist" >&2 + usage + fi + infiles[$infcount]="$fullname" + ((infcount++)) + shift +done + if ((infcount == 0)) ; then echo "$progname: No input file(s) specified." >&2 usage @@ -58,19 +77,18 @@ if ((outfcount != 1)) ; then usage fi - workdir=$(mktemp --tmpdir -d pdfattach.XXXXXX) cd $workdir -> ${outbase}.tex -echo -E "\documentclass{book}" >> ${outbase}.tex -echo -E "\usepackage{attachfile}" >> ${outbase}.tex -echo -E "\begin{document}" >> ${outbase}.tex -echo -E "\pagestyle{empty}" >> ${outbase}.tex +> tmp.tex +echo -E "\documentclass{book}" >> tmp.tex +echo -E "\usepackage{attachfile}" >> tmp.tex +echo -E "\begin{document}" >> tmp.tex +echo -E "\pagestyle{empty}" >> tmp.tex for ((i = 0 ; i < ${#infiles[*]} ; i++)); do - echo "\attachfile{${infiles[$i]}}" >> ${outbase}.tex + echo "\attachfile{${infiles[$i]}}" >> tmp.tex done -echo -E "\end{document}" >> ${outbase}.tex -pdflatex -halt-on-error ${outbase} > /dev/null && mv ${outbase}.pdf $targetdir +echo -E "\end{document}" >> tmp.tex +pdflatex -halt-on-error tmp.tex > /dev/null && mv tmp.pdf "$outfile" cd - > /dev/null rm -rf $workdir From 52e7bb867ef4bbff53e4624be81dee19ecab8102 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Wed, 24 Oct 2012 16:23:16 +0100 Subject: [PATCH 48/70] Use tabs not spaces for indenting. --- extr.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/extr.c b/extr.c index c73f86ce5..ccf7e211d 100644 --- a/extr.c +++ b/extr.c @@ -1,25 +1,25 @@ /* - extr: Extract attachments from PDF file + extr: Extract attachments from PDF file Usage: extr /dir/file.pdf pageno Returns 0 if one or more attachments saved, otherwise returns non-zero. Prints the number of saved attachments on stdout. Attachments are saved in /dir directory with the appropriate filenames. - Copyright (C) 2012 Tigran Aivazian + Copyright (C) 2012 Tigran Aivazian - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ #include "mupdf-internal.h" From a86e2ece23f688a8617a462e3f9b62380ab403a3 Mon Sep 17 00:00:00 2001 From: chrox Date: Wed, 24 Oct 2012 23:53:22 +0800 Subject: [PATCH 49/70] bugfix: dpi should be multiplied by shrink_factor not zoom Conflicts: koptreader.lua --- k2pdfopt.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/k2pdfopt.c b/k2pdfopt.c index df1bfe35e..5f2859129 100644 --- a/k2pdfopt.c +++ b/k2pdfopt.c @@ -218,7 +218,7 @@ static double dst_min_figure_height_in = 0.75; static int dst_fulljustify = -1; // 0 = no, 1 = yes static int dst_color = 0; static int dst_landscape = 0; -static double dst_mar = 0.02; +static double dst_mar = 0.06; static double dst_martop = -1.0; static double dst_marbot = -1.0; static double dst_marleft = -1.0; @@ -491,10 +491,10 @@ void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ // ctm=fz_concat(ctm,fz_rotate(rotation)); bounds2 = fz_transform_rect(ctm, bounds); bbox = fz_round_rect(bounds2); - printf("reading page:%d,%d,%d,%d dpi:%.0f\n",bbox.x0,bbox.y0,bbox.x1,bbox.y1,dpi); + printf("reading page:%d,%d,%d,%d zoom:%.2f dpi:%.0f\n",bbox.x0,bbox.y0,bbox.x1,bbox.y1,zoom,dpi); zoom_value = zoom; zoom *= shrink_factor; - dpi *= zoom; + dpi *= shrink_factor; } while (bbox.x1 > max_page_width_pix | bbox.y1 > max_page_height_pix); // ctm=fz_translate(0,-page->mediabox.y1); // ctm=fz_concat(ctm,fz_scale(dpp,-dpp)); @@ -553,7 +553,7 @@ void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ printf("reading page:%d,%d,%d,%d dpi:%.0f\n",prect.x,prect.y,prect.w,prect.h,dpi); zoom_value = zoom; zoom *= shrink_factor; - dpi *= zoom; + dpi *= shrink_factor; } while (prect.w > max_page_width_pix | prect.h > max_page_height_pix); rrect = prect; From 6d02aaaa7b4cff45396e0b196357f45f3ecf0a77 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Wed, 24 Oct 2012 17:15:50 +0100 Subject: [PATCH 50/70] Enhance pdfattach script 1. Set the page size to 9x12cm as appropriate for Kindle 3 2. Together with each attachment print its base filename and file size in bytes. 3. Print the total size of all attachments (so one can estimate whether it is safe to extract the attachments or not). --- utils/pdfattach | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/utils/pdfattach b/utils/pdfattach index 6af91c98b..50dbb8dc3 100755 --- a/utils/pdfattach +++ b/utils/pdfattach @@ -10,6 +10,13 @@ progname=$(basename $0) +function escape_tex_specialchars() +{ + local txt=$1 + local res=$(echo "$txt" | sed -e "s%_%\\\_%g") + echo "$res" +} + function usage() { echo "Usage: $progname -o file.pdf file1.djvu [file2.mp3] ..." @@ -33,7 +40,8 @@ fi declare outfile="" declare -a infiles=() -declare -i infcount=0 outfcount=0 +declare -a infilesize=() +declare -i infcount=0 outfcount=0 totalsize=0 while getopts ":o:" opt; do case $opt in @@ -63,6 +71,8 @@ do usage fi infiles[$infcount]="$fullname" + infilesize[$infcount]=$(stat --print="%s" "$fullname") + ((totalsize=totalsize+${infilesize[$infcount]})) ((infcount++)) shift done @@ -81,13 +91,17 @@ workdir=$(mktemp --tmpdir -d pdfattach.XXXXXX) cd $workdir > tmp.tex echo -E "\documentclass{book}" >> tmp.tex +echo -E "\usepackage[margin={1mm},papersize={9cm,12cm}]{geometry}" >> tmp.tex echo -E "\usepackage{attachfile}" >> tmp.tex echo -E "\begin{document}" >> tmp.tex -echo -E "\pagestyle{empty}" >> tmp.tex +echo -E "\pagestyle{empty}\small" >> tmp.tex for ((i = 0 ; i < ${#infiles[*]} ; i++)); do - echo "\attachfile{${infiles[$i]}}" >> tmp.tex + descr=$(escape_tex_specialchars $(basename "${infiles[$i]}")) + echo -E "\noindent $((i+1)): $descr (\textattachfile[color={0 0 0}]{${infiles[$i]}}{${infilesize[$i]} bytes})" >> tmp.tex + echo >> tmp.tex done +echo -E "\noindent Total size $totalsize bytes" >> tmp.tex echo -E "\end{document}" >> tmp.tex pdflatex -halt-on-error tmp.tex > /dev/null && mv tmp.pdf "$outfile" cd - > /dev/null From ed76aa78f4c10a52402e36eb5d003da498c540e8 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Wed, 24 Oct 2012 20:35:10 +0100 Subject: [PATCH 51/70] More enhancements to pdfattach 1. Emit the list of attached files first and then emit each individual attachment one per page. 2. Use hyperlinks in the list of attached files so one can jump directly to the file which he wants to extract. --- utils/pdfattach | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/utils/pdfattach b/utils/pdfattach index 50dbb8dc3..c2b3a1142 100755 --- a/utils/pdfattach +++ b/utils/pdfattach @@ -40,6 +40,7 @@ fi declare outfile="" declare -a infiles=() +declare -a infiles_texclean=() declare -a infilesize=() declare -i infcount=0 outfcount=0 totalsize=0 @@ -71,6 +72,7 @@ do usage fi infiles[$infcount]="$fullname" + infiles_texclean[$infcount]=$(escape_tex_specialchars $(basename "${infiles[$infcount]}")) infilesize[$infcount]=$(stat --print="%s" "$fullname") ((totalsize=totalsize+${infilesize[$infcount]})) ((infcount++)) @@ -90,18 +92,26 @@ fi workdir=$(mktemp --tmpdir -d pdfattach.XXXXXX) cd $workdir > tmp.tex +# emit TeX preamble echo -E "\documentclass{book}" >> tmp.tex echo -E "\usepackage[margin={1mm},papersize={9cm,12cm}]{geometry}" >> tmp.tex -echo -E "\usepackage{attachfile}" >> tmp.tex +echo -E "\usepackage{hyperref,attachfile}" >> tmp.tex echo -E "\begin{document}" >> tmp.tex -echo -E "\pagestyle{empty}\small" >> tmp.tex +echo -E "\tolerance=10000\pagestyle{empty}\fontsize{7}{13}\selectfont" >> tmp.tex + +# emit the list of all files for ((i = 0 ; i < ${#infiles[*]} ; i++)); do - descr=$(escape_tex_specialchars $(basename "${infiles[$i]}")) - echo -E "\noindent $((i+1)): $descr (\textattachfile[color={0 0 0}]{${infiles[$i]}}{${infilesize[$i]} bytes})" >> tmp.tex + echo -E "\noindent \hyperlink{L$i}{$((i+1))/${infcount}} \texttt{${infiles_texclean[$i]}} (${infilesize[$i]} bytes)" >> tmp.tex echo >> tmp.tex done -echo -E "\noindent Total size $totalsize bytes" >> tmp.tex +echo -E "\noindent Total size $totalsize bytes\newpage" >> tmp.tex + +# now emit all the attachments, one per page +for ((i = 0 ; i < ${#infiles[*]} ; i++)); +do + echo -E "\noindent\hypertarget{L$i}$((i+1))/${infcount}\\\\\texttt{${infiles_texclean[$i]}} (\textattachfile[color={0 0 0}]{${infiles[$i]}}{${infilesize[$i]} bytes})\newpage" >> tmp.tex +done echo -E "\end{document}" >> tmp.tex pdflatex -halt-on-error tmp.tex > /dev/null && mv tmp.pdf "$outfile" cd - > /dev/null From 9348b5c553ee9caf9e68de139e48aacaee4ce594 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Wed, 24 Oct 2012 22:03:35 +0100 Subject: [PATCH 52/70] Escape ampersand & in the filename also. I am amazed what stupid filenames some people come up with, instead of abiding by old good [a-zA-Z0-9] character set and 8.3 filename length... --- utils/pdfattach | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/pdfattach b/utils/pdfattach index c2b3a1142..73f81f02f 100755 --- a/utils/pdfattach +++ b/utils/pdfattach @@ -13,7 +13,7 @@ progname=$(basename $0) function escape_tex_specialchars() { local txt=$1 - local res=$(echo "$txt" | sed -e "s%_%\\\_%g") + local res=$(echo "$txt" | sed -e "s%_%\\\_%g" -e "s%&%\\\&%g") echo "$res" } From 82450170788ef8b57b7410209fe630ecc5fe1e18 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Wed, 24 Oct 2012 23:22:21 +0100 Subject: [PATCH 53/70] Cleanups to extr.c and pdfattach 1. In extr.c the error message should use the correct full pathname, not the base filename. 2. In pdfattach the check for existence of input files should be replaced with the check for read access to them. --- extr.c | 2 +- utils/pdfattach | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extr.c b/extr.c index ccf7e211d..a202b103b 100644 --- a/extr.c +++ b/extr.c @@ -59,7 +59,7 @@ int save_attachments(int pageno, char *targetdir) sprintf(pathname, "%s/%s", targetdir, name); FILE *fout = fopen(pathname, "w"); if (!fout) { - fprintf(stderr, "extr: cannot write to file %s\n", name); + fprintf(stderr, "extr: cannot write to file %s\n", pathname); exit(1); } dump_stream(pdf_to_num(f_obj), fout); diff --git a/utils/pdfattach b/utils/pdfattach index 73f81f02f..1bd7ab18d 100755 --- a/utils/pdfattach +++ b/utils/pdfattach @@ -67,7 +67,7 @@ numargs=$# for ((i=1 ; i <= $numargs ; i++)) do fullname=$(readlink -f "$1") - if [ ! -f "$fullname" ] ; then + if [ ! -r "$fullname" ] ; then echo "$progname: file \"$fullname\" does not exist" >&2 usage fi From c0cd31be6dedd6975dc96fb1ad4d6d716376e09e Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Wed, 24 Oct 2012 23:24:54 +0100 Subject: [PATCH 54/70] Adjust the error message as well. --- utils/pdfattach | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/pdfattach b/utils/pdfattach index 1bd7ab18d..a7ae4d76f 100755 --- a/utils/pdfattach +++ b/utils/pdfattach @@ -68,7 +68,7 @@ for ((i=1 ; i <= $numargs ; i++)) do fullname=$(readlink -f "$1") if [ ! -r "$fullname" ] ; then - echo "$progname: file \"$fullname\" does not exist" >&2 + echo "$progname: cannot access the file \"$fullname\"" >&2 usage fi infiles[$infcount]="$fullname" From 5c0201d3e32f43b47c1911065d5363f02e38c9b0 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Thu, 25 Oct 2012 10:53:12 +0100 Subject: [PATCH 55/70] Makefile: extr depends on MUPDFLIBS/THIRDPARTYLIBS --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b92c0c7e9..0ef81f324 100644 --- a/Makefile +++ b/Makefile @@ -144,7 +144,7 @@ kpdfview: kpdfview.o einkfb.o pdf.o k2pdfopt.o blitbuffer.o drawcontext.o input. $(EMU_LDFLAGS) \ $(DYNAMICLIBSTDCPP) -extr: extr.o +extr: extr.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(CC) $(CFLAGS) extr.o $(MUPDFLIBS) $(THIRDPARTYLIBS) -lm -o extr extr.o: %.o: %.c From ce0b3acd6bb49a54b3eda7a1f0e76d517840f396 Mon Sep 17 00:00:00 2001 From: chrox Date: Fri, 26 Oct 2012 00:37:50 +0800 Subject: [PATCH 56/70] add config dialog for koptreader For now only line spaceing and word spaceing are configurable with 'F'/'Aa' commands. And device size are passed to k2pdfopt in place of the hard-coded default width and height(via @dpavlin). Conflicts: koptreader.lua --- djvu.c | 8 +- k2pdfopt.c | 20 ++++- k2pdfopt.h | 6 +- koptconfig.lua | 226 +++++++++++++++++++++++++++++++++++++++++++++++++ pdf.c | 9 +- 5 files changed, 261 insertions(+), 8 deletions(-) create mode 100644 koptconfig.lua diff --git a/djvu.c b/djvu.c index 7c75158b8..18b78737d 100644 --- a/djvu.c +++ b/djvu.c @@ -475,9 +475,13 @@ static int reflowPage(lua_State *L) { DjvuPage *page = (DjvuPage*) luaL_checkudata(L, 1, "djvupage"); DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); ddjvu_render_mode_t mode = (int) luaL_checkint(L, 3); + int width = luaL_checkint(L, 4); // framebuffer size + int height = luaL_checkint(L, 5); + double line_spacing = luaL_checknumber(L, 6); + double word_spacing = luaL_checknumber(L, 7); - int width, height; - k2pdfopt_djvu_reflow(page->page_ref, page->doc->context, mode, page->doc->pixelformat, dc->zoom); + k2pdfopt_djvu_reflow(page->page_ref, page->doc->context, mode, page->doc->pixelformat, dc->zoom, \ + width, height, line_spacing, word_spacing); k2pdfopt_rfbmp_size(&width, &height); k2pdfopt_rfbmp_zoom(&dc->zoom); diff --git a/k2pdfopt.c b/k2pdfopt.c index 5f2859129..889b92766 100644 --- a/k2pdfopt.c +++ b/k2pdfopt.c @@ -472,7 +472,8 @@ static void k2pdfopt_reflow_bmp(MASTERINFO *masterinfo, WILLUSBITMAP *src) { } void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ - double zoom, double gamma, double rot_deg) { + double zoom, double gamma, double rot_deg, \ + int bb_width, int bb_height, double line_space, double word_space) { fz_device *dev; fz_pixmap *pix; fz_rect bounds,bounds2; @@ -480,6 +481,14 @@ void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ fz_bbox bbox; WILLUSBITMAP _src, *src; + dst_userwidth = bb_width; // dst_width is adjusted in adjust_params_init + dst_userheight = bb_height; + vertical_line_spacing = line_space; + word_spacing = word_space; + + printf("k2pdfopt_mupdf_reflow width:%d height:%d, line space:%.2f, word space:%.2f\n", \ + bb_width,bb_height,vertical_line_spacing,word_spacing); + double dpp; double dpi = 250*zoom; do { @@ -533,11 +542,18 @@ void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ } void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ - ddjvu_render_mode_t mode, ddjvu_format_t *fmt, double zoom) { + ddjvu_render_mode_t mode, ddjvu_format_t *fmt, double zoom, \ + int bb_width, int bb_height, double line_space, double word_space) { WILLUSBITMAP _src, *src; ddjvu_rect_t prect; ddjvu_rect_t rrect; int i, iw, ih, idpi, status; + + dst_userwidth = bb_width; // dst_width is adjusted in adjust_params_init + dst_userheight = bb_height; + vertical_line_spacing = line_space; + word_spacing = word_space; + double dpi = 250*zoom; while (!ddjvu_page_decoding_done(page)) diff --git a/k2pdfopt.h b/k2pdfopt.h index 1fef6d24d..51743b3c5 100644 --- a/k2pdfopt.h +++ b/k2pdfopt.h @@ -27,9 +27,11 @@ #include void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ - double dpi, double gamma, double rot_deg); + double zoom, double gamma, double rot_deg, int bb_width, int bb_height, \ + double line_spacing, double word_spacing); void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ - ddjvu_render_mode_t mode, ddjvu_format_t *fmt, double dpi); + ddjvu_render_mode_t mode, ddjvu_format_t *fmt, double zoom, int bb_width, int bb_height, \ + double line_spacing, double word_spacing); void k2pdfopt_rfbmp_size(int *width, int *height); void k2pdfopt_rfbmp_ptr(unsigned char** bmp_ptr_ptr); void k2pdfopt_rfbmp_zoom(double *zoom); diff --git a/koptconfig.lua b/koptconfig.lua new file mode 100644 index 000000000..64f02c605 --- /dev/null +++ b/koptconfig.lua @@ -0,0 +1,226 @@ +require "font" +require "keys" +require "settings" + +KOPTOptions = { + { + name="line_spacing", + option_text="Line Spacing", + items_text={"small","medium","large"}, + current_item=2, + text_dirty=true, + marker_dirty={true, true, true}, + space={1.0, 1.2, 1.4}}, + { + name="word_spacing", + option_text="Word Spacing", + items_text={"small","medium","large"}, + current_item=2, + text_dirty=true, + marker_dirty={true, true, true}, + space={0.2, 0.375, 0.5}}, +} + +KOPTConfig = { + -- UI constants + HEIGHT = 200, -- height + MARGIN_BOTTOM = 20, -- window bottom margin + MARGIN_HORISONTAL = 75, -- window horisontal margin + OPTION_PADDING_T = 50, -- options top padding + OPTION_PADDING_H = 50, -- options horisontal padding + OPTION_SPACING_V = 35, -- options vertical spacing + VALUE_PADDING_H = 150, -- values horisontal padding + VALUE_SPACING_H = 10, -- values horisontal spacing + OPT_NAME_FONT_SIZE = 20, -- option name font size + OPT_VALUE_FONT_SIZE = 16, -- option value font size + + -- last pos text is drawn + text_pos = 0, + -- current selected option + current_option = 1, + -- page dirty + page_dirty = false, +} + +configurable = { + font_size = 1.0, + page_margin = 0.06, + line_spacing = 1.2, + word_spacing = 0.375, +} + +function KOPTConfig:drawBox(xpos, ypos, width, hight, bgcolor, bdcolor) + -- draw dialog border + local r = 6 -- round corners + fb.bb:paintRect(xpos, ypos+r, width, hight - 2*r, bgcolor) + blitbuffer.paintBorder(fb.bb, xpos, ypos, width, r, r, bgcolor, r) + blitbuffer.paintBorder(fb.bb, xpos, ypos+hight-2*r, width, r, r, bgcolor, r) +end + +function KOPTConfig:drawOptionName(xpos, ypos, option_index, text, font_face, refresh) + local xpos, ypos = xpos+self.OPTION_PADDING_H, ypos+self.OPTION_PADDING_T + if KOPTOptions[option_index].text_dirty or refresh then + --Debug("drawing option name:", KOPTOptions[option_index].option_text) + renderUtf8Text(fb.bb, xpos, ypos+self.OPTION_SPACING_V*(option_index-1), font_face, text, true) + end +end + +function KOPTConfig:drawOptionItem(xpos, ypos, option_index, item_index, text, font_face, refresh) + if item_index == 1 then + self.text_pos = 0 + end + + local xpos = xpos+self.OPTION_PADDING_H+self.VALUE_PADDING_H+self.VALUE_SPACING_H*(item_index-1)+self.text_pos + local ypos = ypos+self.OPTION_PADDING_T+self.OPTION_SPACING_V*(option_index-1) + + if KOPTOptions[option_index].text_dirty or refresh then + --Debug("drawing option:", KOPTOptions[option_index].option_text, "item:", text) + renderUtf8Text(fb.bb, xpos, ypos, font_face, text, true) + end + + local text_len = sizeUtf8Text(0, G_width, font_face, text, true).x + self.text_pos = self.text_pos + text_len + + if KOPTOptions[option_index].marker_dirty[item_index] then + --Debug("drawing option:", KOPTOptions[option_index].option_text, "marker:", text) + if item_index == KOPTOptions[option_index].current_item then + fb.bb:paintRect(xpos, ypos+5, text_len, 3,(option_index == self.current_option) and 15 or 5) + fb:refresh(1, xpos, ypos+5, text_len, 3) + else + fb.bb:paintRect(xpos, ypos+5, text_len, 3, 3) + fb:refresh(1, xpos, ypos+5, text_len, 3) + end + KOPTOptions[option_index].marker_dirty[item_index] = false + end +end + +function KOPTConfig:drawOptions(xpos, ypos, name_font, value_font, refresh) + local width, height = fb.bb:getWidth()-2*self.MARGIN_HORISONTAL, self.HEIGHT + for i=1,#KOPTOptions do + self:drawOptionName(xpos, ypos, i, KOPTOptions[i].option_text, name_font, refresh) + for j=1,#KOPTOptions[i].items_text do + self:drawOptionItem(xpos, ypos, i, j, KOPTOptions[i].items_text[j], value_font, refresh) + end + KOPTOptions[i].text_dirty = false + end +end + +function KOPTConfig:config(callback, reader) + local kopt_callback = callback + local koptreader = reader + self:addAllCommands() + + local name_font = Font:getFace("tfont", self.OPT_NAME_FONT_SIZE) + local value_font = Font:getFace("cfont", self.OPT_VALUE_FONT_SIZE) + + -- base window coordinates + local width, height = fb.bb:getWidth()-2*self.MARGIN_HORISONTAL, self.HEIGHT + local topleft_x, topleft_y = self.MARGIN_HORISONTAL, fb.bb:getHeight()-self.MARGIN_BOTTOM-height + local botleft_x, botleft_y = self.MARGIN_HORISONTAL, topleft_y+height + + self:drawBox(topleft_x, topleft_y, width, height, 3, 15) + self:drawOptions(topleft_x, topleft_y, name_font, value_font) + fb:refresh(1, topleft_x, topleft_y, width, height) + + local ev, keydef, command, ret_code + while true do + configurable.line_spacing = KOPTOptions[1].space[KOPTOptions[1].current_item] + configurable.word_spacing = KOPTOptions[2].space[KOPTOptions[2].current_item] + --Debug("Line spacing:", configurable.line_spacing, "Word spacing:", configurable.word_spacing) + if self.page_dirty then + kopt_callback(koptreader, configurable) + self:drawBox(topleft_x, topleft_y, width, height, 3, 15) + self:drawOptions(topleft_x, topleft_y, name_font, value_font, true) + fb:refresh(1, topleft_x, topleft_y, width, height) + self.page_dirty = false + end + self:drawOptions(topleft_x, topleft_y, name_font, value_font) + + ev = input.saveWaitForEvent() + ev.code = adjustKeyEvents(ev) + if ev.type == EV_KEY and ev.value ~= EVENT_VALUE_KEY_RELEASE then + keydef = Keydef:new(ev.code, getKeyModifier()) + Debug("key pressed: "..tostring(keydef)) + command = self.commands:getByKeydef(keydef) + if command ~= nil then + Debug("command to execute: "..tostring(command)) + ret_code = command.func(self, keydef) + else + Debug("command not found: "..tostring(command)) + end + if ret_code == "break" then + ret_code = nil + if self.final_choice then + return self.readers[self.final_choice] + else + return nil + end + end + end -- if + end -- while +end + +-- add available commands +function KOPTConfig:addAllCommands() + self.commands = Commands:new{} + self.commands:add(KEY_FW_DOWN, nil, "joypad down", + "next item", + function(self) + local last_option = self.current_option + self.current_option = (self.current_option + #KOPTOptions + 1)%#KOPTOptions + self.current_option = (self.current_option == 0) and #KOPTOptions or self.current_option + + last_option_item = KOPTOptions[last_option].current_item + KOPTOptions[last_option].marker_dirty[last_option_item] = true + current_option_item = KOPTOptions[self.current_option].current_item + KOPTOptions[self.current_option].marker_dirty[current_option_item] = true + end + ) + self.commands:add(KEY_FW_UP, nil, "joypad up", + "previous item", + function(self) + local last_option = self.current_option + self.current_option = (self.current_option + #KOPTOptions - 1)%#KOPTOptions + self.current_option = (self.current_option == 0) and #KOPTOptions or self.current_option + + last_option_item = KOPTOptions[last_option].current_item + KOPTOptions[last_option].marker_dirty[last_option_item] = true + current_option_item = KOPTOptions[self.current_option].current_item + KOPTOptions[self.current_option].marker_dirty[current_option_item] = true + end + ) + self.commands:add(KEY_FW_LEFT, nil, "joypad left", + "last item", + function(self) + local last_item = KOPTOptions[self.current_option].current_item + local item_count = #KOPTOptions[self.current_option].items_text + local current_item = (KOPTOptions[self.current_option].current_item + item_count - 1)%item_count + current_item = (current_item == 0) and item_count or current_item + KOPTOptions[self.current_option].current_item = current_item + + KOPTOptions[self.current_option].marker_dirty[last_item] = true + KOPTOptions[self.current_option].marker_dirty[current_item] = true + self.page_dirty = true + end + ) + self.commands:add(KEY_FW_RIGHT, nil, "joypad right", + "next item", + function(self) + local last_item = KOPTOptions[self.current_option].current_item + local item_count = #KOPTOptions[self.current_option].items_text + local current_item = (KOPTOptions[self.current_option].current_item + item_count + 1)%item_count + current_item = (current_item == 0) and item_count or current_item + KOPTOptions[self.current_option].current_item = current_item + + KOPTOptions[self.current_option].marker_dirty[last_item] = true + KOPTOptions[self.current_option].marker_dirty[current_item] = true + self.page_dirty = true + end + ) + self.commands:add({KEY_F,KEY_AA,KEY_BACK}, nil, "Back", + "back", + function(self) + return "break" + end + ) +end \ No newline at end of file diff --git a/pdf.c b/pdf.c index b51d6a73a..579dc8a02 100644 --- a/pdf.c +++ b/pdf.c @@ -515,9 +515,14 @@ static int reflowPage(lua_State *L) { PdfPage *page = (PdfPage*) luaL_checkudata(L, 1, "pdfpage"); DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); + int width = (int) luaL_checkint(L, 4); // framebuffer size + int height = (int) luaL_checkint(L, 5); + double line_spacing = luaL_checknumber(L, 6); + double word_spacing = luaL_checknumber(L, 7); - int width, height; - k2pdfopt_mupdf_reflow(page->doc->xref, page->page, page->doc->context, dc->zoom, dc->gamma, 0); + //printf("reflowPage width:%d height:%d\n", width, height); + + k2pdfopt_mupdf_reflow(page->doc->xref, page->page, page->doc->context, dc->zoom, dc->gamma, 0.0, width, height, line_spacing, word_spacing); k2pdfopt_rfbmp_size(&width, &height); k2pdfopt_rfbmp_zoom(&dc->zoom); From ad4deb26be690c6905e9a535e06958edef6919c8 Mon Sep 17 00:00:00 2001 From: chrox Date: Fri, 26 Oct 2012 17:16:31 +0800 Subject: [PATCH 57/70] add page margin and text wrap and contrast option in koptconfig Conflicts: koptconfig.lua koptreader.lua --- djvu.c | 8 +++-- k2pdfopt.c | 44 +++++++++++++------------- k2pdfopt.h | 9 +++--- koptconfig.lua | 85 +++++++++++++++++++++++++++++++++++++++----------- pdf.c | 16 +++++----- 5 files changed, 109 insertions(+), 53 deletions(-) diff --git a/djvu.c b/djvu.c index 18b78737d..ceff6e693 100644 --- a/djvu.c +++ b/djvu.c @@ -477,9 +477,13 @@ static int reflowPage(lua_State *L) { ddjvu_render_mode_t mode = (int) luaL_checkint(L, 3); int width = luaL_checkint(L, 4); // framebuffer size int height = luaL_checkint(L, 5); - double line_spacing = luaL_checknumber(L, 6); - double word_spacing = luaL_checknumber(L, 7); + double page_margin = luaL_checknumber(L, 6); + double line_spacing = luaL_checknumber(L, 7); + double word_spacing = luaL_checknumber(L, 8); + int text_wrap = luaL_checkint(L, 9); + double contrast = luaL_checknumber(L, 10); + k2pdfopt_set_params(width, height, page_margin, line_spacing, word_spacing, text_wrap, contrast); k2pdfopt_djvu_reflow(page->page_ref, page->doc->context, mode, page->doc->pixelformat, dc->zoom, \ width, height, line_spacing, word_spacing); k2pdfopt_rfbmp_size(&width, &height); diff --git a/k2pdfopt.c b/k2pdfopt.c index 889b92766..58de4b64c 100644 --- a/k2pdfopt.c +++ b/k2pdfopt.c @@ -218,7 +218,7 @@ static double dst_min_figure_height_in = 0.75; static int dst_fulljustify = -1; // 0 = no, 1 = yes static int dst_color = 0; static int dst_landscape = 0; -static double dst_mar = 0.06; +static double dst_mar = 0.2; static double dst_martop = -1.0; static double dst_marbot = -1.0; static double dst_marleft = -1.0; @@ -410,6 +410,7 @@ static int max_page_width_pix = 3000; static int max_page_height_pix = 4000; static double shrink_factor = 0.9; static double zoom_value = 1.0; +static double gamma_correction = 1.0; static void k2pdfopt_reflow_bmp(MASTERINFO *masterinfo, WILLUSBITMAP *src) { PAGEINFO _pageinfo, *pageinfo; @@ -471,9 +472,25 @@ static void k2pdfopt_reflow_bmp(MASTERINFO *masterinfo, WILLUSBITMAP *src) { bmp_free(srcgrey); } +void k2pdfopt_set_params(int bb_width, int bb_height, double page_margin, \ + double line_space, double word_space, \ + int wrapping, double contrast) { + dst_userwidth = bb_width; // dst_width is adjusted in adjust_params_init + dst_userheight = bb_height; + vertical_line_spacing = line_space; + word_spacing = word_space; + text_wrap = wrapping; + gamma_correction = contrast; // contrast is only used by k2pdfopt_mupdf_reflow + + dst_mar = page_margin; + dst_martop = -1.0; + dst_marbot = -1.0; + dst_marleft = -1.0; + dst_marright = -1.0; +} + void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ - double zoom, double gamma, double rot_deg, \ - int bb_width, int bb_height, double line_space, double word_space) { + double zoom, double gamma, double rot_deg) { fz_device *dev; fz_pixmap *pix; fz_rect bounds,bounds2; @@ -481,14 +498,6 @@ void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ fz_bbox bbox; WILLUSBITMAP _src, *src; - dst_userwidth = bb_width; // dst_width is adjusted in adjust_params_init - dst_userheight = bb_height; - vertical_line_spacing = line_space; - word_spacing = word_space; - - printf("k2pdfopt_mupdf_reflow width:%d height:%d, line space:%.2f, word space:%.2f\n", \ - bb_width,bb_height,vertical_line_spacing,word_spacing); - double dpp; double dpi = 250*zoom; do { @@ -527,8 +536,8 @@ void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ fz_run_page(doc, page, dev, ctm, NULL); fz_free_device(dev); - if(gamma >= 0.0) { - fz_gamma_pixmap(ctx, pix, gamma); + if(gamma_correction >= 0.0) { + fz_gamma_pixmap(ctx, pix, gamma_correction); } src = &_src; @@ -542,18 +551,11 @@ void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ } void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ - ddjvu_render_mode_t mode, ddjvu_format_t *fmt, double zoom, \ - int bb_width, int bb_height, double line_space, double word_space) { + ddjvu_render_mode_t mode, ddjvu_format_t *fmt, double zoom) { WILLUSBITMAP _src, *src; ddjvu_rect_t prect; ddjvu_rect_t rrect; int i, iw, ih, idpi, status; - - dst_userwidth = bb_width; // dst_width is adjusted in adjust_params_init - dst_userheight = bb_height; - vertical_line_spacing = line_space; - word_spacing = word_space; - double dpi = 250*zoom; while (!ddjvu_page_decoding_done(page)) diff --git a/k2pdfopt.h b/k2pdfopt.h index 51743b3c5..ae9bb764f 100644 --- a/k2pdfopt.h +++ b/k2pdfopt.h @@ -26,12 +26,13 @@ #include #include +void k2pdfopt_set_params(int bb_width, int bb_height, double page_margin, \ + double line_space, double word_space, \ + int wrapping, double contrast); void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ - double zoom, double gamma, double rot_deg, int bb_width, int bb_height, \ - double line_spacing, double word_spacing); + double zoom, double gamma, double rot_deg); void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ - ddjvu_render_mode_t mode, ddjvu_format_t *fmt, double zoom, int bb_width, int bb_height, \ - double line_spacing, double word_spacing); + ddjvu_render_mode_t mode, ddjvu_format_t *fmt, double zoom); void k2pdfopt_rfbmp_size(int *width, int *height); void k2pdfopt_rfbmp_ptr(unsigned char** bmp_ptr_ptr); void k2pdfopt_rfbmp_zoom(double *zoom); diff --git a/koptconfig.lua b/koptconfig.lua index 64f02c605..342f5308f 100644 --- a/koptconfig.lua +++ b/koptconfig.lua @@ -3,6 +3,14 @@ require "keys" require "settings" KOPTOptions = { + { + name="page_margin", + option_text="Page Margin", + items_text={"small","medium","large"}, + current_item=2, + text_dirty=true, + marker_dirty={true, true, true}, + value={0.02, 0.06, 0.10}}, { name="line_spacing", option_text="Line Spacing", @@ -10,7 +18,7 @@ KOPTOptions = { current_item=2, text_dirty=true, marker_dirty={true, true, true}, - space={1.0, 1.2, 1.4}}, + value={1.0, 1.2, 1.4}}, { name="word_spacing", option_text="Word Spacing", @@ -18,19 +26,44 @@ KOPTOptions = { current_item=2, text_dirty=true, marker_dirty={true, true, true}, - space={0.2, 0.375, 0.5}}, + value={0.2, 0.375, 0.5}}, + { + name="text_wrap", + option_text="Text Wrap", + items_text={"fitting","reflowing"}, + current_item=2, + text_dirty=true, + marker_dirty={true, true}, + value={0, 1}}, + { + name="contrast", + option_text="Contrast", + items_text={"lightest","lighter","default","darker","darkest"}, + current_item=3, + text_dirty=true, + marker_dirty={true, true, true, true, true}, + value={0.2, 0.4, 1.0, 1.8, 2.6}}, } KOPTConfig = { -- UI constants - HEIGHT = 200, -- height + HEIGHT = 220, -- height MARGIN_BOTTOM = 20, -- window bottom margin +<<<<<<< HEAD MARGIN_HORISONTAL = 75, -- window horisontal margin OPTION_PADDING_T = 50, -- options top padding OPTION_PADDING_H = 50, -- options horisontal padding OPTION_SPACING_V = 35, -- options vertical spacing VALUE_PADDING_H = 150, -- values horisontal padding VALUE_SPACING_H = 10, -- values horisontal spacing +======= + MARGIN_HORISONTAL = 50, -- window horisontal margin + NAME_PADDING_T = 50, -- option name top padding + OPTION_SPACING_V = 35, -- options vertical spacing + NAME_ALIGN_RIGHT = 0.3, -- align name right to the window width + ITEM_ALIGN_LEFT = 0.35, -- align item left to the window width + ITEM_SPACING_H = 10, -- items horisontal spacing +>>>>>>> f4a2b5f... add page margin and text wrap and contrast option in koptconfig OPT_NAME_FONT_SIZE = 20, -- option name font size OPT_VALUE_FONT_SIZE = 16, -- option value font size @@ -42,13 +75,6 @@ KOPTConfig = { page_dirty = false, } -configurable = { - font_size = 1.0, - page_margin = 0.06, - line_spacing = 1.2, - word_spacing = 0.375, -} - function KOPTConfig:drawBox(xpos, ypos, width, hight, bgcolor, bdcolor) -- draw dialog border local r = 6 -- round corners @@ -105,9 +131,34 @@ function KOPTConfig:drawOptions(xpos, ypos, name_font, value_font, refresh) end end +<<<<<<< HEAD function KOPTConfig:config(callback, reader) local kopt_callback = callback local koptreader = reader +======= +function KOPTConfig:makeDefault() + for i=1,#KOPTOptions do + KOPTOptions[i].text_dirty = true + for j=1,#KOPTOptions[i].items_text do + KOPTOptions[i].marker_dirty[j] = true + end + end +end + +function KOPTConfig:reconfigure(configurable) + for i=1,#KOPTOptions do + option = KOPTOptions[i].name + configurable[option] = KOPTOptions[i].value[KOPTOptions[i].current_item] + end +end + +function KOPTConfig:config(callback, reader, configurable) + local kopt_callback = callback + local koptreader = reader + local configurable = configurable + + self:makeDefault() +>>>>>>> f4a2b5f... add page margin and text wrap and contrast option in koptconfig self:addAllCommands() local name_font = Font:getFace("tfont", self.OPT_NAME_FONT_SIZE) @@ -124,11 +175,11 @@ function KOPTConfig:config(callback, reader) local ev, keydef, command, ret_code while true do - configurable.line_spacing = KOPTOptions[1].space[KOPTOptions[1].current_item] - configurable.word_spacing = KOPTOptions[2].space[KOPTOptions[2].current_item] - --Debug("Line spacing:", configurable.line_spacing, "Word spacing:", configurable.word_spacing) + + self:reconfigure(configurable) + if self.page_dirty then - kopt_callback(koptreader, configurable) + kopt_callback(koptreader) self:drawBox(topleft_x, topleft_y, width, height, 3, 15) self:drawOptions(topleft_x, topleft_y, name_font, value_font, true) fb:refresh(1, topleft_x, topleft_y, width, height) @@ -150,11 +201,7 @@ function KOPTConfig:config(callback, reader) end if ret_code == "break" then ret_code = nil - if self.final_choice then - return self.readers[self.final_choice] - else - return nil - end + return nil end end -- if end -- while diff --git a/pdf.c b/pdf.c index 579dc8a02..7c38a2af0 100644 --- a/pdf.c +++ b/pdf.c @@ -515,14 +515,16 @@ static int reflowPage(lua_State *L) { PdfPage *page = (PdfPage*) luaL_checkudata(L, 1, "pdfpage"); DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); - int width = (int) luaL_checkint(L, 4); // framebuffer size - int height = (int) luaL_checkint(L, 5); - double line_spacing = luaL_checknumber(L, 6); - double word_spacing = luaL_checknumber(L, 7); + int width = luaL_checkint(L, 4); // framebuffer size + int height = luaL_checkint(L, 5); + double page_margin = luaL_checknumber(L, 6); + double line_spacing = luaL_checknumber(L, 7); + double word_spacing = luaL_checknumber(L, 8); + int text_wrap = luaL_checkint(L, 9); + double contrast = luaL_checknumber(L, 10); - //printf("reflowPage width:%d height:%d\n", width, height); - - k2pdfopt_mupdf_reflow(page->doc->xref, page->page, page->doc->context, dc->zoom, dc->gamma, 0.0, width, height, line_spacing, word_spacing); + k2pdfopt_set_params(width, height, page_margin, line_spacing, word_spacing, text_wrap, contrast); + k2pdfopt_mupdf_reflow(page->doc->xref, page->page, page->doc->context, dc->zoom, dc->gamma, 0.0); k2pdfopt_rfbmp_size(&width, &height); k2pdfopt_rfbmp_zoom(&dc->zoom); From 72aeb4244154b8450c17bb43371701f80624209b Mon Sep 17 00:00:00 2001 From: chrox Date: Fri, 26 Oct 2012 17:20:56 +0800 Subject: [PATCH 58/70] default dst_mar in k2pdfopt should be 0.06 --- k2pdfopt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k2pdfopt.c b/k2pdfopt.c index 58de4b64c..4d74adc64 100644 --- a/k2pdfopt.c +++ b/k2pdfopt.c @@ -218,7 +218,7 @@ static double dst_min_figure_height_in = 0.75; static int dst_fulljustify = -1; // 0 = no, 1 = yes static int dst_color = 0; static int dst_landscape = 0; -static double dst_mar = 0.2; +static double dst_mar = 0.06; static double dst_martop = -1.0; static double dst_marbot = -1.0; static double dst_marleft = -1.0; From 5294d178a010f40ba00d5378f86bfad0e7c81822 Mon Sep 17 00:00:00 2001 From: chrox Date: Fri, 26 Oct 2012 19:48:28 +0800 Subject: [PATCH 59/70] add justification option in koptconfig dialog Conflicts: koptconfig.lua koptreader.lua --- djvu.c | 6 ++++-- k2pdfopt.c | 4 +++- k2pdfopt.h | 2 +- koptconfig.lua | 26 ++++++++++++++++++++++++-- pdf.c | 6 ++++-- 5 files changed, 36 insertions(+), 8 deletions(-) diff --git a/djvu.c b/djvu.c index ceff6e693..b84725ab4 100644 --- a/djvu.c +++ b/djvu.c @@ -481,9 +481,11 @@ static int reflowPage(lua_State *L) { double line_spacing = luaL_checknumber(L, 7); double word_spacing = luaL_checknumber(L, 8); int text_wrap = luaL_checkint(L, 9); - double contrast = luaL_checknumber(L, 10); + int justification = luaL_checkint(L, 10); + int full_just = luaL_checkint(L, 11); + double contrast = luaL_checknumber(L, 12); - k2pdfopt_set_params(width, height, page_margin, line_spacing, word_spacing, text_wrap, contrast); + k2pdfopt_set_params(width, height, page_margin, line_spacing, word_spacing, text_wrap, justification, full_just, contrast); k2pdfopt_djvu_reflow(page->page_ref, page->doc->context, mode, page->doc->pixelformat, dc->zoom, \ width, height, line_spacing, word_spacing); k2pdfopt_rfbmp_size(&width, &height); diff --git a/k2pdfopt.c b/k2pdfopt.c index 4d74adc64..725a91987 100644 --- a/k2pdfopt.c +++ b/k2pdfopt.c @@ -474,12 +474,14 @@ static void k2pdfopt_reflow_bmp(MASTERINFO *masterinfo, WILLUSBITMAP *src) { void k2pdfopt_set_params(int bb_width, int bb_height, double page_margin, \ double line_space, double word_space, \ - int wrapping, double contrast) { + int wrapping, int justification, int full_just, double contrast) { dst_userwidth = bb_width; // dst_width is adjusted in adjust_params_init dst_userheight = bb_height; vertical_line_spacing = line_space; word_spacing = word_space; text_wrap = wrapping; + dst_justify = justification; + dst_fulljustify = full_just; gamma_correction = contrast; // contrast is only used by k2pdfopt_mupdf_reflow dst_mar = page_margin; diff --git a/k2pdfopt.h b/k2pdfopt.h index ae9bb764f..1708f22eb 100644 --- a/k2pdfopt.h +++ b/k2pdfopt.h @@ -28,7 +28,7 @@ void k2pdfopt_set_params(int bb_width, int bb_height, double page_margin, \ double line_space, double word_space, \ - int wrapping, double contrast); + int wrapping, int justification, int full_just, double contrast); void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ double zoom, double gamma, double rot_deg); void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ diff --git a/koptconfig.lua b/koptconfig.lua index 342f5308f..1354eefa7 100644 --- a/koptconfig.lua +++ b/koptconfig.lua @@ -26,7 +26,7 @@ KOPTOptions = { current_item=2, text_dirty=true, marker_dirty={true, true, true}, - value={0.2, 0.375, 0.5}}, + value={0.02, 0.375, 0.5}}, { name="text_wrap", option_text="Text Wrap", @@ -36,6 +36,22 @@ KOPTOptions = { marker_dirty={true, true}, value={0, 1}}, { + name="justification", + option_text="Justification", + items_text={"default","left","center","right"}, + current_item=1, + text_dirty=true, + marker_dirty={true, true, true, true}, + value={-1,0,1,2}}, + { + name="full_just", + option_text="Full Justification", + items_text={"default","no","yes"}, + current_item=1, + text_dirty=true, + marker_dirty={true, true, true}, + value={-1,0,1}}, + { name="contrast", option_text="Contrast", items_text={"lightest","lighter","default","darker","darkest"}, @@ -47,6 +63,7 @@ KOPTOptions = { KOPTConfig = { -- UI constants +<<<<<<< HEAD HEIGHT = 220, -- height MARGIN_BOTTOM = 20, -- window bottom margin <<<<<<< HEAD @@ -58,6 +75,11 @@ KOPTConfig = { VALUE_SPACING_H = 10, -- values horisontal spacing ======= MARGIN_HORISONTAL = 50, -- window horisontal margin +======= + HEIGHT = 300, -- height + MARGIN_BOTTOM = 30, -- window bottom margin + MARGIN_HORISONTAL = 35, -- window horisontal margin +>>>>>>> 83cc0ea... add justification option in koptconfig dialog NAME_PADDING_T = 50, -- option name top padding OPTION_SPACING_V = 35, -- options vertical spacing NAME_ALIGN_RIGHT = 0.3, -- align name right to the window width @@ -107,7 +129,7 @@ function KOPTConfig:drawOptionItem(xpos, ypos, option_index, item_index, text, f local text_len = sizeUtf8Text(0, G_width, font_face, text, true).x self.text_pos = self.text_pos + text_len - if KOPTOptions[option_index].marker_dirty[item_index] then + if KOPTOptions[option_index].marker_dirty[item_index] or refresh then --Debug("drawing option:", KOPTOptions[option_index].option_text, "marker:", text) if item_index == KOPTOptions[option_index].current_item then fb.bb:paintRect(xpos, ypos+5, text_len, 3,(option_index == self.current_option) and 15 or 5) diff --git a/pdf.c b/pdf.c index 7c38a2af0..b2e6a6fb3 100644 --- a/pdf.c +++ b/pdf.c @@ -521,9 +521,11 @@ static int reflowPage(lua_State *L) { double line_spacing = luaL_checknumber(L, 7); double word_spacing = luaL_checknumber(L, 8); int text_wrap = luaL_checkint(L, 9); - double contrast = luaL_checknumber(L, 10); + int justification = luaL_checkint(L, 10); + int full_just = luaL_checkint(L, 11); + double contrast = luaL_checknumber(L, 12); - k2pdfopt_set_params(width, height, page_margin, line_spacing, word_spacing, text_wrap, contrast); + k2pdfopt_set_params(width, height, page_margin, line_spacing, word_spacing, text_wrap, justification, full_just, contrast); k2pdfopt_mupdf_reflow(page->doc->xref, page->page, page->doc->context, dc->zoom, dc->gamma, 0.0); k2pdfopt_rfbmp_size(&width, &height); k2pdfopt_rfbmp_zoom(&dc->zoom); From 75b1e716fed9d2cb44f3653e0ceaa48dc49fbc81 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 31 Oct 2012 22:34:19 -0400 Subject: [PATCH 60/70] rm koptconfig.lua for new ui branch --- koptconfig.lua | 295 ------------------------------------------------- 1 file changed, 295 deletions(-) delete mode 100644 koptconfig.lua diff --git a/koptconfig.lua b/koptconfig.lua deleted file mode 100644 index 1354eefa7..000000000 --- a/koptconfig.lua +++ /dev/null @@ -1,295 +0,0 @@ -require "font" -require "keys" -require "settings" - -KOPTOptions = { - { - name="page_margin", - option_text="Page Margin", - items_text={"small","medium","large"}, - current_item=2, - text_dirty=true, - marker_dirty={true, true, true}, - value={0.02, 0.06, 0.10}}, - { - name="line_spacing", - option_text="Line Spacing", - items_text={"small","medium","large"}, - current_item=2, - text_dirty=true, - marker_dirty={true, true, true}, - value={1.0, 1.2, 1.4}}, - { - name="word_spacing", - option_text="Word Spacing", - items_text={"small","medium","large"}, - current_item=2, - text_dirty=true, - marker_dirty={true, true, true}, - value={0.02, 0.375, 0.5}}, - { - name="text_wrap", - option_text="Text Wrap", - items_text={"fitting","reflowing"}, - current_item=2, - text_dirty=true, - marker_dirty={true, true}, - value={0, 1}}, - { - name="justification", - option_text="Justification", - items_text={"default","left","center","right"}, - current_item=1, - text_dirty=true, - marker_dirty={true, true, true, true}, - value={-1,0,1,2}}, - { - name="full_just", - option_text="Full Justification", - items_text={"default","no","yes"}, - current_item=1, - text_dirty=true, - marker_dirty={true, true, true}, - value={-1,0,1}}, - { - name="contrast", - option_text="Contrast", - items_text={"lightest","lighter","default","darker","darkest"}, - current_item=3, - text_dirty=true, - marker_dirty={true, true, true, true, true}, - value={0.2, 0.4, 1.0, 1.8, 2.6}}, -} - -KOPTConfig = { - -- UI constants -<<<<<<< HEAD - HEIGHT = 220, -- height - MARGIN_BOTTOM = 20, -- window bottom margin -<<<<<<< HEAD - MARGIN_HORISONTAL = 75, -- window horisontal margin - OPTION_PADDING_T = 50, -- options top padding - OPTION_PADDING_H = 50, -- options horisontal padding - OPTION_SPACING_V = 35, -- options vertical spacing - VALUE_PADDING_H = 150, -- values horisontal padding - VALUE_SPACING_H = 10, -- values horisontal spacing -======= - MARGIN_HORISONTAL = 50, -- window horisontal margin -======= - HEIGHT = 300, -- height - MARGIN_BOTTOM = 30, -- window bottom margin - MARGIN_HORISONTAL = 35, -- window horisontal margin ->>>>>>> 83cc0ea... add justification option in koptconfig dialog - NAME_PADDING_T = 50, -- option name top padding - OPTION_SPACING_V = 35, -- options vertical spacing - NAME_ALIGN_RIGHT = 0.3, -- align name right to the window width - ITEM_ALIGN_LEFT = 0.35, -- align item left to the window width - ITEM_SPACING_H = 10, -- items horisontal spacing ->>>>>>> f4a2b5f... add page margin and text wrap and contrast option in koptconfig - OPT_NAME_FONT_SIZE = 20, -- option name font size - OPT_VALUE_FONT_SIZE = 16, -- option value font size - - -- last pos text is drawn - text_pos = 0, - -- current selected option - current_option = 1, - -- page dirty - page_dirty = false, -} - -function KOPTConfig:drawBox(xpos, ypos, width, hight, bgcolor, bdcolor) - -- draw dialog border - local r = 6 -- round corners - fb.bb:paintRect(xpos, ypos+r, width, hight - 2*r, bgcolor) - blitbuffer.paintBorder(fb.bb, xpos, ypos, width, r, r, bgcolor, r) - blitbuffer.paintBorder(fb.bb, xpos, ypos+hight-2*r, width, r, r, bgcolor, r) -end - -function KOPTConfig:drawOptionName(xpos, ypos, option_index, text, font_face, refresh) - local xpos, ypos = xpos+self.OPTION_PADDING_H, ypos+self.OPTION_PADDING_T - if KOPTOptions[option_index].text_dirty or refresh then - --Debug("drawing option name:", KOPTOptions[option_index].option_text) - renderUtf8Text(fb.bb, xpos, ypos+self.OPTION_SPACING_V*(option_index-1), font_face, text, true) - end -end - -function KOPTConfig:drawOptionItem(xpos, ypos, option_index, item_index, text, font_face, refresh) - if item_index == 1 then - self.text_pos = 0 - end - - local xpos = xpos+self.OPTION_PADDING_H+self.VALUE_PADDING_H+self.VALUE_SPACING_H*(item_index-1)+self.text_pos - local ypos = ypos+self.OPTION_PADDING_T+self.OPTION_SPACING_V*(option_index-1) - - if KOPTOptions[option_index].text_dirty or refresh then - --Debug("drawing option:", KOPTOptions[option_index].option_text, "item:", text) - renderUtf8Text(fb.bb, xpos, ypos, font_face, text, true) - end - - local text_len = sizeUtf8Text(0, G_width, font_face, text, true).x - self.text_pos = self.text_pos + text_len - - if KOPTOptions[option_index].marker_dirty[item_index] or refresh then - --Debug("drawing option:", KOPTOptions[option_index].option_text, "marker:", text) - if item_index == KOPTOptions[option_index].current_item then - fb.bb:paintRect(xpos, ypos+5, text_len, 3,(option_index == self.current_option) and 15 or 5) - fb:refresh(1, xpos, ypos+5, text_len, 3) - else - fb.bb:paintRect(xpos, ypos+5, text_len, 3, 3) - fb:refresh(1, xpos, ypos+5, text_len, 3) - end - KOPTOptions[option_index].marker_dirty[item_index] = false - end -end - -function KOPTConfig:drawOptions(xpos, ypos, name_font, value_font, refresh) - local width, height = fb.bb:getWidth()-2*self.MARGIN_HORISONTAL, self.HEIGHT - for i=1,#KOPTOptions do - self:drawOptionName(xpos, ypos, i, KOPTOptions[i].option_text, name_font, refresh) - for j=1,#KOPTOptions[i].items_text do - self:drawOptionItem(xpos, ypos, i, j, KOPTOptions[i].items_text[j], value_font, refresh) - end - KOPTOptions[i].text_dirty = false - end -end - -<<<<<<< HEAD -function KOPTConfig:config(callback, reader) - local kopt_callback = callback - local koptreader = reader -======= -function KOPTConfig:makeDefault() - for i=1,#KOPTOptions do - KOPTOptions[i].text_dirty = true - for j=1,#KOPTOptions[i].items_text do - KOPTOptions[i].marker_dirty[j] = true - end - end -end - -function KOPTConfig:reconfigure(configurable) - for i=1,#KOPTOptions do - option = KOPTOptions[i].name - configurable[option] = KOPTOptions[i].value[KOPTOptions[i].current_item] - end -end - -function KOPTConfig:config(callback, reader, configurable) - local kopt_callback = callback - local koptreader = reader - local configurable = configurable - - self:makeDefault() ->>>>>>> f4a2b5f... add page margin and text wrap and contrast option in koptconfig - self:addAllCommands() - - local name_font = Font:getFace("tfont", self.OPT_NAME_FONT_SIZE) - local value_font = Font:getFace("cfont", self.OPT_VALUE_FONT_SIZE) - - -- base window coordinates - local width, height = fb.bb:getWidth()-2*self.MARGIN_HORISONTAL, self.HEIGHT - local topleft_x, topleft_y = self.MARGIN_HORISONTAL, fb.bb:getHeight()-self.MARGIN_BOTTOM-height - local botleft_x, botleft_y = self.MARGIN_HORISONTAL, topleft_y+height - - self:drawBox(topleft_x, topleft_y, width, height, 3, 15) - self:drawOptions(topleft_x, topleft_y, name_font, value_font) - fb:refresh(1, topleft_x, topleft_y, width, height) - - local ev, keydef, command, ret_code - while true do - - self:reconfigure(configurable) - - if self.page_dirty then - kopt_callback(koptreader) - self:drawBox(topleft_x, topleft_y, width, height, 3, 15) - self:drawOptions(topleft_x, topleft_y, name_font, value_font, true) - fb:refresh(1, topleft_x, topleft_y, width, height) - self.page_dirty = false - end - self:drawOptions(topleft_x, topleft_y, name_font, value_font) - - ev = input.saveWaitForEvent() - ev.code = adjustKeyEvents(ev) - if ev.type == EV_KEY and ev.value ~= EVENT_VALUE_KEY_RELEASE then - keydef = Keydef:new(ev.code, getKeyModifier()) - Debug("key pressed: "..tostring(keydef)) - command = self.commands:getByKeydef(keydef) - if command ~= nil then - Debug("command to execute: "..tostring(command)) - ret_code = command.func(self, keydef) - else - Debug("command not found: "..tostring(command)) - end - if ret_code == "break" then - ret_code = nil - return nil - end - end -- if - end -- while -end - --- add available commands -function KOPTConfig:addAllCommands() - self.commands = Commands:new{} - self.commands:add(KEY_FW_DOWN, nil, "joypad down", - "next item", - function(self) - local last_option = self.current_option - self.current_option = (self.current_option + #KOPTOptions + 1)%#KOPTOptions - self.current_option = (self.current_option == 0) and #KOPTOptions or self.current_option - - last_option_item = KOPTOptions[last_option].current_item - KOPTOptions[last_option].marker_dirty[last_option_item] = true - current_option_item = KOPTOptions[self.current_option].current_item - KOPTOptions[self.current_option].marker_dirty[current_option_item] = true - end - ) - self.commands:add(KEY_FW_UP, nil, "joypad up", - "previous item", - function(self) - local last_option = self.current_option - self.current_option = (self.current_option + #KOPTOptions - 1)%#KOPTOptions - self.current_option = (self.current_option == 0) and #KOPTOptions or self.current_option - - last_option_item = KOPTOptions[last_option].current_item - KOPTOptions[last_option].marker_dirty[last_option_item] = true - current_option_item = KOPTOptions[self.current_option].current_item - KOPTOptions[self.current_option].marker_dirty[current_option_item] = true - end - ) - self.commands:add(KEY_FW_LEFT, nil, "joypad left", - "last item", - function(self) - local last_item = KOPTOptions[self.current_option].current_item - local item_count = #KOPTOptions[self.current_option].items_text - local current_item = (KOPTOptions[self.current_option].current_item + item_count - 1)%item_count - current_item = (current_item == 0) and item_count or current_item - KOPTOptions[self.current_option].current_item = current_item - - KOPTOptions[self.current_option].marker_dirty[last_item] = true - KOPTOptions[self.current_option].marker_dirty[current_item] = true - self.page_dirty = true - end - ) - self.commands:add(KEY_FW_RIGHT, nil, "joypad right", - "next item", - function(self) - local last_item = KOPTOptions[self.current_option].current_item - local item_count = #KOPTOptions[self.current_option].items_text - local current_item = (KOPTOptions[self.current_option].current_item + item_count + 1)%item_count - current_item = (current_item == 0) and item_count or current_item - KOPTOptions[self.current_option].current_item = current_item - - KOPTOptions[self.current_option].marker_dirty[last_item] = true - KOPTOptions[self.current_option].marker_dirty[current_item] = true - self.page_dirty = true - end - ) - self.commands:add({KEY_F,KEY_AA,KEY_BACK}, nil, "Back", - "back", - function(self) - return "break" - end - ) -end \ No newline at end of file From ddb46eeb41fe6a83cda2e3fb86fc147f6d69b072 Mon Sep 17 00:00:00 2001 From: chrox Date: Sat, 27 Oct 2012 11:06:53 +0800 Subject: [PATCH 61/70] merge justification and full justification to one option Conflicts: koptconfig.lua koptreader.lua --- djvu.c | 5 ++--- k2pdfopt.c | 18 ++++++++++++++++-- k2pdfopt.h | 2 +- pdf.c | 5 ++--- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/djvu.c b/djvu.c index b84725ab4..3e6f0c409 100644 --- a/djvu.c +++ b/djvu.c @@ -482,10 +482,9 @@ static int reflowPage(lua_State *L) { double word_spacing = luaL_checknumber(L, 8); int text_wrap = luaL_checkint(L, 9); int justification = luaL_checkint(L, 10); - int full_just = luaL_checkint(L, 11); - double contrast = luaL_checknumber(L, 12); + double contrast = luaL_checknumber(L, 11); - k2pdfopt_set_params(width, height, page_margin, line_spacing, word_spacing, text_wrap, justification, full_just, contrast); + k2pdfopt_set_params(width, height, page_margin, line_spacing, word_spacing, text_wrap, justification, contrast); k2pdfopt_djvu_reflow(page->page_ref, page->doc->context, mode, page->doc->pixelformat, dc->zoom, \ width, height, line_spacing, word_spacing); k2pdfopt_rfbmp_size(&width, &height); diff --git a/k2pdfopt.c b/k2pdfopt.c index 725a91987..560d2236f 100644 --- a/k2pdfopt.c +++ b/k2pdfopt.c @@ -474,21 +474,35 @@ static void k2pdfopt_reflow_bmp(MASTERINFO *masterinfo, WILLUSBITMAP *src) { void k2pdfopt_set_params(int bb_width, int bb_height, double page_margin, \ double line_space, double word_space, \ - int wrapping, int justification, int full_just, double contrast) { + int wrapping, int justification, double contrast) { dst_userwidth = bb_width; // dst_width is adjusted in adjust_params_init dst_userheight = bb_height; vertical_line_spacing = line_space; word_spacing = word_space; text_wrap = wrapping; dst_justify = justification; - dst_fulljustify = full_just; gamma_correction = contrast; // contrast is only used by k2pdfopt_mupdf_reflow + // margin dst_mar = page_margin; dst_martop = -1.0; dst_marbot = -1.0; dst_marleft = -1.0; dst_marright = -1.0; + + // justification + if (justification < 0) { + dst_justify = -1; + dst_fulljustify = -1; + } + else if (justification <= 2) { + dst_justify = justification; + dst_fulljustify = 0; + } + else { + dst_justify = -1; + dst_fulljustify = 1; + } } void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ diff --git a/k2pdfopt.h b/k2pdfopt.h index 1708f22eb..e454145dc 100644 --- a/k2pdfopt.h +++ b/k2pdfopt.h @@ -28,7 +28,7 @@ void k2pdfopt_set_params(int bb_width, int bb_height, double page_margin, \ double line_space, double word_space, \ - int wrapping, int justification, int full_just, double contrast); + int wrapping, int justification, double contrast); void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ double zoom, double gamma, double rot_deg); void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ diff --git a/pdf.c b/pdf.c index b2e6a6fb3..8001d5bac 100644 --- a/pdf.c +++ b/pdf.c @@ -522,10 +522,9 @@ static int reflowPage(lua_State *L) { double word_spacing = luaL_checknumber(L, 8); int text_wrap = luaL_checkint(L, 9); int justification = luaL_checkint(L, 10); - int full_just = luaL_checkint(L, 11); - double contrast = luaL_checknumber(L, 12); + double contrast = luaL_checknumber(L, 11); - k2pdfopt_set_params(width, height, page_margin, line_spacing, word_spacing, text_wrap, justification, full_just, contrast); + k2pdfopt_set_params(width, height, page_margin, line_spacing, word_spacing, text_wrap, justification, contrast); k2pdfopt_mupdf_reflow(page->doc->xref, page->page, page->doc->context, dc->zoom, dc->gamma, 0.0); k2pdfopt_rfbmp_size(&width, &height); k2pdfopt_rfbmp_zoom(&dc->zoom); From 8424011286bcf20fe43db56886d3fce13dc0011e Mon Sep 17 00:00:00 2001 From: chrox Date: Sat, 27 Oct 2012 11:16:31 +0800 Subject: [PATCH 62/70] cleanup: dst_justify is assigned according to both justification and full_just --- k2pdfopt.c | 1 - 1 file changed, 1 deletion(-) diff --git a/k2pdfopt.c b/k2pdfopt.c index 560d2236f..ab84bfc73 100644 --- a/k2pdfopt.c +++ b/k2pdfopt.c @@ -480,7 +480,6 @@ void k2pdfopt_set_params(int bb_width, int bb_height, double page_margin, \ vertical_line_spacing = line_space; word_spacing = word_space; text_wrap = wrapping; - dst_justify = justification; gamma_correction = contrast; // contrast is only used by k2pdfopt_mupdf_reflow // margin From 06f3c382acb37d9d9700123c23ca89b9cf59282a Mon Sep 17 00:00:00 2001 From: chrox Date: Sat, 27 Oct 2012 14:24:27 +0800 Subject: [PATCH 63/70] add font size option in koptconfig dialog Conflicts: koptconfig.lua koptreader.lua --- djvu.c | 18 +++++++++--------- k2pdfopt.c | 14 ++++++++------ k2pdfopt.h | 9 ++++----- pdf.c | 17 +++++++++-------- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/djvu.c b/djvu.c index 3e6f0c409..fd26a1c44 100644 --- a/djvu.c +++ b/djvu.c @@ -477,16 +477,16 @@ static int reflowPage(lua_State *L) { ddjvu_render_mode_t mode = (int) luaL_checkint(L, 3); int width = luaL_checkint(L, 4); // framebuffer size int height = luaL_checkint(L, 5); - double page_margin = luaL_checknumber(L, 6); - double line_spacing = luaL_checknumber(L, 7); - double word_spacing = luaL_checknumber(L, 8); - int text_wrap = luaL_checkint(L, 9); - int justification = luaL_checkint(L, 10); - double contrast = luaL_checknumber(L, 11); + double font_size = luaL_checknumber(L, 6); + double page_margin = luaL_checknumber(L, 7); + double line_spacing = luaL_checknumber(L, 8); + double word_spacing = luaL_checknumber(L, 9); + int text_wrap = luaL_checkint(L, 10); + int justification = luaL_checkint(L, 11); + double contrast = luaL_checknumber(L, 12); - k2pdfopt_set_params(width, height, page_margin, line_spacing, word_spacing, text_wrap, justification, contrast); - k2pdfopt_djvu_reflow(page->page_ref, page->doc->context, mode, page->doc->pixelformat, dc->zoom, \ - width, height, line_spacing, word_spacing); + k2pdfopt_set_params(width, height, font_size, page_margin, line_spacing, word_spacing, text_wrap, justification, contrast); + k2pdfopt_djvu_reflow(page->page_ref, page->doc->context, mode, page->doc->pixelformat); k2pdfopt_rfbmp_size(&width, &height); k2pdfopt_rfbmp_zoom(&dc->zoom); diff --git a/k2pdfopt.c b/k2pdfopt.c index ab84bfc73..63274bb0e 100644 --- a/k2pdfopt.c +++ b/k2pdfopt.c @@ -472,7 +472,8 @@ static void k2pdfopt_reflow_bmp(MASTERINFO *masterinfo, WILLUSBITMAP *src) { bmp_free(srcgrey); } -void k2pdfopt_set_params(int bb_width, int bb_height, double page_margin, \ +void k2pdfopt_set_params(int bb_width, int bb_height, \ + double font_size, double page_margin, \ double line_space, double word_space, \ int wrapping, int justification, double contrast) { dst_userwidth = bb_width; // dst_width is adjusted in adjust_params_init @@ -481,7 +482,7 @@ void k2pdfopt_set_params(int bb_width, int bb_height, double page_margin, \ word_spacing = word_space; text_wrap = wrapping; gamma_correction = contrast; // contrast is only used by k2pdfopt_mupdf_reflow - + zoom_value = font_size; // margin dst_mar = page_margin; dst_martop = -1.0; @@ -504,8 +505,7 @@ void k2pdfopt_set_params(int bb_width, int bb_height, double page_margin, \ } } -void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ - double zoom, double gamma, double rot_deg) { +void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx) { fz_device *dev; fz_pixmap *pix; fz_rect bounds,bounds2; @@ -513,7 +513,8 @@ void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ fz_bbox bbox; WILLUSBITMAP _src, *src; - double dpp; + double dpp,zoom; + zoom = zoom_value; double dpi = 250*zoom; do { dpp = dpi / 72.; @@ -566,11 +567,12 @@ void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ } void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ - ddjvu_render_mode_t mode, ddjvu_format_t *fmt, double zoom) { + ddjvu_render_mode_t mode, ddjvu_format_t *fmt) { WILLUSBITMAP _src, *src; ddjvu_rect_t prect; ddjvu_rect_t rrect; int i, iw, ih, idpi, status; + double zoom = zoom_value; double dpi = 250*zoom; while (!ddjvu_page_decoding_done(page)) diff --git a/k2pdfopt.h b/k2pdfopt.h index e454145dc..bb6978c4b 100644 --- a/k2pdfopt.h +++ b/k2pdfopt.h @@ -26,13 +26,12 @@ #include #include -void k2pdfopt_set_params(int bb_width, int bb_height, double page_margin, \ +void k2pdfopt_set_params(int bb_width, int bb_height, \ + double font_size, double page_margin, \ double line_space, double word_space, \ int wrapping, int justification, double contrast); -void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx, \ - double zoom, double gamma, double rot_deg); -void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, \ - ddjvu_render_mode_t mode, ddjvu_format_t *fmt, double zoom); +void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx); +void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, ddjvu_render_mode_t mode, ddjvu_format_t *fmt); void k2pdfopt_rfbmp_size(int *width, int *height); void k2pdfopt_rfbmp_ptr(unsigned char** bmp_ptr_ptr); void k2pdfopt_rfbmp_zoom(double *zoom); diff --git a/pdf.c b/pdf.c index 8001d5bac..1c2d5321d 100644 --- a/pdf.c +++ b/pdf.c @@ -517,15 +517,16 @@ static int reflowPage(lua_State *L) { DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); int width = luaL_checkint(L, 4); // framebuffer size int height = luaL_checkint(L, 5); - double page_margin = luaL_checknumber(L, 6); - double line_spacing = luaL_checknumber(L, 7); - double word_spacing = luaL_checknumber(L, 8); - int text_wrap = luaL_checkint(L, 9); - int justification = luaL_checkint(L, 10); - double contrast = luaL_checknumber(L, 11); + double font_size = luaL_checknumber(L, 6); + double page_margin = luaL_checknumber(L, 7); + double line_spacing = luaL_checknumber(L, 8); + double word_spacing = luaL_checknumber(L, 9); + int text_wrap = luaL_checkint(L, 10); + int justification = luaL_checkint(L, 11); + double contrast = luaL_checknumber(L, 12); - k2pdfopt_set_params(width, height, page_margin, line_spacing, word_spacing, text_wrap, justification, contrast); - k2pdfopt_mupdf_reflow(page->doc->xref, page->page, page->doc->context, dc->zoom, dc->gamma, 0.0); + k2pdfopt_set_params(width, height, font_size, page_margin, line_spacing, word_spacing, text_wrap, justification, contrast); + k2pdfopt_mupdf_reflow(page->doc->xref, page->page, page->doc->context); k2pdfopt_rfbmp_size(&width, &height); k2pdfopt_rfbmp_zoom(&dc->zoom); From 148dc4855642678acfee1a4b15b86623faa94cf2 Mon Sep 17 00:00:00 2001 From: chrox Date: Sat, 27 Oct 2012 14:55:50 +0800 Subject: [PATCH 64/70] add column detection in koptconfig Conflicts: koptconfig.lua koptreader.lua --- djvu.c | 6 ++++-- k2pdfopt.c | 7 +++++-- k2pdfopt.h | 3 ++- pdf.c | 6 ++++-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/djvu.c b/djvu.c index fd26a1c44..24e1c526f 100644 --- a/djvu.c +++ b/djvu.c @@ -483,9 +483,11 @@ static int reflowPage(lua_State *L) { double word_spacing = luaL_checknumber(L, 9); int text_wrap = luaL_checkint(L, 10); int justification = luaL_checkint(L, 11); - double contrast = luaL_checknumber(L, 12); + int columns = luaL_checkint(L, 12); + double contrast = luaL_checknumber(L, 13); - k2pdfopt_set_params(width, height, font_size, page_margin, line_spacing, word_spacing, text_wrap, justification, contrast); + k2pdfopt_set_params(width, height, font_size, page_margin, line_spacing, word_spacing, \ + text_wrap, justification, columns, contrast); k2pdfopt_djvu_reflow(page->page_ref, page->doc->context, mode, page->doc->pixelformat); k2pdfopt_rfbmp_size(&width, &height); k2pdfopt_rfbmp_zoom(&dc->zoom); diff --git a/k2pdfopt.c b/k2pdfopt.c index 63274bb0e..a9b5049c7 100644 --- a/k2pdfopt.c +++ b/k2pdfopt.c @@ -475,14 +475,17 @@ static void k2pdfopt_reflow_bmp(MASTERINFO *masterinfo, WILLUSBITMAP *src) { void k2pdfopt_set_params(int bb_width, int bb_height, \ double font_size, double page_margin, \ double line_space, double word_space, \ - int wrapping, int justification, double contrast) { + int wrapping, int justification, \ + int columns, double contrast) { dst_userwidth = bb_width; // dst_width is adjusted in adjust_params_init dst_userheight = bb_height; + zoom_value = font_size; vertical_line_spacing = line_space; word_spacing = word_space; text_wrap = wrapping; + max_columns = columns; gamma_correction = contrast; // contrast is only used by k2pdfopt_mupdf_reflow - zoom_value = font_size; + // margin dst_mar = page_margin; dst_martop = -1.0; diff --git a/k2pdfopt.h b/k2pdfopt.h index bb6978c4b..8ddf8f4de 100644 --- a/k2pdfopt.h +++ b/k2pdfopt.h @@ -29,7 +29,8 @@ void k2pdfopt_set_params(int bb_width, int bb_height, \ double font_size, double page_margin, \ double line_space, double word_space, \ - int wrapping, int justification, double contrast); + int wrapping, int justification, \ + int columns, double contrast); void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx); void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, ddjvu_render_mode_t mode, ddjvu_format_t *fmt); void k2pdfopt_rfbmp_size(int *width, int *height); diff --git a/pdf.c b/pdf.c index 1c2d5321d..056a31d94 100644 --- a/pdf.c +++ b/pdf.c @@ -523,9 +523,11 @@ static int reflowPage(lua_State *L) { double word_spacing = luaL_checknumber(L, 9); int text_wrap = luaL_checkint(L, 10); int justification = luaL_checkint(L, 11); - double contrast = luaL_checknumber(L, 12); + int columns = luaL_checkint(L, 12); + double contrast = luaL_checknumber(L, 13); - k2pdfopt_set_params(width, height, font_size, page_margin, line_spacing, word_spacing, text_wrap, justification, contrast); + k2pdfopt_set_params(width, height, font_size, page_margin, line_spacing, word_spacing, \ + text_wrap, justification, columns, contrast); k2pdfopt_mupdf_reflow(page->doc->xref, page->page, page->doc->context); k2pdfopt_rfbmp_size(&width, &height); k2pdfopt_rfbmp_zoom(&dc->zoom); From 2c3b4fa9687fd1b70ae14c1f4a26c4fdfe64ffe7 Mon Sep 17 00:00:00 2001 From: chrox Date: Sat, 27 Oct 2012 16:03:49 +0800 Subject: [PATCH 65/70] add auto straighten option for scanned pages Conflicts: koptconfig.lua koptreader.lua --- djvu.c | 9 +- k2pdfopt.c | 381 ++++++++++++++++++++++++++++++++++++++++++++++++++++- k2pdfopt.h | 2 +- pdf.c | 9 +- 4 files changed, 391 insertions(+), 10 deletions(-) diff --git a/djvu.c b/djvu.c index 24e1c526f..02ecd790c 100644 --- a/djvu.c +++ b/djvu.c @@ -482,12 +482,13 @@ static int reflowPage(lua_State *L) { double line_spacing = luaL_checknumber(L, 8); double word_spacing = luaL_checknumber(L, 9); int text_wrap = luaL_checkint(L, 10); - int justification = luaL_checkint(L, 11); - int columns = luaL_checkint(L, 12); - double contrast = luaL_checknumber(L, 13); + int straighten = luaL_checkint(L, 11); + int justification = luaL_checkint(L, 12); + int columns = luaL_checkint(L, 13); + double contrast = luaL_checknumber(L, 14); k2pdfopt_set_params(width, height, font_size, page_margin, line_spacing, word_spacing, \ - text_wrap, justification, columns, contrast); + text_wrap, straighten, justification, columns, contrast); k2pdfopt_djvu_reflow(page->page_ref, page->doc->context, mode, page->doc->pixelformat); k2pdfopt_rfbmp_size(&width, &height); k2pdfopt_rfbmp_zoom(&dc->zoom); diff --git a/k2pdfopt.c b/k2pdfopt.c index a9b5049c7..ed7dc1e17 100644 --- a/k2pdfopt.c +++ b/k2pdfopt.c @@ -82,6 +82,7 @@ /* bmp.c */ #define WILLUSBITMAP_TYPE_NATIVE 0 #define WILLUSBITMAP_TYPE_WIN32 1 +#define BOUND(x,xmin,xmax) if ((x)<(xmin)) (x)=(xmin); else { if ((x)>(xmax)) (x)=(xmax); } #ifdef PI #undef PI @@ -218,6 +219,7 @@ static double dst_min_figure_height_in = 0.75; static int dst_fulljustify = -1; // 0 = no, 1 = yes static int dst_color = 0; static int dst_landscape = 0; +static int src_autostraighten = 0; static double dst_mar = 0.06; static double dst_martop = -1.0; static double dst_marbot = -1.0; @@ -398,6 +400,8 @@ static int bmp_resample(WILLUSBITMAP *dest, WILLUSBITMAP *src, double x1, double y1, double x2, double y2, int newwidth, int newheight); static void bmp_contrast_adjust(WILLUSBITMAP *dest,WILLUSBITMAP *src,double contrast); static void bmp_convert_to_greyscale_ex(WILLUSBITMAP *dst, WILLUSBITMAP *src); +static double bmp_autostraighten(WILLUSBITMAP *src, WILLUSBITMAP *srcgrey, int white, + double maxdegrees, double mindegrees, int debug); static int bmpmupdf_pixmap_to_bmp(WILLUSBITMAP *bmp, fz_context *ctx, fz_pixmap *pixmap); static void handle(int wait, ddjvu_context_t *ctx); @@ -453,6 +457,15 @@ static void k2pdfopt_reflow_bmp(MASTERINFO *masterinfo, WILLUSBITMAP *src) { adjust_contrast(src, srcgrey, &white); white_margins(src, srcgrey); + if (erase_vertical_lines > 0) + bmp_detect_vertical_lines(srcgrey, src, (double) src_dpi, 0.005, 0.25, + min_column_height_inches, src_autostraighten, white); + if (src_autostraighten > 0.) { + double rot; + rot = bmp_autostraighten(src, srcgrey, white, src_autostraighten, 0.1, debug); + pageinfo->page_rot_deg += rot; + } + region.r1 = 0; region.r2 = srcgrey->height - 1; region.c1 = 0; @@ -475,7 +488,7 @@ static void k2pdfopt_reflow_bmp(MASTERINFO *masterinfo, WILLUSBITMAP *src) { void k2pdfopt_set_params(int bb_width, int bb_height, \ double font_size, double page_margin, \ double line_space, double word_space, \ - int wrapping, int justification, \ + int wrapping, int straighten, int justification, \ int columns, double contrast) { dst_userwidth = bb_width; // dst_width is adjusted in adjust_params_init dst_userheight = bb_height; @@ -483,6 +496,7 @@ void k2pdfopt_set_params(int bb_width, int bb_height, \ vertical_line_spacing = line_space; word_spacing = word_space; text_wrap = wrapping; + src_autostraighten = straighten; max_columns = columns; gamma_correction = contrast; // contrast is only used by k2pdfopt_mupdf_reflow @@ -6426,6 +6440,371 @@ static void bmp_convert_to_greyscale_ex(WILLUSBITMAP *dst, WILLUSBITMAP *src) dst->bpp = 8; /* Possibly restore dst->bpp to 8 since src & dst may be the same. */ } +/* + ** Bitmap is assumed to be grayscale + */ +static double bmp_row_by_row_stdev(WILLUSBITMAP *bmp, int ccount, + int whitethresh, double theta_radians) + +{ + int dc1, dc2, c1, c2; + int r, n, nn, dw; + double tanth, csum, csumsq, stdev; + + c1 = bmp->width / 15.; + c2 = bmp->width - c1; + dw = (int) ((c2 - c1) / ccount + .5); + if (dw < 1) + dw = 1; + tanth = -tan(theta_radians); + dc1 = (int) (tanth * bmp->width); + if (dc1 < 0) { + dc1 = 1 - dc1; + dc2 = 0; + } else { + dc2 = -dc1 - 1; + dc1 = 0; + } + dc1 += bmp->height / 15.; + dc2 -= bmp->height / 15.; + csum = csumsq = 0.; + n = 0; + for (r = dc1 + 1; r < bmp->height + dc2 - 1; r++) { + int c, count, r0last; + double dcount; + unsigned char *p; + + r0last = 0; + p = bmp_rowptr_from_top(bmp, r0last); + for (nn = count = 0, c = c1; c < c2; c += dw) { + int r0; + + r0 = r + tanth * c; + if (r0 < 0 || r0 >= bmp->height) + continue; + if (r0 != r0last) { + r0last = r0; + p = bmp_rowptr_from_top(bmp, r0last); + } + nn++; + if (p[c] < whitethresh) + count++; + } + dcount = 100. * count / nn; + csum += dcount; + csumsq += dcount * dcount; + n++; + } + stdev = sqrt(fabs((csum / n) * (csum / n) - csumsq / n)); + return (stdev); +} + +/* + ** y0 = 0 ==> bottom row! + */ +static void bmp_pix_vali(WILLUSBITMAP *bmp, int x0, int y0, int *r, int *g, int *b) + +{ + unsigned char *p; + int rr, gg, bb; + + p = bmp_rowptr_from_top(bmp, bmp->height - 1 - y0); + p = &p[x0 * (bmp->bpp >> 3)]; + RGBGET(bmp, p, rr, gg, bb); + (*r) = rr; + (*g) = gg; + (*b) = bb; +} + +/* + ** y0 = 0 ==> bottom row! + */ +static int bmp_grey_pix_vali(WILLUSBITMAP *bmp, int x0, int y0) + +{ + unsigned char *p; + int r, g, b; + + p = bmp_rowptr_from_top(bmp, bmp->height - 1 - y0); + p = &p[x0 * (bmp->bpp >> 3)]; + RGBGET(bmp, p, r, g, b); + return (bmp8_greylevel_convert(r, g, b)); +} + +/* + ** Return pix value (0.0 - 255.0) in double precision given + ** a double precision position. Bitmap is assumed to be 8-bit greyscale. + ** + ** x0,y0 are from bottom corner. + ** x0=0.5, y0=0.5 would give exactly the value of the pixel + ** in the lower left corner of the bitmap. + */ +double bmp_grey_pix_vald(WILLUSBITMAP *bmp, double x0, double y0) + +{ + int ix0, iy0, ix1, iy1; + double fx0, fx1, fy0, fy1; + + ix0 = (int) (x0 - .5); + ix1 = ix0 + 1; + iy0 = (int) (y0 - .5); + iy1 = iy0 + 1; + BOUND(ix0, 0, bmp->width - 1); + BOUND(ix1, 0, bmp->width - 1); + BOUND(iy0, 0, bmp->height - 1); + BOUND(iy1, 0, bmp->height - 1); + fx0 = 1. - fabs(ix0 + 0.5 - x0); + if (fx0 < 0.) + fx0 = 0.; + fx1 = 1. - fabs(ix1 + 0.5 - x0); + if (fx1 < 0.) + fx1 = 0.; + fy0 = 1. - fabs(iy0 + 0.5 - y0); + if (fy0 < 0.) + fy0 = 0.; + fy1 = 1. - fabs(iy1 + 0.5 - y0); + if (fy1 < 0.) + fy1 = 0.; + if ((fx0 == 0. && fx1 == 0.) || (fy0 == 0. && fy1 == 0.)) + return (-1.); + return ((fy0 + * (fx0 * bmp_grey_pix_vali(bmp, ix0, iy0) + + fx1 * bmp_grey_pix_vali(bmp, ix1, iy0)) + + fy1 + * (fx0 * bmp_grey_pix_vali(bmp, ix0, iy1) + + fx1 * bmp_grey_pix_vali(bmp, ix1, iy1))) + / ((fx0 + fx1) * (fy0 + fy1))); +} + + +/* + ** Return pix values (0.0 - 255.0) in double precision given + ** a double precision position. + ** + ** x0,y0 are from BOTTOM CORNER. + ** x0=0.5, y0=0.5 would give exactly the value of the pixel + ** in the lower left corner of the bitmap. + */ +static void bmp_pix_vald(WILLUSBITMAP *bmp, double x0, double y0, double *r, double *g, + double *b) + +{ + int ix0, iy0, ix1, iy1; + double fx0, fx1, fy0, fy1; + int r00, r10, r01, r11; + int g00, g10, g01, g11; + int b00, b10, b01, b11; + + ix0 = (int) (x0 - .5); + ix1 = ix0 + 1; + iy0 = (int) (y0 - .5); + iy1 = iy0 + 1; + BOUND(ix0, 0, bmp->width - 1); + BOUND(ix1, 0, bmp->width - 1); + BOUND(iy0, 0, bmp->height - 1); + BOUND(iy1, 0, bmp->height - 1); + fx0 = 1. - fabs(ix0 + 0.5 - x0); + if (fx0 < 0.) + fx0 = 0.; + fx1 = 1. - fabs(ix1 + 0.5 - x0); + if (fx1 < 0.) + fx1 = 0.; + fy0 = 1. - fabs(iy0 + 0.5 - y0); + if (fy0 < 0.) + fy0 = 0.; + fy1 = 1. - fabs(iy1 + 0.5 - y0); + if (fy1 < 0.) + fy1 = 0.; + if ((fx0 == 0. && fx1 == 0.) || (fy0 == 0. && fy1 == 0.)) { + (*r) = (*g) = (*b) = -1.; + return; + } + bmp_pix_vali(bmp, ix0, iy0, &r00, &g00, &b00); + bmp_pix_vali(bmp, ix1, iy0, &r10, &g10, &b10); + bmp_pix_vali(bmp, ix0, iy1, &r01, &g01, &b01); + bmp_pix_vali(bmp, ix1, iy1, &r11, &g11, &b11); + (*r) = ((fy0 * (fx0 * r00 + fx1 * r10) + fy1 * (fx0 * r01 + fx1 * r11)) + / ((fx0 + fx1) * (fy0 + fy1))); + (*g) = ((fy0 * (fx0 * g00 + fx1 * g10) + fy1 * (fx0 * g01 + fx1 * g11)) + / ((fx0 + fx1) * (fy0 + fy1))); + (*b) = ((fy0 * (fx0 * b00 + fx1 * b10) + fy1 * (fx0 * b01 + fx1 * b11)) + / ((fx0 + fx1) * (fy0 + fy1))); +} + +static void bmp_rotate_fast(WILLUSBITMAP *bmp, double degrees, int expand) + +{ + WILLUSBITMAP _dst, *dst; + double th, sth, cth; + int i, r, g, b, w, h, row, col; + + dst = &_dst; + th = degrees * PI / 180.; + sth = sin(th); + cth = cos(th); + if (expand) { + w = (int) (fabs(bmp->width * cth) + fabs(bmp->height * sth) + .5); + h = (int) (fabs(bmp->height * cth) + fabs(bmp->width * sth) + .5); + } else { + w = bmp->width; + h = bmp->height; + } + dst = &_dst; + bmp_init(dst); + dst->width = w; + dst->height = h; + dst->bpp = bmp->bpp; + if (dst->bpp == 8) + for (i = 0; i <= 255; i++) + dst->red[i] = dst->green[i] = dst->blue[i] = i; + bmp_alloc(dst); + bmp_pix_vali(bmp, 0, 0, &r, &g, &b); + bmp_fill(dst, r, g, b); + if (dst->bpp == 8) + for (row = 0; row < dst->height; row++) { + unsigned char *p; + double x1, y1, x2, y2; + + y2 = dst->height / 2. - row; + p = bmp_rowptr_from_top(dst, row); + for (x2 = -dst->width / 2., col = 0; col < dst->width; + col++, p++, x2 += 1.0) { + double g; + x1 = -.5 + bmp->width / 2. + x2 * cth + y2 * sth; + y1 = -.5 + bmp->height / 2. + y2 * cth - x2 * sth; + if (x1 < 0. || x1 >= bmp->width || y1 < 0. || y1 >= bmp->height) + continue; + g = bmp_grey_pix_vald(bmp, x1, y1); + if (g >= 0.) + p[0] = g; + } + } + else + for (row = 0; row < dst->height; row++) { + unsigned char *p; + double x1, y1, x2, y2; + + y2 = dst->height / 2. - row; + p = bmp_rowptr_from_top(dst, row); + for (x2 = -dst->width / 2., col = 0; col < dst->width; col++, p += + 3, x2 += 1.0) { + double rr, gg, bb; + x1 = -.5 + bmp->width / 2. + x2 * cth + y2 * sth; + y1 = -.5 + bmp->height / 2. + y2 * cth - x2 * sth; + if (x1 < 0. || x1 >= bmp->width || y1 < 0. || y1 >= bmp->height) + continue; + bmp_pix_vald(bmp, x1, y1, &rr, &gg, &bb); + if (rr < 0.) + continue; + p[0] = rr; + p[1] = gg; + p[2] = bb; + } + } + bmp_copy(bmp, dst); + bmp_free(dst); +} + +static double bmp_autostraighten(WILLUSBITMAP *src, WILLUSBITMAP *srcgrey, int white, + double maxdegrees, double mindegrees, int debug) + +{ + int i, na, n, imax; + double stepsize, sdmin, sdmax, rotdeg; + double *sdev; + static int rpc = 0; + static char *funcname = "bmp_autostraighten"; + + rpc++; + stepsize = .5; + na = (int) (maxdegrees / stepsize + .5); + if (na < 1) + na = 1; + n = 1 + na * 2; + sdmin = 999.; + sdmax = -999.; + imax = 0; + willus_mem_alloc_warn((void **) &sdev, n * sizeof(double), funcname, 10); + for (i = 0; i < n; i++) { + double theta, sdev0; + + theta = (i - na) * stepsize * PI / 180.; + sdev0 = bmp_row_by_row_stdev(srcgrey, 400, white, theta); + if (sdmin > sdev0) + sdmin = sdev0; + if (sdmax < sdev0) { + imax = i; + sdmax = sdev0; + } + sdev[i] = sdev0; + } + if (sdmax <= 0.) { + willus_mem_free((double **) &sdev, funcname); + return (0.); + } + for (i = 0; i < n; i++) + sdev[i] /= sdmax; + sdmin /= sdmax; + rotdeg = -(imax - na) * stepsize; + if (sdmin > 0.95 || fabs(rotdeg) <= mindegrees + || fabs(fabs(rotdeg) - fabs(maxdegrees)) < 0.25) { + willus_mem_free((double **) &sdev, funcname); + return (0.); + } + if (imax >= 3 && imax <= n - 4) { + double sd1min, sd2min, sdthresh; + + for (sd1min = sdev[imax - 1], i = imax - 2; i >= 0; i--) + if (sd1min > sdev[i]) + sd1min = sdev[i]; + for (sd2min = sdev[imax + 1], i = imax + 2; i < n; i++) + if (sd2min > sdev[i]) + sd2min = sdev[i]; + sdthresh = sd1min > sd2min ? sd1min * 1.01 : sd2min * 1.01; + if (sdthresh < 0.9) + sdthresh = 0.9; + if (sdthresh < 0.95) { + double deg1, deg2; + + for (i = imax - 1; i >= 0; i--) + if (sdev[i] < sdthresh) + break; + deg1 = + stepsize + * ((i - na) + + (sdthresh - sdev[i]) + / (sdev[i + 1] - sdev[i])); + for (i = imax + 1; i < n - 1; i++) + if (sdev[i] < sdthresh) + break; + deg2 = + stepsize + * ((i - na) + - (sdthresh - sdev[i]) + / (sdev[i - 1] - sdev[i])); + if (deg2 - deg1 < 2.5) { + rotdeg = -(deg1 + deg2) / 2.; + if (debug) + printf("/sa l \"srcpage %d, %.1f%% line\" 2\n/sa m 2 2\n" + "%g 0\n%g 1\n//nc\n" + "/sa l \"srcpage %d, %.1f%% line\" 2\n/sa m 2 2\n" + "%g 0\n%g 1\n//nc\n", rpc, sdthresh * 100., + deg1, deg1, rpc, sdthresh * 100., deg2, deg2); + } + } + } + printf("\n(Straightening page: rotating cc by %.2f deg.)\n", rotdeg); + /* BMP rotation fills with pixel value at (0,0) */ + srcgrey->data[0] = 255; + bmp_rotate_fast(srcgrey, rotdeg, 0); + if (src != NULL) { + src->data[0] = src->data[1] = src->data[2] = 255; + bmp_rotate_fast(src, rotdeg, 0); + } + willus_mem_free((double **) &sdev, funcname); + return (rotdeg); +} + /* bmpmupdf.c */ static int bmpmupdf_pixmap_to_bmp(WILLUSBITMAP *bmp, fz_context *ctx, fz_pixmap *pixmap) diff --git a/k2pdfopt.h b/k2pdfopt.h index 8ddf8f4de..ae49c64c8 100644 --- a/k2pdfopt.h +++ b/k2pdfopt.h @@ -29,7 +29,7 @@ void k2pdfopt_set_params(int bb_width, int bb_height, \ double font_size, double page_margin, \ double line_space, double word_space, \ - int wrapping, int justification, \ + int wrapping, int straighten, int justification, \ int columns, double contrast); void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx); void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, ddjvu_render_mode_t mode, ddjvu_format_t *fmt); diff --git a/pdf.c b/pdf.c index 056a31d94..10748f9e5 100644 --- a/pdf.c +++ b/pdf.c @@ -522,12 +522,13 @@ static int reflowPage(lua_State *L) { double line_spacing = luaL_checknumber(L, 8); double word_spacing = luaL_checknumber(L, 9); int text_wrap = luaL_checkint(L, 10); - int justification = luaL_checkint(L, 11); - int columns = luaL_checkint(L, 12); - double contrast = luaL_checknumber(L, 13); + int straighten = luaL_checkint(L, 11); + int justification = luaL_checkint(L, 12); + int columns = luaL_checkint(L, 13); + double contrast = luaL_checknumber(L, 14); k2pdfopt_set_params(width, height, font_size, page_margin, line_spacing, word_spacing, \ - text_wrap, justification, columns, contrast); + text_wrap, straighten, justification, columns, contrast); k2pdfopt_mupdf_reflow(page->doc->xref, page->page, page->doc->context); k2pdfopt_rfbmp_size(&width, &height); k2pdfopt_rfbmp_zoom(&dc->zoom); From 0b2f89d830f295f5925cee6981ffedc129ff732b Mon Sep 17 00:00:00 2001 From: chrox Date: Sat, 27 Oct 2012 22:38:53 +0800 Subject: [PATCH 66/70] add screen rotation in koptconfig Conflicts: koptconfig.lua koptreader.lua --- djvu.c | 4 +- k2pdfopt.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++- k2pdfopt.h | 2 +- pdf.c | 3 +- 4 files changed, 145 insertions(+), 4 deletions(-) diff --git a/djvu.c b/djvu.c index 02ecd790c..c80ba75ad 100644 --- a/djvu.c +++ b/djvu.c @@ -486,9 +486,11 @@ static int reflowPage(lua_State *L) { int justification = luaL_checkint(L, 12); int columns = luaL_checkint(L, 13); double contrast = luaL_checknumber(L, 14); + int rotation = luaL_checknumber(L, 15); k2pdfopt_set_params(width, height, font_size, page_margin, line_spacing, word_spacing, \ - text_wrap, straighten, justification, columns, contrast); + text_wrap, straighten, justification, columns, contrast, rotation); + k2pdfopt_djvu_reflow(page->page_ref, page->doc->context, mode, page->doc->pixelformat); k2pdfopt_rfbmp_size(&width, &height); k2pdfopt_rfbmp_zoom(&dc->zoom); diff --git a/k2pdfopt.c b/k2pdfopt.c index ed7dc1e17..163a728cf 100644 --- a/k2pdfopt.c +++ b/k2pdfopt.c @@ -200,6 +200,7 @@ static int debug = 0; ** Blank Area Threshold Widths--average black pixel width, in inches, that ** prevents a region from being determined as "blank" or clear. */ +static int src_rot = 0; static double gtc_in = .005; // detecting gap between columns static double gtr_in = .006; // detecting gap between rows static double gtw_in = .0015; // detecting gap between words @@ -402,6 +403,7 @@ static void bmp_contrast_adjust(WILLUSBITMAP *dest,WILLUSBITMAP *src,double cont static void bmp_convert_to_greyscale_ex(WILLUSBITMAP *dst, WILLUSBITMAP *src); static double bmp_autostraighten(WILLUSBITMAP *src, WILLUSBITMAP *srcgrey, int white, double maxdegrees, double mindegrees, int debug); +static int bmp_rotate_right_angle(WILLUSBITMAP *bmp, int degrees); static int bmpmupdf_pixmap_to_bmp(WILLUSBITMAP *bmp, fz_context *ctx, fz_pixmap *pixmap); static void handle(int wait, ddjvu_context_t *ctx); @@ -452,6 +454,10 @@ static void k2pdfopt_reflow_bmp(MASTERINFO *masterinfo, WILLUSBITMAP *src) { bmp_alloc(&masterinfo->bmp); bmp_fill(&masterinfo->bmp, 255, 255, 255); + if (src_rot != 0) { + bmp_rotate_right_angle(src, src_rot); + } + BMPREGION region; bmp_copy(srcgrey, src); adjust_contrast(src, srcgrey, &white); @@ -489,7 +495,7 @@ void k2pdfopt_set_params(int bb_width, int bb_height, \ double font_size, double page_margin, \ double line_space, double word_space, \ int wrapping, int straighten, int justification, \ - int columns, double contrast) { + int columns, double contrast, int rotation) { dst_userwidth = bb_width; // dst_width is adjusted in adjust_params_init dst_userheight = bb_height; zoom_value = font_size; @@ -499,6 +505,7 @@ void k2pdfopt_set_params(int bb_width, int bb_height, \ src_autostraighten = straighten; max_columns = columns; gamma_correction = contrast; // contrast is only used by k2pdfopt_mupdf_reflow + src_rot = rotation; // margin dst_mar = page_margin; @@ -6805,6 +6812,137 @@ static double bmp_autostraighten(WILLUSBITMAP *src, WILLUSBITMAP *srcgrey, int w return (rotdeg); } +static void bmp_flip_horizontal(WILLUSBITMAP *bmp) + +{ + int i, j, bpp; + + bpp = bmp->bpp / 8; + for (i = 0; i < bmp->height; i++) { + unsigned char *p, *p2; + + p = bmp_rowptr_from_top(bmp, i); + p2 = &p[(bmp->width - 1) * bpp]; + for (; p < p2; p += bpp, p2 -= bpp) + for (j = 0; j < bpp; j++) { + unsigned char t; + t = p[j]; + p[j] = p2[j]; + p2[j] = t; + } + } +} + +static void bmp_flip_vertical(WILLUSBITMAP *bmp) + +{ + int i, bw, n; + + bw = bmp_bytewidth(bmp); + n = bmp->height / 2; + for (i = 0; i < n; i++) { + unsigned char *p, *p2; + int j; + + p = bmp_rowptr_from_top(bmp, i); + p2 = bmp_rowptr_from_top(bmp, bmp->height - i - 1); + for (j = bw; j > 0; j--, p++, p2++) { + unsigned char t; + t = p[0]; + p[0] = p2[0]; + p2[0] = t; + } + } +} + +static int bmp_rotate_90(WILLUSBITMAP *bmp) + +{ + WILLUSBITMAP *sbmp, _sbmp; + int bpp, dbw, sr; + + sbmp = &_sbmp; + bmp_init(sbmp); + if (!bmp_copy(sbmp, bmp)) + return (0); + bmp->width = sbmp->height; + bmp->height = sbmp->width; + bpp = bmp->bpp / 8; + if (!bmp_alloc(bmp)) { + bmp_free(sbmp); + return (0); + } + dbw = (int) (bmp_rowptr_from_top(bmp, 1) - bmp_rowptr_from_top(bmp, 0)); + for (sr = 0; sr < sbmp->height; sr++) { + unsigned char *sp, *dp; + int j, sc; + + sp = bmp_rowptr_from_top(sbmp, sr); + dp = bmp_rowptr_from_top(bmp, bmp->height - 1) + bpp * sr; + for (sc = sbmp->width; sc > 0; sc--, dp -= dbw) + for (j = 0; j < bpp; j++, sp++) + dp[j] = sp[0]; + } + bmp_free(sbmp); + return (1); +} + +static int bmp_rotate_270(WILLUSBITMAP *bmp) + +{ + WILLUSBITMAP *sbmp, _sbmp; + int bpp, dbw, sr; + + sbmp = &_sbmp; + bmp_init(sbmp); + if (!bmp_copy(sbmp, bmp)) + return (0); + bmp->width = sbmp->height; + bmp->height = sbmp->width; + bpp = bmp->bpp / 8; + if (!bmp_alloc(bmp)) { + bmp_free(sbmp); + return (0); + } + dbw = (int) (bmp_rowptr_from_top(bmp, 1) - bmp_rowptr_from_top(bmp, 0)); + for (sr = 0; sr < sbmp->height; sr++) { + unsigned char *sp, *dp; + int j, sc; + + sp = bmp_rowptr_from_top(sbmp, sr); + dp = bmp_rowptr_from_top(bmp, 0) + bpp * (sbmp->height - 1 - sr); + for (sc = sbmp->width; sc > 0; sc--, dp += dbw) + for (j = 0; j < bpp; j++, sp++) + dp[j] = sp[0]; + } + bmp_free(sbmp); + return (1); +} + +/* + ** 1 = okay, 0 = fail + */ +static int bmp_rotate_right_angle(WILLUSBITMAP *bmp, int degrees) + +{ + int d; + + d = degrees % 360; + if (d < 0) + d += 360; + d = (d + 45) / 90; + if (d == 1) + return (bmp_rotate_90(bmp)); + if (d == 2) { + bmp_flip_horizontal(bmp); + bmp_flip_vertical(bmp); + return (1); + } + if (d == 3) + return (bmp_rotate_270(bmp)); + return (1); +} + /* bmpmupdf.c */ static int bmpmupdf_pixmap_to_bmp(WILLUSBITMAP *bmp, fz_context *ctx, fz_pixmap *pixmap) diff --git a/k2pdfopt.h b/k2pdfopt.h index ae49c64c8..1c8c3a884 100644 --- a/k2pdfopt.h +++ b/k2pdfopt.h @@ -30,7 +30,7 @@ void k2pdfopt_set_params(int bb_width, int bb_height, \ double font_size, double page_margin, \ double line_space, double word_space, \ int wrapping, int straighten, int justification, \ - int columns, double contrast); + int columns, double contrast, int rotation); void k2pdfopt_mupdf_reflow(fz_document *doc, fz_page *page, fz_context *ctx); void k2pdfopt_djvu_reflow(ddjvu_page_t *page, ddjvu_context_t *ctx, ddjvu_render_mode_t mode, ddjvu_format_t *fmt); void k2pdfopt_rfbmp_size(int *width, int *height); diff --git a/pdf.c b/pdf.c index 10748f9e5..133574f4e 100644 --- a/pdf.c +++ b/pdf.c @@ -526,9 +526,10 @@ static int reflowPage(lua_State *L) { int justification = luaL_checkint(L, 12); int columns = luaL_checkint(L, 13); double contrast = luaL_checknumber(L, 14); + int rotation = luaL_checknumber(L, 15); k2pdfopt_set_params(width, height, font_size, page_margin, line_spacing, word_spacing, \ - text_wrap, straighten, justification, columns, contrast); + text_wrap, straighten, justification, columns, contrast, rotation); k2pdfopt_mupdf_reflow(page->doc->xref, page->page, page->doc->context); k2pdfopt_rfbmp_size(&width, &height); k2pdfopt_rfbmp_zoom(&dc->zoom); From d4642a5df12dfc802ae79c3984d2e3893fb7c362 Mon Sep 17 00:00:00 2001 From: Tigran Aivazian Date: Wed, 31 Oct 2012 14:34:17 +0000 Subject: [PATCH 67/70] Build djvulibre as a shared library --- Makefile | 26 ++++++++++++++++++-------- kpdf.sh | 1 + 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 0ef81f324..5c0a64fb1 100644 --- a/Makefile +++ b/Makefile @@ -90,7 +90,8 @@ KPDFREADER_CFLAGS=$(CFLAGS) -I$(LUADIR)/src -I$(MUPDFDIR)/ # for now, all dependencies except for the libc are compiled into the final binary: MUPDFLIBS := $(MUPDFLIBDIR)/libfitz.a -DJVULIBS := $(DJVUDIR)/build/libdjvu/.libs/libdjvulibre.a +DJVULIBS := $(DJVUDIR)/build/libdjvu/.libs/libdjvulibre.so +DJVULIBDIR := $(DJVUDIR)/build/libdjvu/.libs/ CRENGINELIBS := $(CRENGINEDIR)/crengine/libcrengine.a \ $(CRENGINEDIR)/thirdparty/chmlib/libchmlib.a \ $(CRENGINEDIR)/thirdparty/libpng/libpng.a \ @@ -132,7 +133,6 @@ kpdfview: kpdfview.o einkfb.o pdf.o k2pdfopt.o blitbuffer.o drawcontext.o input. $(THIRDPARTYLIBS) \ $(LUALIB) \ djvu.o \ - $(DJVULIBS) \ pic.o \ pic_jpeg.o \ cre.o \ @@ -140,7 +140,7 @@ kpdfview: kpdfview.o einkfb.o pdf.o k2pdfopt.o blitbuffer.o drawcontext.o input. $(STATICLIBSTDCPP) \ $(LDFLAGS) \ -o $@ \ - -lm -ldl -lpthread -ljpeg -L$(MUPDFLIBDIR) \ + -lm -ldl -lpthread -ldjvulibre -ljpeg -L$(MUPDFLIBDIR) -L$(DJVULIBDIR)\ $(EMU_LDFLAGS) \ $(DYNAMICLIBSTDCPP) @@ -216,6 +216,11 @@ clean: rm -f *.o kpdfview slider_watcher extr cleanthirdparty: +ifdef EMULATE_READER + rm -rf libs-emu ; mkdir libs-emu +else + rm -rf libs ; mkdir libs +endif $(MAKE) -C $(LUADIR) CC="$(HOSTCC)" CFLAGS="$(BASE_CFLAGS)" clean $(MAKE) -C $(MUPDFDIR) build="release" clean $(MAKE) -C $(CRENGINEDIR)/thirdparty/antiword clean @@ -245,11 +250,14 @@ $(MUPDFLIBS) $(THIRDPARTYLIBS): $(MUPDFDIR)/cmapdump.host $(MUPDFDIR)/fontdump.h $(DJVULIBS): mkdir -p $(DJVUDIR)/build ifdef EMULATE_READER - cd $(DJVUDIR)/build && CC="$(HOSTCC)" CXX="$(HOSTCXX)" CFLAGS="$(HOSTCFLAGS)" CXXFLAGS="$(HOSTCFLAGS)" LDFLAGS="$(LDFLAGS)" ../configure --disable-desktopfiles --disable-shared --enable-static --disable-xmltools --disable-largefile -else - cd $(DJVUDIR)/build && CC="$(CC)" CXX="$(CXX)" CFLAGS="$(CFLAGS)" CXXFLAGS="$(CXXFLAGS)" LDFLAGS="$(LDFLAGS)" ../configure --disable-desktopfiles --disable-shared --enable-static --host=$(CHOST) --disable-xmltools --disable-largefile -endif + cd $(DJVUDIR)/build && CC="$(HOSTCC)" CXX="$(HOSTCXX)" CFLAGS="$(HOSTCFLAGS)" CXXFLAGS="$(HOSTCFLAGS)" LDFLAGS="$(LDFLAGS)" ../configure --disable-desktopfiles --disable-static --enable-shared --disable-xmltools --disable-largefile $(MAKE) -C $(DJVUDIR)/build + cp -a $(DJVULIBDIR)/libdjvulibre.so* libs-emu +else + cd $(DJVUDIR)/build && CC="$(CC)" CXX="$(CXX)" CFLAGS="$(CFLAGS)" CXXFLAGS="$(CXXFLAGS)" LDFLAGS="$(LDFLAGS)" ../configure --disable-desktopfiles --disable-static --enable-shared --host=$(CHOST) --disable-xmltools --disable-largefile + $(MAKE) -C $(DJVUDIR)/build + cp $(DJVULIBDIR)/libdjvulibre.so.21 libs +endif $(CRENGINELIBS): cd $(KPVCRLIBDIR) && rm -rf CMakeCache.txt CMakeFiles && \ @@ -280,9 +288,11 @@ customupdate: all $(STRIP) --strip-unneeded kpdfview extr rm -f kindlepdfviewer-$(VERSION).zip rm -rf $(INSTALL_DIR) - mkdir -p $(INSTALL_DIR)/{history,screenshots} + mkdir -p $(INSTALL_DIR)/{history,screenshots,libs} cp -p README.md COPYING kpdfview extr kpdf.sh $(LUA_FILES) $(INSTALL_DIR) mkdir $(INSTALL_DIR)/data + cp libs/* $(INSTALL_DIR)/libs + $(STRIP) --strip-unneeded $(INSTALL_DIR)/libs/* cp -rpL data/*.css $(INSTALL_DIR)/data cp -rpL fonts $(INSTALL_DIR) cp -r git-rev resources $(INSTALL_DIR) diff --git a/kpdf.sh b/kpdf.sh index 3f5cffc43..69be73d9f 100755 --- a/kpdf.sh +++ b/kpdf.sh @@ -21,6 +21,7 @@ fi # stop cvm killall -stop cvm +export LD_LIBRARY_PATH=`pwd`/libs # finally call reader ./reader.lua "$1" 2> /mnt/us/kindlepdfviewer/crash.log || cat /mnt/us/kindlepdfviewer/crash.log From 6f6cb3c1a9c3d786468bd462fdb0d236c4a406d5 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 31 Oct 2012 22:53:50 -0400 Subject: [PATCH 68/70] build: create libs-emu dir if not found --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 5c0a64fb1..fd0412e77 100644 --- a/Makefile +++ b/Makefile @@ -252,6 +252,7 @@ $(DJVULIBS): ifdef EMULATE_READER cd $(DJVUDIR)/build && CC="$(HOSTCC)" CXX="$(HOSTCXX)" CFLAGS="$(HOSTCFLAGS)" CXXFLAGS="$(HOSTCFLAGS)" LDFLAGS="$(LDFLAGS)" ../configure --disable-desktopfiles --disable-static --enable-shared --disable-xmltools --disable-largefile $(MAKE) -C $(DJVUDIR)/build + test -d libs-emu || mkdir libs-emu cp -a $(DJVULIBDIR)/libdjvulibre.so* libs-emu else cd $(DJVUDIR)/build && CC="$(CC)" CXX="$(CXX)" CFLAGS="$(CFLAGS)" CXXFLAGS="$(CXXFLAGS)" LDFLAGS="$(LDFLAGS)" ../configure --disable-desktopfiles --disable-static --enable-shared --host=$(CHOST) --disable-xmltools --disable-largefile From 625de9b2600fd2b782b0cac5a51bdd2a3876751c Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 31 Oct 2012 22:54:36 -0400 Subject: [PATCH 69/70] update pic.c from latest code after lots of cherry-pick I found that for some strange reason, pic.c is not updated and I cannot find out which commit I missed. So I simply copied the latest pic.c from the master branch here. --- pic.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/pic.c b/pic.c index b40e47752..aaec55744 100644 --- a/pic.c +++ b/pic.c @@ -20,6 +20,7 @@ #include #include #include + #include "blitbuffer.h" #include "drawcontext.h" #include "pic.h" @@ -86,6 +87,11 @@ static int openDocument(lua_State *L) { } else { free(raw_image); return luaL_error(L, "Unsupported image format"); + } + + doc->width = width; + doc->height = height; + doc->components = components; return 1; } @@ -165,6 +171,26 @@ static int drawPage(lua_State *L) { scaleImage(scaled_image, page->image, img_width, img_height, img_new_width, img_new_height); + uint8_t *bbptr = bb->data; + uint8_t *pmptr = scaled_image; + bbptr += bb->pitch * y_offset; + for(y = y_offset; y < img_new_height; y++) { + for(x = x_offset/2; x < (img_new_width / 2); x++) { + int p = x*2 - x_offset; + unsigned char low = 15 - (pmptr[p + 1] >> 4); + unsigned char high = 15 - (pmptr[p] >> 4); + if (adjust_pixels) + bbptr[x] = adjusted_high[high] | adjusted_low[low]; + else + bbptr[x] = (high << 4) | low; + } + if (img_new_width & 1) + bbptr[x] = 255 - (pmptr[x*2] & 0xF0); + bbptr += bb->pitch; + pmptr += img_new_width; + } + + free(scaled_image); return 0; } From e2abd326065aed9c811f0dbda20f82964cef33aa Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 31 Oct 2012 23:01:36 -0400 Subject: [PATCH 70/70] gitignore cleanup --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 115c9b5c2..0345cc573 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,6 @@ lua-* .reader.kpdfview.lua mupdf-thirdparty.zip djvulibre* -<<<<<<< HEAD -======= crash.log .vimrc git-rev