Line numbers for soft-wrapped styled text?
Alex Tweedly
alex at tweedly.net
Sat Apr 1 10:54:01 EDT 2017
Conclusion : I give up :-)
We have three 'completed' approaches to the line numbering problem:
1. Jim Lambert's use of decimal-numbered-list style within the field
(has the limitation of not allowing the user's text to include list
styles, and some aesthetic constraints - but if you can live with those,
IMHO it's by far the best solution)
2. Jim's earlier version using a separate text field with only the line
numbers available. (Still has some aesthetic constraints, and noticeably
slower than (1) above)
3. My styled text-based version using space-below in a separate field
(works, no restrictions afaik, but *barely* fast enough for general use
- and certainly too slow for many uses).
And then we have another, not yet complete, solution - use Hermann's
script to find the limits on the line numbers which are visible, and
then something lik emy 'spacebelow' to set just those lines - so it has
the potential to be fast enough.
I set out to optimise the 'visibleLineNumber' function, and succeeded in
getting it down to approx 20% of the original version, by:
- reduce from 2 to 1 height calculations per iteration
- convert from recursive to iterative
- use Newton-Raphson style linear interpolation, with tweaks to avoid
the pathological end cases
- optimise the start values for finding the last visible line based on
the effective field height and min allowed text size
I was just feeling good about that, and getting ready to start on the
second half - displaying the line numbers for only those visible lines -
when I got this email from Hermann re. the bug in formattedRect, and the
need to use formattedHeight of chunk instead.
formattedHeight of line N to M of fld F has its own problems - it
misses out the space needed for the last line if that line happens to be
empty, and ignores spacebelow - but that can be easily avoided
> function getAccurateFormattedHeight pFld, N, M
>
> put the formattedheight of line N to M of fld pFld into h
>
> if line M of fld pFld is empty then add the formattedheight of line
> M of fld pFld to h
>
> add the spacebelow of line M of fld pFld to h
>
> return h
>
> end getAccurateFormattedHeight
>
Then I started testing on "hard" fields - and only then did I discover
the real problem - formattedHeight of a chunk is just too slow. It takes
50-100 msecs to get such a chunk from a large-ish field (30K lines,
heavily styled, from the dictionary); so even though the number of
iterations was down to between 7 and 12, that still represents over a
second of elapsed time - which is just too long to be feasible.
And now that I have created this test case, it shows that solution no. 3
above is also too slow.
So, I give up :-)
-- Alex.
On 29/03/2017 11:48, hh via use-livecode wrote:
> Alex,
> before you waste valuable time:
> The formattedRect can NOT be used in LC 7/8/9, because of the
> (2^15 div 2)-limit for coordinates is active for that.
>
> So the algorithm works in LC 6.7.11, but the results are sadly
> 'extremely' wrong in LC 7/8/9:
> Crossing the 'limit' with a vertical coordinate jumps to negative
> values for these coordinates. No chance to repair.
>
> Using instead the formattedHeight of line 1 to N minus the
> vscroll works. But it is not exactly doable if the last line is
> empty (this is not added to the formattedHeight) = your space-below
> problem -- a bug, TMHO.
>
> I found another "dirty" but fast way to get pixel-exact line
> positions for any field in LC 6/7/8/9. One could perhaps use it
> until the text measuring problem is solved.
>
> One needs a field that is transparent, is not threeD and has no
> border (use instead an opaque rectangle with border as background).
> Then set the hgrid to true (this has no visual effect if using as
> borderColor the backColor of the graphic behind the field, for
> testing I set the borderColor to red).
> Now make a snapshot from the field and walk through the maskData
> to search all such rows in the image that have a fully opaque
> line. This is unique as long as the field has left and right
> margins > 0.
> This search in the image is *very fast* (< 2 ms), only taking the
> snapshot and getting the maskdata needs some time (around 70 ms in
> sum for 414x736 pixels).
>
> A script that works here correctly with my tests is attached below.
> It needs here in LC 9 about 120 ms for 4000 lines of heavily styled
> text from the dictionary in a field of iPhone6 size (414x736).
> Please change/improve/optimize it, especially the search part. This
> is still the 'simple' binary search.
>
> Hermann
>
> You need a field "TEXT" (transparent etc, see above) and a
> template-num-field "n00" (see my settings below as example).
> Fields for feedback: "Range", "timing1", "timing2", "info"
> ===== [1] The field's script
> on textchanged
> updateNbs2
> end textchanged
>
> on scrollbardrag
> updateNbs2
> end scrollbardrag
> ===== [2] The numbering script (100 lines)
> local nn="lineNumbers",l0,t0,b0,w0,h0,sw0,v0
> local rg="TEXT", fw=32 -- num-field width
>
> on updateNbs2
> lock screen; lock messages
> put the millisecs into m0 ---- start
> put the top of fld rg into t0
> put the bottom of fld rg into b0
> put the left of fld rg into l0
> put the width of fld rg into w0
> put the height of fld rg into h0
> put the vscroll of fld rg into v0
> put the scrollbarwidth of fld rg into sw0
> set the properties of the templatefield to \
> the properties of fld "n00" -- see below
> if there is a grp nn then delete grp nn
> create grp nn
> set lockloc of grp nn to true
> set opaque of grp nn to true
> set backColor of grp nn to "204,204,204"
> set lockloc of grp nn to true
> set width of grp nn to fw
> set height of grp nn to the height of fld "text"
> set topleft of grp nn to l0-fw, t0
> set layer of grp nn to 1
> put the millisecs into m1 ---- diff1
> put visibleTextLines()-1 into tL
> put the millisecs into m2 ---- diff2
> put getLocs() into g
> put the millisecs into m3 ---- diff3
> put "Lines: " & (tL,tL-1+the num of lines of g) \
> into fld "range"
> repeat for each line L in g
> put item 2 of L into i
> put ("n"&i) into ni
> if there is a fld ni then delete fld ni
> create fld ni in grp nn
> set left of fld ni to l0-fw+2
> set top of fld ni to i-10
> put tL into fld ni
> add 1 to tL
> end repeat
> reset the templatefield
> put the millisecs into m4 ---- diff4
> put (m4-m0 &"="& m2-m1 &"+"& m3-m2 &"+"& m4-m3+m1-m0) & \
> " ms" into fld "timing"
> unlock screen; unlock messages
> end updateNbs2
>
> function getLocs
> put the millisecs into m0 --------------------- start
> set topleft of img rg to \
> (5+the right of fld rg, the top of fld rg)
> put w0-sw0 into w1
> put the millisecs into m1 --------------------- diff1
> export snapshot from fld rg to img rg as PNG
> put the millisecs into m2 --------------------- diff2
> put the maskdata of img rg into mData
> put the millisecs into m3 --------------------- diff3
> put NumToByte(255) into c1
> repeat 1+log2(w1)
> put c1 after c1
> end repeat
> put byte 1 to w1 of c1 into c00
> put the millisecs into m4 --------------------- diff4
> repeat with i=1 to h0
> put (i-1)*w0 into i0
> if byte i0+1 to i0+w1 of mData is c00
> then put cr&(0,t0+i) after s
> end repeat
> -- avoid overlapping:
> -- put (0,min(t0,10+item 2 of line 1 of s)) before s
> put cr & (0,max(h0+t0,10+item 2 of line -1 of s)) after s
> put s into fld "info"
> put the millisecs into m5 --------------------- diff5
> put (m5-m0 &"="& m2-m1 &"+"& m3-m2 &"+"& m5-m3+m1-m0) & \
> " ms" into fld "timing2"
> return char 2 to -1 of s
> end getLocs
>
> function visibleTextLines
> lock screen; lock messages
> put the selectedChunk into sc
> put the scrollbarWidth of fld rg into sw
> put the margins of fld rg into m
> put (m,m,m,m) into m -- now we have at least 4 items
> put the num of lines of fld rg into n
> put findTopLine(v0+t0-1+item 4 of m,1,n) into L1
> return L1-1
> end visibleTextLines
>
> function findTopLine x,n1,n -- x=top pixel, n1=start, n=max
> put n1+((n-n1) div 2) into m
> if (the formattedHeight of line 1 to (m+1) of fld rg) >= x then
> if (the formattedHeight of line 1 to m of fld rg) < x then
> return m+1
> else
> if m <= n1 then return n1
> else return findTopLine(x,n1,m-1)
> end if
> else
> if m >= n then return n
> else return findTopLine(x,m+1,n)
> end if
> end findTopLine
>
> ===== [3] This may be used for the "template"-num-fld "n00"
> -- set locktext of fld "n00" to true
> -- set dontwrap of fld "n00" to true
> -- set traversalOn of fld "n00" to false
> -- set height of fld "n00" to 12
> -- set width of fld "n00" to fw
> -- set margins of fld "n00" to 4
> -- set textalign of fld "n00" to "right"
> -- set opaque of fld "n00" to false
> -- set threed of fld "n00" to false
> -- set showborder of fld "n00" to false
> -- set textsize of fld "n00" to 10
> -- set foreColor of fld "n00" to "0,0,255"
> =====END_OF_SCRIPT
>
>
> _______________________________________________
> use-livecode mailing list
> use-livecode at lists.runrev.com
> Please visit this url to subscribe, unsubscribe and manage your subscription preferences:
> http://lists.runrev.com/mailman/listinfo/use-livecode
More information about the use-livecode
mailing list