xref: /haiku/src/kits/interface/PicturePlayer.cpp (revision 991dadd6324f7b7a68e94743a39ebae789823228)
1 /*
2  * Copyright 2001-2007, Haiku Inc.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Marc Flerackers (mflerackers@androme.be)
7  *		Stefano Ceccherini (stefano.ceccherini@gmail.com)
8  *		Marcus Overhagen (marcus@overhagen.de)
9  */
10 
11 /**	PicturePlayer is used to play picture data. */
12 
13 #include <PicturePlayer.h>
14 
15 #include <stdio.h>
16 #include <string.h>
17 
18 #include <AffineTransform.h>
19 #include <PictureProtocol.h>
20 #include <Shape.h>
21 
22 
23 using BPrivate::PicturePlayer;
24 
25 
26 typedef void (*fnc)(void*);
27 typedef void (*fnc_BPoint)(void*, BPoint);
28 typedef void (*fnc_BPointBPoint)(void*, BPoint, BPoint);
29 typedef void (*fnc_BRect)(void*, BRect);
30 typedef void (*fnc_BRectBPoint)(void*, BRect, BPoint);
31 typedef void (*fnc_PBPoint)(void*, const BPoint*);
32 typedef void (*fnc_i)(void*, int32);
33 typedef void (*fnc_iPBPointb)(void*, int32, const BPoint*, bool);
34 typedef void (*fnc_iPBPoint)(void*, int32, const BPoint*);
35 typedef void (*fnc_Pc)(void*, const char*);
36 typedef void (*fnc_Pcff)(void*, const char*, float, float);
37 typedef void (*fnc_BPointBPointff)(void*, BPoint, BPoint, float, float);
38 typedef void (*fnc_s)(void*, int16);
39 typedef void (*fnc_ssf)(void*, int16, int16, float);
40 typedef void (*fnc_f)(void*, float);
41 typedef void (*fnc_Color)(void*, rgb_color);
42 typedef void (*fnc_Pattern)(void*, pattern);
43 typedef void (*fnc_ss)(void *, int16, int16);
44 typedef void (*fnc_PBRecti)(void*, const BRect*, uint32);
45 typedef void (*fnc_DrawPixels)(void *, BRect, BRect, int32, int32, int32,
46 							   int32, int32, const void *);
47 typedef void (*fnc_DrawPicture)(void *, BPoint, int32);
48 typedef void (*fnc_BShape)(void*, BShape*);
49 typedef void (*fnc_BAffineTransform)(void*, BAffineTransform);
50 
51 
52 static void
53 nop()
54 {
55 }
56 
57 
58 #if DEBUG > 1
59 static const char *
60 PictureOpToString(int op)
61 {
62 	#define RETURN_STRING(x) case x: return #x
63 
64 	switch(op) {
65 		RETURN_STRING(B_PIC_MOVE_PEN_BY);
66 		RETURN_STRING(B_PIC_STROKE_LINE);
67 		RETURN_STRING(B_PIC_STROKE_RECT);
68 		RETURN_STRING(B_PIC_FILL_RECT);
69 		RETURN_STRING(B_PIC_STROKE_ROUND_RECT);
70 		RETURN_STRING(B_PIC_FILL_ROUND_RECT);
71 		RETURN_STRING(B_PIC_STROKE_BEZIER);
72 		RETURN_STRING(B_PIC_FILL_BEZIER);
73 		RETURN_STRING(B_PIC_STROKE_POLYGON);
74 		RETURN_STRING(B_PIC_FILL_POLYGON);
75 		RETURN_STRING(B_PIC_STROKE_SHAPE);
76 		RETURN_STRING(B_PIC_FILL_SHAPE);
77 		RETURN_STRING(B_PIC_DRAW_STRING);
78 		RETURN_STRING(B_PIC_DRAW_PIXELS);
79 		RETURN_STRING(B_PIC_DRAW_PICTURE);
80 		RETURN_STRING(B_PIC_STROKE_ARC);
81 		RETURN_STRING(B_PIC_FILL_ARC);
82 		RETURN_STRING(B_PIC_STROKE_ELLIPSE);
83 		RETURN_STRING(B_PIC_FILL_ELLIPSE);
84 
85 		RETURN_STRING(B_PIC_ENTER_STATE_CHANGE);
86 		RETURN_STRING(B_PIC_SET_CLIPPING_RECTS);
87 		RETURN_STRING(B_PIC_CLIP_TO_PICTURE);
88 		RETURN_STRING(B_PIC_PUSH_STATE);
89 		RETURN_STRING(B_PIC_POP_STATE);
90 		RETURN_STRING(B_PIC_CLEAR_CLIPPING_RECTS);
91 
92 		RETURN_STRING(B_PIC_SET_ORIGIN);
93 		RETURN_STRING(B_PIC_SET_PEN_LOCATION);
94 		RETURN_STRING(B_PIC_SET_DRAWING_MODE);
95 		RETURN_STRING(B_PIC_SET_LINE_MODE);
96 		RETURN_STRING(B_PIC_SET_PEN_SIZE);
97 		RETURN_STRING(B_PIC_SET_SCALE);
98 		RETURN_STRING(B_PIC_SET_TRANSFORM);
99 		RETURN_STRING(B_PIC_SET_FORE_COLOR);
100 		RETURN_STRING(B_PIC_SET_BACK_COLOR);
101 		RETURN_STRING(B_PIC_SET_STIPLE_PATTERN);
102 		RETURN_STRING(B_PIC_ENTER_FONT_STATE);
103 		RETURN_STRING(B_PIC_SET_BLENDING_MODE);
104 		RETURN_STRING(B_PIC_SET_FONT_FAMILY);
105 		RETURN_STRING(B_PIC_SET_FONT_STYLE);
106 		RETURN_STRING(B_PIC_SET_FONT_SPACING);
107 		RETURN_STRING(B_PIC_SET_FONT_ENCODING);
108 		RETURN_STRING(B_PIC_SET_FONT_FLAGS);
109 		RETURN_STRING(B_PIC_SET_FONT_SIZE);
110 		RETURN_STRING(B_PIC_SET_FONT_ROTATE);
111 		RETURN_STRING(B_PIC_SET_FONT_SHEAR);
112 		RETURN_STRING(B_PIC_SET_FONT_BPP);
113 		RETURN_STRING(B_PIC_SET_FONT_FACE);
114 		default: return "Unknown op";
115 	}
116 	#undef RETURN_STRING
117 }
118 #endif
119 
120 
121 PicturePlayer::PicturePlayer(const void *data, size_t size, BList *pictures)
122 	:	fData(data),
123 		fSize(size),
124 		fPictures(pictures)
125 {
126 }
127 
128 
129 PicturePlayer::~PicturePlayer()
130 {
131 }
132 
133 
134 status_t
135 PicturePlayer::Play(void **callBackTable, int32 tableEntries, void *userData)
136 {
137 	// We don't check if the functions in the table are NULL, but we
138 	// check the tableEntries to see if the table is big enough.
139 	// If an application supplies the wrong size or an invalid pointer,
140 	// it's its own fault.
141 #if DEBUG
142 	FILE *file = fopen("/var/log/PicturePlayer.log", "a");
143 	fprintf(file, "Start rendering BPicture...\n");
144 	bigtime_t startTime = system_time();
145 	int32 numOps = 0;
146 #endif
147 	// If the caller supplied a function table smaller than needed,
148 	// we use our dummy table, and copy the supported ops from the supplied one.
149 	void **functionTable = callBackTable;
150 	void *dummyTable[kOpsTableSize] = {
151 		(void *)nop, (void *)nop, (void *)nop, (void *)nop,
152 		(void *)nop, (void *)nop, (void *)nop, (void *)nop,
153 		(void *)nop, (void *)nop, (void *)nop, (void *)nop,
154 		(void *)nop, (void *)nop, (void *)nop, (void *)nop,
155 		(void *)nop, (void *)nop, (void *)nop, (void *)nop,
156 		(void *)nop, (void *)nop, (void *)nop, (void *)nop,
157 		(void *)nop, (void *)nop, (void *)nop, (void *)nop,
158 		(void *)nop, (void *)nop, (void *)nop, (void *)nop,
159 		(void *)nop, (void *)nop, (void *)nop, (void *)nop,
160 		(void *)nop, (void *)nop, (void *)nop, (void *)nop,
161 		(void *)nop, (void *)nop, (void *)nop, (void *)nop,
162 		(void *)nop, (void *)nop, (void *)nop, (void *)nop
163 	};
164 
165 	if ((uint32)tableEntries < kOpsTableSize) {
166 #if DEBUG
167 		fprintf(file, "PicturePlayer: A smaller than needed function table was supplied.\n");
168 #endif
169 		functionTable = dummyTable;
170 		memcpy(functionTable, callBackTable, tableEntries * sizeof(void *));
171 	}
172 
173 	const char *data = reinterpret_cast<const char *>(fData);
174 	size_t pos = 0;
175 
176 	int32 fontStateBlockSize = -1;
177 	int32 stateBlockSize = -1;
178 
179 	while ((pos + 6) <= fSize) {
180 		int16 op = *reinterpret_cast<const int16 *>(data);
181 		int32 size = *reinterpret_cast<const int32 *>(data + 2);
182 		pos += 6;
183 		data += 6;
184 
185 		if (pos + size > fSize)
186 			debugger("PicturePlayer::Play: buffer overrun\n");
187 
188 #if DEBUG > 1
189 		bigtime_t startOpTime = system_time();
190 		fprintf(file, "Op %s ", PictureOpToString(op));
191 #endif
192 		switch (op) {
193 			case B_PIC_MOVE_PEN_BY:
194 			{
195 				((fnc_BPoint)functionTable[1])(userData,
196 					*reinterpret_cast<const BPoint *>(data)); /* where */
197 				break;
198 			}
199 
200 			case B_PIC_STROKE_LINE:
201 			{
202 				((fnc_BPointBPoint)functionTable[2])(userData,
203 					*reinterpret_cast<const BPoint *>(data), /* start */
204 					*reinterpret_cast<const BPoint *>(data + sizeof(BPoint))); /* end */
205 				break;
206 			}
207 
208 			case B_PIC_STROKE_RECT:
209 			{
210 				((fnc_BRect)functionTable[3])(userData,
211 					*reinterpret_cast<const BRect *>(data)); /* rect */
212 				break;
213 			}
214 
215 			case B_PIC_FILL_RECT:
216 			{
217 				((fnc_BRect)functionTable[4])(userData,
218 					*reinterpret_cast<const BRect *>(data)); /* rect */
219 				break;
220 			}
221 
222 			case B_PIC_STROKE_ROUND_RECT:
223 			{
224 				((fnc_BRectBPoint)functionTable[5])(userData,
225 					*reinterpret_cast<const BRect *>(data), /* rect */
226 					*reinterpret_cast<const BPoint *>(data + sizeof(BRect))); /* radii */
227 				break;
228 			}
229 
230 			case B_PIC_FILL_ROUND_RECT:
231 			{
232 				((fnc_BRectBPoint)functionTable[6])(userData,
233 					*reinterpret_cast<const BRect *>(data), /* rect */
234 					*reinterpret_cast<const BPoint *>(data + sizeof(BRect))); /* radii */
235 				break;
236 			}
237 
238 			case B_PIC_STROKE_BEZIER:
239 			{
240 				((fnc_PBPoint)functionTable[7])(userData,
241 					reinterpret_cast<const BPoint *>(data));
242 				break;
243 			}
244 
245 			case B_PIC_FILL_BEZIER:
246 			{
247 				((fnc_PBPoint)functionTable[8])(userData,
248 					reinterpret_cast<const BPoint *>(data));
249 				break;
250 			}
251 
252 			case B_PIC_STROKE_ARC:
253 			{
254 				((fnc_BPointBPointff)functionTable[9])(userData,
255 					*reinterpret_cast<const BPoint *>(data), /* center */
256 					*reinterpret_cast<const BPoint *>(data + sizeof(BPoint)), /* radii */
257 					*reinterpret_cast<const float *>(data + 2 * sizeof(BPoint)), /* startTheta */
258 					*reinterpret_cast<const float *>(data + 2 * sizeof(BPoint) + sizeof(float))); /* arcTheta */
259 				break;
260 			}
261 
262 			case B_PIC_FILL_ARC:
263 			{
264 				((fnc_BPointBPointff)functionTable[10])(userData,
265 					*reinterpret_cast<const BPoint *>(data), /* center */
266 					*reinterpret_cast<const BPoint *>(data + sizeof(BPoint)), /* radii */
267 					*reinterpret_cast<const float *>(data + 2 * sizeof(BPoint)), /* startTheta */
268 					*reinterpret_cast<const float *>(data + 2 * sizeof(BPoint) + sizeof(float))); /* arcTheta */
269 				break;
270 			}
271 
272 			case B_PIC_STROKE_ELLIPSE:
273 			{
274 				const BRect *rect = reinterpret_cast<const BRect *>(data);
275 				BPoint radii((rect->Width() + 1) / 2.0f, (rect->Height() + 1) / 2.0f);
276 				BPoint center = rect->LeftTop() + radii;
277 				((fnc_BPointBPoint)functionTable[11])(userData, center, radii);
278 				break;
279 			}
280 
281 			case B_PIC_FILL_ELLIPSE:
282 			{
283 				const BRect *rect = reinterpret_cast<const BRect *>(data);
284 				BPoint radii((rect->Width() + 1) / 2.0f, (rect->Height() + 1) / 2.0f);
285 				BPoint center = rect->LeftTop() + radii;
286 				((fnc_BPointBPoint)functionTable[12])(userData, center, radii);
287 				break;
288 			}
289 
290 			case B_PIC_STROKE_POLYGON:
291 			{
292 				int32 numPoints = *reinterpret_cast<const int32 *>(data);
293 				((fnc_iPBPointb)functionTable[13])(userData,
294 					numPoints,
295 					reinterpret_cast<const BPoint *>(data + sizeof(int32)), /* points */
296 					*reinterpret_cast<const uint8 *>(data + sizeof(int32) + numPoints * sizeof(BPoint))); /* is-closed */
297 				break;
298 			}
299 
300 			case B_PIC_FILL_POLYGON:
301 			{
302 				((fnc_iPBPoint)functionTable[14])(userData,
303 					*reinterpret_cast<const int32 *>(data), /* numPoints */
304 					reinterpret_cast<const BPoint *>(data + sizeof(int32))); /* points */
305 				break;
306 			}
307 
308 			case B_PIC_STROKE_SHAPE:
309 			case B_PIC_FILL_SHAPE:
310 			{
311 				const bool stroke = (op == B_PIC_STROKE_SHAPE);
312 				int32 opCount = *reinterpret_cast<const int32 *>(data);
313 				int32 ptCount = *reinterpret_cast<const int32 *>(data + sizeof(int32));
314 				const uint32 *opList = reinterpret_cast<const uint32 *>(data + 2 * sizeof(int32));
315 				const BPoint *ptList = reinterpret_cast<const BPoint *>(data + 2 * sizeof(int32) + opCount * sizeof(uint32));
316 
317 				// TODO: remove BShape data copying
318 				BShape shape;
319 				shape.SetData(opCount, ptCount, opList, ptList);
320 
321 				const int32 tableIndex = stroke ? 15 : 16;
322 				((fnc_BShape)functionTable[tableIndex])(userData, &shape);
323 				break;
324 			}
325 
326 			case B_PIC_DRAW_STRING:
327 			{
328 				((fnc_Pcff)functionTable[17])(userData,
329 					reinterpret_cast<const char *>(data + 2 * sizeof(float)), /* string */
330 					*reinterpret_cast<const float *>(data), /* escapement.space */
331 					*reinterpret_cast<const float *>(data + sizeof(float))); /* escapement.nonspace */
332 				break;
333 			}
334 
335 			case B_PIC_DRAW_PIXELS:
336 			{
337 				((fnc_DrawPixels)functionTable[18])(userData,
338 					*reinterpret_cast<const BRect *>(data), /* src */
339 					*reinterpret_cast<const BRect *>(data + 1 * sizeof(BRect)), /* dst */
340 					*reinterpret_cast<const int32 *>(data + 2 * sizeof(BRect)), /* width */
341 					*reinterpret_cast<const int32 *>(data + 2 * sizeof(BRect) + 1 * sizeof(int32)), /* height */
342 					*reinterpret_cast<const int32 *>(data + 2 * sizeof(BRect) + 2 * sizeof(int32)), /* bytesPerRow */
343 					*reinterpret_cast<const int32 *>(data + 2 * sizeof(BRect) + 3 * sizeof(int32)), /* pixelFormat */
344 					*reinterpret_cast<const int32 *>(data + 2 * sizeof(BRect) + 4 * sizeof(int32)), /* flags */
345 					reinterpret_cast<const void *>(data + 2 * sizeof(BRect) + 5 * sizeof(int32))); /* data */
346 				break;
347 			}
348 
349 			case B_PIC_DRAW_PICTURE:
350 			{
351 				((fnc_DrawPicture)functionTable[19])(userData,
352 					*reinterpret_cast<const BPoint *>(data),
353 					*reinterpret_cast<const int32 *>(data + sizeof(BPoint)));
354 				break;
355 			}
356 
357 			case B_PIC_SET_CLIPPING_RECTS:
358 			{
359 				// TODO: Not sure if it's compatible with R5's BPicture version
360 				const uint32 numRects = *reinterpret_cast<const uint32 *>(data);
361 				const BRect *rects = reinterpret_cast<const BRect *>(data + sizeof(uint32));
362 				((fnc_PBRecti)functionTable[20])(userData, rects, numRects);
363 
364 				break;
365 			}
366 
367 			case B_PIC_CLEAR_CLIPPING_RECTS:
368 			{
369 				((fnc_PBRecti)functionTable[20])(userData, NULL, 0);
370 				break;
371 			}
372 
373 			case B_PIC_CLIP_TO_PICTURE:
374 			{
375 				// TODO: Implement
376 				break;
377 			}
378 
379 			case B_PIC_PUSH_STATE:
380 			{
381 				((fnc)functionTable[22])(userData);
382 				break;
383 			}
384 
385 			case B_PIC_POP_STATE:
386 			{
387 				((fnc)functionTable[23])(userData);
388 				break;
389 			}
390 
391 			case B_PIC_ENTER_STATE_CHANGE:
392 			{
393 				((fnc)functionTable[24])(userData);
394 				stateBlockSize = size;
395 				break;
396 			}
397 
398 			case B_PIC_ENTER_FONT_STATE:
399 			{
400 				((fnc)functionTable[26])(userData);
401 				fontStateBlockSize = size;
402 				break;
403 			}
404 
405 			case B_PIC_SET_ORIGIN:
406 			{
407 				((fnc_BPoint)functionTable[28])(userData,
408 					*reinterpret_cast<const BPoint *>(data)); /* origin */
409 				break;
410 			}
411 
412 			case B_PIC_SET_PEN_LOCATION:
413 			{
414 				((fnc_BPoint)functionTable[29])(userData,
415 					*reinterpret_cast<const BPoint *>(data)); /* location */
416 				break;
417 			}
418 
419 			case B_PIC_SET_DRAWING_MODE:
420 			{
421 				((fnc_s)functionTable[30])(userData,
422 					*reinterpret_cast<const int16 *>(data)); /* mode */
423 				break;
424 			}
425 
426 			case B_PIC_SET_LINE_MODE:
427 			{
428 				((fnc_ssf)functionTable[31])(userData,
429 					*reinterpret_cast<const int16 *>(data), /* cap-mode */
430 					*reinterpret_cast<const int16 *>(data + 1 * sizeof(int16)), /* join-mode */
431 					*reinterpret_cast<const float *>(data + 2 * sizeof(int16))); /* miter-limit */
432 				break;
433 			}
434 
435 			case B_PIC_SET_PEN_SIZE:
436 			{
437 				((fnc_f)functionTable[32])(userData,
438 					*reinterpret_cast<const float *>(data)); /* size */
439 				break;
440 			}
441 
442 			case B_PIC_SET_FORE_COLOR:
443 			{
444 				((fnc_Color)functionTable[33])(userData,
445 					*reinterpret_cast<const rgb_color *>(data)); /* color */
446 				break;
447 			}
448 
449 			case B_PIC_SET_BACK_COLOR:
450 			{
451 				((fnc_Color)functionTable[34])(userData,
452 					*reinterpret_cast<const rgb_color *>(data)); /* color */
453 				break;
454 			}
455 
456 			case B_PIC_SET_STIPLE_PATTERN:
457 			{
458 				((fnc_Pattern)functionTable[35])(userData,
459 					*reinterpret_cast<const pattern *>(data)); /* pattern */
460 				break;
461 			}
462 
463 			case B_PIC_SET_SCALE:
464 			{
465 				((fnc_f)functionTable[36])(userData,
466 					*reinterpret_cast<const float *>(data)); /* scale */
467 				break;
468 			}
469 
470 			case B_PIC_SET_FONT_FAMILY:
471 			{
472 				((fnc_Pc)functionTable[37])(userData,
473 					reinterpret_cast<const char *>(data)); /* string */
474 				break;
475 			}
476 
477 			case B_PIC_SET_FONT_STYLE:
478 			{
479 				((fnc_Pc)functionTable[38])(userData,
480 					reinterpret_cast<const char *>(data)); /* string */
481 				break;
482 			}
483 
484 			case B_PIC_SET_FONT_SPACING:
485 			{
486 				((fnc_i)functionTable[39])(userData,
487 					*reinterpret_cast<const int32 *>(data)); /* spacing */
488 				break;
489 			}
490 
491 			case B_PIC_SET_FONT_SIZE:
492 			{
493 				((fnc_f)functionTable[40])(userData,
494 					*reinterpret_cast<const float *>(data)); /* size */
495 				break;
496 			}
497 
498 			case B_PIC_SET_FONT_ROTATE:
499 			{
500 				((fnc_f)functionTable[41])(userData,
501 					*reinterpret_cast<const float *>(data)); /* rotation */
502 				break;
503 			}
504 
505 			case B_PIC_SET_FONT_ENCODING:
506 			{
507 				((fnc_i)functionTable[42])(userData,
508 					*reinterpret_cast<const int32 *>(data)); /* encoding */
509 				break;
510 			}
511 
512 			case B_PIC_SET_FONT_FLAGS:
513 			{
514 				((fnc_i)functionTable[43])(userData,
515 					*reinterpret_cast<const int32 *>(data)); /* flags */
516 				break;
517 			}
518 
519 			case B_PIC_SET_FONT_SHEAR:
520 			{
521 				((fnc_f)functionTable[44])(userData,
522 					*reinterpret_cast<const float *>(data)); /* shear */
523 				break;
524 			}
525 
526 			case B_PIC_SET_FONT_FACE:
527 			{
528 				((fnc_i)functionTable[46])(userData,
529 					*reinterpret_cast<const int32 *>(data)); /* flags */
530 				break;
531 			}
532 
533 			case B_PIC_SET_BLENDING_MODE:
534 			{
535 				((fnc_ss)functionTable[47])(userData,
536 					*reinterpret_cast<const int16 *>(data), /* alphaSrcMode */
537 					*reinterpret_cast<const int16 *>(data + sizeof(int16))); /* alphaFncMode */
538 				break;
539 			}
540 
541 			case B_PIC_SET_TRANSFORM:
542 			{
543 				((fnc_BAffineTransform)functionTable[48])(userData,
544 					*reinterpret_cast<const BAffineTransform *>(data));
545 				break;
546 			}
547 
548 			default:
549 				break;
550 		}
551 
552 		// Skip the already handled block unless it's one of these two,
553 		// since they can contain other nested ops.
554 		if (op != B_PIC_ENTER_STATE_CHANGE && op != B_PIC_ENTER_FONT_STATE) {
555 			pos += size;
556 			data += size;
557 			if (stateBlockSize > 0)
558 				stateBlockSize -= size + 6;
559 			if (fontStateBlockSize > 0)
560 				fontStateBlockSize -= size + 6;
561 		}
562 
563 		// call the exit_state_change hook if needed
564 		if (stateBlockSize == 0) {
565 			((fnc)functionTable[25])(userData);
566 			stateBlockSize = -1;
567 		}
568 
569 		// call the exit_font_state hook if needed
570 		if (fontStateBlockSize == 0) {
571 			((fnc)functionTable[27])(userData);
572 			fontStateBlockSize = -1;
573 		}
574 #if DEBUG
575 		numOps++;
576 #if DEBUG > 1
577 		fprintf(file, "executed in %" B_PRId64 " usecs\n", system_time()
578 			- startOpTime);
579 #endif
580 #endif
581 		// TODO: what if too much was read, should we return B_ERROR?
582 	}
583 
584 #if DEBUG
585 	fprintf(file, "Done! %" B_PRId32 " ops, rendering completed in %"
586 		B_PRId64 " usecs.\n", numOps, system_time() - startTime);
587 	fclose(file);
588 #endif
589 	return B_OK;
590 }
591