1 /* ligkern_routines.c: The ligature/kerning table.
2
3 This file is part of Omega,
4 which is based on the web2c distribution of TeX,
5
6 Copyright (c) 1994--2001 John Plaice and Yannis Haralambous
7
8 Omega is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
12
13 Omega is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Omega; if not, write to the Free Software Foundation, Inc.,
20 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
21
22 */
23
24 #include "cpascal.h"
25 #include "manifests.h"
26 #include "list_routines.h"
27 #include "ligkern_routines.h"
28 #include "char_routines.h"
29 #include "out_routines.h"
30 #include "parse_ofm.h"
31 #include "print_routines.h"
32 #include "error_routines.h"
33 #include "header_routines.h"
34 #include "out_ofm.h"
35
36 unsigned nk=0;
37 unsigned nl=0;
38
39 fix *kern_table;
40 av_list kern_list = NULL;
41 unsigned no_kerns = 0;
42
43 unsigned min_nl=0;
44 unsigned bchar = CHAR_BOUNDARY;
45 #define MAX_LABEL 0x7fffffff
46 unsigned bchar_label = MAX_LABEL;
47 unsigned bchar_remainder;
48 unsigned lk_step_ended=FALSE;
49
50 #define LIG_KERN_CHUNK 512
51 four_entries *lig_kern_table;
52 unsigned char *activity;
53 static int lig_kern_size;
54
55 void
set_boundary_character(unsigned c)56 set_boundary_character(unsigned c)
57 {
58 /* What checks are required ? */
59 bchar = c;
60 }
61
62 void
init_ligkern(void)63 init_ligkern(void)
64 {
65 lk_step_ended = FALSE;
66 nl = 0;
67 min_nl = 0;
68 lig_kern_size = LIG_KERN_CHUNK;
69 lig_kern_table = (four_entries *) xcalloc(lig_kern_size, sizeof(four_entries));
70 }
71
72 static void
lig_kern_incr(void)73 lig_kern_incr(void)
74 {
75 nl++;
76 if (nl < lig_kern_size)
77 return;
78 lig_kern_size += LIG_KERN_CHUNK;
79 lig_kern_table = (four_entries *)
80 xrealloc(lig_kern_table, lig_kern_size * sizeof(four_entries));
81 memset(lig_kern_table + nl, 0, LIG_KERN_CHUNK * sizeof(four_entries));
82 }
83
84 void
set_label_command(unsigned c)85 set_label_command(unsigned c)
86 {
87 if (c==CHAR_BOUNDARY) { /* BOUNDARYCHAR */
88 bchar_label = nl;
89 } else {
90 check_char_tag(c);
91 set_char_tag(c, TAG_LIG);
92 set_char_remainder(c, nl);
93 }
94 if (min_nl <= nl) { min_nl = nl+1; }
95 lk_step_ended = FALSE;
96 no_labels++;
97
98 }
99
100 void
set_stop_command(void)101 set_stop_command(void)
102 {
103 if (lk_step_ended == TRUE) {
104 lig_kern_table[nl-1].entries[0] =
105 lig_kern_table[nl-1].entries[0] / 256 * 256 + STOP_FLAG;
106 } else {
107 warning_0("STOP must follow LIG or KRN; ignored");
108 }
109 lk_step_ended = FALSE;
110 }
111
112 void
set_skip_command(unsigned val)113 set_skip_command(unsigned val)
114 {
115 if (lk_step_ended == TRUE) {
116 if (val>127) {
117 warning_0("Maximum SKIP amount is 127; ignored");
118 } else {
119 lig_kern_table[nl-1].entries[0] = val;
120 if (min_nl <= (nl+val)) { min_nl = nl+val+1; }
121 }
122 } else {
123 warning_0("SKIP must follow LIG or KRN; ignored");
124 }
125 lk_step_ended = FALSE;
126 }
127
128 void
set_ligature_command(unsigned lig,unsigned c,unsigned val)129 set_ligature_command(unsigned lig, unsigned c, unsigned val)
130 {
131 lig_kern_table[nl].entries[0] = 0;
132 lig_kern_table[nl].entries[1] = c;
133 lig_kern_table[nl].entries[2] = lig;
134 lig_kern_table[nl].entries[3] = val;
135 lig_kern_incr();
136 lk_step_ended = TRUE;
137
138 }
139
140 static int set_new_kern(fix);
141
142 void
set_kerning_command(unsigned c,fix fval)143 set_kerning_command(unsigned c, fix fval)
144 {
145 unsigned k = set_new_kern(fval);
146
147 lig_kern_table[nl].entries[0] = 0;
148 lig_kern_table[nl].entries[1] = c;
149 if (ofm_level==OFM_TFM) {
150 lig_kern_table[nl].entries[2] = KERN_FLAG + (k/256);
151 lig_kern_table[nl].entries[3] = k % 256;
152 } else {
153 lig_kern_table[nl].entries[2] = KERN_FLAG + (k/65536);
154 lig_kern_table[nl].entries[3] = k % 65536;
155 }
156 lig_kern_incr();
157 lk_step_ended = TRUE;
158 }
159
160 void
set_c_label_command(unsigned new_class)161 set_c_label_command(unsigned new_class)
162 {
163 fatal_error_0("CLABEL not currently supported");
164 }
165
166 void
set_c_kerning_command(unsigned new_class,fix fval)167 set_c_kerning_command(unsigned new_class, fix fval)
168 {
169 fatal_error_0("CKRN not currently supported");
170 }
171
172 void
set_c_glue_command(unsigned new_class,unsigned glue_index)173 set_c_glue_command(unsigned new_class, unsigned glue_index)
174 {
175 fatal_error_0("CGLUE not currently supported");
176 }
177
178 void
set_c_penalty_command(unsigned new_class,unsigned pen_index)179 set_c_penalty_command(unsigned new_class, unsigned pen_index)
180 {
181 fatal_error_0("CPEN not currently supported");
182 }
183
184 void
set_c_penglue_command(unsigned new_class,unsigned pen_index,unsigned glue_index)185 set_c_penglue_command(unsigned new_class, unsigned pen_index, unsigned glue_index)
186 {
187 fatal_error_0("CPENGLUE not currently supported");
188 }
189
190 static int
set_new_kern(fix fval)191 set_new_kern(fix fval)
192 {
193 unsigned index;
194 av_list L1, L2;
195
196 L1 = kern_list;
197 if (L1 == NULL) {
198 index = nk++;
199 kern_list = av_list1(index, fval);
200 } else {
201 L2 = L1->ptr;
202 while ((lval(L1) != fval) && (L2 != NULL)) {
203 L1 = L2;
204 L2 = L2->ptr;
205 }
206 if (fval == lval(L1)) {
207 index = lattr(L1);
208 } else {
209 index = nk++;
210 if (index == (ofm_level==OFM_TFM ? 0x8000 : 0x800000))
211 fatal_error_1("more than %d different kerns", index);
212 L2 = av_list1(index, fval);
213 L1->ptr = L2;
214 }
215 }
216 return index;
217 }
218
219
220 void
build_kern_table(void)221 build_kern_table(void)
222 {
223 av_list L1 = kern_list, L2;
224 unsigned i = 0;
225
226 kern_table = (fix *) xmalloc((nk+1)*sizeof(int));
227 while (L1 != NULL) {
228 kern_table[i] = lval(L1);
229 L2 = L1->ptr;
230 free(L1); L1 = L2;
231 i++;
232 }
233 }
234
235 void
print_ligkern_table(void)236 print_ligkern_table(void)
237 {
238 unsigned i;
239
240 sort_ptr = 1;
241 if (nl>0) {
242 left(); out("LIGTABLE"); out_ln();
243 for (i=0; i<nl; i++) {
244 unsigned r = lig_kern_table[i].entries[0];
245 if (r >= 256)
246 activity[i] = A_ACCESSIBLE;
247 else if ((activity[i] == A_ACCESSIBLE) && (r < STOP_FLAG)) {
248 r += i + 1;
249 if (r >= nl) {
250 fprintf(stderr, "Bad OFM file: Ligature/kern step %u skips too far;\n"
251 "I made it stop.\n", i);
252 lig_kern_table[i].entries[0] = STOP_FLAG;
253 changed = TRUE;
254 } else
255 activity[r] = A_ACCESSIBLE;
256 }
257 }
258 for (i=0; i<nl; i++) {
259 if (activity[i] == A_UNREACHABLE) {
260 if (parenthesis_level == 1) {
261 left();
262 out("COMMENT THIS PART OF THE PROGRAM IS NEVER USED!");
263 out_ln();
264 }
265 } else if (parenthesis_level == 2)
266 right();
267 if (activity[i] != A_PASS_THROUGH) {
268 while ((sort_ptr<=label_ptr) && (i==label_table[sort_ptr].rr)) {
269 print_label_command(label_table[sort_ptr].cc);
270 sort_ptr++;
271 }
272 print_one_lig_kern_entry(lig_kern_table+i, TRUE);
273 }
274 }
275 if (parenthesis_level == 2) /* the final step was unreachable */
276 right();
277 right();
278 }
279 }
280
281 void
print_one_lig_kern_entry(four_entries * lentry,boolean show_stop)282 print_one_lig_kern_entry(four_entries *lentry, boolean show_stop)
283 {
284 if (lentry->entries[2] >= KERN_FLAG) {
285 unsigned r = (ofm_level==OFM_TFM ? 256 : 65536)*(lentry->entries[2]-KERN_FLAG)+lentry->entries[3];
286 fix v;
287 if (r < nk)
288 v = kern_table[r];
289 else {
290 if (show_stop == TRUE)
291 fprintf(stderr, "Bad OFM file: Kern index too large.\n");
292 v = 0;
293 changed = TRUE;
294 }
295 print_kerning_command(lentry->entries[1], v);
296 } else {
297 print_ligature_command(lentry->entries[2],
298 lentry->entries[1],
299 lentry->entries[3]);
300 }
301 if ((show_stop == TRUE) && (lentry->entries[0] > 0) && (parenthesis_level == 1)) {
302 if (lentry->entries[0] >= STOP_FLAG) {
303 print_stop_command();
304 } else { /* count number of accessible steps */
305 unsigned i, k = lentry-lig_kern_table, count = 0;
306 for (i=1; i<=lentry->entries[0]; i++)
307 if (activity[k+i] == A_ACCESSIBLE)
308 count++;
309 print_skip_command(count);
310 }
311 }
312 }
313
314 hash_list hash_table[PRIME];
315
316 unsigned x_lig_cycle;
317 unsigned y_lig_cycle = CHAR_BOUNDARY;
318
319 static int l_eval(unsigned, unsigned);
320
321 static int
l_f(hash_list h,unsigned x,unsigned y)322 l_f(hash_list h, unsigned x, unsigned y)
323 {
324 switch(h->new_class) {
325 case LIG_SIMPLE: {break;}
326 case LIG_LEFT_Z: {
327 h->new_class = LIG_PENDING;
328 h->lig_z = l_eval(h->lig_z, y);
329 h->new_class = LIG_SIMPLE;
330 break;
331 }
332 case LIG_RIGHT_Z: {
333 h->new_class = LIG_PENDING;
334 h->lig_z = l_eval(x, h->lig_z);
335 h->new_class = LIG_SIMPLE;
336 break;
337 }
338 case LIG_BOTH_Z: {
339 h->new_class = LIG_PENDING;
340 h->lig_z = l_eval(l_eval(x,h->lig_z), y);
341 h->new_class = LIG_SIMPLE;
342 break;
343 }
344 case LIG_PENDING: {
345 x_lig_cycle = x;
346 y_lig_cycle = y;
347 h->lig_z = CHAR_ERROR;
348 h->new_class = LIG_SIMPLE;
349 break;
350 }
351 default: {
352 internal_error_1("f (new_class=%d)", h->new_class);
353 }
354 }
355 return (h->lig_z);
356 }
357
358 static hash_list l_hash_lookup(unsigned, unsigned);
359
360 static int
l_eval(unsigned x,unsigned y)361 l_eval(unsigned x, unsigned y)
362 {
363 hash_list h;
364
365 if ((x==CHAR_ERROR) || (y==CHAR_ERROR)) return CHAR_ERROR;
366 h = l_hash_lookup(x, y);
367 if (h == NULL) return y;
368 return l_f(h, x, y);
369 }
370
371 static int
l_hash_input(unsigned p,unsigned c)372 l_hash_input(unsigned p, unsigned c)
373 {
374
375 four_entries *entry = lig_kern_table+p;
376 unsigned y = entry->entries[1];
377 unsigned t = entry->entries[2];
378 unsigned cc = LIG_SIMPLE;
379 unsigned zz = entry->entries[3];
380 unsigned key;
381 hash_list L1;
382
383 if (t >= KERN_FLAG) zz = y;
384 else {
385 switch(t) {
386 case L_0:
387 case L_Ax: { break; }
388 case L_Bx:
389 case L_ABxx: { zz = y; break; }
390 case L_B:
391 case L_ABx: { cc = LIG_LEFT_Z; break; }
392 case L_A: { cc = LIG_RIGHT_Z; break; }
393 case L_AB: { cc = LIG_BOTH_Z; break; }
394 default: {
395 internal_error_1("l_hash_input (case=%d)", t);
396 }
397 }
398 }
399 key = (c & 0x7fff)*(y & 0x7fff) % PRIME;
400 L1 = hash_table[key];
401 if ((L1 == NULL) || (c < L1->x) || ((c == L1->x) && (y < L1->y)))
402 hash_table[key] = hash_list1(c,y,cc,zz,L1);
403 else {
404 hash_list L2 = L1->ptr;
405 while ((L2 != NULL) && ((c > L2->x) || ((c == L2->x) && (y > L2->y)))) {
406 L1 = L2;
407 L2 = L2->ptr;
408 }
409 if ((L2 == NULL) || (c < L2->x) || ((c == L2->x) && (y < L2->y)))
410 L1->ptr = hash_list1(c,y,cc,zz,L2);
411 else
412 return FALSE;
413 }
414 return TRUE;
415 }
416
417 static hash_list
l_hash_lookup(unsigned x,unsigned y)418 l_hash_lookup(unsigned x, unsigned y)
419 {
420 unsigned key = (x & 0x7fff)*(y & 0x7fff) % PRIME;
421 hash_list L = hash_table[key];
422
423 while ((L != NULL) && ((x > L->x) || ((x == L->x) && (y > L->y))))
424 L = L->ptr;
425 if ((L == NULL) || (x < L->x) || ((x == L->x) && (y < L->y)))
426 return NULL;
427 return L;
428 }
429
430 void
check_ligature_ends_properly(void)431 check_ligature_ends_properly(void)
432 {
433 if (nl>0) {
434 if (bchar_label < nl) {
435 /* make room for it; the actual label will be stored later */
436 lig_kern_table[nl].entries[0] = 255;
437 lig_kern_incr();
438 }
439 while (min_nl > nl) {
440 lig_kern_table[nl].entries[0] = 255;
441 lig_kern_incr();
442 }
443 if (lig_kern_table[nl-1].entries[0] == 0) {
444 lig_kern_table[nl-1].entries[0] = STOP_FLAG;
445 }
446 }
447 }
448
449 void
check_ligature_program(unsigned c,unsigned lab)450 check_ligature_program(unsigned c, unsigned lab)
451 {
452 unsigned lig_ptr = lab;
453 four_entries *entry;
454
455 while (lig_ptr < nl) {
456 entry = lig_kern_table+lig_ptr;
457 if (l_hash_input(lig_ptr,c)) {
458 if (entry->entries[2] < KERN_FLAG) {
459 if (entry->entries[1] != bchar) {
460 check_existence_and_safety(c, entry->entries[1],
461 ligature_commands[entry->entries[2]],
462 "%s character examined by (H %X)");
463 if (entry->entries[1] > 0xffff) {
464 fprintf(stderr, "%s character (H %X) examined by (H %X) "
465 "exceeds ffff\n", ligature_commands[entry->entries[2]], entry->entries[1], c);
466 exit(2);
467 }
468 }
469 if (entry->entries[3] >= 128)
470 /* Needs fixing */
471 if ((c < 128) || (c == -1))
472 if ((entry->entries[1] < 128) ||
473 (entry->entries[1] == bchar))
474 seven_bit_calculated = 0;
475 check_existence_and_safety(c, entry->entries[3],
476 ligature_commands[entry->entries[2]],
477 "%s character generated by (H %X)");
478 if (entry->entries[3] >0xffff) {
479 fprintf(stderr, "%s character (H %X) generated by (H %X) "
480 "exceeds ffff\n", ligature_commands[entry->entries[2]], entry->entries[3], c);
481 exit(2);
482 }
483 } else {
484 check_existence_and_safety(c, entry->entries[1],
485 "KRN", "%s character examined by (H %X)");
486 if (entry->entries[1] > 0xffff) {
487 fprintf(stderr, "KRN character (H %X) examined by (H %X) "
488 "exceeds ffff\n", entry->entries[1], c);
489 exit(2);
490 }
491 }
492 }
493 if (entry->entries[0] >= STOP_FLAG) lig_ptr = nl;
494 else lig_ptr = lig_ptr + 1 + entry->entries[0];
495 }
496 }
497
498 void
check_ligature_infinite_loops(void)499 check_ligature_infinite_loops(void)
500 {
501 unsigned key;
502
503 for (key = 0; key < PRIME; key++) {
504 hash_list tt = hash_table[key];
505 while (tt != NULL) {
506 if (tt->new_class > LIG_SIMPLE)
507 l_f(tt, tt->x, tt->y);
508 tt = tt->ptr;
509 }
510 }
511
512 if (y_lig_cycle != CHAR_BOUNDARY) {
513 if (x_lig_cycle == CHAR_BOUNDARY) {
514 warning_1("Infinite ligature loop starting with boundary and %d",
515 y_lig_cycle);
516 } else {
517 warning_2("Infinite ligature loop starting with %d and %d",
518 x_lig_cycle, y_lig_cycle);
519 }
520 clear_ligature_entries();
521 nl = 0; bchar = CHAR_BOUNDARY; bchar_label = MAX_LABEL;
522 }
523 }
524
525 void
doublecheck_ligatures(void)526 doublecheck_ligatures(void)
527 {
528 unsigned i;
529
530 if (nl>0) {
531 for (i=0; i<nl; i++) {
532 if (lig_kern_table[i].entries[2] < KERN_FLAG) {
533 if (lig_kern_table[i].entries[0] < 255) {
534 doublecheck_existence(
535 lig_kern_table[i].entries[1],
536 ligature_commands[lig_kern_table[i].entries[2]],
537 "Unused %s step refers to nonexistent character (H %X)");
538 doublecheck_existence(
539 lig_kern_table[i].entries[3],
540 ligature_commands[lig_kern_table[i].entries[2]],
541 "Unused %s step refers to nonexistent character (H %X)");
542 }
543 } else {
544 doublecheck_existence(
545 lig_kern_table[i].entries[1],
546 "KRN",
547 "Unused %s step refers to nonexistent character (H %X)");
548 }
549 }
550 }
551 }
552
553 void
output_ofm_ligkern(void)554 output_ofm_ligkern(void)
555 {
556 unsigned i;
557 four_entries *entry;
558
559 if (extra_loc_needed) { /* lk_offset==1 */
560 if (ofm_level == OFM_TFM) {
561 out_ofm(255); out_ofm(bchar); out_ofm(0); out_ofm(0);
562 } else {
563 out_ofm_2(255); out_ofm_2(bchar); out_ofm_2(0); out_ofm_2(0);
564 }
565 } else { /* output the redirection specs */
566 if (ofm_level == OFM_TFM) {
567 for (sort_ptr = 1; sort_ptr <= lk_offset; sort_ptr++) {
568 unsigned t = label_table[label_ptr].rr;
569 if (bchar != CHAR_BOUNDARY) {
570 out_ofm(255); out_ofm(bchar);
571 } else {
572 out_ofm(254); out_ofm(0);
573 }
574 out_ofm_2(t+lk_offset);
575 do {
576 label_ptr--;
577 } while(label_table[label_ptr].rr == t);
578 }
579 } else {
580 for (sort_ptr = 1; sort_ptr <= lk_offset; sort_ptr++) {
581 unsigned t = label_table[label_ptr].rr;
582 if (bchar != CHAR_BOUNDARY) {
583 out_ofm_2(255); out_ofm_2(bchar);
584 } else {
585 out_ofm_2(254); out_ofm_2(0);
586 }
587 out_ofm_2((t+lk_offset) / 256); out_ofm_2((t+lk_offset) % 256);
588 do {
589 label_ptr--;
590 } while(label_table[label_ptr].rr == t);
591 }
592 }
593 }
594
595 if (ofm_level == OFM_TFM) {
596 for (i=0; i<nl; i++) {
597 entry = lig_kern_table+i;
598 out_ofm(entry->entries[0] & 0xff);
599 out_ofm(entry->entries[1] & 0xff);
600 out_ofm(entry->entries[2] & 0xff);
601 out_ofm(entry->entries[3] & 0xff);
602 }
603 } else {
604 for (i=0; i<nl; i++) {
605 entry = lig_kern_table+i;
606 out_ofm_2(entry->entries[0] & 0xffff);
607 out_ofm_2(entry->entries[1] & 0xffff);
608 out_ofm_2(entry->entries[2] & 0xffff);
609 out_ofm_2(entry->entries[3] & 0xffff);
610 }
611 }
612 for (i=0; i<nk; i++) {
613 out_ofm_4(kern_table[i]);
614 }
615 }
616
617 void
retrieve_ligkern_table(unsigned char * ofm_lig_table,unsigned char * ofm_kern_table)618 retrieve_ligkern_table(unsigned char *ofm_lig_table,
619 unsigned char *ofm_kern_table)
620 {
621 unsigned i;
622 four_entries *entry;
623 unsigned char *table_entry;
624
625 lig_kern_size = nl;
626 lig_kern_table = (four_entries *) xcalloc(lig_kern_size, sizeof(four_entries));
627
628 if (ofm_level == OFM_TFM) {
629 for (i=0; i<nl; i++) {
630 entry = lig_kern_table+i;
631 table_entry = ofm_lig_table+(4*i);
632 entry->entries[0] = (*table_entry) & 0xff;
633 entry->entries[1] = (*(table_entry+1)) & 0xff;
634 entry->entries[2] = (*(table_entry+2)) & 0xff;
635 entry->entries[3] = (*(table_entry+3)) & 0xff;
636 }
637 } else {
638 for (i=0; i<nl; i++) {
639 entry = lig_kern_table+i;
640 table_entry = ofm_lig_table+(8*i);
641 entry->entries[0] = (((*table_entry) & 0xff) * 0x100)
642 + ((*(table_entry+1)) & 0xff);
643 entry->entries[1] = (((*(table_entry+2)) & 0xff) * 0x100)
644 + ((*(table_entry+3)) & 0xff);
645 entry->entries[2] = (((*(table_entry+4)) & 0xff) * 0x100)
646 + ((*(table_entry+5)) & 0xff);
647 entry->entries[3] = (((*(table_entry+6)) & 0xff) * 0x100)
648 + ((*(table_entry+7)) & 0xff);
649 }
650 }
651
652 activity = (unsigned char *) xcalloc(lig_kern_size, sizeof(unsigned char));
653
654 if (nl > 0) {
655 if (lig_kern_table[0].entries[0] == 255) {
656 bchar = lig_kern_table[0].entries[1];
657 print_boundary_char(bchar);
658 activity[0] = A_PASS_THROUGH;
659 }
660 if (lig_kern_table[nl-1].entries[0] == 255) {
661 unsigned r = 256 * lig_kern_table[nl-1].entries[2] + lig_kern_table[nl-1].entries[3];
662 if (r >= nl) {
663 fprintf(stderr, "Ligature/kern starting index for boundarychar is too large;\n"
664 "so I removed it.\n");
665 changed = TRUE;
666 } else {
667 bchar_label = r;
668 activity[r] = A_ACCESSIBLE;
669 }
670 activity[nl-1] = A_PASS_THROUGH;
671 }
672 }
673
674 kern_table = (fix *) xmalloc((nk+1)*sizeof(int));
675 for (i=0; i<nk; i++) {
676 table_entry = ofm_kern_table+(4*i);
677 kern_table[i] = (((*table_entry) & 0xff) << 24) |
678 (((*(table_entry+1)) & 0xff) << 16) |
679 (((*(table_entry+2)) & 0xff) << 8) |
680 ((*(table_entry+3)) & 0xff);
681 }
682 }
683