"Fossies" - the Fresh Open Source Software Archive

Member "scribus-1.5.6.1/scribus/text/storytext.cpp" (14 Nov 2020, 69514 Bytes) of package /linux/misc/scribus-1.5.6.1.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "storytext.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.5.6_vs_1.5.6.1.

    1 /*
    2  For general Scribus (>=1.3.2) copyright and licensing information please refer
    3  to the COPYING file provided with the program. Following this notice may exist
    4  a copyright and/or license notice that predates the release of Scribus 1.3.2
    5  for which a new license (GPL+exception) is in place.
    6  */
    7 /***************************************************************************
    8 pageitem.cpp  -  description
    9 -------------------
   10     begin                : Sat Apr 7 2001
   11     copyright            : (C) 2001 by Franz Schmid
   12     email                : Franz.Schmid@altmuehlnet.de
   13 ***************************************************************************/
   14 
   15 /***************************************************************************
   16 *                                                                         *
   17 *   This program is free software; you can redistribute it and/or modify  *
   18 *   it under the terms of the GNU General Public License as published by  *
   19 *   the Free Software Foundation; either version 2 of the License, or     *
   20 *   (at your option) any later version.                                   *
   21 *                                                                         *
   22 ***************************************************************************/
   23 
   24 //#include <QDebug>
   25 #include <unicode/brkiter.h>
   26 
   27 //FIXME: this include must go to sctextstruct.h !
   28 #include <QList>
   29 #include <cassert>  //added to make Fedora-5 happy
   30 #include "fpoint.h"
   31 #include "notesstyles.h"
   32 #include "scfonts.h"
   33 #include "scribusdoc.h"
   34 #include "sctext_shared.h"
   35 #include "selection.h"
   36 #include "storytext.h"
   37 //
   38 #include "util.h"
   39 #include "resourcecollection.h"
   40 #include "desaxe/saxiohelper.h"
   41 #include "desaxe/digester.h"
   42 #include "desaxe/simple_actions.h"
   43 #include "shapedtextcache.h"
   44 
   45 using namespace icu;
   46 
   47 StoryText::StoryText(ScribusDoc * doc_) : m_doc(doc_)
   48 {
   49     if (doc_)
   50     {
   51         d = new ScText_Shared(&doc_->paragraphStyles());
   52         m_doc->paragraphStyles().connect(this, SLOT(invalidateAll()));
   53         m_doc->charStyles().connect(this, SLOT(invalidateAll()));
   54     }
   55     else
   56         d = new ScText_Shared(nullptr);
   57 
   58     d->selFirst = 0;
   59     d->selLast = -1;
   60     
   61     m_shapedTextCache = new ShapedTextCache();
   62     
   63     d->len = 0;
   64     invalidateAll();
   65 }
   66 
   67 StoryText::StoryText() : m_doc(nullptr)
   68 {
   69     d = new ScText_Shared(nullptr);
   70 
   71     d->selFirst = 0;
   72     d->selLast = -1;
   73     m_shapedTextCache = nullptr;
   74 }
   75 
   76 StoryText::StoryText(const StoryText & other) : m_doc(other.m_doc)
   77 {
   78     d = other.d;
   79     d->refs++;
   80     
   81     if (m_doc)
   82     {
   83         m_doc->paragraphStyles().connect(this, SLOT(invalidateAll()));
   84         m_doc->charStyles().connect(this, SLOT(invalidateAll()));
   85     }
   86     
   87     d->selFirst = 0;
   88     d->selLast = -1;
   89     m_shapedTextCache = nullptr;
   90 
   91     invalidateLayout();
   92 }
   93 
   94 StoryText::~StoryText()
   95 {
   96 /* Code below is not needed imho, as all connections will be disconnected automatically
   97     by the QObject destructor. At least removing that code fixes a crash when closing
   98     documents */
   99 /*  if (doc)
  100     {
  101         doc->paragraphStyles().disconnect(this, SLOT(invalidateAll()));
  102         doc->charStyles().disconnect(this, SLOT(invalidateAll()));
  103     } */
  104     d->refs--;
  105     if (d->refs == 0) {
  106         d->clear();
  107         d->len = 0;
  108         delete d;
  109     }   
  110 }
  111 
  112 bool StoryText::hasBulletOrNum() const
  113 {
  114     if (!d)
  115         return false;
  116 
  117     bool lastWasPARSEP = true;
  118     for (int i = 0; i < length(); ++i)
  119     {
  120         lastWasPARSEP = (d->at(i)->ch == SpecialChars::PARSEP);
  121         if (!lastWasPARSEP)
  122             continue;
  123         const ParagraphStyle& paraStyle = paragraphStyle(i);
  124         if (paraStyle.hasBullet() || paraStyle.hasNum())
  125             return true;
  126     }
  127 
  128     const ParagraphStyle& trailingStyle = d->trailingStyle;
  129     return (trailingStyle.hasBullet() || trailingStyle.hasNum());
  130 }
  131 
  132 bool StoryText::hasTextMarks() const
  133 {
  134     if (d)
  135         return (d->marksCount > 0);
  136     return false;
  137 }
  138 
  139 bool StoryText::marksCountChanged() const
  140 {
  141     if (d)
  142         return d->marksCountChanged;
  143     return false;
  144 }
  145 
  146 void StoryText::resetMarksCountChanged()
  147 {
  148     d->marksCountChanged = false;
  149 }
  150 
  151 void StoryText::setDoc(ScribusDoc *docin)
  152 {
  153     if (m_doc)
  154     {
  155         m_doc->paragraphStyles().disconnect(this, SLOT(invalidateAll()));
  156         m_doc->charStyles().disconnect(this, SLOT(invalidateAll()));
  157     }
  158 
  159     m_doc = docin;
  160 
  161     if (m_doc)
  162     {
  163         m_doc->paragraphStyles().connect(this, SLOT(invalidateAll()));
  164         m_doc->charStyles().connect(this, SLOT(invalidateAll()));
  165     }
  166 }
  167 
  168 StoryText StoryText::copy() const
  169 {
  170     StoryText result(m_doc);
  171     *(result.d) = *d;
  172     return result;
  173 }
  174 
  175 StoryText& StoryText::operator= (const StoryText & other)
  176 {
  177     other.d->refs++;
  178     
  179     d->refs--;
  180     if (d->refs == 0)
  181     {
  182         clear();
  183         delete d;
  184     }
  185     
  186     if (m_doc)
  187     {
  188         m_doc->paragraphStyles().disconnect(this, SLOT(invalidateAll()));
  189         m_doc->charStyles().disconnect(this, SLOT(invalidateAll()));
  190     }
  191     
  192     m_doc = other.m_doc; 
  193     d = other.d;
  194     
  195     if (m_doc)
  196     {
  197         m_doc->paragraphStyles().connect(this, SLOT(invalidateAll()));
  198         m_doc->charStyles().connect(this, SLOT(invalidateAll()));
  199     }
  200     
  201     d->selFirst = 0;
  202     d->selLast = -1;
  203 
  204     invalidateLayout();
  205     return *this;
  206 }
  207 
  208 int StoryText::cursorPosition() const
  209 {
  210     return d->cursorPosition;
  211 }
  212 
  213 void StoryText::setCursorPosition(int pos, bool relative)
  214 {
  215     if (relative)
  216     {
  217         bool forward = (pos >= 0);
  218         pos += d->cursorPosition;
  219         if (isLowSurrogate(pos) && isHighSurrogate(pos - 1))
  220         {
  221             if (forward)
  222                 pos += 1;
  223             else
  224                 pos -= 1;
  225         }
  226     }
  227     d->cursorPosition = qMin((uint) qMax(pos, 0), d->len);
  228 }
  229 
  230 void StoryText::normalizeCursorPosition()
  231 {
  232     d->cursorPosition = qMax((uint) 0, qMin(d->cursorPosition, d->len));
  233 }
  234 
  235 int StoryText::normalizedCursorPosition()
  236 {
  237     return (int) qMax((uint) 0, qMin(d->cursorPosition, d->len));
  238 }
  239 
  240 void StoryText::moveCursorForward()
  241 {
  242     BreakIterator* it = getGraphemeIterator();
  243     if (!it)
  244         return;
  245 
  246     it->setText((const UChar*) plainText().utf16());
  247     int pos = it->following(cursorPosition());
  248     if (pos != BreakIterator::DONE)
  249         setCursorPosition(pos);
  250 }
  251 
  252 void StoryText::moveCursorBackward()
  253 {
  254     BreakIterator* it = getGraphemeIterator();
  255     if (!it)
  256         return;
  257 
  258     it->setText((const UChar*) plainText().utf16());
  259     int pos = it->preceding(cursorPosition());
  260     if (pos != BreakIterator::DONE)
  261         setCursorPosition(pos);
  262 }
  263 
  264 void StoryText::moveCursorLeft()
  265 {
  266     if (paragraphStyle().direction() == ParagraphStyle::RTL)
  267         moveCursorForward();
  268     else
  269         moveCursorBackward();
  270 }
  271 
  272 void StoryText::moveCursorRight()
  273 {
  274     if (paragraphStyle().direction() == ParagraphStyle::RTL)
  275         moveCursorBackward();
  276     else
  277         moveCursorForward();
  278 }
  279 
  280 void StoryText::moveCursorWordLeft()
  281 {
  282     BreakIterator* it = getWordIterator();
  283     if (!it)
  284         return;
  285     it->setText((const UChar*) plainText().utf16());
  286     int pos = cursorPosition();
  287     if (paragraphStyle().direction() == ParagraphStyle::RTL)
  288     {
  289         pos = it->following(pos);
  290         if (pos != BreakIterator::DONE)
  291         {
  292             while (pos < length() && text(pos).isSpace())
  293                 pos += 1;
  294         }
  295     }
  296     else
  297     {
  298         pos = cursorPosition();
  299         while (pos > 0 && text(pos - 1).isSpace())
  300             pos -= 1;
  301         pos = it->preceding(pos);
  302     }
  303 
  304     if (pos != BreakIterator::DONE)
  305         setCursorPosition(pos);
  306 }
  307 
  308 void StoryText::moveCursorWordRight()
  309 {
  310     BreakIterator* it = getWordIterator();
  311     if (!it)
  312         return;
  313 
  314     it->setText((const UChar*) plainText().utf16());
  315     int pos = cursorPosition();
  316     if (paragraphStyle().direction() == ParagraphStyle::RTL)
  317     {
  318         pos = cursorPosition();
  319         while (pos > 0 && text(pos - 1).isSpace())
  320             pos -= 1;
  321         pos = it->preceding(pos);
  322     }
  323     else
  324     {
  325         pos = it->following(pos);
  326         if (pos != BreakIterator::DONE)
  327         {
  328             while (pos < length() && text(pos).isSpace())
  329                 pos += 1;
  330         }
  331     }
  332 
  333     if (pos != BreakIterator::DONE)
  334         setCursorPosition(pos);
  335 }
  336 
  337 void StoryText::clear()
  338 {
  339     d->selFirst = 0;
  340     d->selLast = -1;
  341     
  342     d->defaultStyle.erase();
  343     d->trailingStyle.erase();
  344 
  345     d->clear();
  346     d->len = 0;
  347     invalidateAll();
  348 }
  349 
  350 int StoryText::indexOf(const QString &str, int from, Qt::CaseSensitivity cs, int* pLen) const
  351 {
  352     int foundIndex = -1;
  353     if (pLen)
  354         *pLen = 0;
  355 
  356     if (str.isEmpty() || (from < 0))
  357         return -1;
  358 
  359     QString qStr = str;
  360     if (cs == Qt::CaseInsensitive)
  361         qStr = qStr.toLower();
  362     QChar ch = qStr.at(0);
  363 
  364     int strLen   = qStr.length();
  365     int storyLen = length();
  366     if (cs == Qt::CaseSensitive)
  367     {
  368         int i = indexOf(ch, from, cs);
  369         while (i >= 0 && i < (int) d->len)
  370         {
  371             int index = 0;
  372             while ((index < strLen) && ((index + i) < storyLen))
  373             {
  374                 if (qStr.at(index) != d->at(index + i)->ch)
  375                     break;
  376                 ++index;
  377             }
  378             if (index == strLen)
  379             {
  380                 foundIndex = i;
  381                 if (pLen)
  382                     *pLen = strLen;
  383                 break;
  384             }
  385             i = indexOf(ch, i + 1, cs);
  386         }
  387     }
  388     else
  389     {
  390         bool qCharIsDiacritic, curCharIsDiacritic;
  391         int i = indexOf(ch, from, cs);
  392         while (i >= 0 && i < (int) d->len)
  393         {
  394             int index = 0;
  395             int diacriticsCounter = 0; //counter for diacritics
  396             while ((index < strLen) && ((index + i + diacriticsCounter) < storyLen))
  397             {
  398                 const QChar &qChar = qStr.at(index);
  399                 const QChar &curChar = d->at(index + diacriticsCounter + i)->ch;
  400                 qCharIsDiacritic   = SpecialChars::isArabicModifierLetter(qChar.unicode()) | (qChar.category() == QChar::Mark_NonSpacing);
  401                 curCharIsDiacritic = SpecialChars::isArabicModifierLetter(curChar.unicode()) | (curChar.category() == QChar::Mark_NonSpacing);
  402                 if (qCharIsDiacritic || curCharIsDiacritic)
  403                 {
  404                     if (qCharIsDiacritic)
  405                     {
  406                         ++index;
  407                         --diacriticsCounter;
  408                     }
  409                     if (curCharIsDiacritic)
  410                         ++diacriticsCounter;
  411                     continue;
  412                 }
  413                 if (qChar != curChar.toLower())
  414                     break;
  415                 ++index;
  416             }
  417             if (index == strLen)
  418             {
  419                 foundIndex = i;
  420                 while ((index + i + diacriticsCounter) < storyLen)
  421                 {
  422                     const QChar &curChar = d->at(index + diacriticsCounter + i)->ch;
  423                     if (!SpecialChars::isArabicModifierLetter(curChar.unicode()) && (curChar.category() != QChar::Mark_NonSpacing))
  424                         break;
  425                     ++diacriticsCounter;
  426                 }
  427 
  428                 if (pLen)
  429                     *pLen = strLen + diacriticsCounter;
  430                 break;
  431             }
  432             i = indexOf(ch, i + 1, cs);
  433         }
  434     }
  435     return foundIndex;
  436 }
  437 
  438 int StoryText::indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const
  439 {
  440     int foundIndex = -1;
  441     int textLength = length();
  442 
  443     if (cs == Qt::CaseSensitive)
  444     {
  445         for (int i = from; i < textLength; ++i)
  446         {
  447             if (d->at(i)->ch == ch)
  448             {
  449                 foundIndex = i;
  450                 break;
  451             }
  452         }
  453     }
  454     else
  455     {
  456         for (int i = from; i < textLength; ++i)
  457         {
  458             if (d->at(i)->ch.toLower() == ch)
  459             {
  460                 foundIndex = i;
  461                 break;
  462             }
  463         }
  464     }
  465     return foundIndex;
  466 }
  467 
  468 void StoryText::insert(const StoryText& other, bool onlySelection)
  469 {
  470     insert(d->cursorPosition, other, onlySelection);
  471 }
  472 
  473 void StoryText::insert(int pos, const StoryText& other, bool onlySelection)
  474 {
  475     if (pos < 0)
  476         pos += length() + 1;
  477     
  478     CharStyle cstyle(charStyle(pos));
  479     ParagraphStyle pstyle(paragraphStyle(pos));
  480     
  481     // this style represents all differences between this and other's defaultstyles
  482     ParagraphStyle otherDefault(other.defaultStyle());
  483     otherDefault.eraseStyle(defaultStyle());
  484     
  485     int oldPos = pos;
  486     int oldLen = length();
  487     int otherStart  = onlySelection? other.startOfSelection() : 0;
  488     int otherEnd    = onlySelection? other.endOfSelection() : other.length();
  489     int cstyleStart = otherStart;
  490     for (int i = otherStart; i < otherEnd; ++i)
  491     {
  492         if (other.charStyle(i) == cstyle 
  493             && other.text(i) != SpecialChars::OBJECT
  494             && other.text(i) != SpecialChars::PARSEP)
  495             continue;
  496         int len = i - cstyleStart;
  497         if (len > 0)
  498         {
  499             insertCharsWithSoftHyphens(pos, other.textWithSoftHyphens(cstyleStart, len));
  500             applyCharStyle(pos, len, otherDefault.charStyle());
  501             applyCharStyle(pos, len, cstyle);
  502             pos += len;
  503         }
  504         if (other.text(i) == SpecialChars::PARSEP)
  505         {
  506             insertChars(pos, SpecialChars::PARSEP);
  507             //#5845 : disable for now as it has nasty side effects when linking frames
  508             //applyStyle(pos, otherDefault);
  509             applyStyle(pos, other.paragraphStyle(i));
  510             cstyleStart = i+1;
  511             pos += 1;
  512         }
  513         else if (other.text(i) == SpecialChars::OBJECT)
  514         {
  515             insertChars(pos, SpecialChars::OBJECT);
  516             item(pos)->embedded = other.item(i)->embedded;
  517             item(pos)->mark = other.item(i)->mark;
  518             if (item(pos)->mark)
  519             {
  520                 d->marksCount++;
  521                 d->marksCountChanged = true;
  522             }
  523             applyCharStyle(pos, 1, other.charStyle(i));
  524             cstyleStart = i+1;
  525             pos += 1;
  526         }
  527         else
  528         {
  529             cstyle = other.charStyle(i);
  530             cstyleStart = i;
  531         }
  532     }
  533     int len = otherEnd - cstyleStart;
  534     if (len > 0)
  535     {
  536         insertCharsWithSoftHyphens(pos, other.textWithSoftHyphens(cstyleStart, len));
  537         applyCharStyle(pos, len, otherDefault.charStyle());
  538         applyCharStyle(pos, len, cstyle);
  539         pos += len;
  540         if (other.text(otherEnd-1) != SpecialChars::PARSEP)
  541         {
  542             //#5845 : disable for now as it has nasty side effects when linking frames
  543             //applyStyle(pos, otherDefault);
  544             applyStyle(pos, other.paragraphStyle(otherEnd-1));
  545         }
  546     }
  547     if ((d->selLast >= d->selFirst) && (d->selFirst <= oldPos) && (oldPos <= d->selLast))
  548         d->selLast += (length() - oldLen);
  549     invalidate(pos, length());
  550 }
  551 
  552 
  553 /**
  554     Make sure that the paragraph CharStyle's point to the new ParagraphStyle
  555  */
  556 void StoryText::insertParSep(int pos)
  557 {
  558     ScText* it = item(pos);
  559     if (!it->parstyle)
  560     {
  561         it->parstyle = new ParagraphStyle(paragraphStyle(pos+1));
  562         it->parstyle->setContext( & d->pstyleContext);
  563         // #7432 : when inserting a paragraph separator, apply/erase the trailing Style
  564         if (pos >= signed(d->len - 1))
  565         {
  566             applyStyle(pos, d->trailingStyle);
  567             d->trailingStyle.erase();
  568         }
  569 //      it->parstyle->setName("para"); // DON'T TRANSLATE
  570 //      it->parstyle->charStyle().setName("cpara"); // DON'T TRANSLATE
  571 //      it->parstyle->charStyle().setContext( d->defaultStyle.charStyleContext() );
  572     }
  573     d->replaceCharStyleContextInParagraph(pos, it->parstyle->charStyleContext());
  574 }
  575 /**
  576      need to remove the ParagraphStyle structure and replace all pointers
  577      to it...
  578  */
  579 void StoryText::removeParSep(int pos)
  580 {
  581     ScText* it = item(pos);
  582     if (it->parstyle) {
  583 //      const CharStyle* oldP = & it->parstyle->charStyle();
  584 //      const CharStyle* newP = & that->paragraphStyle(pos+1).charStyle();
  585 //      d->replaceParentStyle(pos, oldP, newP);
  586         delete it->parstyle;
  587         it->parstyle = nullptr;
  588     }
  589     // demote this parsep so the assert code in replaceCharStyleContextInParagraph()
  590     // doesn't choke:
  591     it->ch = 0;
  592     d->replaceCharStyleContextInParagraph(pos, paragraphStyle(pos+1).charStyleContext());
  593 }
  594 
  595 void StoryText::removeChars(int pos, uint len)
  596 {
  597     if (pos < 0)
  598         pos += length();
  599     
  600     assert( len > 0 );
  601     assert( pos >= 0 );
  602     // This case can be handled more gracefully
  603     // assert( pos + static_cast<int>(len) <= length() );
  604     if (pos >= length())
  605         return;
  606     if (pos + static_cast<int>(len) > length())
  607         len = length() - pos;
  608 
  609     uint oldMarksCount = d->marksCount;
  610 
  611     if ((pos == 0) && (len > 0) && (static_cast<int>(len) == length()))
  612     {
  613         int lastChar = length() - 1;
  614         while (lastChar > 0 && text(lastChar) == SpecialChars::PARSEP)
  615             --lastChar;
  616         d->orphanedCharStyle = charStyle(lastChar);
  617     }
  618     for (int i = pos + static_cast<int>(len) - 1; i >= pos; --i)
  619     {
  620         ScText *it = d->at(i);
  621         if (it->ch == SpecialChars::PARSEP)
  622             removeParSep(i);
  623         if ((it->ch == SpecialChars::OBJECT) && (it->mark != nullptr))
  624             d->marksCount--;
  625         d->takeAt(i);
  626         d->len--;
  627         delete it;
  628         // #9592 : adjust d->selFirst and d->selLast, those values have to be
  629         // consistent in functions such as select()
  630         if (i <= d->selLast)
  631             --d->selLast;
  632         if (i < d->selFirst)
  633             --d->selFirst;
  634         if (static_cast<uint>(i + 1) <= d->cursorPosition && d->cursorPosition > 0)
  635             d->cursorPosition -= 1;
  636     }
  637 
  638     if (oldMarksCount != d->marksCount)
  639         d->marksCountChanged = true;
  640 
  641     d->len = d->count();
  642     d->cursorPosition = qMin(d->cursorPosition, d->len);
  643     if (d->selFirst > d->selLast)
  644     {
  645         d->selFirst =  0;
  646         d->selLast  = -1;
  647     }
  648     invalidate(pos, length());
  649 }
  650 
  651 void StoryText::trim()
  652 {
  653     if (length() < 2)
  654         return;
  655     int posCount = 0;
  656     int pos = static_cast<int>(length()) - 1;
  657     for ( int i = static_cast<int>(length()) - 1; i >= 0; --i )
  658     {
  659         ScText *it = d->at(i);
  660         if ((it->ch == SpecialChars::PARSEP) || (it->ch.isSpace()))
  661         {
  662             pos--;
  663             posCount++;
  664         }
  665         else
  666             break;
  667     }
  668     if (posCount > 0)
  669     {
  670         ParagraphStyle pst = paragraphStyle(pos);
  671         if (posCount > 1)
  672             removeChars(pos+1, posCount-1);
  673         applyStyle(pos, pst);
  674     }
  675 }
  676 
  677 void StoryText::insertChars(const QString& txt, bool applyNeighbourStyle) //, const CharStyle & charstyle)
  678 {
  679     insertChars(d->cursorPosition, txt, applyNeighbourStyle);
  680 }
  681 
  682 void StoryText::insertChars(int pos, const QString& txt, bool applyNeighbourStyle) //, const CharStyle & charstyle)
  683 {
  684     if (pos < 0)
  685         pos += length() + 1;
  686 
  687     assert(pos >= 0);
  688     assert(pos <= length());
  689     
  690     if (txt.length() == 0)
  691         return;
  692     
  693     const StyleContext* cStyleContext = paragraphStyle(pos).charStyleContext();
  694 
  695     ScText clone;
  696     if (applyNeighbourStyle)
  697     {
  698         int referenceChar = qMax(0, qMin(pos, length()-1));
  699         clone.applyCharStyle(charStyle(referenceChar));
  700         clone.setEffects(ScStyle_Default);
  701     }
  702 
  703     for (int i = 0; i < txt.length(); ++i)
  704     {
  705         ScText * item = new ScText(clone);
  706         item->ch= txt.at(i);
  707         item->setContext(cStyleContext);
  708         d->insert(pos + i, item);
  709         d->len++;
  710         if (item->ch == SpecialChars::PARSEP)
  711         {
  712 //          qDebug() << QString("new PARSEP %2 at %1").arg(pos).arg(paragraphStyle(pos).name());
  713             insertParSep(pos + i);
  714         }
  715         if (d->cursorPosition >= static_cast<uint>(pos + i))
  716             d->cursorPosition += 1;
  717     }
  718 
  719     d->len = d->count();
  720     if ((d->selLast >= d->selFirst) && (d->selFirst <= pos) && (pos <= d->selLast))
  721         d->selLast += txt.length();
  722     invalidate(pos, pos + txt.length());
  723 }
  724 
  725 void StoryText::insertCharsWithSoftHyphens(int pos, const QString& txt, bool applyNeighbourStyle)
  726 {
  727     if (pos < 0)
  728         pos += length() + 1;
  729 
  730     assert(pos >= 0);
  731     assert(pos <= length());
  732     
  733     if (txt.length() == 0)
  734         return;
  735     
  736     const StyleContext* cStyleContext = paragraphStyle(pos).charStyleContext();
  737 
  738     ScText clone;
  739     if (applyNeighbourStyle)
  740     {
  741         int referenceChar = qMax(0, qMin(pos, length() - 1));
  742         clone.applyCharStyle(charStyle(referenceChar));
  743         clone.setEffects(ScStyle_Default);
  744     }
  745 
  746     int inserted = 0;
  747     for (int i = 0; i < txt.length(); ++i) 
  748     {
  749         QChar ch = txt.at(i);
  750         int  index  = pos + inserted;
  751         bool insert = true; 
  752         if (ch == SpecialChars::SHYPHEN && index > 0)
  753         {
  754             ScText* lastItem = this->item(index - 1);
  755             // qreal SHY means user provided SHY, single SHY is automatic one
  756             if (lastItem->effects() & ScStyle_HyphenationPossible)
  757                 lastItem->setEffects(lastItem->effects() & ~ScStyle_HyphenationPossible);
  758             else
  759             {
  760                 lastItem->setEffects(lastItem->effects() | ScStyle_HyphenationPossible);
  761                 insert = false;
  762             }
  763         }
  764         if (insert)
  765         {
  766             ScText * item = new ScText(clone);
  767             item->ch = ch;
  768             item->setContext(cStyleContext);
  769             d->insert(index, item);
  770             d->len++;
  771             if (item->ch == SpecialChars::PARSEP)
  772                 insertParSep(index);
  773             if (d->cursorPosition >= static_cast<uint>(index))
  774                 d->cursorPosition += 1;
  775             ++inserted;
  776         }
  777     }
  778 
  779     d->len = d->count();
  780     if ((d->selLast >= d->selFirst) && (d->selFirst <= pos) && (pos <= d->selLast))
  781         d->selLast += inserted;
  782     invalidate(pos, pos + inserted);
  783 }
  784 
  785 void StoryText::replaceChar(int pos, QChar ch)
  786 {
  787     if (pos < 0)
  788         pos += length();
  789 
  790     assert(pos >= 0);
  791     assert(pos < length());
  792 
  793     ScText* item = d->at(pos);
  794     if (item->ch == ch)
  795         return;
  796 
  797     uint oldMarksCount = d->marksCount;
  798     
  799     if (item->ch == SpecialChars::PARSEP)
  800         removeParSep(pos);
  801     if ((item->ch == SpecialChars::OBJECT) && (item->mark != nullptr))
  802         d->marksCount--;
  803     item->ch = ch;
  804     if (d->at(pos)->ch == SpecialChars::PARSEP)
  805         insertParSep(pos);
  806 
  807     if (oldMarksCount != d->marksCount)
  808         d->marksCountChanged = true;
  809     
  810     invalidate(pos, pos + 1);
  811 }
  812 
  813 void StoryText::replaceSelection(const QString& newText)
  814 {
  815     if (selectionLength() <= 0)
  816         return;
  817 
  818     int selStart = startOfSelection();
  819     int selLength = selectionLength();
  820 
  821     int lengthDiff = newText.length() - selLength;
  822     if (lengthDiff == 0)
  823     {
  824         for (int i = 0; i < selLength; ++i)
  825             replaceChar(selStart + i, newText[i]);
  826     }
  827     else if (lengthDiff > 0)
  828     {
  829         for (int i = 0; i < selLength; ++i)
  830             replaceChar(selStart + i, newText[i]);
  831         for (int i = selLength; i < newText.length(); ++i)
  832             insertChars(selStart + i, newText.mid(i, 1), true);
  833     }
  834     else
  835     {
  836         for (int i = 0; i < newText.length(); ++i)
  837             replaceChar(selStart + i, newText[i]);
  838         removeChars(selStart + newText.length(), -lengthDiff);
  839     }
  840 
  841     deselectAll();
  842     if (newText.length() > 0)
  843         select(selStart, newText.length());
  844     setCursorPosition(selStart + newText.length());
  845 }
  846 
  847 int StoryText::replaceWord(int pos, QString newWord)
  848 {
  849     QString word = this->word(pos);
  850     int lengthDiff = newWord.length() - word.length();
  851     if (lengthDiff == 0)
  852     {
  853         for (int j = 0; j < word.length(); ++j)
  854             replaceChar(pos + j, newWord[j]);
  855     }
  856     else if (lengthDiff > 0)
  857     {
  858         for (int j = 0; j < word.length(); ++j)
  859             replaceChar(pos + j, newWord[j]);
  860         for (int j = word.length(); j < newWord.length(); ++j)
  861             insertChars(pos + j, newWord.mid(j, 1), true);
  862     }
  863     else
  864     {
  865         for (int j = 0; j < newWord.length(); ++j)
  866             replaceChar(pos + j, newWord[j]);
  867         removeChars(pos + newWord.length(), -lengthDiff);
  868     }
  869     return lengthDiff;
  870 }
  871 
  872 void StoryText::hyphenateWord(int pos, uint len, const char* hyphens)
  873 {
  874     assert(pos >= 0);
  875     assert(pos + signed(len) <= length());
  876     
  877 //  QString dump("");
  878     for (int i=pos; i < pos+signed(len); ++i)
  879     {
  880 //      dump += d->at(i)->ch;
  881         if (hyphens && hyphens[i-pos] & 1)
  882         {
  883             d->at(i)->setEffects(d->at(i)->effects() | ScStyle_HyphenationPossible);
  884 //          dump += "-";
  885         }
  886         else {
  887             d->at(i)->setEffects(d->at(i)->effects() & ~ScStyle_HyphenationPossible);
  888         }
  889     }
  890 //  qDebug() << QString("st: %1").arg(dump);
  891     invalidate(pos, pos + len);
  892 }
  893 
  894 void StoryText::insertObject(int ob)
  895 {
  896     insertObject(d->cursorPosition, ob);
  897 }
  898 
  899 void StoryText::insertObject(int pos, int ob)
  900 {
  901     if (pos < 0)
  902         pos += length()+1;
  903 
  904     insertChars(pos, SpecialChars::OBJECT);
  905     const_cast<StoryText *>(this)->d->at(pos)->embedded = ob;
  906     m_doc->FrameItems[ob]->isEmbedded = true;   // this might not be enough...
  907     m_doc->FrameItems[ob]->OwnPage = -1; // #10379: OwnPage is not meaningful for inline object
  908 }
  909 
  910 void StoryText::insertMark(Mark* mark, int pos)
  911 {
  912     if (mark == nullptr)
  913         return;
  914     if (pos < 0)
  915         pos = d->cursorPosition;
  916 
  917     insertChars(pos, SpecialChars::OBJECT, false);
  918     d->at(pos)->mark = mark;
  919     if (mark)
  920     {
  921         d->marksCount++;
  922         d->marksCountChanged = true;
  923     }
  924 }
  925 
  926 void StoryText::replaceObject(int pos, int ob)
  927 {
  928     if (pos < 0)
  929         pos += length()+1;
  930 
  931     replaceChar(pos, SpecialChars::OBJECT);
  932     const_cast<StoryText *>(this)->d->at(pos)->embedded = ob;
  933     m_doc->FrameItems[ob]->isEmbedded = true;   // this might not be enough...
  934     m_doc->FrameItems[ob]->OwnPage = -1; // #10379: OwnPage is not meaningful for inline object
  935 }
  936 
  937 
  938 int StoryText::length() const
  939 {
  940     return d->len;
  941 }
  942 
  943 QString StoryText::plainText() const
  944 {
  945     if (length() <= 0)
  946         return QString();
  947 
  948     QChar   ch;
  949     QString result;
  950 
  951     int len = length();
  952     result.reserve(len);
  953 
  954     StoryText* that(const_cast<StoryText*>(this));
  955     for (int i = 0; i < len; ++i) {
  956         ch = that->d->at(i)->ch;
  957         if (ch == SpecialChars::PARSEP)
  958             ch = QLatin1Char('\n');
  959         result += ch;
  960     }
  961 
  962     return result;
  963 }
  964 #if 0
  965 QChar StoryText::text() const
  966 {
  967     return text(d->cursorPosition);
  968 }
  969 #endif
  970 
  971 QChar StoryText::text(int pos) const
  972 {
  973     if (pos < 0)
  974         pos += length();
  975 
  976     assert(pos >= 0);
  977     assert(pos < length());
  978 
  979     return const_cast<StoryText *>(this)->d->at(pos)->ch;
  980 }
  981 
  982 QString StoryText::text(int pos, uint len) const
  983 {
  984     if (pos < 0)
  985         pos += length();
  986 
  987     assert(pos >= 0);
  988     assert(pos + signed(len) <= length());
  989 
  990     QString result;
  991     StoryText* that(const_cast<StoryText*>(this));
  992     for (int i = pos; i < pos+signed(len); ++i)
  993         result += that->d->at(i)->ch;
  994 
  995     return result;
  996 }
  997 
  998 
  999 bool StoryText::isBlockStart(int pos) const 
 1000 {
 1001     return pos == 0 || text(pos-1) == SpecialChars::PARSEP;
 1002 }
 1003 
 1004 
 1005 int StoryText::nextBlockStart(int pos) const
 1006 {
 1007     int result = pos + 1;
 1008     while (result < length() && !isBlockStart(result))
 1009         ++result;
 1010     
 1011     // lump empty (or small) paragraphs together
 1012     while (result+1 < length() && isBlockStart(result+1))
 1013         ++result;
 1014     
 1015     return result;
 1016 }
 1017 
 1018 
 1019 InlineFrame StoryText::object(int pos) const
 1020 {
 1021     if (pos < 0)
 1022         pos += length();
 1023 
 1024     assert(pos >= 0);
 1025     assert(pos < length());
 1026 
 1027     StoryText* that = const_cast<StoryText *>(this);
 1028     return InlineFrame(that->d->at(pos)->embedded);
 1029 }
 1030 
 1031 
 1032 bool StoryText::hasExpansionPoint(int pos) const
 1033 {
 1034     return text(pos) == SpecialChars::PAGENUMBER || text(pos) == SpecialChars::PAGECOUNT || hasMark(pos);
 1035 }
 1036 
 1037 
 1038 ExpansionPoint StoryText::expansionPoint(int pos) const
 1039 {
 1040     if (text(pos) == SpecialChars::PAGENUMBER)
 1041         return ExpansionPoint(ExpansionPoint::PageNumber);
 1042     if( text(pos) == SpecialChars::PAGECOUNT)
 1043         return ExpansionPoint(ExpansionPoint::PageCount);
 1044     if (hasMark(pos))
 1045         return ExpansionPoint(mark(pos));
 1046     return ExpansionPoint(ExpansionPoint::Invalid);
 1047 }
 1048 
 1049 
 1050 QString StoryText::sentence(int pos, int &posn)
 1051 {
 1052     int sentencePos = qMax(0, prevSentence(pos));
 1053     posn = sentencePos;
 1054     int nextSentencePos = qMin(length(), endOfSentence(pos));
 1055     return text(sentencePos, nextSentencePos - sentencePos);
 1056 }
 1057 
 1058 QString StoryText::word(int pos)
 1059 {
 1060     BreakIterator* it = getWordIterator();
 1061     if (it)
 1062     {
 1063         it->setText((const UChar*) plainText().utf16());
 1064         int end = it->following(pos);
 1065         return text(pos, end - pos);
 1066     }
 1067 
 1068     // Fallback to old code
 1069     int eoWord = pos;
 1070     while (eoWord < length())
 1071     {
 1072         if (text(eoWord).isLetterOrNumber() || SpecialChars::isIgnorableCodePoint(text(eoWord).unicode()))
 1073             ++eoWord;
 1074         else
 1075             break;
 1076     }
 1077     return text(pos, eoWord - pos);
 1078 }
 1079 
 1080 QString StoryText::textWithSoftHyphens(int pos, uint len) const
 1081 {
 1082     QString result;
 1083     int lastPos = pos;
 1084 
 1085     len = qMin((uint) (length() - pos), len);
 1086     for (int i = pos; i < pos+signed(len); ++i)
 1087     {
 1088         if (this->charStyle(i).effects() & ScStyle_HyphenationPossible 
 1089             // duplicate SHYPHEN if already present to indicate a user provided SHYPHEN:
 1090             || this->text(i) == SpecialChars::SHYPHEN)
 1091         {
 1092             result += text(lastPos, i + 1 - lastPos);
 1093             result += SpecialChars::SHYPHEN;
 1094             lastPos = i+1;
 1095         }
 1096     }
 1097     if (lastPos < pos+signed(len))
 1098         result += text(lastPos, pos+signed(len) - lastPos);
 1099     return result;
 1100 }
 1101 
 1102 bool StoryText::hasObject(int pos) const
 1103 {
 1104     if (pos < 0)
 1105         pos += length();
 1106 
 1107     assert(pos >= 0);
 1108     assert(pos < length());
 1109 
 1110     StoryText* that = const_cast<StoryText *>(this);
 1111     if (that->d->at(pos)->ch == SpecialChars::OBJECT)
 1112         return that->d->at(pos)->hasObject(m_doc);
 1113     return false;
 1114 }
 1115 
 1116 
 1117 PageItem* StoryText::getItem(int pos) const
 1118 {
 1119     if (pos < 0)
 1120         pos += length();
 1121 
 1122     assert(pos >= 0);
 1123     assert(pos < length());
 1124 
 1125     StoryText* that = const_cast<StoryText *>(this);
 1126     return that->d->at(pos)->getItem(m_doc);
 1127 }
 1128 
 1129 int StoryText::findMark(const Mark* mrk, int startPos) const
 1130 {
 1131     assert(startPos >= 0);
 1132 
 1133     int len = d->len;
 1134     for (int i = startPos; i < len; ++i)
 1135     {
 1136         if (d->at(i)->ch != SpecialChars::OBJECT)
 1137             continue;
 1138         if (d->at(i)->hasMark(mrk))
 1139             return i;
 1140     }
 1141 
 1142     return -1;
 1143 }
 1144 
 1145 bool StoryText::hasMark(int pos, const Mark* mrk) const
 1146 {
 1147     if (pos < 0)
 1148         pos += length();
 1149 
 1150     assert(pos >= 0);
 1151     assert(pos < length());
 1152 
 1153     StoryText* that = const_cast<StoryText *>(this);
 1154     if (that->d->at(pos)->ch == SpecialChars::OBJECT)
 1155         return that->d->at(pos)->hasMark(mrk);
 1156     return false;
 1157 }
 1158 
 1159 Mark* StoryText::mark(int pos) const
 1160 {
 1161     if (pos < 0)
 1162         pos += length();
 1163 
 1164     assert(pos >= 0);
 1165     assert(pos < length());
 1166 
 1167     StoryText* that = const_cast<StoryText *>(this);
 1168     return that->d->at(pos)->mark;
 1169 }
 1170 
 1171 
 1172 void StoryText::applyMarkCharstyle(Mark* mrk, CharStyle& currStyle) const
 1173 {
 1174     TextNote* note = mrk->getNotePtr();
 1175     if (note == nullptr)
 1176         return;
 1177     
 1178     NotesStyle* nStyle = note->notesStyle();
 1179     Q_ASSERT(nStyle != nullptr);
 1180     
 1181     QString chsName = nStyle->marksChStyle();
 1182     if (!chsName.isEmpty())
 1183     {
 1184         CharStyle marksStyle(m_doc->charStyle(chsName));
 1185         if (!currStyle.equiv(marksStyle))
 1186             currStyle.setParent(chsName);
 1187     }
 1188     
 1189     StyleFlag s(currStyle.effects());
 1190     if (mrk->isType(MARKNoteMasterType))
 1191     {
 1192         if (nStyle->isSuperscriptInMaster())
 1193             s |= ScStyle_Superscript;
 1194         else
 1195             s &= ~ScStyle_Superscript;
 1196     }
 1197     else
 1198     {
 1199         if (nStyle->isSuperscriptInNote())
 1200             s |= ScStyle_Superscript;
 1201         else
 1202             s &= ~ScStyle_Superscript;
 1203     }
 1204     if (s != currStyle.effects())
 1205         currStyle.setFeatures(s.featureList());
 1206 }
 1207 
 1208 
 1209 void StoryText::replaceMark(int pos, Mark* mrk)
 1210 {
 1211     if (pos < 0)
 1212         pos += length();
 1213 
 1214     assert(pos >= 0);
 1215     assert(pos < length());
 1216 
 1217     ScText* textItem = this->d->at(pos);
 1218     if (textItem->mark)
 1219         this->d->marksCount--;
 1220     this->d->at(pos)->mark = mrk;
 1221     if (textItem->mark)
 1222         this->d->marksCount++;
 1223 
 1224     // Set marksCountChanged unconditionnaly to force text relayout
 1225     d->marksCountChanged = true;
 1226 }
 1227 
 1228 
 1229 bool StoryText::isHighSurrogate(int pos) const
 1230 {
 1231     return pos >= 0 && pos < length() && text(pos).isHighSurrogate();
 1232 }
 1233 
 1234 bool StoryText::isLowSurrogate(int pos) const
 1235 {
 1236     return pos >= 0 && pos < length() && text(pos).isLowSurrogate();
 1237 }
 1238 
 1239 
 1240 LayoutFlags StoryText::flags(int pos) const
 1241 {
 1242     if (pos < 0)
 1243         pos += length();
 1244 
 1245     assert(pos >= 0);
 1246     assert(pos < length());
 1247 
 1248     StoryText* that = const_cast<StoryText *>(this);
 1249     return  static_cast<LayoutFlags>((*that->d->at(pos)).effects().value & ScStyle_NonUserStyles);
 1250 }
 1251 
 1252 bool StoryText::hasFlag(int pos, LayoutFlags flags) const
 1253 {
 1254     if (pos < 0)
 1255         pos += length();
 1256 
 1257     assert(pos >= 0);
 1258     assert(pos < length());
 1259     assert((flags & ScStyle_UserStyles) == ScStyle_None);
 1260 
 1261     return (flags & d->at(pos)->effects().value) == flags;
 1262 }
 1263 
 1264 void StoryText::setFlag(int pos, LayoutFlags flags)
 1265 {
 1266     if (pos < 0)
 1267         pos += length();
 1268 
 1269     assert(pos >= 0);
 1270     assert(pos < length());
 1271     assert((flags & ScStyle_UserStyles) == ScStyle_None);
 1272 
 1273     d->at(pos)->setEffects(flags | d->at(pos)->effects().value);
 1274 }
 1275 
 1276 void StoryText::clearFlag(int pos, LayoutFlags flags)
 1277 {
 1278     if (pos < 0)
 1279         pos += length();
 1280 
 1281     assert(pos >= 0);
 1282     assert(pos < length());
 1283 
 1284     d->at(pos)->setEffects(~(flags & ScStyle_NonUserStyles) & d->at(pos)->effects().value);
 1285 }
 1286 
 1287 
 1288 const CharStyle & StoryText::charStyle() const
 1289 {
 1290     return charStyle(d->cursorPosition);
 1291 }
 1292 
 1293 const CharStyle & StoryText::charStyle(int pos) const
 1294 {
 1295     if (pos < 0)
 1296         pos += length();
 1297 
 1298     assert(pos >= 0);
 1299     assert(pos <= length());
 1300 
 1301     if (length() == 0)
 1302     {
 1303 //      qDebug() << "storytext::charstyle: default";
 1304         return d->orphanedCharStyle;
 1305     }
 1306     if (pos == length())
 1307     {
 1308         //qDebug() << "storytext::charstyle: access at end of text %i" << pos;
 1309         --pos;
 1310     }
 1311     
 1312     if (text(pos) == SpecialChars::PARSEP)
 1313         return paragraphStyle(pos).charStyle();
 1314     
 1315     StoryText* that = const_cast<StoryText *>(this);
 1316 
 1317     if (hasMark(pos))
 1318     {
 1319         Mark* mrk = mark(pos);
 1320         applyMarkCharstyle(mrk, *that->d->at(pos)); // hack to keep note charstyles current
 1321     }
 1322     
 1323     return dynamic_cast<const CharStyle &> (*that->d->at(pos));
 1324 }
 1325 
 1326 const ParagraphStyle & StoryText::paragraphStyle() const
 1327 {
 1328     return paragraphStyle(d->cursorPosition);
 1329 }
 1330 
 1331 const ParagraphStyle & StoryText::paragraphStyle(int pos) const
 1332 {
 1333     if (pos < 0)
 1334         pos += length();
 1335 
 1336     assert(pos >= 0);
 1337     assert(pos <= length());
 1338 
 1339     assert(d);
 1340     
 1341     StoryText * that = const_cast<StoryText *> (this);
 1342 //  assert( that->at(pos)->cab >= 0 );
 1343 //  assert( that->at(pos)->cab < doc->docParagraphStyles.count() );
 1344 //  return doc->docParagraphStyles[that->at(pos)->cab];
 1345     
 1346     while (pos < length() && that->d->at(pos)->ch != SpecialChars::PARSEP)
 1347         ++pos;
 1348 
 1349     if (pos >= length())
 1350         return that->d->trailingStyle;
 1351     if ( !that->d->at(pos)->parstyle )
 1352     {
 1353         ScText* current = that->d->at(pos);
 1354         qDebug("inserting default parstyle at %i", pos);
 1355         current->parstyle = new ParagraphStyle();
 1356         current->parstyle->setContext( & d->pstyleContext);
 1357 //      current->parstyle->setName( "para(paragraphStyle)" ); // DON'T TRANSLATE
 1358 //      current->parstyle->charStyle().setName( "cpara(paragraphStyle)" ); // DON'T TRANSLATE
 1359 //      current->parstyle->charStyle().setContext( d->defaultStyle.charStyleContext());
 1360     }
 1361     else {
 1362 //      qDebug() << QString("using parstyle at %1").arg(pos);
 1363     }
 1364     assert (that->d->at(pos)->parstyle);
 1365     return *that->d->at(pos)->parstyle;
 1366 }
 1367 
 1368 const ParagraphStyle& StoryText::defaultStyle() const
 1369 {
 1370     assert(d);
 1371     return d->defaultStyle;
 1372 }
 1373 
 1374 
 1375 void StoryText::setDefaultStyle(const ParagraphStyle& style)
 1376 {
 1377     const StyleContext * oldPContext = d->defaultStyle.context();
 1378 //  const StyleContext * oldCContext = d->defaultStyle.charStyle().context();
 1379     d->defaultStyle = style;
 1380     d->defaultStyle.setContext( oldPContext );
 1381 //  d->defaultStyle.setName( "storydefault" ); // DON'T TRANSLATE
 1382 //  d->defaultStyle.charStyle().setName( "cstorydefault" ); // DON'T TRANSLATE
 1383 //  qDebug() << QString("defstyle %1 context %2 defcstyle %3 ccontext %4 newcontext %5")
 1384 //         .arg((uint)&d->defaultStyle,16).arg((uint)oldPContext,16)
 1385 //         .arg((uint)&d->defaultStyle.charStyle(),16).arg((uint)oldCContext,16)
 1386 //         .arg((uint)d->defaultStyle.charStyle().context(),16);
 1387 //  d->defaultStyle.charStyle().setContext( oldCContext );
 1388     invalidateAll();
 1389 }
 1390 
 1391 
 1392 void StoryText::applyCharStyle(int pos, uint len, const CharStyle& style )
 1393 {
 1394     if (pos < 0)
 1395         pos += length();
 1396 
 1397 /* Why do we use an assert here instead of a gracefully return doing nothing ????? */
 1398 //  assert(pos >= 0);
 1399 //  assert(pos + signed(len) <= length());
 1400     if (pos < 0)
 1401         return;
 1402     if (pos + signed(len) > length())
 1403         return;
 1404     if (len == 0)
 1405         return;
 1406 
 1407 //  int lastParStart = pos == 0? 0 : -1;
 1408     ScText* itText;
 1409     for (uint i=pos; i < pos+len; ++i)
 1410     {
 1411         itText = d->at(i);
 1412         // #6165 : applying style on last character applies style on whole text on next open 
 1413         /*if (itText->ch == SpecialChars::PARSEP && itText->parstyle != nullptr)
 1414             itText->parstyle->charStyle().applyCharStyle(style);*/
 1415         
 1416         // Does not work well, do not reenable before checking #9337, #9376 and #9428
 1417         // #9173 et. al.: move charstyle to parstyle if whole paragraph is affected
 1418         /*if (itText->ch == SpecialChars::PARSEP && itText->parstyle != nullptr && lastParStart >= 0)
 1419         {
 1420             eraseCharStyle(lastParStart, i - lastParStart, style);
 1421             itText->parstyle->charStyle().applyCharStyle(style);
 1422             lastParStart = i + 1;
 1423         }*/
 1424         itText->applyCharStyle(style);
 1425     }
 1426     // Does not work well, do not reenable before checking #9337, #9376 and #9428
 1427     /*if (pos + signed(len) == length() && lastParStart >= 0)
 1428     {
 1429         eraseCharStyle(lastParStart, length() - lastParStart, style);
 1430         d->trailingStyle.charStyle().applyCharStyle(style);
 1431     }*/
 1432     
 1433     invalidate(pos, pos + len);
 1434 }
 1435 
 1436 
 1437 
 1438 void StoryText::eraseCharStyle(int pos, uint len, const CharStyle& style )
 1439 {
 1440     if (pos < 0)
 1441         pos += length();
 1442     
 1443     assert(pos >= 0);
 1444     assert(pos + signed(len) <= length());
 1445     
 1446     if (len == 0)
 1447         return;
 1448     
 1449     ScText* itText;
 1450     for (uint i=pos; i < pos+len; ++i) {
 1451         itText = d->at(i);
 1452         // FIXME?? see #6165 : should we really erase charstyle of paragraph style??
 1453         if (itText->ch == SpecialChars::PARSEP && itText->parstyle != nullptr)
 1454             itText->parstyle->charStyle().eraseCharStyle(style);
 1455         itText->eraseCharStyle(style);
 1456     }
 1457     // Does not work well, do not reenable before checking #9337, #9376 and #9428
 1458     /*if (pos + signed(len) == length())
 1459     {
 1460         d->trailingStyle.charStyle().eraseCharStyle(style);
 1461     }*/
 1462     
 1463     invalidate(pos, pos + len);
 1464 }
 1465 
 1466 void StoryText::applyStyle(int pos, const ParagraphStyle& style, bool rmDirectFormatting)
 1467 {
 1468     if (pos < 0)
 1469         pos += length();
 1470 
 1471     assert(pos >= 0);
 1472     assert(pos <= length());
 1473 
 1474     int i = pos;
 1475     while (i < length() && d->at(i)->ch != SpecialChars::PARSEP)
 1476         ++i;
 1477 
 1478     if (i < length())
 1479     {
 1480         if (!d->at(i)->parstyle) {
 1481             qDebug("PARSEP without style at pos %i", i);
 1482             d->at(i)->parstyle = new ParagraphStyle();
 1483             d->at(i)->parstyle->setContext( & d->pstyleContext);
 1484 //          d->at(i)->parstyle->setName( "para(applyStyle)" ); // DON'T TRANSLATE
 1485 //          d->at(i)->parstyle->charStyle().setName( "cpara(applyStyle)" ); // DON'T TRANSLATE
 1486 //          d->at(i)->parstyle->charStyle().setContext( d->defaultStyle.charStyleContext() );
 1487         }
 1488 //      qDebug() << QString("applying parstyle %2 at %1 for %3").arg(i).arg(paragraphStyle(pos).name()).arg(pos);
 1489         d->at(i)->parstyle->applyStyle(style);
 1490     }
 1491     else {
 1492         // not happy about this but inserting a new PARSEP makes more trouble
 1493 //      qDebug() << QString("applying parstyle %1 as defaultstyle for %2").arg(paragraphStyle(pos).name()).arg(pos);
 1494         d->trailingStyle.applyStyle(style);
 1495     }
 1496     if (rmDirectFormatting)
 1497     {
 1498         --i;
 1499         while (i >= 0 && d->at(i)->ch != SpecialChars::PARSEP)
 1500         {
 1501             d->at(i)->eraseDirectFormatting();
 1502             --i;
 1503         }
 1504     }
 1505     invalidate(pos, qMin(i, length()));
 1506 }
 1507 
 1508 void StoryText::eraseStyle(int pos, const ParagraphStyle& style)
 1509 {
 1510     if (pos < 0)
 1511         pos += length();
 1512     
 1513     assert(pos >= 0);
 1514     assert(pos <= length());
 1515         
 1516     int i = pos;
 1517     while (i < length() && d->at(i)->ch != SpecialChars::PARSEP)
 1518         ++i;
 1519 
 1520     if (i < length())
 1521     {
 1522         if (!d->at(i)->parstyle) {
 1523             qDebug("PARSEP without style at pos %i", i);
 1524             d->at(i)->parstyle = new ParagraphStyle();
 1525             d->at(i)->parstyle->setContext( & d->pstyleContext);
 1526 //          d->at(i)->parstyle->setName( "para(eraseStyle)" ); // DON'T TRANSLATE
 1527 //          d->at(i)->parstyle->charStyle().setName( "cpara(eraseStyle)" ); // DON'T TRANSLATE
 1528 //          d->at(i)->parstyle->charStyle().setContext( d->defaultStyle.charStyleContext());
 1529         }
 1530         //      qDebug() << QString("applying parstyle %2 at %1 for %3").arg(i).arg(paragraphStyle(pos).name()).arg(pos);
 1531         d->at(i)->parstyle->eraseStyle(style);
 1532     }
 1533     else {
 1534         // not happy about this but inserting a new PARSEP makes more trouble
 1535         //      qDebug() << QString("applying parstyle %1 as defaultstyle for %2").arg(paragraphStyle(pos).name()).arg(pos);
 1536         d->trailingStyle.eraseStyle(style);
 1537     }
 1538     invalidate(pos, qMin(i, length()));
 1539 }
 1540 
 1541 
 1542 void StoryText::setStyle(int pos, const ParagraphStyle& style)
 1543 {
 1544     eraseStyle(pos, paragraphStyle(pos));
 1545     applyStyle(pos, style);
 1546 }
 1547 
 1548 
 1549 void StoryText::setCharStyle(int pos, uint len, const CharStyle& style)
 1550 {
 1551     if (pos < 0)
 1552         pos += length();
 1553     
 1554     assert(pos >= 0);
 1555     assert(len <= unsigned(length()));
 1556     assert(pos + signed(len) <= length());
 1557     
 1558     if (len == 0)
 1559         return;
 1560     
 1561     ScText* itText;
 1562     for (uint i=pos; i < pos+len; ++i)
 1563     {
 1564         itText = d->at(i);
 1565         // #6165 : applying style on last character applies style on whole text on next open 
 1566         /*if (itText->ch == SpecialChars::PARSEP && itText->parstyle != nullptr)
 1567             itText->parstyle->charStyle() = style;*/
 1568         itText->setStyle(style);
 1569     }
 1570     
 1571     invalidate(pos, pos + len);
 1572 }
 1573 
 1574 
 1575 
 1576 void StoryText::getNamedResources(ResourceCollection& lists) const
 1577 {
 1578     d->defaultStyle.getNamedResources(lists);
 1579     d->trailingStyle.getNamedResources(lists);
 1580 
 1581     for (int i=0; i < length(); ++i)
 1582     {
 1583         if (text(i) == SpecialChars::PARSEP)
 1584             paragraphStyle(i).getNamedResources(lists);
 1585         else if (hasObject(i))
 1586             getItem(i)->getNamedResources(lists);
 1587         else
 1588             charStyle(i).getNamedResources(lists);
 1589     }
 1590 }
 1591 
 1592 
 1593 void StoryText::replaceStyles(const QMap<QString,QString>& newNameForOld)
 1594 {
 1595     ResourceCollection newnames;
 1596     newnames.mapStyles(newNameForOld);
 1597     replaceNamedResources(newnames);
 1598 }
 1599 
 1600 void StoryText::replaceNamedResources(ResourceCollection& newNames)
 1601 {
 1602     int len = length();
 1603 
 1604     d->defaultStyle.replaceNamedResources(newNames);
 1605     d->trailingStyle.replaceNamedResources(newNames);
 1606     
 1607     if (len == 0)
 1608         return;
 1609     
 1610     ScText* itText;
 1611     for (int i=0; i < len; ++i)
 1612     {
 1613         itText = d->at(i);
 1614         if (itText->parstyle)
 1615             itText->parstyle->replaceNamedResources(newNames);
 1616         else
 1617             itText->replaceNamedResources(newNames);
 1618     }
 1619     
 1620     invalidate(0, len); 
 1621 }
 1622 
 1623 
 1624 void StoryText::replaceCharStyles(QMap<QString,QString> newNameForOld)
 1625 {
 1626     ResourceCollection newnames;
 1627     newnames.mapCharStyles(newNameForOld);
 1628     replaceNamedResources(newnames);
 1629 }
 1630 
 1631 void StoryText::fixLegacyFormatting()
 1632 {
 1633     if (length() == 0)
 1634         return;
 1635 
 1636     for (int i = 0; i < length(); ++ i)
 1637     {
 1638         if (d->at(i)->ch == SpecialChars::PARSEP)
 1639             fixLegacyFormatting(i);
 1640     }
 1641     fixLegacyFormatting( length() );
 1642 }
 1643 
 1644 void StoryText::fixLegacyFormatting(int pos)
 1645 {
 1646     if (length() == 0)
 1647         return;
 1648 
 1649     if (pos < 0)
 1650         pos += length();
 1651 
 1652     assert(pos >= 0);
 1653     assert(pos <= length());
 1654 
 1655     int i = pos;
 1656     while (i > 0 && d->at(i - 1)->ch != SpecialChars::PARSEP)
 1657         --i;
 1658 
 1659     const ParagraphStyle& parStyle = this->paragraphStyle(pos);
 1660     parStyle.validate();
 1661 
 1662     if (parStyle.hasParent())
 1663     {
 1664         int start = i;
 1665         while ((i < length()) && (d->at(i)->ch != SpecialChars::PARSEP))
 1666         {
 1667             d->at(i)->validate();
 1668             d->at(i)->eraseCharStyle( parStyle.charStyle() );
 1669             ++i;
 1670         }
 1671         invalidate(start, qMin(i + 1, length()));
 1672     }
 1673 }
 1674 
 1675 uint StoryText::nrOfParagraph() const
 1676 {
 1677     return nrOfParagraph(d->cursorPosition);
 1678 }
 1679 
 1680 uint StoryText::nrOfParagraph(int pos) const
 1681 {
 1682     uint result = 0;
 1683     StoryText* that = const_cast<StoryText *>(this);
 1684     bool lastWasPARSEP = true;
 1685     pos = qMin(pos, that->length());
 1686     for (int i=0; i < pos; ++i)
 1687     {
 1688         lastWasPARSEP = that->d->at(i)->ch == SpecialChars::PARSEP;
 1689         if (lastWasPARSEP)
 1690             ++result;
 1691     }
 1692     return result;
 1693 }
 1694 
 1695 uint StoryText::nrOfParagraphs() const
 1696 {
 1697     uint result = 0;
 1698     StoryText* that = const_cast<StoryText *>(this);
 1699     bool lastWasPARSEP = true;
 1700     for (int i=0; i < length(); ++i)
 1701     {
 1702         lastWasPARSEP = that->d->at(i)->ch == SpecialChars::PARSEP;
 1703         if (lastWasPARSEP)
 1704             ++result;
 1705     }
 1706     return lastWasPARSEP ? result : result + 1;
 1707 }
 1708 
 1709 int StoryText::startOfParagraph() const
 1710 {
 1711     return startOfParagraph(nrOfParagraph());
 1712 }
 1713 
 1714 int StoryText::startOfParagraph(uint index) const
 1715 {
 1716     if (index == 0)
 1717         return 0;
 1718 
 1719     StoryText* that = const_cast<StoryText *>(this);
 1720     for (int i=0; i < length(); ++i)
 1721     {
 1722         if (that->d->at(i)->ch == SpecialChars::PARSEP && ! --index)
 1723             return i + 1;
 1724     }
 1725     return length();
 1726 }
 1727 
 1728 int StoryText::endOfParagraph() const
 1729 {
 1730     return endOfParagraph(nrOfParagraph());
 1731 }
 1732 
 1733 int StoryText::endOfParagraph(uint index) const
 1734 {
 1735     ++index;
 1736     StoryText* that = const_cast<StoryText *>(this);
 1737     for (int i=0; i < length(); ++i)
 1738     {
 1739         if (that->d->at(i)->ch == SpecialChars::PARSEP && ! --index)
 1740             return i;
 1741     }
 1742     return length();
 1743 }
 1744 
 1745 uint StoryText::nrOfRuns() const
 1746 {
 1747     return length();
 1748 }
 1749 
 1750 int StoryText::startOfRun(uint index) const
 1751 {
 1752     return index;
 1753 }
 1754 
 1755 int StoryText::endOfRun(uint index) const
 1756 {
 1757     return index + 1;
 1758 }
 1759 
 1760 // positioning. all positioning methods return char positions
 1761 
 1762 int StoryText::nextChar(int pos)
 1763 {
 1764     if (pos < length())
 1765         return pos + 1;
 1766     return length();
 1767 }
 1768 
 1769 int StoryText::prevChar(int pos)
 1770 {
 1771     if (pos > 0)
 1772         return pos - 1;
 1773     return 0;
 1774 }
 1775 
 1776 int StoryText::firstWord()
 1777 {
 1778     int len = length();
 1779     int pos = 0;
 1780 
 1781     while (pos < len)
 1782     {
 1783         if (text(pos).isLetter())
 1784             break;
 1785         ++pos;
 1786     }
 1787     return pos;
 1788 }
 1789 
 1790 int StoryText::nextWord(int pos)
 1791 {
 1792     BreakIterator* it = getWordIterator();
 1793     if (!it)
 1794         return pos;
 1795 
 1796     it->setText((const UChar*) plainText().utf16());
 1797     pos = it->following(pos);
 1798     
 1799     int len = length();
 1800     while ((pos < len) && (text(pos).isSpace() || text(pos).isPunct()))
 1801         ++pos;
 1802     return pos;
 1803 }
 1804 
 1805 int StoryText::prevWord(int pos)
 1806 {
 1807     BreakIterator* it = getWordIterator();
 1808     if (!it)
 1809         return pos;
 1810 
 1811     it->setText((const UChar*) plainText().utf16());
 1812     pos = it->preceding(pos);
 1813     return pos;
 1814 }
 1815 
 1816 int StoryText::endOfWord(int pos) const
 1817 {
 1818     BreakIterator* it = getWordIterator();
 1819     if (!it)
 1820         return pos;
 1821 
 1822     it->setText((const UChar*) plainText().utf16());
 1823     pos = it->following(pos);
 1824     return pos;
 1825 }
 1826 
 1827 int StoryText::endOfSentence(int pos) const
 1828 {
 1829     BreakIterator* it = getSentenceIterator();
 1830     if (!it)
 1831         return pos;
 1832 
 1833     it->setText((const UChar*) plainText().utf16());
 1834     int end = it->following(pos);
 1835     return end;
 1836 }
 1837 
 1838 int StoryText::nextSentence(int pos)
 1839 {
 1840     BreakIterator* it = getSentenceIterator();
 1841     if (!it)
 1842         return pos;
 1843 
 1844     it->setText((const UChar*) plainText().utf16());
 1845     pos = it->following(pos);
 1846     pos = it->next();
 1847     return pos;
 1848 }
 1849 
 1850 int StoryText::prevSentence(int pos)
 1851 {
 1852     //we cannot go before the first position so just return it.
 1853     if (pos == 0)
 1854         return 0;
 1855     BreakIterator* it = getSentenceIterator();
 1856     if (!it)
 1857         return pos;
 1858 
 1859     it->setText((const UChar*) plainText().utf16());
 1860     pos = it->preceding(pos);
 1861     return pos;
 1862 }
 1863 int StoryText::nextParagraph(int pos)
 1864 {
 1865     int len = length();
 1866     pos = qMin(len, pos + 1);
 1867     while (pos < len && text(pos) != SpecialChars::PARSEP)
 1868         ++pos;
 1869     return pos;
 1870 }
 1871 int StoryText::prevParagraph(int pos)
 1872 {
 1873     pos = qMax(0, pos - 1);
 1874     while (pos > 0 && text(pos) != SpecialChars::PARSEP)
 1875         --pos;
 1876     return pos;
 1877 }
 1878 
 1879 // selection
 1880 int StoryText::startOfSelection() const
 1881 {
 1882     return d->selFirst <= d->selLast? d->selFirst : 0;
 1883 }
 1884 
 1885 int StoryText::endOfSelection() const
 1886 {
 1887     return d->selFirst <= d->selLast? d->selLast + 1 : -1;
 1888 }
 1889 
 1890 int StoryText::selectionLength() const
 1891 {
 1892     //FIX ME - sometimes I saw values equal or greater than length of text
 1893     int last = d->selLast;
 1894     if (d->selFirst >= length())
 1895         return 0;
 1896     if (d->selLast >= length())
 1897         last = length() -1;
 1898     return d->selFirst <= last? last - d->selFirst + 1 : 0;
 1899 }
 1900 
 1901 bool StoryText::hasSelection() const
 1902 {
 1903     return selectionLength() > 0;
 1904 }
 1905 
 1906 
 1907 bool StoryText::selected(int pos) const
 1908 {
 1909     return (d->selFirst <= pos && pos <= d->selLast) 
 1910 //         || (pos >= 0 && pos < length() && const_cast<StoryText*>(this)->d->at(pos)->cselect)
 1911     ;
 1912 }
 1913 
 1914 QString StoryText::selectedText() const
 1915 {
 1916     int selLen = selectionLength();
 1917     if (selLen <= 0)
 1918         return QString();
 1919     return text(startOfSelection(), selLen);
 1920 }
 1921 
 1922 int StoryText::selectWord(int pos)
 1923 {
 1924     //Double click in a frame to select a word
 1925     BreakIterator* it = getWordIterator();
 1926     if (!it)
 1927         return pos;
 1928     it->setText((const UChar*) plainText().utf16());
 1929     int start = it->preceding(pos + 1);
 1930     int end = it->next();
 1931     int wordLength = end - start;
 1932     if (wordLength > 0)
 1933     {
 1934         select(start, wordLength);
 1935         setCursorPosition(endOfSelection());
 1936     }
 1937     else
 1938     {
 1939         deselectAll();
 1940         setCursorPosition(start);
 1941     }
 1942     return start;
 1943 }
 1944 
 1945 
 1946 void StoryText::select(int pos, int len, bool on)
 1947 {
 1948     if (pos < 0)
 1949         pos += length();
 1950     if (len < 0)
 1951         len = 0;
 1952 
 1953     assert( pos >= 0 );
 1954     assert( pos + signed(len) <= length() );
 1955 
 1956 //  qDebug("old selection: %d - %d", d->selFirst, d->selLast);
 1957 
 1958 //  StoryText* that = const_cast<StoryText *>(this);
 1959 //  for (int i=pos; i < pos+signed(len); ++i)
 1960 //      that->at(i)->cselect = on;
 1961 
 1962     if (on) {
 1963         // extend if possible
 1964         if (selected(pos - 1))
 1965             d->selLast = qMax(d->selLast, pos + len - 1);
 1966         else if (selected(pos + len))
 1967             d->selFirst = qMin(d->selFirst, pos);
 1968         else {
 1969             d->selFirst = pos;
 1970             d->selLast = pos + len - 1;
 1971         }
 1972     }
 1973     else {
 1974         if (pos <= d->selFirst && d->selLast < pos + signed(len))
 1975             deselectAll();
 1976         // shrink
 1977         else if (!selected(pos - 1) && selected(pos + len - 1))
 1978             d->selFirst = pos + len;
 1979         else if (selected(pos) && !selected(pos + len))
 1980             d->selLast = pos - 1;
 1981         else if (selected(pos) || selected(pos + len - 1))
 1982             // Grr, deselection splits selection
 1983             d->selLast = pos - 1;
 1984     }
 1985 
 1986     fixSurrogateSelection();
 1987     
 1988 //  qDebug("new selection: %d - %d", d->selFirst, d->selLast);
 1989 }
 1990 
 1991 void StoryText::extendSelection(int oldPos, int newPos)
 1992 {
 1993     if (d->selFirst <= d->selLast)
 1994     {
 1995         // have selection
 1996         if (d->selLast == oldPos - 1)
 1997         {
 1998             d->selLast = newPos - 1;
 1999             return;
 2000         }
 2001         if (d->selFirst == oldPos)
 2002         {
 2003             d->selFirst = newPos;
 2004             return;
 2005         }
 2006         // can't extend, fall through
 2007     }
 2008     // no previous selection
 2009     if (newPos > oldPos)
 2010     {
 2011         d->selFirst = oldPos;
 2012         d->selLast = newPos - 1;
 2013     }
 2014     else
 2015     {
 2016         d->selFirst = newPos;
 2017         d->selLast = oldPos - 1;
 2018     }
 2019 
 2020     fixSurrogateSelection();
 2021 }
 2022 
 2023 
 2024 void StoryText::fixSurrogateSelection()
 2025 {
 2026     if (isLowSurrogate(d->selFirst) && isHighSurrogate(d->selFirst - 1))
 2027         d->selFirst -= 1;
 2028     if (isHighSurrogate(d->selLast) && isLowSurrogate(d->selLast + 1))
 2029         d->selLast += 1;
 2030 }
 2031 
 2032 BreakIterator* StoryText::m_graphemeIterator = nullptr;
 2033 
 2034 BreakIterator* StoryText::getGraphemeIterator()
 2035 {
 2036     UErrorCode status = U_ZERO_ERROR;
 2037     if (m_graphemeIterator == nullptr)
 2038         m_graphemeIterator = BreakIterator::createCharacterInstance(Locale(), status);
 2039 
 2040     if (U_FAILURE(status))
 2041     {
 2042         delete m_graphemeIterator;
 2043         m_graphemeIterator = nullptr;
 2044     }
 2045 
 2046     return m_graphemeIterator;
 2047 }
 2048 
 2049 BreakIterator* StoryText::m_wordIterator = nullptr;
 2050 
 2051 BreakIterator* StoryText::getWordIterator()
 2052 {
 2053     UErrorCode status = U_ZERO_ERROR;
 2054     if (m_wordIterator == nullptr)
 2055         m_wordIterator = BreakIterator::createWordInstance(Locale(), status);
 2056 
 2057     if (U_FAILURE(status))
 2058     {
 2059         delete m_wordIterator;
 2060         m_wordIterator = nullptr;
 2061     }
 2062     return m_wordIterator;
 2063 }
 2064 
 2065 BreakIterator* StoryText::m_sentenceIterator = nullptr;
 2066 
 2067 BreakIterator* StoryText::getSentenceIterator()
 2068 {
 2069     UErrorCode status = U_ZERO_ERROR;
 2070     if (m_sentenceIterator == nullptr)
 2071         m_sentenceIterator = BreakIterator::createSentenceInstance(Locale(), status);
 2072 
 2073     if (U_FAILURE(status))
 2074     {
 2075         delete m_sentenceIterator;
 2076         m_sentenceIterator = nullptr;
 2077     }
 2078 
 2079     return m_sentenceIterator;
 2080 }
 2081 
 2082 BreakIterator* StoryText::m_lineIterator = nullptr;
 2083 
 2084 BreakIterator* StoryText::getLineIterator()
 2085 {
 2086     UErrorCode status = U_ZERO_ERROR;
 2087     if (m_lineIterator == nullptr)
 2088         m_lineIterator = BreakIterator::createLineInstance(Locale(), status);
 2089 
 2090     if (U_FAILURE(status))
 2091     {
 2092         delete m_lineIterator;
 2093         m_lineIterator = nullptr;
 2094     }
 2095 
 2096     return m_lineIterator;
 2097 }
 2098 
 2099 void StoryText::selectAll()
 2100 {
 2101 /*  StoryText* that = const_cast<StoryText *>(this);
 2102     that->at(0);
 2103     for (int i=0; i < length(); ++i) {
 2104         that->current()->cselect = true;
 2105         that->next();
 2106     }
 2107 */
 2108     d->selFirst = 0;
 2109     d->selLast = length() - 1;
 2110 }
 2111 
 2112 void StoryText::deselectAll()
 2113 {
 2114 /*  StoryText* that = const_cast<StoryText *>(this);
 2115     that->at(0);
 2116     for (int i=0; i < length(); ++i) {
 2117         that->current()->cselect = false;
 2118         that->next();
 2119     }
 2120 */  
 2121     d->selFirst = 0;
 2122     d->selLast = -1;
 2123 }
 2124 
 2125 void StoryText::removeSelection()
 2126 {
 2127 //  qDebug("removeSelection: %d - %d", d->selFirst, d->selLast);
 2128     if (d->selFirst > d->selLast)
 2129         return;
 2130 
 2131     assert( d->selFirst >= 0 );
 2132     assert( d->selLast < length() );
 2133 
 2134     removeChars(d->selFirst, d->selLast - d->selFirst+1);
 2135     deselectAll();
 2136 }
 2137 
 2138 
 2139 
 2140 void StoryText::invalidateObject(const PageItem * embedded)
 2141 {
 2142 }
 2143 
 2144 void StoryText::invalidateLayout()
 2145 {
 2146 }
 2147 
 2148 void StoryText::invalidateAll()
 2149 {
 2150     d->pstyleContext.invalidate();
 2151     invalidate(0, length());
 2152 }
 2153 
 2154 void StoryText::invalidate(int firstItem, int endItem)
 2155 {
 2156     for (int i = firstItem; i < endItem; ++i)
 2157     {
 2158         ParagraphStyle* par = item(i)->parstyle;
 2159         if (par)
 2160             par->charStyleContext()->invalidate();
 2161     }
 2162     if (!signalsBlocked())
 2163         emit changed(firstItem, endItem);
 2164 }
 2165 
 2166 // physical view
 2167 
 2168 /*
 2169 void StoryText::validate()
 2170 {
 2171     static bool withinValidate = false;
 2172     
 2173     assert( !withinValidate );
 2174     withinValidate = true;
 2175     
 2176     withinValidate = false;
 2177 }
 2178 */
 2179 
 2180 ScText*  StoryText::item(int index)
 2181 {
 2182     assert( index < length() );
 2183     return const_cast<StoryText *>(this)->d->at(index);
 2184 }
 2185 
 2186 const ScText*  StoryText::item(int index) const
 2187 {
 2188     assert( index < length() );
 2189     return const_cast<StoryText *>(this)->d->at(index);
 2190 }
 2191 
 2192 
 2193 using namespace desaxe;
 2194 
 2195 void StoryText::saxx(SaxHandler& handler, const Xml_string& elemtag) const
 2196 {
 2197     Xml_attr empty;
 2198     Xml_attr pageno;
 2199     pageno.insert("name", "pgno");
 2200     Xml_attr pageco;
 2201     pageco.insert("name", "pgco");
 2202 
 2203     handler.begin(elemtag, empty);
 2204     defaultStyle().saxx(handler, "defaultstyle");
 2205 
 2206     CharStyle lastStyle(charStyle(0));
 2207     bool lastWasPar = true;
 2208     int lastPos = 0;
 2209     handler.begin("p", empty);
 2210     paragraphStyle(0).saxx(handler);
 2211     handler.begin("span", empty);
 2212     lastStyle.saxx(handler);
 2213     for (int i=0; i < length(); ++i)
 2214     {
 2215         const QChar curr(text(i));
 2216         const CharStyle& style(charStyle(i));
 2217         
 2218         if (curr == SpecialChars::OBJECT ||
 2219             curr == SpecialChars::TAB ||
 2220             curr == SpecialChars::PARSEP ||
 2221             curr == SpecialChars::LINEBREAK ||
 2222             curr == SpecialChars::COLBREAK ||
 2223             curr == SpecialChars::FRAMEBREAK ||
 2224             curr == SpecialChars::PAGENUMBER ||
 2225             curr == SpecialChars::PAGECOUNT ||
 2226             curr.unicode() < 32 || 
 2227             (0xd800 <= curr.unicode() && curr.unicode() < 0xe000) ||
 2228             curr.unicode() == 0xfffe || curr.unicode() == 0xffff ||
 2229             style != lastStyle)
 2230         {
 2231             // something new, write pending chars
 2232             if  (i - lastPos > 0)
 2233             {
 2234                 handler.chars(textWithSoftHyphens(lastPos, i-lastPos));
 2235             }
 2236             lastPos = i;
 2237         }
 2238 
 2239         lastWasPar = (curr == SpecialChars::PARSEP);
 2240         if (lastWasPar)
 2241         {
 2242             handler.end("span");
 2243             handler.end("p");
 2244             handler.begin("p", empty);
 2245             paragraphStyle(i+1).saxx(handler);
 2246             handler.begin("span", empty);
 2247             lastStyle.erase();
 2248         }
 2249         else if (this->hasObject(i))
 2250         {
 2251             getItem(i)->saxx(handler);
 2252         }
 2253         else if (hasMark(i))
 2254         {
 2255             Mark* mrk = mark(i);
 2256             if ((m_doc->m_Selection->itemAt(0)->isNoteFrame() && mrk->isType(MARKNoteMasterType))
 2257                 || (!m_doc->m_Selection->itemAt(0)->isTextFrame() && mrk->isType(MARKNoteFrameType))
 2258                 || mrk->isType(MARKBullNumType))
 2259                 continue; //do not insert notes marks into text frames nad vice versa
 2260             Xml_attr mark_attr;
 2261             mark_attr.insert("label", mrk->label);
 2262             mark_attr.insert("typ", QString::number((int )mrk->getType()));
 2263 //          if (!mrk->isType(MARKBullNumType))
 2264 //          {
 2265 //              mark_attr.insert("strtxt", mrk->getString());
 2266 //              ParagraphStyle pstyle = this->paragraphStyle(i);
 2267 //              mark_attr.insert("style_peoffset", QString::number(pstyle.parEffectOffset(),'f',2));
 2268 //              mark_attr.insert("style_peindent", QString::number(pstyle.parEffectIndent(),'f',2));
 2269 //              mark_attr.insert("style_pecharstyle", pstyle.peCharStyleName());
 2270 //              mark_attr.insert("style_hasbul", pstyle.hasBullet() ? "1" : "0");
 2271 //              mark_attr.insert("style_bulletstr", pstyle.bulletStr());
 2272 //              mark_attr.insert("style_hasnum", pstyle.hasNum() ? "1" : "0");
 2273 //              mark_attr.insert("style_numname", pstyle.numName());
 2274 //              mark_attr.insert("style_numformat", QString::number( (int)pstyle.numFormat()));
 2275 //              mark_attr.insert("style_numprefix", pstyle.numPrefix());
 2276 //              mark_attr.insert("style_numsuffix", pstyle.numSuffix());
 2277 //              mark_attr.insert("style_numlevel", QString::number(pstyle.numLevel()));
 2278 //              mark_attr.insert("style_numstart", QString::number(pstyle.numStart()));
 2279 //              mark_attr.insert("style_numrestart", QString::number((int) pstyle.numRestart()));
 2280 //              mark_attr.insert("style_numother", pstyle.numOther() ? "1" : "0");
 2281 //              mark_attr.insert("style_numhigher", pstyle.numHigher() ? "1" : "0");
 2282 //          }
 2283             if (mrk->isType(MARK2ItemType) && (mrk->getItemPtr() != nullptr))
 2284                 mark_attr.insert("item", mrk->getItemPtr()->itemName());
 2285             else if (mrk->isType(MARK2MarkType))
 2286             {
 2287                 QString l;
 2288                 MarkType t;
 2289                 mrk->getMark(l, t);
 2290                 if (m_doc->getMark(l,t) != nullptr)
 2291                 {
 2292                     mark_attr.insert("mark_l", l);
 2293                     mark_attr.insert("mark_t", QString::number((int) t));
 2294                 }
 2295             }
 2296             else if (mrk->isType(MARKNoteMasterType))
 2297             {
 2298                 TextNote * note = mrk->getNotePtr();
 2299                 assert(note != nullptr);
 2300                 mark_attr.insert("nStyle", note->notesStyle()->name());
 2301                 mark_attr.insert("note",note->saxedText());
 2302                 //store noteframe name for inserting into note if it is non-auto-removable
 2303                 if (note->noteMark() && note->noteMark()->getItemPtr() && !note->noteMark()->getItemPtr()->isAutoNoteFrame())
 2304                     mark_attr.insert("noteframe", note->noteMark()->getItemPtr()->getUName());
 2305             }
 2306             handler.beginEnd("mark", mark_attr);
 2307         }
 2308         else if (curr == SpecialChars::TAB)
 2309         {
 2310             handler.beginEnd("tab", empty);
 2311         }
 2312         else if (curr == SpecialChars::LINEBREAK)
 2313         {
 2314             handler.beginEnd("breakline", empty);
 2315         }
 2316         else if (curr == SpecialChars::COLBREAK)
 2317         {
 2318             handler.beginEnd("breakcol", empty);
 2319         }
 2320         else if (curr == SpecialChars::FRAMEBREAK)
 2321         {
 2322             handler.beginEnd("breakframe", empty);
 2323         }
 2324         else if (curr == SpecialChars::PAGENUMBER)
 2325         {
 2326             handler.beginEnd("var", pageno);
 2327         }
 2328         else if (curr == SpecialChars::PAGECOUNT)
 2329         {
 2330             handler.beginEnd("var", pageco);
 2331         }
 2332         else if (curr.unicode() < 32 || 
 2333                  (0xd800 <= curr.unicode() && curr.unicode() < 0xe000) ||
 2334                  curr.unicode() == 0xfffe || curr.unicode() == 0xffff)
 2335         {
 2336             Xml_attr unic;
 2337             unic.insert("code", toXMLString(curr.unicode()));
 2338             handler.beginEnd("unicode", unic);
 2339         }
 2340         else if (lastStyle != style)
 2341         {
 2342             handler.end("span");
 2343             handler.begin("span", empty);
 2344             style.saxx(handler);
 2345             lastStyle = style;
 2346             continue;
 2347         }
 2348         else
 2349             continue;
 2350         lastPos = i+1;
 2351     }
 2352     
 2353     if  (length() - lastPos > 0)
 2354         handler.chars(textWithSoftHyphens(lastPos, length()-lastPos));
 2355     handler.end("span");
 2356     handler.end("p");
 2357     
 2358 //  if (!lastWasPar)
 2359 //      paragraphStyle(length()-1).saxx(handler);
 2360     
 2361     handler.end(elemtag);
 2362 
 2363 }
 2364 
 2365 
 2366 class AppendText_body : public Action_body
 2367 {
 2368 public: 
 2369     void chars(const Xml_string& txt)
 2370     {
 2371         QChar chr;
 2372         int   lastPos = 0, len;
 2373         StoryText* obj = this->dig->top<StoryText>();
 2374         for (int i = 0; i < txt.length(); ++i)
 2375         {
 2376             chr = txt.at(i);
 2377             if (chr == SpecialChars::SHYPHEN && i > 0)
 2378             {
 2379                 int toInsert = i - lastPos;
 2380                 if (toInsert > 0)
 2381                     obj->insertChars(obj->length(), txt.mid(lastPos, toInsert));
 2382                 len = obj->length();
 2383                 // qreal SHY means user provided SHY, single SHY is automatic one
 2384                 if (obj->hasFlag(len-1, ScLayout_HyphenationPossible))
 2385                 {
 2386                     obj->clearFlag(len-1, ScLayout_HyphenationPossible);
 2387                     obj->insertChars(len, QString(chr));
 2388                 }
 2389                 else
 2390                 {
 2391                     obj->setFlag(len-1, ScLayout_HyphenationPossible);
 2392                 }
 2393                 lastPos = i + 1;
 2394             } 
 2395         }
 2396         if (lastPos < txt.length())
 2397         {
 2398             QString ins = (lastPos == 0) ? txt : txt.right(txt.length() - lastPos);
 2399             len = obj->length();
 2400             obj->insertChars(len, ins);
 2401         }
 2402     }
 2403 };
 2404 
 2405 struct  AppendText : public MakeAction<AppendText_body> 
 2406 {};
 2407 
 2408 
 2409 class AppendSpecial_body : public Action_body
 2410 {
 2411 public:
 2412     AppendSpecial_body(QChar sp) : chr(sp) {}
 2413     
 2414     void begin(const Xml_string& tag, Xml_attr attr)
 2415     {
 2416         StoryText* obj = this->dig->top<StoryText>();
 2417         Xml_attr::iterator code = attr.find("code");
 2418         Xml_attr::iterator name = attr.find("name");
 2419         if (tag == "unicode" && code != attr.end())
 2420             obj->insertChars(-1, QChar(parseUInt(Xml_data(code))));
 2421         else if (tag == "var" && name != attr.end())
 2422         {
 2423             if (Xml_data(name) == "pgno")
 2424                 obj->insertChars(-1, SpecialChars::PAGENUMBER);
 2425             else
 2426                 obj->insertChars(-1, SpecialChars::PAGECOUNT);
 2427         }
 2428         else
 2429             obj->insertChars(-1, chr);
 2430     }
 2431 private:
 2432     QChar chr;
 2433 };
 2434 
 2435 struct AppendSpecial : public MakeAction<AppendSpecial_body, QChar>
 2436 {
 2437     AppendSpecial(QChar sp) : MakeAction<AppendSpecial_body, QChar>(sp) {}
 2438     AppendSpecial() : MakeAction<AppendSpecial_body, QChar>(SpecialChars::BLANK) {}
 2439 };
 2440 
 2441 
 2442 class AppendInlineFrame_body : public Action_body
 2443 {
 2444 public:
 2445     void end(const Xml_string& tag) // this could be a setter if we had StoryText::appendObject() ...
 2446     {
 2447         StoryText* story = this->dig->top<StoryText>(1);
 2448         PageItem* obj = this->dig->top<PageItem>(0);
 2449         // FIXME : currently inline objects are added to doc items when parsing
 2450         // We have to remove them from object list and add them to the FrameItems list
 2451         // before inserting them in story object
 2452         QList<PageItem*> *items = obj->doc()->Items;
 2453         if (items->count() > 0)
 2454         {
 2455             if (items->last() == obj) // try a fast path first
 2456                 items->removeLast();
 2457             else
 2458                 items->removeAll(obj);
 2459         }
 2460         story->insertObject(-1, obj->doc()->addToInlineFrames(obj));
 2461     }
 2462 };
 2463 
 2464 struct AppendInlineFrame : public MakeAction<AppendInlineFrame_body>
 2465 {};
 2466 
 2467 //marks support
 2468 class AppendMark_body : public Action_body
 2469 {
 2470 public:
 2471     void begin(const Xml_string& tag, Xml_attr attr)
 2472     {
 2473         StoryText* story = this->dig->top<StoryText>();
 2474         QString l = "";
 2475         MarkType t = MARKNoType;
 2476         
 2477         Mark* mrk = nullptr;
 2478         
 2479         if (tag == "mark")
 2480         {
 2481             Xml_attr::iterator lIt = attr.find("label");
 2482             Xml_attr::iterator tIt = attr.find("typ");
 2483             //          Xml_attr::iterator nf_It = attr.find("noteframe");
 2484             if (lIt != attr.end())
 2485                 l = Xml_data(lIt);
 2486             if (tIt != attr.end())
 2487                 t = (MarkType) parseInt(Xml_data(tIt));
 2488             if (t != MARKBullNumType)
 2489             {
 2490                 ScribusDoc* doc  = this->dig->lookup<ScribusDoc>("<scribusdoc>");
 2491                 //              ParagraphStyle* pstyle = nullptr;
 2492                 if (t == MARKVariableTextType)
 2493                     mrk = doc->getMark(l,t);
 2494                 //          else if (t == MARKBullNumType)
 2495                 //          {
 2496                 //              mrk = (Mark*) new BulNumMark();
 2497                 //              Xml_attr::iterator str_It = attr.find("strtxt");
 2498                 //              mrk->setString(Xml_data(str_It));
 2499                 //              pstyle = new ParagraphStyle();
 2500                 //              Xml_attr::iterator ite;
 2501                 //              ite = attr.find("style_peoffset");
 2502                 //              if (ite != attr.end())
 2503                 //                  pstyle->setParEffectOffset(parseDouble(Xml_data(ite)));
 2504                 //              ite = attr.find("style_peindent");
 2505                 //              if (ite != attr.end())
 2506                 //                  pstyle->setParEffectIndent(parseDouble(Xml_data(ite)));
 2507                 //              ite = attr.find("style_pecharstyle");
 2508                 //              if (ite != attr.end())
 2509                 //                  pstyle->setPeCharStyleName(Xml_data(ite));
 2510                 //              ite = attr.find("style_hasbul");
 2511                 //              if (ite != attr.end())
 2512                 //                  pstyle->setHasBullet(parseInt(Xml_data(ite)) == 1);
 2513                 //              ite = attr.find("style_bulletstr");
 2514                 //              if (ite != attr.end())
 2515                 //                  pstyle->setBulletStr(Xml_data(ite));
 2516                 //              ite = attr.find("style_hasnum");
 2517                 //              if (ite != attr.end())
 2518                 //                  pstyle->setHasNum(parseInt(Xml_data(ite)) == 1);
 2519                 //              ite = attr.find("style_numname");
 2520                 //              if (ite != attr.end())
 2521                 //                  pstyle->setNumName(Xml_data(ite));
 2522                 //              ite = attr.find("style_numformat");
 2523                 //              if (ite != attr.end())
 2524                 //                  pstyle->setNumFormat(parseInt(Xml_data(ite)));
 2525                 //              ite = attr.find("style_numprefix");
 2526                 //              if (ite != attr.end())
 2527                 //                  pstyle->setNumPrefix(Xml_data(ite));
 2528                 //              ite = attr.find("style_numsuffix");
 2529                 //              if (ite != attr.end())
 2530                 //                  pstyle->setNumSuffix(Xml_data(ite));
 2531                 //              ite = attr.find("style_numlevel");
 2532                 //              if (ite != attr.end())
 2533                 //                  pstyle->setNumLevel(parseInt(Xml_data(ite)));
 2534                 //              ite = attr.find("style_numstart");
 2535                 //              if (ite != attr.end())
 2536                 //                  pstyle->setNumStart(parseInt(Xml_data(ite)));
 2537                 //              ite = attr.find("style_numrestart");
 2538                 //              if (ite != attr.end())
 2539                 //                  pstyle->setNumRestart(parseInt(Xml_data(ite)));
 2540                 //              ite = attr.find("style_numother");
 2541                 //              if (ite != attr.end())
 2542                 //                  pstyle->setNumOther(parseInt(Xml_data(ite)) == 1);
 2543                 //              ite = attr.find("style_numhigher");
 2544                 //              if (ite != attr.end())
 2545                 //                  pstyle->setNumHigher(parseInt(Xml_data(ite)) == 1);
 2546                 //          }
 2547                 else
 2548                 {
 2549                     mrk = doc->newMark();
 2550                     getUniqueName(l,doc->marksLabelsList(t), "_");
 2551                     mrk->label = l;
 2552                     mrk->OwnPage = doc->currentPage()->pageNr();
 2553                     mrk->setType(t);
 2554                     Xml_attr::iterator iIt = attr.find("item");
 2555                     Xml_attr::iterator m_lIt = attr.find("mark_l");
 2556                     Xml_attr::iterator m_tIt = attr.find("mark_t");
 2557                     if (mrk->isType(MARK2ItemType) && (iIt != attr.end()))
 2558                     {
 2559                         PageItem* item = doc->getItemFromName(Xml_data(iIt));
 2560                         mrk->setItemPtr(item);
 2561                         if (item == nullptr)
 2562                             mrk->setString("0");
 2563                         else
 2564                             mrk->setString(doc->getSectionPageNumberForPageIndex(item->OwnPage));
 2565                         mrk->setItemName(Xml_data(iIt));
 2566                     }
 2567                     if (mrk->isType(MARK2MarkType) && (m_lIt != attr.end()) && (m_tIt != attr.end()))
 2568                     {
 2569                         Mark* targetMark = doc->getMark(Xml_data(m_lIt), (MarkType) parseInt(Xml_data(m_tIt)));
 2570                         mrk->setMark(targetMark);
 2571                         if (targetMark == nullptr)
 2572                             mrk->setString("0");
 2573                         else
 2574                             mrk->setString(doc->getSectionPageNumberForPageIndex(targetMark->OwnPage));
 2575                         mrk->setItemName(Xml_data(m_lIt));
 2576                     }
 2577                     if (mrk->isType(MARKNoteMasterType))
 2578                     {
 2579                         Xml_attr::iterator nIt = attr.find("note");
 2580                         Xml_attr::iterator nsIt = attr.find("nStyle");
 2581                         NotesStyle* NS;
 2582                         if (nsIt == attr.end())
 2583                             NS = doc->m_docNotesStylesList.at(0);
 2584                         else
 2585                             NS = doc->getNotesStyle(Xml_data(nsIt));
 2586                         TextNote* note = doc->newNote(NS);
 2587                         note->setMasterMark(mrk);
 2588                         if (nIt != attr.end())
 2589                             note->setSaxedText(Xml_data(nIt));
 2590                         //                  if (!NS->isAutoRemoveEmptyNotesFrames() && (nf_It != attr.end()))
 2591                         //                  {
 2592                         //                      PageItem_NoteFrame* nF = (PageItem_NoteFrame*) doc->getItemFromName(Xml_data(nf_It));
 2593                         //                      if (nF != nullptr)
 2594                         //                          doc->m_Selection->itemAt(0)->asTextFrame()->setNoteFrame(nF);
 2595                         //                  }
 2596                         mrk->setNotePtr(note);
 2597                         doc->setNotesChanged(true);
 2598                     }
 2599                     doc->newMark(mrk);
 2600                 }
 2601                 story->insertMark(mrk);
 2602                 //          if (pstyle != nullptr)
 2603                 //          {
 2604                 //              int i = story->cursorPosition() -1;
 2605                 //              if (story->item(i)->parstyle == nullptr) {
 2606                 //                  story->item(i)->parstyle = new ParagraphStyle(*pstyle);
 2607                 //                  story->item(i)->parstyle->setContext( &doc->paragraphStyles());
 2608                 //              }
 2609                 //              else
 2610                 //                  story->item(i)->parstyle->applyStyle(*pstyle);
 2611                 //          }
 2612             }
 2613         }
 2614     }
 2615 };
 2616 
 2617 struct AppendMark : public MakeAction<AppendMark_body>
 2618 {};
 2619 
 2620 /*
 2621 class ApplyStyle_body : public Action_body
 2622 {
 2623 public:
 2624     void end(const Xml_string& tag) 
 2625     {
 2626         qDebug() << "storytext desaxe: apply style";
 2627         StoryText* story = this->dig->top<StoryText>(1);
 2628         ParagraphStyle* obj = this->dig->top<ParagraphStyle>(0);
 2629         story->applyStyle(-1, *obj);
 2630     }
 2631 };
 2632 
 2633 struct ApplyStyle : public MakeAction<ApplyStyle_body>
 2634 {};
 2635 
 2636 
 2637 class ApplyCharStyle_body : public Action_body
 2638 {
 2639 public:
 2640     ApplyCharStyle_body() : storyTag(StoryText::saxxDefaultElem), lastPos(0), lastStyle()
 2641     {}
 2642     ApplyCharStyle_body(const Xml_string& tag) : storyTag(tag), lastPos(0), lastStyle()
 2643     {}
 2644     
 2645     void end(const Xml_string& tag) 
 2646     {
 2647         qDebug() << "storytext desaxe: apply charstyle";
 2648         if (tag == CharStyle::saxxDefaultElem)
 2649         {
 2650             StoryText* story = this->dig->top<StoryText>(1);
 2651             CharStyle* obj = this->dig->top<CharStyle>(0);
 2652             int len = story->length();
 2653             if (len > lastPos && lastStyle != *obj)
 2654             {
 2655                 story->applyCharStyle(lastPos, len - lastPos, lastStyle);
 2656                 lastPos = len;
 2657                 lastStyle = *obj;
 2658             }
 2659         }
 2660         else if (tag == StoryText::saxxDefaultElem)
 2661         {
 2662             StoryText* story = this->dig->top<StoryText>();
 2663             int len = story->length();
 2664             if (len > lastPos)
 2665             {
 2666                 story->applyCharStyle(lastPos, len - lastPos, lastStyle);
 2667             }
 2668         }
 2669     }
 2670 private:
 2671     Xml_string storyTag;
 2672     int lastPos;
 2673     CharStyle lastStyle;
 2674 };
 2675 
 2676 struct ApplyCharStyle : public MakeAction<ApplyCharStyle_body, const Xml_string&>
 2677 {
 2678     ApplyCharStyle() : MakeAction<ApplyCharStyle_body, const Xml_string&>() {}
 2679     ApplyCharStyle(const Xml_string& tag) : MakeAction<ApplyCharStyle_body, const Xml_string&>(tag) {}
 2680 };
 2681 
 2682 */
 2683 
 2684 class Paragraph_body : public Action_body
 2685 {
 2686 public:
 2687     Paragraph_body() : lastPos(0), numPara(0), lastStyle(nullptr)
 2688     {}
 2689     
 2690     ~Paragraph_body() 
 2691     {
 2692         delete lastStyle;
 2693     }
 2694 
 2695     virtual void reset()
 2696     {
 2697         numPara = 0;
 2698     }
 2699     
 2700     void begin(const Xml_string& tag, Xml_attr attr)
 2701     {
 2702         if (tag == "text-content")
 2703         {
 2704             numPara = 0;
 2705         }
 2706         else if (tag == "p")
 2707         {
 2708             StoryText* story = this->dig->top<StoryText>();
 2709 //          qDebug() << QString("startpar: %1->%2 %3->nullptr").arg(lastPos).arg(story->length()).arg((ulong)lastStyle);
 2710             lastPos = story->length();
 2711             if (numPara > 0) {
 2712                 story->insertChars(-1, SpecialChars::PARSEP);
 2713                 ++lastPos;
 2714             }
 2715             delete lastStyle;
 2716             lastStyle = nullptr;
 2717         }
 2718     }
 2719     
 2720     void end(const Xml_string& tag) 
 2721     {
 2722         if (tag == ParagraphStyle::saxxDefaultElem)
 2723         {
 2724             delete lastStyle;
 2725             lastStyle = this->dig->top<ParagraphStyle>(0);
 2726 //          qDebug() << QString("endstyle: %1 %2 %3").arg("?").arg(lastPos).arg((ulong)lastStyle);
 2727         }
 2728         else if (tag == "p")
 2729         {
 2730             StoryText* story = this->dig->top<StoryText>();
 2731             int len = story->length();
 2732 //          qDebug() << QString("endpar: %1 %2 %3 %4").arg(len).arg(lastPos).arg((ulong)lastStyle).arg(lastStyle? lastStyle->parent() : QString());
 2733             if (len >= lastPos && lastStyle)
 2734             {
 2735                 story->applyStyle(lastPos, *lastStyle);
 2736             }
 2737             ++numPara;
 2738         }
 2739     }
 2740 private:
 2741     int lastPos;
 2742     int numPara;
 2743     ParagraphStyle* lastStyle;
 2744 };
 2745 
 2746 struct Paragraph : public MakeAction<Paragraph_body>
 2747 {
 2748     Paragraph() {}
 2749 };
 2750 
 2751 
 2752 class SpanAction_body : public Action_body
 2753 {
 2754 public:
 2755     SpanAction_body() : lastPos(0), lastStyle(nullptr)
 2756     {}
 2757     
 2758     ~SpanAction_body() 
 2759     {
 2760         delete lastStyle;
 2761     }
 2762     
 2763     void begin(const Xml_string& tag, Xml_attr attr)
 2764     {
 2765 //      qDebug() << QString("spanaction: begin %1").arg(tag);
 2766         if (tag == "span")
 2767         {
 2768             StoryText* story = this->dig->top<StoryText>();
 2769             lastPos = story->length();
 2770             delete lastStyle;
 2771             lastStyle = nullptr;
 2772         }
 2773     }
 2774     
 2775     void end(const Xml_string& tag) 
 2776     {
 2777         if (tag == CharStyle::saxxDefaultElem)
 2778 //          qDebug() << QString("spanaction: end %1").arg(tag);
 2779         {
 2780             delete lastStyle;
 2781             lastStyle = this->dig->top<CharStyle>(0);
 2782         }
 2783         else if (tag == "span")
 2784         {
 2785             StoryText* story = this->dig->top<StoryText>();
 2786             int len = story->length();
 2787             if (len > lastPos && lastStyle)
 2788             {
 2789                 story->applyCharStyle(lastPos, len - lastPos, *lastStyle);
 2790             }
 2791         }
 2792     }
 2793 private:
 2794     int lastPos;
 2795     CharStyle* lastStyle;
 2796 };
 2797 
 2798 struct SpanAction : public MakeAction<SpanAction_body>
 2799 {
 2800     SpanAction() {}
 2801 };
 2802 
 2803 
 2804 const Xml_string StoryText::saxxDefaultElem("story");
 2805 
 2806 void StoryText::desaxeRules(const Xml_string& prefixPattern, Digester& ruleset, Xml_string elemtag)
 2807 {
 2808     Xml_string storyPrefix(Digester::concat(prefixPattern, elemtag));
 2809     ruleset.addRule(storyPrefix, Factory<StoryText>());
 2810 
 2811     ParagraphStyle::desaxeRules(storyPrefix, ruleset, "defaultstyle");
 2812     ruleset.addRule(Digester::concat(storyPrefix, "defaultstyle"), SetterWithConversion<StoryText, const ParagraphStyle&, ParagraphStyle>( & StoryText::setDefaultStyle ));
 2813     
 2814     Paragraph paraAction;
 2815     Xml_string paraPrefix(Digester::concat(storyPrefix, "p"));
 2816     ruleset.addRule(storyPrefix, paraAction ); // needed to initialize some variables
 2817     ruleset.addRule(paraPrefix, paraAction );
 2818     ParagraphStyle::desaxeRules(paraPrefix, ruleset, ParagraphStyle::saxxDefaultElem);
 2819     ruleset.addRule(Digester::concat(paraPrefix, ParagraphStyle::saxxDefaultElem), paraAction );
 2820     
 2821     SpanAction spanAction;
 2822     Xml_string spanPrefix(Digester::concat(paraPrefix, "span"));
 2823     ruleset.addRule(spanPrefix, spanAction );
 2824     CharStyle::desaxeRules(spanPrefix, ruleset, CharStyle::saxxDefaultElem);
 2825     ruleset.addRule(Digester::concat(spanPrefix, CharStyle::saxxDefaultElem), spanAction );
 2826         
 2827     ruleset.addRule(spanPrefix, AppendText());
 2828     
 2829     ruleset.addRule(Digester::concat(spanPrefix, "breakline"), AppendSpecial(SpecialChars::LINEBREAK) );
 2830     ruleset.addRule(Digester::concat(spanPrefix, "breakcol"), AppendSpecial(SpecialChars::COLBREAK) );
 2831     ruleset.addRule(Digester::concat(spanPrefix, "breakframe"), AppendSpecial(SpecialChars::FRAMEBREAK) );
 2832     ruleset.addRule(Digester::concat(spanPrefix, "tab"), AppendSpecial(SpecialChars::TAB) );
 2833     ruleset.addRule(Digester::concat(spanPrefix, "unicode"), AppendSpecial() );
 2834     ruleset.addRule(Digester::concat(spanPrefix, "var"), AppendSpecial());
 2835     
 2836     //PageItem::desaxeRules(storyPrefix, ruleset); argh, that would be recursive!
 2837     ruleset.addRule(Digester::concat(spanPrefix, "item"), AppendInlineFrame() );
 2838     ruleset.addRule(Digester::concat(spanPrefix, "mark"), AppendMark() );
 2839     
 2840 }