/* Copyright (C) 1989, 1995, 1996, 1997, 1998 artofcode LLC. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA, 02111-1307. */ /*$Id: gxccache.c,v 1.5.2.1.2.1 2003/01/17 00:49:03 giles Exp $ */ /* Fast case character cache routines for Ghostscript library */ #include "gx.h" #include "gpcheck.h" #include "gserrors.h" #include "gsstruct.h" #include "gxfixed.h" #include "gxmatrix.h" #include "gzstate.h" #include "gzpath.h" #include "gxdevice.h" #include "gxdevmem.h" #include "gzcpath.h" #include "gxchar.h" #include "gxfont.h" #include "gxfcache.h" #include "gxxfont.h" #include "gscspace.h" /* for gsimage.h */ #include "gsimage.h" #include "gxhttile.h" /* Forward references */ private byte *compress_alpha_bits(P2(const cached_char *, gs_memory_t *)); /* Define a scale factor of 1. */ static const gs_log2_scale_point scale_log2_1 = {0, 0}; /* Look up, and if necessary add, a font/matrix pair in the cache */ cached_fm_pair * gx_lookup_fm_pair(gs_font * pfont, register const gs_state * pgs) { float mxx = pgs->char_tm.xx, mxy = pgs->char_tm.xy, myx = pgs->char_tm.yx, myy = pgs->char_tm.yy; gs_font *font = pfont; register gs_font_dir *dir = font->dir; register cached_fm_pair *pair = dir->fmcache.mdata + dir->fmcache.mnext; int count = dir->fmcache.mmax; gs_uid uid; if (font->FontType == ft_composite || font->PaintType != 0) { /* We can't cache by UID alone. */ uid_set_invalid(&uid); } else { uid = ((gs_font_base *) font)->UID; if (uid_is_valid(&uid)) font = 0; } while (count--) { if (pair == dir->fmcache.mdata) pair += dir->fmcache.mmax; pair--; /* We have either a non-zero font and an invalid UID, */ /* or a zero font and a valid UID. */ /* We have to break up the test */ /* because of a bug in the Zortech compiler. */ if (font != 0) { if (pair->font != font) continue; } else { if (!uid_equal(&pair->UID, &uid) || pair->FontType != pfont->FontType ) continue; } if (pair->mxx == mxx && pair->mxy == mxy && pair->myx == myx && pair->myy == myy ) { if (pair->font == 0) { pair->font = pfont; if_debug2('k', "[k]updating pair 0x%lx with font 0x%lx\n", (ulong) pair, (ulong) pfont); } else { if_debug2('k', "[k]found pair 0x%lx: font=0x%lx\n", (ulong) pair, (ulong) pair->font); } return pair; } } return gx_add_fm_pair(dir, pfont, &uid, pgs); } /* Look up a glyph in the cache. */ /* The character depth must be either 1 or alt_depth. */ /* Return the cached_char or 0. */ cached_char * gx_lookup_cached_char(const gs_font * pfont, const cached_fm_pair * pair, gs_glyph glyph, int wmode, int alt_depth) { gs_font_dir *dir = pfont->dir; uint chi = chars_head_index(glyph, pair); register cached_char *cc; while ((cc = dir->ccache.table[chi & dir->ccache.table_mask]) != 0) { if (cc->code == glyph && cc_pair(cc) == pair && cc->wmode == wmode && (cc_depth(cc) == 1 || cc_depth(cc) == alt_depth) ) { if_debug4('K', "[K]found 0x%lx (depth=%d) for glyph=0x%lx, wmode=%d\n", (ulong) cc, cc_depth(cc), (ulong) glyph, wmode); return cc; } chi++; } if_debug3('K', "[K]not found: glyph=0x%lx, wmode=%d, alt_depth=%d\n", (ulong) glyph, wmode, alt_depth); return 0; } /* Look up a character in an external font. */ /* Return the cached_char or 0. */ cached_char * gx_lookup_xfont_char(const gs_state * pgs, cached_fm_pair * pair, gs_char chr, gs_glyph glyph, const gx_xfont_callbacks * callbacks, int wmode) { gs_font *font = pair->font; int enc_index; gx_xfont *xf; gx_xglyph xg; gs_log2_scale_point log2_scale; gs_point wxy; gs_int_rect bbox; cached_char *cc; if (font == 0) return NULL; enc_index = (font->FontType == ft_composite ? -1 : ((gs_font_base *) font)->nearest_encoding_index); if (!pair->xfont_tried) { /* Look for an xfont now. */ gx_lookup_xfont(pgs, pair, enc_index); pair->xfont_tried = true; } xf = pair->xfont; if (xf == 0) return NULL; { const gx_xfont_procs *procs = xf->common.procs; if (procs->char_xglyph2 == 0) { /* The xfont can't recognize reencoded fonts. */ /* Use the registered encoding only if this glyph */ /* is the same as the one in the registered encoding. */ if (enc_index >= 0 && (*callbacks->known_encode) (chr, enc_index) != glyph ) enc_index = -1; xg = (*procs->char_xglyph) (xf, chr, enc_index, glyph, callbacks->glyph_name); } else { /* The xfont can recognize reencoded fonts. */ xg = (*procs->char_xglyph2) (xf, chr, enc_index, glyph, callbacks); } if (xg == gx_no_xglyph) return NULL; if ((*procs->char_metrics) (xf, xg, wmode, &wxy, &bbox) < 0) return NULL; } log2_scale.x = log2_scale.y = 1; cc = gx_alloc_char_bits(font->dir, NULL, NULL, bbox.q.x - bbox.p.x, bbox.q.y - bbox.p.y, &log2_scale, 1); if (cc == 0) return NULL; /* Success. Make the cache entry. */ cc->code = glyph; cc->wmode = wmode; cc->xglyph = xg; cc->wxy.x = float2fixed(wxy.x); cc->wxy.y = float2fixed(wxy.y); cc->offset.x = int2fixed(-bbox.p.x); cc->offset.y = int2fixed(-bbox.p.y); if_debug5('k', "[k]xfont %s char %d/0x%x#0x%lx=>0x%lx\n", font->font_name.chars, enc_index, (int)chr, (ulong) glyph, (ulong) xg); if_debug6('k', " wxy=(%g,%g) bbox=(%d,%d),(%d,%d)\n", wxy.x, wxy.y, bbox.p.x, bbox.p.y, bbox.q.x, bbox.q.y); gx_add_cached_char(font->dir, NULL, cc, pair, &scale_log2_1); return cc; } /* Copy a cached character to the screen. */ /* Assume the caller has already done gx_color_load. */ /* Return 0 if OK, 1 if we couldn't do the operation but no error */ /* should be signalled, or a negative error code. */ int gx_image_cached_char(register gs_show_enum * penum, register cached_char * cc) { register gs_state *pgs = penum->pgs; gx_device_color *pdevc = pgs->dev_color; int x, y, w, h, depth; int code; gs_fixed_point pt; gx_device *dev = penum->dev; gx_device *imaging_dev = penum->imaging_dev ? penum->imaging_dev : dev; gx_device *orig_dev = imaging_dev; gx_device_clip cdev; gx_xglyph xg = cc->xglyph; gx_xfont *xf; byte *bits; top:code = gx_path_current_point_inline(pgs->path, &pt); if (code < 0) return code; /* * If the character doesn't lie entirely within the inner * clipping rectangle, we set up an intermediate clipping device. * Note that if the original device implements fill_mask, we may * never actually use the clipping device. */ pt.x -= cc->offset.x; x = fixed2int_var_rounded(pt.x) + penum->ftx; pt.y -= cc->offset.y; y = fixed2int_var_rounded(pt.y) + penum->fty; w = cc->width; h = cc->height; #ifdef DEBUG if (gs_debug_c('K')) { if (cc_has_bits(cc)) debug_dump_bitmap(cc_bits(cc), cc_raster(cc), h, "[K]bits"); else dputs("[K]no bits\n"); dlprintf3("[K]copying 0x%lx, offset=(%g,%g)\n", (ulong) cc, fixed2float(-cc->offset.x), fixed2float(-cc->offset.y)); dlprintf6(" at (%g,%g)+(%d,%d)->(%d,%d)\n", fixed2float(pt.x), fixed2float(pt.y), penum->ftx, penum->fty, x, y); } #endif if ((x < penum->ibox.p.x || x + w > penum->ibox.q.x || y < penum->ibox.p.y || y + h > penum->ibox.q.y) && imaging_dev != (gx_device *) & cdev /* might be 2nd time around */ ) { /* Check for the character falling entirely outside */ /* the clipping region. */ gx_clip_path *pcpath; if (x >= penum->obox.q.x || x + w <= penum->obox.p.x || y >= penum->obox.q.y || y + h <= penum->obox.p.y ) return 0; /* nothing to do */ code = gx_effective_clip_path(pgs, &pcpath); if (code < 0) return code; gx_make_clip_device(&cdev, gx_cpath_list(pcpath)); cdev.target = imaging_dev; imaging_dev = (gx_device *) & cdev; (*dev_proc(imaging_dev, open_device)) (imaging_dev); if_debug0('K', "[K](clipping)\n"); } /* If an xfont can render this character, use it. */ if (xg != gx_no_xglyph && (xf = cc_pair(cc)->xfont) != 0) { int cx = x + fixed2int(cc->offset.x); int cy = y + fixed2int(cc->offset.y); /* * Note that we prefer a 1-bit xfont implementation over * a multi-bit cached bitmap. Eventually we should change * the xfont interface so it can deliver multi-bit bitmaps, * or else implement oversampling for xfonts. */ if (gs_color_writes_pure(pgs)) { code = (*xf->common.procs->render_char) (xf, xg, imaging_dev, cx, cy, pdevc->colors.pure, 0); if_debug8('K', "[K]render_char display: xfont=0x%lx, glyph=0x%lx\n\tdev=0x%lx(%s) x,y=%d,%d, color=0x%lx => %d\n", (ulong) xf, (ulong) xg, (ulong) imaging_dev, imaging_dev->dname, cx, cy, (ulong) pdevc->colors.pure, code); if (code == 0) return_check_interrupt(0); } /* Can't render directly. If we don't have a bitmap yet, */ /* get it from the xfont now. */ if (!cc_has_bits(cc)) { gx_device_memory mdev; gs_make_mem_mono_device(&mdev, 0, imaging_dev); gx_open_cache_device(&mdev, cc); code = (*xf->common.procs->render_char) (xf, xg, (gx_device *) & mdev, cx - x, cy - y, (gx_color_index) 1, 1); if_debug7('K', "[K]render_char to bits: xfont=0x%lx, glyph=0x%lx\n\tdev=0x%lx(%s) x,y=%d,%d => %d\n", (ulong) xf, (ulong) xg, (ulong) & mdev, mdev.dname, cx - x, cy - y, code); if (code != 0) return_check_interrupt(1); gx_add_char_bits(cc_pair(cc)->font->dir, cc, &scale_log2_1); /* gx_add_char_bits may change width, height, */ /* raster, and/or offset. It's easiest to */ /* start over from the top. Clear xg so that */ /* we don't waste time trying render_char again. */ xg = gx_no_xglyph; goto top; } } /* * No xfont. Render from the cached bits. If the cached bits * have more than 1 bit of alpha, and the color isn't pure or * the copy_alpha operation fails, construct a single-bit mask * by taking the high-order alpha bit. */ bits = cc_bits(cc); depth = cc_depth(cc); if (dev_proc(orig_dev, fill_mask) != gx_default_fill_mask || !lop_no_S_is_T(pgs->log_op) ) { gx_clip_path *pcpath; code = gx_effective_clip_path(pgs, &pcpath); if (code >= 0) { code = (*dev_proc(orig_dev, fill_mask)) (orig_dev, bits, 0, cc_raster(cc), cc->id, x, y, w, h, pdevc, depth, pgs->log_op, pcpath); if (code >= 0) goto done; } } else if (gs_color_writes_pure(pgs)) { gx_color_index color = pdevc->colors.pure; if (depth > 1) { code = (*dev_proc(imaging_dev, copy_alpha)) (imaging_dev, bits, 0, cc_raster(cc), cc->id, x, y, w, h, color, depth); if (code >= 0) return_check_interrupt(0); /* copy_alpha failed, construct a monobit mask. */ bits = compress_alpha_bits(cc, &gs_memory_default); if (bits == 0) return 1; /* VMerror, but recoverable */ } code = (*dev_proc(imaging_dev, copy_mono)) (imaging_dev, bits, 0, cc_raster(cc), cc->id, x, y, w, h, gx_no_color_index, pdevc->colors.pure); goto done; } if (depth > 1) { /* Complex color or fill_mask / copy_alpha failed, */ /* construct a monobit mask. */ bits = compress_alpha_bits(cc, &gs_memory_default); if (bits == 0) return 1; /* VMerror, but recoverable */ } { /* Use imagemask to render the character. */ gs_memory_t *mem = &gs_memory_default; gs_image_enum *pie = gs_image_enum_alloc(mem, "image_char(image_enum)"); gs_image_t image; int iy; uint used; if (pie == 0) { if (bits != cc_bits(cc)) gs_free_object(&gs_memory_default, bits, "compress_alpha_bits"); return 1; /* VMerror, but recoverable */ } /* Make a matrix that will place the image */ /* at (x,y) with no transformation. */ gs_image_t_init_mask(&image, true); #define mat image.ImageMatrix gs_make_translation((floatp) - x, (floatp) - y, &mat); gs_matrix_multiply(&ctm_only(pgs), &mat, &mat); #undef mat image.Width = w; image.Height = h; image.adjust = false; code = gs_image_init(pie, &image, false, pgs); switch (code) { case 1: /* empty image */ code = 0; default: break; case 0: for (iy = 0; iy < h && code >= 0; iy++) code = gs_image_next(pie, bits + iy * cc_raster(cc), (w + 7) >> 3, &used); gs_image_cleanup(pie); } gs_free_object(mem, pie, "image_char(image_enum)"); } done:if (bits != cc_bits(cc)) gs_free_object(&gs_memory_default, bits, "compress_alpha_bits"); if (code > 0) code = 0; return_check_interrupt(code); } /* ------ Image manipulation ------ */ /* * Compress a mask with 2 or 4 bits of alpha to a monobit mask. * Allocate and return the address of the monobit mask. */ private byte * compress_alpha_bits(const cached_char * cc, gs_memory_t * mem) { const byte *data = cc_const_bits(cc); uint width = cc->width; uint height = cc->height; int log2_scale = cc_depth(cc); int scale = 1 << log2_scale; uint sraster = cc_raster(cc); uint sskip = sraster - ((width * scale + 7) >> 3); uint draster = bitmap_raster(width); uint dskip = draster - ((width + 7) >> 3); byte *mask = gs_alloc_bytes(mem, draster * height, "compress_alpha_bits"); const byte *sptr = data; byte *dptr = mask; uint h; if (mask == 0) return 0; for (h = height; h; --h) { byte sbit = 0x80; byte d = 0; byte dbit = 0x80; uint w; for (w = width; w; --w) { if (*sptr & sbit) d += dbit; if (!(sbit >>= log2_scale)) sbit = 0x80, sptr++; if (!(dbit >>= 1)) dbit = 0x80, dptr++, d = 0; } if (dbit != 0x80) *dptr++ = d; for (w = dskip; w != 0; --w) *dptr++ = 0; sptr += sskip; } return mask; }