1 /**
2  *
3  * $Header: /cvsroot/lesstif/lesstif/lib/Xm-2.1/RenderTable.c,v 1.6 2005/06/24 11:36:03 dannybackx Exp $
4  *
5  * Copyright (C) 1998 Free Software Foundation, Inc.
6  * Copyright � 1998, 2000, 2001, 2002, 2004, 2005 LessTif Development Team
7  *
8  * This file is part of the GNU LessTif Library.
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public
21  * License along with this library; if not, write to the Free
22  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23  *
24  **/
25 
26 static const char rcsid[] = "$Header: /cvsroot/lesstif/lesstif/lib/Xm-2.1/RenderTable.c,v 1.6 2005/06/24 11:36:03 dannybackx Exp $";
27 
28 #include <LTconfig.h>
29 
30 #include <string.h>
31 
32 #include <Xm/XmP.h>
33 #include <XmI/XmI.h>
34 
35 #include <XmI/DebugUtil.h>
36 
37 #ifdef	USE_XFT
38 #include <X11/Xft/Xft.h>
39 #endif
40 
_XmRenditionCopy(XmRendition s)41 static XmRendition _XmRenditionCopy(XmRendition s)
42 {
43 	s->count++;
44 #if 0
45 	DEBUGOUT(_LtDebug(__FILE__, NULL, "_XmRenditionCopy(%p) rc %d\n",
46 		s, s->count));
47 #endif
48 	return s;
49 }
50 
_XmRenditionFree(XmRendition r)51 static void _XmRenditionFree(XmRendition r)
52 {
53 	_XmFontListEntryFree(r);
54 }
55 
56 extern XmRenderTable
XmRenderTableAddRenditions(XmRenderTable oldtable,XmRendition * renditions,Cardinal rendition_count,XmMergeMode merge_mode)57 XmRenderTableAddRenditions(XmRenderTable oldtable,
58                            XmRendition  *renditions,
59                            Cardinal rendition_count,
60                            XmMergeMode merge_mode)
61 {
62 	/* FIX ME simplistic */
63 	int		count, i, off;
64 	XmRenderTable	r;
65 
66 	off = oldtable ? oldtable->count : 0;
67 	count = off + rendition_count;
68 	r = __XmFontListAlloc(count);
69 
70 	if (oldtable)
71 		for (i=0; i<oldtable->count; i++)
72 			r->renditions[i] = _XmRenditionCopy(oldtable->renditions[i]);
73 	for (i=0; i<rendition_count; i++)
74 		r->renditions[i+off] = _XmRenditionCopy(renditions[i]);
75 
76 	r->dpy = r->renditions[0]->dpy;
77 	return r;
78 }
79 
_XmRenderTablePushRendition(XmRenderTable old,XmRendition rend)80 XmRenderTable _XmRenderTablePushRendition(XmRenderTable old, XmRendition rend)
81 {
82 	int		count, i, off;
83 	XmRenderTable	r;
84 
85 	off = old ? old->count : 0;
86 	count = off + 1;
87 	r = __XmFontListAlloc(count);
88 
89 	r->renditions[0] = _XmRenditionCopy(rend);
90 	for (i=0; i<off; i++)
91 		r->renditions[i+1] = _XmRenditionCopy(old->renditions[i]);
92 
93 	r->dpy = r->renditions[0]->dpy;
94 	r->count = count;
95 	return r;
96 }
97 
_XmRenderTablePopRendition(XmRenderTable old,XmRendition rend)98 XmRenderTable _XmRenderTablePopRendition(XmRenderTable old, XmRendition rend)
99 {
100 	XmRenderTable	r;
101 	int		i;
102 
103 	if (!old)
104 		return NULL;
105 	r = __XmFontListAlloc(old->count - 1);
106 	for (i=1; i<old->count; i++)
107 		r->renditions[i-1] = _XmRenditionCopy(old->renditions[i]);
108 	r->dpy = old->dpy;
109 	r->count = old->count - 1;
110 	return r;
111 }
112 
113 /*
114  * Be sure to maintain the same allocation as XmFontList
115  *
116  * NULL tag means copy all renditions.
117  */
118 extern XmRenderTable
XmRenderTableCopy(XmRenderTable table,XmStringTag * tags,int tag_count)119 XmRenderTableCopy(XmRenderTable table, XmStringTag *tags, int tag_count)
120 {
121 	int		i, j, k, count;
122 	XmRenderTable	r;
123 
124 	if (table == NULL)
125 		return NULL;
126 
127 	if (!tags) {
128 		/* Allocate & copy */
129 		r = __XmFontListAlloc(table->count);
130 		for (i=0; i < table->count; i++)
131 			r->renditions[i] = _XmRenditionCopy(table->renditions[i]);
132 		r->count = table->count;
133 		r->dpy = table->dpy;
134 		return r;
135 	}
136 	for (i=0, count=0; i<table->count; i++) {
137 		for (j=0; j<tag_count; j++)
138 			if (strcmp(table->renditions[i]->tag, tags[j]) == 0) {
139 				count++;
140 				j = tag_count;	/* end loop */
141 			}
142 	}
143 
144 	/* Allocate & copy */
145 	r = __XmFontListAlloc(count);
146 
147 	for (i=0, j=0; i < table->count; i++)
148 		for (k=0; k<tag_count; k++) {
149 			if (strcmp(table->renditions[i]->tag, tags[k]) == 0) {
150 				r->renditions[j] = _XmRenditionCopy(table->renditions[i]);
151 				j++;
152 				/* force end of internal loop */
153 				k = tag_count;
154 			}
155 		}
156 	r->count = count;
157 	r->dpy = table->dpy;
158 	return r;
159 }
160 
161 
162 extern XmRenderTable
XmRenderTableCvtFromProp(Widget widget,char * property,unsigned int length)163 XmRenderTableCvtFromProp(Widget widget,
164                          char *property,
165                          unsigned int length)
166 {
167 	return NULL;
168 }
169 
170 
171 extern unsigned int
XmRenderTableCvtToProp(Widget widget,XmRenderTable table,char ** prop_return)172 XmRenderTableCvtToProp(Widget widget,
173                        XmRenderTable table,
174                        char **prop_return)
175 {
176 	return 0;
177 }
178 
179 
180 extern void
XmRenderTableFree(XmRenderTable table)181 XmRenderTableFree(XmRenderTable table)
182 {
183 	XmFontListFree(table);
184 }
185 
186 extern XmRendition
XmRenderTableGetRendition(XmRenderTable table,XmStringTag tag)187 XmRenderTableGetRendition(XmRenderTable table, XmStringTag tag)
188 {
189 	int	i;
190 
191 	for (i=0; i<table->count; i++) {
192 		if (tag == table->renditions[i]->tag)
193 			return _XmRenditionCopy(table->renditions[i]);
194 		else if (tag && table->renditions[i]->tag &&
195 				strcmp(table->renditions[i]->tag, tag) == 0)
196 			return _XmRenditionCopy(table->renditions[i]);
197 	}
198 	return NULL;
199 }
200 
201 #ifdef	USE_XFT
202 extern XmRendition
XmeRenderTableGetXftRendition(XmRenderTable table)203 XmeRenderTableGetXftRendition(XmRenderTable table)
204 {
205 	if (table == NULL || table->count == 0)
206 		return NULL;
207 	return _XmRenditionCopy(table->renditions[0]);
208 }
209 #endif
210 
211 extern XmRendition *
XmRenderTableGetRenditions(XmRenderTable table,XmStringTag * tags,Cardinal tag_count)212 XmRenderTableGetRenditions(XmRenderTable table,
213                            XmStringTag *tags,
214                            Cardinal tag_count)
215 {
216 	return NULL;
217 }
218 
219 
XmRenderTableGetTags(XmRenderTable table,XmStringTag ** tag_list)220 extern int XmRenderTableGetTags(XmRenderTable table, XmStringTag **tag_list)
221 {
222 	return 0;
223 }
224 
225 /*
226  * This function deallocates the original render table and the matching
227  * renditions after extracting the required information.
228  */
229 extern XmRenderTable
XmRenderTableRemoveRenditions(XmRenderTable table,XmStringTag * tags,int tag_count)230 XmRenderTableRemoveRenditions(XmRenderTable table, XmStringTag *tags, int tag_count)
231 {
232 	int		i, j, k, count, match;
233 	XmRenderTable	r;
234 
235 	count = table->count;
236 	if (tags) {
237 		for (i=0; i<table->count; i++) {
238 			for (j=0; j<tag_count; j++)
239 				if (strcmp(table->renditions[i]->tag, tags[j]) == 0) {
240 					count--;
241 					j = tag_count;	/* end loop */
242 				}
243 		}
244 	}
245 
246 	/* Allocate & copy */
247 	r = __XmFontListAlloc(count);
248 
249 	for (i=0, j=0; i < table->count; i++) {
250 		for (match=0, k=0; k<tag_count && match == 0; k++)
251 			if (strcmp(table->renditions[i]->tag, tags[k]) == 0)
252 				match = 1;
253 		if (match)
254 			continue;
255 
256 		r->renditions[j] = _XmRenditionCopy(table->renditions[i]);
257 		j++;
258 		/* force end of internal loop */
259 		k = tag_count;
260 	}
261 	r->count = count;
262 
263 	return r;
264 }
265 
266 extern Boolean
XmeRenderTableGetDefaultFont(XmRenderTable renderTable,XFontStruct ** fontStruct)267 XmeRenderTableGetDefaultFont(XmRenderTable renderTable,
268 			     XFontStruct **fontStruct)
269 {
270 	return False;
271 }
272 
273 /*
274  * Note that this uses the same structure as a XmFontListEntry.
275  */
276 extern XmRendition
XmRenditionCreate(Widget widget,XmStringTag tag,ArgList al,Cardinal ac)277 XmRenditionCreate(Widget widget, XmStringTag tag, ArgList al, Cardinal ac)
278 {
279 	int		i;
280 	XmRendition	r;
281 
282 	r = _XmFontListEntryCreate();
283 	r->tag = XtNewString(tag);
284 	r->dpy = XtDisplay(widget);
285 
286 	/* Assign defaults */
287 	r->font = (XFontSet)XmAS_IS;
288 	r->font_name = NULL;	/* instead of XmAS_IS */
289 	r->type = XmAS_IS;
290 	r->rendition_foreground = XmAS_IS;
291 	r->rendition_background = XmAS_IS;
292 	r->load_model = XmAS_IS;
293 	r->strike_thru_type = XmAS_IS;
294 	r->tab_list = (XmTabList)XmAS_IS;
295 	r->underline_type = XmAS_IS;
296 
297 #ifdef	USE_XFT
298 	r->xft_font = NULL;
299 	r->font_average_width = 0;
300 	memset(&r->xft_foreground, 0, sizeof(XftColor));
301 	memset(&r->xft_background, 0, sizeof(XftColor));
302 	r->pattern = NULL;
303 	r->font_style = NULL;
304 	r->font_foundry = NULL;
305 	r->font_encoding = NULL;
306 	r->font_size = 0;
307 	r->pixel_size = 0;
308 	r->font_slant = 0;
309 	r->font_spacing = 0;
310 	r->font_weight = 0;
311 #endif
312 
313 
314 #ifdef	DEBUG_POINTERS
315 	DEBUGOUT(_LtDebug(__FILE__, widget, "XmRenditionCreate() -> %p\n", r));
316 #endif
317 	for (i=0; i<ac; i++) {
318 		if (strcmp(al[i].name, XmNrenditionBackground) == 0) {
319 			r->rendition_background = al[i].value;
320 		}
321 		if (strcmp(al[i].name, XmNrenditionForeground) == 0) {
322 			r->rendition_foreground = al[i].value;
323 		}
324 		if (strcmp(al[i].name, XmNfontName) == 0) {
325 			XtFree(r->font_name);
326 			r->font_name = XtNewString((char *)al[i].value);
327 		}
328 		if (strcmp(al[i].name, XmNfont) == 0) {
329 			r->font = (XFontSet)al[i].value;
330 		}
331 		if (strcmp(al[i].name, XmNfontType) == 0) {
332 			r->type = (XmFontType)al[i].value;
333 		}
334 		if (strcmp(al[i].name, XmNloadModel) == 0) {
335 			r->load_model = al[i].value;
336 		}
337 		if (strcmp(al[i].name, XmNstrikethruType) == 0) {
338 			r->strike_thru_type = al[i].value;
339 		}
340 		if (strcmp(al[i].name, XmNtabList) == 0) {
341 			r->tab_list = XmTabListCopy((XmTabList)al[i].value, 0, 0);
342 		}
343 		if (strcmp(al[i].name, XmNunderlineType) == 0) {
344 			r->underline_type = al[i].value;
345 		}
346 #ifdef	USE_XFT
347 		/* Xft specific resources ?? FIX ME */
348 #endif
349 	}
350 
351 	return r;
352 }
353 
354 
XmRenditionFree(XmRendition rendition)355 extern void XmRenditionFree(XmRendition rendition)
356 {
357 	/* XXX FIX ME XXX: Is this right? */
358 	_XmRenditionFree(rendition);
359 }
360 
361 #ifndef	XtRXftColor
362 #define	XtRXftColor	"XftColor"
363 #endif
364 
365 extern void
XmRenditionUpdate(XmRendition r,ArgList al,Cardinal ad)366 XmRenditionUpdate(XmRendition r, ArgList al, Cardinal ad)
367 {
368 	int		i;
369 	Boolean		ok;
370 	Widget		w = NULL;	/* Crash !! */
371 	XrmValue	to, from;
372 
373 	for (i=0; i<ad; i++) {
374 #ifdef	USE_XFT
375 		if (strcmp(al[i].name, XmNrenditionForeground) == 0) {
376 			/* XftColor */
377 			from.addr = &al[i].value;
378 			from.size = sizeof(XtPointer);
379 			to.addr = &r->xft_foreground;
380 			to.size = sizeof(XftColor);
381 
382 			ok = XtConvertAndStore(w,
383 				XmRString, &from,
384 				XtRXftColor, &to);
385 		} else
386 		if (strcmp(al[i].name, XmNrenditionBackground) == 0) {
387 			/* XftColor */
388 			from.addr = &al[i].value;
389 			from.size = sizeof(XtPointer);
390 			to.addr = &r->xft_background;
391 			to.size = sizeof(XftColor);
392 			ok = XtConvertAndStore(w,
393 				XmRString, &from,
394 				XtRXftColor, &to);
395 		} else
396 #endif
397 		{
398 			;	/* Nothing */
399 		}
400 	}
401 #if 0
402 	_XmWarning(NULL, "XmRenditionUpdate(): not yet implemented!");
403 #endif
404 }
405 
406 
XmRenditionRetrieve(XmRendition r,ArgList al,Cardinal ac)407 extern void XmRenditionRetrieve(XmRendition r, ArgList al, Cardinal ac)
408 {
409 	int		i;
410 #if 0
411 	_XmWarning(NULL, "XmRenditionRetrieve(): not yet implemented!");
412 #endif
413 
414 	for (i=0; i<ac; i++) {
415 		if (strcmp(al[i].name, XmNrenditionBackground) == 0) {
416 			*(Pixel *)al[i].value = r->rendition_background;
417 		} else if (strcmp(al[i].name, XmNrenditionForeground) == 0) {
418 			*(Pixel *)al[i].value = r->rendition_foreground;
419 		} else if (strcmp(al[i].name, XmNfontName) == 0) {
420 			*(String *)al[i].value = XtNewString(r->font_name);
421 		} else if (strcmp(al[i].name, XmNfont) == 0) {
422 			*(XFontSet *)al[i].value = r->font;
423 		} else if (strcmp(al[i].name, XmNfontType) == 0) {
424 			*(XmFontType *)al[i].value = r->type;
425 		} else if (strcmp(al[i].name, XmNloadModel) == 0) {
426 			*(unsigned char *)al[i].value = r->load_model;
427 		} else if (strcmp(al[i].name, XmNstrikethruType) == 0) {
428 			*(unsigned char *)al[i].value = r->strike_thru_type;
429 		} else if (strcmp(al[i].name, XmNtabList) == 0) {
430 			*(XmTabList *)al[i].value = XmTabListCopy(r->tab_list, 0, 0);
431 		} else if (strcmp(al[i].name, XmNunderlineType) == 0) {
432 			*(unsigned char *)al[i].value = r->underline_type;
433 		}
434 #ifdef	USE_XFT
435 		/* Xft specific resources ?? FIX ME */
436 #endif
437 	}
438 }
439 
440 /* If there are unspecified values in the primary rendition,
441  * then the widget must create an "effective" rendition for that
442  * segment. This is formed by using the previous (active) rendition
443  * to fill in the unspecified values of the primary rendition.
444  * ..
445  * Finally if the resulting rendition still has resources with
446  * unspecified values and the segment has a locale or charset tag
447  * (these are optional and mutually exclusive) this tag is matched
448  * with a rendition in the render table, and the missing rendition
449  * values are filled in from that entry.
450  *
451  * If no matching rendition is found for a particular tag, then the
452  * XmNoRenditionCallback of the XmDisplay object is called and the
453  * render table is searched again for that tag.
454  * If the resulting rendition does not specify a font or fontset,
455  * then  ...
456  */
_XmRenderTableFinaliseTag(Widget w,XmRenderTable r,char * tag)457 void _XmRenderTableFinaliseTag(Widget w, XmRenderTable r, char *tag)
458 {
459 	int		i;
460 	int		found = 0;
461 	XmFontListEntry	e;
462 	char		*fn;
463 
464 	/* FIX ME */
465 	DEBUGOUT(_LtDebug(__FILE__, w, "_XmRenderTableFinaliseTag(%s)\n", tag));
466 #if 1
467 	/* Experimental start */
468 	if (r->dpy == 0)
469 		r->dpy = XtDisplay(w);
470 	/* Experimental end */
471 #endif
472 	for (i=0; i<r->count; i++) {
473 		if (strcmp(tag, r->renditions[i]->tag) == 0) {
474 #if	USE_XFT
475 			if (r->renditions[i]->type == XmAS_IS) {
476 				XftResult	res;
477 				XftPattern	*p, *p2;
478 
479 				p = FcPatternCreate();
480 /* Don't even specify anything	XftPatternAddString(p, XFT_FAMILY, "fixed"); */
481 				p2 = XftFontMatch(XtDisplay(w), 0, p, &res);
482 				r->renditions[i]->xft_font =
483 					XftFontOpenPattern(XtDisplay(w), p2);
484 				_XmXftFontAverageWidth(w,
485 					(XtPointer)r->renditions[i]->xft_font,
486 					&r->renditions[i]->font_average_width,
487 					&r->renditions[i]->font_average_height);
488 				r->renditions[i]->type = XmFONT_IS_XFT;
489 				r->renditions[i]->font = NULL;			/* HACK */
490 
491 				DEBUGOUT(_LtDebug(__FILE__, w,
492 					"_XmRenderTableFinaliseTag(%s): AS IS\n", tag));
493 			}
494 #endif
495 			if (r->renditions[i]->font == 0
496 				|| r->renditions[i]->type == XmAS_IS
497 				|| r->renditions[i]->font == (XFontSet)XmAS_IS)
498 			{
499 				found = 1;
500 
501 				if (r->renditions[i]->font_name
502 				&& r->renditions[i]->font_name != (char *)XmAS_IS)
503 					fn = r->renditions[i]->font_name;
504 				else
505 					fn = XmDEFAULT_FONT;
506 
507 				e = XmFontListEntryLoad(r->dpy,
508 					fn,
509 					XmFONT_IS_FONT,
510 					tag);
511 				if (e == NULL || e->font == NULL) {
512 					if (e)
513 						XmFontListEntryFree(&e);
514 					continue;
515 				}
516 				r->renditions[i]->font = e->font;
517 				XmFontListEntryFree(&e);
518 				return;
519 			}
520 		}
521 	}
522 
523 	if (r->renditions[0]->font == NULL ||
524 			r->renditions[0]->font == (XFontSet)XmAS_IS) {
525 		XmFontListEntry e = XmFontListEntryLoad(r->dpy,
526 			XmDEFAULT_FONT, XmFONT_IS_FONT, XmFONTLIST_DEFAULT_TAG);
527 		r->renditions[0]->font = e->font;
528 		XmFontListEntryFree(&e);
529 	}
530 }
531 
532 /*
533  * This version takes the internal _XmString representation
534  */
__XmRenderTableFinalise(Widget w,XmRenderTable r,_XmString xms)535 void __XmRenderTableFinalise(Widget w, XmRenderTable r, _XmString xms)
536 {
537 	int			i;
538 
539 	DEBUGOUT(_LtDebug(__FILE__, w, "__XmRenderTableFinalise(rt %p xms %p)\n", r, xms));
540 
541 	if (xms == NULL) {
542 		_XmRenderTableFinaliseTag(w, r, XmFONTLIST_DEFAULT_TAG);
543 		return;
544 	}
545 
546 	for (i=0; i<xms->number_of_components; i++) {
547 		/* Run through the string, make sure all tags "work" */
548 		switch (xms->components[i]->type) {
549 		case XmSTRING_COMPONENT_RENDITION_BEGIN:
550 		case XmSTRING_COMPONENT_CHARSET:
551 			_XmRenderTableFinaliseTag(w, r, xms->components[i]->data);
552 			break;
553 		case XmSTRING_COMPONENT_LOCALE_TEXT:
554 			_XmRenderTableFinaliseTag(w, r, xms->components[i]->data);
555 			break;
556 		default:
557                ;       /* Nothing to do */
558 		}
559 	}
560 }
561 
562 /*
563  * This one takes external XmString representation
564  */
_XmRenderTableFinalise(Widget w,XmRenderTable r,XmString xms)565 void _XmRenderTableFinalise(Widget w, XmRenderTable r, XmString xms)
566 {
567 	XmStringContext		ctx;
568 	XmStringComponentType	t;
569 	char			*tag = NULL, *txt = NULL;
570 
571 	DEBUGOUT(_LtDebug(__FILE__, w, "_XmRenderTableFinalise(rt %p xms %p)\n", r, xms));
572 
573 	if (xms == NULL) {
574 		_XmRenderTableFinaliseTag(w, r, XmFONTLIST_DEFAULT_TAG);
575 		return;
576 	}
577 
578 	/* Run through the string, make sure all tags "work" */
579 	if (! XmStringInitContext(&ctx, xms)) {
580 		_XmRenderTableFinaliseTag(w, r, XmFONTLIST_DEFAULT_TAG);
581 		return;
582 	}
583 
584 	/* FIX ME XmStringGetNextComponent() is obsolete */
585 	while ((t = XmStringGetNextComponent(ctx, &txt,
586 			&tag, NULL, NULL, NULL, NULL)) != XmSTRING_COMPONENT_END) {
587 		switch (t) {
588 		case XmSTRING_COMPONENT_RENDITION_BEGIN:
589 		case XmSTRING_COMPONENT_CHARSET:
590 			_XmRenderTableFinaliseTag(w, r, tag);
591 		default:
592                ;       /* Nothing to do */
593 		}
594 		XtFree(tag);
595 		XtFree(txt);
596 		tag = NULL;
597 		txt = NULL;
598 	}
599 #if 0
600 	if (r->renditions[0]->font == NULL ||
601 			r->renditions[0]->font == (XFontSet)XmAS_IS) {
602 		XmFontListEntry	e = XmFontListEntryLoad(r->dpy,
603 			XmDEFAULT_FONT, XmFONT_IS_FONT, XmFONTLIST_DEFAULT_TAG);
604 		r->renditions[0]->font = e->font;
605 		XmFontListEntryFree(&e);
606 	}
607 #endif
608 }
609 
610 Boolean
XmeUseXftFont(XmRenderTable r)611 XmeUseXftFont(XmRenderTable r)
612 {
613 #ifdef	USE_XFT
614 	if (r == NULL || r->count == 0)
615 		return False;
616 	if (r->renditions[0]->type == XmFONT_IS_XFT)
617 		return True;
618 	return False;
619 #else
620 	return False;
621 #endif
622 }
623 
624 XtPointer
XmeXftFont(XmRendition r)625 XmeXftFont(XmRendition r)
626 {
627 #ifdef	USE_XFT
628 	if (r == NULL)
629 		return NULL;
630 	if (r->type == XmFONT_IS_XFT)
631 		return r->xft_font;
632 	return NULL;
633 #else
634 	return NULL;
635 #endif
636 }
637