Scribus Developer Documentation - API
logo-pen

Scribus Developer Documentation - API

pageitem_textframe.cpp

Go to the documentation of this file.
00001 /*
00002 For general Scribus (>=1.3.2) copyright and licensing information please refer
00003 to the COPYING file provided with the program. Following this notice may exist
00004 a copyright and/or license notice that predates the release of Scribus 1.3.2
00005 for which a new license (GPL+exception) is in place.
00006 */
00007 /***************************************************************************
00008                           pageitem.cpp  -  description
00009                              -------------------
00010     begin                : Sat Apr 7 2001
00011     copyright            : (C) 2001 by Franz Schmid
00012     email                : Franz.Schmid@altmuehlnet.de
00013  ***************************************************************************/
00014 
00015 /***************************************************************************
00016  *                                                                         *
00017  *   This program is free software; you can redistribute it and/or modify  *
00018  *   it under the terms of the GNU General Public License as published by  *
00019  *   the Free Software Foundation; either version 2 of the License, or     *
00020  *   (at your option) any later version.                                   *
00021  *                                                                         *
00022  ***************************************************************************/
00023 
00024 #include <QPolygon>
00025 #include <QList>
00026 #include <QMatrix>
00027 #include <QPoint>
00028 #include <QRegion>
00029 #include <QPalette>
00030 #include <cmath>
00031 #include <cassert>
00032 
00033 #include "canvas.h"
00034 #include "commonstrings.h"
00035 #include "guidemanager.h"
00036 #include "hyphenator.h"
00037 #include "page.h"
00038 #include "pageitem.h"
00039 #include "pageitem_textframe.h"
00040 #include "prefsmanager.h"
00041 #include "scconfig.h"
00042 #include "scpainter.h"
00043 #include "scpaths.h"
00044 #include "scraction.h"
00045 #include "scribus.h"
00046 #include "scribusdoc.h"
00047 #include "scribusstructs.h"
00048 #include "selection.h"
00049 #include "text/nlsconfig.h"
00050 #include "undomanager.h"
00051 #include "undostate.h"
00052 #include "util.h"
00053 #include "util_math.h"
00054 
00055 
00056 using namespace std;
00057 
00058 PageItem_TextFrame::PageItem_TextFrame(ScribusDoc *pa, double x, double y, double w, double h, double w2, QString fill, QString outline)
00059     : PageItem(pa, PageItem::TextFrame, x, y, w, h, w2, fill, outline)
00060 {
00061     invalid = true;
00062     firstChar = 0;
00063     cursorBiasBackward = false;
00064     unicodeTextEditMode = false;
00065     unicodeInputCount = 0;
00066     unicodeInputString = "";
00067 }
00068 
00069 
00070 static QRegion itemShape(PageItem* docItem, double xOffset, double yOffset)
00071 {
00072     QRegion res;
00073     QMatrix pp;
00074 //  pp.begin(view->viewport());
00075     pp.translate(docItem->xPos() - xOffset, docItem->yPos() - yOffset);
00076     pp.rotate(docItem->rotation());
00077     if (docItem->textFlowUsesBoundingBox())
00078     {
00079         QPolygon tcli(4);
00080         tcli.setPoint(0, QPoint(0,0));
00081         tcli.setPoint(1, QPoint(qRound(docItem->width()), 0));
00082         tcli.setPoint(2, QPoint(qRound(docItem->width()), qRound(docItem->height())));
00083         tcli.setPoint(3, QPoint(0, qRound(docItem->height())));
00084 //      res = QRegion(pp.xForm(tcli));
00085         res = QRegion(pp.map(tcli));
00086     }
00087     else if ((docItem->textFlowUsesImageClipping()) && (docItem->imageClip.size() != 0))
00088     {
00089         QList<uint> Segs;
00090         QPolygon Clip2 = FlattenPath(docItem->imageClip, Segs);
00091 //      res = QRegion(pp.xForm(Clip2));
00092         res = QRegion(pp.map(Clip2)).intersect(QRegion(pp.map(docItem->Clip)));
00093     }
00094     else if ((docItem->textFlowUsesContourLine()) && (docItem->ContourLine.size() != 0))
00095     {
00096         QList<uint> Segs;
00097         QPolygon Clip2 = FlattenPath(docItem->ContourLine, Segs);
00098 //      res = QRegion(pp.xForm(Clip2));
00099         res = QRegion(pp.map(Clip2));
00100     }
00101     else
00102 //      res = QRegion(pp.xForm(docItem->Clip));
00103         res = QRegion(pp.map(docItem->Clip));
00104 //  pp.end();
00105     return  res;
00106 }
00107 
00108 QRegion PageItem_TextFrame::availableRegion(QRegion clip)
00109 {
00110     QRegion result(clip);
00111     if (!isEmbedded)
00112     {
00113         int LayerLev = m_Doc->layerLevelFromNumber(LayerNr);
00114         uint docItemsCount=m_Doc->Items->count();
00115         Page* Mp=0;
00116         Page* Dp=0;
00117         PageItem* docItem=0;
00118         int LayerLevItem;
00119         if (!OnMasterPage.isEmpty())
00120         {
00121             if ((savedOwnPage == -1) || (savedOwnPage >= signed(m_Doc->Pages->count())))
00122                 return result;
00123             Mp = m_Doc->MasterPages.at(m_Doc->MasterNames[OnMasterPage]);
00124             Dp = m_Doc->Pages->at(savedOwnPage);
00125             int currentGroup = -1;
00126             for (int a = 0; a < m_Doc->MasterItems.count(); ++a)
00127             {
00128                 docItem = m_Doc->MasterItems.at(a);
00129                 LayerLevItem = m_Doc->layerLevelFromNumber(docItem->LayerNr);
00130                 if (((docItem->ItemNr > ItemNr) && (docItem->LayerNr == LayerNr)) || (LayerLevItem > LayerLev && m_Doc->layerFlow(docItem->LayerNr)))
00131                 {
00132                     if (docItem->Groups.count() == 0)
00133                         currentGroup = -1;
00134                     if ((currentGroup != -1) && (docItem->Groups.count() != 0) && (currentGroup == docItem->Groups.top()))
00135                         continue;
00136                     if (docItem->textFlowAroundObject())
00137                     {
00138                         result = result.subtract(itemShape(docItem, Mp->xOffset() - Dp->xOffset(), Mp->yOffset() - Dp->yOffset()));
00139                     }
00140                     if (docItem->isGroupControl)
00141                         currentGroup = docItem->Groups.top();
00142                 }
00143             } // for all masterItems
00144             // (JG) #6009 : disable possible interaction between master text frames and normal frames
00145             // which have the text flow option set
00146             /*if (!m_Doc->masterPageMode())
00147             {
00148                 for (uint a = 0; a < docItemsCount; ++a)
00149                 {
00150                     docItem = m_Doc->Items->at(a);
00151                     Mp = m_Doc->MasterPages.at(m_Doc->MasterNames[OnMasterPage]);
00152                     Dp = m_Doc->Pages->at(OwnPage);
00153                     if ((docItem->textFlowAroundObject()) && (docItem->OwnPage == OwnPage))
00154                     {
00155                         result = result.subtract(itemShape(docItem, m_Doc->view(), Mp->xOffset() - Dp->xOffset(), Mp->yOffset() - Dp->yOffset()));
00156                     }
00157                 } // for all docItems
00158             } // if (! masterPageMode) */
00159         } // if (!OnMasterPage.isEmpty())
00160         else
00161         {
00162             int currentGroup = -1;
00163             for (uint a = 0; a < docItemsCount; ++a)
00164             {
00165                 docItem = m_Doc->Items->at(a);
00166                 LayerLevItem = m_Doc->layerLevelFromNumber(docItem->LayerNr);
00167                 if (((docItem->ItemNr > ItemNr) && (docItem->LayerNr == LayerNr)) || (LayerLevItem > LayerLev && m_Doc->layerFlow(docItem->LayerNr)))
00168                 {
00169                     if (docItem->Groups.count() == 0)
00170                         currentGroup = -1;
00171                     if ((currentGroup != -1) && (docItem->Groups.count() != 0) && (currentGroup == docItem->Groups.top()))
00172                         continue;
00173                     if (docItem->textFlowAroundObject())
00174                         result = result.subtract(itemShape(docItem, 0, 0));
00175                     if (docItem->isGroupControl)
00176                         currentGroup = docItem->Groups.top();
00177                 }
00178             } // for all docItems
00179             } // if(OnMasterPage.isEmpty()      
00180     } // if(!Embedded)
00181     return result;
00182 }
00183 
00184 
00185 void PageItem_TextFrame::setShadow()
00186 {
00187     if (OnMasterPage.isEmpty())
00188         return;
00189     
00190     QString newShadow = m_Doc->masterPageMode() ? OnMasterPage : QString::number(OwnPage);
00191     if (newShadow != currentShadow) {
00192         if (currentShadow == OnMasterPage) {
00193             // masterpage was edited, clear all shadows
00194             shadows.clear();
00195         }
00196         if (!shadows.contains(newShadow)) {
00197             if (!shadows.contains(OnMasterPage)) {
00198                 shadows[OnMasterPage] = itemText;
00199 //              const ParagraphStyle& pstyle(shadows[OnMasterPage].paragraphStyle(0));
00200 //              qDebug(QString("Pageitem_Textframe: style of master: %1 align=%2").arg(pstyle.parent()).arg(pstyle.alignment()));
00201 //              qDebug(QString("Pageitem_Textframe: shadow itemText->%1").arg(OnMasterPage));
00202             }
00203             if (newShadow != OnMasterPage) {
00204                 shadows[newShadow] = shadows[OnMasterPage].copy();
00205 //              const ParagraphStyle& pstyle(shadows[newShadow].paragraphStyle(0));
00206 //              qDebug(QString("Pageitem_Textframe: style of shadow copy: %1 align=%2").arg(pstyle.parent()).arg(pstyle.alignment()));
00207             }
00208 //          qDebug(QString("Pageitem_Textframe: shadow %1<-%2").arg(newShadow).arg(OnMasterPage));
00209         }
00210         itemText = shadows[newShadow];
00211 //      const ParagraphStyle& pstyle(itemText.paragraphStyle(0));
00212 //      qDebug(QString("Pageitem_Textframe: style of shadow: %1 align=%2").arg(pstyle.parent()).arg(pstyle.alignment()));
00213         invalid = true;
00214         currentShadow = newShadow;
00215     }
00216 }
00217         
00218 #ifdef NLS_PROTO
00219 #include "text/pageitem_textframe.cpp"
00220 #else
00221 
00222 static void dumpIt(const ParagraphStyle& pstyle, QString indent = QString("->"))
00223 {
00224     QString db = QString("%6%1/%2 @ %3: %4--%5 linespa%6: %7 align%8")
00225            .arg(pstyle.name())
00226            .arg(pstyle.parent())
00227            .arg( (unsigned long int) &pstyle)
00228            .arg(pstyle.leftMargin())
00229            .arg(pstyle.rightMargin())
00230            .arg(indent)
00231            .arg(pstyle.lineSpacingMode())
00232            .arg(pstyle.lineSpacing())
00233            .arg(pstyle.alignment());
00234     qDebug(db.toLatin1().constData());
00235     static QString more("  ");
00236     if (pstyle.hasParent())
00237         dumpIt(*dynamic_cast<const ParagraphStyle*>(pstyle.parentStyle()), more + indent);
00238 }
00239 
00240 
00241 static const bool legacy = true;
00242 
00243 /*
00244 static void layoutDropCap(GlyphLayout layout, double curX, double curY, double offsetX, double offsetY, double dropCapDrop) 
00245 {   
00246 }
00247 */
00248 
00252 static void fillInTabLeaders(StoryText & itemText, LineSpec & curLine)
00253 {
00254     // fill in tab leaders
00255     double xPos = curLine.x;
00256     for (int ti= curLine.firstItem; ti < curLine.lastItem; ++ti)
00257     {
00258         ScText * hl = itemText.item(ti);
00259         if (hl->ch == SpecialChars::TAB) 
00260         {
00261             GlyphLayout * tglyph = hl->glyph.more;
00262             
00263             if (!tglyph)
00264                 continue;
00265             
00266             const CharStyle & charStyle(itemText.charStyle(ti));
00267             double wt   = charStyle.font().glyphWidth(tglyph->glyph, charStyle.fontSize() * tglyph->scaleV / 10.0);
00268             double len  = hl->glyph.xadvance;
00269             int count   = static_cast<int>(len / wt);
00270             double sPos = -len;
00271             tglyph->xoffset = sPos;
00272 //          qDebug(QString("tab leaders: %1 %2 width=%3 count=%4").arg(sPos).arg(curLine.y).arg(wt).arg(count));
00273             for (int cx = 1; cx < count; ++cx)
00274             {
00275                 // clone fillchar
00276                 tglyph->grow();
00277                 *(tglyph->more) = *tglyph;
00278                 tglyph->more->more = NULL;
00279                 tglyph = tglyph->more;
00280                 tglyph->xoffset =  sPos + wt * cx;
00281             }
00282         }
00283         xPos += hl->glyph.wide();
00284     }
00285 }
00286 
00287 
00288 
00289 enum TabStatus {
00290     TabNONE    = 0,
00291     TabLEFT    = TabNONE,
00292     TabRIGHT   = 1,
00293     TabPOINT   = 2,
00294     TabCOMMA   = 3,
00295     TabCENTER  = 4
00296 };
00297 
00298 
00302 struct TabControl {
00303     bool   active;
00304     int    status;
00305     int    charIndex;
00306     double xPos;
00307     QChar  fillChar;
00308 };
00309 
00313 struct LineControl {
00314     LineSpec line;
00315     int      itemsInLine;
00316     int      hyphenCount;
00317     double   colWidth;
00318     double   colGap;
00319     double   colLeft;
00320     double   colRight;
00321     int      column;
00322     bool     startOfCol;
00323     double   ascend;
00324     double   descend;
00325     double   width;
00326     double   xPos;
00327     double   yPos;
00328     int      breakIndex;
00329     double   breakXPos;
00330 
00331     double   maxShrink;
00332     double   maxStretch;
00333     
00334     
00336     void init(double w, double h, const MarginStruct& extra, double lCorr)
00337     {
00338         insets = extra;
00339         lineCorr = lCorr;
00340         frameWidth = w;
00341         frameHeight = h;
00342     }
00343     
00345     void initColumns(double width, double gap)
00346     {
00347         column = 0;
00348         colWidth = width;
00349         colGap = gap;
00350     }
00351     
00353     void nextColumn(double asce)
00354     {
00355         startOfCol = true;
00356         colLeft = (colWidth + colGap) * column + insets.Left + lineCorr;
00357         colRight = colLeft + colWidth;
00358         if (legacy)
00359             colRight += insets.Right + lineCorr; //???
00360         xPos = colLeft;
00361         yPos = asce + insets.Top + lineCorr + 1;
00362     }
00363     
00364     bool isEndOfCol(double morespace = 0)
00365     {
00366         return yPos + morespace + insets.Bottom + lineCorr > frameHeight;
00367     }
00368     
00372     void startLine(int first)
00373     {       
00374         itemsInLine = 0;
00375         line.x = xPos;
00376         line.y = yPos;
00377         line.firstItem = first;
00378         line.ascent = 10;
00379         line.descent = 0;
00380         line.width = 0;
00381         line.naturalWidth = 0;
00382         breakIndex = -1;
00383         breakXPos = 0;
00384         maxShrink = 0;
00385         maxStretch = 0;
00386     }
00387     
00388 
00390     void rememberShrinkStretch(QChar ch, double wide, const ParagraphStyle& style)
00391     {
00392         if (SpecialChars::isExpandingSpace(ch))
00393             maxShrink += (1 - style.minWordTracking()) * wide;
00394         else
00395         {
00396             maxShrink += (1 - style.minGlyphExtension()) * wide;
00397         }
00398         maxStretch += (style.maxGlyphExtension() - 1) * wide;
00399         }
00400 
00402     void rememberBreak(int index, double pos)
00403     {
00404         if (pos > colRight)
00405         {
00406             // only look for the first break behind the right edge
00407             maxShrink = 0;
00408             
00409             // check if we already have a better break
00410             if (breakIndex >= 0)
00411             {
00412                 double oldLooseness = qAbs(colRight - breakXPos);
00413 
00414                 double newLooseness = pos - colRight;
00415                 if (newLooseness >= oldLooseness)
00416                     return;
00417             }
00418             breakXPos = colRight;
00419         }
00420         else
00421         {
00422             breakXPos = pos;
00423         }
00424         breakIndex = index;
00425     }
00426     
00428     void breakLine(const StoryText& itemText, int last)
00429     {
00430         breakIndex = last;
00431         breakXPos  = line.x;
00432         for (int j = line.firstItem; j <= last; ++j)
00433             if ( (itemText.item(j)->effects() & ScStyle_SuppressSpace) == 0)
00434                 breakXPos += itemText.item(j)->glyph.wide();
00435     }
00436     
00438     void finishLine(double endX)
00439     {
00440         line.lastItem = breakIndex;
00441         line.naturalWidth = breakXPos - line.x;
00442         line.width = endX - line.x;
00443         maxShrink = maxStretch = 0;
00444     }
00445     
00446     
00447     bool isEndOfLine(double moreSpace = 0)
00448     {
00449         if (legacy)
00450             return xPos + insets.Right + lineCorr + moreSpace - maxShrink >= colRight;
00451         else
00452             return xPos + moreSpace - maxShrink >= colRight;
00453     }
00454     
00456     double startOfLine(const QRegion& shape, const QMatrix& pf2, double ascent, double descent, double morespace)
00457     {
00458         QPoint pt1, pt2;
00459         double tmpX = xPos;
00460         pt1 = QPoint(static_cast<int>(ceil(tmpX)), static_cast<int>(yPos + descent));
00461         pt2 = QPoint(static_cast<int>(ceil(tmpX)), static_cast<int>(ceil(yPos - ascent)));
00462         // increase pt1/pt2 until i-beam reaches end of line
00463         while ((!shape.contains(pf2.map(pt1))) || (!shape.contains(pf2.map(pt2))))
00464         {
00465             tmpX++;
00466             if (xPos + (legacy? lineCorr + insets.Right : 0) + lineCorr + morespace >= colRight)
00467             {
00468                 return tmpX;
00469             }
00470             pt1 = QPoint(static_cast<int>(ceil(tmpX)), static_cast<int>(yPos + descent));
00471             pt2 = QPoint(static_cast<int>(ceil(tmpX)), static_cast<int>(ceil(yPos - ascent)));
00472         }
00473         return tmpX;
00474     }
00475     
00477     double endOfLine(const QRegion& shape, const QMatrix& pf2, double ascent, double descent, double morespace = 0)
00478     {
00479         double EndX = floor(qMax(line.x, qMin(colRight,breakXPos) - 1));
00480         QPoint pt1, pt2;
00481         //  qDebug(QString("endx start=%1, hl is '%2'").arg(EndX).arg(hl->ch));
00482         do {
00483             EndX += 0.125;
00484             pt1 = QPoint(qRound(ceil(EndX + insets.Right)), static_cast<int>(yPos+descent));
00485             pt2 = QPoint(qRound(ceil(EndX + insets.Right)), static_cast<int>(ceil(yPos-ascent)));
00486         } while 
00487             (   (EndX + (legacy? lineCorr + insets.Right : 0) < colRight - morespace)
00488               && shape.contains(pf2.map(pt1))
00489               && shape.contains(pf2.map(pt2)) 
00490               );
00491         
00492         return EndX;
00493     }
00494     
00495     
00496     double getLineAscent(const StoryText& itemText)
00497     {
00498         double result = 0;
00499         if ((itemText.text(line.firstItem) == SpecialChars::PARSEP) || (itemText.text(line.firstItem) == SpecialChars::LINEBREAK))
00500             result = itemText.charStyle(line.firstItem).font().ascent(itemText.charStyle(line.firstItem).fontSize() / 10.0);
00501         else if (itemText.object(line.firstItem) != 0)
00502             result = qMax(result, (itemText.object(line.firstItem)->gHeight + itemText.object(line.firstItem)->lineWidth()) * (itemText.charStyle(line.firstItem).scaleV() / 1000.0));
00503         else //if (itemText.charStyle(current.line.firstItem).effects() & ScStyle_DropCap == 0)
00504             result = itemText.charStyle(line.firstItem).font().realCharAscent(itemText.text(line.firstItem), itemText.charStyle(line.firstItem).fontSize() / 10.0);
00505         for (int zc = 0; zc < itemsInLine; ++zc)
00506         {
00507             QChar ch = itemText.text(line.firstItem + zc);
00508             if (ch == SpecialChars::PAGENUMBER)
00509                 ch = '8'; // should have highest ascender even in oldstyle
00510             const CharStyle& cStyle(itemText.charStyle(line.firstItem + zc));
00511             if ((ch == SpecialChars::TAB) || (ch == QChar(10))
00512                 || (ch == SpecialChars::PARSEP) || (ch == SpecialChars::NBHYPHEN)
00513                 || (ch == SpecialChars::COLBREAK) || (ch == SpecialChars::LINEBREAK)
00514                 || (ch == SpecialChars::FRAMEBREAK) || (ch.isSpace()))
00515                 continue;
00516             double asce;
00517             if (itemText.object(line.firstItem + zc) != 0)
00518                 asce = itemText.object(line.firstItem + zc)->gHeight + itemText.object(line.firstItem + zc)->lineWidth() * (cStyle.scaleV() / 1000.0);
00519             else //if (itemText.charStyle(current.line.firstItem + zc).effects() & ScStyle_DropCap == 0)
00520                 asce = cStyle.font().realCharAscent(ch, cStyle.fontSize() / 10.0);
00521             //                          qDebug(QString("checking char 'x%2' with ascender %1 > %3").arg(asce).arg(ch.unicode()).arg(result));
00522             result = qMax(result, asce);
00523         }
00524         return result;
00525     }
00526     
00527     double getLineHeight(const StoryText& itemText)
00528     {
00529         double result = 0;
00530         if (itemText.object(line.firstItem) != 0)
00531             result = qMax(result, (itemText.object(line.firstItem)->gHeight + itemText.object(line.firstItem)->lineWidth()) * (itemText.charStyle(line.firstItem).scaleV() / 1000.0));
00532         else //if (itemText.charStyle(current.line.firstItem).effects() & ScStyle_DropCap == 0)
00533             result = itemText.charStyle(line.firstItem).font().height(itemText.charStyle(line.firstItem).fontSize() / 10.0);
00534         for (int zc = 0; zc < itemsInLine; ++zc)
00535         {
00536             QChar ch = itemText.text(line.firstItem+zc);
00537             if ((ch == SpecialChars::TAB) || (ch == QChar(10))
00538                 || (ch == SpecialChars::PARSEP) || (ch == SpecialChars::NBHYPHEN)
00539                 || (ch == SpecialChars::COLBREAK) || (ch == SpecialChars::FRAMEBREAK)
00540                 || (ch == SpecialChars::LINEBREAK) || (ch.isSpace()))
00541                 continue;
00542             double asce;
00543             if (itemText.object(line.firstItem+zc) != 0)
00544                 asce = (itemText.object(line.firstItem+zc)->gHeight + itemText.object(line.firstItem+zc)->lineWidth()) * (itemText.charStyle(line.firstItem+zc).scaleV() / 1000.0);
00545             else //if (itemText.charStyle(current.line.firstItem+zc).effects() & ScStyle_DropCap == 0)
00546                 asce = itemText.charStyle(line.firstItem+zc).font().height(itemText.charStyle(line.firstItem+zc).fontSize() / 10.0);
00547             //                  qDebug(QString("checking char 'x%2' with ascender %1 > %3").arg(asce).arg(ch.unicode()).arg(result));
00548             result = qMax(result, asce);
00549         }
00550         return result;
00551     }
00552     
00553     
00554 private:
00555     double frameWidth;
00556     double frameHeight;
00557     MarginStruct insets;
00558     double lineCorr;
00559 };
00560 
00561 
00562 
00564 static void justifyLine(StoryText& itemText, LineSpec& line)
00565 {
00566     
00567     double glyphNatural = 0;
00568     double spaceNatural = 0;
00569     double glyphExtension;
00570     double spaceExtension;
00571 
00572     const ParagraphStyle& style(itemText.paragraphStyle(line.firstItem));
00573 
00574     // measure natural widths for glyphs and spaces
00575     for (int sof = line.firstItem; sof <= line.lastItem; ++sof)
00576     {
00577         if (!SpecialChars::isExpandingSpace(itemText.text(sof)))
00578         {
00579             glyphNatural += itemText.item(sof)->glyph.wide();
00580         }
00581         else if ( (itemText.item(sof)->effects() & ScStyle_SuppressSpace) == 0)
00582         {
00583             spaceNatural += itemText.item(sof)->glyph.wide();
00584         }
00585     }
00586 
00587     // decision: prio 1: stretch glyph;  prio 2: stretch spaces
00588     
00589     if (line.width < spaceNatural + glyphNatural * style.minGlyphExtension() && spaceNatural > 0)
00590     {
00591         glyphExtension = style.minGlyphExtension() - 1;
00592         spaceExtension = (line.width - glyphNatural * (1+glyphExtension) ) / spaceNatural  - 1;
00593     }
00594     else if (line.width < spaceNatural + glyphNatural * style.maxGlyphExtension() && glyphNatural > 0)
00595     {
00596         spaceExtension = 0;
00597         glyphExtension = (line.width - spaceNatural) / glyphNatural  - 1;
00598     }
00599     else
00600     {
00601         glyphExtension = style.maxGlyphExtension() - 1;
00602         if (spaceNatural > 0)
00603             spaceExtension = (line.width - glyphNatural * (1+glyphExtension) ) / spaceNatural  - 1;
00604         else
00605             spaceExtension = 0;
00606     }
00607     
00608     double glyphScale = 1 + glyphExtension;
00609     
00610 /*
00611     qDebug(QString("justify: line = %7 natural = %1 + %2 = %3 (%4); spaces + %5%%; min=%8; glyphs + %6%%; min=%9")
00612            .arg(spaceNatural).arg(glyphNatural).arg(spaceNatural+glyphNatural).arg(line.naturalWidth)
00613            .arg(spaceExtension).arg(glyphExtension).arg(line.width)
00614            .arg(style.minWordTracking()).arg(style.minGlyphExtension()));
00615     */
00616     
00617     // distribute whitespace on spaces and glyphs
00618     for (int yof = line.firstItem; yof <= line.lastItem; ++yof)
00619     {
00620         double wide = itemText.item(yof)->glyph.wide();
00621         if (!SpecialChars::isExpandingSpace(itemText.text(yof)))
00622         {
00623             itemText.item(yof)->glyph.last()->xadvance += wide * glyphExtension;
00624             GlyphLayout* glyph = &(itemText.item(yof)->glyph);
00625             while (glyph)
00626             {
00627                 glyph->xoffset *= glyphScale;
00628                 glyph->scaleH *= glyphScale;
00629                 glyph = glyph->more;
00630             }
00631         }
00632         else if ((itemText.item(yof)->effects() & ScStyle_SuppressSpace) == 0)
00633         {
00634             itemText.item(yof)->glyph.last()->xadvance += wide * spaceExtension;
00635         }
00636     }
00637 }
00638 
00639 
00641 static void indentLine(StoryText& itemText, LineSpec& line, double leftIndent)
00642 {
00643     if (line.naturalWidth > line.width)
00644     {
00645         justifyLine(itemText, line);
00646     }
00647     if (leftIndent > 0)
00648     {
00649         line.x += leftIndent;
00650         line.width -= leftIndent;
00651     }
00652 }
00653 
00655 static double opticalLeftMargin(const StoryText& itemText, const LineSpec& line)
00656 {
00657     int b = line.firstItem;
00658     while (b < line.lastItem && (itemText.item(b)->effects() & ScStyle_SuppressSpace))
00659            ++b;
00660     
00661     double chs = itemText.charStyle(b).fontSize() * (itemText.charStyle(b).scaleH() / 1000.0);
00662     QChar chr = itemText.text(b);
00663     double leftCorr = itemText.charStyle(b).font().realCharWidth(chr, chs / 10.0);
00664     if (QString("'´`").indexOf(chr) >= 0
00665         || chr == QChar(0x2018) // quote 6
00666         || chr == QChar(0x2019) // quote 9
00667         || chr == QChar(0x201a) // lower quote 9
00668         || chr == QChar(0x201b) // upper reversed 9 6
00669         || chr == QChar(0x2039) // single guillemet <
00670         || chr == QChar(0x203a) // single guillemet >
00671         )
00672         leftCorr *= -0.7;
00673     else if (QString("\"").indexOf(chr) >= 0
00674              || chr == QChar(0x00ab) // guillemet <<
00675              || chr == QChar(0x00bb) // guillemet >>
00676              || chr == QChar(0x201c) // quote 66
00677              || chr == QChar(0x201d) // quote 99
00678              || chr == QChar(0x201e) // lower quote 99
00679              || chr == QChar(0x201f) // upper reversed 99
00680              ) 
00681         leftCorr *= -0.5;
00682     else {
00683         leftCorr = itemText.charStyle(b).font().charWidth(QChar(' '), chs / 10.0, chr);
00684         leftCorr -= itemText.charStyle(b).font().charWidth(QChar(' '), chs / 10.0);
00685 //              double leftCorr2 = itemText.charStyle(a).font().charWidth(QChar('K'), chs / 10.0, chr);
00686 //              leftCorr2 -= itemText.charStyle(a).font().charWidth(QChar('K'), chs / 10.0);
00687 //              leftCorr = qMin(leftCorr, leftCorr2);
00688     }
00689     return leftCorr;
00690 }
00691 
00692 
00693 
00695 static double opticalRightMargin(const StoryText& itemText, const LineSpec& line)
00696 {
00697     int b = line.lastItem;
00698     while (b > line.firstItem && 
00699            (SpecialChars::isBreakingSpace(itemText.text(b)) || SpecialChars::isBreak(itemText.text(b)))
00700            )
00701         --b;
00702     if (b >= line.firstItem) 
00703     {
00704         double chs = itemText.charStyle(b).fontSize() * (itemText.charStyle(b).scaleH() / 1000.0);
00705         QChar chr = (itemText.item(b)->effects() & ScStyle_SmartHyphenVisible) ? 
00706             QChar('-') : itemText.text(b);
00707         double rightCorr = itemText.charStyle(b).font().realCharWidth(chr, chs / 10.0);
00708         if (QString("-,.`´'~").indexOf(chr) >= 0
00709             || chr == QChar(0x2018)
00710             || chr == QChar(0x2019)
00711             || chr == QChar(0x201a)
00712             || chr == QChar(0x201b)
00713             || chr == QChar(0x2039)
00714             || chr == QChar(0x203a)
00715             || chr == QChar(0x2032) // PRIME
00716             )
00717             rightCorr *= 0.7;
00718         else if (QString(";:\"").indexOf(chr) >= 0
00719                  || chr == QChar(0x00ab)
00720                  || chr == QChar(0x00bb)
00721                  || chr == QChar(0x201c)
00722                  || chr == QChar(0x201d)
00723                  || chr == QChar(0x201e)
00724                  || chr == QChar(0x201f)
00725                  || chr == QChar(0x2013) // EN DASH
00726                  || chr == QChar(0x2033) // double prime
00727                  )
00728             rightCorr *= 0.5;
00729         else {
00730             rightCorr = itemText.charStyle(b).font().charWidth(chr, chs / 10.0);
00731             rightCorr -= itemText.charStyle(b).font().charWidth(chr, chs / 10.0, QChar('.'));
00732         }
00733         return rightCorr;
00734     }
00735     return 0.0;
00736 }
00737 
00738 
00739 
00740 
00741 
00742 void PageItem_TextFrame::layout() 
00743 {
00744     
00745     if (BackBox != NULL && BackBox->invalid) {
00746 //      qDebug("textframe: len=%d, going back", itemText.length());
00747         invalid = false;
00748         PageItem_TextFrame* prevInChain = dynamic_cast<PageItem_TextFrame*>(BackBox);
00749         if (!prevInChain)
00750             qDebug(QString("layout(): backBox=%1 is no textframe!!").arg((ulong)BackBox).toLatin1().constData());
00751         else 
00752             BackBox->layout();
00753         return;
00754     }
00755     else if (!invalid && OnMasterPage.isEmpty()) {
00756 //      qDebug(QString("textframe: len=%1, invalid=%2 OnMasterPage=%3: no relayout").arg(itemText.length()).arg(invalid).arg(OnMasterPage));
00757         return;
00758     }
00759     if (invalid && BackBox == NULL)
00760         firstChar = 0;
00761     
00762 //  qDebug(QString("textframe(%1,%2): len=%3, start relayout at %4").arg(Xpos).arg(Ypos).arg(itemText.length()).arg(firstInFrame()));
00763 //  ScribusView* view = m_Doc->view();
00764     QMatrix pf2;
00765     QPoint pt1, pt2;
00766     QRegion cm;
00767     double chs, chsd = 0;
00768     double oldCurY, EndX, OFs, wide, kernVal;
00769     QString chstr;
00770     ScText *hl;
00771     ParagraphStyle style;
00772     int /*ParagraphStyle::OpticalMarginType*/ opticalMargins = ParagraphStyle::OM_None;
00773     
00774     bool outs = false;
00775     bool fBorder = false;
00776     bool goNoRoom = false;
00777     bool goNextColumn = false;
00778     
00779     TabControl tabs;
00780     tabs.active    = false;     // RTab
00781     tabs.status    = TabNONE;   // TabCode
00782     tabs.charIndex = -1;        // StartRT
00783     tabs.xPos      = 0;         // RTabX
00784 
00785     QList<ParagraphStyle::TabRecord> tTabValues;
00786     tTabValues.clear();
00787     
00788     bool DropCmode = false;
00789     bool AbsHasDrop = false;
00790     double desc=0, asce=0, maxDY=0, desc2=0, maxDX=0;
00791     int DropLines = 0;
00792     double DropCapDrop = 0;
00793     
00794     itemText.clearLines();
00795 
00796     double lineCorr = 0;
00797     if (lineColor() != CommonStrings::None)
00798         lineCorr = m_lineWidth / 2.0;
00799     
00800     // TODO: refactor this into PageItem
00801     MarginStruct extra;
00802     extra.Top = TExtra;
00803     extra.Left = Extra;
00804     extra.Right = RExtra;
00805     extra.Bottom = BExtra;
00806 
00807     LineControl current;
00808     current.init(Width, Height, extra, lineCorr);
00809     current.initColumns(columnWidth(), ColGap);
00810     current.hyphenCount = 0;
00811                  
00812     // dump styles
00813 /*  
00814     for (int i=0; i < itemText.nrOfParagraphs(); ++i) {
00815         const ParagraphStyle& pstyle(itemText.paragraphStyle(itemText.endOfParagraph(i)));
00816         qDebug(QString("par %1:").arg(i));
00817         dumpIt(pstyle);
00818     }
00819     qDebug(QString("default:"));
00820     dumpIt(itemText.defaultStyle());
00821 */  
00822     
00823 //  pf2.begin(view->viewport());
00824     pf2.translate(Xpos, Ypos);
00825     pf2.rotate(Rot);
00826     
00827     setShadow();
00828     if ((itemText.length() != 0)) // || (NextBox != 0))
00829     {
00830         // determine layout area
00831         QRegion cl = availableRegion(QRegion(pf2.map(Clip)));
00832         
00833         if (imageFlippedH())
00834         {
00835             pf2.translate(Width, 0);
00836             pf2.scale(-1, 1);
00837         }
00838         if (imageFlippedV())
00839         {
00840             pf2.translate(0, Height);
00841             pf2.scale(1, -1);
00842         }
00843         
00844         current.nextColumn(0);
00845 
00846         // find start of first line
00847         if (firstInFrame() < itemText.length())
00848         {
00849             hl = itemText.item(firstInFrame());
00850             style = itemText.paragraphStyle(firstInFrame());
00851             if (style.lineSpacingMode() == ParagraphStyle::AutomaticLineSpacing)
00852             {
00853                 style.setLineSpacing(style.charStyle().font().height(style.charStyle().fontSize() / 10.0));
00854 //              qDebug(QString("auto linespacing: %1").arg(style.lineSpacing()));
00855             }
00856 
00857 //          qDebug(QString("style @0: %1 -- %2, %4/%5 char: %3").arg(style.leftMargin()).arg(style.rightMargin())
00858 //                 .arg(style.charStyle().asString()).arg(style.name()).arg(style.parent()?style.parent()->name():""));
00859             if (style.hasDropCap())
00860             {
00861                 if (style.lineSpacingMode() == ParagraphStyle::BaselineGridLineSpacing)
00862                     chs = qRound(m_Doc->typographicSettings.valueBaseGrid  * style.dropCapLines() * 10);
00863                 else
00864                     chs = qRound(style.lineSpacing() * style.dropCapLines() * 10);
00865             }
00866             else 
00867                 chs = hl->fontSize();
00868             desc2 = -hl->font().descent(chs / 10.0);
00869             current.yPos = extra.Top + lineCorr;
00870 //          qDebug(QString("first line at y=%1").arg(current.yPos));
00871         }
00872         else // empty itemText:
00873         {
00874             desc2 = -itemText.defaultStyle().charStyle().font().descent(itemText.defaultStyle().charStyle().fontSize() / 10.0);
00875             current.yPos = itemText.defaultStyle().lineSpacing() + extra.Top+lineCorr-desc2;
00876         }
00877         current.startLine(firstInFrame());
00878         outs = false;
00879         OFs = 0;
00880         MaxChars = 0;
00881         for (int a = firstInFrame(); a < itemText.length(); ++a)
00882         {
00883             hl = itemText.item(a);
00884             if (a > 0 && itemText.text(a-1) == SpecialChars::PARSEP)
00885                 style = itemText.paragraphStyle(a);
00886             if (current.itemsInLine == 0)
00887                 opticalMargins = style.opticalMargins();
00888             
00889 //          qDebug(QString("style pos %1: %2 (%3)").arg(a).arg(style.alignment()).arg(style.parent()));
00890             const CharStyle& charStyle =  (chstr[0] != SpecialChars::PARSEP? itemText.charStyle(a) : itemText.paragraphStyle(a).charStyle());
00891             if (!charStyle.parent().isEmpty())
00892             {
00893 //              qDebug(QString("charstyle pos %1: %2 (%3 %4 %5 %6 %7 %8 %9)").arg(a).arg(charStyle.parent())
00894 //                     .arg((uint)charStyle.parentStyle()).arg(charStyle.font().scName()).arg(charStyle.fontSize())
00895 //                     .arg(charStyle.fillColor()).arg(charStyle.strokeColor()).arg(charStyle.parentStyle()->name()).arg((uint)charStyle.parentStyle()->parentStyle()));
00896             }               
00897 //          qDebug(QString("charstyle pos %1: %2 (%3)").arg(a).arg(charStyle.asString()).arg(charStyle.fontSize()));
00898 //          qDebug(QString("style @%6: %1 -- %2, %4/%5 char: %3").arg(style.leftMargin()).arg(style.rightMargin())
00899 //                 .arg(style.charStyle().asString()).arg(style.name()).arg(style.parent())
00900 //                 .arg(a));
00901 
00902             chstr = ExpandToken(a);
00903 //          qDebug(QString("expanded token: '%1'").arg(chstr));
00904             if (chstr.isEmpty())
00905                 chstr = SpecialChars::ZWNBSPACE;
00906             if (style.lineSpacingMode() == ParagraphStyle::AutomaticLineSpacing)
00907             {
00908                 style.setLineSpacing(style.charStyle().font().height(style.charStyle().fontSize() / 10.0));
00909             }
00910             // find out about par gap and dropcap
00911             if (a == firstInFrame())
00912             {
00913                 if (a == 0 || itemText.text(a-1) == SpecialChars::PARSEP)
00914                 {
00915                     current.yPos += style.gapBefore();
00916                     if (chstr[0] != SpecialChars::PARSEP)
00917                     {
00918                         DropCmode = style.hasDropCap();
00919                         if (DropCmode)
00920                             DropLines = style.dropCapLines();
00921                     }
00922                     else
00923                         DropCmode = false;
00924                 }
00925             }
00926 
00927             {  // local block for 'fl'
00928                 StyleFlag fl = hl->effects();
00929                 fl &= ~ScStyle_DropCap;
00930                 fl &= ~ScStyle_SmartHyphenVisible;
00931                 hl->setEffects(fl);
00932             }
00933 
00934             // No space at begin of line, 
00935             if (legacy)
00936             {
00937                 // unless at begin of par (eeks)
00938                 if ( (current.itemsInLine == 0) && (SpecialChars::isBreakingSpace(hl->ch))
00939                      && (a > 0 && ! SpecialChars::isBreak(itemText.text(a-1)))
00940                      && ! (a > 0 && SpecialChars::isBreakingSpace(itemText.text(a-1)) 
00941                            && (itemText.charStyle(a-1).effects() & ScStyle_SuppressSpace) != ScStyle_SuppressSpace))
00942                 {
00943                     hl->setEffects(hl->effects() | ScStyle_SuppressSpace);
00944                     hl->glyph.xadvance = 0;
00945                     continue;
00946                 }
00947                 else
00948                     hl->setEffects(hl->effects() & ~ScStyle_SuppressSpace);
00949             }
00950             else // from 134 on use NBSPACE for this effect
00951             {
00952                 if ( current.itemsInLine == 0 && SpecialChars::isBreakingSpace(hl->ch))
00953                 {
00954                     hl->setEffects(hl->effects() | ScStyle_SuppressSpace);
00955                     hl->glyph.xadvance = 0;
00956                     continue;
00957                 }
00958                 else
00959                     hl->setEffects(hl->effects() & ~ScStyle_SuppressSpace);
00960             }
00961             //          qDebug(QString("textframe(%1,%2): len=%3, fontsize=%4, ascent=%5").arg(Xpos).arg(Ypos).arg(itemText.length())
00962 //                     .arg(charStyle.fontSize()).arg(charStyle.font().ascent()));              
00963             if (current.i