mirror of
https://github.com/koreader/koreader
synced 2024-11-10 01:10:34 +00:00
0b2f89d830
Conflicts: koptconfig.lua koptreader.lua
7016 lines
196 KiB
C
7016 lines
196 KiB
C
/*
|
|
** 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 <http://www.gnu.org/licenses/>.
|
|
**
|
|
/*
|
|
** 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#include "k2pdfopt.h"
|
|
|
|
#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
|
|
#define BOUND(x,xmin,xmax) if ((x)<(xmin)) (x)=(xmin); else { if ((x)>(xmax)) (x)=(xmax); }
|
|
|
|
#ifdef PI
|
|
#undef PI
|
|
#endif
|
|
/*
|
|
** Constants from the front of the CRC standard math tables
|
|
** (Accuracy = 50 digits)
|
|
*/
|
|
#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
|
|
#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 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
|
|
// 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 int src_autostraighten = 0;
|
|
static double dst_mar = 0.06;
|
|
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 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);
|
|
|
|
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 double zoom_value = 1.0;
|
|
static double gamma_correction = 1.0;
|
|
|
|
static void k2pdfopt_reflow_bmp(MASTERINFO *masterinfo, WILLUSBITMAP *src) {
|
|
PAGEINFO _pageinfo, *pageinfo;
|
|
WILLUSBITMAP _srcgrey, *srcgrey;
|
|
int i, white, dpi;
|
|
double area_ratio;
|
|
|
|
masterinfo->debugfolder[0] = '\0';
|
|
white = src_whitethresh; /* Will be set by adjust_contrast() or set to src_whitethresh */
|
|
dpi = src_dpi;
|
|
adjust_params_init();
|
|
set_region_widths();
|
|
|
|
srcgrey = &_srcgrey;
|
|
if (master_bmp_inited == 0) {
|
|
bmp_init(&masterinfo->bmp);
|
|
master_bmp_inited = 1;
|
|
}
|
|
|
|
bmp_free(&masterinfo->bmp);
|
|
bmp_init(&masterinfo->bmp);
|
|
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);
|
|
|
|
if (src_rot != 0) {
|
|
bmp_rotate_right_angle(src, src_rot);
|
|
}
|
|
|
|
BMPREGION region;
|
|
bmp_copy(srcgrey, 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;
|
|
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, (int) (0.25 * src_dpi + .5));
|
|
|
|
master_bmp_width = masterinfo->bmp.width;
|
|
master_bmp_height = masterinfo->rows;
|
|
|
|
bmp_free(srcgrey);
|
|
}
|
|
|
|
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 rotation) {
|
|
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;
|
|
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;
|
|
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) {
|
|
fz_device *dev;
|
|
fz_pixmap *pix;
|
|
fz_rect bounds,bounds2;
|
|
fz_matrix ctm;
|
|
fz_bbox bbox;
|
|
WILLUSBITMAP _src, *src;
|
|
|
|
double dpp,zoom;
|
|
zoom = zoom_value;
|
|
double dpi = 250*zoom;
|
|
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 zoom:%.2f dpi:%.0f\n",bbox.x0,bbox.y0,bbox.x1,bbox.y1,zoom,dpi);
|
|
zoom_value = zoom;
|
|
zoom *= shrink_factor;
|
|
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_correction >= 0.0) {
|
|
fz_gamma_pixmap(ctx, pix, gamma_correction);
|
|
}
|
|
|
|
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, \
|
|
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))
|
|
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;
|
|
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);
|
|
zoom_value = zoom;
|
|
zoom *= shrink_factor;
|
|
dpi *= shrink_factor;
|
|
} while (prect.w > max_page_width_pix | prect.h > max_page_height_pix);
|
|
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_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
|
|
|
|
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;i<src->width;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;i<breakinfo->n;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;i<breakinfo->n;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;i<breakinfo->n;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 (rmid<rmin || rmid>rmax)
|
|
{
|
|
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;i<bmp->height;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;i<breakinfo->n;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;i<nc;i++)
|
|
fprintf(f,"%d\n",bp[i]);
|
|
fprintf(f,"//nc\n");
|
|
fclose(f);
|
|
}
|
|
#endif
|
|
thlow = 10;
|
|
thhigh = 50;
|
|
/*
|
|
** Break into pieces
|
|
*/
|
|
for (col0 = newregion->c1; 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;i<colbreaks->n;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;i<colbreaks->n;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;i<wrapbmp->height;i++)
|
|
{
|
|
unsigned char *p;
|
|
int j;
|
|
p=bmp_rowptr_from_top(wrapbmp,i);
|
|
for (j=0;j<wrapbmp->width;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;i<n-ww;i++)
|
|
fprintf(f,"%g\n",xs[i]);
|
|
fprintf(f,"//nc\n");
|
|
fclose(f);
|
|
}
|
|
#endif /* DEBUG */
|
|
willus_dmem_free(23, &xs, funcname);
|
|
return (f1 * f2 * ni);
|
|
}
|
|
|
|
static void pdfboxes_init(PDFBOXES *boxes)
|
|
|
|
{
|
|
boxes->n = 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 && n<boxes->n)
|
|
{
|
|
int i;
|
|
for (i=0;i<boxes->n-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 <r>.
|
|
** If 24-bit, it gets <r>, <g>, <b> 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;i<n;i++)
|
|
{
|
|
new=x1+(x2-x1)*(i+1)/n;
|
|
dst[i] = resample_single(src,last,new);
|
|
last=new;
|
|
}
|
|
}
|
|
|
|
static void bmp_resample_1(double *tempbmp,WILLUSBITMAP *src,double x1,double y1,
|
|
double x2,double y2,int newwidth,int newheight,
|
|
double *temprow,int color)
|
|
|
|
{
|
|
int row,col,x0,dx,y0,dy;
|
|
|
|
x0=floor(x1);
|
|
dx=ceil(x2)-x0;
|
|
x1-=x0;
|
|
x2-=x0;
|
|
y0=floor(y1);
|
|
dy=ceil(y2)-y0;
|
|
y1-=y0;
|
|
y2-=y0;
|
|
if (src->type==WILLUSBITMAP_TYPE_WIN32 && color>=0)
|
|
color=2-color;
|
|
for (row=0;row<dy;row++)
|
|
{
|
|
unsigned char *p;
|
|
p=bmp_rowptr_from_top(src,row+y0);
|
|
if (src->bpp==8)
|
|
{
|
|
switch (color)
|
|
{
|
|
case -1:
|
|
for (col=0,p+=x0;col<dx;col++,p++)
|
|
temprow[col]=p[0];
|
|
break;
|
|
case 0:
|
|
for (col=0,p+=x0;col<dx;col++,p++)
|
|
temprow[col]=src->red[p[0]];
|
|
break;
|
|
case 1:
|
|
for (col=0,p+=x0;col<dx;col++,p++)
|
|
temprow[col]=src->green[p[0]];
|
|
break;
|
|
case 2:
|
|
for (col=0,p+=x0;col<dx;col++,p++)
|
|
temprow[col]=src->blue[p[0]];
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
p+=color;
|
|
for (col=0,p+=3*x0;col<dx;temprow[col]=p[0],col++,p+=3);
|
|
}
|
|
resample_1d(&tempbmp[row*newwidth],temprow,x1,x2,newwidth);
|
|
}
|
|
for (col=0;col<newwidth;col++)
|
|
{
|
|
double *p,*s;
|
|
p=&tempbmp[col];
|
|
s=&temprow[dy];
|
|
for (row=0;row<dy;row++,p+=newwidth)
|
|
temprow[row]=p[0];
|
|
resample_1d(s,temprow,y1,y2,newheight);
|
|
p=&tempbmp[col];
|
|
for (row=0;row<newheight;row++,p+=newwidth,s++)
|
|
p[0]=s[0];
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Resample (re-size) bitmap. The pixel positions left to right go from
|
|
** 0.0 to src->width (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;ir<src->height;ir++)
|
|
{
|
|
unsigned char *sp,*dp;
|
|
sp=bmp_rowptr_from_top(src,ir);
|
|
dp=bmp_rowptr_from_top(dest,ir);
|
|
for (i=0;i<src->width;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;ir<src->height;ir++)
|
|
{
|
|
unsigned char *sp,*dp;
|
|
sp=bmp_rowptr_from_top(src,ir);
|
|
dp=bmp_rowptr_from_top(dest,ir);
|
|
for (ic=0;ic<src->width;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. */
|
|
}
|
|
|
|
/*
|
|
** 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);
|
|
}
|
|
|
|
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)
|
|
|
|
{
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|