/* 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2006  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "swidget/STextView.h"
#include "stoolkit/SCluster.h"

#define DEBUG_SPEED 0


/* I could debug speed of redrawing. Most time (98%) is taken in
  i += font.draw (c, p, dm, &g.array()[i], g.size()-i); */

#if DEBUG_SPEED
#ifndef USE_WINAPI
#include <sys/time.h>
#else
#include <winsock.h>
#include <time.h>
#endif


static struct timeval thatTime;

static void
timerStart()
{
  gettimeofday (&thatTime, 0);
}

static void
timerStop()
{
  struct timeval thisTime;
  gettimeofday (&thisTime, 0);
  if (thisTime.tv_usec < thatTime.tv_usec)
  {
    thisTime.tv_sec--;
    thisTime.tv_usec+=1000000;
  }
  thisTime.tv_sec -= thatTime.tv_sec;
  thisTime.tv_usec -= thatTime.tv_usec;
  int msec = (int) thisTime.tv_sec * 1000 + thisTime.tv_usec/1000;
  fprintf (stderr, "Elapsed time: %d msecs\n", msec);
}
#endif


static unsigned int sane_index (const SV_UINT& array, unsigned int index);

/**
 * The text data is mine. I'll delete it.
 */
STextView::STextView (void)
  : lrpen (SColor (0.0, 0.0, 0.0, 1.0)),
  rlpen (SColor (0.0, 0.0, 1.0, 1.0)),
  underlineColor("red")
{
  printerPageSize = 0;
  highlightMode = "";
  isWordWrapOn = false;
  isEditable = false;
  clipx = clipy = 0;
  clipw = cliph = 0;
  fontSize = 16.0;
  lineend = true;
  multiline = true;
  syntax.setTextData (&textData);
  syntax.addTextDataListener (this);
  textData.addTextDataListener (this);
  textData.addLineTracker (&syntax);
}

/**
 * Make a text-view from external text
 * @param utf8 is the utf8 ancoded text
 */
STextView::STextView (const SString& utf8)
  : textData (utf8), lrpen (SColor (0.0, 0.0, 0.0, 1.0)),
  rlpen (SColor (0.0, 0.0, 1.0, 1.0)),
  underlineColor("red")
{
  printerPageSize = 0;
  highlightMode = "";
  isWordWrapOn = false;
  isEditable = false;
  clipx = clipy = 0;
  clipw = cliph = 0;
  fontSize = 16.0;
  lineend = true;
  multiline = true;
  wrapAndPosition ();
  syntax.setTextData (&textData);
  syntax.addTextDataListener (this);
  textData.addTextDataListener (this);
  textData.addLineTracker (&syntax);
  textData.clearEvent();
}

/**
 * Set the text and 
 * Do the reordering, expanding for the whole text.
 * Not very efficient with large text.
 */
void
STextView::setText(const SString& text)
{
  textData.clear();
  textData.fireEvent();
  textData.insert(text);
 
  /* HACK FOR LABELS - they neeed to know their exact size */
  for (unsigned int i=0; i<textData.size(); i++)
  {
    textData.setVisible(i);
    textData.setReordered(i);
  }
  wrapAndPosition();
  textData.fireEvent();
}

STextView::~STextView ()
{
}

void
STextView::setClippingArea (int _x, int _y, 
          unsigned int _width, unsigned int _height)
{
  clipx = _x;
  clipy = _y;
  clipw = _width;
  cliph = _height;
}

/**
 * Set the font and recalculate sizes.
 * @param _font is the new font.
 */
void
STextView::setFont (const SString& _font, double _fontSize)
{
  if (_fontSize > 0.0) fontSize = _fontSize;
  font = SFont(_font, fontSize);
  setPen ();
  setReordered ();

  lineHeight = (unsigned int ) (font.ascent() + font.descent() + font.gap());
  lineAscent = (unsigned int) font.ascent();
  preferredSize.height = (textData.size()==0) ? lineHeight
      : textData.size() *  lineHeight;
}

/**
 * Set the size of the font.
 * @param size is the size of the font.
 */
void
STextView::setFontSize (double newsize)
{
  font.setSize(newsize);
  setPen ();
  setReordered ();

  lineHeight = (unsigned int ) (font.ascent() + font.descent() + font.gap());
  lineAscent = (unsigned int) font.ascent();
  preferredSize.height = (textData.size()==0) ? lineHeight
      : textData.size() *  lineHeight;
}

/**
 * Set the pen size according to font size.
 */
void
STextView::setPen ()
{
  double pensize = 1.0;
  double pointsize = font.getSize();
  if (pointsize <= 16)
  {
   pensize = 0.125;
  }
  else if (pointsize <=24)
  {
   pensize = 0.25;
  }
  else if (pointsize <=80)
  {
   pensize = 0.5;
  }
  else if (pointsize <=100)
  {
   pensize = 0.75;
  }
  lrpen.setLineWidth (pensize);
  rlpen.setLineWidth (pensize);
}

/**
 * Set the text alignment align is true if it is right aligned.
 */
void
STextView::setAlignment (bool align)
{
  alignment = align;
}
/**
 */
void
STextView::setMultiline (bool _multiline)
{
  multiline = _multiline;
  setReordered ();
}
/**
 */
bool
STextView::isMultiline () const
{
  return multiline;
}
/**
 * Set the viewport. This is the location that casts to {0;0}
 * @param _viewPort is the new viewport.
 */
void
STextView::setViewPort (const SLocation& _viewPort)
{
  viewPort = _viewPort;
}

const SLocation&
STextView::getViewPort()
{
  return viewPort;
}

/**
 * Resize the component and redraw.
 */
void
STextView::resize (const SDimension& _dimension)
{
  SComponent::resize (_dimension);
  setReordered ();
}

unsigned int
STextView::getLineIndex (int locy)
{
  /* binary search linespan */
  unsigned int    top;
  unsigned int    bottom;
  unsigned int    mid;
  top = lineSpan.size();
  bottom = 0;
  int y = locy - (location.y + viewPort.y) + 1;
  while (top > bottom)
  {
    mid = (top+bottom)/2;
    unsigned int sindex = lineHeight * sane_index (lineSpan, mid+1);
    if (y == (int) (sindex))
    {
      top = mid;
      break;
    }
    if (y < (int) (sindex))
    {
      top = mid;
      continue;
    }
    bottom = mid + 1;
  }
  return top;
}

/**
 * @param l is the location on the canvas.
 */
SCursorIndex
STextView::getCursorIndex (const SLocation& l)
{
  unsigned int line = getLineIndex (l.y);

  if (line >= textData.size())
  {
    if (line > 0 && !textData.isProperLine (line-1))
    {
      return SCursorIndex (line-1, textData.size(line-1));
    }
    return SCursorIndex (line, 0);
  }


  SV_UINT brk;
  if (line < textData.size())
  {
    setVisible (line);
    brk = breaks[line];
  }

  /* Linear search. I don't expect long lines. */
  unsigned int offset = lineHeight * sane_index (lineSpan, line);

  SLocation lm (0, viewPort.y-location.y+(int)offset);
  bool lr  = textData.isLR (line);

  /* Transpose to LR */
  lm.x = l.x-location.x+viewPort.x;

  /* convert as if it were lr */
  if (!lr) lm.x =  (int)size.width - (int)lm.x;

  /* find where the glyph starts */
  unsigned int i;
  unsigned int currExt=0;
  for (i=0; i<brk.size(); i++)
  {
    lm.y += lineHeight; 
    if (lm.y > l.y) break; 
    currExt = brk[i];
  }
  unsigned int si = textData.size(line);
  unsigned int nextExt = si;
  if (i<brk.size()) nextExt = brk[i];
  // SGC bool endsline = false;

  /* find the x position witihn the line */
  SCursorIndex cindex (line, currExt);
  bool llr = textData.isLR(line);
  bool before = true;
  for (i=currExt; i<nextExt && i<si; i++)
  {
     unsigned int begpos = posBefore[line][i];
     unsigned int endpos = posAfter[line][i];
     unsigned int midpos = (begpos+endpos) / 2;

     const SGlyph& g = textData.glyphAt (STextIndex (line, i));
     bool glr = g.isLR();
     before = ((glr && llr) | (!glr && !llr));

     if (g.isEOP())
     {
       // SGC endsline = true; 
     }
     else if (lm.x >= (int)begpos && lm.x < (int)midpos)
     {
       cindex = SCursorIndex(line, i, before); /* logical before */
       break;
     }
     else if (lm.x >= (int)midpos && lm.x < (int)endpos) 
     {
       cindex = SCursorIndex(line, i, !before); /* logical after */
       break;
     }
  }
  if (i==si && lm.x > 0 && si > 0)
  {
    /* rl is initialized already */
    cindex = SCursorIndex(line, i, before);
  } 
  /* beginning or found */
  return SCursorIndex(cindex);
}

SLocation
STextView::getCursorLocation (const SCursorIndex& cursorIndex)
{
  return SLocation(getTextLocation (cursorIndex.textIndex, cursorIndex.before));
}

/**
 * Convert a line and index to a location on the screen
 * This is the top left(lr) or right(rl) corner of the glyph.
 * @param line is the line number. 
 * @param index is the index.
 * @param before is true if we need the index before the glyph - 
 *  logical meaning.
 * Here is an LR glyph:
 *   Paragraph LR  A                        A Paragrph RL
 *                ^  ^                     ^ ^
 *                |  |                     | |
 *     Before ----+  +--- After  Before ---+ +---- After              
 */
SLocation
STextView::getTextLocation (const STextIndex& textIndex, bool before)
{
  unsigned int line = textIndex.line;
  unsigned int index = textIndex.index;
  unsigned int ll = sane_index (lineSpan, line);
  SLocation ret;
  if (line >= textData.size())
  {
    ret  = SLocation (location.x+viewPort.x, 
          location.y+viewPort.y + lineHeight * ll);
  }
  else
  {
    setVisible (line);
    SV_UINT brk =  breaks[line];
    /* It may be second or third line. */
    unsigned int i = 0;
    int yoffset = 0;
    unsigned int posafter = 0;
    for (i=0; i<brk.size()-1; i++)
    {
      if (index < brk[i]) break;
      posafter = brk[i];
      yoffset++;
    }

    int si = 0;
    if (index < textData.size(line))
    {
       /* convert logical 'before' to phisical */ 
       bool glr = textData.isLR(STextIndex(line, index));
       bool llr = textData.isLR(line);

       /*  paragraph directionality is different from glyph directionality */
       bool swap = ((llr && !glr) || (!llr && glr));

       si = ((before && !swap) || (!before && swap)) 
             ? (int) posBefore[line][index] 
             : (int) posAfter[line][index];

    }
    else if (index > textData.size(line) && textData.isProperLine(line))
    {
      /* last one */
      si = 0;
      yoffset++;
    }
    else /* Streched beyond the last one. */
    { 
      SV_UINT v = posAfter[line];
      unsigned int max = 0;
      unsigned int maxi = 0;
      for (unsigned int i=posafter; i<v.size(); i++)
      {
        if (v[i] > max)
        {
          max = v[i];
          maxi  = i;
        }
      }
      si = posAfter[line][maxi];
    }
    ret = SLocation (location.x+si+viewPort.x, 
        location.y+viewPort.y + lineHeight * (ll+yoffset));
  }
  bool lr  = textData.isLR (line);
  int nloc = (lr) ? ret.x
     : 2 * location.x + (int)size.width - (int)ret.x - 2 * viewPort.x;
  return SLocation(nloc, ret.y);
}

/**
 * This is coming from the SWindowListener
 * @param c is the canvas to draw on.
 * @param x is the upper lect corner.
 * @param y is the upper lect corner.
 * @param width is the width of this event.
 * @param height is the height of this event.
 */
void
STextView::redraw (SWindow *w, int x, int y, 
        unsigned int width, unsigned int height)
{
  internalRedraw (w, x, y, width, height);
}

/**
 * This is coming from the SWindowListener
 * @param c is the canvas to draw on.
 * @param x is the upper lect corner.
 * @param y is the upper lect corner.
 * @param width is the width of this event.
 * @param height is the height of this event.
 */
void
STextView::redraw (SCanvas *c, int x, int y, 
        unsigned int width, unsigned int height)
{
  internalRedraw (c, x, y, width, height);
}

/**
 * This is coming from the SWindowListener
 * @param c is the canvas to draw on.
 * @param x is the upper lect corner.
 * @param y is the upper lect corner.
 * @param width is the width of this event.
 * @param height is the height of this event.
 */
void
STextView::internalRedraw (SCanvas *c, int x, int y, 
        unsigned int width, unsigned int height)
{
#if DEBUG_SPEED
  x = getLocation ().x;
  y = getLocation ().y;
  width = getSize().width;
  height = getSize().height;
  timerStart();
#endif
  SLocation lb (x, y);
  SLocation le (x+width, y+height);

  unsigned int line = getLineIndex (lb.y);

  bool islr  = textData.isLR (line);
  SLocation lstart = lb;
  if (!islr)
  {
    lstart.x = le.x;
  }

  SLocation lleft(location.x+viewPort.x, 
       location.y+viewPort.y + (int) lineHeight 
       * (int) sane_index (lineSpan, line));
  SLocation lright(location.x+viewPort.x+(int)size.width, lleft.y);

  unsigned int i;

  // expand +/- SD_PRE_EXPAND  lines by calling textData.size(i).
  unsigned int start = (line > SD_PRE_EXPAND) ? line-SD_PRE_EXPAND : 0;
  for (i=start; i<textData.size(); i++)
  {
    // dummy is never negative.
    textData.size(i);
    if (i>line+SD_PRE_EXPAND) break;
  }

  for (i=line; i<textData.size(); i++)
  { 
    textData.size(i);
    setVisible (i);
    islr  = textData.isLR (i);
    unsigned int cs = islr 
      ?  drawParagraph (c, islr, i, lleft, lb, le, false)
      :  drawParagraph (c, islr, i, lright, lb, le, false);
    lleft.y = lleft.y + lineHeight * cs;
    lright.y = lleft.y;
    if (lleft.y  > location.y + (int) size.height ) break;
    if (lleft.y  > y + (int)height) break;
  }

#if DEBUG_SPEED
  timerStop();
#endif
}

/**
 * Draw a whole line of glyphs.
 * @param c is the canvas to draw to 
 * @param islr is true if we draw from left to right.
 * @param line is the line index to draw.
 * @param l is the beginning upper corner location
 * @param lb is the beginning exposure
 * @param le is the end exposure
 * @param iswindow is true if we want to set the clipping area 
 *   this is only if you want to experiment - we dont want to do that.
 * @return the number of lines drawn.
 */
unsigned int 
STextView::drawParagraph (SCanvas* c, bool islr, unsigned int line, 
  const SLocation& l, const SLocation& lb, const SLocation& le, bool iswindow)
{
  SV_UINT br; 
  if (line < breaks.size()) br = breaks[line];
  unsigned int currExt = 0;
  SLocation lm = l;
  unsigned int ls = textData.size(line);
  unsigned int mycliph = 0;

  /**
   *  set clip to line so that we won't overflow... 
   */
  if (iswindow && clipw != 0 && cliph != 0)
  {
    int myclipy0 = (clipy > lm.y) ? clipy : lm.y;
    int myclipy1 = myclipy0 + lineHeight;
    if (myclipy1 > clipy + (int) cliph)
    {
      myclipy1 = clipy + (int) cliph;
    }
    mycliph = (myclipy1 > myclipy0) ? myclipy1-myclipy0 : 0;
   //fprintf (stderr, "clip=%d,%d wh=%u,%u\n", clipx, myclipy0, clipw, mycliph);
    if (mycliph)
    {
      ((SWindow*)c)->setClippingArea (clipx, myclipy0, clipw, mycliph);
    }
  }
  for (unsigned int i=0; i<ls; i++)
  {
     /* move the clipping area */
     if (br[currExt] == i)
     {
       // printer can have same extent
       while (br[currExt] == i)
       {
         currExt++;
         if (currExt >= br.size()) break;
       }

       lm.y = lineHeight * currExt + l.y;
       if (iswindow && clipw != 0 && cliph != 0)
       {
         int myclipy0 = (clipy > lm.y) ? clipy : lm.y;
         int myclipy1 = myclipy0 + lineHeight;
         if (myclipy1 > clipy + (int) cliph)
         {
           myclipy1 = clipy + (int) cliph;
         }
         mycliph = (myclipy1 > myclipy0) ? myclipy1-myclipy0 : 0;
         if (mycliph)
         {
           ((SWindow*)c)->setClippingArea (clipx, myclipy0, clipw, mycliph);
         }
       }
     }
     lm.x = islr 
        ? l.x + (int) posBefore[line][i] 
        : l.x-1-(int) posAfter[line][i];

     unsigned int e = posAfter[line][i] - posBefore[line][i];

     /* is it drawable ? */
     if (lm.x < le.x  && lb.x < lm.x + (int) e 
        && lm.y <  le.y && lb.y < lm.y + (int) lineHeight)
     {       
       if (!iswindow || mycliph) drawGlyph (c, lm, e, STextIndex (line,i));
     }
  }
  if (iswindow && clipw!=0 && cliph !=0)
  {
       ((SWindow*)c)->setClippingArea (clipx, clipy, clipw, cliph);
  }
  return currExt+1;
}

/**
 * Set Syntax Hilight Mode
 */
void
STextView::setSyntax (const SString& hlm)
{
  highlightMode = hlm;
  // these are kept for backward compatibility, hard coded.
  if (hlm == "simple" || hlm == "simple-dark" || hlm == "none")
  {
    syntax.setSyntax ("");
  }
  else
  {
    syntax.setSyntax (hlm);
  }
}

/**
 * Get Syntax Hilight Mode
 */
const SString&
STextView::getSyntax () const
{
  return highlightMode;
}

void
STextView::setSyntaxColors (const SSyntaxColors& attr)
{
  syntaxColors = attr;
}

const SSyntaxColors&
STextView::getSyntaxColors () const
{
  return syntaxColors;
}

/**
 * Set WordWrap  Mode
 * @param pbm is true if line break is on
 */
void
STextView::setWordWrap (bool lbm)
{
  isWordWrapOn = lbm;
  setReordered ();
}

/**
 * Some stuff displays differently if this is editable.
 */
void
STextView::setEditable (bool editable)
{
  isEditable = editable;
  setReordered ();
}

/**
 * Get WordWrap Mode
 * @return true if line break is on.
 */
bool
STextView::getWordWrap () const
{
  return isWordWrapOn;
}

/**
 * Syntax Highlighting  system.
 * Added by Maarten van Gompel <proycon@anaproy.homeip.net>
 * Note:  this is a dumb system and merely colors single characters.
 * For error highlighting, set forground to NONE and error to true.
 */
void
STextView::syntaxHighlight(STextIndex index, SPen* pen, bool *isError)
{
  *isError = false;
  if (highlightMode == "simple") 
  {
    const SGlyph& g = textData.glyphAt (index);
    if (g.isLetter ())
    {
    }
    else if (g.isNumber())
    {
      pen->setForeground(SColor("orange"));
    }
    else // not a letter, nor a number
    {
      pen->setForeground(SColor("CornflowerBlue"));
    }
    if (getLigatureScriptCode (g.getChar()) == SD_AS_LITERAL) *isError = true; 
  }
  else if (highlightMode == "simple-dark") 
  {
    const SGlyph& g = textData.glyphAt (index);
    if (g.isLetter ())
    {
    }
    else if (g.isNumber())
    {
      pen->setForeground(SColor("orange4"));
    }
    else // not a letter, nor a number
    {
      pen->setForeground(SColor("DeepSkyBlue4"));
    }
    if (getLigatureScriptCode (g.getChar()) == SD_AS_LITERAL) *isError = true; 
  }
  else if (highlightMode != "none" && highlightMode != "") 
  {
    
    SSyntax::SS_Tag tag = syntax.getTagByTDI (index);
    const SGlyph& g = textData.glyphAt (index);
    if (getLigatureScriptCode (g.getChar()) == SD_AS_LITERAL)
    {
      *isError = true; 
      tag = SSyntax::SD_CONTROL;
      SColor c = syntaxColors.colors[(unsigned int) tag];
      pen->setForeground(c);
    }
    // SD_ERROR and SD_NONE is preserving old color
    else if (tag == SSyntax::SD_ERROR)
    {
      // SColor c = syntaxColors.colors[(unsigned int) SSyntax::SD_NONE];
      // pen->setForeground(c);
      *isError = true;
    }
    else if (tag == SSyntax::SD_NONE)
    {
      // preserve original color
    }
    else
    {
      SColor c = syntaxColors.colors[(unsigned int) tag];
      pen->setForeground(c);
    }
  }
  return;
}

/**
 * Draw one signle glyph on the screen.
 * @param c is the canvas to draw to
 * @param l is the location of the glyph.
 * @return the length of the text drawn
 */
void
STextView::drawGlyph (SCanvas* c, 
  SLocation& l, unsigned int ext, STextIndex index)
{
  const SGlyph& g = textData.glyphAt (index);
  
  SS_Matrix2D dm;
  dm.y1 = -dm.y1; /* updown */

  
  dm.translate (0, font.ascent ());
  dm.translate ((double)l.x, (double)l.y);
  SPen p (lrpen);
  if (!g.isLR())
  {
    p = rlpen;
  }
  unsigned int explevel = g.getExplicitLevel();
  bool isError = false;
  if (g.selected)
  {
     //SColor fg = p.getForeground();
     //SColor bg = p.getBackground();
     SColor fg = SColor("DeepSkyBlue4");
     SColor bg = SColor("white");
     p.setForeground (bg);
     p.setBackground (fg);
     c->bitfill (fg, l.x, l.y, ext, lineHeight);
  }
   /* fade background according to embed level */
  else if (isEditable && explevel!=0) 
  {
     SColor bg = p.getBackground();
     if (explevel > 5) explevel = 5; /* 5 shades max*/
     /* This funny linear curve is the result of experiments */
     double alpha = 1.0/2.0 +  (1.0/2.0) * 0.9 * ((double)explevel)/5.0;
     /* we fade gray in with an alpha */
     double cg = 0.5;
     SColor grey(cg,cg,cg, alpha);
     bg.blend (grey);
     p.setBackground (bg);
     c->bitfill (bg, l.x, l.y, ext, lineHeight);
  }
  if (!g.selected) 
  { 
   /* 
    * Syntax highlighting, an addition 
    * by Maarten van Gompel <proycon@anaproy.homeip.net>
    */
    syntaxHighlight(index, &p, &isError); /* change pen color if necessary */
  }

  if (!lineend && g.isEOP()) return;
  SS_UCS4 fc = g.getFirstChar();
  /* I would just check for SD_CC_Mn also */
  if (!isEditable && (g.isEOL() || fc == SD_CD_LRM || fc == SD_CD_RLM
      || fc == SD_CD_ZWNJ || fc == SD_CD_ZWJ))
  {
    return;
  }
  /* Zero width space */
  if (fc == SD_CD_ZWSP) return;

  if (!g.isTab()) font.draw (c, p, dm, g);

  if (g.underlined)
  {
    unsigned int w = ext;
    unsigned int h = lineHeight/24+1;
    unsigned int base =  (lineAscent + h >= lineHeight) ?
         lineHeight -1 : lineAscent + h;
    /* construct a square */
    // changed in 2.8.2
    //c->bitfill (underlineColor, l.x, l.y + (int) base - h, w, h);
    c->bitfill (underlineColor, l.x, l.y + (int) base, w, h);
  }
  else if(isError)
  {
    SColor errColor = syntaxColors.colors[(unsigned int) SSyntax::SD_ERROR];
    unsigned int w = ext;
    unsigned int h = 3; // height occupies 3 pixels
    unsigned int base =  (lineAscent + h >= lineHeight) ?
         lineHeight - 1 : lineAscent + h;
    int screenBase = l.y + (int) base;
    SBinVector<int> x;
    SBinVector<int> y;
    for (unsigned i=0; i<w; i++)
    {
      x.append (l.x + i);
      int state = ((l.x+i+clipx) % 6);
      switch (state)
      {
      case 0: y.append (screenBase); break;
      case 1: y.append (screenBase-1); break;
      case 2: y.append (screenBase-1); break;
      case 3: y.append (screenBase); break;
      case 4: y.append (screenBase+1); break;
      case 5: y.append (screenBase+1); break;
      default: y.append (screenBase);
      }
    }
    if (x.size() > 0)
    {
      c->bitpoints (errColor, x.array(), y.array(), x.size());
    }
  }
}

/**
 * This can be called by the STextData and SSyntax.
 */
void
STextView::textChanged (void* src, const STextDataEvent& unparsedEvent)
{
  if (src == &syntax && unparsedEvent.attribute)
  {
    STextIndex tb = unparsedEvent.start;
    STextIndex te = unparsedEvent.remaining;
    // convert te back to text data coords
    te.line = te.line >= textData.size() ? 0 : textData.size() - te.line;
    // Filter out visible range
    unsigned int firstVisible = getLineIndex (0);
    int height = (int) getSize().height;
    int width = (int) getSize().width;
    unsigned int lastVisible = getLineIndex (height);
    // filter out nont visible portion
    if (te.line < firstVisible) return;
    if (tb.line > lastVisible+1) return;

    if (tb.line < firstVisible) tb.line = firstVisible;
    tb.index = 0;
    // non-inclusive
    if (te.line > lastVisible) te.line = lastVisible+1;
    te.index = 0;
    // calculate the screen index, and do a redraw 
    if (te.line > textData.size()) te.line = textData.size();
    if (tb.line > textData.size()) tb.line = textData.size();
    int bcoord = (int) lineHeight * sane_index (lineSpan, tb.line); 
    int ecoord = (int) lineHeight * sane_index (lineSpan, te.line); 
    // as we scroll down viewPort.y becomes negative.
    // y coord of top of starting line.
    bcoord += (location.y + viewPort.y) - 1;
    // y coord of top of ending line.
    ecoord += (location.y + viewPort.y) - 1;
    SWindow* w = getWindow ();
    if (w && ecoord > bcoord && bcoord < height && ecoord > 0)
    {
      /* request a redraw and clear the whole area + overdraw */
      w->redraw (true, 0, bcoord, width, ecoord-bcoord + 2);
//fprintf (stderr, "redraw 0,%d %u,%u\n", bcoord, getSize().width, ecoord-bcoord);
//fprintf (stderr, "tb=%u te=%u\n", tb.line, te.line);
    }
  }
  else
  {
    textChangedInternal (src, unparsedEvent);
  }
}

void
STextView::textChangedInternal (void* src, const STextDataEvent& event)
{
  /* The whole text has been cleared */
  if (textData.size()==0)
  {
     wrapAndPosition(); 
     SWindow* w = getWindow ();
     if (w)
     {
       /* request a redraw and clear the whole area */
       w->redraw (true, location.x, location.y, size.width, size.height);
     }
     return;
  }
  /* overdraw */
  int odw = (int) lineHeight / 3 + 1; 
  STextIndex tb = textData.getMinTextIndex (event);
  STextIndex te = textData.getMaxTextIndex (event);


  unsigned int oldsize = lineSpan.size();
  unsigned int oldspan = sane_index (lineSpan, oldsize);
  bool oldlr =  textData.isLR(tb.line);

  SV_UINT oldbreaks;

  if (tb.line == te.line && tb.line < oldsize && tb.line < breaks.size())
  {
    /* This is still the old breaks */
    oldbreaks = breaks[tb.line];
  }

  /* change in text contents */
  SV_UINT mapBefore = textData.getLogicalMap(tb.line);
  SV_UINT mapAfter = mapBefore;
  if (!event.attribute)
  {
    /* For efficiency, multiline guys will make it only partial */
    if (multiline)
    {
      /* was recalc */
      wrapAndPosition (tb.line, te.line+1, 
         (int)textData.size() - (int) lineSpan.size());
    }
    else
    {
      wrapAndPosition ();
    }
    mapAfter = textData.getLogicalMap(tb.line);
    /* find the highest and visual index */
  }

  SWindow* w = getWindow();
  if (w == 0)
  {
    /* This is a strange place to return - but we needed to rebuild indeces */
    return;
  }

  unsigned int newsize = lineSpan.size();

  unsigned int newspan = sane_index (lineSpan, newsize);
  bool samebreak = false;

  bool newlr =  textData.isLR(tb.line);
  bool drawwholeline = (newlr != oldlr && tb.line == te.line);
  if (tb.line == te.line && tb.line < newsize && tb.line <breaks.size())
  {
    SV_UINT o = oldbreaks;
    SV_UINT n = breaks[tb.line]; 
    samebreak = (o.size() == n.size());
    if (samebreak)
    {
      /* of course it break at the end */
      for (unsigned int i=0; i+1<n.size(); i++)
      {
        /* break changed or it was before the text change */
        /* for attribute break can not change */
        if (!event.attribute && (n[i] != o[i] || tb.index <= n[i])) 
        {
          samebreak = false;
          break;
        }
        /* break is between begin and end */
        if (n[i] >= tb.index && n[i] <= te.index)
        {
          drawwholeline = true;
        }
      }
    }
  }
  if (tb.line == te.line && drawwholeline)
  {
    tb.index = 0;
    te.index = mapAfter.size();
  }

  /* adjust tb te */
  if (tb.line == te.line && !drawwholeline && samebreak)
  {
    unsigned int i;
    /* find out lowest common stuff in map */
    unsigned int min = mapAfter.size() < mapBefore.size()
      ? mapAfter.size() : mapBefore.size();

    /* make logical to visual maps */
    SS_UINT * mapa = new SS_UINT[mapAfter.size()+1];
    CHECK_NEW (mapa);
    for (i=0; i<mapAfter.size(); i++) mapa[i] = mapAfter[i];

    SS_UINT * mapb = new SS_UINT[mapBefore.size()+1];
    CHECK_NEW (mapb);
    for (i=0; i<mapBefore.size(); i++) mapb[i] = mapBefore[i];

    unsigned int lowestvis = min;
    for (i=0; i<min; i++)
    {
      if (mapb[i] != mapa[i])
      {
        tb.index = mapa[i];
        lowestvis = i;
        break;
      }
      /* at least from here it changed yeah... */
      if (mapa[i] == tb.index)
      {
        lowestvis = i;
        break;
      }
    }
    if (i==0)
    {
      if (mapAfter.size()> 0)
      {
        tb.index = mapAfter[0];
      }
      else
      {
        tb.index = 0;
      }
     lowestvis=0;
    }
    /* find out if there is something between zero and lowes vis */
    for (i=0; i<lowestvis; i++)
    {
      /* we can have one glyph difference  */
      if (mapa[i]+1 >= tb.index)
      {
        tb.index = mapa[i];
        lowestvis = i;
        break;
      }
    }
    // if still between lowest and end there is a lower index, take 0.
    for (i=lowestvis; i<mapAfter.size(); i++)
    {
      if (mapa[i] <= tb.index)
      {
        /* find the smallest */
        unsigned int smallest = mapa[i];
        while (++i < mapAfter.size())
        {
           if (mapa[i] < smallest) smallest = mapa[i];
        }
        if (smallest > 0) smallest--;
        tb.index = smallest; 
        break;
      }
    }
   
    /* for attribute te.index is also used and mapafter = mapbefore */
    if (event.attribute && te.index < mapAfter.size())
    {
      unsigned int vis = mapAfter[te.index];
      unsigned int max = mapAfter.size();
      for (i=mapAfter.size(); i>vis; i--)
      {
        if (mapa[i-1] < te.index)
        {
          te.index= max;
          break;
        }
        max = mapa[i-1];
      }
    }
    else
    {
      te.index = mapAfter.size();
    }
    delete [] mapa;
    delete [] mapb;
  }

  SLocation lb = getTextLocation (tb);
  SLocation le = getTextLocation (te);

  /*
   * Get smallest and biggest. 
   */
  if (tb.line == te.line && samebreak && le.y == lb.y)
  {
    if (le.x < lb.x)
    {
       int tmp = lb.x; lb.x = le.x; le.x = tmp;
    }
    for (unsigned int i=tb.index; i<=te.index; i++)
    {
      SLocation l = getTextLocation (STextIndex(tb.line, i));
      if (l.x < lb.x) lb = l;
      if (l.x > le.x) le = l;
      l = getTextLocation (STextIndex(tb.line, i), false);
      if (l.x < lb.x) lb = l;
      if (l.x > le.x) le = l;
    }
  }

//fprintf (stderr, "lb.x =%d, le.x=%u\n", lb.x, le.x);
  /* make sure we are inside the window */
  if (lb.y + (int)lineHeight < 0) lb.y = -(int)lineHeight;
  if (le.y > location.y + (int)size.height) le.y =  size.height + location.y;

  /* Text content did not change, only the attribute */

  int starty = (lb.y > 5) ? lb.y - odw: 0;
  unsigned int lheight = lineHeight + 2*odw;

  if (event.attribute)
  {
    /* single */
    if (lb.y == le.y && samebreak)
    {
      /* we add 1 to make sure it is non-null positive */
      w->redraw (true, lb.x-odw, starty, (unsigned int) (le.x-lb.x)+2*odw, lheight);
    }
    else // multiline - redraw whole thing.
    {
      le = getTextLocation (STextIndex (te.line, textData.size(te.line)));
      if (lb.y < le.y) /* always */
      {
        w->redraw (true, location.x, starty, 
                size.width, lheight + (unsigned int)(le.y-lb.y));
      }
      else /* I dont know what happened - redraw */
      {
        w->redraw (true, location.x, location.y, size.width, size.height);
      }
    }
    return;
  }

  /* Change is inside a single paragraph */
  if (tb.line == te.line && oldsize == newsize && oldspan == newspan)
  {
    /* The whole change is on the same line (breaks did not change)  */
    if (lb.y == le.y && samebreak)
    {
      bool lrline  = textData.isLR (tb.line);
      int wid = 0;
      if (lrline)
      {
         //lb.x = lb.x;
         wid = (int) size.width; /* till end of line */
      }
      else
      {
         lb.x = 0;
         wid = le.x + location.x;
      }
      /* redraw till end of line */
      w->redraw (true, lb.x-odw, starty, (unsigned int) wid + 2*odw, lheight);
    }
    else /* This is a multi-line paragraph change. redraw till end */
    {
      le = getTextLocation (STextIndex (te.line, textData.size(te.line)));
      if (le.y > lb.y) /* always */
      {
        w->redraw (true, location.x, starty, 
            size.width, lheight + (unsigned int)(le.y-lb.y));
      }
      else /* I dont know what happened - redraw */
      {
        w->redraw (true, location.x, location.y, size.width, size.height);
      }
      
    }
    return;
  }
  /* Multi-paragraph change. Is it visible? */
  if (starty < location.y + (int) size.height)
  {
    w->redraw (true, location.x, starty, 
        size.width, location.y + (int)size.height - starty);
  }
}

/**
 * Makr lines so that they will recalculate
 */
void 
STextView::setReordered()
{
  /* HACK FOR LABELS - they neeed to know their exact size */
  if (!isEditable)
  {
    for (unsigned int i=0; i<textData.size(); i++)
    {
      textData.setVisible(i);
      textData.setReordered (i);
    }
    wrapAndPosition();
    return;
  }
  lineSpan.clear ();
  unsigned int sum = 0;
  for (unsigned int i=0; i<textData.size(); i++)
  {
    sum++;
    textData.setReordered (i);
    lineSpan.append (sum);
  }
}
/**
 * Walk through the text and remake the linespan.
 * Recalculate the preferred sizes.
 */
void
STextView::wrapAndPosition ()
{
  lineHeight = (unsigned int ) (font.ascent() + font.descent() + font.gap());
  lineAscent = (unsigned int) font.ascent();
  breaks.clear ();
  posAfter.clear ();
  posBefore.clear ();
  lineSpan.clear ();

  SH_UINT hint;
  unsigned int sum = 0;
  preferredSize.width = 0;
  for (unsigned int i=0; i<textData.size(); i++)
  {
    sum += wrapAndPosition (i, &hint);
    lineSpan.append (sum);
  }
  preferredSize.height = (textData.size()==0) ? lineHeight
      : textData.size() *  lineHeight;
}

/**
 * recalculate partially. This is used for multi-line stuff
 * to make it more efficient.
 * @param from is the starting index.
 * @param until is the index before last
 * @paran addcount show how many lines were added. can be negative (removed)
 */
void
STextView::wrapAndPosition (unsigned int from, unsigned int until, int addcount)
{
  //unsigned int longestline = 1;
  unsigned int sum = sane_index (lineSpan, from);
  int mycount=0;
  unsigned int i=0;
  SH_UINT cache;
  for (i=from; i<textData.size() && i<until; i++)
  {
    sum += wrapAndPosition (i, &cache);
    lineSpan.insert (i, sum);
    mycount++;
  }

  unsigned int removesum = sum;
  if (i<breaks.size())
  {
    for (int j=0; j<mycount-addcount; j++)
    {
      /* yes i ! */
      removesum = lineSpan[i];
      lineSpan.remove (i);
      breaks.remove (i);
      posBefore.remove (i);
      posAfter.remove (i);
    }
  }
  /* recalibrate the whole linespan array */
  if (removesum != sum)
  {
    while (i < textData.size())
    {
      unsigned int s = lineSpan[i];
      if (removesum > sum)
      {
         s -= removesum-sum;
      }
      else
      {
         s += sum-removesum;
      }
      lineSpan.replace (i, s);
      i++;
    }
  }
  preferredSize.height = (textData.size()==0) ? lineHeight
      : textData.size() *  lineHeight;
}

/**
 * Caclulate the extent as one line.
 * It inserts an element at line in positions, and breaks.
 * The positions array will have the positions of the end
 * of the glyph, ragrdless of paragraph embedding, in LR order. 
 * @param line is the line to calculate.
 * @return the linesspan
 */
unsigned int
STextView::wrapAndPosition (unsigned int line, SH_UINT* cache)
{
  /* first line is always visible - multiline */

  if (!textData.isVisible(line))
  {
    SV_UINT empty;
    posAfter.insert(line, empty);
    posBefore.insert(line, empty);
    breaks.insert(line, empty);
    /* make span 1 */
    return 1;
  }

  /* +1 is only because of zero sized arrays */
  SS_UCS4* logical = new SS_UCS4[textData.size(line)+1];
  CHECK_NEW(logical);
  SS_UCS4* logicalBefore = new SS_UCS4[textData.size(line)+1];
  CHECK_NEW(logicalBefore);

  SS_UCS4* visual = new SS_UCS4[textData.size(line)+1];
  CHECK_NEW(visual);

  unsigned int ae=0;
  unsigned int ce=0;
  unsigned int le=0;
  unsigned int i;
  SV_UINT b;
  bool wrapNext = false;
  bool wrapPage = false;
  unsigned int lastbreak = 0;
  /* go through the text in logical order */
  for (i=0; i<textData.size(line); i++)
  {
    const SGlyph& g = textData.glyphAt (STextIndex (line, i));
    ce = cache->get (g.charKey());
    if (ce ==0)
    {
      ce = (unsigned int) (0.5 + font.width (g));
      SS_UCS4 fc = g.getFirstChar();
      if (!isEditable && (g.isEOL() || fc == SD_CD_LRM || fc == SD_CD_RLM
         || fc == SD_CD_ZWNJ || fc == SD_CD_ZWJ))
      {
        ce = 1;
      }
      else if (fc == SD_CD_ZWSP)
      {
        ce = 1;
      }
      else  if (g.isTab())
      {
        int tabsize = (int)(4.0 * font.getSize());
        if (tabsize < 1) tabsize = 1;
        ce = tabsize - (le % (unsigned int)tabsize);
        if (multiline && (le + ce)> size.width && le > 0)
        {
           /* force line break. */
           ce = (int)(4.0 * font.getSize());
        }
      }
      /* Shaped glyphs width and tab may change. */
      if (g.getShapeArray()==0 && !g.isTab())
      {
        cache->put (g.charKey(), ce);
      }
    }
    le  += ce;
    ae += ce;
    logical[i] = ce;

    if (multiline && le > size.width && i > 0 && !wrapNext)
    {
       if (g.isTab())
       {
         /* nothing to do. we break here */
       }
       /* we might want to wrap earlier */
       else if (isWordWrapOn && !textData.canWrap (STextIndex (line, i-1)))
       {
         unsigned int oldae = ae;
         unsigned int oldi = i;
         while (i>lastbreak && !textData.canWrap (STextIndex (line, i-1)))
         {
           ae -= logical[i];
           i--;
         }
         /* emergency break */
         if (i==lastbreak)
         {
           ae = oldae;
           i = oldi;
         }
       }
       le = logical[i];
       b.append (i);
       lastbreak = i;
    }
    else if (wrapNext)
    {
      le = logical[i];
      lastbreak = i;
      if (wrapPage)
      {
        if (i == 1 && line == 0) // first line, first char is a FF
        {
          b.append (i);
        }
        else if (i > 1) // we have something on the line
        {
          //const SGlyph& gp = textData.glyphAt (STextIndex (line, i-2));
          // the one before FF is an FF too
          b.append (i);
        }
        unsigned int currSpan =  (line == 0 || lineSpan.size() < line-1)
          ? 0 : lineSpan[line-1];

        // how many more we need to add to reach top?
        unsigned int lh = (lineHeight == 0) ? 1 : lineHeight;
        unsigned int linesPerPage = printerPageSize / lh;
        while (((currSpan + b.size()) % linesPerPage) != 0)
        {
           b.append (i);
        }
        
      }
      else
      {
        b.append (i);
      }
    }
    wrapNext = (multiline && g.isEOL() && !g.isEOP());
    if (printerPageSize != 0 && wrapNext)
    {
      wrapPage = g.isFF();
    }
  }
  /* now b contains the logical positions where the glyph should start at 0 */
  b.append (textData.size(line));

  /* break the text into lines */
  textData.setLineBreaks(line, b);

  if (preferredSize.width < ae) preferredSize.width = ae;

  /* make a visual map */
  for (i=0; i<textData.size(line); i++)
  {
    visual[i] = textData.toLogical (line, i);
  }
  le = 0;
  /* add up visual */
  unsigned int nextbreak = 0;

  /* go through in visual order */
  for (i=0; i<textData.size(line); i++)
  {
    /* we use visual break here */
    while (nextbreak < b.size() && i == b[nextbreak])
    {
      nextbreak++;
      le = 0;
    }
    /* 
     *  save space - make confusion :).
     *  logical[visual[i]] will not be used 
     *  any more here so we re-use it
     */
    logicalBefore[visual[i]] = le;
    le += logical[visual[i]];
    logical[visual[i]] = le;
  }
  SV_UINT pb;
  SV_UINT pa;
  for (i=0; i<textData.size(line); i++)
  {
     pb.append (logicalBefore[i]);
     pa.append (logical[i]);
  }

  delete logicalBefore;
  delete logical;
  delete visual;

  breaks.insert(line, b);
  posAfter.insert(line, pa);
  posBefore.insert(line, pb);
  /* updating lineSpan is in the calling routine*/

  return b.size();
}

/**
 * Set the background.
 * @param bg is the new background
 */
void
STextView::setBackground (const SColor& bg)
{
  lrpen.setBackground (bg);
  rlpen.setBackground (bg);
}

/**
 * Set the foreground.
 * @param fg is the new foreground
 */
void
STextView::setForeground (const SColor& rlfg, const SColor& lrfg)
{
  lrpen.setForeground (rlfg);
  rlpen.setForeground (lrfg);
}
const SColor&
STextView::getBackground ()
{
  return lrpen.getBackground();
}

const SColor&
STextView::getForeground (bool lr)
{
  return (lr) ? lrpen.getForeground() : rlpen.getForeground();
}

/**
 * If show <- newline characters
 * @param _lineend is true if lineend is shown.
 */
void
STextView::setLineEndMark (bool _lineend)
{
  lineend = _lineend;
  setReordered();
  SWindow* w = getWindow();
  if (w == 0)
  {
    return;
  }
  if (!w->isVisible()) return;
  w->redraw (true, location.x, location.y, size.width, size.height);
}

/**
 * Is new line shown?
 * @return true if newline characters are shown.
 */
bool
STextView::getLineEndMark () const
{
  return lineend;
}

/**
 * calculate the height of the document.
 */
unsigned int 
STextView::getDocumentHeight() const
{
  if (textData.size() == 0)
  {
    return lineHeight;
  }
  unsigned int fheight = lineSpan[textData.size()-1];
  if (textData.isProperLine (textData.size()-1))
  {
    fheight += 1;
  }
  return (fheight * lineHeight);
}

/**
 * return the 'sane index'.
 * That is, at index 0 it should be 0
 * at index at array->size() is should be the last element.
 */
static unsigned int
sane_index (const SV_UINT& array, unsigned int index)
{
  if (index == 0 || array.size() < index) return  0;
  return array[index-1];
}

void
STextView::setUnderlineColor (const SColor& c)
{
  underlineColor = c;
}

/**
 * Mark this visible 
 */
void
STextView::setVisible (unsigned int line)
{
  if (!textData.isVisible (line))
  {
    textData.setVisible(line);
    wrapAndPosition (line, line+1, 0);
  }
  else if (textData.isReordered(line))
  {
    wrapAndPosition (line, line+1, 0);
  }
}

/**
 * return the cursor index that is left (screen-wise) of
 * ci.
 * @checkembed is true  check for embedding boundary
 */
SCursorIndex
STextView::leftOf (const SCursorIndex& ci)
{
  if (ci.textIndex.line >= textData.size())
  {
    return SCursorIndex(ci.textIndex.line, ci.textIndex.index);
  }
  setVisible (ci.textIndex.line);
  SCursorIndex cn = moveCursor (ci, false);
  return SCursorIndex (cn);
}

SCursorIndex
STextView::rightOf (const SCursorIndex& ci)
{
  if (ci.textIndex.line >= textData.size())
  {
    return SCursorIndex(ci.textIndex.line, ci.textIndex.index);
  }
  setVisible (ci.textIndex.line);
  SCursorIndex cn = moveCursor (ci, true);
  return SCursorIndex (cn);
}

/**
 * Move the cursor up or down one slot visuallly.
 * You have to expand the paragrapgh before this call.
 * @param ci is the input index.
 * @praram isup is true if we walk right visuallly.
 * @return 1 index up or down.
 */
SCursorIndex
STextView::moveCursor (const SCursorIndex& ci, bool isup)
{
  SV_UINT map = textData.getLogicalMap(ci.textIndex.line);
  if (map.size()==0) return SCursorIndex(ci.textIndex.line,0);
  if (textData.isProperLine (ci.textIndex.line))
  {
    map.truncate (map.size()-1);
  }
  if (map.size()==0) return SCursorIndex(ci.textIndex.line,0);
  /* we need to find the current index in the map. */
  int current = map.size();
  for (unsigned int i=0; i<map.size(); i++)
  {
    if (map[i] == ci.textIndex.index) 
    {
      current = i;
    }
  }
  /* normalize to our visual index. */
  bool llr = textData.isLR(ci.textIndex.line);

  /* for lr before is after and vice versa */
  bool clr = textData.isLR(ci.textIndex);

  /* it is easier to visualize this in visual order */
  bool cbefore = clr ? ci.before : !ci.before;
  bool resbefore = false;

  SEmbedState eold = textData.getEmbedState(ci.textIndex);
  SEmbedState enew;
  bool samembed = true;
  if (isup)
  {
    if (cbefore) // set it to after
    {
      resbefore = false;
    }
    else /* increment visual index and increment one */
    {
      resbefore = false;
      //current = current+1;
      current = llr ? current+1 : current-1;
      /* check the mbedding state of the next */
      unsigned int ei = (current < 0) ? map.size()+1 
        : map[(unsigned int)current];
      enew = textData.getEmbedState(STextIndex (ci.textIndex.line, ei));
      samembed = (enew==eold);
    }
  }
  else /* !isup */
  {
    /* set it to after */
    if (!cbefore)
    {
      resbefore = true;
    }
    /* set it to next */
    else
    {
      resbefore = true;
      /* the map is not visual */
      current = llr ? current-1 : current+1;
      /* check the mbedding state of the next */
      unsigned int ei = (current < 0) ? map.size()+1 
        : map[(unsigned int)current];
      enew = textData.getEmbedState(STextIndex (ci.textIndex.line, ei));
      samembed = (enew==eold);
      //current = current-1;
    }
  }
  /* check bounds */
  unsigned int resindex = 0;
  if (current < 0)
  {
    current = 0;
    /* we need to move to the rightmost */
    resbefore  = llr;
    SCursorIndex rc(ci.textIndex.line, map[(unsigned int)current], resbefore);
    bool islr = textData.isLR (rc.textIndex);
    if (!islr) rc.before = !rc.before;
    return SCursorIndex (rc);
  }
  if (current >= (int)map.size())
  {
    current = (int)map.size();
    resbefore  = true;
    return SCursorIndex (ci.textIndex.line, (unsigned int) current, resbefore);
  }
  resindex = map[(unsigned int) current];
  /* check changed index */
  SCursorIndex res (ci.textIndex.line, resindex, resbefore); 
  bool nlr = textData.isLR (res.textIndex);

  /* rl before is logical after switch */
  /* nlr already switched */
  /* back to logical order */
  if (!nlr) res.before = !res.before;

  /* embed changed  */
  if (!samembed)
  {
    res.before = !res.before;
    return SCursorIndex (res);
  }
  /* direction changed */
  if (clr != nlr)
  {
    res.before = !res.before;
  }
  return SCursorIndex (res);
}
