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  }