comparison mupdf-source/platform/x11/x11_image.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 // Copyright (C) 2004-2021 Artifex Software, Inc.
2 //
3 // This file is part of MuPDF.
4 //
5 // MuPDF is free software: you can redistribute it and/or modify it under the
6 // terms of the GNU Affero General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or (at your option)
8 // any later version.
9 //
10 // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13 // details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17 //
18 // Alternative licensing terms are available from the licensor.
19 // For commercial licensing, see <https://www.artifex.com/> or contact
20 // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21 // CA 94129, USA, for further information.
22
23 /*
24 * Blit RGBA images to X with X(Shm)Images
25 */
26
27 #ifndef _XOPEN_SOURCE
28 # define _XOPEN_SOURCE 1
29 #endif
30
31 #ifndef _XOPEN_SOURCE
32 # define _XOPEN_SOURCE 1
33 #endif
34
35 #define noSHOWINFO
36
37 #include "mupdf/fitz.h"
38
39 #include <string.h>
40 #include <stdlib.h>
41 #include <stdio.h>
42
43 #include <X11/Xlib.h>
44 #include <X11/Xutil.h>
45 #include <sys/ipc.h>
46 #include <sys/shm.h>
47 #include <X11/extensions/XShm.h>
48
49 #include <limits.h>
50
51 extern int ffs(int);
52
53 static int is_big_endian(void)
54 {
55 static const int one = 1;
56 return *(char*)&one == 0;
57 }
58
59 typedef void (*ximage_convert_func_t)
60 (
61 const unsigned char *src,
62 int srcstride,
63 unsigned char *dst,
64 int dststride,
65 int w,
66 int h
67 );
68
69 #define POOLSIZE 4
70 #define WIDTH 256
71 #define HEIGHT 256
72
73 enum {
74 ARGB8888,
75 BGRA8888,
76 RGBA8888,
77 ABGR8888,
78 RGB888,
79 BGR888,
80 RGB565,
81 RGB565_BR,
82 RGB555,
83 RGB555_BR,
84 BGR233,
85 UNKNOWN
86 };
87
88 #ifdef SHOWINFO
89 static char *modename[] = {
90 "ARGB8888",
91 "BGRA8888",
92 "RGBA8888",
93 "ABGR8888",
94 "RGB888",
95 "BGR888",
96 "RGB565",
97 "RGB565_BR",
98 "RGB555",
99 "RGB555_BR",
100 "BGR233",
101 "UNKNOWN"
102 };
103 #endif
104
105 extern ximage_convert_func_t ximage_convert_funcs[];
106
107 static struct
108 {
109 Display *display;
110 int screen;
111 XVisualInfo visual;
112 Colormap colormap;
113
114 int bitsperpixel;
115 int mode;
116
117 XColor rgbcube[256];
118
119 ximage_convert_func_t convert_func;
120
121 int useshm;
122 int shmcode;
123 XImage *pool[POOLSIZE];
124 /* MUST exist during the lifetime of the shared ximage according to the
125 xc/doc/hardcopy/Xext/mit-shm.PS.gz */
126 XShmSegmentInfo shminfo[POOLSIZE];
127 int lastused;
128 } info;
129
130 static XImage *
131 createximage(Display *dpy, Visual *vis, XShmSegmentInfo *xsi, int depth, int w, int h)
132 {
133 XImage *img;
134 Status status;
135
136 if (!XShmQueryExtension(dpy))
137 goto fallback;
138 if (!info.useshm)
139 goto fallback;
140
141 img = XShmCreateImage(dpy, vis, depth, ZPixmap, NULL, xsi, w, h);
142 if (!img)
143 {
144 fprintf(stderr, "warn: could not XShmCreateImage\n");
145 goto fallback;
146 }
147
148 xsi->shmid = shmget(IPC_PRIVATE,
149 (size_t)img->bytes_per_line * img->height,
150 IPC_CREAT | 0777);
151 if (xsi->shmid < 0)
152 {
153 XDestroyImage(img);
154 fprintf(stderr, "warn: could not shmget\n");
155 goto fallback;
156 }
157
158 img->data = xsi->shmaddr = shmat(xsi->shmid, NULL, 0);
159 if (img->data == (char*)-1)
160 {
161 XDestroyImage(img);
162 fprintf(stderr, "warn: could not shmat\n");
163 goto fallback;
164 }
165
166 xsi->readOnly = False;
167 status = XShmAttach(dpy, xsi);
168 if (!status)
169 {
170 shmdt(xsi->shmaddr);
171 XDestroyImage(img);
172 fprintf(stderr, "warn: could not XShmAttach\n");
173 goto fallback;
174 }
175
176 XSync(dpy, False);
177
178 shmctl(xsi->shmid, IPC_RMID, NULL);
179
180 return img;
181
182 fallback:
183 info.useshm = 0;
184
185 img = XCreateImage(dpy, vis, depth, ZPixmap, 0, NULL, w, h, 32, 0);
186 if (!img)
187 {
188 fprintf(stderr, "fail: could not XCreateImage");
189 abort();
190 }
191
192 img->data = malloc((size_t)h * img->bytes_per_line);
193 if (!img->data)
194 {
195 fprintf(stderr, "fail: could not malloc");
196 abort();
197 }
198
199 return img;
200 }
201
202 static void
203 make_colormap(void)
204 {
205 if (info.visual.class == PseudoColor && info.visual.depth == 8)
206 {
207 int i, r, g, b;
208 i = 0;
209 for (b = 0; b < 4; b++) {
210 for (g = 0; g < 8; g++) {
211 for (r = 0; r < 8; r++) {
212 info.rgbcube[i].pixel = i;
213 info.rgbcube[i].red = (r * 36) << 8;
214 info.rgbcube[i].green = (g * 36) << 8;
215 info.rgbcube[i].blue = (b * 85) << 8;
216 info.rgbcube[i].flags =
217 DoRed | DoGreen | DoBlue;
218 i++;
219 }
220 }
221 }
222 info.colormap = XCreateColormap(info.display,
223 RootWindow(info.display, info.screen),
224 info.visual.visual,
225 AllocAll);
226 XStoreColors(info.display, info.colormap, info.rgbcube, 256);
227 return;
228 }
229 else if (info.visual.class == TrueColor)
230 {
231 info.colormap = 0;
232 return;
233 }
234 fprintf(stderr, "Cannot handle visual class %d with depth: %d\n",
235 info.visual.class, info.visual.depth);
236 return;
237 }
238
239 static void
240 select_mode(void)
241 {
242 int byteorder;
243 int byterev;
244 unsigned long rm, gm, bm;
245 unsigned long rs, gs, bs;
246
247 byteorder = ImageByteOrder(info.display);
248 if (is_big_endian())
249 byterev = byteorder != MSBFirst;
250 else
251 byterev = byteorder != LSBFirst;
252
253 rm = info.visual.red_mask;
254 gm = info.visual.green_mask;
255 bm = info.visual.blue_mask;
256
257 rs = ffs(rm) - 1;
258 gs = ffs(gm) - 1;
259 bs = ffs(bm) - 1;
260
261 #ifdef SHOWINFO
262 printf("ximage: mode %d/%d %08lx %08lx %08lx (%ld,%ld,%ld) %s%s\n",
263 info.visual.depth,
264 info.bitsperpixel,
265 rm, gm, bm, rs, gs, bs,
266 byteorder == MSBFirst ? "msb" : "lsb",
267 byterev ? " <swap>":"");
268 #endif
269
270 info.mode = UNKNOWN;
271 if (info.bitsperpixel == 8) {
272 /* Either PseudoColor with BGR233 colormap, or TrueColor */
273 info.mode = BGR233;
274 }
275 else if (info.bitsperpixel == 16) {
276 if (rm == 0xF800 && gm == 0x07E0 && bm == 0x001F)
277 info.mode = !byterev ? RGB565 : RGB565_BR;
278 if (rm == 0x7C00 && gm == 0x03E0 && bm == 0x001F)
279 info.mode = !byterev ? RGB555 : RGB555_BR;
280 }
281 else if (info.bitsperpixel == 24) {
282 if (rs == 0 && gs == 8 && bs == 16)
283 info.mode = byteorder == MSBFirst ? RGB888 : BGR888;
284 if (rs == 16 && gs == 8 && bs == 0)
285 info.mode = byteorder == MSBFirst ? BGR888 : RGB888;
286 }
287 else if (info.bitsperpixel == 32) {
288 if (rs == 0 && gs == 8 && bs == 16)
289 info.mode = byteorder == MSBFirst ? ABGR8888 : RGBA8888;
290 if (rs == 8 && gs == 16 && bs == 24)
291 info.mode = byteorder == MSBFirst ? BGRA8888 : ARGB8888;
292 if (rs == 16 && gs == 8 && bs == 0)
293 info.mode = byteorder == MSBFirst ? ARGB8888 : BGRA8888;
294 if (rs == 24 && gs == 16 && bs == 8)
295 info.mode = byteorder == MSBFirst ? RGBA8888 : ABGR8888;
296 }
297
298 #ifdef SHOWINFO
299 printf("ximage: RGBA8888 to %s\n", modename[info.mode]);
300 #endif
301
302 /* select conversion function */
303 info.convert_func = ximage_convert_funcs[info.mode];
304 }
305
306 static int
307 create_pool(void)
308 {
309 int i;
310
311 info.lastused = 0;
312
313 for (i = 0; i < POOLSIZE; i++) {
314 info.pool[i] = NULL;
315 }
316
317 for (i = 0; i < POOLSIZE; i++) {
318 info.pool[i] = createximage(info.display,
319 info.visual.visual, &info.shminfo[i], info.visual.depth,
320 WIDTH, HEIGHT);
321 if (!info.pool[i]) {
322 return 0;
323 }
324 }
325
326 return 1;
327 }
328
329 static XImage *
330 next_pool_image(void)
331 {
332 if (info.lastused + 1 >= POOLSIZE) {
333 if (info.useshm)
334 XSync(info.display, False);
335 else
336 XFlush(info.display);
337 info.lastused = 0;
338 }
339 return info.pool[info.lastused ++];
340 }
341
342 static int
343 ximage_error_handler(Display *display, XErrorEvent *event)
344 {
345 /* Turn off shared memory images if we get an error from the MIT-SHM extension */
346 if (event->request_code == info.shmcode)
347 {
348 char buf[80];
349 XGetErrorText(display, event->error_code, buf, sizeof buf);
350 fprintf(stderr, "ximage: disabling shared memory extension: %s\n", buf);
351 info.useshm = 0;
352 return 0;
353 }
354
355 XSetErrorHandler(NULL);
356 return (XSetErrorHandler(ximage_error_handler))(display, event);
357 }
358
359 int
360 ximage_init(Display *display, int screen, Visual *visual)
361 {
362 XVisualInfo template;
363 XVisualInfo *visuals;
364 int nvisuals;
365 XPixmapFormatValues *formats;
366 int nformats;
367 int ok;
368 int i;
369 int major;
370 int event;
371 int error;
372
373 info.display = display;
374 info.screen = screen;
375 info.colormap = 0;
376
377 /* Get XVisualInfo for this visual */
378 template.visualid = XVisualIDFromVisual(visual);
379 visuals = XGetVisualInfo(display, VisualIDMask, &template, &nvisuals);
380 if (nvisuals != 1) {
381 fprintf(stderr, "Visual not found!\n");
382 XFree(visuals);
383 return 0;
384 }
385 memcpy(&info.visual, visuals, sizeof (XVisualInfo));
386 XFree(visuals);
387
388 /* Get appropriate PixmapFormat for this visual */
389 formats = XListPixmapFormats(info.display, &nformats);
390 for (i = 0; i < nformats; i++) {
391 if (formats[i].depth == info.visual.depth) {
392 info.bitsperpixel = formats[i].bits_per_pixel;
393 break;
394 }
395 }
396 XFree(formats);
397 if (i == nformats) {
398 fprintf(stderr, "PixmapFormat not found!\n");
399 return 0;
400 }
401
402 /* extract mode */
403 select_mode();
404
405 /* prepare colormap */
406 make_colormap();
407
408 /* identify code for MIT-SHM extension */
409 if (XQueryExtension(display, "MIT-SHM", &major, &event, &error) &&
410 XShmQueryExtension(display))
411 info.shmcode = major;
412
413 /* intercept errors looking for SHM code */
414 XSetErrorHandler(ximage_error_handler);
415
416 /* prepare pool of XImages */
417 info.useshm = 1;
418 ok = create_pool();
419 if (!ok)
420 return 0;
421
422 #ifdef SHOWINFO
423 printf("ximage: %sPutImage\n", info.useshm ? "XShm" : "X");
424 #endif
425
426 return 1;
427 }
428
429 int
430 ximage_get_depth(void)
431 {
432 return info.visual.depth;
433 }
434
435 Visual *
436 ximage_get_visual(void)
437 {
438 return info.visual.visual;
439 }
440
441 Colormap
442 ximage_get_colormap(void)
443 {
444 return info.colormap;
445 }
446
447 void
448 ximage_blit(Drawable d, GC gc,
449 int dstx, int dsty,
450 unsigned char *srcdata,
451 int srcx, int srcy,
452 int srcw, int srch,
453 int srcstride)
454 {
455 XImage *image;
456 int ax, ay;
457 int w, h;
458 unsigned char *srcptr;
459
460 if (srcw >= (INT_MAX / 4) / srch)
461 {
462 fprintf(stderr, "image size overflow: %d x %d\n", srcw, srch);
463 exit(1);
464 }
465
466 for (ay = 0; ay < srch; ay += HEIGHT)
467 {
468 h = fz_mini(srch - ay, HEIGHT);
469 for (ax = 0; ax < srcw; ax += WIDTH)
470 {
471 w = fz_mini(srcw - ax, WIDTH);
472
473 image = next_pool_image();
474
475 srcptr = srcdata +
476 (ay + srcy) * srcstride +
477 (ax + srcx) * 4;
478
479 info.convert_func(srcptr, srcstride,
480 (unsigned char *) image->data,
481 image->bytes_per_line, w, h);
482
483 if (info.useshm)
484 {
485 XShmPutImage(info.display, d, gc, image,
486 0, 0, dstx + ax, dsty + ay,
487 w, h, False);
488 }
489 else
490 {
491 XPutImage(info.display, d, gc, image,
492 0, 0,
493 dstx + ax,
494 dsty + ay,
495 w, h);
496 }
497 }
498 }
499 }
500
501 /*
502 * Primitive conversion functions
503 */
504
505 #ifndef restrict
506 #ifndef _C99
507 #ifdef __GNUC__
508 #define restrict __restrict__
509 #else
510 #define restrict
511 #endif
512 #endif
513 #endif
514
515 #define PARAMS \
516 const unsigned char * restrict src, \
517 int srcstride, \
518 unsigned char * restrict dst, \
519 int dststride, \
520 int w, \
521 int h
522
523 /*
524 * Convert byte:RGBA8888 to various formats
525 */
526
527 static void
528 ximage_convert_argb8888(PARAMS)
529 {
530 int x, y;
531 for (y = 0; y < h; y++) {
532 for (x = 0; x < w; x ++) {
533 dst[x * 4 + 0] = src[x * 4 + 3]; /* a */
534 dst[x * 4 + 1] = src[x * 4 + 0]; /* r */
535 dst[x * 4 + 2] = src[x * 4 + 1]; /* g */
536 dst[x * 4 + 3] = src[x * 4 + 2]; /* b */
537 }
538 dst += dststride;
539 src += srcstride;
540 }
541 }
542
543 static void
544 ximage_convert_bgra8888(PARAMS)
545 {
546 int x, y;
547 for (y = 0; y < h; y++) {
548 for (x = 0; x < w; x++) {
549 dst[x * 4 + 0] = src[x * 4 + 2];
550 dst[x * 4 + 1] = src[x * 4 + 1];
551 dst[x * 4 + 2] = src[x * 4 + 0];
552 dst[x * 4 + 3] = src[x * 4 + 3];
553 }
554 dst += dststride;
555 src += srcstride;
556 }
557 }
558
559 static void
560 ximage_convert_abgr8888(PARAMS)
561 {
562 int x, y;
563 for (y = 0; y < h; y++) {
564 for (x = 0; x < w; x++) {
565 dst[x * 4 + 0] = src[x * 4 + 3];
566 dst[x * 4 + 1] = src[x * 4 + 2];
567 dst[x * 4 + 2] = src[x * 4 + 1];
568 dst[x * 4 + 3] = src[x * 4 + 0];
569 }
570 dst += dststride;
571 src += srcstride;
572 }
573 }
574
575 static void
576 ximage_convert_rgba8888(PARAMS)
577 {
578 int x, y;
579 for (y = 0; y < h; y++) {
580 for (x = 0; x < w; x++) {
581 ((unsigned *)dst)[x] = ((unsigned *)src)[x];
582 }
583 dst += dststride;
584 src += srcstride;
585 }
586 }
587
588 static void
589 ximage_convert_bgr888(PARAMS)
590 {
591 int x, y;
592 for (y = 0; y < h; y++) {
593 for (x = 0; x < w; x++) {
594 dst[3*x + 0] = src[4*x + 2];
595 dst[3*x + 1] = src[4*x + 1];
596 dst[3*x + 2] = src[4*x + 0];
597 }
598 src += srcstride;
599 dst += dststride;
600 }
601 }
602
603 static void
604 ximage_convert_rgb888(PARAMS)
605 {
606 int x, y;
607 for (y = 0; y < h; y++) {
608 for (x = 0; x < w; x++) {
609 dst[3*x + 0] = src[4*x + 0];
610 dst[3*x + 1] = src[4*x + 1];
611 dst[3*x + 2] = src[4*x + 2];
612 }
613 src += srcstride;
614 dst += dststride;
615 }
616 }
617
618 static void
619 ximage_convert_rgb565(PARAMS)
620 {
621 unsigned char r, g, b;
622 int x, y;
623 for (y = 0; y < h; y++) {
624 for (x = 0; x < w; x++) {
625 r = src[4*x + 0];
626 g = src[4*x + 1];
627 b = src[4*x + 2];
628 ((unsigned short *)dst)[x] =
629 ((r & 0xF8) << 8) |
630 ((g & 0xFC) << 3) |
631 (b >> 3);
632 }
633 src += srcstride;
634 dst += dststride;
635 }
636 }
637
638 static void
639 ximage_convert_rgb565_br(PARAMS)
640 {
641 unsigned char r, g, b;
642 int x, y;
643 for (y = 0; y < h; y++) {
644 for (x = 0; x < w; x++) {
645 r = src[4*x + 0];
646 g = src[4*x + 1];
647 b = src[4*x + 2];
648 /* final word is:
649 g4 g3 g2 b7 b6 b5 b4 b3 : r7 r6 r5 r4 r3 g7 g6 g5
650 */
651 ((unsigned short *)dst)[x] =
652 (r & 0xF8) |
653 ((g & 0xE0) >> 5) |
654 ((g & 0x1C) << 11) |
655 ((b & 0xF8) << 5);
656 }
657 src += srcstride;
658 dst += dststride;
659 }
660 }
661
662 static void
663 ximage_convert_rgb555(PARAMS)
664 {
665 unsigned char r, g, b;
666 int x, y;
667 for (y = 0; y < h; y++) {
668 for (x = 0; x < w; x++) {
669 r = src[4*x + 0];
670 g = src[4*x + 1];
671 b = src[4*x + 2];
672 ((unsigned short *)dst)[x] =
673 ((r & 0xF8) << 7) |
674 ((g & 0xF8) << 2) |
675 (b >> 3);
676 }
677 src += srcstride;
678 dst += dststride;
679 }
680 }
681
682 static void
683 ximage_convert_rgb555_br(PARAMS)
684 {
685 unsigned char r, g, b;
686 int x, y;
687 for (y = 0; y < h; y++) {
688 for (x = 0; x < w; x++) {
689 r = src[4*x + 0];
690 g = src[4*x + 1];
691 b = src[4*x + 2];
692 /* final word is:
693 g5 g4 g3 b7 b6 b5 b4 b3 : 0 r7 r6 r5 r4 r3 g7 g6
694 */
695 ((unsigned short *)dst)[x] =
696 ((r & 0xF8) >> 1) |
697 ((g & 0xC0) >> 6) |
698 ((g & 0x38) << 10) |
699 ((b & 0xF8) << 5);
700 }
701 src += srcstride;
702 dst += dststride;
703 }
704 }
705
706 static void
707 ximage_convert_bgr233(PARAMS)
708 {
709 unsigned char r, g, b;
710 int x,y;
711 for(y = 0; y < h; y++) {
712 for(x = 0; x < w; x++) {
713 r = src[4*x + 0];
714 g = src[4*x + 1];
715 b = src[4*x + 2];
716 /* format: b7 b6 g7 g6 g5 r7 r6 r5 */
717 dst[x] = (b&0xC0) | ((g>>2)&0x38) | ((r>>5)&0x7);
718 }
719 src += srcstride;
720 dst += dststride;
721 }
722 }
723
724 static void
725 ximage_convert_generic(PARAMS)
726 {
727 unsigned long rm, gm, bm, rs, gs, bs, rx, bx, gx;
728 unsigned long pixel;
729 unsigned long r, g, b;
730 int x, y;
731
732 rm = info.visual.red_mask;
733 gm = info.visual.green_mask;
734 bm = info.visual.blue_mask;
735
736 rs = ffs(rm) - 1;
737 gs = ffs(gm) - 1;
738 bs = ffs(bm) - 1;
739
740 rx = ffs(~(rm >> rs)) - 1;
741 gx = ffs(~(gm >> gs)) - 1;
742 bx = ffs(~(bm >> bs)) - 1;
743
744 for (y = 0; y < h; y++) {
745 for (x = 0; x < w; x++) {
746 r = src[4*x + 0];
747 g = src[4*x + 1];
748 b = src[4*x + 2];
749
750 /* adjust precision */
751 if (rx < 8) r >>= (8 - rx); else if (rx > 8) r <<= (rx - 8);
752 if (gx < 8) g >>= (8 - gx); else if (gx > 8) g <<= (gx - 8);
753 if (bx < 8) b >>= (8 - bx); else if (bx > 8) b <<= (bx - 8);
754
755 pixel = (r << rs) | (g << gs) | (b << bs);
756
757 if (ImageByteOrder(info.display) == MSBFirst) {
758 if (info.bitsperpixel > 16) {
759 dst[4*x + 0] = (pixel >> 24) & 0xFF;
760 dst[4*x + 1] = (pixel >> 16) & 0xFF;
761 dst[4*x + 2] = (pixel >> 8) & 0xFF;
762 dst[4*x + 3] = (pixel) & 0xFF;
763 } else if (info.bitsperpixel > 8) {
764 dst[2*x + 0] = (pixel >> 8) & 0xFF;
765 dst[2*x + 1] = (pixel) & 0xFF;
766 }
767 } else {
768 if (info.bitsperpixel > 16) {
769 dst[4*x + 0] = (pixel) & 0xFF;
770 dst[4*x + 1] = (pixel >> 8) & 0xFF;
771 dst[4*x + 2] = (pixel >> 16) & 0xFF;
772 dst[4*x + 3] = (pixel >> 24) & 0xFF;
773 } else if (info.bitsperpixel > 8) {
774 dst[2*x + 0] = (pixel) & 0xFF;
775 dst[2*x + 1] = (pixel >> 8) & 0xFF;
776 }
777 }
778 }
779 src += srcstride;
780 dst += dststride;
781 }
782 }
783
784 ximage_convert_func_t ximage_convert_funcs[] = {
785 ximage_convert_argb8888,
786 ximage_convert_bgra8888,
787 ximage_convert_rgba8888,
788 ximage_convert_abgr8888,
789 ximage_convert_rgb888,
790 ximage_convert_bgr888,
791 ximage_convert_rgb565,
792 ximage_convert_rgb565_br,
793 ximage_convert_rgb555,
794 ximage_convert_rgb555_br,
795 ximage_convert_bgr233,
796 ximage_convert_generic
797 };