PageRenderTime 75ms CodeModel.GetById 21ms app.highlight 49ms RepoModel.GetById 1ms app.codeStats 0ms

/contrib/groff/src/devices/xditview/draw.c

https://bitbucket.org/freebsd/freebsd-head/
C | 699 lines | 592 code | 79 blank | 28 comment | 123 complexity | 380d1b35d4444cea835b7b1456388d50 MD5 | raw file
  1/*
  2 * draw.c
  3 *
  4 * accept dvi function calls and translate to X
  5 */
  6
  7#include <X11/Xos.h>
  8#include <X11/IntrinsicP.h>
  9#include <X11/StringDefs.h>
 10#include <stdio.h>
 11#include <ctype.h>
 12#include <math.h>
 13
 14/* math.h on a Sequent doesn't define M_PI, apparently */
 15#ifndef M_PI
 16#define M_PI	3.14159265358979323846
 17#endif
 18
 19#include "DviP.h"
 20
 21#define DeviceToX(dw, n) ((int)((n) * (dw)->dvi.scale_factor + .5))
 22#define XPos(dw) (DeviceToX((dw), (dw)->dvi.state->x - \
 23                  (dw)->dvi.text_device_width) + (dw)->dvi.text_x_width)
 24#define YPos(dw) (DeviceToX((dw), (dw)->dvi.state->y))
 25
 26static int FakeCharacter(DviWidget, char *, int);
 27
 28/* font.c */
 29extern int MaxFontPosition(DviWidget);
 30
 31void
 32HorizontalMove(DviWidget dw, int delta)
 33{
 34	dw->dvi.state->x += delta;
 35}
 36
 37void
 38HorizontalGoto(DviWidget dw, int NewPosition)
 39{
 40	dw->dvi.state->x = NewPosition;
 41}
 42
 43void
 44VerticalMove(DviWidget dw, int delta)
 45{
 46	dw->dvi.state->y += delta;
 47}
 48
 49void
 50VerticalGoto(DviWidget dw, int NewPosition)
 51{
 52	dw->dvi.state->y = NewPosition;
 53}
 54
 55void
 56AdjustCacheDeltas (DviWidget dw)
 57{
 58	int extra;
 59	int nadj;
 60	int i;
 61
 62	nadj = 0;
 63	extra = DeviceToX(dw, dw->dvi.text_device_width)
 64		- dw->dvi.text_x_width;
 65	if (extra == 0)
 66		return;
 67	for (i = 0; i <= dw->dvi.cache.index; i++)
 68		if (dw->dvi.cache.adjustable[i])
 69			++nadj;
 70	dw->dvi.text_x_width += extra;
 71	if (nadj <= 1)
 72		return;
 73	for (i = 0; i <= dw->dvi.cache.index; i++)
 74		if (dw->dvi.cache.adjustable[i]) {
 75			int x;
 76			int *deltap;
 77
 78			x = extra/nadj;
 79			deltap = &dw->dvi.cache.cache[i].delta;
 80#define MIN_DELTA 2
 81			if (*deltap > 0 && x + *deltap < MIN_DELTA) {
 82				x = MIN_DELTA - *deltap;
 83				if (x <= 0)
 84					*deltap = MIN_DELTA;
 85				else
 86					x = 0;
 87			}
 88			else
 89				*deltap += x;
 90			extra -= x;
 91			--nadj;
 92			dw->dvi.cache.adjustable[i] = 0;
 93		}
 94}
 95
 96void
 97FlushCharCache (DviWidget dw)
 98{
 99	if (dw->dvi.cache.char_index != 0) {
100		AdjustCacheDeltas (dw);
101		XDrawText (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
102			   dw->dvi.cache.start_x, dw->dvi.cache.start_y,
103			   dw->dvi.cache.cache, dw->dvi.cache.index + 1);
104	}	
105	dw->dvi.cache.index = 0;
106	dw->dvi.cache.max = DVI_TEXT_CACHE_SIZE;
107#if 0
108	if (dw->dvi.noPolyText)
109	    dw->dvi.cache.max = 1;
110#endif
111	dw->dvi.cache.char_index = 0;
112	dw->dvi.cache.cache[0].nchars = 0;
113	dw->dvi.cache.start_x = dw->dvi.cache.x	= XPos (dw);
114	dw->dvi.cache.start_y = dw->dvi.cache.y = YPos (dw);
115}
116
117void
118Newline (DviWidget dw)
119{
120	FlushCharCache (dw);
121	dw->dvi.text_x_width = dw->dvi.text_device_width = 0;
122	dw->dvi.word_flag = 0;
123}
124
125void
126Word (DviWidget dw)
127{
128	dw->dvi.word_flag = 1;
129}
130
131#define charWidth(fi,c) (\
132    (fi)->per_char ?\
133	(fi)->per_char[(c) - (fi)->min_char_or_byte2].width\
134    :\
135	(fi)->max_bounds.width\
136)
137 
138
139static
140int charExists (XFontStruct *fi, int c)
141{
142	XCharStruct *p;
143
144	/* `c' is always >= 0 */
145	if (fi->per_char == NULL
146	    || (unsigned int)c < fi->min_char_or_byte2
147	    || (unsigned int)c > fi->max_char_or_byte2)
148		return 0;
149	p = fi->per_char + (c - fi->min_char_or_byte2);
150	return (p->lbearing != 0 || p->rbearing != 0 || p->width != 0
151		|| p->ascent != 0 || p->descent != 0 || p->attributes != 0);
152}
153
154/* `wid' is in device units */
155static void
156DoCharacter (DviWidget dw, int c, int wid)
157{
158	register XFontStruct	*font;
159	register XTextItem	*text;
160	int	x, y;
161	
162	x = XPos(dw);
163	y = YPos(dw);
164
165	/*
166	 * quick and dirty extents calculation:
167	 */
168	if (!(y + 24 >= dw->dvi.extents.y1
169	      && y - 24 <= dw->dvi.extents.y2
170#if 0
171	      && x + 24 >= dw->dvi.extents.x1
172	      && x - 24 <= dw->dvi.extents.x2
173#endif
174	    ))
175		return;
176	
177	if (y != dw->dvi.cache.y
178	    || dw->dvi.cache.char_index >= DVI_CHAR_CACHE_SIZE) {
179		FlushCharCache (dw);
180		x = dw->dvi.cache.x;
181		dw->dvi.cache.adjustable[dw->dvi.cache.index] = 0;
182	}
183	/*
184	 * load a new font, if the current block is not empty,
185	 * step to the next.
186	 */
187	if (dw->dvi.cache.font_size != dw->dvi.state->font_size ||
188	    dw->dvi.cache.font_number != dw->dvi.state->font_number)
189	{
190		FlushCharCache (dw);
191		x = dw->dvi.cache.x;
192		dw->dvi.cache.font_size = dw->dvi.state->font_size;
193		dw->dvi.cache.font_number = dw->dvi.state->font_number;
194		dw->dvi.cache.font = QueryFont (dw,
195						dw->dvi.cache.font_number,
196						dw->dvi.cache.font_size);
197		if (dw->dvi.cache.cache[dw->dvi.cache.index].nchars != 0) {
198			++dw->dvi.cache.index;
199			if (dw->dvi.cache.index >= dw->dvi.cache.max)
200				FlushCharCache (dw);
201			dw->dvi.cache.cache[dw->dvi.cache.index].nchars = 0;
202			dw->dvi.cache.adjustable[dw->dvi.cache.index] = 0;
203		}
204	}
205	if (x != dw->dvi.cache.x || dw->dvi.word_flag) {
206		if (dw->dvi.cache.cache[dw->dvi.cache.index].nchars != 0) {
207			++dw->dvi.cache.index;
208			if (dw->dvi.cache.index >= dw->dvi.cache.max)
209				FlushCharCache (dw);
210			dw->dvi.cache.cache[dw->dvi.cache.index].nchars = 0;
211			dw->dvi.cache.adjustable[dw->dvi.cache.index] = 0;
212		}
213		dw->dvi.cache.adjustable[dw->dvi.cache.index]
214			= dw->dvi.word_flag;
215		dw->dvi.word_flag = 0;
216	}
217	font = dw->dvi.cache.font;
218	text = &dw->dvi.cache.cache[dw->dvi.cache.index];
219	if (text->nchars == 0) {
220		text->chars = &dw->dvi.cache.char_cache[dw->dvi.cache.char_index];
221		text->delta = x - dw->dvi.cache.x;
222		if (font != dw->dvi.font) {
223			text->font = font->fid;
224			dw->dvi.font = font;
225		} else
226			text->font = None;
227		dw->dvi.cache.x += text->delta;
228	}
229	if (charExists(font, c)) {
230		int w;
231		dw->dvi.cache.char_cache[dw->dvi.cache.char_index++] = (char) c;
232		++text->nchars;
233		w = charWidth(font, c);
234		dw->dvi.cache.x += w;
235		if (wid != 0) {
236			dw->dvi.text_x_width += w;
237			dw->dvi.text_device_width += wid;
238		}
239	}
240}
241
242static
243int FindCharWidth (DviWidget dw, char *buf, int *widp)
244{
245	int maxpos;
246	int i;
247
248	if (dw->dvi.device_font == 0
249	    || dw->dvi.state->font_number != dw->dvi.device_font_number) {
250		dw->dvi.device_font_number = dw->dvi.state->font_number;
251		dw->dvi.device_font
252			= QueryDeviceFont (dw, dw->dvi.device_font_number);
253	}
254	if (dw->dvi.device_font
255	    && device_char_width (dw->dvi.device_font,
256				  dw->dvi.state->font_size, buf, widp))
257		return 1;
258
259	maxpos = MaxFontPosition (dw);
260	for (i = 1; i <= maxpos; i++) {
261		DeviceFont *f = QueryDeviceFont (dw, i);
262		if (f && device_font_special (f)
263		    && device_char_width (f, dw->dvi.state->font_size,
264					  buf, widp)) {
265			dw->dvi.state->font_number = i;
266			return 1;
267		}
268	}
269	return 0;
270}
271
272/* Return the width of the character in device units. */
273
274int PutCharacter (DviWidget dw, char *buf)
275{
276	int		prevFont;
277	int		c = -1;
278	int		wid = 0;
279	DviCharNameMap	*map;
280
281	if (!dw->dvi.display_enable)
282		return 0;	/* The width doesn't matter in this case. */
283	prevFont = dw->dvi.state->font_number;
284	if (!FindCharWidth (dw, buf, &wid))
285		return 0;
286	map = QueryFontMap (dw, dw->dvi.state->font_number);
287	if (map)
288		c = DviCharIndex (map, buf);
289	if (c >= 0)
290		DoCharacter (dw, c, wid);
291	else
292		(void) FakeCharacter (dw, buf, wid);
293	dw->dvi.state->font_number = prevFont;
294	return wid;
295}
296
297/* Return 1 if we can fake it; 0 otherwise. */
298
299static
300int FakeCharacter (DviWidget dw, char *buf, int wid)
301{
302	int oldx, oldw;
303	char ch[2];
304	const char *chars = 0;
305
306	if (buf[0] == '\0' || buf[1] == '\0' || buf[2] != '\0')
307		return 0;
308#define pack2(c1, c2) (((c1) << 8) | (c2))
309
310	switch (pack2(buf[0], buf[1])) {
311	case pack2('f', 'i'):
312		chars = "fi";
313		break;
314	case pack2('f', 'l'):
315		chars = "fl";
316		break;
317	case pack2('f', 'f'):
318		chars = "ff";
319		break;
320	case pack2('F', 'i'):
321		chars = "ffi";
322		break;
323	case pack2('F', 'l'):
324		chars = "ffl";
325		break;
326	}
327	if (!chars)
328		return 0;
329	oldx = dw->dvi.state->x;
330	oldw = dw->dvi.text_device_width;
331	ch[1] = '\0';
332	for (; *chars; chars++) {
333		ch[0] = *chars;
334		dw->dvi.state->x += PutCharacter (dw, ch);
335	}
336	dw->dvi.state->x = oldx;
337	dw->dvi.text_device_width = oldw + wid;
338	return 1;
339}
340
341void
342PutNumberedCharacter (DviWidget dw, int c)
343{
344	char *name;
345	int wid;
346	DviCharNameMap	*map;
347
348	if (!dw->dvi.display_enable)
349		return;
350
351	if (dw->dvi.device_font == 0
352	    || dw->dvi.state->font_number != dw->dvi.device_font_number) {
353		dw->dvi.device_font_number = dw->dvi.state->font_number;
354		dw->dvi.device_font
355			= QueryDeviceFont (dw, dw->dvi.device_font_number);
356	}
357	
358	if (dw->dvi.device_font == 0
359	    || !device_code_width (dw->dvi.device_font,
360				   dw->dvi.state->font_size, c, &wid))
361		return;
362	if (dw->dvi.native) {
363		DoCharacter (dw, c, wid);
364		return;
365	}
366	map = QueryFontMap (dw, dw->dvi.state->font_number);
367	if (!map)
368		return;
369	for (name = device_name_for_code (dw->dvi.device_font, c);
370	     name;
371	     name = device_name_for_code ((DeviceFont *)0, c)) {
372		int code = DviCharIndex (map, name);
373		if (code >= 0) {
374			DoCharacter (dw, code, wid);
375			break;
376		}
377		if (FakeCharacter (dw, name, wid))
378			break;
379	}
380}
381
382void
383ClearPage (DviWidget dw)
384{
385	XClearWindow (XtDisplay (dw), XtWindow (dw));
386}
387
388static void
389setGC (DviWidget dw)
390{
391	int desired_line_width;
392	
393	if (dw->dvi.line_thickness < 0)
394		desired_line_width = (int)(((double)dw->dvi.device_resolution
395					    * dw->dvi.state->font_size)
396					   / (10.0*72.0*dw->dvi.sizescale));
397	else
398		desired_line_width = dw->dvi.line_thickness;
399	
400	if (desired_line_width != dw->dvi.line_width) {
401		XGCValues values;
402		values.line_width = DeviceToX(dw, desired_line_width);
403		if (values.line_width == 0)
404			values.line_width = 1;
405		XChangeGC(XtDisplay (dw), dw->dvi.normal_GC,
406			  GCLineWidth, &values);
407		dw->dvi.line_width = desired_line_width;
408	}
409}
410
411static void
412setFillGC (DviWidget dw)
413{
414	int fill_type;
415	unsigned long mask = GCFillStyle | GCForeground;
416
417	fill_type = (dw->dvi.fill * 10) / (DVI_FILL_MAX + 1);
418	if (dw->dvi.fill_type != fill_type) {
419		XGCValues values;
420		if (fill_type <= 0) {
421			values.foreground = dw->dvi.background;
422			values.fill_style = FillSolid;
423		} else if (fill_type >= 9) {
424			values.foreground = dw->dvi.foreground;
425			values.fill_style = FillSolid;
426		} else {
427			values.foreground = dw->dvi.foreground;
428			values.fill_style = FillOpaqueStippled;
429			values.stipple = dw->dvi.gray[fill_type - 1];
430			mask |= GCStipple;
431		}
432		XChangeGC(XtDisplay (dw), dw->dvi.fill_GC, mask, &values);
433		dw->dvi.fill_type = fill_type;
434	}
435}
436
437void
438DrawLine (DviWidget dw, int x, int y)
439{
440	int xp, yp;
441
442	AdjustCacheDeltas (dw);
443	setGC (dw);
444	xp = XPos (dw);
445	yp = YPos (dw);
446	XDrawLine (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
447		   xp, yp,
448		   xp + DeviceToX (dw, x), yp + DeviceToX (dw, y));
449}
450
451void
452DrawCircle (DviWidget dw, int diam)
453{
454	int d;
455
456	AdjustCacheDeltas (dw);
457	setGC (dw);
458	d = DeviceToX (dw, diam);
459	XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
460		  XPos (dw), YPos (dw) - d/2,
461		  d, d, 0, 64*360);
462}
463
464void
465DrawFilledCircle (DviWidget dw, int diam)
466{
467	int d;
468
469	AdjustCacheDeltas (dw);
470	setFillGC (dw);
471	d = DeviceToX (dw, diam);
472	XFillArc (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
473		  XPos (dw), YPos (dw) - d/2,
474		  d, d, 0, 64*360);
475	XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
476		  XPos (dw), YPos (dw) - d/2,
477		  d, d, 0, 64*360);
478}
479
480void
481DrawEllipse (DviWidget dw, int a, int b)
482{
483	AdjustCacheDeltas (dw);
484	setGC (dw);
485	XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
486		  XPos (dw), YPos (dw) - DeviceToX (dw, b/2),
487		  DeviceToX (dw, a), DeviceToX (dw, b), 0, 64*360);
488}
489
490void
491DrawFilledEllipse (DviWidget dw, int a, int b)
492{
493	AdjustCacheDeltas (dw);
494	setFillGC (dw);
495	XFillArc (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
496		  XPos (dw), YPos (dw) - DeviceToX (dw, b/2),
497		  DeviceToX (dw, a), DeviceToX (dw, b), 0, 64*360);
498	XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
499		  XPos (dw), YPos (dw) - DeviceToX (dw, b/2),
500		  DeviceToX (dw, a), DeviceToX (dw, b), 0, 64*360);
501}
502
503void
504DrawArc (DviWidget dw, int x_0, int y_0, int x_1, int y_1)
505{
506	int angle1, angle2;
507	int rad = (int)((sqrt ((double)x_0*x_0 + (double)y_0*y_0)
508			 + sqrt ((double)x_1*x_1 + (double)y_1*y_1)
509			 + 1.0)/2.0);
510	if ((x_0 == 0 && y_0 == 0) || (x_1 == 0 && y_1 == 0))
511		return;
512	angle1 = (int)(atan2 ((double)y_0, (double)-x_0)*180.0*64.0/M_PI);
513	angle2 = (int)(atan2 ((double)-y_1, (double)x_1)*180.0*64.0/M_PI);
514	
515	angle2 -= angle1;
516	if (angle2 < 0)
517		angle2 += 64*360;
518	
519	AdjustCacheDeltas (dw);
520	setGC (dw);
521
522	rad = DeviceToX (dw, rad);
523	XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
524		  XPos (dw) + DeviceToX (dw, x_0) - rad,
525		  YPos (dw) + DeviceToX (dw, y_0) - rad,
526		  rad*2, rad*2, angle1, angle2);
527}
528
529void
530DrawPolygon (DviWidget dw, int *v, int n)
531{
532	XPoint *p;
533	int i;
534	int dx, dy;
535	
536	n /= 2;
537	
538	AdjustCacheDeltas (dw);
539	setGC (dw);
540	p = (XPoint *)XtMalloc((n + 2)*sizeof(XPoint));
541	p[0].x = XPos (dw);
542	p[0].y = YPos (dw);
543	dx = 0;
544	dy = 0;
545	for (i = 0; i < n; i++) {
546		dx += v[2*i];
547		p[i + 1].x = DeviceToX (dw, dx) + p[0].x;
548		dy += v[2*i + 1];
549		p[i + 1].y = DeviceToX (dw, dy) + p[0].y;
550	}
551	p[n+1].x = p[0].x;
552	p[n+1].y = p[0].y;
553	XDrawLines (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
554		   p, n + 2, CoordModeOrigin);
555	XtFree((char *)p);
556}
557
558void
559DrawFilledPolygon (DviWidget dw, int *v, int n)
560{
561	XPoint *p;
562	int i;
563	int dx, dy;
564	
565	n /= 2;
566	if (n < 2)
567		return;
568	
569	AdjustCacheDeltas (dw);
570	setFillGC (dw);
571	p = (XPoint *)XtMalloc((n + 2)*sizeof(XPoint));
572	p[0].x = p[n+1].x = XPos (dw);
573	p[0].y = p[n+1].y = YPos (dw);
574	dx = 0;
575	dy = 0;
576	for (i = 0; i < n; i++) {
577		dx += v[2*i];
578		p[i + 1].x = DeviceToX (dw, dx) + p[0].x;
579		dy += v[2*i + 1];
580		p[i + 1].y = DeviceToX (dw, dy) + p[0].y;
581	}
582	XFillPolygon (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
583		      p, n + 1, Complex, CoordModeOrigin);
584	XDrawLines (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
585		      p, n + 2, CoordModeOrigin);
586	XtFree((char *)p);
587}
588
589#define POINTS_MAX 10000
590
591static void
592appendPoint(XPoint *points, int *pointi, int x, int y)
593{
594	if (*pointi < POINTS_MAX) {
595		points[*pointi].x = x;
596		points[*pointi].y = y;
597		*pointi += 1;
598	}
599}
600
601#define FLATNESS 1
602
603static void
604flattenCurve(XPoint *points, int *pointi,
605	     int x_2, int y_2, int x_3, int y_3, int x_4, int y_4)
606{
607	int x_1, y_1, dx, dy, n1, n2, n;
608
609	x_1 = points[*pointi - 1].x;
610	y_1 = points[*pointi - 1].y;
611	
612	dx = x_4 - x_1;
613	dy = y_4 - y_1;
614	
615	n1 = dy*(x_2 - x_1) - dx*(y_2 - y_1);
616	n2 = dy*(x_3 - x_1) - dx*(y_3 - y_1);
617	if (n1 < 0)
618		n1 = -n1;
619	if (n2 < 0)
620		n2 = -n2;
621	n = n1 > n2 ? n1 : n2;
622
623	if (n*n / (dy*dy + dx*dx) <= FLATNESS*FLATNESS)
624		appendPoint (points, pointi, x_4, y_4);
625	else {
626		flattenCurve (points, pointi,
627			      (x_1 + x_2)/2,
628			      (y_1 + y_2)/2,
629			      (x_1 + x_2*2 + x_3)/4,
630			      (y_1 + y_2*2 + y_3)/4,
631			      (x_1 + 3*x_2 + 3*x_3 + x_4)/8,
632			      (y_1 + 3*y_2 + 3*y_3 + y_4)/8);
633		flattenCurve (points, pointi,
634			      (x_2 + x_3*2 + x_4)/4,
635			      (y_2 + y_3*2 + y_4)/4,
636			      (x_3 + x_4)/2,
637			      (y_3 + y_4)/2,
638			      x_4,
639			      y_4);
640	}
641}
642
643void
644DrawSpline (DviWidget dw, int *v, int n)
645{
646	int sx, sy, tx, ty;
647	int ox, oy, dx, dy;
648	int i;
649	int pointi;
650	XPoint points[POINTS_MAX];
651	
652	if (n == 0 || (n & 1) != 0)
653		return;
654	AdjustCacheDeltas (dw);
655	setGC (dw);
656	ox = XPos (dw);
657	oy = YPos (dw);
658	dx = v[0];
659	dy = v[1];
660	sx = ox;
661	sy = oy;
662	tx = sx + DeviceToX (dw, dx);
663	ty = sy + DeviceToX (dw, dy);
664	
665	pointi = 0;
666	
667	appendPoint (points, &pointi, sx, sy);
668	appendPoint (points, &pointi, (sx + tx)/2, (sy + ty)/2);
669	
670	for (i = 2; i < n; i += 2) {
671		int ux = ox + DeviceToX (dw, dx += v[i]);
672		int uy = oy + DeviceToX (dw, dy += v[i+1]);
673		flattenCurve (points, &pointi,
674			       (sx + tx*5)/6, (sy + ty*5)/6,
675			       (tx*5 + ux)/6, (ty*5 + uy)/6,
676			       (tx + ux)/2, (ty + uy)/2);
677		sx = tx;
678		sy = ty;
679		tx = ux;
680		ty = uy;
681	}
682	
683	appendPoint (points, &pointi, tx, ty);
684	
685	XDrawLines (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
686		   points, pointi, CoordModeOrigin);
687}
688
689
690/*
691Local Variables:
692c-indent-level: 8
693c-continued-statement-offset: 8
694c-brace-offset: -8
695c-argdecl-indent: 8
696c-label-offset: -8
697c-tab-always-indent: nil
698End:
699*/