From f6209e67f37882d72539d868a0799bbfca325c08 Mon Sep 17 00:00:00 2001 From: malc Date: Fri, 12 Aug 2011 09:06:41 +0400 Subject: [PATCH] Stop using glut to draw text a. glutBitmapCharacter is dreadfully inefficient/slow b. The repertoire of symbols is limited c. ... So use Tor Anderrson's excellent glfont.c from [1] to do the drawing (since we are transitively dependent on freetype anyway) [1] https://github.com/ccxvii/snippets --- build.ml | 4 +- build.sh | 4 +- buildall.sh | 5 +- glfont.c | 326 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ link.c | 47 ++++++++- main.ml | 72 +++++--------- tbs | 4 +- 7 files changed, 409 insertions(+), 53 deletions(-) create mode 100644 glfont.c diff --git a/build.ml b/build.ml index 74f420b..d26b709 100644 --- a/build.ml +++ b/build.ml @@ -31,7 +31,9 @@ let boc flags src = o (StrSet.singleton o) [Filename.concat srcdir c] - StrSet.empty + (* Since we are using ocaml instead of gcc to compile this + the dep scanning passs is not done, hence this cludge *) + (StrSet.singleton (Filename.concat srcdir "glfont.c")) ; ;; diff --git a/build.sh b/build.sh index d14137f..8f92f45 100644 --- a/build.sh +++ b/build.sh @@ -6,13 +6,13 @@ mupdflibpath=$mupdf/build/release mupdfincpath=$mupdf/fitz:$mupdf/pdf cclib="-lmupdf -lfitz -lz -ljpeg -lopenjpeg -ljbig2dec -lfreetype" - +ccopt="$(freetype-config --cflags) -O -include ft2build.h" export LIBRARY_PATH=$LIBRARY_PATH:$mupdflibpath export CPATH=$CPATH:$mupdfincpath sh mkhelp.sh $srcpath/keystoml.ml $srcpath/KEYS > help.ml -ocamlc -c -o link.o -ccopt -O $srcpath/link.c +ocamlc -c -o link.o -ccopt "$ccopt" $srcpath/link.c ocamlc -c -o help.cmo help.ml ocamlc -c -o parser.cmo $srcpath/parser.ml ocamlc -c -o main.cmo -I +lablGL $srcpath/main.ml diff --git a/buildall.sh b/buildall.sh index f8f9f09..23d4d34 100644 --- a/buildall.sh +++ b/buildall.sh @@ -58,9 +58,10 @@ srcpath=$(dirname $0) sh mkhelp.sh $srcpath/keystoml.ml $srcpath/KEYS > help.ml +ccopt="$(freetype-config --cflags) -O -include ft2build.h" if test "$1" = "opt"; then cclib="-lmupdf -lfitz -lz -ljpeg -lopenjpeg -ljbig2dec -lfreetype -lpthread" - ocamlopt -c -o link.o -ccopt -O $srcpath/link.c + ocamlopt -c -o link.o -ccopt "$ccopt" $srcpath/link.c ocamlopt -c -o help.cmx help.ml ocamlopt -c -o parser.cmx $srcpath/parser.ml ocamlopt -c -o main.cmx -I $root/lib/ocaml/lablGL $srcpath/main.ml @@ -75,7 +76,7 @@ if test "$1" = "opt"; then main.cmx else cclib="-lmupdf -lfitz -lz -ljpeg -lopenjpeg -ljbig2dec -lfreetype" - ocamlc -c -o link.o -ccopt -O $srcpath/link.c + ocamlc -c -o link.o -ccopt "$ccopt" $srcpath/link.c ocamlc -c -o help.cmo help.ml ocamlc -c -o parser.cmo $srcpath/parser.ml ocamlc -c -o main.cmo -I $root/lib/ocaml/lablGL $srcpath/main.ml diff --git a/glfont.c b/glfont.c new file mode 100644 index 0000000..14e9ab8 --- /dev/null +++ b/glfont.c @@ -0,0 +1,326 @@ +/* This is a slightly modified + https://github.com/ccxvii/snippets/blob/master/glfont.c by Tor Andersson +*/ +/* + * A very simple font cache and rasterizer that uses freetype + * to draw fonts from a single OpenGL texture. The code uses + * a linear-probe hashtable, and writes new glyphs into + * the texture using glTexSubImage2D. When the texture fills + * up, or the hash table gets too crowded, everything is wiped. + * + * This is designed to be used for horizontal text only, + * and draws unhinted text with subpixel accurate metrics + * and kerning. As such, you should always call the drawing + * function with an identity transform that maps units + * to pixels accurately. + * + * If you wish to use it to draw arbitrarily transformed + * text, change the min and mag filters to GL_LINEAR and + * add a pixel of padding between glyphs and rows, and + * make sure to clear the texture when wiping the cache. + */ + +#include FT_ADVANCES_H +typedef int Rune; /* 32 bits */ + +#define PADDING 1 /* set to 0 to save some space but disallow arbitrary transforms */ + +#define MAXGLYPHS 4093 /* prime number for hash table goodness */ +#define CACHESIZE 256 +#define XPRECISION 4 +#define YPRECISION 1 + +struct key +{ + FT_Face face; + short size; + short gid; + short subx; + short suby; +}; + +struct glyph +{ + signed char lsb, top, w, h; + short s, t; + float advance; +}; + +struct table +{ + struct key key; + struct glyph glyph; +}; + +static FT_Library g_freetype_lib = NULL; +static struct table g_table[MAXGLYPHS]; +static int g_table_load = 0; +static unsigned int g_cache_tex = 0; +static int g_cache_w = CACHESIZE; +static int g_cache_h = CACHESIZE; +static int g_cache_row_y = 0; +static int g_cache_row_x = 0; +static int g_cache_row_h = 0; + +static void init_font_cache(void) +{ + int code; + + code = FT_Init_FreeType(&g_freetype_lib); + if (code) + errx(1, "cannot initialize freetype"); + + glGenTextures(1, &g_cache_tex); + glBindTexture(GL_TEXTURE_2D, g_cache_tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, g_cache_w, g_cache_h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL); +} + +static void clear_font_cache(void) +{ +#if PADDING > 0 + unsigned char *zero = malloc(g_cache_w * g_cache_h); + memset(zero, 0, g_cache_w * g_cache_h); + glBindTexture(GL_TEXTURE_2D, g_cache_tex); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, g_cache_w, g_cache_h, GL_ALPHA, GL_UNSIGNED_BYTE, zero); + free(zero); +#endif + + memset(g_table, 0, sizeof(g_table)); + g_table_load = 0; + + g_cache_row_y = PADDING; + g_cache_row_x = PADDING; + g_cache_row_h = 0; +} + +static FT_Face load_font(char *fontname) +{ + FT_Face face; + int code; + + if (g_freetype_lib == NULL) + { + init_font_cache(); + clear_font_cache(); + } + + code = FT_New_Face (g_freetype_lib, fontname, 0, &face); + if (code) { + fprintf (stderr, "failed to load font font `%s'\n", fontname); + return NULL; + } + + FT_Select_Charmap(face, ft_encoding_unicode); + return face; +} + +static void UNUSED free_font(FT_Face face) +{ + clear_font_cache(); + FT_Done_Face(face); +} + +static unsigned int hashfunc(struct key *key) +{ + unsigned char *buf = (unsigned char *)key; + unsigned int len = sizeof(struct key); + unsigned int h = 0; + while (len--) + h = *buf++ + (h << 6) + (h << 16) - h; + return h; +} + +static unsigned int lookup_table(struct key *key) +{ + unsigned int pos = hashfunc(key) % MAXGLYPHS; + while (1) + { + if (!g_table[pos].key.face) /* empty slot */ + return pos; + if (!memcmp(key, &g_table[pos].key, sizeof(struct key))) /* matching slot */ + return pos; + pos = (pos + 1) % MAXGLYPHS; + } +} + +static struct glyph * lookup_glyph(FT_Face face, int size, int gid, int subx, int suby) +{ + FT_Vector subv; + struct key key; + unsigned int pos; + int code; + int w, h; + + /* + * Look it up in the table + */ + + key.face = face; + key.size = size; + key.gid = gid; + key.subx = subx; + key.suby = suby; + + pos = lookup_table(&key); + if (g_table[pos].key.face) + return &g_table[pos].glyph; + + /* + * Render the bitmap + */ + + glEnd(); + + subv.x = subx; + subv.y = suby; + + FT_Set_Transform(face, NULL, &subv); + + code = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING); + if (code < 0) + return NULL; + + code = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_LIGHT); + if (code < 0) + return NULL; + + w = face->glyph->bitmap.width; + h = face->glyph->bitmap.rows; + + /* + * Find an empty slot in the texture + */ + + if (g_table_load == (MAXGLYPHS * 3) / 4) + { + lprintf("font cache table full, clearing cache"); + clear_font_cache(); + pos = lookup_table(&key); + } + + if (h + PADDING > g_cache_h || w + PADDING > g_cache_w) + errx(1, "rendered glyph exceeds cache dimensions"); + + if (g_cache_row_x + w + PADDING > g_cache_w) + { + g_cache_row_y += g_cache_row_h + PADDING; + g_cache_row_x = PADDING; + } + if (g_cache_row_y + h + PADDING > g_cache_h) + { + lprintf("font cache texture full, clearing cache"); + clear_font_cache(); + pos = lookup_table(&key); + } + + /* + * Copy bitmap into texture + */ + + memcpy(&g_table[pos].key, &key, sizeof(struct key)); + g_table[pos].glyph.w = face->glyph->bitmap.width; + g_table[pos].glyph.h = face->glyph->bitmap.rows; + g_table[pos].glyph.lsb = face->glyph->bitmap_left; + g_table[pos].glyph.top = face->glyph->bitmap_top; + g_table[pos].glyph.s = g_cache_row_x; + g_table[pos].glyph.t = g_cache_row_y; + g_table[pos].glyph.advance = face->glyph->advance.x / 64.0; + g_table_load ++; + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ROW_LENGTH, face->glyph->bitmap.pitch); + glTexSubImage2D(GL_TEXTURE_2D, 0, g_cache_row_x, g_cache_row_y, w, h, + GL_ALPHA, GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + glBegin(GL_QUADS); + + g_cache_row_x += w + PADDING; + if (g_cache_row_h < h + PADDING) + g_cache_row_h = h + PADDING; + + return &g_table[pos].glyph; +} + +static float draw_glyph(FT_Face face, int size, int gid, float x, float y) +{ + struct glyph *glyph; + int subx = (x - floor(x)) * XPRECISION; + int suby = (y - floor(y)) * YPRECISION; + subx = (subx * 64) / XPRECISION; + suby = (suby * 64) / YPRECISION; + + glyph = lookup_glyph(face, size, gid, subx, suby); + if (!glyph) + return 0.0; + + float s0 = (float) glyph->s / g_cache_w; + float t0 = (float) glyph->t / g_cache_h; + float s1 = (float) (glyph->s + glyph->w) / g_cache_w; + float t1 = (float) (glyph->t + glyph->h) / g_cache_h; + float xc = floor(x) + glyph->lsb; + float yc = floor(y) - glyph->top + glyph->h; + + glTexCoord2f(s0, t0); glVertex2f(xc, yc - glyph->h); + glTexCoord2f(s1, t0); glVertex2f(xc + glyph->w, yc - glyph->h); + glTexCoord2f(s1, t1); glVertex2f(xc + glyph->w, yc); + glTexCoord2f(s0, t1); glVertex2f(xc, yc); + + return glyph->advance; +} + +float measure_string(FT_Face face, float fsize, char *str) +{ + int size = fsize * 64; + FT_Fixed advance; + FT_Vector kern; + Rune ucs, gid; + float w = 0.0; + int left = 0; + + FT_Set_Char_Size(face, size, size, 72, 72); + + while (*str) + { + str += chartorune(&ucs, str); + gid = FT_Get_Char_Index(face, ucs); + FT_Get_Advance(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING, &advance); + w += advance / 65536.0; + FT_Get_Kerning(face, left, gid, FT_KERNING_UNFITTED, &kern); + w += kern.x / 64.0; + left = gid; + } + + return w; +} + +float draw_string(FT_Face face, float fsize, float x, float y, char *str) +{ + int size = fsize * 64; + FT_Vector kern; + Rune ucs, gid; + int left = 0; + + FT_Set_Char_Size(face, size, size, 72, 72); + + glBindTexture(GL_TEXTURE_2D, g_cache_tex); + glBegin(GL_QUADS); + + while (*str) + { + str += chartorune(&ucs, str); + gid = FT_Get_Char_Index(face, ucs); + x += draw_glyph(face, size, gid, x, y); + FT_Get_Kerning(face, left, gid, FT_KERNING_UNFITTED, &kern); + x += kern.x / 64.0; + left = gid; + } + + glEnd(); + + return x; +} diff --git a/link.c b/link.c index 239117f..415a688 100644 --- a/link.c +++ b/link.c @@ -25,10 +25,13 @@ #ifdef _MSC_VER #define NORETURN __declspec (noreturn) +#define UNUSED #elif defined __GNUC__ #define NORETURN __attribute__ ((noreturn)) +#define UNUSED __attribute__ ((unused)) #else #define NORETURN +#define UNUSED #endif #ifdef _WIN32 @@ -190,6 +193,8 @@ struct { pthread_t thread; #endif FILE *xselpipe; + + FT_Face face; } state; #ifdef _WIN32 @@ -1367,6 +1372,29 @@ static void upload2 (struct page *page, int slicenum, const char *cap) } } +#include "glfont.c" + +CAMLprim value ml_draw_string (value pt_v, value x_v, value y_v, value string_v) +{ + CAMLparam4 (pt_v, x_v, y_v, string_v); + int pt = Int_val(pt_v); + int x = Int_val (x_v); + int y = Int_val (y_v); + + if (!state.face) { + errx (1, "draw string with no face"); + } + + glEnable (GL_TEXTURE_2D); + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + draw_string (state.face, pt, x, y, String_val (string_v)); + glDisable (GL_BLEND); + glDisable (GL_TEXTURE_2D); + + CAMLreturn (Val_unit); +} + CAMLprim value ml_draw (value args_v, value ptr_v) { CAMLparam2 (args_v, ptr_v); @@ -1899,18 +1927,35 @@ CAMLprim value ml_zoom_for_height (value winw_v, value winh_v, value dw_v) CAMLprim value ml_init (value sock_v, value params_v) { + CAMLparam2 (sock_v, params_v); #ifndef _WIN32 int ret; #endif - CAMLparam2 (sock_v, params_v); + char *fontpath; state.rotate = Int_val (Field (params_v, 0)); state.proportional = Bool_val (Field (params_v, 1)); state.texcount = Int_val (Field (params_v, 2)); state.sliceheight = Int_val (Field (params_v, 3)); + fontpath = String_val (Field (params_v, 4)); state.texform = GL_RGBA; state.texty = GL_UNSIGNED_BYTE; + if (*fontpath) { + state.face = load_font (fontpath); + } + else { + int code; + unsigned int len; + void *base = pdf_find_builtin_font ("Courier-Bold", &len); + + if (!len || !base) errx (1, "failed to find builtin font\n"); + + code = FT_New_Memory_Face (g_freetype_lib, base, len, 0, &state.face); + if (code) errx (1, "failed to load font bultin font\n"); + } + if (!state.face) _exit (1); + fz_accelerate (); state.texids = calloc (state.texcount * sizeof (*state.texids), 1); if (!state.texids) { diff --git a/main.ml b/main.ml index 91edc83..b71b4ad 100644 --- a/main.ml +++ b/main.ml @@ -7,7 +7,7 @@ and facename = string;; let dolog fmt = Printf.kprintf prerr_endline fmt;; -type params = angle * proportional * texcount * sliceheight +type params = angle * proportional * texcount * sliceheight * fontpath and pageno = int and width = int and height = int @@ -22,6 +22,7 @@ and texcount = int and sliceheight = int and gen = int and top = float +and fontpath = string ;; external init : Unix.file_descr -> params -> unit = "ml_init";; @@ -32,6 +33,7 @@ external copysel : string -> unit = "ml_copysel";; external getpdimrect : int -> float array = "ml_getpdimrect";; external whatsunder : string -> int -> int -> under = "ml_whatsunder";; external zoomforh : int -> int -> int -> float = "ml_zoom_for_height";; +external drawstring : int -> int -> int -> string -> unit = "ml_draw_string";; type mpos = int * int and mstate = @@ -158,6 +160,7 @@ type conf = ; mutable bgcolor : float * float * float ; mutable bedefault : bool ; mutable scrollbarinpm : bool + ; mutable uifont : string } ;; @@ -267,6 +270,7 @@ let defconf = ; bgcolor = (0.5, 0.5, 0.5) ; bedefault = false ; scrollbarinpm = true + ; uifont = "" } ;; @@ -764,11 +768,8 @@ let showtext s = (0.0, float (conf.winh - 18)) (float (conf.winw - state.scrollw - 1), float conf.winh) ; - let font = Glut.BITMAP_8_BY_13 in GlDraw.color (1.0, 1.0, 1.0); - GlPix.raster_pos ~x:0.0 ~y:(float (conf.winh - 5)) (); - Glut.bitmapCharacter ~font ~c:(Char.code ' '); - String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) s; + drawstring 14 8 (conf.winh - 5) s; ;; let enttext () = @@ -780,16 +781,16 @@ let enttext () = | 0 | 1 -> if len > 0 then - Printf.sprintf "%s%s^ [%s]" prefix text state.text + Printf.sprintf "%s%s\226–� [%s]" prefix text state.text else - Printf.sprintf "%s%s^" prefix text + Printf.sprintf "%s%s\226–�" prefix text | _ -> if len > 0 then - Printf.sprintf "%s: %s^ [%s]" prefix text state.text + Printf.sprintf "%s: %s\226–� [%s]" prefix text state.text else - Printf.sprintf "%s: %s^" prefix text + Printf.sprintf "%s: %s\226–�" prefix text in showtext s; @@ -934,27 +935,6 @@ let act cmd = Scanf.sscanf cmd "o %u %u %d %u %n" (fun l n t h pos -> l, n, t, h, pos) in let s = String.sub cmd pos (String.length cmd - pos) in - let s = - let l = String.length s in - let b = Buffer.create (String.length s) in - let rec loop pc2 i = - if i = l - then () - else - let pc2 = - match s.[i] with - | '\xa0' when pc2 -> Buffer.add_char b ' '; false - | '\xc2' -> true - | c -> - let c = if Char.code c land 0x80 = 0 then c else '?' in - Buffer.add_char b c; - false - in - loop pc2 (i+1) - in - loop false 0; - Buffer.contents b - in let outline = (s, l, (n, float t /. float h)) in let outlines = match state.outlines with @@ -2694,13 +2674,10 @@ let drawplaceholder l = (float l.pagex, float l.pagedispy) (float (l.pagew + l.pagex), float (l.pagedispy + l.pagevh)) ; - let x = float (if margin < 0 then -margin else l.pagex) - and y = float (l.pagedispy + 13) in - let font = Glut.BITMAP_8_BY_13 in + let x = if margin < 0 then -margin else l.pagex + and y = l.pagedispy + 13 in GlDraw.color (0.0, 0.0, 0.0); - GlPix.raster_pos ~x ~y (); - String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) - ("Loading " ^ string_of_int (l.pageno + 1)); + drawstring 13 x y ("Loading " ^ string_of_int (l.pageno + 1)) ;; let now () = Unix.gettimeofday ();; @@ -2830,11 +2807,6 @@ let showstrings active first pan strings = Gl.disable `blend; GlDraw.color (1., 1., 1.); - let font = Glut.BITMAP_9_BY_15 in - let draw_string x y s = - GlPix.raster_pos ~x ~y (); - String.iter (fun c -> Glut.bitmapCharacter ~font ~c:(Char.code c)) s - in let rec loop row = if row = Array.length strings || (row - first) * 16 > conf.winh then () @@ -2868,10 +2840,10 @@ let showstrings active first pan strings = then s else String.sub s (-pos) left in - draw_string (float x) (float (y + 16)) s + drawstring 14 x (y + 16) s ) else - draw_string (float (x + pan*15)) (float (y + 16)) s + drawstring 14 (x + pan*15) (y + 16) s in draw_string s; loop (row+1) @@ -3225,6 +3197,7 @@ struct | "background-color" -> { c with bgcolor = color_of_string v } | "scrollbar-in-presentation" -> { c with scrollbarinpm = bool_of_string v } + | "ui-font" -> { c with uifont = v } | _ -> c with exn -> prerr_endline ("Error processing attribute (`" ^ @@ -3301,6 +3274,7 @@ struct dst.jumpback <- src.jumpback; dst.bgcolor <- src.bgcolor; dst.scrollbarinpm <- src.scrollbarinpm; + dst.uifont <- src.uifont; ;; let unent s = @@ -3344,7 +3318,7 @@ struct let anchor = (pageno, rely) in if closed then (Hashtbl.add h path (c, [], pan, anchor); v) - else { v with f = doc path pan anchor c [] } + else { v with f = doc path pan anchor c [] } | Vopen _ -> error "unexpected subelement in llppconfig" s spos @@ -3500,6 +3474,10 @@ struct if always || a <> b then Printf.bprintf bb "\n %s='%s'" s (color_to_string a) + and os s a b = + if always || a <> b + then + Printf.bprintf bb "\n %s='%s'" s a in let w, h = if always @@ -3545,6 +3523,7 @@ struct ob "persistent-location" c.jumpback dc.jumpback; oc "background-color" c.bgcolor dc.bgcolor; ob "scrollbar-in-presentation" c.scrollbarinpm dc.scrollbarinpm; + os "ui-font" c.uifont dc.uifont; ;; let save () = @@ -3686,7 +3665,10 @@ let () = let () = Glut.motionFunc motion in let () = Glut.passiveMotionFunc pmotion in - init ssock (conf.angle, conf.proportional, conf.texcount, conf.sliceheight); + init ssock ( + conf.angle, conf.proportional, conf.texcount, + conf.sliceheight, conf.uifont + ); state.csock <- csock; state.ssock <- ssock; state.text <- "Opening " ^ state.path; diff --git a/tbs b/tbs index 7dfde67..4cbfea1 100644 --- a/tbs +++ b/tbs @@ -26,10 +26,10 @@ mupdf=/home/malc/x/rcs/svn/sumatrapdf-read-only/mupdf mupdf=/home/malc/x/rcs/git/mupdf cc=gcc-4.6.0 ccopt="-std=c89 -Wall -Werror -I$mupdf/fitz -I$mupdf/pdf" -ccopt="$ccopt $(freetype-config --cflags) -include ft2build.h -O" +ccopt="$ccopt $(freetype-config --cflags) -include ft2build.h" targets="llpp" libs="-lmupdf -lfitz -lopenjpeg -ljbig2dec -ljpeg -lz -lfreetype -lfontconfig" -ccopt="$ccopt -maltivec" +ccopt="$ccopt -maltivec -O" lpath=$mupdf/build/linux-ppc-release/ lpath=$mupdf/build/release -- 2.11.4.GIT