/src/ghostpdl/base/gxccache.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright (C) 2001-2021 Artifex Software, Inc. |
2 | | All Rights Reserved. |
3 | | |
4 | | This software is provided AS-IS with no warranty, either express or |
5 | | implied. |
6 | | |
7 | | This software is distributed under license and may not be copied, |
8 | | modified or distributed except as expressly authorized under the terms |
9 | | of the license contained in the file LICENSE in this distribution. |
10 | | |
11 | | Refer to licensing information at http://www.artifex.com or contact |
12 | | Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato, |
13 | | CA 94945, U.S.A., +1(415)492-9861, for further information. |
14 | | */ |
15 | | |
16 | | |
17 | | /* Fast case character cache routines for Ghostscript library */ |
18 | | #include "memory_.h" |
19 | | #include "gx.h" |
20 | | #include "gpcheck.h" |
21 | | #include "gserrors.h" |
22 | | #include "gsstruct.h" |
23 | | #include "gscencs.h" |
24 | | #include "gxfixed.h" |
25 | | #include "gxmatrix.h" |
26 | | #include "gzstate.h" |
27 | | #include "gzpath.h" |
28 | | #include "gxdevice.h" |
29 | | #include "gxdevmem.h" |
30 | | #include "gzcpath.h" |
31 | | #include "gxchar.h" |
32 | | #include "gxfont.h" |
33 | | #include "gxfcache.h" |
34 | | #include "gxxfont.h" |
35 | | #include "gximask.h" |
36 | | #include "gscspace.h" /* for gsimage.h */ |
37 | | #include "gsimage.h" |
38 | | #include "gxhttile.h" |
39 | | #include "gsptype1.h" /* for gx_dc_is_pattern1_color_with_trans */ |
40 | | |
41 | | /* Forward references */ |
42 | | static byte *compress_alpha_bits(const cached_char *, gs_memory_t *); |
43 | | |
44 | | /* Define a scale factor of 1. */ |
45 | | static const gs_log2_scale_point scale_log2_1 = |
46 | | {0, 0}; |
47 | | |
48 | | void |
49 | | gx_compute_char_matrix(const gs_matrix *char_tm, const gs_log2_scale_point *log2_scale, |
50 | | float *mxx, float *mxy, float *myx, float *myy) |
51 | 9.29M | { |
52 | 9.29M | int scale_x = 1 << log2_scale->x; |
53 | 9.29M | int scale_y = 1 << log2_scale->y; |
54 | | |
55 | 9.29M | *mxx = char_tm->xx * scale_x; |
56 | 9.29M | *mxy = char_tm->xy * scale_x; |
57 | 9.29M | *myx = char_tm->yx * scale_y; |
58 | 9.29M | *myy = char_tm->yy * scale_y; |
59 | 9.29M | } |
60 | | |
61 | | void |
62 | | gx_compute_ccache_key(gs_font * pfont, const gs_matrix *char_tm, |
63 | | const gs_log2_scale_point *log2_scale, bool design_grid, |
64 | | float *mxx, float *mxy, float *myx, float *myy) |
65 | 9.42M | { |
66 | 9.42M | if (design_grid && |
67 | 9.42M | (pfont->FontType == ft_TrueType || pfont->FontType == ft_CID_TrueType)) { |
68 | | /* |
69 | | * We need a special face for this case, because the TT interpreter |
70 | | * can't generate both grid_fitted and non-grid-fitted outlines |
71 | | * with a same face instance. This happens due to control |
72 | | * values in 'cvt' must be different. |
73 | | * Since a single face satisfies all font sizes, |
74 | | * we use a zero matrix as the cache entry key. |
75 | | */ |
76 | 195k | *mxx = *mxy = *myx = *myy = 0; |
77 | 195k | } else |
78 | 9.23M | gx_compute_char_matrix(char_tm, log2_scale, mxx, mxy, myx, myy); |
79 | 9.42M | } |
80 | | |
81 | | /* Look up, and if necessary add, a font/matrix pair in the cache */ |
82 | | int |
83 | | gx_lookup_fm_pair(gs_font * pfont, const gs_matrix *char_tm, |
84 | | const gs_log2_scale_point *log2_scale, bool design_grid, cached_fm_pair **ppair) |
85 | 8.44M | { |
86 | 8.44M | float mxx, mxy, myx, myy; |
87 | 8.44M | gs_font *font = pfont; |
88 | 8.44M | register gs_font_dir *dir = font->dir; |
89 | 8.44M | register cached_fm_pair *pair = dir->fmcache.mdata + dir->fmcache.used; |
90 | 8.44M | int count = dir->fmcache.msize; |
91 | 8.44M | gs_uid uid; |
92 | | |
93 | 8.44M | gx_compute_ccache_key(pfont, char_tm, log2_scale, design_grid, |
94 | 8.44M | &mxx, &mxy, &myx, &myy); |
95 | 8.44M | if (font->FontType == ft_composite || font->PaintType != 0) { /* We can't cache by UID alone. */ |
96 | 0 | uid_set_invalid(&uid); |
97 | 8.44M | } else { |
98 | 8.44M | uid = ((gs_font_base *) font)->UID; |
99 | 8.44M | if (uid_is_valid(&uid)) |
100 | 17 | font = 0; |
101 | 8.44M | } |
102 | 164M | for (;count--; pair = dir->fmcache.mdata + pair->next) { |
103 | | /* We have either a non-zero font and an invalid UID, */ |
104 | | /* or a zero font and a valid UID. */ |
105 | | /* We have to break up the test */ |
106 | | /* because of a bug in the Zortech compiler. */ |
107 | 163M | if (font != 0) { |
108 | 163M | if (pair->font != font) |
109 | 2.69M | continue; |
110 | 163M | } else { |
111 | 57 | if (!uid_equal(&pair->UID, &uid) || |
112 | 57 | pair->FontType != pfont->FontType |
113 | 57 | ) |
114 | 22 | continue; |
115 | 57 | } |
116 | 160M | if (pair->mxx == mxx && pair->mxy == mxy && |
117 | 160M | pair->myx == myx && pair->myy == myy |
118 | 160M | && pair->design_grid == design_grid) { |
119 | 7.45M | int code; |
120 | | |
121 | 7.45M | if (pair->font == 0) { |
122 | 0 | pair->font = pfont; |
123 | 0 | if_debug2m('k', pfont->memory, "[k]updating pair "PRI_INTPTR" with font "PRI_INTPTR"\n", |
124 | 0 | (intptr_t)pair, (intptr_t)pfont); |
125 | 7.45M | } else { |
126 | 7.45M | if_debug2m('k', pfont->memory, "[k]found pair "PRI_INTPTR": font="PRI_INTPTR"\n", |
127 | 7.45M | (intptr_t)pair, (intptr_t)pair->font); |
128 | 7.45M | } |
129 | 7.45M | code = gx_touch_fm_pair(dir, pair); |
130 | 7.45M | if (code < 0) |
131 | 0 | return code; |
132 | 7.45M | code = gx_provide_fm_pair_attributes(dir, pfont, pair, |
133 | 7.45M | char_tm, log2_scale, design_grid); |
134 | 7.45M | if (code < 0) |
135 | 0 | return code; |
136 | 7.45M | *ppair = pair; |
137 | 7.45M | return 0; |
138 | 7.45M | } |
139 | 160M | } |
140 | 983k | return gx_add_fm_pair(dir, pfont, &uid, char_tm, log2_scale, design_grid, ppair); |
141 | 8.44M | } |
142 | | |
143 | | /* Look up a glyph with the right depth in the cache. */ |
144 | | /* Return the cached_char or 0. */ |
145 | | cached_char * |
146 | | gx_lookup_cached_char(const gs_font * pfont, const cached_fm_pair * pair, |
147 | | gs_glyph glyph, int wmode, int depth, |
148 | | gs_fixed_point *subpix_origin) |
149 | 23.0M | { |
150 | 23.0M | gs_font_dir *dir = pfont->dir; |
151 | 23.0M | uint chi = chars_head_index(glyph, pair); |
152 | 23.0M | register cached_char *cc; |
153 | | |
154 | 23.0M | while ((cc = dir->ccache.table[chi & dir->ccache.table_mask]) != 0) { |
155 | 19.9M | if (cc->code == glyph && cc_pair(cc) == pair && |
156 | 19.9M | cc->subpix_origin.x == subpix_origin->x && |
157 | 19.9M | cc->subpix_origin.y == subpix_origin->y && |
158 | 19.9M | cc->wmode == wmode && cc_depth(cc) == depth |
159 | 19.9M | ) { |
160 | 19.8M | if_debug4m('K', pfont->memory, |
161 | 19.8M | "[K]found "PRI_INTPTR" (depth=%d) for glyph=0x%lx, wmode=%d\n", |
162 | 19.8M | (intptr_t)cc, cc_depth(cc), (ulong)glyph, wmode); |
163 | 19.8M | return cc; |
164 | 19.8M | } |
165 | 46.2k | chi++; |
166 | 46.2k | } |
167 | 23.0M | if_debug3m('K', pfont->memory, "[K]not found: glyph=0x%lx, wmode=%d, depth=%d\n", |
168 | 3.17M | (ulong) glyph, wmode, depth); |
169 | 3.17M | return 0; |
170 | 23.0M | } |
171 | | |
172 | | /* Copy a cached character to the screen. */ |
173 | | /* Assume the caller has already done gx_color_load. */ |
174 | | /* Return 0 if OK, 1 if we couldn't do the operation but no error */ |
175 | | /* should be signalled, or a negative error code. */ |
176 | | int |
177 | | gx_image_cached_char(register gs_show_enum * penum, register cached_char * cc) |
178 | 21.7M | { |
179 | 21.7M | register gs_gstate *pgs = penum->pgs; |
180 | 21.7M | gx_device_color *pdevc = gs_currentdevicecolor_inline(pgs); |
181 | 21.7M | int x, y, w, h, depth; |
182 | 21.7M | int code; |
183 | 21.7M | gs_fixed_point pt; |
184 | 21.7M | gx_device *dev = penum->dev; |
185 | 21.7M | gx_device *imaging_dev = penum->imaging_dev ? penum->imaging_dev : dev; |
186 | 21.7M | gx_device *orig_dev = imaging_dev; |
187 | 21.7M | gx_device_clip cdev; |
188 | 21.7M | gx_xglyph xg = cc->xglyph; |
189 | 21.7M | gx_xfont *xf; |
190 | 21.7M | byte *bits; |
191 | | |
192 | 21.7M | top:code = gx_path_current_point_inline(pgs, &pt); |
193 | 21.7M | if (code < 0) |
194 | 0 | return code; |
195 | | /* |
196 | | * If the character doesn't lie entirely within the inner |
197 | | * clipping rectangle, we set up an intermediate clipping device. |
198 | | * Note that if the original device implements fill_mask, we may |
199 | | * never actually use the clipping device. |
200 | | */ |
201 | 21.7M | pt.x -= cc->offset.x + cc->subpix_origin.x; |
202 | 21.7M | x = fixed2int_var_rounded(pt.x) + penum->ftx; |
203 | 21.7M | pt.y -= cc->offset.y + cc->subpix_origin.y; |
204 | 21.7M | y = fixed2int_var_rounded(pt.y) + penum->fty; |
205 | 21.7M | w = cc->width; |
206 | 21.7M | h = cc->height; |
207 | | #ifdef DEBUG |
208 | | if (gs_debug_c('K')) { |
209 | | if (cc_has_bits(cc)) |
210 | | debug_dump_bitmap(penum->memory, cc_bits(cc), cc_raster(cc), h, |
211 | | "[K]bits"); |
212 | | else |
213 | | dmputs(penum->memory, "[K]no bits\n"); |
214 | | dmlprintf3(penum->memory, "[K]copying "PRI_INTPTR", offset=(%g,%g)\n", |
215 | | (intptr_t) cc, |
216 | | fixed2float(-cc->offset.x), |
217 | | fixed2float(-cc->offset.y)); |
218 | | dmlprintf6(penum->memory, " at (%g,%g)+(%d,%d)->(%d,%d)\n", |
219 | | fixed2float(pt.x), fixed2float(pt.y), |
220 | | penum->ftx, penum->fty, x, y); |
221 | | } |
222 | | #endif |
223 | 21.7M | if ((x < penum->ibox.p.x || x + w > penum->ibox.q.x || |
224 | 21.7M | y < penum->ibox.p.y || y + h > penum->ibox.q.y) && |
225 | 21.7M | imaging_dev != (gx_device *) & cdev /* might be 2nd time around */ |
226 | 21.7M | ) { /* Check for the character falling entirely outside */ |
227 | | /* the clipping region. */ |
228 | 4.51M | gx_clip_path *pcpath; |
229 | | |
230 | 4.51M | if (x >= penum->obox.q.x || x + w <= penum->obox.p.x || |
231 | 4.51M | y >= penum->obox.q.y || y + h <= penum->obox.p.y |
232 | 4.51M | ) |
233 | 4.42M | return 0; /* nothing to do */ |
234 | 97.8k | code = gx_effective_clip_path(pgs, &pcpath); |
235 | 97.8k | if (code < 0) |
236 | 0 | return code; |
237 | 97.8k | gx_make_clip_device_on_stack(&cdev, pcpath, imaging_dev); |
238 | 97.8k | imaging_dev = (gx_device *) & cdev; |
239 | 97.8k | if_debug0m('K', penum->memory, "[K](clipping)\n"); |
240 | 97.8k | } |
241 | 17.3M | code = gx_set_dev_color(pgs); |
242 | 17.3M | if (code != 0) |
243 | 0 | return code; |
244 | | /* If an xfont can render this character, use it. */ |
245 | 17.3M | if (xg != gx_no_xglyph && (xf = cc_pair(cc)->xfont) != 0) { |
246 | 0 | int cx = x + fixed2int(cc->offset.x); |
247 | 0 | int cy = y + fixed2int(cc->offset.y); |
248 | | |
249 | | /* |
250 | | * Note that we prefer a 1-bit xfont implementation over |
251 | | * a multi-bit cached bitmap. Eventually we should change |
252 | | * the xfont interface so it can deliver multi-bit bitmaps, |
253 | | * or else implement oversampling for xfonts. |
254 | | */ |
255 | 0 | if (gs_color_writes_pure(pgs)) { |
256 | 0 | code = (*xf->common.procs->render_char) (xf, xg, |
257 | 0 | imaging_dev, cx, cy, |
258 | 0 | pdevc->colors.pure, 0); |
259 | 0 | if_debug8m('K', penum->memory, |
260 | 0 | "[K]render_char display: xfont="PRI_INTPTR", glyph=0x%lx\n\tdev="PRI_INTPTR"(%s) x,y=%d,%d, color=0x%lx => %d\n", |
261 | 0 | (intptr_t)xf, (ulong)xg, (intptr_t)imaging_dev, |
262 | 0 | imaging_dev->dname, cx, cy, |
263 | 0 | (ulong) pdevc->colors.pure, code); |
264 | 0 | if (code == 0) |
265 | 0 | return_check_interrupt(penum->memory, 0); |
266 | 0 | } |
267 | | /* Can't render directly. If we don't have a bitmap yet, */ |
268 | | /* get it from the xfont now. */ |
269 | 0 | if (!cc_has_bits(cc)) { |
270 | 0 | gx_device_memory mdev; |
271 | |
|
272 | 0 | gs_make_mem_mono_device(&mdev, dev->memory, imaging_dev); |
273 | 0 | gx_open_cache_device(&mdev, cc); |
274 | 0 | code = (*xf->common.procs->render_char) (xf, xg, |
275 | 0 | (gx_device *) & mdev, cx - x, cy - y, |
276 | 0 | (gx_color_index) 1, 1); |
277 | 0 | if_debug7m('K', penum->memory, |
278 | 0 | "[K]render_char to bits: xfont="PRI_INTPTR", glyph=0x%lx\n\tdev="PRI_INTPTR"(%s) x,y=%d,%d => %d\n", |
279 | 0 | (intptr_t)xf, (ulong) xg, (intptr_t)&mdev, |
280 | 0 | mdev.dname, cx - x, cy - y, code); |
281 | 0 | if (code != 0) |
282 | 0 | return_check_interrupt(penum->memory, 1); |
283 | 0 | gx_add_char_bits(cc_pair(cc)->font->dir, |
284 | 0 | cc, &scale_log2_1); |
285 | | /* gx_add_char_bits may change width, height, */ |
286 | | /* raster, and/or offset. It's easiest to */ |
287 | | /* start over from the top. Clear xg so that */ |
288 | | /* we don't waste time trying render_char again. */ |
289 | 0 | xg = gx_no_xglyph; |
290 | 0 | goto top; |
291 | 0 | } |
292 | 0 | } |
293 | | /* |
294 | | * No xfont. Render from the cached bits. If the cached bits |
295 | | * have more than 1 bit of alpha, and the color isn't pure or |
296 | | * the copy_alpha operation fails, construct a single-bit mask |
297 | | * by taking the high-order alpha bit. |
298 | | */ |
299 | 17.3M | bits = cc_bits(cc); |
300 | | /* With 4x2 scale, depth == 3. |
301 | | * An example is -dTextAlphaBits=4 comparefiles/fonttest.pdf . |
302 | | * We need to map 4 bitmap bits to 2 alpha bits. |
303 | | */ |
304 | 17.3M | depth = (cc_depth(cc) == 3 ? 2 : cc_depth(cc)); |
305 | 17.3M | if ((dev_proc(orig_dev, fill_mask) != gx_default_fill_mask || |
306 | 17.3M | !lop_no_S_is_T(pgs->log_op))) { |
307 | | |
308 | 16.2M | gx_clip_path *pcpath; |
309 | | |
310 | 16.2M | penum->use_wxy_float = false; |
311 | 16.2M | penum->wxy_float.x = penum->wxy_float.y = 0.0; |
312 | 16.2M | penum->wxy = cc->wxy; |
313 | | |
314 | 16.2M | code = gx_effective_clip_path(pgs, &pcpath); |
315 | 16.2M | if (code >= 0) { |
316 | 16.2M | code = gx_image_fill_masked |
317 | 16.2M | (orig_dev, bits, 0, cc_raster(cc), cc->id, |
318 | 16.2M | x, y, w, h, pdevc, depth, pgs->log_op, pcpath); |
319 | 16.2M | if (code >= 0) |
320 | 16.2M | goto done; |
321 | 16.2M | } |
322 | 16.2M | } else if (gs_color_writes_pure(pgs)) { |
323 | 1.09M | gx_color_index color = pdevc->colors.pure; |
324 | | |
325 | 1.09M | if (depth > 1) { |
326 | 0 | code = (*dev_proc(imaging_dev, copy_alpha)) |
327 | 0 | (imaging_dev, bits, 0, cc_raster(cc), cc->id, |
328 | 0 | x, y, w, h, color, depth); |
329 | 0 | if (code >= 0) |
330 | 0 | return_check_interrupt(penum->memory, 0); |
331 | | /* copy_alpha failed, construct a monobit mask. */ |
332 | 0 | bits = compress_alpha_bits(cc, penum->memory->non_gc_memory); |
333 | 0 | if (bits == 0) |
334 | 0 | return 1; /* VMerror, but recoverable */ |
335 | 0 | } |
336 | 1.09M | code = (*dev_proc(imaging_dev, copy_mono)) |
337 | 1.09M | (imaging_dev, bits, 0, bitmap_raster(w), gs_no_id, |
338 | 1.09M | x, y, w, h, gx_no_color_index, color); |
339 | 1.09M | goto done; |
340 | 1.09M | } |
341 | 148 | if (depth > 1) { /* Complex color or fill_mask / copy_alpha failed, */ |
342 | | /* construct a monobit mask. */ |
343 | 0 | bits = compress_alpha_bits(cc, penum->memory->non_gc_memory); |
344 | 0 | if (bits == 0) |
345 | 0 | return 1; /* VMerror, but recoverable */ |
346 | |
|
347 | 148 | } { /* Use imagemask to render the character. */ |
348 | 148 | gs_memory_t *mem = penum->memory->non_gc_memory; |
349 | 148 | gs_image_enum *pie = |
350 | 148 | gs_image_enum_alloc(mem, "image_char(image_enum)"); |
351 | 148 | gs_image_t image; |
352 | 148 | int iy; |
353 | 148 | uint used, raster = (bits == cc_bits(cc) ? cc_raster(cc) |
354 | 148 | : bitmap_raster(cc->width) ); |
355 | 148 | int code1; |
356 | | |
357 | 148 | if (pie == 0) { |
358 | 0 | if (bits != cc_bits(cc)) |
359 | 0 | gs_free_object(mem, bits, |
360 | 0 | "compress_alpha_bits"); |
361 | 0 | return 1; /* VMerror, but recoverable */ |
362 | 0 | } |
363 | | /* Make a matrix that will place the image */ |
364 | | /* at (x,y) with no transformation. */ |
365 | 148 | gs_image_t_init_mask(&image, true); |
366 | 148 | gs_make_translation((double) - x, (double) - y, &image.ImageMatrix); |
367 | 148 | gs_matrix_multiply(&ctm_only(pgs), &image.ImageMatrix, &image.ImageMatrix); |
368 | 148 | image.Width = w; |
369 | 148 | image.Height = h; |
370 | 148 | image.adjust = false; |
371 | 148 | code = gs_image_init(pie, &image, false, true, pgs); |
372 | 148 | switch (code) { |
373 | 14 | case 1: /* empty image */ |
374 | 14 | code = 0; |
375 | 14 | default: |
376 | 14 | break; |
377 | 134 | case 0: |
378 | 9.53k | for (iy = 0; iy < h && code >= 0; iy++) |
379 | 9.39k | code = gs_image_next(pie, bits + iy * raster, |
380 | 9.39k | (w + 7) >> 3, &used); |
381 | 148 | } |
382 | 148 | code1 = gs_image_cleanup_and_free_enum(pie, pgs); |
383 | 148 | if (code >= 0 && code1 < 0) |
384 | 4 | code = code1; |
385 | 148 | } |
386 | 17.3M | done:if (bits != cc_bits(cc)) |
387 | 0 | gs_free_object(penum->memory->non_gc_memory, bits, "compress_alpha_bits"); |
388 | 17.3M | if (code > 0) |
389 | 130 | code = 0; |
390 | 17.3M | return_check_interrupt(penum->memory, code); |
391 | 148 | } |
392 | | |
393 | | /* ------ Image manipulation ------ */ |
394 | | |
395 | | /* |
396 | | * Compress a mask with 2 or 4 bits of alpha to a monobit mask. |
397 | | * Allocate and return the address of the monobit mask. |
398 | | */ |
399 | | static byte * |
400 | | compress_alpha_bits(const cached_char * cc, gs_memory_t * mem) |
401 | 0 | { |
402 | 0 | const byte *data = cc_const_bits(cc); |
403 | 0 | uint width = cc->width; |
404 | 0 | uint height = cc->height; |
405 | | /* With 4x2 scale, depth == 3. |
406 | | * An example is -dTextAlphaBits=4 comparefiles/fonttest.pdf . |
407 | | * We need to map 4 bitmap bits to 2 alpha bits. |
408 | | */ |
409 | 0 | int depth = (cc_depth(cc) == 3 ? 2 : cc_depth(cc)); |
410 | 0 | uint sraster = cc_raster(cc); |
411 | 0 | uint sskip = sraster - ((width * depth + 7) >> 3); |
412 | 0 | uint draster = bitmap_raster(width); |
413 | 0 | uint dskip = draster - ((width + 7) >> 3); |
414 | 0 | byte *mask = gs_alloc_bytes(mem, (size_t)draster * height, |
415 | 0 | "compress_alpha_bits"); |
416 | 0 | const byte *sptr = data; |
417 | 0 | byte *dptr = mask; |
418 | 0 | uint h; |
419 | |
|
420 | 0 | if (mask == 0) |
421 | 0 | return 0; |
422 | 0 | for (h = height; h; --h) { |
423 | 0 | byte sbit = 0x80; |
424 | 0 | byte d = 0; |
425 | 0 | byte dbit = 0x80; |
426 | 0 | uint w; |
427 | |
|
428 | 0 | for (w = width; w; --w) { |
429 | 0 | if (*sptr & sbit) |
430 | 0 | d += dbit; |
431 | 0 | if (!(sbit >>= depth)) |
432 | 0 | sbit = 0x80, sptr++; |
433 | 0 | if (!(dbit >>= 1)) { |
434 | 0 | *dptr++ = d; |
435 | 0 | dbit = 0x80, d = 0; |
436 | 0 | } |
437 | 0 | } |
438 | 0 | if (dbit != 0x80) |
439 | 0 | *dptr++ = d; |
440 | 0 | for (w = dskip; w != 0; --w) |
441 | 0 | *dptr++ = 0; |
442 | 0 | if (sbit != 0x80) |
443 | 0 | ++sptr; |
444 | 0 | sptr += sskip; |
445 | 0 | } |
446 | 0 | return mask; |
447 | 0 | } |