1 /******************************************************************************
2 /
3 / File: CC.cpp
4 /
5 / Description: Closed caption module.
6 /
7 / Copyright 2001, Carlos Hasan
8 /
9 *******************************************************************************/
10
11 #include <stdio.h>
12 #include <Debug.h>
13 #include "CC.h"
14
CCaption(CCapture & capture)15 CCaption::CCaption(CCapture & capture)
16 : fCapture(capture),
17 fChannel(C_RADEON_CC1),
18 fRow(0),
19 fColumn(0),
20 fColor(0),
21 fLastControlCode(0)
22 {
23 for (int row = 0; row < C_RADEON_CC_ROWS; row++) {
24 for (int column = 0; column < C_RADEON_CC_COLUMNS; column++) {
25 fText[row][column] = fDisplayText[row][column] = 0x0000;
26 }
27 }
28 }
29
~CCaption()30 CCaption::~CCaption()
31 {
32 }
33
InitCheck() const34 status_t CCaption::InitCheck() const
35 {
36 return fCapture.InitCheck();
37 }
38
SetMode(cc_mode mode)39 void CCaption::SetMode(cc_mode mode)
40 {
41 fChannel = mode;
42 fLastControlCode = 0;
43 }
44
DecodeBits(const unsigned char * buffer,int & data)45 bool CCaption::DecodeBits(const unsigned char * buffer, int & data)
46 {
47 // assume the buffer is long enough (at least VBI line width bytes)
48 if (buffer == NULL)
49 return false;
50
51 // compute low/high levels
52 int low = 0xff, high = 0x00;
53 for (int offset = C_RADEON_CC_BLANK_START; offset < C_RADEON_CC_BLANK_START + 4 * C_RADEON_CC_BIT_DURATION; offset++) {
54 if (low > buffer[offset])
55 low = buffer[offset];
56 if (high < buffer[offset])
57 high = buffer[offset];
58 }
59 if (low + C_RADEON_CC_LEVEL_THRESHOLD >= high)
60 return false;
61
62 const int middle = (low + high) >> 1;
63
64 // find position of the first pulse
65 int start = C_RADEON_CC_BLANK_START + C_RADEON_CC_BIT_DURATION;
66 while (start <= C_RADEON_CC_BLANK_START + 4 * C_RADEON_CC_BIT_DURATION) {
67 if (buffer[start + 0] < middle && buffer[start + 1] < middle &&
68 buffer[start + 3] > middle && buffer[start + 4] > middle)
69 break;
70 start++;
71 }
72 if (start >= C_RADEON_CC_BLANK_START + 4 * C_RADEON_CC_BIT_DURATION)
73 return false;
74
75 // compute position of the last pulse
76 int end = start + 17 * C_RADEON_CC_BIT_DURATION;
77
78 // start in middle of first pulse
79 int bit = 0;
80 bool one = true;
81 for (int offset = start + C_RADEON_CC_BIT_DURATION / 2; offset < end; offset++) {
82 int width = 1;
83
84 // search the next pulse front
85 while (offset < end) {
86 if (one) {
87 if (buffer[offset + 0] > middle && buffer[offset + 1] > middle &&
88 buffer[offset + 3] < middle && buffer[offset + 4] < middle)
89 break;
90 }
91 else {
92 if (buffer[offset + 0] < middle && buffer[offset + 1] < middle &&
93 buffer[offset + 3] > middle && buffer[offset + 4] > middle)
94 break;
95 }
96 offset++;
97 width++;
98 }
99
100 // compute pulse width in bits
101 const int nbits = (width + (bit == 0 ? 0 : bit == 15 ? C_RADEON_CC_BIT_DURATION :
102 C_RADEON_CC_BIT_DURATION / 2)) / C_RADEON_CC_BIT_DURATION;
103 data >>= nbits;
104 if (one)
105 data |= (0xffff << (16 - nbits)) & 0xffff;
106
107 if ((bit += nbits) >= C_RADEON_CC_BITS_PER_FIELD)
108 break;
109
110 one = !one;
111 }
112
113 if (bit != C_RADEON_CC_BITS_PER_FIELD)
114 return false;
115
116 return true;
117 }
118
Decode(const unsigned char * buffer0,const unsigned char * buffer1)119 bool CCaption::Decode(const unsigned char * buffer0,
120 const unsigned char * buffer1)
121 {
122 enum caption_code {
123 /* channel */
124 C_CC1 = 0x0000,
125 C_CC2 = 0x0800,
126
127 /* address */
128 C_ROW_1 = 0x0100,
129 C_ROW_2 = 0x0120,
130 C_ROW_3 = 0x0200,
131 C_ROW_4 = 0x0220,
132 C_ROW_5 = 0x0500,
133 C_ROW_6 = 0x0520,
134 C_ROW_7 = 0x0600,
135 C_ROW_8 = 0x0620,
136 C_ROW_9 = 0x0700,
137 C_ROW_10 = 0x0720,
138 C_ROW_11 = 0x0000,
139 C_ROW_12 = 0x0300,
140 C_ROW_13 = 0x0320,
141 C_ROW_14 = 0x0400,
142 C_ROW_15 = 0x0420,
143
144 /* color */
145 C_WHITE = 0x0000,
146 C_GREEN = 0x0002,
147 C_BLUE = 0x0004,
148 C_CYAN = 0x0006,
149 C_RED = 0x0008,
150 C_YELLOW = 0x000a,
151 C_MAGENTA = 0x000c,
152 C_ITALICS = 0x000e,
153
154 /* underline */
155 C_NORMAL = 0x0000,
156 C_UNDERLINE = 0x0001,
157
158 /* control */
159 C_RCL = 0x0000, // resume caption loading
160 C_BS = 0x0001, // backspace
161 C_AOF = 0x0002, // alarm off
162 C_AON = 0x0003, // alarm on
163 C_DER = 0x0004, // delete to end of row
164 C_RU2 = 0x0005, // roll-up captions (2 rows)
165 C_RU3 = 0x0006, // roll-up captions (3 rows)
166 C_RU4 = 0x0007, // roll-up captions (4 rows)
167 C_FON = 0x0008, // flash on
168 C_RDC = 0x0009, // resume direct captioning
169 C_TR = 0x000a, // text restart
170 C_RTD = 0x000b, // resume text display
171 C_EDM = 0x000c, // erase displayed memory
172 C_CR = 0x000d, // carriage return
173 C_ENM = 0x000e, // erase non-displayed memory
174 C_EOC = 0x000f, // end of caption
175
176 /* special character */
177 C_reg = 0x0000, // registered
178 C_deg = 0x0001, // degree
179 C_frac12 = 0x0002, // fraction 1/2
180 C_iquest = 0x0003, // invert question
181 C_trademark = 0x0004, // trademark
182 C_cent = 0x0005, // cent
183 C_pound = 0x0006, // pound
184 C_music = 0x0007, // music note
185 C_agrave = 0x0008, // agrave
186 C_tspace = 0x0009, // transparent space
187 C_egrave = 0x000a, // egrave
188 C_acirc = 0x000b, // a circ
189 C_ecirc = 0x000c, // e circ
190 C_icirc = 0x000d, // i circ
191 C_ocirc = 0x000e, // o circ
192 C_ucirc = 0x000f, // u circ
193
194 /* standard character (ASCII) */
195 C_aacute = 0x002a, // a acute accent
196 C_eacute = 0x005c, // e acute accent
197 C_iacute = 0x005e, // i acute accent
198 C_oacute = 0x005f, // o acute accent
199 C_uacute = 0x0060, // u acute accent
200 C_ccedil = 0x007b, // c cedilla
201 C_division = 0x007c, // division sign
202 C_Ntilde = 0x007d, // N tilde
203 C_ntilde = 0x007e, // n tilde
204 C_block = 0x007f // solid block
205 };
206
207 int code, channel, character;
208
209 /* decode next pair of bytes from the odd or even field buffer */
210 if (!DecodeBits(fChannel == C_RADEON_CC1 || fChannel == C_RADEON_CC2 ? buffer0 : buffer1, code))
211 return false;
212
213 /* swap decoded bytes */
214 code = ((code << 8) & 0xff00) | ((code >> 8) & 0x00ff);
215
216 /* check parity */
217 for (int bit = 0; bit < 7; bit++) {
218 if ((code & (0x0001 << bit)) != 0)
219 code ^= 0x0080;
220 if ((code & (0x0100 << bit)) != 0)
221 code ^= 0x8000;
222 }
223 if ((code & 0x8080) != 0x8080) {
224 PRINT(("CCaption::Decode() - parity error (%04X)\n", code));
225 return false;
226 }
227
228 /* check channel number */
229 channel = (code & 0x0800) >> 12;
230 if (channel == 0) {
231 if (fChannel != C_RADEON_CC1 && fChannel != C_RADEON_CC3) {
232 PRINT(("CCaption::Decode() - ignore channel (%02X)\n", code & 0x7f7f));
233 return false;
234 }
235 }
236 else {
237 if (fChannel != C_RADEON_CC2 && fChannel != C_RADEON_CC4) {
238 PRINT(("CCaption::Decode() - ignore channel (%02X)\n", code & 0x7f7f));
239 return false;
240 }
241 }
242
243 if ((code & 0x7000) == 0x0000) {
244 /* one-byte standard character (0?XX) */
245 character = code & 0x007f;
246
247 if (character >= 0x20) {
248
249 PRINT(("%c", character));
250
251 fText[fRow][fColumn] = (fColor << 8) + character;
252 if (fColumn < C_RADEON_CC_COLUMNS - 1)
253 fColumn++;
254 }
255 else if (character != 0x00) {
256 PRINT(("<%04X>", code & 0x7f7f));
257 }
258 }
259 else if ((code & 0x7770) == 0x1120) {
260 /* middle row code (112X) */
261 fColor = code & 0x000f;
262
263 fText[fRow][fColumn] = (fColor << 8) + ' ';
264 if (fColumn < C_RADEON_CC_COLUMNS - 1)
265 fColumn++;
266
267 switch (fColor & 0x000e) {
268 case C_WHITE:
269 PRINT(("<white"));
270 break;
271 case C_GREEN:
272 PRINT(("<green"));
273 break;
274 case C_BLUE:
275 PRINT(("<blue"));
276 break;
277 case C_CYAN:
278 PRINT(("<cyan"));
279 break;
280 case C_RED:
281 PRINT(("<red"));
282 break;
283 case C_YELLOW:
284 PRINT(("<yellow"));
285 break;
286 case C_MAGENTA:
287 PRINT(("<magenta"));
288 break;
289 case C_ITALICS:
290 PRINT(("<italics"));
291 break;
292 }
293 if ((fColor & 0x0001) != 0)
294 PRINT((",underline>"));
295 else
296 PRINT((">"));
297 }
298 else if ((code & 0x7770) == 0x1130) {
299 /* two-byte special character (113X) */
300 character = (code & 0x000f);
301
302 fText[fRow][fColumn] = (fColor << 8) + character + 0x0080;
303 if (fColumn < C_RADEON_CC_COLUMNS - 1)
304 fColumn++;
305
306 switch (character) {
307 case C_reg:
308 PRINT(("®"));
309 break;
310 case C_deg:
311 PRINT(("°"));
312 break;
313 case C_frac12:
314 PRINT(("½"));
315 break;
316 case C_iquest:
317 PRINT(("¿"));
318 break;
319 case C_trademark:
320 PRINT(("&trademark;"));
321 break;
322 case C_cent:
323 PRINT(("¢"));
324 break;
325 case C_pound:
326 PRINT(("£"));
327 break;
328 case C_music:
329 PRINT(("&music;"));
330 break;
331 case C_agrave:
332 PRINT(("à"));
333 break;
334 case C_tspace:
335 PRINT(("&tspace;"));
336 break;
337 case C_egrave:
338 PRINT(("è"));
339 break;
340 case C_acirc:
341 PRINT(("â"));
342 break;
343 case C_ecirc:
344 PRINT(("ê"));
345 break;
346 case C_icirc:
347 PRINT(("î"));
348 break;
349 case C_ocirc:
350 PRINT(("ô"));
351 break;
352 case C_ucirc:
353 PRINT(("û"));
354 break;
355 default:
356 PRINT(("<special=%04X>", code & 0x7f7f));
357 break;
358 }
359 }
360 else if ((code & 0x7770) == 0x1420) {
361
362 if (code == fLastControlCode)
363 return false;
364 fLastControlCode = code;
365
366 /* miscellaneous control code (142X) */
367 switch (code & 0x000f) {
368 case C_RCL:
369 // resume caption loading
370 PRINT(("<rcl>"));
371 break;
372
373 case C_BS:
374 // backspace
375 PRINT(("<bs>"));
376 if (fColumn > 0)
377 fText[fRow][--fColumn] = 0x0000;
378 break;
379
380 case C_AOF:
381 // alarm off
382 PRINT(("<aof>"));
383 break;
384
385 case C_AON:
386 // alarm on
387 PRINT(("<aon>"));
388 break;
389
390 case C_DER:
391 // delete to end of row
392 PRINT(("<der>"));
393 for (int column = fColumn; column < C_RADEON_CC_COLUMNS; column++)
394 fText[fRow][column] = 0x0000;
395 break;
396
397 case C_RU2:
398 // rollup captions (2 rows)
399 PRINT(("<ru2>"));
400 for (int row = 0; row < C_RADEON_CC_ROWS - 2; row++) {
401 for (int column = 0; column < C_RADEON_CC_COLUMNS; column++)
402 fText[row][column] = fText[row + 2][column];
403 }
404 for (int row = C_RADEON_CC_ROWS - 2; row < C_RADEON_CC_ROWS; row++) {
405 for (int column = 0; column < C_RADEON_CC_COLUMNS; column++)
406 fText[row][column] = 0x0000;
407 }
408 break;
409
410 case C_RU3:
411 // rollup captions (3 rows)
412 PRINT(("<ru3>"));
413 for (int row = 0; row < C_RADEON_CC_ROWS - 3; row++) {
414 for (int column = 0; column < C_RADEON_CC_COLUMNS; column++)
415 fText[row][column] = fText[row + 2][column];
416 }
417 for (int row = C_RADEON_CC_ROWS - 3; row < C_RADEON_CC_ROWS; row++) {
418 for (int column = 0; column < C_RADEON_CC_COLUMNS; column++)
419 fText[row][column] = 0x0000;
420 }
421 break;
422
423 case C_RU4:
424 // rollup captions (4 rows)
425 PRINT(("<ru4>"));
426 for (int row = 0; row < C_RADEON_CC_ROWS - 4; row++) {
427 for (int column = 0; column < C_RADEON_CC_COLUMNS; column++)
428 fText[row][column] = fText[row + 2][column];
429 }
430 for (int row = C_RADEON_CC_ROWS - 4; row < C_RADEON_CC_ROWS; row++) {
431 for (int column = 0; column < C_RADEON_CC_COLUMNS; column++)
432 fText[row][column] = 0x0000;
433 }
434 break;
435
436 case C_FON:
437 // flash on
438 PRINT(("<fon>"));
439 break;
440
441 case C_RDC:
442 // resume direct captioning
443 PRINT(("<rdc>"));
444 break;
445
446 case C_TR:
447 // text restart
448 PRINT(("<tr>"));
449
450 fRow = fColumn = 0;
451 break;
452
453 case C_RTD:
454 // resume text display
455 PRINT(("<rtd>"));
456
457 fRow = fColumn = 0;
458 break;
459
460 case C_EDM:
461 // erase displayed memory
462 PRINT(("<edm>\n"));
463 for (int row = 0; row < C_RADEON_CC_ROWS; row++) {
464 for (int column = 0; column < C_RADEON_CC_COLUMNS; column++)
465 fDisplayText[row][column] = 0x0000;
466 }
467 DisplayCaptions();
468 break;
469
470 case C_CR:
471 // carriage return
472 PRINT(("<cr>"));
473
474 /* has no effect in caption loading mode */
475 break;
476
477 case C_ENM:
478 // erase non-displayed memory
479 PRINT(("<enm>"));
480 for (int row = 0; row < C_RADEON_CC_ROWS; row++) {
481 for (int column = 0; column < C_RADEON_CC_COLUMNS; column++)
482 fText[row][column] = 0x0000;
483 }
484 break;
485
486 case C_EOC:
487 // end of caption
488 PRINT(("<eoc>\n"));
489 for (int row = 0; row < C_RADEON_CC_ROWS; row++) {
490 for (int column = 0; column < C_RADEON_CC_COLUMNS; column++) {
491 const int code = fText[row][column];
492 fText[row][column] = fDisplayText[row][column];
493 fDisplayText[row][column] = code;
494 }
495 }
496
497 DisplayCaptions();
498 break;
499 }
500 }
501 else if ((code & 0x7770) == 0x1720) {
502 /* tab offset (172X) */
503 const int offset = code & 0x000f;
504
505 if (offset >= 1 && offset <= 3) {
506 PRINT(("<tab%d>", offset));
507
508 if ((fColumn += offset) >= C_RADEON_CC_COLUMNS - 1)
509 fColumn = C_RADEON_CC_COLUMNS - 1;
510 }
511 else {
512 PRINT(("<tab=%04X>", code & 0x7f7f));
513 }
514 }
515 else if ((code & 0x7040) == 0x1040) {
516 /* preamble address code (1Y4X, 1Y6X) */
517
518 switch (code & 0x0720) {
519 case C_ROW_1:
520 PRINT(("\n<row1"));
521 fRow = 0;
522 break;
523 case C_ROW_2:
524 PRINT(("\n<row2"));
525 fRow = 1;
526 break;
527 case C_ROW_3:
528 PRINT(("\n<row3"));
529 fRow = 2;
530 break;
531 case C_ROW_4:
532 PRINT(("\n<row4"));
533 fRow = 3;
534 break;
535 case C_ROW_5:
536 PRINT(("\n<row5"));
537 fRow = 4;
538 break;
539 case C_ROW_6:
540 PRINT(("\n<row6"));
541 fRow = 5;
542 break;
543 case C_ROW_7:
544 PRINT(("\n<row7"));
545 fRow = 6;
546 break;
547 case C_ROW_8:
548 PRINT(("\n<row8"));
549 fRow = 7;
550 break;
551 case C_ROW_9:
552 PRINT(("\n<row9"));
553 fRow = 8;
554 break;
555 case C_ROW_10:
556 PRINT(("\n<row10"));
557 fRow = 9;
558 break;
559 case C_ROW_11:
560 PRINT(("\n<row11"));
561 fRow = 10;
562 break;
563 case C_ROW_12:
564 PRINT(("\n<row12"));
565 fRow = 11;
566 break;
567 case C_ROW_13:
568 PRINT(("\n<row13"));
569 fRow = 12;
570 break;
571 case C_ROW_14:
572 PRINT(("\n<row14"));
573 fRow = 13;
574 break;
575 case C_ROW_15:
576 PRINT(("\n<row15"));
577 fRow = 14;
578 break;
579 default:
580 PRINT(("\n<pac=%04X>", code & 0x7f7f));
581 return false;
582 }
583
584 if ((code & 0x0010) == 0x0000) {
585 /* change color */
586 fColor = (code & 0x000f);
587 fColumn = 0;
588
589 switch (fColor & 0x000e) {
590 case C_WHITE:
591 PRINT((",white"));
592 break;
593 case C_GREEN:
594 PRINT((",green"));
595 break;
596 case C_BLUE:
597 PRINT((",blue"));
598 break;
599 case C_CYAN:
600 PRINT((",cyan"));
601 break;
602 case C_RED:
603 PRINT((",red"));
604 break;
605 case C_YELLOW:
606 PRINT((",yellow"));
607 break;
608 case C_MAGENTA:
609 PRINT((",magenta"));
610 break;
611 case C_ITALICS:
612 PRINT((",italics"));
613 break;
614 }
615 if ((fColor & 0x0001) != 0)
616 PRINT((",underline>"));
617 else
618 PRINT((">"));
619
620 }
621 else {
622 /* indent, white */
623 fColor = C_WHITE | (code & 0x0001);
624 fColumn = (code & 0x000e) << 1;
625 PRINT((",col%d>", fColumn));
626 }
627 }
628 else {
629 /* two one-byte standard characters */
630 character = (code >> 8) & 0x7f;
631 if (character >= 0x20) {
632
633 PRINT(("%c", character));
634
635 fText[fRow][fColumn] = (fColor << 8) + character;
636 if (fColumn < C_RADEON_CC_COLUMNS - 1)
637 fColumn++;
638 }
639 else if (character != 0x00) {
640 PRINT(("<%02X>", character));
641 }
642
643 character = (code >> 0) & 0x7f;
644 if (character >= 0x20) {
645
646 PRINT(("%c", character));
647
648 fText[fRow][fColumn] = (fColor << 8) + character;
649 if (fColumn < C_RADEON_CC_COLUMNS - 1)
650 fColumn++;
651 }
652 else if (character != 0x00) {
653 PRINT(("<%02X>", character));
654 }
655 }
656
657 return true;
658 }
659
DisplayCaptions() const660 void CCaption::DisplayCaptions() const
661 {
662 printf("\x1b[H\x1b[J");
663
664 for (int row = 0; row < C_RADEON_CC_ROWS; row++) {
665 for (int column = 0; column < C_RADEON_CC_COLUMNS; column++) {
666 if (fDisplayText[row][column] == 0x0000) {
667 printf("\x1b[0;47;37m ");
668 continue;
669 }
670
671 const int code = (fDisplayText[row][column] >> 0) & 0xff;
672 const int color = (fDisplayText[row][column] >> 8) & 0xff;
673
674 switch (color & 0x000e) {
675 case 0x0000: // WHITE
676 printf("\x1b[0;%d;40;37m", (color & 1) << 2);
677 break;
678 case 0x0002: // GREEN
679 printf("\x1b[0;%d;40;32m", (color & 1) << 2);
680 break;
681 case 0x0004: // BLUE
682 printf("\x1b[0;%d;40;34m", (color & 1) << 2);
683 break;
684 case 0x0006: // CYAN
685 printf("\x1b[0;%d;40;35m", (color & 1) << 2);
686 break;
687 case 0x0008: // RED
688 printf("\x1b[0;%d;40;31m", (color & 1) << 2);
689 break;
690 case 0x000a: // YELLOW
691 printf("\x1b[0;%d;40;33m", (color & 1) << 2);
692 break;
693 case 0x000c: // MAGENTA
694 printf("\x1b[0;%d;40;36m", (color & 1) << 2);
695 break;
696 case 0x000e: // WHITE ITALICS
697 if ((color & 1) == 0)
698 printf("\x1b[1;40;37m");
699 else
700 printf("\x1b[1;4;40;37m");
701 break;
702 }
703
704 if (code >= 0x20 && code <= 0x7f) {
705 switch (code) {
706 case 0x002a:
707 // aacute
708 printf("\xc3\xa1");
709 break;
710 case 0x005c:
711 // eacute
712 printf("\xc3\xa9");
713 break;
714 case 0x005e:
715 // iacute
716 printf("\xc3\xad");
717 break;
718 case 0x005f:
719 // oacute
720 printf("\xc3\xb3");
721 break;
722 case 0x0060:
723 // uacute
724 printf("\xc3\xba");
725 break;
726 case 0x007b:
727 // ccedil
728 printf("\xc3\xa7");
729 break;
730 case 0x007c:
731 // division
732 printf("\xc3\xb7");
733 break;
734 case 0x007d:
735 // Ntilde
736 printf("\xc3\x91");
737 break;
738 case 0x007e:
739 // ntilde
740 printf("\xc3\xb1");
741 break;
742 case 0x007f:
743 // block
744 printf("\xc1\xbf");
745 break;
746 default:
747 // ASCII character
748 printf("%c", code);
749 break;
750 }
751 }
752 else {
753 switch (code) {
754 case 0x0080:
755 // reg
756 printf("\xc2\xae");
757 break;
758 case 0x0081:
759 // deg
760 printf("\xc2\xb0");
761 break;
762 case 0x0082:
763 // frac12
764 printf("\xc2\xbd");
765 break;
766 case 0x0083:
767 // iquest
768 printf("\xc2\xbf");
769 break;
770 case 0x0084:
771 // trademark
772 printf("\xe2\x84");
773 break;
774 case 0x0085:
775 // cent
776 printf("\xc2\xa2");
777 break;
778 case 0x0086:
779 // pound
780 printf("\xc2\xa3");
781 break;
782 case 0x0087:
783 // music
784 printf("\xc2\xa7");
785 break;
786 case 0x0088:
787 // agrave
788 printf("\xc3\xa0");
789 break;
790 case 0x0089:
791 // tspace
792 printf("\x1b[0;47;37m ");
793 break;
794 case 0x008a:
795 // egrave
796 printf("\xc3\xa8");
797 break;
798 case 0x008b:
799 // acirc
800 printf("\xc3\xa2");
801 break;
802 case 0x008c:
803 // ecirc
804 printf("\xc3\xaa");
805 break;
806 case 0x008d:
807 // icirc
808 printf("\xc3\xae");
809 break;
810 case 0x008e:
811 // ocirc
812 printf("\xc3\xb4");
813 break;
814 case 0x008f:
815 // ucirc
816 printf("\xc3\xbb");
817 break;
818 default:
819 // buggy code
820 printf("<%02X>", code);
821 break;
822 }
823 }
824 }
825 printf("\n");
826 }
827 printf("\x1b[0;30;47m");
828 }
829