istereo2

view libs/drawtext/font.c @ 35:643f4ab609a4

added readme and license
author John Tsiombikas <nuclear@member.fsf.org>
date Sat, 31 Oct 2015 05:45:35 +0200
parents a3c4fcc9f8f3
children
line source
1 /*
2 libdrawtext - a simple library for fast text rendering in OpenGL
3 Copyright (C) 2011-2014 John Tsiombikas <nuclear@member.fsf.org>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 #ifndef NO_FREETYPE
19 #define USE_FREETYPE
20 #endif
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <math.h>
26 #include <limits.h>
27 #include <ctype.h>
28 #include <float.h>
29 #include <errno.h>
30 #ifdef USE_FREETYPE
31 #include <ft2build.h>
32 #include FT_FREETYPE_H
33 #endif
34 #include "assman.h"
35 #include "drawtext.h"
36 #include "drawtext_impl.h"
38 #define FTSZ_TO_PIXELS(x) ((x) / 64)
39 #define MAX_IMG_WIDTH 4096
41 struct dtx_font *dtx_font;
42 int dtx_font_sz;
45 #ifdef USE_FREETYPE
46 static int init_freetype(void);
47 static void cleanup(void);
49 static void calc_best_size(int total_width, int max_gwidth, int max_gheight,
50 int padding, int pow2, int *imgw, int *imgh);
51 static int next_pow2(int x);
53 static FT_Library ft;
56 static int init_done;
58 static int init_freetype(void)
59 {
60 if(!init_done) {
61 if(FT_Init_FreeType(&ft) != 0) {
62 return -1;
63 }
64 atexit(cleanup);
65 init_done = 1;
66 }
67 return 0;
68 }
70 static void cleanup(void)
71 {
72 if(init_done) {
73 FT_Done_FreeType(ft);
74 }
75 }
76 #endif /* USE_FREETYPE */
78 struct dtx_font *dtx_open_font(const char *fname, int sz)
79 {
80 struct dtx_font *fnt = 0;
82 #ifdef USE_FREETYPE
83 init_freetype();
85 if(!(fnt = calloc(1, sizeof *fnt))) {
86 fperror("failed to allocate font structure");
87 return 0;
88 }
90 if(FT_New_Face(ft, fname, 0, (FT_Face*)&fnt->face) != 0) {
91 fprintf(stderr, "failed to open font file: %s\n", fname);
92 return 0;
93 }
95 /* pre-create the extended ASCII range glyphmap */
96 if(sz) {
97 dtx_prepare_range(fnt, sz, 0, 256);
99 if(!dtx_font) {
100 dtx_use_font(fnt, sz);
101 }
102 }
103 #else
104 fprintf(stderr, "ignoring call to dtx_open_font: not compiled with freetype support!\n");
105 #endif
107 return fnt;
108 }
110 struct dtx_font *dtx_open_font_glyphmap(const char *fname)
111 {
112 struct dtx_font *fnt;
113 struct dtx_glyphmap *gmap;
115 if(!(fnt = calloc(1, sizeof *fnt))) {
116 fperror("failed to allocate font structure");
117 return 0;
118 }
120 if(fname) {
121 if(!(gmap = dtx_load_glyphmap(fname))) {
122 free(fnt);
123 return 0;
124 }
126 dtx_add_glyphmap(fnt, gmap);
128 if(!dtx_font) {
129 dtx_use_font(fnt, gmap->ptsize);
130 }
131 }
132 return fnt;
133 }
135 void dtx_close_font(struct dtx_font *fnt)
136 {
137 if(!fnt) return;
139 #ifdef USE_FREETYPE
140 FT_Done_Face(fnt->face);
141 #endif
143 /* destroy the glyphmaps */
144 while(fnt->gmaps) {
145 void *tmp = fnt->gmaps;
146 fnt->gmaps = fnt->gmaps->next;
147 dtx_free_glyphmap(tmp);
148 }
150 free(fnt);
151 }
153 void dtx_prepare(struct dtx_font *fnt, int sz)
154 {
155 if(!dtx_get_font_glyphmap_range(fnt, sz, 0, 256)) {
156 fprintf(stderr, "%s: failed (sz: %d, range: 0-255 [ascii])\n", __func__, sz);
157 }
158 }
160 void dtx_prepare_range(struct dtx_font *fnt, int sz, int cstart, int cend)
161 {
162 if(!dtx_get_font_glyphmap_range(fnt, sz, cstart, cend)) {
163 fprintf(stderr, "%s: failed (sz: %d, range: %d-%d)\n", __func__, sz, cstart, cend);
164 }
165 }
167 int dtx_get_font_glyphmap_count(struct dtx_font *fnt)
168 {
169 return fnt->num_gmaps;
170 }
172 struct dtx_glyphmap *dtx_get_font_glyphmap_idx(struct dtx_font *fnt, int idx)
173 {
174 int i;
175 struct dtx_glyphmap *gm = fnt->gmaps;
177 for(i=0; i<idx; i++) {
178 gm = gm->next;
179 if(!gm) return 0;
180 }
181 return gm;
182 }
184 struct dtx_glyphmap *dtx_get_font_glyphmap(struct dtx_font *fnt, int sz, int code)
185 {
186 struct dtx_glyphmap *gm;
188 /* check to see if the last we've given out fits the bill */
189 if(fnt->last_gmap && code >= fnt->last_gmap->cstart && code < fnt->last_gmap->cend && fnt->last_gmap->ptsize == sz) {
190 return fnt->last_gmap;
191 }
193 /* otherwise search for the appropriate glyphmap */
194 gm = fnt->gmaps;
195 while(gm) {
196 if(code >= gm->cstart && code < gm->cend && sz == gm->ptsize) {
197 fnt->last_gmap = gm;
198 return gm;
199 }
200 gm = gm->next;
201 }
202 return 0;
203 }
205 struct dtx_glyphmap *dtx_get_font_glyphmap_range(struct dtx_font *fnt, int sz, int cstart, int cend)
206 {
207 struct dtx_glyphmap *gm;
209 /* search the available glyphmaps to see if we've got one that includes
210 * the requested range
211 */
212 gm = fnt->gmaps;
213 while(gm) {
214 if(gm->cstart <= cstart && gm->cend >= cend && gm->ptsize == sz) {
215 return gm;
216 }
217 gm = gm->next;
218 }
220 /* not found, create one and add it to the list */
221 if(!(gm = dtx_create_glyphmap_range(fnt, sz, cstart, cend))) {
222 return 0;
223 }
224 return gm;
225 }
227 struct dtx_glyphmap *dtx_create_glyphmap_range(struct dtx_font *fnt, int sz, int cstart, int cend)
228 {
229 struct dtx_glyphmap *gmap = 0;
231 #ifdef USE_FREETYPE
232 FT_Face face = fnt->face;
233 int i, j;
234 int gx, gy;
235 int padding = 4;
236 int total_width, max_width, max_height;
238 FT_Set_Char_Size(fnt->face, 0, sz * 64, 72, 72);
240 if(!(gmap = calloc(1, sizeof *gmap))) {
241 return 0;
242 }
244 gmap->ptsize = sz;
245 gmap->cstart = cstart;
246 gmap->cend = cend;
247 gmap->crange = cend - cstart;
248 gmap->line_advance = FTSZ_TO_PIXELS((float)face->size->metrics.height);
250 if(!(gmap->glyphs = malloc(gmap->crange * sizeof *gmap->glyphs))) {
251 free(gmap);
252 return 0;
253 }
255 total_width = padding;
256 max_width = max_height = 0;
258 for(i=0; i<gmap->crange; i++) {
259 int w, h;
261 FT_Load_Char(face, i + cstart, 0);
262 w = FTSZ_TO_PIXELS(face->glyph->metrics.width);
263 h = FTSZ_TO_PIXELS(face->glyph->metrics.height);
265 if(w > max_width) max_width = w;
266 if(h > max_height) max_height = h;
268 total_width += w + padding;
269 }
271 calc_best_size(total_width, max_width, max_height, padding, 1, &gmap->xsz, &gmap->ysz);
273 if(!(gmap->pixels = malloc(gmap->xsz * gmap->ysz))) {
274 free(gmap->glyphs);
275 free(gmap);
276 return 0;
277 }
278 memset(gmap->pixels, 0, gmap->xsz * gmap->ysz);
280 gx = padding;
281 gy = padding;
283 for(i=0; i<gmap->crange; i++) {
284 float gwidth, gheight;
285 unsigned char *src, *dst;
286 FT_GlyphSlot glyph;
288 FT_Load_Char(face, i + cstart, FT_LOAD_RENDER);
289 glyph = face->glyph;
290 gwidth = FTSZ_TO_PIXELS((float)glyph->metrics.width);
291 gheight = FTSZ_TO_PIXELS((float)glyph->metrics.height);
293 if(gx > gmap->xsz - gwidth - padding) {
294 gx = padding;
295 gy += max_height + padding;
296 }
298 src = glyph->bitmap.buffer;
299 dst = gmap->pixels + gy * gmap->xsz + gx;
301 for(j=0; j<glyph->bitmap.rows; j++) {
302 memcpy(dst, src, glyph->bitmap.width);
303 dst += gmap->xsz;
304 src += glyph->bitmap.pitch;
305 }
307 gmap->glyphs[i].code = i;
308 gmap->glyphs[i].x = gx - 1;
309 gmap->glyphs[i].y = gy - 1;
310 gmap->glyphs[i].width = gwidth + 2;
311 gmap->glyphs[i].height = gheight + 2;
312 gmap->glyphs[i].orig_x = -FTSZ_TO_PIXELS((float)glyph->metrics.horiBearingX) + 1;
313 gmap->glyphs[i].orig_y = FTSZ_TO_PIXELS((float)glyph->metrics.height - glyph->metrics.horiBearingY) + 1;
314 gmap->glyphs[i].advance = FTSZ_TO_PIXELS((float)glyph->metrics.horiAdvance);
315 /* also precalc normalized */
316 gmap->glyphs[i].nx = (float)gmap->glyphs[i].x / (float)gmap->xsz;
317 gmap->glyphs[i].ny = (float)gmap->glyphs[i].y / (float)gmap->ysz;
318 gmap->glyphs[i].nwidth = (float)gmap->glyphs[i].width / (float)gmap->xsz;
319 gmap->glyphs[i].nheight = (float)gmap->glyphs[i].height / (float)gmap->ysz;
321 gx += gwidth + padding;
322 }
324 /* add it to the glyphmaps list of the font */
325 dtx_add_glyphmap(fnt, gmap);
326 #endif /* USE_FREETYPE */
328 return gmap;
329 }
331 void dtx_free_glyphmap(struct dtx_glyphmap *gmap)
332 {
333 if(gmap) {
334 free(gmap->pixels);
335 free(gmap->glyphs);
336 free(gmap);
337 }
338 }
340 unsigned char *dtx_get_glyphmap_pixels(struct dtx_glyphmap *gmap)
341 {
342 return gmap->pixels;
343 }
345 int dtx_get_glyphmap_ptsize(struct dtx_glyphmap *gmap)
346 {
347 return gmap->ptsize;
348 }
350 int dtx_get_glyphmap_width(struct dtx_glyphmap *gmap)
351 {
352 return gmap->xsz;
353 }
355 int dtx_get_glyphmap_height(struct dtx_glyphmap *gmap)
356 {
357 return gmap->ysz;
358 }
360 struct dtx_glyphmap *dtx_load_glyphmap(const char *fname)
361 {
362 ass_file *fp;
363 struct dtx_glyphmap *gmap;
365 if(!(fp = ass_fopen(fname, "rb"))) {
366 return 0;
367 }
368 gmap = dtx_load_glyphmap_asset(fp);
369 ass_fclose(fp);
370 return gmap;
371 }
373 struct dtx_glyphmap *dtx_load_glyphmap_asset(ass_file *fp)
374 {
375 char buf[512];
376 int hdr_lines = 0;
377 struct dtx_glyphmap *gmap;
378 struct glyph *glyphs = 0;
379 struct glyph *g;
380 int min_code = INT_MAX;
381 int max_code = INT_MIN;
382 int i, max_pixval, num_pixels;
384 if(!(gmap = calloc(1, sizeof *gmap))) {
385 fperror("failed to allocate glyphmap");
386 return 0;
387 }
388 gmap->ptsize = -1;
389 gmap->line_advance = FLT_MIN;
391 while(hdr_lines < 3) {
392 char *line = buf;
393 if(!ass_fgets(buf, sizeof buf, fp)) {
394 fperror("unexpected end of file");
395 goto err;
396 }
398 while(isspace(*line)) {
399 line++;
400 }
402 if(line[0] == '#') {
403 int c, res;
404 float x, y, xsz, ysz, orig_x, orig_y, adv, line_adv;
405 int ptsize;
407 if((res = sscanf(line + 1, " size: %d\n", &ptsize)) == 1) {
408 gmap->ptsize = ptsize;
410 } else if((res = sscanf(line + 1, " advance: %f\n", &line_adv)) == 1) {
411 gmap->line_advance = line_adv;
413 } else if((res = sscanf(line + 1, " %d: %fx%f+%f+%f o:%f,%f adv:%f\n",
414 &c, &xsz, &ysz, &x, &y, &orig_x, &orig_y, &adv)) == 8) {
415 if(!(g = malloc(sizeof *g))) {
416 fperror("failed to allocate glyph");
417 goto err;
418 }
419 g->code = c;
420 g->x = x;
421 g->y = y;
422 g->width = xsz;
423 g->height = ysz;
424 g->orig_x = orig_x;
425 g->orig_y = orig_y;
426 g->advance = adv;
427 /* normalized coordinates will be precalculated after everything is loaded */
429 g->next = glyphs;
430 glyphs = g;
432 if(c < min_code) {
433 min_code = c;
434 }
435 if(c > max_code) {
436 max_code = c;
437 }
439 } else {
440 fprintf(stderr, "%s: invalid glyph info line\n", __func__);
441 goto err;
442 }
444 } else {
445 switch(hdr_lines) {
446 case 0:
447 if(0[line] != 'P' || 1[line] != '6') {
448 fprintf(stderr, "%s: invalid file format (magic)\n", __func__);
449 goto err;
450 }
451 break;
453 case 1:
454 if(sscanf(line, "%d %d", &gmap->xsz, &gmap->ysz) != 2) {
455 fprintf(stderr, "%s: invalid file format (dim)\n", __func__);
456 goto err;
457 }
458 break;
460 case 2:
461 {
462 char *endp;
463 max_pixval = strtol(line, &endp, 10);
464 if(endp == line) {
465 fprintf(stderr, "%s: invalid file format (maxval)\n", __func__);
466 goto err;
467 }
468 }
469 break;
471 default:
472 break; /* can't happen */
473 }
474 hdr_lines++;
475 }
476 }
478 if(gmap->ptsize == -1 || gmap->line_advance == FLT_MIN) {
479 fprintf(stderr, "%s: invalid glyphmap, insufficient information in ppm comments\n", __func__);
480 goto err;
481 }
483 /* precalculate normalized glyph coordinates */
484 g = glyphs;
485 while(g) {
486 g->nx = g->x / gmap->xsz;
487 g->ny = g->y / gmap->ysz;
488 g->nwidth = g->width / gmap->xsz;
489 g->nheight = g->height / gmap->ysz;
490 g = g->next;
491 }
493 num_pixels = gmap->xsz * gmap->ysz;
494 if(!(gmap->pixels = malloc(num_pixels))) {
495 fperror("failed to allocate pixels");
496 goto err;
497 }
499 for(i=0; i<num_pixels; i++) {
500 int c = ass_fgetc(fp);
501 if(c == -1) {
502 fprintf(stderr, "unexpected end of file while reading pixels (%d/%d)\n", i, num_pixels);
503 goto err;
504 }
505 gmap->pixels[i] = 255 * c / max_pixval;
506 ass_fseek(fp, 2, SEEK_CUR);
507 }
509 gmap->cstart = min_code;
510 gmap->cend = max_code + 1;
511 gmap->crange = gmap->cend - gmap->cstart;
513 if(!(gmap->glyphs = calloc(gmap->crange, sizeof *gmap->glyphs))) {
514 fperror("failed to allocate glyph info");
515 goto err;
516 }
518 while(glyphs) {
519 struct glyph *g = glyphs;
520 glyphs = glyphs->next;
522 gmap->glyphs[g->code - gmap->cstart] = *g;
523 free(g);
524 }
525 return gmap;
527 err:
528 dtx_free_glyphmap(gmap);
529 while(glyphs) {
530 void *tmp = glyphs;
531 glyphs = glyphs->next;
532 free(tmp);
533 }
534 return 0;
535 }
537 int dtx_save_glyphmap(const char *fname, const struct dtx_glyphmap *gmap)
538 {
539 FILE *fp;
540 int res;
542 if(!(fp = fopen(fname, "wb"))) {
543 fprintf(stderr, "%s: failed to open file: %s: %s\n", __func__, fname, strerror(errno));
544 return -1;
545 }
546 res = dtx_save_glyphmap_stream(fp, gmap);
547 fclose(fp);
548 return res;
549 }
551 int dtx_save_glyphmap_stream(FILE *fp, const struct dtx_glyphmap *gmap)
552 {
553 int i, num_pixels;
554 struct glyph *g = gmap->glyphs;
556 fprintf(fp, "P6\n%d %d\n", gmap->xsz, gmap->ysz);
557 fprintf(fp, "# size: %d\n", gmap->ptsize);
558 fprintf(fp, "# advance: %g\n", gmap->line_advance);
559 for(i=0; i<gmap->crange; i++) {
560 fprintf(fp, "# %d: %gx%g+%g+%g o:%g,%g adv:%g\n", g->code, g->width, g->height, g->x, g->y,
561 g->orig_x, g->orig_y, g->advance);
562 g++;
563 }
564 fprintf(fp, "255\n");
566 num_pixels = gmap->xsz * gmap->ysz;
567 for(i=0; i<num_pixels; i++) {
568 int c = gmap->pixels[i];
569 fputc(c, fp);
570 fputc(c, fp);
571 fputc(c, fp);
572 }
573 return 0;
574 }
576 void dtx_add_glyphmap(struct dtx_font *fnt, struct dtx_glyphmap *gmap)
577 {
578 gmap->next = fnt->gmaps;
579 fnt->gmaps = gmap;
580 fnt->num_gmaps++;
581 }
584 void dtx_use_font(struct dtx_font *fnt, int sz)
585 {
586 dtx_gl_init();
588 dtx_font = fnt;
589 dtx_font_sz = sz;
590 }
592 float dtx_line_height(void)
593 {
594 struct dtx_glyphmap *gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, '\n');
596 return gmap->line_advance;
597 }
599 void dtx_glyph_box(int code, struct dtx_box *box)
600 {
601 int cidx;
602 struct dtx_glyphmap *gmap;
603 gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code);
605 cidx = code - gmap->cstart;
607 box->x = gmap->glyphs[cidx].orig_x;
608 box->y = gmap->glyphs[cidx].orig_y;
609 box->width = gmap->glyphs[cidx].width;
610 box->height = gmap->glyphs[cidx].height;
611 }
613 float dtx_glyph_width(int code)
614 {
615 struct dtx_box box;
616 dtx_glyph_box(code, &box);
617 return box.width;
618 }
620 float dtx_glyph_height(int code)
621 {
622 struct dtx_box box;
623 dtx_glyph_box(code, &box);
624 return box.height;
625 }
627 void dtx_string_box(const char *str, struct dtx_box *box)
628 {
629 int code;
630 float pos_x = 0.0f, pos_y = 0.0f;
631 struct glyph *g = 0;
632 float x0, y0, x1, y1;
634 x0 = y0 = FLT_MAX;
635 x1 = y1 = -FLT_MAX;
637 while(*str) {
638 float px, py;
639 struct dtx_glyphmap *gmap;
641 code = dtx_utf8_char_code(str);
642 str = dtx_utf8_next_char((char*)str);
644 px = pos_x;
645 py = pos_y;
647 if((gmap = dtx_proc_char(code, &pos_x, &pos_y))) {
648 g = gmap->glyphs + code - gmap->cstart;
650 if(px + g->orig_x < x0) {
651 x0 = px + g->orig_x;
652 }
653 if(py - g->orig_y < y0) {
654 y0 = py - g->orig_y;
655 }
656 if(px + g->orig_x + g->width > x1) {
657 x1 = px + g->orig_x + g->width;
658 }
659 if(py - g->orig_y + g->height > y1) {
660 y1 = py - g->orig_y + g->height;
661 }
662 }
663 }
665 box->x = x0;
666 box->y = y0;
667 box->width = x1 - x0;
668 box->height = y1 - y0;
669 }
671 float dtx_string_width(const char *str)
672 {
673 struct dtx_box box;
675 dtx_string_box(str, &box);
676 return box.width;
677 }
679 float dtx_string_height(const char *str)
680 {
681 struct dtx_box box;
683 dtx_string_box(str, &box);
684 return box.height;
685 }
687 float dtx_char_pos(const char *str, int n)
688 {
689 int i;
690 float pos = 0.0;
691 struct dtx_glyphmap *gmap;
693 for(i=0; i<n; i++) {
694 int code = dtx_utf8_char_code(str);
695 str = dtx_utf8_next_char((char*)str);
697 gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code);
698 pos += gmap->glyphs[i].advance;
699 }
700 return pos;
701 }
703 int dtx_char_at_pt(const char *str, float pt)
704 {
705 int i;
706 float prev_pos = 0.0f, pos = 0.0f;
707 struct dtx_glyphmap *gmap;
709 for(i=0; *str; i++) {
710 int code = dtx_utf8_char_code(str);
711 str = dtx_utf8_next_char((char*)str);
713 gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code);
714 pos += gmap->glyphs[i].advance;
716 if(fabs(pt - prev_pos) < fabs(pt - pos)) {
717 break;
718 }
719 prev_pos = pos;
720 }
721 return i;
722 }
724 struct dtx_glyphmap *dtx_proc_char(int code, float *xpos, float *ypos)
725 {
726 struct dtx_glyphmap *gmap;
727 gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code);
729 switch(code) {
730 case '\n':
731 *xpos = 0.0;
732 if(gmap) {
733 *ypos -= gmap->line_advance;
734 }
735 return 0;
737 case '\t':
738 if(gmap) {
739 *xpos = (fmod(*xpos, 4.0) + 4.0) * gmap->glyphs[0].advance;
740 }
741 return 0;
743 case '\r':
744 *xpos = 0.0;
745 return 0;
747 default:
748 break;
749 }
751 if(gmap) {
752 *xpos += gmap->glyphs[code - gmap->cstart].advance;
753 }
754 return gmap;
755 }
757 #ifdef USE_FREETYPE
758 static void calc_best_size(int total_width, int max_gwidth, int max_gheight, int padding, int pow2, int *imgw, int *imgh)
759 {
760 int xsz, ysz, num_rows;
761 float aspect;
763 for(xsz=2; xsz<=MAX_IMG_WIDTH; xsz *= 2) {
764 num_rows = total_width / xsz + 1;
766 /* assume worst case, all last glyphs will float to the next line
767 * so let's add extra rows for that. */
768 num_rows += (padding + (max_gwidth + padding) * num_rows + xsz - 1) / xsz;
770 ysz = num_rows * (max_gheight + padding) + padding;
771 if(pow2) {
772 ysz = next_pow2(ysz);
773 }
774 aspect = (float)xsz / (float)ysz;
776 if(aspect >= 1.0) {
777 break;
778 }
779 }
781 if(xsz > MAX_IMG_WIDTH) {
782 xsz = MAX_IMG_WIDTH;
783 }
785 *imgw = xsz;
786 *imgh = ysz;
787 }
790 static int next_pow2(int x)
791 {
792 x--;
793 x = (x >> 1) | x;
794 x = (x >> 2) | x;
795 x = (x >> 4) | x;
796 x = (x >> 8) | x;
797 x = (x >> 16) | x;
798 return x + 1;
799 }
800 #endif