pen.c (19092B)
1 #include <stdio.h> 2 3 #include "nvim/vterm/pen.h" 4 #include "nvim/vterm/vterm.h" 5 #include "nvim/vterm/vterm_internal_defs.h" 6 7 #include "vterm/pen.c.generated.h" 8 9 // Structure used to store RGB triples without the additional metadata stored in VTermColor. 10 typedef struct { 11 uint8_t red, green, blue; 12 } VTermRGB; 13 14 static const VTermRGB ansi_colors[] = { 15 // R G B 16 { 0, 0, 0 }, // black 17 { 224, 0, 0 }, // red 18 { 0, 224, 0 }, // green 19 { 224, 224, 0 }, // yellow 20 { 0, 0, 224 }, // blue 21 { 224, 0, 224 }, // magenta 22 { 0, 224, 224 }, // cyan 23 { 224, 224, 224 }, // white == light grey 24 25 // high intensity 26 { 128, 128, 128 }, // black 27 { 255, 64, 64 }, // red 28 { 64, 255, 64 }, // green 29 { 255, 255, 64 }, // yellow 30 { 64, 64, 255 }, // blue 31 { 255, 64, 255 }, // magenta 32 { 64, 255, 255 }, // cyan 33 { 255, 255, 255 }, // white for real 34 }; 35 36 static uint8_t ramp6[] = { 37 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF, 38 }; 39 40 static uint8_t ramp24[] = { 41 0x00, 0x0B, 0x16, 0x21, 0x2C, 0x37, 0x42, 0x4D, 0x58, 0x63, 0x6E, 0x79, 42 0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF, 43 }; 44 45 static void lookup_default_colour_ansi(long idx, VTermColor *col) 46 { 47 if (idx >= 0 && idx < 16) { 48 vterm_color_rgb(col, 49 ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue); 50 } 51 } 52 53 static bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col) 54 { 55 if (index >= 0 && index < 16) { 56 *col = state->colors[index]; 57 return true; 58 } 59 60 return false; 61 } 62 63 static bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col) 64 { 65 if (index >= 0 && index < 16) { 66 // Normal 8 colours or high intensity - parse as palette 0 67 return lookup_colour_ansi(state, index, col); 68 } else if (index >= 16 && index < 232) { 69 // 216-colour cube 70 index -= 16; 71 72 vterm_color_rgb(col, ramp6[index/6/6 % 6], 73 ramp6[index/6 % 6], 74 ramp6[index % 6]); 75 76 return true; 77 } else if (index >= 232 && index < 256) { 78 // 24 greyscales 79 index -= 232; 80 81 vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]); 82 83 return true; 84 } 85 86 return false; 87 } 88 89 static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, 90 VTermColor *col) 91 { 92 switch (palette) { 93 case 2: // RGB mode - 3 args contain colour values directly 94 if (argcount < 3) { 95 return argcount; 96 } 97 98 vterm_color_rgb(col, (uint8_t)CSI_ARG(args[0]), (uint8_t)CSI_ARG(args[1]), 99 (uint8_t)CSI_ARG(args[2])); 100 101 return 3; 102 103 case 5: // XTerm 256-colour mode 104 if (!argcount || CSI_ARG_IS_MISSING(args[0])) { 105 return argcount ? 1 : 0; 106 } 107 108 vterm_color_indexed(col, (uint8_t)args[0]); 109 110 return argcount ? 1 : 0; 111 112 default: 113 DEBUG_LOG("Unrecognised colour palette %d\n", palette); 114 return 0; 115 } 116 } 117 118 // Some conveniences 119 120 static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) 121 { 122 #ifdef DEBUG 123 if (type != vterm_get_attr_type(attr)) { 124 DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", 125 attr, vterm_get_attr_type(attr), type); 126 return; 127 } 128 #endif 129 if (state->callbacks && state->callbacks->setpenattr) { 130 (*state->callbacks->setpenattr)(attr, val, state->cbdata); 131 } 132 } 133 134 static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean) 135 { 136 VTermValue val = { .boolean = boolean }; 137 setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val); 138 } 139 140 static void setpenattr_int(VTermState *state, VTermAttr attr, int number) 141 { 142 VTermValue val = { .number = number }; 143 setpenattr(state, attr, VTERM_VALUETYPE_INT, &val); 144 } 145 146 static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color) 147 { 148 VTermValue val = { .color = color }; 149 setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val); 150 } 151 152 static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col) 153 { 154 VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg; 155 156 vterm_color_indexed(colp, (uint8_t)col); 157 158 setpenattr_col(state, attr, *colp); 159 } 160 161 void vterm_state_newpen(VTermState *state) 162 { 163 // 90% grey so that pure white is brighter 164 vterm_color_rgb(&state->default_fg, 240, 240, 240); 165 vterm_color_rgb(&state->default_bg, 0, 0, 0); 166 vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg); 167 168 for (int col = 0; col < 16; col++) { 169 lookup_default_colour_ansi(col, &state->colors[col]); 170 } 171 } 172 173 void vterm_state_resetpen(VTermState *state) 174 { 175 state->pen.bold = 0; setpenattr_bool(state, VTERM_ATTR_BOLD, 0); 176 state->pen.underline = 0; setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0); 177 state->pen.italic = 0; setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); 178 state->pen.blink = 0; setpenattr_bool(state, VTERM_ATTR_BLINK, 0); 179 state->pen.reverse = 0; setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); 180 state->pen.conceal = 0; setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0); 181 state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); 182 state->pen.font = 0; setpenattr_int(state, VTERM_ATTR_FONT, 0); 183 state->pen.small = 0; setpenattr_bool(state, VTERM_ATTR_SMALL, 0); 184 state->pen.baseline = 0; setpenattr_int(state, VTERM_ATTR_BASELINE, 0); 185 state->pen.dim = 0; setpenattr_bool(state, VTERM_ATTR_DIM, 0); 186 state->pen.overline = 0; setpenattr_bool(state, VTERM_ATTR_OVERLINE, 0); 187 188 state->pen.fg = state->default_fg; 189 setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg); 190 state->pen.bg = state->default_bg; 191 setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg); 192 193 state->pen.uri = 0; setpenattr_int(state, VTERM_ATTR_URI, 0); 194 } 195 196 void vterm_state_savepen(VTermState *state, int save) 197 { 198 if (save) { 199 state->saved.pen = state->pen; 200 } else { 201 state->pen = state->saved.pen; 202 203 setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold); 204 setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); 205 setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic); 206 setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink); 207 setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse); 208 setpenattr_bool(state, VTERM_ATTR_CONCEAL, state->pen.conceal); 209 setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike); 210 setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font); 211 setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small); 212 setpenattr_int(state, VTERM_ATTR_BASELINE, state->pen.baseline); 213 setpenattr_bool(state, VTERM_ATTR_DIM, state->pen.dim); 214 setpenattr_bool(state, VTERM_ATTR_OVERLINE, state->pen.overline); 215 216 setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); 217 setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); 218 219 setpenattr_int(state, VTERM_ATTR_URI, state->pen.uri); 220 } 221 } 222 223 void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, 224 const VTermColor *default_bg) 225 { 226 if (default_fg) { 227 state->default_fg = *default_fg; 228 state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK) 229 | VTERM_COLOR_DEFAULT_FG; 230 } 231 232 if (default_bg) { 233 state->default_bg = *default_bg; 234 state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK) 235 | VTERM_COLOR_DEFAULT_BG; 236 } 237 } 238 239 void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col) 240 { 241 if (index >= 0 && index < 16) { 242 state->colors[index] = *col; 243 } 244 } 245 246 /// Makes sure that the given color `col` is indeed an RGB colour. After this 247 /// function returns, VTERM_COLOR_IS_RGB(col) will return true, while all other 248 /// flags stored in `col->type` will have been reset. 249 /// 250 /// @param state is the VTermState instance from which the colour palette should 251 /// be extracted. 252 /// @param col is a pointer at the VTermColor instance that should be converted 253 /// to an RGB colour. 254 void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col) 255 { 256 if (VTERM_COLOR_IS_INDEXED(col)) { // Convert indexed colors to RGB 257 lookup_colour_palette(state, col->indexed.idx, col); 258 } 259 col->type &= VTERM_COLOR_TYPE_MASK; // Reset any metadata but the type 260 } 261 262 void vterm_state_setpen(VTermState *state, const long args[], int argcount) 263 { 264 // SGR - ECMA-48 8.3.117 265 266 int argi = 0; 267 int value; 268 269 while (argi < argcount) { 270 // This logic is easier to do 'done' backwards; set it true, and make it 271 // false again in the 'default' case 272 int done = 1; 273 274 long arg; 275 switch (arg = CSI_ARG(args[argi])) { 276 case CSI_ARG_MISSING: 277 case 0: // Reset 278 vterm_state_resetpen(state); 279 break; 280 281 case 1: { // Bold on 282 const VTermColor *fg = &state->pen.fg; 283 state->pen.bold = 1; 284 setpenattr_bool(state, VTERM_ATTR_BOLD, 1); 285 if (!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->indexed.idx < 8 286 && state->bold_is_highbright) { 287 set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, fg->indexed.idx + (state->pen.bold ? 8 : 0)); 288 } 289 break; 290 } 291 292 case 2: // Dim/faint on 293 state->pen.dim = 1; 294 setpenattr_bool(state, VTERM_ATTR_DIM, 1); 295 break; 296 297 case 3: // Italic on 298 state->pen.italic = 1; 299 setpenattr_bool(state, VTERM_ATTR_ITALIC, 1); 300 break; 301 302 case 4: // Underline 303 state->pen.underline = VTERM_UNDERLINE_SINGLE; 304 if (CSI_ARG_HAS_MORE(args[argi])) { 305 argi++; 306 switch (CSI_ARG(args[argi])) { 307 case 0: 308 state->pen.underline = 0; 309 break; 310 case 1: 311 state->pen.underline = VTERM_UNDERLINE_SINGLE; 312 break; 313 case 2: 314 state->pen.underline = VTERM_UNDERLINE_DOUBLE; 315 break; 316 case 3: 317 state->pen.underline = VTERM_UNDERLINE_CURLY; 318 break; 319 } 320 } 321 setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); 322 break; 323 324 case 5: // Blink 325 state->pen.blink = 1; 326 setpenattr_bool(state, VTERM_ATTR_BLINK, 1); 327 break; 328 329 case 7: // Reverse on 330 state->pen.reverse = 1; 331 setpenattr_bool(state, VTERM_ATTR_REVERSE, 1); 332 break; 333 334 case 8: // Conceal on 335 state->pen.conceal = 1; 336 setpenattr_bool(state, VTERM_ATTR_CONCEAL, 1); 337 break; 338 339 case 9: // Strikethrough on 340 state->pen.strike = 1; 341 setpenattr_bool(state, VTERM_ATTR_STRIKE, 1); 342 break; 343 344 case 10: 345 case 11: 346 case 12: 347 case 13: 348 case 14: 349 case 15: 350 case 16: 351 case 17: 352 case 18: 353 case 19: // Select font 354 state->pen.font = CSI_ARG(args[argi]) - 10; 355 setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font); 356 break; 357 358 case 21: // Underline double 359 state->pen.underline = VTERM_UNDERLINE_DOUBLE; 360 setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); 361 break; 362 363 case 22: // Normal intensity (bold and dim off) 364 state->pen.bold = 0; 365 setpenattr_bool(state, VTERM_ATTR_BOLD, 0); 366 state->pen.dim = 0; 367 setpenattr_bool(state, VTERM_ATTR_DIM, 0); 368 break; 369 370 case 23: // Italic and Gothic (currently unsupported) off 371 state->pen.italic = 0; 372 setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); 373 break; 374 375 case 24: // Underline off 376 state->pen.underline = 0; 377 setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0); 378 break; 379 380 case 25: // Blink off 381 state->pen.blink = 0; 382 setpenattr_bool(state, VTERM_ATTR_BLINK, 0); 383 break; 384 385 case 27: // Reverse off 386 state->pen.reverse = 0; 387 setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); 388 break; 389 390 case 28: // Conceal off (Reveal) 391 state->pen.conceal = 0; 392 setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0); 393 break; 394 395 case 29: // Strikethrough off 396 state->pen.strike = 0; 397 setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); 398 break; 399 400 case 30: 401 case 31: 402 case 32: 403 case 33: 404 case 34: 405 case 35: 406 case 36: 407 case 37: // Foreground colour palette 408 value = CSI_ARG(args[argi]) - 30; 409 if (state->pen.bold && state->bold_is_highbright) { 410 value += 8; 411 } 412 set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); 413 break; 414 415 case 38: // Foreground colour alternative palette 416 if (argcount - argi < 1) { 417 return; 418 } 419 argi += 1 + lookup_colour(state, CSI_ARG(args[argi + 1]), args + argi + 2, 420 argcount - argi - 2, &state->pen.fg); 421 setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); 422 break; 423 424 case 39: // Foreground colour default 425 state->pen.fg = state->default_fg; 426 setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); 427 break; 428 429 case 40: 430 case 41: 431 case 42: 432 case 43: 433 case 44: 434 case 45: 435 case 46: 436 case 47: // Background colour palette 437 value = CSI_ARG(args[argi]) - 40; 438 set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); 439 break; 440 441 case 48: // Background colour alternative palette 442 if (argcount - argi < 1) { 443 return; 444 } 445 argi += 1 + lookup_colour(state, CSI_ARG(args[argi + 1]), args + argi + 2, 446 argcount - argi - 2, &state->pen.bg); 447 setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); 448 break; 449 450 case 49: // Default background 451 state->pen.bg = state->default_bg; 452 setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); 453 break; 454 455 case 53: // Overline on 456 state->pen.overline = 1; 457 setpenattr_bool(state, VTERM_ATTR_OVERLINE, 1); 458 break; 459 460 case 55: // Overline off 461 state->pen.overline = 0; 462 setpenattr_bool(state, VTERM_ATTR_OVERLINE, 0); 463 break; 464 465 case 73: // Superscript 466 case 74: // Subscript 467 case 75: // Superscript/subscript off 468 state->pen.small = (arg != 75); 469 state->pen.baseline = 470 (arg == 73) ? VTERM_BASELINE_RAISE 471 : (arg == 74) ? VTERM_BASELINE_LOWER 472 : VTERM_BASELINE_NORMAL; 473 setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small); 474 setpenattr_int(state, VTERM_ATTR_BASELINE, state->pen.baseline); 475 break; 476 477 case 90: 478 case 91: 479 case 92: 480 case 93: 481 case 94: 482 case 95: 483 case 96: 484 case 97: // Foreground colour high-intensity palette 485 value = CSI_ARG(args[argi]) - 90 + 8; 486 set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); 487 break; 488 489 case 100: 490 case 101: 491 case 102: 492 case 103: 493 case 104: 494 case 105: 495 case 106: 496 case 107: // Background colour high-intensity palette 497 value = CSI_ARG(args[argi]) - 100 + 8; 498 set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); 499 break; 500 501 default: 502 done = 0; 503 break; 504 } 505 506 if (!done) { 507 DEBUG_LOG("libvterm: Unhandled CSI SGR %ld\n", arg); 508 } 509 510 while (CSI_ARG_HAS_MORE(args[argi++])) {} 511 } 512 } 513 514 static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg) 515 { 516 // Do nothing if the given color is the default color 517 if ((fg && VTERM_COLOR_IS_DEFAULT_FG(col)) 518 || (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) { 519 return argi; 520 } 521 522 // Decide whether to send an indexed color or an RGB color 523 if (VTERM_COLOR_IS_INDEXED(col)) { 524 const uint8_t idx = col->indexed.idx; 525 if (idx < 8) { 526 args[argi++] = (idx + (fg ? 30 : 40)); 527 } else if (idx < 16) { 528 args[argi++] = (idx - 8 + (fg ? 90 : 100)); 529 } else { 530 args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); 531 args[argi++] = CSI_ARG_FLAG_MORE | 5; 532 args[argi++] = idx; 533 } 534 } else if (VTERM_COLOR_IS_RGB(col)) { 535 args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); 536 args[argi++] = CSI_ARG_FLAG_MORE | 2; 537 args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red; 538 args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green; 539 args[argi++] = col->rgb.blue; 540 } 541 return argi; 542 } 543 544 int vterm_state_getpen(VTermState *state, long args[], int argcount) 545 { 546 int argi = 0; 547 548 if (state->pen.bold) { 549 args[argi++] = 1; 550 } 551 552 if (state->pen.dim) { 553 args[argi++] = 2; 554 } 555 556 if (state->pen.italic) { 557 args[argi++] = 3; 558 } 559 560 if (state->pen.underline == VTERM_UNDERLINE_SINGLE) { 561 args[argi++] = 4; 562 } 563 if (state->pen.underline == VTERM_UNDERLINE_CURLY) { 564 args[argi++] = 4 | CSI_ARG_FLAG_MORE, args[argi++] = 3; 565 } 566 567 if (state->pen.blink) { 568 args[argi++] = 5; 569 } 570 571 if (state->pen.reverse) { 572 args[argi++] = 7; 573 } 574 575 if (state->pen.conceal) { 576 args[argi++] = 8; 577 } 578 579 if (state->pen.strike) { 580 args[argi++] = 9; 581 } 582 583 if (state->pen.font) { 584 args[argi++] = 10 + state->pen.font; 585 } 586 587 if (state->pen.underline == VTERM_UNDERLINE_DOUBLE) { 588 args[argi++] = 21; 589 } 590 591 argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true); 592 593 argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false); 594 595 if (state->pen.overline) { 596 args[argi++] = 53; 597 } 598 599 if (state->pen.small) { 600 if (state->pen.baseline == VTERM_BASELINE_RAISE) { 601 args[argi++] = 73; 602 } else if (state->pen.baseline == VTERM_BASELINE_LOWER) { 603 args[argi++] = 74; 604 } 605 } 606 607 return argi; 608 } 609 610 int vterm_state_set_penattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) 611 { 612 if (!val) { 613 return 0; 614 } 615 616 if (type != vterm_get_attr_type(attr)) { 617 DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", 618 attr, vterm_get_attr_type(attr), type); 619 return 0; 620 } 621 622 switch (attr) { 623 case VTERM_ATTR_BOLD: 624 state->pen.bold = (unsigned)val->boolean; 625 break; 626 case VTERM_ATTR_UNDERLINE: 627 state->pen.underline = (unsigned)val->number; 628 break; 629 case VTERM_ATTR_ITALIC: 630 state->pen.italic = (unsigned)val->boolean; 631 break; 632 case VTERM_ATTR_BLINK: 633 state->pen.blink = (unsigned)val->boolean; 634 break; 635 case VTERM_ATTR_REVERSE: 636 state->pen.reverse = (unsigned)val->boolean; 637 break; 638 case VTERM_ATTR_CONCEAL: 639 state->pen.conceal = (unsigned)val->boolean; 640 break; 641 case VTERM_ATTR_STRIKE: 642 state->pen.strike = (unsigned)val->boolean; 643 break; 644 case VTERM_ATTR_FONT: 645 state->pen.font = (unsigned)val->number; 646 break; 647 case VTERM_ATTR_FOREGROUND: 648 state->pen.fg = val->color; 649 break; 650 case VTERM_ATTR_BACKGROUND: 651 state->pen.bg = val->color; 652 break; 653 case VTERM_ATTR_SMALL: 654 state->pen.small = (unsigned)val->boolean; 655 break; 656 case VTERM_ATTR_BASELINE: 657 state->pen.baseline = (unsigned)val->number; 658 break; 659 case VTERM_ATTR_URI: 660 state->pen.uri = val->number; 661 break; 662 case VTERM_ATTR_DIM: 663 state->pen.dim = (unsigned)val->boolean; 664 break; 665 case VTERM_ATTR_OVERLINE: 666 state->pen.overline = (unsigned)val->boolean; 667 break; 668 default: 669 return 0; 670 } 671 672 if (state->callbacks && state->callbacks->setpenattr) { 673 (*state->callbacks->setpenattr)(attr, val, state->cbdata); 674 } 675 676 return 1; 677 }