github.com/jmigpin/editor@v1.6.0/util/drawutil/drawer4/drawer.go (about) 1 package drawer4 2 3 import ( 4 "image" 5 "image/color" 6 "image/draw" 7 8 "github.com/davecgh/go-spew/spew" 9 "github.com/jmigpin/editor/util/drawutil" 10 "github.com/jmigpin/editor/util/fontutil" 11 "github.com/jmigpin/editor/util/iout/iorw" 12 "github.com/jmigpin/editor/util/mathutil" 13 ) 14 15 const ( 16 eofRune = -1 17 noDrawRune = -2 18 ) 19 20 type Drawer struct { 21 reader iorw.ReaderAt 22 23 fface *fontutil.FontFace 24 lineHeight mathutil.Intf 25 offset image.Point 26 bounds image.Rectangle 27 firstLineOffsetX int 28 fg color.Color 29 smoothScroll bool 30 31 iters struct { 32 runeR RuneReader // init 33 measure Measure // end 34 drawR DrawRune 35 line Line 36 lineWrap LineWrap // init, insert 37 lineStart LineStart // init 38 indent Indent // insert 39 earlyExit EarlyExit 40 curColors CurColors 41 bgFill BgFill 42 cursor Cursor 43 pointOf PointOf // end 44 indexOf IndexOf // end 45 colorize Colorize // init 46 annotations Annotations // insert 47 annotationsIndexOf AnnotationsIndexOf 48 } 49 50 st State 51 52 loopv struct { 53 iters []Iterator 54 i int 55 stop bool 56 } 57 58 // internal opt data 59 opt struct { 60 measure struct { 61 updated bool 62 measure image.Point 63 } 64 runeO struct { 65 offset int 66 } 67 cursor struct { 68 offset int 69 } 70 wordH struct { 71 word []byte 72 updatedWord bool 73 updatedOps bool 74 } 75 parenthesisH struct { 76 updated bool 77 } 78 syntaxH struct { 79 updated bool 80 } 81 } 82 83 // external options 84 Opt struct { 85 QuickMeasure bool // just return the bounds size 86 EarlyExitMeasure bool // allow early exit 87 RuneReader struct { 88 StartOffsetX int 89 } 90 LineWrap struct { 91 On bool 92 Fg, Bg color.Color 93 } 94 Cursor struct { 95 On bool 96 Fg color.Color 97 AddedWidth int 98 } 99 Colorize struct { 100 Groups []*ColorizeGroup 101 } 102 Annotations struct { 103 On bool 104 Fg, Bg color.Color 105 Selected struct { 106 EntryIndex int 107 Fg, Bg color.Color 108 } 109 Entries []*Annotation // must be ordered by offset 110 } 111 WordHighlight struct { 112 On bool 113 Fg, Bg color.Color 114 Group ColorizeGroup 115 } 116 ParenthesisHighlight struct { 117 On bool 118 Fg, Bg color.Color 119 Group ColorizeGroup 120 } 121 SyntaxHighlight struct { 122 On bool 123 Comment struct { 124 Defs []*drawutil.SyntaxHighlightComment 125 Fg, Bg color.Color 126 } 127 String struct { 128 Fg, Bg color.Color 129 } 130 Group ColorizeGroup 131 } 132 } 133 } 134 135 // State should not be stored/restored except in initializations. 136 // ex: runeR.extra and runeR.ru won't be correctly set if the iterators were stopped. 137 type State struct { 138 runeR struct { 139 ri int 140 ru, prevRu rune 141 pen mathutil.PointIntf // upper left corner (not at baseline) 142 kern, advance mathutil.Intf 143 extra int 144 startRi int 145 fface *fontutil.FontFace 146 } 147 measure struct { 148 penMax mathutil.PointIntf 149 } 150 drawR struct { 151 img draw.Image 152 delay *DrawRuneDelay 153 } 154 line struct { 155 lineStart bool 156 } 157 lineWrap struct { 158 //wrapRi int 159 wrapping bool 160 preLineWrap bool 161 postLineWrap bool 162 } 163 lineStart struct { 164 offset int 165 nLinesUp int 166 q []int 167 ri int 168 uppedLines int 169 reader iorw.ReaderAt // limited reader 170 } 171 indent struct { 172 notStartingSpaces bool 173 indent mathutil.Intf 174 } 175 earlyExit struct { 176 extraLine bool 177 } 178 curColors struct { 179 fg, bg color.Color 180 lineBg color.Color 181 } 182 bgFill struct{} 183 cursor struct { 184 delay *CursorDelay 185 } 186 pointOf struct { 187 index int 188 p image.Point 189 } 190 indexOf struct { 191 p mathutil.PointIntf 192 index int 193 } 194 colorize struct { 195 indexes []int 196 } 197 annotations struct { 198 cei int // current entries index (to add to q) 199 indexQ []int 200 } 201 annotationsIndexOf struct { 202 p mathutil.PointIntf 203 eindex int 204 offset int 205 inside struct { // inside an annotation 206 on bool 207 ei int // entry index 208 soffset int // start offset 209 } 210 } 211 } 212 213 func (st State) Dump() { 214 st.drawR.img = nil 215 spew.Dump(st) 216 } 217 218 //---------- 219 220 func New() *Drawer { 221 d := &Drawer{} 222 d.Opt.LineWrap.On = true 223 d.smoothScroll = true 224 225 // iterators 226 d.iters.runeR.d = d 227 d.iters.measure.d = d 228 d.iters.drawR.d = d 229 d.iters.line.d = d 230 d.iters.lineWrap.d = d 231 d.iters.lineStart.d = d 232 d.iters.indent.d = d 233 d.iters.earlyExit.d = d 234 d.iters.curColors.d = d 235 d.iters.bgFill.d = d 236 d.iters.cursor.d = d 237 d.iters.pointOf.d = d 238 d.iters.indexOf.d = d 239 d.iters.colorize.d = d 240 d.iters.annotations.d = d 241 d.iters.annotationsIndexOf.d = d 242 return d 243 } 244 245 //---------- 246 247 func (d *Drawer) SetReader(r iorw.ReaderAt) { 248 d.reader = r 249 // always run since an underlying reader could have been changed 250 d.ContentChanged() 251 } 252 253 func (d *Drawer) Reader() iorw.ReaderAt { return d.reader } 254 255 //---------- 256 257 var limitedReaderPadding = 3000 258 259 func (d *Drawer) limitedReaderPad(offset int) iorw.ReaderAt { 260 pad := limitedReaderPadding 261 return iorw.NewLimitedReaderAtPad(d.reader, offset, offset, pad) 262 } 263 264 func (d *Drawer) limitedReaderPadSpace(offset int) iorw.ReaderAt { 265 // adjust the padding to avoid immediate flicker for x chars for the case of long lines 266 max := 1000 267 pad := limitedReaderPadding // in tests it could be a small num 268 if limitedReaderPadding >= max { 269 u := offset - limitedReaderPadding 270 diff := max - (u % max) 271 pad = limitedReaderPadding - diff 272 } 273 return iorw.NewLimitedReaderAtPad(d.reader, offset, offset, pad) 274 } 275 276 //---------- 277 278 func (d *Drawer) ContentChanged() { 279 d.opt.measure.updated = false 280 d.opt.syntaxH.updated = false 281 d.opt.wordH.updatedWord = false 282 d.opt.wordH.updatedOps = false 283 d.opt.parenthesisH.updated = false 284 } 285 286 //---------- 287 288 func (d *Drawer) FontFace() *fontutil.FontFace { return d.fface } 289 func (d *Drawer) SetFontFace(ff *fontutil.FontFace) { 290 if ff == d.fface { 291 return 292 } 293 d.fface = ff 294 d.lineHeight = mathutil.Intf2(d.fface.LineHeight()) 295 296 d.opt.measure.updated = false 297 } 298 299 func (d *Drawer) LineHeight() int { 300 if d.fface == nil { 301 return 0 302 } 303 return d.fface.LineHeightInt() 304 } 305 306 func (d *Drawer) SetFg(fg color.Color) { d.fg = fg } 307 308 //---------- 309 310 func (d *Drawer) FirstLineOffsetX() int { return d.firstLineOffsetX } 311 func (d *Drawer) SetFirstLineOffsetX(x int) { 312 if x != d.firstLineOffsetX { 313 d.firstLineOffsetX = x 314 d.opt.measure.updated = false 315 } 316 } 317 318 //---------- 319 320 func (d *Drawer) Bounds() image.Rectangle { return d.bounds } 321 func (d *Drawer) SetBounds(r image.Rectangle) { 322 //d.ContentChanged() // commented for performance 323 // performance (doesn't redo d.opt.wordH.updatedWord) 324 if r.Size() != d.bounds.Size() { 325 d.opt.measure.updated = false 326 d.opt.syntaxH.updated = false 327 d.opt.wordH.updatedOps = false 328 d.opt.parenthesisH.updated = false 329 } 330 331 d.bounds = r // always update value (can change min) 332 } 333 334 //---------- 335 336 func (d *Drawer) RuneOffset() int { 337 return d.opt.runeO.offset 338 } 339 func (d *Drawer) SetRuneOffset(v int) { 340 d.opt.runeO.offset = v 341 342 d.opt.syntaxH.updated = false 343 d.opt.wordH.updatedOps = false 344 d.opt.parenthesisH.updated = false 345 } 346 347 //---------- 348 349 func (d *Drawer) SetCursorOffset(v int) { 350 d.opt.cursor.offset = v 351 352 d.opt.wordH.updatedWord = false 353 d.opt.wordH.updatedOps = false 354 d.opt.parenthesisH.updated = false 355 } 356 357 //---------- 358 359 func (d *Drawer) ready() bool { 360 return !(d.fface == nil || d.reader == nil || d.bounds == image.ZR) 361 } 362 363 //---------- 364 365 func (d *Drawer) Measure() image.Point { 366 if !d.ready() { 367 return image.Point{} 368 } 369 if d.opt.measure.updated { 370 return d.opt.measure.measure 371 } 372 d.opt.measure.updated = true 373 d.opt.measure.measure = d.measure2() 374 return d.opt.measure.measure 375 } 376 377 func (d *Drawer) measure2() image.Point { 378 if d.Opt.QuickMeasure { 379 return d.bounds.Size() 380 } 381 return d.measureContent() 382 } 383 384 // Full content measure in pixels. To be used only for small content. 385 func (d *Drawer) measureContent() image.Point { 386 d.st = State{} 387 iters := d.sIters(d.Opt.EarlyExitMeasure, &d.iters.measure) 388 d.loopInit(iters) 389 d.loop() 390 // remove bounds min and return only the measure 391 p := d.st.measure.penMax.ToPointCeil() 392 m := p.Sub(d.bounds.Min) 393 return m 394 } 395 396 //---------- 397 398 func (d *Drawer) Draw(img draw.Image) { 399 updateSyntaxHighlightOps(d) 400 updateWordHighlightWord(d) 401 updateWordHighlightOps(d) 402 updateParenthesisHighlight(d) 403 404 d.st = State{} 405 iters := []Iterator{ 406 &d.iters.runeR, 407 &d.iters.curColors, 408 &d.iters.colorize, 409 &d.iters.line, 410 &d.iters.lineWrap, 411 &d.iters.earlyExit, // after iters that change pen.Y 412 &d.iters.indent, 413 &d.iters.annotations, // after iters that change the line 414 &d.iters.bgFill, 415 &d.iters.drawR, 416 &d.iters.cursor, 417 } 418 d.loopInit(iters) 419 d.header0() 420 d.st.drawR.img = img 421 d.loop() 422 } 423 424 //---------- 425 426 func (d *Drawer) LocalPointOf(index int) image.Point { 427 if !d.ready() { 428 return image.Point{} 429 } 430 d.st = State{} 431 d.st.pointOf.index = index 432 iters := d.sIters(true, &d.iters.pointOf) 433 d.loopInit(iters) 434 d.header1() 435 d.loop() 436 return d.st.pointOf.p 437 } 438 439 //---------- 440 441 func (d *Drawer) LocalIndexOf(p image.Point) int { 442 if !d.ready() { 443 return 0 444 } 445 d.st = State{} 446 d.st.indexOf.p = mathutil.PIntf2(p) 447 iters := d.sIters(true, &d.iters.indexOf) 448 d.loopInit(iters) 449 d.header1() 450 d.loop() 451 return d.st.indexOf.index 452 } 453 454 //---------- 455 456 func (d *Drawer) AnnotationsIndexOf(p image.Point) (int, int, bool) { 457 if !d.ready() { 458 return 0, 0, false 459 } 460 d.st = State{} 461 d.st.annotationsIndexOf.p = mathutil.PIntf2(p) 462 iters := d.sIters(true, &d.iters.annotations, &d.iters.annotationsIndexOf) 463 d.loopInit(iters) 464 d.header0() 465 d.loop() 466 467 st := &d.st.annotationsIndexOf 468 if st.eindex < 0 { 469 return 0, 0, false 470 } 471 return st.eindex, st.offset, true 472 } 473 474 //---------- 475 476 func (d *Drawer) loopInit(iters []Iterator) { 477 l := &d.loopv 478 l.iters = iters 479 for _, iter := range l.iters { 480 iter.Init() 481 } 482 } 483 484 func (d *Drawer) loop() { 485 l := &d.loopv 486 l.stop = false 487 for !l.stop { // loop for each rune 488 l.i = 0 489 _ = d.iterNext() 490 } 491 for _, iter := range l.iters { 492 iter.End() 493 } 494 } 495 496 // To be called from iterators, inside the Iter() func. 497 func (d *Drawer) iterNext() bool { 498 l := &d.loopv 499 if !l.stop && l.i < len(l.iters) { 500 u := l.iters[l.i] 501 l.i++ 502 u.Iter() 503 l.i-- 504 } 505 return !l.stop 506 } 507 508 func (d *Drawer) iterStop() { 509 d.loopv.stop = true 510 } 511 512 func (d *Drawer) iterNextExtra() bool { 513 d.iters.runeR.pushExtra() 514 defer d.iters.runeR.popExtra() 515 return d.iterNext() 516 } 517 518 //---------- 519 520 func (d *Drawer) visibleLen() (int, int, int, int) { 521 d.st = State{} 522 iters := d.sIters(true) 523 d.loopInit(iters) 524 d.header0() 525 startRi := d.st.runeR.ri 526 d.loop() 527 528 // from the line start 529 drawOffset := startRi 530 drawLen := d.st.runeR.ri - drawOffset 531 // from the current offset 532 offset := d.opt.runeO.offset 533 offsetLen := d.st.runeR.ri - offset 534 535 return drawOffset, drawLen, offset, offsetLen 536 } 537 538 //---------- 539 540 func (d *Drawer) ScrollOffset() image.Point { 541 return image.Point{0, d.RuneOffset()} 542 } 543 func (d *Drawer) SetScrollOffset(o image.Point) { 544 d.SetRuneOffset(o.Y) 545 } 546 547 func (d *Drawer) ScrollSize() image.Point { 548 return image.Point{0, d.reader.Max() - d.reader.Min()} 549 } 550 551 func (d *Drawer) ScrollViewSize() image.Point { 552 nlines := d.boundsNLines() 553 n := d.scrollSizeY(nlines, false) // n runes 554 return image.Point{0, n} 555 } 556 557 //---------- 558 559 func (d *Drawer) ScrollPageSizeY(up bool) int { 560 nlines := d.boundsNLines() 561 return d.scrollSizeY(nlines, up) 562 } 563 564 //---------- 565 566 func (d *Drawer) ScrollWheelSizeY(up bool) int { 567 nlines := d.boundsNLines() 568 569 // limit nlines 570 nlines /= 4 571 if nlines < 1 { 572 nlines = 1 573 } else if nlines > 4 { 574 nlines = 4 575 } 576 577 return d.scrollSizeY(nlines, up) 578 } 579 580 //---------- 581 582 // integer lines 583 func (d *Drawer) boundsNLines() int { 584 dy := mathutil.Intf1(d.bounds.Dy()) 585 return int(dy / d.lineHeight) 586 } 587 588 //---------- 589 590 func (d *Drawer) scrollSizeY(nlines int, up bool) int { 591 if up { 592 o := d.scrollSizeYUp(nlines) 593 return -(d.opt.runeO.offset - o) 594 } else { 595 o := d.scrollSizeYDown(nlines) 596 return o - d.opt.runeO.offset 597 } 598 } 599 600 //---------- 601 602 func (d *Drawer) scrollSizeYUp(nlines int) int { 603 return d.wlineStartIndex(true, d.opt.runeO.offset, nlines, nil) 604 } 605 func (d *Drawer) scrollSizeYDown(nlines int) int { 606 return d.wlineStartIndexDown(d.opt.runeO.offset, nlines) 607 } 608 609 //---------- 610 611 func (d *Drawer) RangeVisible(offset, length int) bool { 612 v1 := penVisibility(d, offset) 613 if v1.full || v1.partial { 614 return true 615 } 616 v2 := penVisibility(d, offset+length) 617 if v2.full || v2.partial { 618 return true 619 } 620 // v1 above and v2 below view is considered not visible (will align with v1 at top on RangeVisibleOffset(...)) 621 return false 622 } 623 624 func (d *Drawer) RangeVisibleOffset(offset, length int) int { 625 rnlines := d.rangeNLines(offset, length) 626 bnlines := d.boundsNLines() 627 // extra lines beyond the lines ocuppied by the range 628 freeLines := bnlines - rnlines 629 if freeLines < 0 { 630 freeLines = 0 631 } 632 633 topLines := func(n int) int { 634 // top lines visible before the offset line 635 return d.wlineStartIndex(true, offset, n, nil) 636 } 637 alignTop := func() int { 638 return topLines(0) 639 } 640 alignBottom := func() int { 641 return topLines(freeLines) 642 } 643 alignCenter := func() int { 644 return topLines(freeLines / 2) 645 } 646 keepCurAlignment := func() int { 647 return mathutil.Min(d.opt.runeO.offset, d.reader.Max()) 648 } 649 650 // don't let offset+length be beyond max for v2 (would give not visible) 651 offset2 := offset + length 652 if offset2 > d.reader.Max() { 653 offset2 = offset 654 } 655 656 v1 := penVisibility(d, offset) 657 v2 := penVisibility(d, offset2) 658 if v1.full { 659 if v2.full { 660 return keepCurAlignment() 661 } else if v2.partial { 662 if v2.top { 663 // panic (can't be: v1 is full) 664 } else { 665 return alignBottom() 666 } 667 } else if v2.not { // past bottom line 668 return alignBottom() 669 } else { 670 // panic 671 } 672 } else if v1.partial { 673 if v1.top { 674 return alignTop() 675 } else { 676 return alignBottom() 677 } 678 } else if v1.not { 679 if v2.full { 680 return alignTop() 681 } else if v2.partial { 682 return alignTop() 683 } else if v2.not { 684 return alignCenter() 685 } else { 686 // panic 687 } 688 } else { 689 // panic 690 } 691 692 // should never get here 693 return alignCenter() 694 } 695 696 //---------- 697 698 func (d *Drawer) rangeNLines(offset, length int) int { 699 pr1, pr2, ok := d.wlineRangePenBounds(offset, length) 700 if ok { 701 w := pr2.Max.Y - pr1.Min.Y 702 u := int(w / d.lineHeight) 703 if u >= 1 { 704 return u 705 } 706 } 707 return 1 // always at least one line 708 } 709 710 func (d *Drawer) wlineRangePenBounds(offset, length int) (_, _ mathutil.RectangleIntf, _ bool) { 711 var pr1, pr2 mathutil.RectangleIntf 712 var ok1, ok2 bool 713 d.wlineStartLoopFn(true, offset, 0, 714 func() { 715 ok1 = true 716 pr1 = d.iters.runeR.penBounds() 717 }, 718 func() { 719 if d.st.runeR.ri == offset+length { 720 ok2 = true 721 pr2 = d.iters.runeR.penBounds() 722 d.iterStop() 723 return 724 } 725 if !d.iterNext() { 726 return 727 } 728 }) 729 return pr1, pr2, ok1 && ok2 730 } 731 732 //---------- 733 734 func (d *Drawer) wlineStartIndexDown(offset, nlinesDown int) int { 735 count := 0 736 startRi := 0 737 d.wlineStartLoopFn(true, offset, 0, 738 func() { 739 startRi = d.st.runeR.ri 740 if nlinesDown == 0 { 741 d.iterStop() 742 } 743 }, 744 func() { 745 if d.st.line.lineStart || d.st.lineWrap.postLineWrap { 746 if d.st.runeR.ri != startRi { // bypass ri at line start 747 count++ 748 if count >= nlinesDown { 749 d.iterStop() 750 return 751 } 752 } 753 } 754 if !d.iterNext() { 755 return 756 } 757 }) 758 return d.st.runeR.ri 759 } 760 761 //---------- 762 763 func (d *Drawer) header0() { 764 _ = d.header(d.opt.runeO.offset, 0) 765 } 766 767 func (d *Drawer) header1() { 768 d.st.earlyExit.extraLine = true // extra line at bottom 769 ul := d.header(d.opt.runeO.offset, 1) // extra line at top 770 if ul > 0 { 771 d.st.runeR.pen.Y -= d.lineHeight * mathutil.Intf(ul) 772 } 773 } 774 775 //---------- 776 777 func (d *Drawer) header(offset, nLinesUp int) int { 778 // smooth scrolling 779 adjustPenY := mathutil.Intf(0) 780 if d.smoothScroll { 781 adjustPenY += d.smoothScrolling(offset) 782 } 783 784 // iterate to the wline start 785 st1RRPen := d.st.runeR.pen // keep initialized state to refer to pen difference 786 uppedLines := d.wlineStartState(false, offset, nLinesUp) 787 adjustPenY += d.st.runeR.pen.Y - st1RRPen.Y 788 d.st.runeR.pen.Y -= adjustPenY 789 790 return uppedLines 791 } 792 793 func (d *Drawer) smoothScrolling(offset int) mathutil.Intf { 794 // keep/restore state to avoid interfering with other running iterations 795 st := d.st 796 defer func() { d.st = st }() 797 798 s, e := d.wlineStartEnd(offset) 799 t := e - s 800 if t <= 0 { 801 return 0 802 } 803 k := offset - s 804 perc := float64(k) / float64(t) 805 return mathutil.Intf(int64(float64(d.lineHeight) * perc)) 806 } 807 808 func (d *Drawer) wlineStartEnd(offset int) (int, int) { 809 s, e := 0, 0 810 d.wlineStartLoopFn(true, offset, 0, 811 func() { 812 s = d.st.runeR.ri 813 }, 814 func() { 815 if d.st.line.lineStart || d.st.lineWrap.postLineWrap { 816 if d.st.runeR.ri > offset { 817 e = d.st.runeR.ri 818 d.iterStop() 819 return 820 } 821 } 822 if !d.iterNext() { 823 return 824 } 825 }) 826 if e == 0 { 827 e = d.st.runeR.ri 828 } 829 return s, e 830 } 831 832 //---------- 833 834 func (d *Drawer) wlineStartLoopFn(clearState bool, offset, nLinesUp int, fnInit func(), fn func()) { 835 // keep/restore iters 836 iters := d.loopv.iters 837 defer func() { d.loopv.iters = iters }() 838 839 d.loopv.iters = d.sIters(false, &FnIter{fn: fn}) 840 d.wlineStartState(clearState, offset, nLinesUp) 841 fnInit() 842 d.loop() 843 } 844 845 //---------- 846 847 // Leaves the state at line start 848 func (d *Drawer) wlineStartState(clearState bool, offset, nLinesUp int) int { 849 // keep/restore iters 850 iters := d.loopv.iters 851 defer func() { d.loopv.iters = iters }() 852 853 // set limited reading here to have common limits on the next two calls 854 //var rd iorw.Reader 855 //rd := d.limitedReaderPad(offset) 856 rd := d.limitedReaderPadSpace(offset) 857 858 // find start (state will reach offset) 859 cp := d.st // keep state 860 k := d.wlineStartIndex(clearState, offset, nLinesUp, rd) 861 uppedLines := d.st.lineStart.uppedLines 862 863 // leave state at line start instead of offset 864 d.st = cp // restore state 865 _ = d.wlineStartIndex(clearState, k, 0, rd) 866 867 return uppedLines 868 } 869 870 //---------- 871 872 func (d *Drawer) wlineStartIndex(clearState bool, offset, nLinesUp int, rd iorw.ReaderAt) int { 873 if clearState { 874 d.st = State{} 875 } 876 d.st.lineStart.offset = offset 877 d.st.lineStart.nLinesUp = nLinesUp 878 d.st.lineStart.reader = rd 879 iters := d.sIters(false, &d.iters.lineStart) 880 d.loopInit(iters) 881 d.loop() 882 return d.st.lineStart.ri 883 } 884 885 //---------- 886 887 // structure iterators 888 func (d *Drawer) sIters(earlyExit bool, more ...Iterator) []Iterator { 889 iters := []Iterator{ 890 &d.iters.runeR, 891 &d.iters.line, 892 &d.iters.lineWrap, 893 } 894 if earlyExit { 895 iters = append(iters, &d.iters.earlyExit) 896 } 897 iters = append(iters, &d.iters.indent) 898 iters = append(iters, more...) 899 return iters 900 } 901 902 //---------- 903 904 type Iterator interface { 905 Init() 906 Iter() 907 End() 908 } 909 910 //---------- 911 912 type FnIter struct { 913 fn func() 914 } 915 916 func (it *FnIter) Init() {} 917 func (it *FnIter) Iter() { it.fn() } 918 func (it *FnIter) End() {} 919 920 //---------- 921 922 func assignColor(dest *color.Color, src color.Color) { 923 if src != nil { 924 *dest = src 925 } 926 }