1 /* Graphics_record.cpp
2  *
3  * Copyright (C) 1992-2005,2007-2020 Paul Boersma
4  *
5  * This code is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or (at
8  * your option) any later version.
9  *
10  * This code is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13  * See the GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this work. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "GraphicsP.h"
20 
21 #define RECORDING_HEADER_LENGTH 2
22 
23 double * _Graphics_check (Graphics me, integer number) {
24 	Melder_assert (number >= 0);
25 	static bool messageHasAlreadyBeenShownOnce = false;
26 	double *result = nullptr;
27 	double *record = my record;
28 	integer nrecord = my nrecord;
29 	if (nrecord == 0) {
30 		nrecord = 1000;
31 		try {
32 			record = Melder_malloc (double, 1 + nrecord);
33 		} catch (MelderError) {
34 			if (messageHasAlreadyBeenShownOnce) {
35 				Melder_clearError ();
36 			} else {
37 				messageHasAlreadyBeenShownOnce = true;
38 				Melder_flushError (U"_Graphics_growRecorder: out of memory.\n"
39 					U"This message will not show up on future occasions.");   // because of loop danger when redrawing
40 			}
41 			return nullptr;
42 		}
43 		my record = record;
44 		my nrecord = nrecord;
AArch64DAGToDAGISel(AArch64TargetMachine & tm,CodeGenOpt::Level OptLevel)45 	}
46 	if (nrecord < my irecord + RECORDING_HEADER_LENGTH + number) {
47 		while (nrecord < my irecord + RECORDING_HEADER_LENGTH + number)
48 			nrecord *= 2;
49 		try {
50 			record = (double *) Melder_realloc (record, (1 + nrecord) * (integer) sizeof (double));
51 		} catch (MelderError) {
52 			if (messageHasAlreadyBeenShownOnce) {
53 				Melder_clearError ();
54 			} else {
55 				messageHasAlreadyBeenShownOnce = true;
56 				Melder_flushError (U"_Graphics_growRecorder: out of memory.\n"
57 					U"This message will not show up on future occasions.");   // because of loop danger when redrawing
58 			}
59 			return nullptr;
60 		}
61 		my record = record;
62 		my nrecord = nrecord;
63 	}
64 	result = my record + my irecord;
65 	my irecord += number + RECORDING_HEADER_LENGTH;
66 	return result;
67 }
68 
69 /***** RECORD AND PLAY *****/
70 
71 bool Graphics_startRecording (Graphics me) {
72 	const bool wasRecording = my recording;
73 	my recording = true;
SelectArithShiftedRegister(SDValue N,SDValue & Reg,SDValue & Shift)74 	return wasRecording;
75 }
76 
SelectLogicalShiftedRegister(SDValue N,SDValue & Reg,SDValue & Shift)77 bool Graphics_stopRecording (Graphics me) {
78 	const bool wasRecording = my recording;
79 	my recording = false;
80 	return wasRecording;
81 }
82 
SelectAddrModeIndexed7S16(SDValue N,SDValue & Base,SDValue & OffImm)83 void Graphics_clearRecording (Graphics me) {
84 	if (my record) {
85 		Melder_free (my record);
86 		my irecord = 0;
87 		my nrecord = 0;
88 	}
89 }
90 
91 void Graphics_play (Graphics me, Graphics thee) {
SelectAddrModeIndexed7S128(SDValue N,SDValue & Base,SDValue & OffImm)92 	const double *p = my record;
93 	const double * const endp = p + my irecord;
94 	const bool wasRecording = my recording;
SelectAddrModeIndexedS9S128(SDValue N,SDValue & Base,SDValue & OffImm)95 	if (! p)
96 		return;
97 	my recording = false;   // temporarily, in case me == thee
SelectAddrModeIndexedU6S128(SDValue N,SDValue & Base,SDValue & OffImm)98 	while (p < endp) {
99 		#define get  (* ++ p)
100 		#define iget  (integer) (* ++ p)
101 		#define mget(n)  (p += n, p - n)
102 		#define sget(n)  ((char *) (p += n, p - n + 1))
103 		int opcode = (int) get;
104 		(void) (integer) get;   // ignore number of arguments
105 		switch (opcode) {
106 			case SET_VIEWPORT: {
107 				const double x1NDC = get, x2NDC = get, y1NDC = get, y2NDC = get;
108 				Graphics_setViewport (thee, x1NDC, x2NDC, y1NDC, y2NDC);
109 			} break;
110 			case SET_INNER: {
111 				Graphics_setInner (thee);
112 			} break;
113 			case UNSET_INNER: {
114 				Graphics_unsetInner (thee);
115 			} break;
116 			case SET_WINDOW: {
117 				const double x1 = get, x2 = get, y1 = get, y2 = get;
118 				Graphics_setWindow (thee, x1, x2, y1, y2);
119 			} break;
120 			case TEXT: {
121 				const double x = get, y = get;
122 				const integer length = iget;
123 				const conststring8 text_utf8 = sget (length);
124 				Graphics_text (thee, x, y, Melder_peek8to32 (text_utf8));
125 			} break;
126 			case POLYLINE: {
127 				const integer n = iget;
128 				const double *x = mget (n), *y = mget (n);
129 				Graphics_polyline (thee, n, & x [1], & y [1]);
130 			} break;
131 			case LINE: {
132 				const double x1 = get, y1 = get, x2 = get, y2 = get;
133 				Graphics_line (thee, x1, y1, x2, y2);
134 			} break;
135 			case ARROW: {
136 				const double x1 = get, y1 = get, x2 = get, y2 = get;
137 				Graphics_arrow (thee, x1, y1, x2, y2);
138 			} break;
139 			case FILL_AREA: {
140 				const integer n = iget;
141 				const double * const x = mget (n), * const y = mget (n);
142 				Graphics_fillArea (thee, n, & x [1], & y [1]);
143 			} break;
144 			case FUNCTION: {
145 				integer n = iget;
146 				const double x1 = get, x2 = get, *y = mget (n);
147 				Graphics_function (thee, y, 1, n, x1, x2);
148 			} break;
149 			case RECTANGLE: {
150 				const double x1 = get, x2 = get, y1 = get, y2 = get;
151 				Graphics_rectangle (thee, x1, x2, y1, y2);
152 			} break;
153 			case FILL_RECTANGLE: {
154 				const double x1 = get, x2 = get, y1 = get, y2 = get;
155 				Graphics_fillRectangle (thee, x1, x2, y1, y2);
156 			} break;
157 			case CIRCLE: {
158 				const double x = get, y = get, r = get;
159 				Graphics_circle (thee, x, y, r);
160 			} break;
161 			case FILL_CIRCLE: {
162 				const double x = get, y = get, r = get;
163 				Graphics_fillCircle (thee, x, y, r);
164 			} break;
165 			case ARC: {
166 				const double x = get, y = get, r = get, fromAngle = get, toAngle = get;
167 				Graphics_arc (thee, x, y, r, fromAngle, toAngle);
168 			} break;
169 			case ARC_ARROW: {
170 				const double x = get, y = get, r = get, fromAngle = get, toAngle = get;
171 				const int arrowAtStart = (int) iget, arrowAtEnd = (int) iget;
172 				Graphics_arcArrow (thee, x, y, r, fromAngle, toAngle, arrowAtStart, arrowAtEnd);
173 			} break;
174 			case HIGHLIGHT: {
175 				const double x1 = get, x2 = get, y1 = get, y2 = get;
176 				Graphics_highlight (thee, x1, x2, y1, y2);
177 			} break;
178 			case CELL_ARRAY: {
179 				const double x1 = get, x2 = get, y1 = get, y2 = get, minimum = get, maximum = get;
180 				const integer nrow = iget, ncol = iget;
181 				/*
182 					We don't copy all the data into a new matrix.
183 					Instead, we create row pointers z [1..nrow] that point directly into the recorded data.
184 					This works because the data is a packed array of double, just as Graphics_cellArray expects.
185 				*/
186 				#if 0
187 				autoMAT z = raw_MAT (nrow, ncol);
188 				for (integer irow = 1; irow <= nrow; irow ++)
189 					for (integer icol = 1; icol <= ncol; icol ++)
190 						z [irow] [icol] = get;
191 				Graphics_cellArray (thee, z.all(), x1, x2, y1, y2, minimum, maximum);
192 				#else
193 				Graphics_cellArray (thee, constMATVU (p + 1, nrow, ncol, ncol, 1), x1, x2, y1, y2, minimum, maximum);
194 				p += nrow * ncol;
195 				#endif
196 			}  break;
197 			case SET_FONT: {
198 				Graphics_setFont (thee, (enum kGraphics_font) get);
199 			} break;
200 			case SET_FONT_SIZE: {
201 				Graphics_setFontSize (thee, get);
202 			} break;
203 			case SET_FONT_STYLE: {
204 				Graphics_setFontStyle (thee, (int) get);
205 			} break;
206 			case SET_TEXT_ALIGNMENT: {
207 				kGraphics_horizontalAlignment hor = (kGraphics_horizontalAlignment) iget;
208 				const int vert = (int) iget;
209 				Graphics_setTextAlignment (thee, hor, vert);
210 			}  break;
211 			case SET_TEXT_ROTATION: {
212 				Graphics_setTextRotation (thee, get);
213 			} break;
214 			case SET_LINE_TYPE: {
215 				Graphics_setLineType (thee, (int) get);
216 			} break;
217 			case SET_LINE_WIDTH: {
218 				Graphics_setLineWidth (thee, get);
219 			} break;
220 			case SET_STANDARD_COLOUR: {   // only used in old Praat picture files
221 				const int standardColour = (int) get;
222 				MelderColour colour =
223 					standardColour == 0 ? Melder_BLACK :
224 					standardColour == 1 ? Melder_WHITE :
225 					standardColour == 2 ? Melder_RED :
226 					standardColour == 3 ? Melder_GREEN :
227 					standardColour == 4 ? Melder_BLUE :
228 					standardColour == 5 ? Melder_CYAN :
229 					standardColour == 6 ? Melder_MAGENTA :
230 					standardColour == 7 ? Melder_YELLOW :
231 					standardColour == 8 ? Melder_MAROON :
232 					standardColour == 9 ? Melder_LIME :
233 					standardColour == 10 ? Melder_NAVY :
234 					standardColour == 11 ? Melder_TEAL :
235 					standardColour == 12 ? Melder_PURPLE :
236 					standardColour == 13 ? Melder_OLIVE :
237 					standardColour == 14 ? Melder_PINK :
238 					standardColour == 15 ? Melder_SILVER :
239 					Melder_GREY;
240 				Graphics_setColour (thee, colour);
241 			} break;
242 			case SET_GREY: {
243 				Graphics_setGrey (thee, get);
244 			} break;
245 			case MARK_GROUP: {
246 				Graphics_markGroup (thee);
247 			} break;
248 			case ELLIPSE: {
249 				const double x1 = get, x2 = get, y1 = get, y2 = get;
250 				Graphics_ellipse (thee, x1, x2, y1, y2);
251 			} break;
252 			case FILL_ELLIPSE: {
253 				const double x1 = get, x2 = get, y1 = get, y2 = get;
254 				Graphics_fillEllipse (thee, x1, x2, y1, y2);
255 			} break;
256 			case CIRCLE_MM: {
257 				const double x = get, y = get, d = get;
258 				Graphics_circle_mm (thee, x, y, d);
259 			} break;
260 			case FILL_CIRCLE_MM: {
261 				const double x = get, y = get, d = get;
262 				Graphics_fillCircle_mm (thee, x, y, d);
263 			} break;
264 			case IMAGE8: {
265 				const double x1 = get, x2 = get, y1 = get, y2 = get;
266 				const uint8 minimum = (uint8) iget, maximum = (uint8) iget;
267 				const integer nrow = iget, ncol = iget;
268 				automatrix <uint8> z = newmatrixzero <uint8> (nrow, ncol);
269 				for (integer irow = 1; irow <= nrow; irow ++)
270 					for (integer icol = 1; icol <= ncol; icol ++)
271 						z [irow] [icol] = (uint8) iget;
272 				Graphics_image8 (thee, z.all(), x1, x2, y1, y2, minimum, maximum);
273 			} break;
274 			case UNHIGHLIGHT: {
275 				(void) mget (4);   // obsolete x1, x2, y1, y2
276 				// do nothing (this has become obsolete since the demise of XOR mode drawing)
277 			} break;
278 			case XOR_ON: {
279 				MelderColour colour; colour. red = get, colour. green = get, colour. blue = get;
280 				Graphics_xorOn (thee, colour);
281 			} break;
282 			case XOR_OFF: {
283 				Graphics_xorOff (thee);
284 			} break;
285 			case RECTANGLE_MM: {
286 				const double x = get, y = get, horSide = get, vertSide = get;
287 				Graphics_rectangle_mm (thee, x, y, horSide, vertSide);
288 			} break;
289 			case FILL_RECTANGLE_MM: {
290 				const double x = get, y = get, horSide = get, vertSide = get;
291 				Graphics_fillRectangle_mm (thee, x, y, horSide, vertSide);
292 			} break;
293 			case SET_WS_WINDOW: {
294 				const double x1NDC = get, x2NDC = get, y1NDC = get, y2NDC = get;
295 				Graphics_setWsWindow (thee, x1NDC, x2NDC, y1NDC, y2NDC);
296 			} break;
297 			case SET_WRAP_WIDTH: {
298 				Graphics_setWrapWidth (thee, get);
299 			} break;
300 			case SET_SECOND_INDENT: {
301 				Graphics_setSecondIndent (thee, get);
302 			} break;
303 			case SET_PERCENT_SIGN_IS_ITALIC: {
304 				Graphics_setPercentSignIsItalic (thee, (bool) get);
305 			} break;
306 			case SET_NUMBER_SIGN_IS_BOLD: {
307 				Graphics_setNumberSignIsBold (thee, (bool) get);
308 			} break;
309 			case SET_CIRCUMFLEX_IS_SUPERSCRIPT: {
310 				Graphics_setCircumflexIsSuperscript (thee, (bool) get);
311 			} break;
312 			case SET_UNDERSCORE_IS_SUBSCRIPT: {
313 				Graphics_setUnderscoreIsSubscript (thee, (bool) get);
314 			} break;
315 			case SET_DOLLAR_SIGN_IS_CODE: {
316 				Graphics_setDollarSignIsCode (thee, (bool) get);
317 			} break;
318 			case SET_AT_SIGN_IS_LINK: {
319 				Graphics_setAtSignIsLink (thee, (bool) get);
320 			} break;
321 			case BUTTON: {
322 				const double x1 = get, x2 = get, y1 = get, y2 = get;
323 				Graphics_button (thee, x1, x2, y1, y2);
324 			} break;
325 			case ROUNDED_RECTANGLE: {
326 				const double x1 = get, x2 = get, y1 = get, y2 = get, r = get;
327 				Graphics_roundedRectangle (thee, x1, x2, y1, y2, r);
328 			} break;
329 			case FILL_ROUNDED_RECTANGLE: {
330 				const double x1 = get, x2 = get, y1 = get, y2 = get, r = get;
331 				Graphics_fillRoundedRectangle (thee, x1, x2, y1, y2, r);
332 			} break;
333 			case FILL_ARC: {
334 				const double x = get, y = get, r = get, fromAngle = get, toAngle = get;
335 				Graphics_fillArc (thee, x, y, r, fromAngle, toAngle);
336 			} break;
337 			case INNER_RECTANGLE: {
338 				const double x1 = get, x2 = get, y1 = get, y2 = get;
339 				Graphics_innerRectangle (thee, x1, x2, y1, y2);
340 			} break;
341 			case CELL_ARRAY8: {
342 				const double x1 = get, x2 = get, y1 = get, y2 = get;
343 				const uint8 minimum = (uint8) iget, maximum = (uint8) iget;
344 				const integer nrow = iget, ncol = iget;
345 				automatrix <uint8> z = newmatrixzero <uint8> (nrow, ncol);
346 				for (integer irow = 1; irow <= nrow; irow ++)
347 					for (integer icol = 1; icol <= ncol; icol ++)
348 						z [irow] [icol] = (uint8) iget;
349 				Graphics_cellArray8 (thee, z.all(), x1, x2, y1, y2, minimum, maximum);
350 			}  break;
351 			case IMAGE: {
352 				const double x1 = get, x2 = get, y1 = get, y2 = get, minimum = get, maximum = get;
353 				const integer nrow = iget, ncol = iget;
354 				autoMAT z = raw_MAT (nrow, ncol);
355 				for (integer irow = 1; irow <= nrow; irow ++)
356 					for (integer icol = 1; icol <= ncol; icol ++)
357 						z [irow] [icol] = get;
358 				Graphics_image (thee, z.all(), x1, x2, y1, y2, minimum, maximum);   // or with constMATVU construction
359 			}  break;
360 			case HIGHLIGHT2: {
361 				const double x1 = get, x2 = get, y1 = get, y2 = get, innerX1 = get, innerX2 = get, innerY1 = get, innerY2 = get;
362 				Graphics_highlight2 (thee, x1, x2, y1, y2, innerX1, innerX2, innerY1, innerY2);
363 			}  break;
364 			case UNHIGHLIGHT2: {
365 				(void) mget (8);   // obsolete x1, x2, y1, y2, innerX1, innerX2, innerY1, innerY2
366 				// do nothing (this has become obsolete since the demise of XOR mode drawing)
367 			}  break;
368 			case SET_ARROW_SIZE: {
369 				Graphics_setArrowSize (thee, get);
370 			} break;
371 			case DOUBLE_ARROW: {
372 				const double x1 = get, y1 = get, x2 = get, y2 = get;
373 				Graphics_doubleArrow (thee, x1, y1, x2, y2);
374 			}  break;
375 			case SET_RGB_COLOUR: {
376 				MelderColour colour;
377 				colour. red = get, colour. green = get, colour. blue = get;
378 				Graphics_setColour (thee, colour);
379 			} break;
380 			case IMAGE_FROM_FILE: {
381 				const double x1 = get, x2 = get, y1 = get, y2 = get;
382 				const integer length = iget;
383 				const conststring8 text_utf8 = sget (length);
384 				Graphics_imageFromFile (thee, Melder_peek8to32 (text_utf8), x1, x2, y1, y2);
385 			}  break;
386 			case POLYLINE_CLOSED: {
387 				const integer n = iget;
388 				const double *x = mget (n), *y = mget (n);
389 				Graphics_polyline_closed (thee, n, & x [1], & y [1]);
390 			} break;
391 			case CELL_ARRAY_COLOUR: {
392 				const double x1 = get, x2 = get, y1 = get, y2 = get, minimum = get, maximum = get;
393 				const integer nrow = iget, ncol = iget;
394 				automatrix <MelderColour> z = newmatrixzero <MelderColour> (nrow, ncol);
395 				for (integer irow = 1; irow <= nrow; irow ++)
396 					for (integer icol = 1; icol <= ncol; icol ++) {
397 						z [irow] [icol]. red = get;
398 						z [irow] [icol]. green = get;
399 						z [irow] [icol]. blue = get;
400 						z [irow] [icol]. transparency = get;
401 					}
402 				Graphics_cellArray_colour (thee, z.all(), x1, x2, y1, y2, minimum, maximum);
403 			}  break;
404 			case IMAGE_COLOUR: {
405 				const double x1 = get, x2 = get, y1 = get, y2 = get, minimum = get, maximum = get;
406 				const integer nrow = iget, ncol = iget;
407 				automatrix <MelderColour> z = newmatrixzero <MelderColour> (nrow, ncol);
408 				for (integer irow = 1; irow <= nrow; irow ++)
409 					for (integer icol = 1; icol <= ncol; icol ++) {
410 						z [irow] [icol]. red = get;
411 						z [irow] [icol]. green = get;
412 						z [irow] [icol]. blue = get;
413 						z [irow] [icol]. transparency = get;
414 					}
415 				Graphics_image_colour (thee, z.all(), x1, x2, y1, y2, minimum, maximum);
416 			}  break;
417 			case SET_COLOUR_SCALE: {
418 				Graphics_setColourScale (thee, (enum kGraphics_colourScale) get);
419 			} break;
420 			case SET_SPECKLE_SIZE: {
421 				Graphics_setSpeckleSize (thee, get);
422 			} break;
423 			case SPECKLE: {
424 				const double x = get, y = get;
425 				Graphics_speckle (thee, x, y);
426 			}  break;
427 			case CLEAR_WS: {
428 				Graphics_clearWs (thee);
429 			} break;
430 			default:
431 				my recording = wasRecording;
432 				Melder_flushError (U"Graphics_play: unknown opcode (", opcode, U").\n", p [-1], U" ", p [1]);
433 				return;
434 		}
435 	}
436 	my recording = wasRecording;
437 }
438 
439 void Graphics_writeRecordings (Graphics me, FILE *f) {
440 	const double * p = my record;
441 	const double * const endp = p + my irecord;
442 	if (! p)
443 		return;
444 	binputi32 (integer_to_int32 (my irecord), f);
445 	while (p < endp) {
446 		#define get  (* ++ p)
447 		const int opcode = (int) get;
448 		binputr32 ((float) opcode, f);
449 		integer numberOfArguments = (integer) get;
450 		const integer largestIntegerRepresentableAs32BitFloat = 0x00FFFFFF;
451 		if (numberOfArguments > largestIntegerRepresentableAs32BitFloat) {
452 			binputr32 (-1.0, f);
453 			binputi32 (integer_to_int32 (numberOfArguments), f);
454 			//Melder_warning ("This picture is very large!");
455 		} else {
456 			binputr32 ((float) numberOfArguments, f);
457 		}
458 		if (opcode == TEXT) {
SelectNegArithImmed(SDValue N,SDValue & Val,SDValue & Shift)459 			binputr32 (get, f);   // x
460 			binputr32 (get, f);   // y
461 			binputr32 (get, f);   // length
462 			Melder_assert (sizeof (double) == 8);
463 			if (uinteger_to_integer (fwrite (++ p, 8, integer_to_uinteger (numberOfArguments - 3), f)) < numberOfArguments - 3)   // text
464 				Melder_throw (U"Error writing graphics recordings.");
465 			p += numberOfArguments - 4;
466 		} else if (opcode == IMAGE_FROM_FILE) {
467 			binputr32 (get, f);   // x1
468 			binputr32 (get, f);   // x2
469 			binputr32 (get, f);   // y1
470 			binputr32 (get, f);   // y2
471 			binputr32 (get, f);   // length
472 			Melder_assert (sizeof (double) == 8);
473 			if (uinteger_to_integer (fwrite (++ p, 8, integer_to_uinteger (numberOfArguments - 5), f)) < numberOfArguments - 5)   // text
474 				Melder_throw (U"Error writing graphics recordings.");
475 			p += numberOfArguments - 6;
476 		} else {
477 			for (integer i = numberOfArguments; i > 0; i --)
478 				binputr32 (get, f);
479 		}
480 	}
481 }
482 
483 void Graphics_readRecordings (Graphics me, FILE *f) {
484 	integer old_irecord = my irecord;
485 	integer added_irecord = 0;
486 	double* p = nullptr;
487 	double* endp = nullptr;
488 	integer numberOfArguments = 0;
489 	int opcode = 0;   // large scope on behalf of message
490 	try {
491 		added_irecord = bingeti32 (f);
getShiftTypeForNode(SDValue N)492 		p = _Graphics_check (me, added_irecord - RECORDING_HEADER_LENGTH);
493 		if (! p)
494 			return;
495 		Melder_assert (my irecord == old_irecord + added_irecord);
496 		endp = p + added_irecord;
497 		while (p < endp) {
498 			opcode = (int) bingetr32 (f);
499 			put (opcode);
500 			numberOfArguments = (integer) bingetr32 (f);
501 			if (numberOfArguments == -1)
502 				numberOfArguments = bingeti32 (f);
503 			put (numberOfArguments);
504 			if (opcode == TEXT) {
505 				put (bingetr32 (f));   // x
506 				put (bingetr32 (f));   // y
507 				put (bingetr32 (f));   // length
508 				if (uinteger_to_integer (fread (++ p, 8, integer_to_uinteger (numberOfArguments - 3), f)) < numberOfArguments - 3)   // text
isWorthFoldingSHL(SDValue V)509 					Melder_throw (U"Error reading graphics recordings.");
510 				p += numberOfArguments - 4;
511 			} else if (opcode == IMAGE_FROM_FILE) {
512 				put (bingetr32 (f));   // x1
513 				put (bingetr32 (f));   // x2
514 				put (bingetr32 (f));   // y1
515 				put (bingetr32 (f));   // y2
516 				put (bingetr32 (f));   // length
517 				if (uinteger_to_integer (fread (++ p, 8, integer_to_uinteger (numberOfArguments - 5), f)) < numberOfArguments - 5)   // text
518 					Melder_throw (U"Error reading graphics recordings.");
519 				p += numberOfArguments - 6;
520 			} else {
521 				for (integer i = numberOfArguments; i > 0; i --)
522 					put (bingetr32 (f));
523 			}
524 		}
525 	} catch (MelderError) {
526 		my irecord = old_irecord;
527 		Melder_throw (U"Error reading graphics record ", added_irecord - (integer) (endp - p),
528 			U" out of ", added_irecord, U".\nOpcode ", opcode, U", args ", numberOfArguments, U".");
529 	}
530 }
531 
isWorthFolding(SDValue V) const532 void Graphics_markGroup (Graphics me) {
533 	if (my recording) { op (MARK_GROUP, 0); }
534 }
535 
536 void Graphics_undoGroup (Graphics me) {
537 	integer lastMark = 0;   // not yet found
538 	integer jrecord = 0;
539 	while (jrecord < my irecord) {   // keep looking for marks until the end
540 		const int opcode = (int) my record [++ jrecord];
541 		integer number = (integer) my record [++ jrecord];
542 		if (opcode == MARK_GROUP)
543 			lastMark = jrecord - 1;   // found a mark
544 		jrecord += number;
545 	}
546 	if (jrecord != my irecord)
547 		Melder_flushError (U"jrecord != my irecord: ", jrecord, U", ", my irecord);
548 	if (lastMark > 0)   // found?
549 		my irecord = lastMark - 1;   // forget all graphics from and including the last mark
550 }
551 
552 /* End of file Graphics_record.cpp */
553