1 /* Copyright (C) 1989, 1995, 1996, 1997 Aladdin Enterprises. All rights reserved.
2
3 This software is provided AS-IS with no warranty, either express or
4 implied.
5
6 This software is distributed under license and may not be copied,
7 modified or distributed except as expressly authorized under the terms
8 of the license contained in the file LICENSE in this distribution.
9
10 For more information about licensing, please refer to
11 http://www.ghostscript.com/licensing/. For information on
12 commercial licensing, go to http://www.artifex.com/licensing/ or
13 contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14 San Rafael, CA 94903, U.S.A., +1(415)492-9861.
15 */
16
17 /* $Id: gdevwddb.c,v 1.7 2004/08/10 13:02:36 stefan Exp $ */
18 /*
19 * Microsoft Windows 3.n driver for Ghostscript,
20 * using device-dependent bitmap.
21 *
22 * Original version by Russell Lang and Maurice Castro with help from
23 * Programming Windows, 2nd Ed., Charles Petzold, Microsoft Press;
24 * created from gdevbgi.c and gnuplot/term/win.trm 5th June 1992.
25 * Extensively modified by L. Peter Deutsch, Aladdin Enterprises.
26 */
27 #include "gdevmswn.h"
28
29 /* Make sure we cast to the correct structure type. */
30 typedef struct gx_device_win_ddb_s gx_device_win_ddb;
31
32 #undef wdev
33 #define wdev ((gx_device_win_ddb *)dev)
34
35 /* Forward references */
36 private void near win_addtool(gx_device_win_ddb *, int);
37 private void near win_maketools(gx_device_win_ddb *, HDC);
38 private void near win_destroytools(gx_device_win_ddb *);
39
40 /* Device procedures */
41
42 /* See gxdevice.h for the definitions of the procedures. */
43 private dev_proc_open_device(win_ddb_open);
44 private dev_proc_close_device(win_ddb_close);
45 private dev_proc_map_rgb_color(win_ddb_map_rgb_color);
46 private dev_proc_fill_rectangle(win_ddb_fill_rectangle);
47 private dev_proc_tile_rectangle(win_ddb_tile_rectangle);
48 private dev_proc_copy_mono(win_ddb_copy_mono);
49 private dev_proc_copy_color(win_ddb_copy_color);
50
51 /* Windows-specific procedures */
52 private win_proc_copy_to_clipboard(win_ddb_copy_to_clipboard);
53 private win_proc_repaint(win_ddb_repaint);
54 private win_proc_alloc_bitmap(win_ddb_alloc_bitmap);
55 private win_proc_free_bitmap(win_ddb_free_bitmap);
56
57 /* The device descriptor */
58 struct gx_device_win_ddb_s {
59 gx_device_common;
60 gx_device_win_common;
61
62 /* Handles */
63
64 HBITMAP FAR hbitmap;
65 HDC FAR hdcbit;
66 HPEN hpen, *hpens;
67 uint hpensize;
68 HBRUSH hbrush, *hbrushs;
69 uint hbrushsize;
70 #define select_brush(color)\
71 if (wdev->hbrush != wdev->hbrushs[color])\
72 { wdev->hbrush = wdev->hbrushs[color];\
73 SelectObject(wdev->hdcbit,wdev->hbrush);\
74 }
75 HPALETTE hpalette;
76 LPLOGPALETTE lpalette;
77
78 /* A staging bitmap for copy_mono. */
79 /* We want one big enough to handle the standard 16x16 halftone; */
80 /* this is also big enough for ordinary-size characters. */
81
82 #define bmWidthBytes 4 /* must be even */
83 #define bmWidthBits (bmWidthBytes * 8)
84 #define bmHeight 32
85 HBITMAP FAR hbmmono;
86 HDC FAR hdcmono;
87 gx_bitmap_id bm_id;
88 };
89 private const gx_device_procs win_ddb_procs =
90 {
91 win_ddb_open,
92 NULL, /* get_initial_matrix */
93 win_sync_output,
94 win_output_page,
95 win_ddb_close,
96 win_ddb_map_rgb_color,
97 win_map_color_rgb,
98 win_ddb_fill_rectangle,
99 win_ddb_tile_rectangle,
100 win_ddb_copy_mono,
101 win_ddb_copy_color,
102 NULL, /* draw_line */
103 NULL, /* get_bits */
104 win_get_params,
105 win_put_params,
106 NULL, /* map_cmyk_color */
107 win_get_xfont_procs
108 };
109 gx_device_win_ddb far_data gs_mswin_device =
110 {
111 std_device_std_body(gx_device_win_ddb, &win_ddb_procs, "mswin",
112 INITIAL_WIDTH, INITIAL_HEIGHT, /* win_open() fills these in later */
113 INITIAL_RESOLUTION, INITIAL_RESOLUTION /* win_open() fills these in later */
114 ),
115 {0}, /* std_procs */
116 0, /* BitsPerPixel - not used */
117 5000, /* UpdateInterval (in milliseconds) */
118 "\0", /* GSVIEW_STR */
119 0, /* not a DLL device */
120 2, /* nColors */
121 0, /* mapped_color_flags */
122 win_ddb_copy_to_clipboard,
123 win_ddb_repaint,
124 win_ddb_alloc_bitmap,
125 win_ddb_free_bitmap
126 };
127
128 /* Open the win_ddb driver */
129 private int
win_ddb_open(gx_device * dev)130 win_ddb_open(gx_device * dev)
131 {
132 int code = win_open(dev);
133 HDC hdc;
134
135 if (code < 0)
136 return code;
137
138 if (wdev->BitsPerPixel > 8)
139 return gs_error_limitcheck; /* don't support 24 bit/pixel */
140
141 /* Create the backing bitmap. */
142 code = win_ddb_alloc_bitmap((gx_device_win *) dev, dev);
143 if (code < 0)
144 return code;
145
146 /* Create the bitmap and DC for copy_mono. */
147 hdc = GetDC(wdev->hwndimg);
148 wdev->hbmmono = CreateBitmap(bmWidthBits, bmHeight, 1, 1, NULL);
149 wdev->hdcmono = CreateCompatibleDC(hdc);
150 if (wdev->hbmmono == NULL || wdev->hdcmono == NULL) {
151 win_ddb_free_bitmap((gx_device_win *) dev);
152 ReleaseDC(wdev->hwndimg, hdc);
153 return win_nomemory();
154 }
155 SetMapMode(wdev->hdcmono, GetMapMode(hdc));
156 SelectObject(wdev->hdcmono, wdev->hbmmono);
157 wdev->bm_id = gx_no_bitmap_id;
158 ReleaseDC(wdev->hwndimg, hdc);
159
160 /* create palette and tools for bitmap */
161 if ((wdev->lpalette = win_makepalette((gx_device_win *) dev))
162 == (LPLOGPALETTE) NULL)
163 return win_nomemory();
164 wdev->hpalette = CreatePalette(wdev->lpalette);
165 (void)SelectPalette(wdev->hdcbit, wdev->hpalette, NULL);
166 RealizePalette(wdev->hdcbit);
167 win_maketools(wdev, wdev->hdcbit);
168
169 wdev->hdctext = wdev->hdcbit; /* draw text here */
170
171 return 0;
172 }
173
174 /* Close the win_ddb driver */
175 private int
win_ddb_close(gx_device * dev)176 win_ddb_close(gx_device * dev)
177 {
178 /* Free resources */
179
180 win_destroytools(wdev);
181 DeleteDC(wdev->hdcmono);
182 win_ddb_free_bitmap((gx_device_win *) dev);
183 DeleteObject(wdev->hpalette);
184 DeleteObject(wdev->hbmmono);
185 gs_free((char *)(wdev->lpalette), 1, sizeof(LOGPALETTE) +
186 (1 << (wdev->color_info.depth)) * sizeof(PALETTEENTRY),
187 "win_ddb_close");
188
189 return win_close(dev);
190 }
191
192 /* Map a r-g-b color to the colors available under Windows */
193 private gx_color_index
win_ddb_map_rgb_color(gx_device * dev,gx_color_value r,gx_color_value g,gx_color_value b)194 win_ddb_map_rgb_color(gx_device * dev, gx_color_value r, gx_color_value g,
195 gx_color_value b)
196 {
197 int i = wdev->nColors;
198 gx_color_index color = win_map_rgb_color(dev, r, g, b);
199 LPLOGPALETTE lipal = wdev->limgpalette;
200 LPLOGPALETTE lpal = wdev->lpalette;
201
202 if (color != i)
203 return color;
204
205 /* We just added a color to the window palette. */
206 /* Add it to the bitmap palette as well. */
207
208 DeleteObject(wdev->hpalette);
209 lpal->palPalEntry[i].peFlags = NULL;
210 lpal->palPalEntry[i].peRed = lipal->palPalEntry[i].peRed;
211 lpal->palPalEntry[i].peGreen = lipal->palPalEntry[i].peGreen;
212 lpal->palPalEntry[i].peBlue = lipal->palPalEntry[i].peBlue;
213 lpal->palNumEntries = i + 1;
214 wdev->hpalette = CreatePalette(lpal);
215 (void)SelectPalette(wdev->hdcbit, wdev->hpalette, NULL);
216 RealizePalette(wdev->hdcbit);
217 win_addtool(wdev, i);
218
219 return color;
220 }
221
222 /* Macro for filling a rectangle with a color. */
223 /* Note that it starts with a declaration. */
224 #define fill_rect(x, y, w, h, color)\
225 RECT rect;\
226 rect.left = x, rect.top = y;\
227 rect.right = x + w, rect.bottom = y + h;\
228 FillRect(wdev->hdcbit, &rect, wdev->hbrushs[(int)color])
229
230
231 /* Fill a rectangle. */
232 private int
win_ddb_fill_rectangle(gx_device * dev,int x,int y,int w,int h,gx_color_index color)233 win_ddb_fill_rectangle(gx_device * dev, int x, int y, int w, int h,
234 gx_color_index color)
235 {
236 fit_fill(dev, x, y, w, h);
237 /* Use PatBlt for filling. Special-case black. */
238 if (color == 0)
239 PatBlt(wdev->hdcbit, x, y, w, h, rop_write_0s);
240 else {
241 select_brush((int)color);
242 PatBlt(wdev->hdcbit, x, y, w, h, rop_write_pattern);
243 }
244 win_update((gx_device_win *) dev);
245
246 return 0;
247 }
248
249 /* Tile a rectangle. If neither color is transparent, */
250 /* pre-clear the rectangle to color0 and just tile with color1. */
251 /* This is faster because of how win_copy_mono is implemented. */
252 /* Note that this also does the right thing for colored tiles. */
253 private int
win_ddb_tile_rectangle(gx_device * dev,const gx_tile_bitmap * tile,int x,int y,int w,int h,gx_color_index czero,gx_color_index cone,int px,int py)254 win_ddb_tile_rectangle(gx_device * dev, const gx_tile_bitmap * tile,
255 int x, int y, int w, int h, gx_color_index czero, gx_color_index cone,
256 int px, int py)
257 {
258 fit_fill(dev, x, y, w, h);
259 if (czero != gx_no_color_index && cone != gx_no_color_index) {
260 fill_rect(x, y, w, h, czero);
261 czero = gx_no_color_index;
262 }
263 if (tile->raster == bmWidthBytes && tile->size.y <= bmHeight &&
264 (px | py) == 0 && cone != gx_no_color_index
265 ) { /* We can do this much more efficiently */
266 /* by using the internal algorithms of copy_mono */
267 /* and gx_default_tile_rectangle. */
268 int width = tile->size.x;
269 int height = tile->size.y;
270 int rwidth = tile->rep_width;
271 int irx = ((rwidth & (rwidth - 1)) == 0 ? /* power of 2 */
272 x & (rwidth - 1) :
273 x % rwidth);
274 int ry = y % tile->rep_height;
275 int icw = width - irx;
276 int ch = height - ry;
277 int ex = x + w, ey = y + h;
278 int fex = ex - width, fey = ey - height;
279 int cx, cy;
280
281 select_brush((int)cone);
282
283 if (tile->id != wdev->bm_id || tile->id == gx_no_bitmap_id) {
284 wdev->bm_id = tile->id;
285 SetBitmapBits(wdev->hbmmono,
286 (DWORD) (bmWidthBytes * tile->size.y),
287 (BYTE *) tile->data);
288 }
289 #define copy_tile(srcx, srcy, tx, ty, tw, th)\
290 BitBlt(wdev->hdcbit, tx, ty, tw, th, wdev->hdcmono, srcx, srcy, rop_write_at_1s)
291
292 if (ch > h)
293 ch = h;
294 for (cy = y;;) {
295 if (w <= icw)
296 copy_tile(irx, ry, x, cy, w, ch);
297 else {
298 copy_tile(irx, ry, x, cy, icw, ch);
299 cx = x + icw;
300 while (cx <= fex) {
301 copy_tile(0, ry, cx, cy, width, ch);
302 cx += width;
303 }
304 if (cx < ex) {
305 copy_tile(0, ry, cx, cy, ex - cx, ch);
306 }
307 }
308 if ((cy += ch) >= ey)
309 break;
310 ch = (cy > fey ? ey - cy : height);
311 ry = 0;
312 }
313
314 win_update((gx_device_win *) dev);
315 return 0;
316 }
317 return gx_default_tile_rectangle(dev, tile, x, y, w, h, czero, cone, px, py);
318 }
319
320
321 /* Copy a monochrome bitmap. The colors are given explicitly. */
322 /* Color = gx_no_color_index means transparent (no effect on the image). */
323 private int
win_ddb_copy_mono(gx_device * dev,const byte * base,int sourcex,int raster,gx_bitmap_id id,int x,int y,int w,int h,gx_color_index zero,gx_color_index one)324 win_ddb_copy_mono(gx_device * dev,
325 const byte * base, int sourcex, int raster, gx_bitmap_id id,
326 int x, int y, int w, int h,
327 gx_color_index zero, gx_color_index one)
328 {
329 int endx;
330 const byte *ptr_line;
331 int width_bytes, height;
332 DWORD rop = rop_write_at_1s;
333 int color;
334 BYTE aBit[bmWidthBytes * bmHeight];
335 BYTE *aptr = aBit;
336
337 fit_copy(dev, base, sourcex, raster, id, x, y, w, h);
338
339 if (sourcex & ~7) {
340 base += sourcex >> 3;
341 sourcex &= 7;
342 }
343 /* Break up large transfers into smaller ones. */
344 while ((endx = sourcex + w) > bmWidthBits) {
345 int lastx = (endx - 1) & -bmWidthBits;
346 int subw = endx - lastx;
347 int code = win_ddb_copy_mono(dev, base, lastx,
348 raster, gx_no_bitmap_id,
349 x + lastx - sourcex, y,
350 subw, h, zero, one);
351
352 if (code < 0)
353 return code;
354 w -= subw;
355 }
356 while (h > bmHeight) {
357 int code;
358
359 h -= bmHeight;
360 code = win_ddb_copy_mono(dev, base + h * raster, sourcex,
361 raster, gx_no_bitmap_id,
362 x, y + h, w, bmHeight, zero, one);
363 if (code < 0)
364 return code;
365 }
366
367 width_bytes = (sourcex + w + 7) >> 3;
368 ptr_line = base;
369
370 if (zero == gx_no_color_index) {
371 if (one == gx_no_color_index)
372 return 0;
373 color = (int)one;
374 if (color == 0)
375 rop = rop_write_0_at_1s;
376 else
377 select_brush(color);
378 } else {
379 if (one == gx_no_color_index) {
380 color = (int)zero;
381 rop = rop_write_at_0s;
382 } else { /* Pre-clear the rectangle to zero */
383 fill_rect(x, y, w, h, zero);
384 color = (int)one;
385 }
386 select_brush(color);
387 }
388
389 if (id != wdev->bm_id || id == gx_no_bitmap_id) {
390 wdev->bm_id = id;
391 if (raster == bmWidthBytes) { /* We can do the whole thing in a single transfer! */
392 SetBitmapBits(wdev->hbmmono,
393 (DWORD) (bmWidthBytes * h),
394 (BYTE *) base);
395 } else {
396 for (height = h; height--;
397 ptr_line += raster, aptr += bmWidthBytes
398 ) { /* Pack the bits into the bitmap. */
399 switch (width_bytes) {
400 default:
401 memcpy(aptr, ptr_line, width_bytes);
402 break;
403 case 4:
404 aptr[3] = ptr_line[3];
405 case 3:
406 aptr[2] = ptr_line[2];
407 case 2:
408 aptr[1] = ptr_line[1];
409 case 1:
410 aptr[0] = ptr_line[0];
411 }
412 }
413 SetBitmapBits(wdev->hbmmono,
414 (DWORD) (bmWidthBytes * h),
415 &aBit[0]);
416 }
417 }
418 BitBlt(wdev->hdcbit, x, y, w, h, wdev->hdcmono, sourcex, 0, rop);
419 win_update((gx_device_win *) dev);
420 return 0;
421 }
422
423
424 /* Copy a color pixel map. This is just like a bitmap, except that */
425 /* each pixel takes 8 or 4 bits instead of 1 when device driver has color. */
426 private int
win_ddb_copy_color(gx_device * dev,const byte * base,int sourcex,int raster,gx_bitmap_id id,int x,int y,int w,int h)427 win_ddb_copy_color(gx_device * dev,
428 const byte * base, int sourcex, int raster, gx_bitmap_id id,
429 int x, int y, int w, int h)
430 {
431 fit_copy(dev, base, sourcex, raster, id, x, y, w, h);
432
433 if (gx_device_has_color(dev)) {
434 switch (dev->color_info.depth) {
435 case 8:
436 {
437 int xi, yi;
438 int skip = raster - w;
439 const byte *sptr = base + sourcex;
440
441 if (w <= 0)
442 return 0;
443 if (x < 0 || x + w > dev->width)
444 return_error(gs_error_rangecheck);
445 for (yi = y; yi - y < h; yi++) {
446 for (xi = x; xi - x < w; xi++) {
447 int color = *sptr++;
448
449 SetPixel(wdev->hdcbit, xi, yi, PALETTEINDEX(color));
450 }
451 sptr += skip;
452 }
453 }
454 break;
455 case 4:
456 { /* color device, four bits per pixel */
457 const byte *line = base + (sourcex >> 1);
458 int dest_y = y, end_x = x + w;
459
460 if (w <= 0)
461 return 0;
462 while (h--) { /* for each line */
463 const byte *source = line;
464 register int dest_x = x;
465
466 if (sourcex & 1) { /* odd nibble first */
467 int color = *source++ & 0xf;
468
469 SetPixel(wdev->hdcbit, dest_x, dest_y, PALETTEINDEX(color));
470 dest_x++;
471 }
472 /* Now do full bytes */
473 while (dest_x < end_x) {
474 int color = *source >> 4;
475
476 SetPixel(wdev->hdcbit, dest_x, dest_y, PALETTEINDEX(color));
477 dest_x++;
478 if (dest_x < end_x) {
479 color = *source++ & 0xf;
480 SetPixel(wdev->hdcbit, dest_x, dest_y, PALETTEINDEX(color));
481 dest_x++;
482 }
483 }
484 dest_y++;
485 line += raster;
486 }
487 }
488 break;
489 default:
490 return (-1); /* panic */
491 }
492 } else
493 /* monochrome device: one bit per pixel */
494 { /* bitmap is the same as win_copy_mono: one bit per pixel */
495 win_ddb_copy_mono(dev, base, sourcex, raster, id, x, y, w, h,
496 (gx_color_index) 0,
497 (gx_color_index) (dev->color_info.depth == 8 ? 63 : dev->color_info.max_gray));
498 }
499 win_update((gx_device_win *) dev);
500 return 0;
501 }
502
503 /* ------ Windows-specific device procedures ------ */
504
505
506 /* Copy the bitmap to the clipboard. */
507 private void
win_ddb_copy_to_clipboard(gx_device_win * dev)508 win_ddb_copy_to_clipboard(gx_device_win * dev)
509 { /* make somewhere to put it and copy */
510 HDC hdcbit = wdev->hdcbit;
511 HBITMAP bitmap = CreateCompatibleBitmap(hdcbit, dev->width,
512 dev->height);
513
514 if (bitmap) {
515 /* there is enough memory and the bitmaps OK */
516 HDC mem = CreateCompatibleDC(hdcbit);
517
518 SelectObject(mem, bitmap);
519 BitBlt(mem, 0, 0, dev->width, dev->height,
520 hdcbit, 0, 0, SRCCOPY);
521 DeleteDC(mem);
522 /* copy it to the clipboard */
523 OpenClipboard(wdev->hwndimg);
524 EmptyClipboard();
525 SetClipboardData(CF_BITMAP, bitmap);
526 SetClipboardData(CF_PALETTE, CreatePalette(wdev->limgpalette));
527 CloseClipboard();
528 }
529 }
530
531
532 /* Repaint a section of the window. */
533 private void
win_ddb_repaint(gx_device_win * dev,HDC hdc,int dx,int dy,int wx,int wy,int sx,int sy)534 win_ddb_repaint(gx_device_win * dev, HDC hdc, int dx, int dy, int wx, int wy,
535 int sx, int sy)
536 {
537 BitBlt(hdc, dx, dy, wx, wy, wdev->hdcbit, sx, sy, SRCCOPY);
538 }
539
540
541 /* Allocate the backing bitmap. */
542 private int
win_ddb_alloc_bitmap(gx_device_win * dev,gx_device * param_dev)543 win_ddb_alloc_bitmap(gx_device_win * dev, gx_device * param_dev)
544 {
545 HDC hdc;
546 int i;
547
548 hdc = GetDC(wdev->hwndimg);
549 for (i = 0;; i++) {
550 wdev->hbitmap = CreateCompatibleBitmap(hdc,
551 param_dev->width, param_dev->height);
552 if (wdev->hbitmap != (HBITMAP) NULL)
553 break;
554 if (i >= 4) {
555 ReleaseDC(wdev->hwndimg, hdc);
556 return win_nomemory();
557 }
558 errprintf("\nNot enough memory for bitmap. Halving resolution... ");
559 param_dev->x_pixels_per_inch /= 2;
560 param_dev->y_pixels_per_inch /= 2;
561 param_dev->width /= 2;
562 param_dev->height /= 2;
563 }
564
565 wdev->hdcbit = CreateCompatibleDC(hdc); /* create Device Context for drawing */
566 SelectObject(wdev->hdcbit, wdev->hbitmap);
567 ReleaseDC(wdev->hwndimg, hdc);
568 return 0;
569 }
570
571
572 /* Free the backing bitmap. */
573 private void
win_ddb_free_bitmap(gx_device_win * dev)574 win_ddb_free_bitmap(gx_device_win * dev)
575 {
576 DeleteDC(wdev->hdcbit); /* must do this first */
577 DeleteObject(wdev->hbitmap);
578 }
579
580
581 /* ------ Internal routines ------ */
582
583 #undef wdev
584
585
586 private void near
win_addtool(gx_device_win_ddb * wdev,int i)587 win_addtool(gx_device_win_ddb * wdev, int i)
588 {
589 wdev->hpens[i] = CreatePen(PS_SOLID, 1, PALETTEINDEX(i));
590 wdev->hbrushs[i] = CreateSolidBrush(PALETTEINDEX(i));
591 }
592
593
594 private void near
win_maketools(gx_device_win_ddb * wdev,HDC hdc)595 win_maketools(gx_device_win_ddb * wdev, HDC hdc)
596 {
597 int i;
598
599 wdev->hpensize = (1 << (wdev->color_info.depth)) * sizeof(HPEN);
600 wdev->hpens = (HPEN *) gs_malloc(wdev->memory, 1, wdev->hpensize,
601 "win_maketools(pens)");
602 wdev->hbrushsize = (1 << (wdev->color_info.depth)) * sizeof(HBRUSH);
603 wdev->hbrushs = (HBRUSH *) gs_malloc(wdev->memory, 1, wdev->hbrushsize,
604 "win_maketools(brushes)");
605 if (wdev->hpens && wdev->hbrushs) {
606 for (i = 0; i < wdev->nColors; i++)
607 win_addtool(wdev, i);
608
609 wdev->hpen = wdev->hpens[0];
610 SelectObject(hdc, wdev->hpen);
611
612 wdev->hbrush = wdev->hbrushs[0];
613 SelectObject(hdc, wdev->hbrush);
614 }
615 }
616
617
618 private void near
win_destroytools(gx_device_win_ddb * wdev)619 win_destroytools(gx_device_win_ddb * wdev)
620 {
621 int i;
622
623 for (i = 0; i < wdev->nColors; i++) {
624 DeleteObject(wdev->hpens[i]);
625 DeleteObject(wdev->hbrushs[i]);
626 }
627 gs_free(wdev->memory, (char *)wdev->hbrushs, 1, wdev->hbrushsize,
628 "win_destroytools(brushes)");
629 gs_free(wdev->memory, (char *)wdev->hpens, 1, wdev->hpensize,
630 "win_destroytools(pens)");
631 }
632