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