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 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 30 CCaption::~CCaption() 31 { 32 } 33 34 status_t CCaption::InitCheck() const 35 { 36 return fCapture.InitCheck(); 37 } 38 39 void CCaption::SetMode(cc_mode mode) 40 { 41 fChannel = mode; 42 fLastControlCode = 0; 43 } 44 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 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 660 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