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