Rev 22170 | Rev 22189 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
4430 | cbradney | 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 | */ |
||
3614 | cbradney | 7 | /*************************************************************************** |
18524 | avox | 8 | pageitem.cpp - description |
9 | ------------------- |
||
10 | begin : Sat Apr 7 2001 |
||
11 | copyright : (C) 2001 by Franz Schmid |
||
12 | email : Franz.Schmid@altmuehlnet.de |
||
3614 | cbradney | 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 | |||
19067 | craig | 24 | #include "pageitem_textframe.h" |
25 | |||
12269 | cbradney | 26 | #include <QDebug> |
9803 | fschmid | 27 | #include <QList> |
13951 | fschmid | 28 | #include <QTransform> |
12269 | cbradney | 29 | #include <QPalette> |
10220 | cbradney | 30 | #include <QPoint> |
12269 | cbradney | 31 | #include <QPolygon> |
10220 | cbradney | 32 | #include <QRegion> |
19067 | craig | 33 | #include <cairo.h> |
12269 | cbradney | 34 | #include <cassert> |
3614 | cbradney | 35 | |
19495 | craig | 36 | #include "actionmanager.h" |
19067 | craig | 37 | #include "appmodes.h" |
10532 | avox | 38 | #include "canvas.h" |
10220 | cbradney | 39 | #include "commonstrings.h" |
3829 | cbradney | 40 | #include "hyphenator.h" |
17826 | craig | 41 | #include "marks.h" |
42 | #include "notesstyles.h" |
||
19067 | craig | 43 | #include "numeration.h" |
3614 | cbradney | 44 | #include "pageitem.h" |
16943 | fschmid | 45 | #include "pageitem_group.h" |
17863 | jghali | 46 | #include "pageitem_noteframe.h" |
3614 | cbradney | 47 | #include "prefsmanager.h" |
19067 | craig | 48 | #include "scconfig.h" |
16736 | jghali | 49 | #include "scpage.h" |
10601 | mrdocs | 50 | #include "scpainter.h" |
3614 | cbradney | 51 | #include "scpaths.h" |
10601 | mrdocs | 52 | #include "scraction.h" |
3614 | cbradney | 53 | #include "scribus.h" |
10220 | cbradney | 54 | #include "scribusdoc.h" |
19093 | craig | 55 | #include "scribusview.h" |
3614 | cbradney | 56 | #include "scribusstructs.h" |
4145 | cbradney | 57 | #include "selection.h" |
21107 | craig | 58 | #include "text/boxes.h" |
59 | #include "text/screenpainter.h" |
||
21155 | jghali | 60 | #include "text/textshaper.h" |
21563 | jghali | 61 | #include "text/shapedtext.h" |
62 | #include "text/shapedtextfeed.h" |
||
19067 | craig | 63 | #include "ui/guidemanager.h" |
20488 | jghali | 64 | #include "ui/marksmanager.h" |
3614 | cbradney | 65 | #include "undomanager.h" |
66 | #include "undostate.h" |
||
20608 | jghali | 67 | #include "units.h" |
10601 | mrdocs | 68 | #include "util.h" |
10203 | cbradney | 69 | #include "util_math.h" |
16736 | jghali | 70 | |
21563 | jghali | 71 | |
72 | |||
3614 | cbradney | 73 | using namespace std; |
74 | |||
75 | PageItem_TextFrame::PageItem_TextFrame(ScribusDoc *pa, double x, double y, double w, double h, double w2, QString fill, QString outline) |
||
76 | : PageItem(pa, PageItem::TextFrame, x, y, w, h, w2, fill, outline) |
||
77 | { |
||
9816 | avox | 78 | invalid = true; |
79 | firstChar = 0; |
||
8931 | cbradney | 80 | cursorBiasBackward = false; |
3829 | cbradney | 81 | unicodeTextEditMode = false; |
82 | unicodeInputCount = 0; |
||
83 | unicodeInputString = ""; |
||
17988 | fschmid | 84 | m_origAnnotPos = QRectF(xPos(), yPos(), width(), height()); |
18889 | fschmid | 85 | verticalAlign = 0; |
21515 | jghali | 86 | connect(&itemText,SIGNAL(changed(int, int)), this, SLOT(slotInvalidateLayout(int, int))); |
3614 | cbradney | 87 | } |
88 | |||
12839 | fschmid | 89 | PageItem_TextFrame::PageItem_TextFrame(const PageItem & p) : PageItem(p) |
90 | { |
||
91 | invalid = true; |
||
92 | cursorBiasBackward = false; |
||
93 | unicodeTextEditMode = false; |
||
94 | unicodeInputCount = 0; |
||
95 | unicodeInputString = ""; |
||
17826 | craig | 96 | m_notesFramesMap.clear(); |
17988 | fschmid | 97 | m_origAnnotPos = QRectF(xPos(), yPos(), width(), height()); |
18889 | fschmid | 98 | verticalAlign = 0; |
21515 | jghali | 99 | connect(&itemText,SIGNAL(changed(int, int)), this, SLOT(slotInvalidateLayout(int, int))); |
12839 | fschmid | 100 | } |
5184 | avox | 101 | |
10701 | avox | 102 | static QRegion itemShape(PageItem* docItem, double xOffset, double yOffset) |
5184 | avox | 103 | { |
104 | QRegion res; |
||
13951 | fschmid | 105 | QTransform pp; |
18794 | jghali | 106 | if (docItem->isGroupChild()) |
17238 | fschmid | 107 | pp.translate(docItem->gXpos, docItem->gYpos); |
108 | else |
||
109 | pp.translate(docItem->xPos() - xOffset, docItem->yPos() - yOffset); |
||
5184 | avox | 110 | pp.rotate(docItem->rotation()); |
111 | if (docItem->textFlowUsesBoundingBox()) |
||
112 | { |
||
17038 | fschmid | 113 | QRectF bb = docItem->getVisualBoundingRect(); |
18794 | jghali | 114 | if (docItem->isGroupChild()) |
17238 | fschmid | 115 | { |
116 | bb.translate(-docItem->xPos(), -docItem->yPos()); |
||
117 | bb.translate(docItem->gXpos, docItem->gYpos); |
||
118 | } |
||
17132 | fschmid | 119 | res = QRegion(bb.toRect()); |
5184 | avox | 120 | } |
8445 | fschmid | 121 | else if ((docItem->textFlowUsesImageClipping()) && (docItem->imageClip.size() != 0)) |
5184 | avox | 122 | { |
9803 | fschmid | 123 | QList<uint> Segs; |
10284 | fschmid | 124 | QPolygon Clip2 = FlattenPath(docItem->imageClip, Segs); |
18194 | fschmid | 125 | res = QRegion(pp.map(Clip2)).intersected(QRegion(pp.map(docItem->Clip))); |
5184 | avox | 126 | } |
8445 | fschmid | 127 | else if ((docItem->textFlowUsesContourLine()) && (docItem->ContourLine.size() != 0)) |
128 | { |
||
9803 | fschmid | 129 | QList<uint> Segs; |
10284 | fschmid | 130 | QPolygon Clip2 = FlattenPath(docItem->ContourLine, Segs); |
9372 | fschmid | 131 | res = QRegion(pp.map(Clip2)); |
8445 | fschmid | 132 | } |
133 | else |
||
17038 | fschmid | 134 | { |
17061 | fschmid | 135 | if (docItem->isSymbol() || docItem->isGroup()) |
136 | { |
||
137 | if (docItem->imageFlippedH()) |
||
138 | { |
||
139 | pp.translate(docItem->width(), 0); |
||
140 | pp.scale(-1, 1); |
||
141 | } |
||
142 | if (docItem->imageFlippedV()) |
||
143 | { |
||
144 | pp.translate(0, docItem->height()); |
||
145 | pp.scale(1, -1); |
||
146 | } |
||
147 | } |
||
17039 | fschmid | 148 | if ((((docItem->lineColor() != CommonStrings::None) || (!docItem->patternStrokeVal.isEmpty()) || (docItem->GrTypeStroke > 0)) && (docItem->lineWidth() > 1)) || (!docItem->NamedLStyle.isEmpty())) |
17038 | fschmid | 149 | { |
17039 | fschmid | 150 | QVector<double> m_array; |
151 | QPainterPath ppa; |
||
152 | QPainterPath result; |
||
153 | if (docItem->itemType() == PageItem::PolyLine) |
||
154 | ppa = docItem->PoLine.toQPainterPath(false); |
||
155 | else |
||
156 | ppa = docItem->PoLine.toQPainterPath(true); |
||
157 | if (docItem->NamedLStyle.isEmpty()) |
||
17038 | fschmid | 158 | { |
159 | QPainterPathStroker stroke; |
||
17039 | fschmid | 160 | stroke.setCapStyle(docItem->lineEnd()); |
161 | stroke.setJoinStyle(docItem->lineJoin()); |
||
17038 | fschmid | 162 | stroke.setDashPattern(Qt::SolidLine); |
17039 | fschmid | 163 | stroke.setWidth(docItem->lineWidth()); |
17038 | fschmid | 164 | result = stroke.createStroke(ppa); |
165 | } |
||
17039 | fschmid | 166 | else |
167 | { |
||
168 | multiLine ml = docItem->doc()->MLineStyles[docItem->NamedLStyle]; |
||
169 | int ind = ml.size()-1; |
||
170 | if ((ml[ind].Color != CommonStrings::None) && (ml[ind].Width != 0)) |
||
171 | { |
||
172 | QPainterPathStroker stroke; |
||
173 | stroke.setCapStyle(static_cast<Qt::PenCapStyle>(ml[ind].LineEnd)); |
||
174 | stroke.setJoinStyle(static_cast<Qt::PenJoinStyle>(ml[ind].LineJoin)); |
||
175 | stroke.setDashPattern(Qt::SolidLine); |
||
176 | stroke.setWidth(ml[ind].Width); |
||
177 | result = stroke.createStroke(ppa); |
||
178 | } |
||
179 | } |
||
180 | res = QRegion(pp.map(docItem->Clip)); |
||
181 | QList<QPolygonF> pl = result.toSubpathPolygons(); |
||
182 | for (int b = 0; b < pl.count(); b++) |
||
183 | { |
||
184 | res = res.united(QRegion(pp.map(pl[b].toPolygon()))); |
||
185 | } |
||
17038 | fschmid | 186 | } |
17039 | fschmid | 187 | else |
188 | res = QRegion(pp.map(docItem->Clip)); |
||
17038 | fschmid | 189 | } |
5184 | avox | 190 | return res; |
191 | } |
||
192 | |||
17578 | craig | 193 | QRegion PageItem_TextFrame::calcAvailableRegion() |
5184 | avox | 194 | { |
16989 | jghali | 195 | QRegion result(this->Clip); |
18794 | jghali | 196 | if ((!isEmbedded) || (isGroupChild())) |
5184 | avox | 197 | { |
16989 | jghali | 198 | bool invertible(false); |
199 | QTransform canvasToLocalMat; |
||
18794 | jghali | 200 | if (isGroupChild()) |
17238 | fschmid | 201 | canvasToLocalMat.translate(gXpos, gYpos); |
202 | else |
||
18032 | craig | 203 | canvasToLocalMat.translate(m_xPos, m_yPos); |
18022 | craig | 204 | canvasToLocalMat.rotate(m_rotation); |
16989 | jghali | 205 | canvasToLocalMat = canvasToLocalMat.inverted(&invertible); |
206 | |||
207 | if (!invertible) return QRegion(); |
||
208 | |||
13875 | jghali | 209 | int LayerLev = m_Doc->layerLevelFromID(LayerID); |
21663 | jghali | 210 | int docItemsCount = m_Doc->Items->count(); |
16729 | fschmid | 211 | ScPage* Mp=0; |
212 | ScPage* Dp=0; |
||
7181 | cbradney | 213 | PageItem* docItem=0; |
214 | int LayerLevItem; |
||
16943 | fschmid | 215 | QList<PageItem*> thisList; |
5184 | avox | 216 | if (!OnMasterPage.isEmpty()) |
217 | { |
||
5559 | avox | 218 | if ((savedOwnPage == -1) || (savedOwnPage >= signed(m_Doc->Pages->count()))) |
5375 | fschmid | 219 | return result; |
7175 | fschmid | 220 | Mp = m_Doc->MasterPages.at(m_Doc->MasterNames[OnMasterPage]); |
221 | Dp = m_Doc->Pages->at(savedOwnPage); |
||
18794 | jghali | 222 | if (isGroupChild()) |
18610 | jghali | 223 | thisList = Parent->asGroupFrame()->groupItemList; |
16943 | fschmid | 224 | else |
225 | thisList = m_Doc->MasterItems; |
||
226 | int thisid = thisList.indexOf(this); |
||
9856 | fschmid | 227 | for (int a = 0; a < m_Doc->MasterItems.count(); ++a) |
5184 | avox | 228 | { |
7175 | fschmid | 229 | docItem = m_Doc->MasterItems.at(a); |
17403 | jghali | 230 | // #10642 : masterpage items interact only with items placed on same masterpage |
231 | if (docItem->OnMasterPage != OnMasterPage) |
||
232 | continue; |
||
13875 | jghali | 233 | LayerLevItem = m_Doc->layerLevelFromID(docItem->LayerID); |
21663 | jghali | 234 | if (((a > thisid) && (docItem->LayerID == LayerID)) || (LayerLevItem > LayerLev && m_Doc->layerFlow(docItem->LayerID))) |
5184 | avox | 235 | { |
5620 | jghali | 236 | if (docItem->textFlowAroundObject()) |
16989 | jghali | 237 | { |
238 | QRegion itemRgn = itemShape(docItem, Mp->xOffset() - Dp->xOffset(), Mp->yOffset() - Dp->yOffset()); |
||
239 | result = result.subtracted( canvasToLocalMat.map(itemRgn) ); |
||
240 | } |
||
5387 | avox | 241 | } |
242 | } // for all masterItems |
||
10240 | jghali | 243 | // (JG) #6009 : disable possible interaction between master text frames and normal frames |
244 | // which have the text flow option set |
||
245 | /*if (!m_Doc->masterPageMode()) |
||
5184 | avox | 246 | { |
247 | for (uint a = 0; a < docItemsCount; ++a) |
||
248 | { |
||
7175 | fschmid | 249 | docItem = m_Doc->Items->at(a); |
250 | Mp = m_Doc->MasterPages.at(m_Doc->MasterNames[OnMasterPage]); |
||
251 | Dp = m_Doc->Pages->at(OwnPage); |
||
5620 | jghali | 252 | if ((docItem->textFlowAroundObject()) && (docItem->OwnPage == OwnPage)) |
5184 | avox | 253 | { |
254 | result = result.subtract(itemShape(docItem, m_Doc->view(), Mp->xOffset() - Dp->xOffset(), Mp->yOffset() - Dp->yOffset())); |
||
255 | } |
||
256 | } // for all docItems |
||
10240 | jghali | 257 | } // if (! masterPageMode) */ |
5387 | avox | 258 | } // if (!OnMasterPage.isEmpty()) |
5184 | avox | 259 | else |
260 | { |
||
16943 | fschmid | 261 | int thisid = 0; |
18794 | jghali | 262 | if (isGroupChild()) |
17238 | fschmid | 263 | { |
17107 | fschmid | 264 | thisid = Parent->asGroupFrame()->groupItemList.indexOf(this); |
17238 | fschmid | 265 | docItemsCount = Parent->asGroupFrame()->groupItemList.count(); |
21663 | jghali | 266 | for (int a = thisid + 1; a < docItemsCount; ++a) |
17238 | fschmid | 267 | { |
268 | docItem = Parent->asGroupFrame()->groupItemList.at(a); |
||
21663 | jghali | 269 | if (docItem->textFlowAroundObject()) |
17238 | fschmid | 270 | { |
21663 | jghali | 271 | QRegion itemRgn = itemShape(docItem, 0, 0); |
272 | result = result.subtracted( canvasToLocalMat.map(itemRgn) ); |
||
17238 | fschmid | 273 | } |
274 | } |
||
275 | } |
||
16943 | fschmid | 276 | else |
17238 | fschmid | 277 | { |
16943 | fschmid | 278 | thisid = m_Doc->Items->indexOf(this); |
21663 | jghali | 279 | for (int a = 0; a < docItemsCount; ++a) |
5184 | avox | 280 | { |
17238 | fschmid | 281 | docItem = m_Doc->Items->at(a); |
282 | LayerLevItem = m_Doc->layerLevelFromID(docItem->LayerID); |
||
21663 | jghali | 283 | if (((a > thisid) && (docItem->LayerID == LayerID)) || (LayerLevItem > LayerLev && m_Doc->layerFlow(docItem->LayerID))) |
16989 | jghali | 284 | { |
17238 | fschmid | 285 | if (docItem->textFlowAroundObject()) |
286 | { |
||
287 | QRegion itemRgn = itemShape(docItem, 0, 0); |
||
288 | result = result.subtracted( canvasToLocalMat.map(itemRgn) ); |
||
289 | } |
||
16989 | jghali | 290 | } |
5387 | avox | 291 | } |
292 | } // for all docItems |
||
18524 | avox | 293 | } // if(OnMasterPage.isEmpty() |
5184 | avox | 294 | } // if(!Embedded) |
19983 | craig | 295 | //else |
296 | // qDebug() << "QRegion empty"; |
||
5184 | avox | 297 | return result; |
298 | } |
||
299 | |||
6144 | avox | 300 | void PageItem_TextFrame::setShadow() |
301 | { |
||
302 | if (OnMasterPage.isEmpty()) |
||
303 | return; |
||
18524 | avox | 304 | |
6144 | avox | 305 | QString newShadow = m_Doc->masterPageMode() ? OnMasterPage : QString::number(OwnPage); |
20691 | craig | 306 | if (newShadow != m_currentShadow) { |
307 | if (m_currentShadow == OnMasterPage) { |
||
6366 | avox | 308 | // masterpage was edited, clear all shadows |
20691 | craig | 309 | m_shadows.clear(); |
6366 | avox | 310 | } |
20691 | craig | 311 | if (!m_shadows.contains(newShadow)) { |
312 | if (!m_shadows.contains(OnMasterPage)) { |
||
313 | m_shadows[OnMasterPage] = itemText; |
||
7905 | avox | 314 | // const ParagraphStyle& pstyle(shadows[OnMasterPage].paragraphStyle(0)); |
13085 | jghali | 315 | // qDebug() << QString("Pageitem_Textframe: style of master: %1 align=%2").arg(pstyle.parent()).arg(pstyle.alignment()); |
316 | // qDebug() << QString("Pageitem_Textframe: shadow itemText->%1").arg(OnMasterPage); |
||
6144 | avox | 317 | } |
7887 | avox | 318 | if (newShadow != OnMasterPage) { |
20691 | craig | 319 | m_shadows[newShadow] = m_shadows[OnMasterPage].copy(); |
7887 | avox | 320 | // const ParagraphStyle& pstyle(shadows[newShadow].paragraphStyle(0)); |
13085 | jghali | 321 | // qDebug() << QString("Pageitem_Textframe: style of shadow copy: %1 align=%2").arg(pstyle.parent()).arg(pstyle.alignment()); |
7887 | avox | 322 | } |
13085 | jghali | 323 | // qDebug() << QString("Pageitem_Textframe: shadow %1<-%2").arg(newShadow).arg(OnMasterPage); |
6144 | avox | 324 | } |
20691 | craig | 325 | itemText = m_shadows[newShadow]; |
7887 | avox | 326 | // const ParagraphStyle& pstyle(itemText.paragraphStyle(0)); |
13085 | jghali | 327 | // qDebug() << QString("Pageitem_Textframe: style of shadow: %1 align=%2").arg(pstyle.parent()).arg(pstyle.alignment()); |
6144 | avox | 328 | invalid = true; |
20691 | craig | 329 | m_currentShadow = newShadow; |
6144 | avox | 330 | } |
331 | } |
||
17790 | fschmid | 332 | /* |
17740 | jghali | 333 | static void debugLineLayout(const StoryText& itemText, const LineSpec& line) |
334 | { |
||
335 | QFile debugFile(QDir::homePath() + "/Desktop/debug_line.csv"); |
||
336 | debugFile.open(QIODevice::WriteOnly); |
||
337 | |||
338 | QTextStream stream(&debugFile); |
||
339 | stream.setRealNumberNotation(QTextStream::FixedNotation); |
||
340 | stream.setRealNumberPrecision(7); |
||
341 | |||
342 | stream << "xoffset" << "\t"; |
||
343 | stream << "yoffset" << "\t"; |
||
344 | stream << "xadvance" << "\t"; |
||
345 | stream << "yadvance" << "\t"; |
||
346 | stream << "scaleH" << "\t"; |
||
347 | stream << "scaleV" << "\t"; |
||
348 | stream << "\n"; |
||
349 | |||
21107 | craig | 350 | for (int zc = line.firstChar; zc < line.lastChar; ++zc) |
17740 | jghali | 351 | { |
352 | const ScText* item = itemText.item(zc); |
||
353 | |||
354 | stream << item->glyph.xoffset << "\t"; |
||
355 | stream << item->glyph.yoffset << "\t"; |
||
356 | stream << item->glyph.xadvance << "\t"; |
||
357 | stream << item->glyph.yadvance << "\t"; |
||
358 | stream << item->glyph.scaleH << "\t"; |
||
359 | stream << item->glyph.scaleV << "\t"; |
||
360 | stream << "\n"; |
||
361 | } |
||
362 | |||
363 | debugFile.close(); |
||
364 | } |
||
17826 | craig | 365 | |
5778 | avox | 366 | static void dumpIt(const ParagraphStyle& pstyle, QString indent = QString("->")) |
367 | { |
||
10553 | fschmid | 368 | QString db = QString("%6%1/%2 @ %3: %4--%5 linespa%6: %7 align%8") |
5778 | avox | 369 | .arg(pstyle.name()) |
6733 | avox | 370 | .arg(pstyle.parent()) |
5850 | tsoots | 371 | .arg( (unsigned long int) &pstyle) |
5778 | avox | 372 | .arg(pstyle.leftMargin()) |
373 | .arg(pstyle.rightMargin()) |
||
6805 | avox | 374 | .arg(indent) |
375 | .arg(pstyle.lineSpacingMode()) |
||
376 | .arg(pstyle.lineSpacing()) |
||
10553 | fschmid | 377 | .arg(pstyle.alignment()); |
13085 | jghali | 378 | qDebug() << db; |
5778 | avox | 379 | static QString more(" "); |
6733 | avox | 380 | if (pstyle.hasParent()) |
381 | dumpIt(*dynamic_cast<const ParagraphStyle*>(pstyle.parentStyle()), more + indent); |
||
5778 | avox | 382 | } |
17826 | craig | 383 | */ |
8103 | avox | 384 | static const bool legacy = true; |
385 | |||
10553 | fschmid | 386 | /* |
18524 | avox | 387 | static void layoutDropCap(GlyphLayout layout, double curX, double curY, double offsetX, double offsetY, double dropCapDrop) |
388 | { |
||
6370 | avox | 389 | } |
10553 | fschmid | 390 | */ |
6370 | avox | 391 | |
21563 | jghali | 392 | |
7905 | avox | 393 | enum TabStatus { |
394 | TabNONE = 0, |
||
395 | TabLEFT = TabNONE, |
||
396 | TabRIGHT = 1, |
||
397 | TabPOINT = 2, |
||
398 | TabCOMMA = 3, |
||
399 | TabCENTER = 4 |
||
7313 | avox | 400 | }; |
6370 | avox | 401 | |
8761 | avox | 402 | |
403 | /** |
||
404 | fields which describe what type of tab is currently active |
||
405 | */ |
||
7313 | avox | 406 | struct TabControl { |
21107 | craig | 407 | bool active; |
408 | int status; |
||
409 | double xPos; |
||
410 | QChar fillChar; |
||
411 | GlyphLayout* tabGlyph; |
||
7313 | avox | 412 | }; |
413 | |||
21715 | craig | 414 | class LineSpec |
21107 | craig | 415 | { |
21715 | craig | 416 | public: |
417 | LineSpec() |
||
418 | { |
||
419 | x = 0.0; |
||
420 | y = 0.0; |
||
21972 | craig | 421 | width = 0.0; |
422 | height = 0.0; |
||
423 | ascent = 0.0; |
||
424 | descent = 0.0; |
||
425 | colLeft = 0.0; |
||
21715 | craig | 426 | firstCluster = 0; |
427 | lastCluster = 0; |
||
428 | naturalWidth = 0.0; |
||
21972 | craig | 429 | isFirstLine = false; |
21715 | craig | 430 | } |
431 | |||
21107 | craig | 432 | qreal x; |
433 | qreal y; |
||
434 | qreal width; |
||
21972 | craig | 435 | qreal height; |
21107 | craig | 436 | qreal ascent; |
437 | qreal descent; |
||
438 | qreal colLeft; |
||
439 | |||
21563 | jghali | 440 | int firstCluster; |
441 | int lastCluster; |
||
21107 | craig | 442 | qreal naturalWidth; |
443 | bool isFirstLine; |
||
444 | }; |
||
445 | |||
8761 | avox | 446 | /** |
447 | fields which describe how the current line is placed into the frame |
||
21107 | craig | 448 | */ |
7313 | avox | 449 | struct LineControl { |
21715 | craig | 450 | LineSpec lineData; |
21563 | jghali | 451 | QList<GlyphCluster> glyphs; |
21107 | craig | 452 | bool isEmpty; |
7313 | avox | 453 | int hyphenCount; |
454 | double colWidth; |
||
8095 | avox | 455 | double colGap; |
7313 | avox | 456 | double colLeft; |
457 | double colRight; |
||
458 | int column; |
||
459 | bool startOfCol; |
||
15029 | jghali | 460 | bool hasDropCap; |
16868 | jghali | 461 | bool afterOverflow; |
462 | bool addLine; |
||
463 | bool recalculateY; |
||
464 | bool lastInRowLine; |
||
465 | bool addLeftIndent; |
||
466 | bool wasFirstInRow; |
||
467 | double leftIndent; |
||
468 | double rightMargin; |
||
469 | double mustLineEnd; |
||
21107 | craig | 470 | int restartIndex; //index of glyph run where line computing should be restarted |
471 | int restartRowIndex; //index of glyph run where row of text is started |
||
16868 | jghali | 472 | double restartX; //starting X position of line if must be restarted |
473 | double rowDesc; |
||
474 | |||
7313 | avox | 475 | double xPos; |
476 | double yPos; |
||
477 | int breakIndex; |
||
478 | double breakXPos; |
||
9047 | avox | 479 | |
480 | double maxShrink; |
||
481 | double maxStretch; |
||
21107 | craig | 482 | ScribusDoc *doc; |
21563 | jghali | 483 | ITextContext* context; |
18524 | avox | 484 | |
8761 | avox | 485 | /// remember frame dimensions and offsets |
21563 | jghali | 486 | LineControl(double w, double h, const MarginStruct& extra, double lCorr, ScribusDoc* d, ITextContext* ctx, double colwidth, double colgap) |
487 | : hasDropCap(false) |
||
488 | , doc(d), context(ctx) |
||
8095 | avox | 489 | { |
490 | insets = extra; |
||
491 | lineCorr = lCorr; |
||
492 | frameWidth = w; |
||
493 | frameHeight = h; |
||
494 | column = 0; |
||
21107 | craig | 495 | colWidth = colwidth; |
496 | colGap = colgap; |
||
497 | hyphenCount = 0; |
||
21119 | craig | 498 | isEmpty = true; |
499 | colLeft = insets.left() + lineCorr; |
||
500 | colRight = colLeft + colWidth; |
||
501 | startOfCol = true; |
||
502 | afterOverflow = false; |
||
503 | addLine = false; |
||
504 | recalculateY = false; |
||
505 | lastInRowLine = false; |
||
506 | addLeftIndent = false; |
||
507 | wasFirstInRow = false; |
||
508 | leftIndent = 0.0; |
||
509 | rightMargin = 0.0; |
||
510 | mustLineEnd = false; |
||
511 | restartIndex = 0; |
||
512 | restartRowIndex = 0; |
||
513 | restartX = 0.0; |
||
514 | rowDesc = 0.0; |
||
515 | xPos = 0.0; |
||
516 | yPos = 0.0; |
||
517 | breakIndex = -1; |
||
518 | breakXPos = 0.0; |
||
519 | maxShrink = 0.0; |
||
21971 | craig | 520 | maxStretch = 0.0; |
8095 | avox | 521 | } |
18524 | avox | 522 | |
8761 | avox | 523 | /// move position to next column |
21107 | craig | 524 | void nextColumn(TextLayout &textLayout) |
8095 | avox | 525 | { |
526 | startOfCol = true; |
||
21563 | jghali | 527 | if (textLayout.story()->defaultStyle().direction() == ParagraphStyle::RTL) |
528 | colLeft = textLayout.frame()->width() - insets.right() - ((colWidth * (column + 1)) + (colGap * column)); |
||
529 | else |
||
530 | colLeft = (colWidth + colGap) * column + insets.left() + lineCorr; |
||
21107 | craig | 531 | textLayout.addColumn(colLeft, colWidth); |
16868 | jghali | 532 | //now colRight is REAL column right edge |
8095 | avox | 533 | colRight = colLeft + colWidth; |
8103 | avox | 534 | if (legacy) |
16868 | jghali | 535 | colRight += lineCorr; |
8095 | avox | 536 | xPos = colLeft; |
21107 | craig | 537 | yPos = insets.top() + lineCorr; |
21715 | craig | 538 | lineData.colLeft = colLeft; |
8095 | avox | 539 | } |
18524 | avox | 540 | |
8095 | avox | 541 | bool isEndOfCol(double morespace = 0) |
542 | { |
||
19831 | craig | 543 | return yPos + morespace + insets.bottom() + lineCorr > frameHeight; |
8095 | avox | 544 | } |
16868 | jghali | 545 | |
8761 | avox | 546 | /** |
547 | init fields for a new line at current position |
||
548 | */ |
||
21563 | jghali | 549 | void startLine(int firstCluster) |
15029 | jghali | 550 | { |
21563 | jghali | 551 | glyphs.clear(); |
21107 | craig | 552 | isEmpty = true; |
21715 | craig | 553 | lineData.x = xPos; |
554 | lineData.y = yPos; |
||
555 | lineData.firstCluster = firstCluster; |
||
556 | lineData.lastCluster = 0; |
||
557 | lineData.ascent = 0.0; |
||
558 | lineData.descent = 0.0; |
||
559 | lineData.width = 0.0; |
||
560 | lineData.height = 0.0; |
||
561 | lineData.naturalWidth = 0.0; |
||
562 | lineData.colLeft = colLeft; |
||
8095 | avox | 563 | breakIndex = -1; |
16868 | jghali | 564 | breakXPos = 0.0; |
565 | maxShrink = 0.0; |
||
566 | maxStretch = 0.0; |
||
567 | leftIndent = 0.0; |
||
568 | rightMargin = 0.0; |
||
569 | rowDesc = 0.0; |
||
8095 | avox | 570 | } |
9047 | avox | 571 | |
18524 | avox | 572 | |
9047 | avox | 573 | /// called when glyphs are placed on the line |
574 | void rememberShrinkStretch(QChar ch, double wide, const ParagraphStyle& style) |
||
575 | { |
||
576 | if (SpecialChars::isExpandingSpace(ch)) |
||
577 | maxShrink += (1 - style.minWordTracking()) * wide; |
||
578 | else |
||
579 | { |
||
580 | maxShrink += (1 - style.minGlyphExtension()) * wide; |
||
581 | } |
||
582 | maxStretch += (style.maxGlyphExtension() - 1) * wide; |
||
12883 | jghali | 583 | } |
9047 | avox | 584 | |
8761 | avox | 585 | /// called when a possible break is passed |
16868 | jghali | 586 | void rememberBreak(int index, double pos, double morespace = 0) |
8095 | avox | 587 | { |
16868 | jghali | 588 | if (pos > colRight - morespace) |
9047 | avox | 589 | { |
590 | // only look for the first break behind the right edge |
||
16868 | jghali | 591 | //maxShrink = 0; |
18524 | avox | 592 | |
9047 | avox | 593 | // check if we already have a better break |
594 | if (breakIndex >= 0) |
||
595 | { |
||
596 | double oldLooseness = qAbs(colRight - breakXPos); |
||
597 | |||
598 | double newLooseness = pos - colRight; |
||
599 | if (newLooseness >= oldLooseness) |
||
600 | return; |
||
601 | } |
||
602 | } |
||
16868 | jghali | 603 | breakXPos = pos; |
8095 | avox | 604 | breakIndex = index; |
605 | } |
||
21107 | craig | 606 | |
8761 | avox | 607 | /// called when a mandatory break is found |
21563 | jghali | 608 | void breakLine(int last) |
8095 | avox | 609 | { |
610 | breakIndex = last; |
||
21715 | craig | 611 | breakXPos = lineData.x; |
21563 | jghali | 612 | |
21715 | craig | 613 | for (int i = 0; i <= breakIndex - lineData.firstCluster; i++) |
21563 | jghali | 614 | breakXPos += glyphs.at(i).width(); |
14554 | jghali | 615 | // #8194, #8717 : update line ascent and descent with sensible values |
616 | // so that endOfLine() returns correct result |
||
21107 | craig | 617 | updateHeightMetrics(); |
15029 | jghali | 618 | // #9060 : update line offset too |
17303 | fschmid | 619 | // updateLineOffset(itemText, style, offsetPolicy); |
8095 | avox | 620 | } |
16868 | jghali | 621 | |
8761 | avox | 622 | /// use the last remembered break to set line width and itemrange |
8095 | avox | 623 | void finishLine(double endX) |
624 | { |
||
21715 | craig | 625 | lineData.lastCluster = breakIndex; |
626 | lineData.naturalWidth = breakXPos - lineData.x; |
||
627 | lineData.width = endX - lineData.x; |
||
9047 | avox | 628 | maxShrink = maxStretch = 0; |
8095 | avox | 629 | } |
16868 | jghali | 630 | |
631 | int restartRow(bool recalcY) |
||
632 | { |
||
633 | if (recalcY) |
||
634 | yPos++; |
||
635 | recalculateY = recalcY; |
||
636 | xPos = restartX = colLeft; |
||
637 | startLine(restartRowIndex); |
||
638 | addLeftIndent = true; |
||
639 | afterOverflow = false; |
||
21107 | craig | 640 | return restartRowIndex - 1; |
16868 | jghali | 641 | } |
642 | |||
643 | int restartLine(bool recalcY, bool add) |
||
644 | { |
||
645 | recalculateY = recalcY; |
||
646 | addLine = add; |
||
647 | xPos = restartX; |
||
648 | startLine(restartIndex); |
||
649 | afterOverflow = false; |
||
21107 | craig | 650 | return restartIndex - 1; |
16868 | jghali | 651 | } |
18524 | avox | 652 | |
8095 | avox | 653 | bool isEndOfLine(double moreSpace = 0) |
654 | { |
||
16868 | jghali | 655 | bool res; |
8103 | avox | 656 | if (legacy) |
16868 | jghali | 657 | res = ceil(xPos + lineCorr - maxShrink) + ceil(moreSpace) >= floor(colRight); |
8103 | avox | 658 | else |
16868 | jghali | 659 | res = ceil(xPos - maxShrink) + ceil(moreSpace) >= floor(colRight); |
660 | return res; |
||
8095 | avox | 661 | } |
18524 | avox | 662 | |
16923 | jghali | 663 | /// Keep old endOfLine code for reference |
664 | /*double endOfLine_old(const QRegion& shape, const QTransform& pf2, double morespace, int Yasc, int Ydesc) |
||
8095 | avox | 665 | { |
13402 | jghali | 666 | // if we aren't restricted further, we'll end at this maxX: |
667 | double maxX = colRight - morespace; |
||
16868 | jghali | 668 | if (legacy) maxX -= lineCorr; |
13402 | jghali | 669 | |
16868 | jghali | 670 | double StartX = floor(qMax(line.x, qMin(maxX,breakXPos-maxShrink-1))-1); |
671 | int xPos = static_cast<int>(ceil(maxX)); |
||
13402 | jghali | 672 | |
16921 | jghali | 673 | QPoint pt12 (xPos, Yasc); |
674 | QPoint pt22 (xPos, Ydesc); |
||
675 | QRect pt(pt12,pt22); |
||
18524 | avox | 676 | QRegion region; |
13402 | jghali | 677 | |
678 | double EndX2 = StartX; |
||
16868 | jghali | 679 | double Interval = 0.25; |
13402 | jghali | 680 | do { |
16868 | jghali | 681 | int xP = static_cast<int>(ceil(EndX2 + morespace)); |
682 | pt.moveTopLeft(QPoint(xP, Yasc)); |
||
16921 | jghali | 683 | region = QRegion(pf2.mapToPolygon(pt)).subtracted(shape); |
684 | if (!region.isEmpty()) |
||
16868 | jghali | 685 | break; |
13402 | jghali | 686 | EndX2 += Interval; |
16921 | jghali | 687 | } while ((EndX2 < maxX) && region.isEmpty()); |
13402 | jghali | 688 | |
16868 | jghali | 689 | return qMin(EndX2, maxX); |
16923 | jghali | 690 | }*/ |
691 | |||
692 | /// find x position where this line must end |
||
16989 | jghali | 693 | double endOfLine(const QRegion& shape, double morespace, int yAsc, int yDesc) |
16923 | jghali | 694 | { |
695 | // if we aren't restricted further, we'll end at this maxX: |
||
696 | double maxX = colRight - morespace; |
||
697 | if (legacy) maxX -= lineCorr; |
||
698 | |||
21715 | craig | 699 | double StartX = floor(qMax(lineData.x, qMin(maxX, breakXPos-maxShrink-1))-1); |
20144 | jghali | 700 | StartX = qMax(0.0, StartX); |
701 | |||
16923 | jghali | 702 | int xPos = static_cast<int>(ceil(maxX)); |
703 | QPoint pt12 (xPos, yAsc); |
||
704 | QPoint pt22 (xPos, yDesc); |
||
705 | |||
706 | QPolygon p; |
||
16989 | jghali | 707 | p.append (QPoint (StartX, yAsc)); |
708 | p.append (QPoint (StartX, yDesc)); |
||
709 | p.append (pt12); |
||
710 | p.append (pt22); |
||
16923 | jghali | 711 | // check if something gets in the way |
712 | QRegion lineI = shape.intersected (p.boundingRect()); |
||
713 | // if the intersection only has 1 rectangle, then nothing gets in the way |
||
18194 | fschmid | 714 | if (lineI.rectCount() == 1) |
16923 | jghali | 715 | { |
716 | int cPos = static_cast<int>(ceil(StartX + morespace)); |
||
717 | QRect cRect (QPoint(cPos, yAsc), QPoint(cPos, yDesc)); |
||
18524 | avox | 718 | QRegion qr2 = QRegion(cRect).subtracted(shape); |
719 | if (qr2.isEmpty()) // qr2 == 0 <=> cRect subset of shape |
||
16923 | jghali | 720 | { |
721 | QRect rect = lineI.rects().at(0); |
||
18524 | avox | 722 | double mx = qMax(rect.left(), rect.right()) /*- pf2.dx()*/; |
16989 | jghali | 723 | int steps = static_cast<int>((mx - StartX - morespace - 2) / 0.25); |
16923 | jghali | 724 | if (steps > 0) |
725 | { |
||
726 | StartX += steps * 0.25; |
||
727 | } |
||
728 | } |
||
729 | } |
||
730 | |||
731 | QRect pt(pt12, pt22); |
||
732 | |||
733 | double EndX2 = StartX; |
||
734 | double Interval = 0.25; |
||
735 | do { |
||
736 | int xP = static_cast<int>(ceil(EndX2 + morespace)); |
||
737 | pt.moveTopLeft(QPoint(xP, yAsc)); |
||
17044 | jghali | 738 | if (!regionContainsRect(shape, pt)) |
16923 | jghali | 739 | break; |
740 | EndX2 += Interval; |
||
17044 | jghali | 741 | } while ((EndX2 < maxX) && regionContainsRect(shape, pt)); |
16923 | jghali | 742 | |
743 | /*double oldEndX2 = endOfLine_old(shape, pf2, morespace, yAsc, yDesc); |
||
744 | if (oldEndX2 != qMin(EndX2, maxX)) |
||
745 | { |
||
746 | qDebug() << "Different EndX : " << oldEndX2 << " (old) " << EndX2 << " (new) "; |
||
747 | }*/ |
||
748 | |||
749 | return qMin(EndX2, maxX); |
||
8095 | avox | 750 | } |
18524 | avox | 751 | |
21107 | craig | 752 | void updateHeightMetrics() |
8956 | avox | 753 | { |
21715 | craig | 754 | lineData.ascent = lineData.descent = 0; |
21563 | jghali | 755 | if (glyphs.isEmpty()) |
21107 | craig | 756 | return; |
21563 | jghali | 757 | const CharStyle& cStyle(glyphs.at(0).style()); |
758 | double scaleV = cStyle.scaleV() / 1000.0; |
||
759 | double offset = (cStyle.fontSize() / 10) * (cStyle.baselineOffset() / 1000.0); |
||
21715 | craig | 760 | lineData.ascent = cStyle.font().ascent(cStyle.fontSize()/10.00) * scaleV + offset; |
761 | lineData.descent = cStyle.font().descent(cStyle.fontSize()/10.00) * scaleV - offset; |
||
8956 | avox | 762 | } |
14554 | jghali | 763 | |
16868 | jghali | 764 | // yPos should not be changed when all line is already calculated - at new y position there can be overflow!!! |
17303 | fschmid | 765 | // edit: can't happen as it should only move upwards, and this is covered by the calculations done. |
766 | //void updateLineOffset(const StoryText& itemText, const ParagraphStyle& style, FirstLineOffsetPolicy offsetPolicy) |
||
767 | //{ |
||
21107 | craig | 768 | // if (charsInLine <= 0) |
17303 | fschmid | 769 | // return; |
770 | // if ((!hasDropCap) && (startOfCol) && (style.lineSpacingMode() != ParagraphStyle::BaselineGridLineSpacing)) |
||
16868 | jghali | 771 | // { |
17303 | fschmid | 772 | // //FIXME: use glyphs, not chars |
21107 | craig | 773 | // double firstasce = itemText.charStyle(line.firstChar).font().ascent(itemText.charStyle(line.firstChar).fontSize() / 10.0); |
17303 | fschmid | 774 | // double adj (0.0); |
775 | // double currasce (this->getLineAscent(itemText)); |
||
19927 | jghali | 776 | // if (offsetPolicy == FLOPRealGlyphHeight) |
16868 | jghali | 777 | // { |
17303 | fschmid | 778 | // adj = firstasce - currasce; |
16868 | jghali | 779 | // } |
19927 | jghali | 780 | // else if (offsetPolicy == FLOPFontAscent) |
16868 | jghali | 781 | // { |
17303 | fschmid | 782 | // adj = 0.0; |
16868 | jghali | 783 | // } |
19927 | jghali | 784 | // else if (offsetPolicy == FLOPLineSpacing) |
17303 | fschmid | 785 | // { |
786 | // adj = firstasce - style.lineSpacing(); |
||
787 | // } |
||
788 | // line.ascent = currasce; |
||
789 | // line.y -= adj; |
||
790 | // yPos -= adj; |
||
16868 | jghali | 791 | // } |
17303 | fschmid | 792 | // else if ((!startOfCol) && (style.lineSpacingMode() == ParagraphStyle::AutomaticLineSpacing)) |
793 | // { |
||
21107 | craig | 794 | // QChar ch = itemText.text(line.firstChar); |
17303 | fschmid | 795 | // double firstasce = style.lineSpacing(); |
796 | // double currasce = getLineHeight(itemText); |
||
797 | // double adj = firstasce - currasce; |
||
21107 | craig | 798 | // qDebug() << QString("move2 line %1.. down by %2").arg(current.line.firstChar).arg(-adj); |
17303 | fschmid | 799 | // line.ascent = currasce; |
800 | // line.y -= adj; |
||
801 | // yPos -= adj; |
||
802 | // } |
||
803 | //} |
||
15029 | jghali | 804 | |
21563 | jghali | 805 | /// called when line length is known and line is to be justified |
806 | void justifyLine(const ParagraphStyle& style) |
||
21107 | craig | 807 | { |
808 | |||
21563 | jghali | 809 | double glyphNatural = 0; |
810 | double spaceNatural = 0; |
||
811 | double glyphExtension; |
||
812 | double spaceExtension; |
||
813 | int spaceInsertion = 0; |
||
814 | double imSpace = -1; |
||
815 | int trackingInsertion = 0; |
||
816 | double trackingAmount = 0; |
||
817 | |||
21715 | craig | 818 | int glyphsCount = lineData.lastCluster - lineData.firstCluster + 1; |
21563 | jghali | 819 | |
820 | for (int i = 0; i < glyphsCount; ++i) |
||
21107 | craig | 821 | { |
21661 | jghali | 822 | const GlyphCluster& glyphCluster = glyphs.at(i); |
21563 | jghali | 823 | if (!glyphCluster.hasFlag(ScLayout_ExpandingSpace)) |
824 | { |
||
825 | glyphNatural += glyphCluster.width(); |
||
826 | } |
||
827 | else if (!glyphCluster.hasFlag(ScLayout_SuppressSpace)) |
||
828 | { |
||
829 | spaceNatural += glyphCluster.width(); |
||
830 | if (imSpace < 0.0 || imSpace > glyphCluster.width()) |
||
831 | imSpace = glyphCluster.width(); |
||
832 | } |
||
833 | if (i != 0 && glyphCluster.hasFlag(ScLayout_ImplicitSpace)) |
||
834 | { |
||
835 | spaceInsertion += 1; |
||
836 | } |
||
837 | if (i != glyphsCount && glyphCluster.hasFlag(ScLayout_JustificationTracking)) |
||
838 | { |
||
839 | trackingInsertion += 1; |
||
840 | } |
||
21107 | craig | 841 | } |
842 | |||
21563 | jghali | 843 | imSpace /= 2; |
21107 | craig | 844 | |
21563 | jghali | 845 | // decision: prio 1: stretch glyph; prio 2: insert spaces; prio 3: stretch spaces |
846 | |||
21715 | craig | 847 | if (lineData.width < spaceNatural + glyphNatural * style.minGlyphExtension() && spaceNatural > 0) |
21107 | craig | 848 | { |
21563 | jghali | 849 | glyphExtension = style.minGlyphExtension() - 1; |
21715 | craig | 850 | spaceExtension = (lineData.width - glyphNatural * (1+glyphExtension) ) / spaceNatural - 1; |
21563 | jghali | 851 | imSpace = 0; |
21107 | craig | 852 | } |
21715 | craig | 853 | else if (lineData.width < spaceNatural + glyphNatural * style.maxGlyphExtension() && glyphNatural > 0) |
21563 | jghali | 854 | { |
855 | spaceExtension = 0; |
||
21715 | craig | 856 | glyphExtension = (lineData.width - spaceNatural) / glyphNatural - 1; |
21563 | jghali | 857 | imSpace = 0; |
858 | } |
||
21107 | craig | 859 | else |
860 | { |
||
21563 | jghali | 861 | glyphExtension = style.maxGlyphExtension() - 1; |
862 | if (spaceInsertion) { |
||
21715 | craig | 863 | double remaining = lineData.width - glyphNatural * (1 + glyphExtension) - spaceNatural; |
21563 | jghali | 864 | if (imSpace > 0) { |
865 | if (remaining / spaceInsertion < imSpace) { |
||
866 | imSpace = remaining / spaceInsertion; |
||
867 | spaceExtension = 0; |
||
868 | } else { |
||
869 | spaceExtension = (remaining + spaceNatural) / (spaceNatural + spaceInsertion * imSpace) - 1; |
||
870 | imSpace *= spaceExtension + 1; |
||
871 | } |
||
872 | } else { |
||
873 | imSpace = remaining / spaceInsertion; |
||
874 | spaceExtension = 0; |
||
875 | } |
||
876 | } else { |
||
877 | if (spaceNatural > 0) |
||
21715 | craig | 878 | spaceExtension = (lineData.width - glyphNatural * (1 + glyphExtension)) / spaceNatural - 1; |
21563 | jghali | 879 | else |
880 | spaceExtension = 0; |
||
881 | |||
882 | if (trackingInsertion && (spaceExtension == 0.0 || spaceExtension >= 20.0)) |
||
883 | { |
||
884 | if (spaceExtension == 0.0) |
||
885 | { |
||
21715 | craig | 886 | double remaining = lineData.width - glyphNatural * (1 + glyphExtension) - spaceNatural; |
21563 | jghali | 887 | trackingAmount = remaining / trackingInsertion; |
888 | } |
||
889 | else |
||
890 | { |
||
891 | spaceExtension = qMin(20.0, spaceExtension); |
||
21715 | craig | 892 | double remaining = lineData.width - glyphNatural * (1 + glyphExtension) - spaceNatural; |
21563 | jghali | 893 | remaining -= spaceExtension * spaceNatural; |
894 | trackingAmount = remaining / trackingInsertion; |
||
895 | if (trackingAmount > imSpace) |
||
896 | { |
||
897 | trackingAmount = imSpace; |
||
21715 | craig | 898 | spaceExtension = (lineData.width - glyphNatural * (1 + glyphExtension) - trackingInsertion * trackingAmount) / spaceNatural - 1; |
21563 | jghali | 899 | } |
900 | } |
||
901 | } |
||
902 | } |
||
21107 | craig | 903 | } |
904 | |||
21563 | jghali | 905 | double glyphScale = 1 + glyphExtension; |
21811 | jghali | 906 | double naturalWidth = 0; |
7313 | avox | 907 | |
21563 | jghali | 908 | /* |
909 | qDebug() << QString("justify: line = %7 natural = %1 + %2 = %3 (%4); spaces + %5%%; min=%8; glyphs + %6%%; min=%9") |
||
910 | .arg(spaceNatural).arg(glyphNatural).arg(spaceNatural+glyphNatural).arg(line.naturalWidth) |
||
911 | .arg(spaceExtension).arg(glyphExtension).arg(line.width) |
||
912 | .arg(style.minWordTracking()).arg(style.minGlyphExtension()); |
||
913 | */ |
||
914 | |||
915 | int startItem = 0; |
||
916 | if (glyphs[startItem].hasFlag(ScLayout_DropCap)) |
||
21811 | jghali | 917 | { |
21563 | jghali | 918 | startItem++; |
21811 | jghali | 919 | naturalWidth += glyphs[startItem].width(); |
920 | } |
||
21563 | jghali | 921 | // distribute whitespace on spaces and glyphs |
922 | for (int i = startItem; i < glyphsCount; ++i) |
||
21107 | craig | 923 | { |
21563 | jghali | 924 | GlyphCluster& glyphCluster = glyphs[i]; |
925 | double wide = glyphCluster.width(); |
||
926 | if (!glyphCluster.hasFlag(ScLayout_ExpandingSpace)) |
||
21107 | craig | 927 | { |
21563 | jghali | 928 | glyphCluster.setScaleH(glyphCluster.scaleH() * glyphScale); |
929 | glyphCluster.xoffset *= glyphScale; |
||
21107 | craig | 930 | } |
21563 | jghali | 931 | else if (!glyphCluster.hasFlag(ScLayout_SuppressSpace)) |
932 | { |
||
933 | glyphCluster.extraWidth += (wide * spaceExtension); |
||
934 | } |
||
935 | if (i != 0 && glyphCluster.hasFlag(ScLayout_ImplicitSpace)) |
||
936 | { |
||
937 | GlyphCluster& lastRun = glyphs[i - 1]; |
||
938 | lastRun.extraWidth += imSpace; |
||
939 | } |
||
940 | if (i != glyphsCount && trackingAmount != 0 && glyphCluster.hasFlag(ScLayout_JustificationTracking)) |
||
941 | { |
||
942 | glyphCluster.extraWidth += trackingAmount; |
||
943 | } |
||
21811 | jghali | 944 | naturalWidth += glyphCluster.width(); |
21107 | craig | 945 | } |
21811 | jghali | 946 | lineData.naturalWidth = naturalWidth; |
21661 | jghali | 947 | |
948 | if ((style.alignment() == ParagraphStyle::Extended) && |
||
21811 | jghali | 949 | (style.direction() == ParagraphStyle::RTL)) |
21661 | jghali | 950 | { |
21811 | jghali | 951 | double offset = lineData.width - lineData.naturalWidth; |
952 | if (offset > 1e-6) |
||
21661 | jghali | 953 | indentLine(style, offset); |
954 | } |
||
21107 | craig | 955 | } |
956 | |||
21563 | jghali | 957 | /// called when linelength is known and line is not justified |
958 | void indentLine(const ParagraphStyle& style, double leftIndent) |
||
8095 | avox | 959 | { |
21715 | craig | 960 | if (lineData.naturalWidth > lineData.width) |
9047 | avox | 961 | { |
21563 | jghali | 962 | justifyLine(style); |
9047 | avox | 963 | } |
21563 | jghali | 964 | if (leftIndent > 0) |
9047 | avox | 965 | { |
21715 | craig | 966 | lineData.x += leftIndent; |
967 | lineData.width -= leftIndent; |
||
9047 | avox | 968 | } |
8095 | avox | 969 | } |
9047 | avox | 970 | |
21563 | jghali | 971 | /** |
972 | Clones the tab fill char as often as necssary after all distances are known |
||
973 | */ |
||
974 | void fillInTabLeaders() |
||
9047 | avox | 975 | { |
21563 | jghali | 976 | // fill in tab leaders |
977 | for (int i = 0; i < glyphs.count(); ++i) |
||
978 | { |
||
979 | GlyphCluster& glyphCluster = glyphs[i]; |
||
980 | if (glyphCluster.hasFlag(ScLayout_TabLeaders)) |
||
981 | { |
||
21684 | jghali | 982 | const CharStyle& charStyle(glyphCluster.style()); |
21563 | jghali | 983 | GlyphLayout tglyph = glyphCluster.glyphs().last(); |
984 | double width = glyphCluster.width(); |
||
985 | double wt = charStyle.font().glyphWidth(tglyph.glyph, charStyle.fontSize() * tglyph.scaleV / 10.0); |
||
986 | int count = static_cast<int>(width / wt); |
||
21684 | jghali | 987 | if (count > 0) |
988 | glyphCluster.glyphs().clear(); |
||
21563 | jghali | 989 | for(int cx = 0; cx < count; ++cx) |
990 | { |
||
991 | GlyphLayout more = tglyph; |
||
992 | more.xadvance = 0; |
||
993 | if (cx != 0) |
||
994 | more.xoffset = (width / count) * cx; |
||
995 | glyphCluster.append(more); |
||
16876 | jghali | 996 | } |
21684 | jghali | 997 | glyphCluster.glyphs().last().xadvance = width / glyphCluster.scaleH(); |
16876 | jghali | 998 | } |
999 | } |
||
9047 | avox | 1000 | } |
18524 | avox | 1001 | |
1002 | |||
21563 | jghali | 1003 | //defined but not used |
1004 | ///// calculate how much the first char should stick out to the left |
||
1005 | //double opticalLeftMargin(const StoryText& itemText) |
||
1006 | //{ |
||
1007 | // int b = line.firstChar; |
||
1008 | // while (b < line.lastChar && (itemText.flags(b) & ScLayout_SuppressSpace)) |
||
1009 | // ++b; |
||
1010 | // |
||
1011 | // double chs = itemText.charStyle(b).fontSize() * (itemText.charStyle(b).scaleH() / 1000.0); |
||
1012 | // QChar chr = itemText.text(b); |
||
1013 | // double leftCorr = itemText.charStyle(b).font().realCharWidth(chr, chs / 10.0); |
||
1014 | // if (QString("'´`").indexOf(chr) >= 0 |
||
1015 | // || chr == QChar(0x2018) // quote 6 |
||
1016 | // || chr == QChar(0x2019) // quote 9 |
||
1017 | // || chr == QChar(0x201a) // lower quote 9 |
||
1018 | // || chr == QChar(0x201b) // upper reversed 9 6 |
||
1019 | // || chr == QChar(0x2039) // single guillemet < |
||
1020 | // || chr == QChar(0x203a) // single guillemet > |
||
1021 | // ) |
||
1022 | // leftCorr *= -0.7; |
||
1023 | // else if (QString("\"").indexOf(chr) >= 0 |
||
1024 | // || chr == QChar(0x00ab) // guillemet << |
||
1025 | // || chr == QChar(0x00bb) // guillemet >> |
||
1026 | // || chr == QChar(0x201c) // quote 66 |
||
1027 | // || chr == QChar(0x201d) // quote 99 |
||
1028 | // || chr == QChar(0x201e) // lower quote 99 |
||
1029 | // || chr == QChar(0x201f) // upper reversed 99 |
||
1030 | // ) |
||
1031 | // leftCorr *= -0.5; |
||
1032 | // else { |
||
1033 | // leftCorr = itemText.charStyle(b).font().charWidth(QChar(' '), chs / 10.0, chr); |
||
1034 | // leftCorr -= itemText.charStyle(b).font().charWidth(QChar(' '), chs / 10.0); |
||
1035 | //// double leftCorr2 = itemText.charStyle(a).font().charWidth(QChar('K'), chs / 10.0, chr); |
||
1036 | //// leftCorr2 -= itemText.charStyle(a).font().charWidth(QChar('K'), chs / 10.0); |
||
1037 | //// leftCorr = qMin(leftCorr, leftCorr2); |
||
1038 | // } |
||
1039 | // return leftCorr; |
||
1040 | //} |
||
18524 | avox | 1041 | |
21563 | jghali | 1042 | /// calculate how much the last char should stick out to the right |
1043 | double opticalRightMargin(const StoryText& itemText) |
||
8095 | avox | 1044 | { |
21715 | craig | 1045 | int b = lineData.lastCluster - lineData.firstCluster; |
21563 | jghali | 1046 | while (b > 0 && |
1047 | (SpecialChars::isBreakingSpace(itemText.text(glyphs[b].lastChar())) || |
||
1048 | SpecialChars::isBreak(itemText.text(glyphs[b].lastChar()))) |
||
1049 | ) |
||
1050 | --b; |
||
1051 | if (b >= 0) |
||
21107 | craig | 1052 | { |
21563 | jghali | 1053 | const CharStyle& style = glyphs[b].style(); |
1054 | const ScFace& font = style.font(); |
||
1055 | double chs = style.fontSize() * (style.scaleH() / 1000.0); |
||
1056 | QChar chr = itemText.text(glyphs[b].lastChar()); |
||
1057 | double rightCorr; |
||
1058 | if (glyphs[b].hasFlag(ScLayout_SoftHyphenVisible)) |
||
1059 | rightCorr = font.hyphenWidth(style, chs / 10.0); |
||
1060 | else |
||
1061 | rightCorr = font.glyphBBox(font.char2CMap(chr.unicode()), chs / 10.0).width; |
||
1062 | if (glyphs[b].hasFlag(ScLayout_SoftHyphenVisible) |
||
1063 | || QString("-,.`´'~").indexOf(chr) >= 0 |
||
1064 | || chr == QChar(0x2010) |
||
1065 | || chr == QChar(0x2018) |
||
1066 | || chr == QChar(0x2019) |
||
1067 | || chr == QChar(0x201a) |
||
1068 | || chr == QChar(0x201b) |
||
1069 | || chr == QChar(0x2039) |
||
1070 | || chr == QChar(0x203a) |
||
1071 | || chr == QChar(0x2032) // PRIME |
||
1072 | ) |
||
1073 | rightCorr *= 0.7; |
||
1074 | else if (QString(";:\"").indexOf(chr) >= 0 |
||
1075 | || chr == QChar(0x00ab) |
||
1076 | || chr == QChar(0x00bb) |
||
1077 | || chr == QChar(0x201c) |
||
1078 | || chr == QChar(0x201d) |
||
1079 | || chr == QChar(0x201e) |
||
1080 | || chr == QChar(0x201f) |
||
1081 | || chr == QChar(0x2013) // EN DASH |
||
1082 | || chr == QChar(0x2033) // double prime |
||
1083 | ) |
||
1084 | rightCorr *= 0.5; |
||
1085 | else { |
||
1086 | #if 0 |
||
1087 | // FIXME HOST: is the kerning with "." a realy reliable way to check this? |
||
1088 | rightCorr = chStyle.font().realCharWidth(chr, chs / 10.0); |
||
1089 | rightCorr -= chStyle.font().charWidth(chr, chs / 10.0, QChar('.')); |
||
1090 | #else |
||
1091 | rightCorr = 0; |
||
1092 | #endif |
||
9047 | avox | 1093 | } |
21563 | jghali | 1094 | return rightCorr; |
8095 | avox | 1095 | } |
21563 | jghali | 1096 | return 0.0; |
1097 | } |
||
1098 | |||
1099 | LineBox* createLineBox() |
||
1100 | { |
||
1101 | LineBox* result = new LineBox(); |
||
21715 | craig | 1102 | result->moveTo(lineData.x - colLeft, lineData.y - lineData.ascent); |
1103 | result->setWidth(lineData.width); |
||
1104 | result->setAscent(lineData.ascent); |
||
1105 | result->setDescent(lineData.descent); |
||
1106 | foreach (const GlyphCluster& run, ShapedTextFeed::putInVisualOrder(glyphs, 0, lineData.lastCluster - lineData.firstCluster + 1)) |
||
9047 | avox | 1107 | { |
21563 | jghali | 1108 | addBox(result, run); |
1109 | // qDebug() << "cluster" << run.firstChar() << ".." << run.lastChar() << "@" << run.visualIndex(); |
||
9047 | avox | 1110 | } |
21563 | jghali | 1111 | return result; |
1112 | } |
||
1113 | |||
1114 | void addBox(LineBox *lineBox, const GlyphCluster& run) |
||
1115 | { |
||
1116 | Box* result; |
||
1117 | if (run.object().getPageItem(doc)) |
||
21135 | jghali | 1118 | { |
21563 | jghali | 1119 | result = new ObjectBox(run, context); |
1120 | QRectF bBox = context->getVisualBoundingBox(run.object()); |
||
1121 | if (run.hasFlag(ScLayout_DropCap)) |
||
1122 | result->setAscent(bBox.height() * run.scaleV() - run.yoffset); |
||
1123 | else |
||
1124 | result->setAscent(bBox.height()); |
||
1125 | result->setDescent(0); |
||
21135 | jghali | 1126 | } |
21563 | jghali | 1127 | else |
1128 | { |
||
1129 | result = new GlyphBox(run); |
||
1130 | result->setAscent(lineBox->ascent()); |
||
1131 | result->setDescent(lineBox->descent()); |
||
1132 | } |
||
1133 | lineBox->addBox(result); |
||
8095 | avox | 1134 | } |
1135 | |||
21563 | jghali | 1136 | private: |
1137 | double frameWidth; |
||
1138 | double frameHeight; |
||
1139 | MarginStruct insets; |
||
1140 | double lineCorr; |
||
1141 | }; |
||
8095 | avox | 1142 | |
21563 | jghali | 1143 | static bool allowedCJKBreakAfter(QChar ch) { |
1144 | unsigned int code[] = {0x201C, 0x300C, 0xFF08, 0xFF3B, 0xFF5B, 0xFF5F, 0xFF62, 0xFF0D, 0}; |
||
1145 | for (int i = 0; code[i]; ++i) |
||
1146 | if (code[i] == ch.unicode()) |
||
1147 | return false; |
||
1148 | return true; |
||
8095 | avox | 1149 | } |
1150 | |||
21563 | jghali | 1151 | static int allowedCJKBreakBefore(QChar ch) { |
1152 | unsigned int code[] = |
||
1153 | {0x201D, 0x3001, 0x3002, 0x300D, 0xFF01, 0xFF09, 0xFF0C, 0xFF0E, 0xFF1A, |
||
1154 | 0xFF1B, 0xFF1F, 0xFF3D, 0xFF5D, 0xFF60, 0xFF63, 0xFF64, 0}; |
||
1155 | for (int i = 0; code[i]; ++i) |
||
1156 | if (code[i] == ch.unicode()) |
||
1157 | return false; |
||
1158 | return true; |
||
1159 | } |
||
8095 | avox | 1160 | |
21563 | jghali | 1161 | static bool implicitBreak(QChar f, QChar s) { |
1162 | if (SpecialChars::isCJK(f.unicode()) && SpecialChars::isCJK(s.unicode())) { |
||
1163 | return allowedCJKBreakAfter(f) && allowedCJKBreakBefore(s); |
||
1164 | } else |
||
1165 | return false; |
||
8095 | avox | 1166 | } |
1167 | |||
16989 | jghali | 1168 | static double findRealOverflowEnd(const QRegion& shape, QRect pt, double maxX) |
16868 | jghali | 1169 | { |
17044 | jghali | 1170 | while (!regionContainsRect(shape, pt) && pt.right() < maxX) |
16868 | jghali | 1171 | pt.translate(1, 0); |
1172 | if (pt.right() >= maxX) |
||
1173 | return maxX; |
||
16989 | jghali | 1174 | return pt.left() + 0.5; |
16868 | jghali | 1175 | } |
1176 | |||
16840 | jghali | 1177 | static double adjustToBaselineGrid (const LineControl &control, PageItem *item, int OwnPage) |
1178 | { |
||
1179 | double by = item->yPos(); |
||
1180 | if (OwnPage != -1) |
||
1181 | by = by - item->doc()->Pages->at(OwnPage)->yOffset(); |
||
1182 | int ol1 = qRound((by + control.yPos - item->doc()->guidesPrefs().offsetBaselineGrid) * 10000.0); |
||
1183 | int ol2 = static_cast<int>(ol1 / item->doc()->guidesPrefs().valueBaselineGrid); |
||
1184 | // qDebug() << QString("baseline adjust: y=%1->%2").arg(current.yPos).arg(ceil( ol2 / 10000.0 ) * item->doc()->typographicSettings.valueBaselineGrid + item->doc()->typographicSettings.offsetBaselineGrid - by); |
||
8095 | avox | 1185 | |
16840 | jghali | 1186 | return ceil( ol2 / 10000.0 ) * item->doc()->guidesPrefs().valueBaselineGrid + item->doc()->guidesPrefs().offsetBaselineGrid - by; |
1187 | } |
||
8761 | avox | 1188 | |
16840 | jghali | 1189 | static double nextAutoTab (const LineControl ¤t, PageItem *item) |
1190 | { |
||
1191 | double dtw = item->doc()->itemToolPrefs().textTabWidth; |
||
1192 | if (current.xPos == current.colLeft) |
||
1193 | return current.colLeft + dtw; |
||
1194 | double res = current.colLeft + ceil ((current.xPos - current.colLeft) / dtw) * dtw; |
||
1195 | if (res == current.xPos) res += dtw; |
||
1196 | return res; |
||
1197 | } |
||
8761 | avox | 1198 | |
17826 | craig | 1199 | //cezaryece: I remove static statement as this function is used also by PageItem_NoteFrame |
1200 | double calculateLineSpacing (const ParagraphStyle &style, PageItem *item) |
||
16840 | jghali | 1201 | { |
1202 | if (style.lineSpacingMode() == ParagraphStyle::AutomaticLineSpacing) |
||
17223 | jghali | 1203 | { |
1204 | double autoLS = static_cast<double>(item->doc()->typographicPrefs().autoLineSpacing) / 100.0; |
||
1205 | return (style.charStyle().font().height(style.charStyle().fontSize() / 10.0) * autoLS); |
||
1206 | } |
||
16840 | jghali | 1207 | if (style.lineSpacingMode() == ParagraphStyle::BaselineGridLineSpacing) |
1208 | return item->doc()->guidesPrefs().valueBaselineGrid; |
||
1209 | return style.lineSpacing(); |
||
1210 | } |
||
1211 | |||
1212 | |||
1213 | // This assumes that layout() ran on the previous page and set the incomplete* vars |
||
1214 | // It also clears the incomplete* vars, and changes the starting position for this frame |
||
1215 | // The incomplete* vars are used to ensure that we don't run into an endless loop |
||
1216 | // This setup won't cause any problems, as the only way for the starting position to change |
||
1217 | // back is if the previous frame's layout() runs again, but if it does, it also sets the |
||
1218 | // incomplete* vars for us |
||
1219 | bool PageItem_TextFrame::moveLinesFromPreviousFrame () |
||
1220 | { |
||
1221 | PageItem_TextFrame* prev = dynamic_cast<PageItem_TextFrame*>(BackBox); |
||
1222 | if (!prev) return false; |
||
1223 | if (!prev->incompleteLines) return false; // no incomplete lines - nothing to do |
||
18987 | avox | 1224 | int pos = textLayout.endOfFrame()-1; |
16840 | jghali | 1225 | QChar lastChar = itemText.text (pos); |
1226 | // qDebug()<<"pos is"<<pos<<", length is"<<itemText.length()<<", incomplete is "<<prev->incompleteLines; |
||
1227 | if ((pos != itemText.length()-1) && (!SpecialChars::isBreak (lastChar, true))) |
||
1228 | return false; // the paragraph isn't ending yet |
||
18987 | avox | 1229 | int lines = textLayout.lines(); // lines added to the current frame |
16840 | jghali | 1230 | |
1231 | ParagraphStyle style = itemText.paragraphStyle (pos); |
||
1232 | int need = style.keepLinesEnd () + 1; |
||
1233 | int prevneed = style.keepLinesStart () + 1; |
||
1234 | if (lines >= need) { |
||
1235 | prev->incompleteLines = 0; // so that further paragraphs don't pull anything |
||
1236 | return false; // we have enough lines |
||
1237 | } |
||
1238 | |||
1239 | // too few lines - we need to pull some from the previous page |
||
17039 | fschmid | 1240 | int pull = need - lines; |
16840 | jghali | 1241 | // if pulling the lines would lead to the original frame having too few, pull the whole paragraph |
1242 | if (prev->incompleteLines - pull < prevneed) |
||
1243 | pull = prev->incompleteLines; |
||
1244 | // qDebug()<<"pulling"<<pull<<"lines; |
||
1245 | // Okay, move the starting/ending character |
||
1246 | int startingPos = prev->incompletePositions[prev->incompleteLines - pull]; |
||
17039 | fschmid | 1247 | for (int i = 0; i < pull; ++i) |
18987 | avox | 1248 | prev->textLayout.removeLastLine(); |
16840 | jghali | 1249 | firstChar = prev->MaxChars = startingPos; |
1250 | // keep the remaining incomplete lines flagged as such |
||
1251 | // this ensures that if pulling one line won't be enough, the subsequent call to layout() will pull more |
||
1252 | prev->incompleteLines -= pull; |
||
1253 | |||
1254 | return true; |
||
1255 | } |
||
1256 | |||
1257 | // called at the end of a frame or column |
||
1258 | void PageItem_TextFrame::adjustParagraphEndings () |
||
1259 | { |
||
1260 | // More text to go - let's apply paragraph flowing options - orphans/widows, etc |
||
18987 | avox | 1261 | int pos = textLayout.endOfFrame() - 1; |
16840 | jghali | 1262 | if (pos >= itemText.length() - 1) return; |
1263 | |||
1264 | ParagraphStyle style = itemText.paragraphStyle (pos); |
||
1265 | int paragraphStart = itemText.prevParagraph (pos) + 1; |
||
1266 | QChar lastChar = itemText.text (pos); |
||
1267 | bool keepWithNext = style.keepWithNext() && (lastChar == SpecialChars::PARSEP); |
||
1268 | if (keepWithNext || (!SpecialChars::isBreak (lastChar, true))) { |
||
1269 | // paragraph continues in the next frame, or needs to be kept with the next one |
||
1270 | // check how many lines are in this frame |
||
18987 | avox | 1271 | int lineStart = textLayout.startOfLine (pos); |
16840 | jghali | 1272 | incompleteLines = 1; |
1273 | incompletePositions.prepend (lineStart); |
||
1274 | while (lineStart > paragraphStart) { |
||
18987 | avox | 1275 | lineStart = textLayout.startOfLine (lineStart - 1); |
16840 | jghali | 1276 | incompleteLines++; |
1277 | incompletePositions.prepend (lineStart); |
||
1278 | } |
||
1279 | int need = style.keepLinesStart () + 1; |
||
1280 | if (style.keepTogether()) need = incompleteLines; |
||
1281 | int pull = 0; |
||
1282 | if (style.keepTogether() || (incompleteLines < need)) pull = incompleteLines; |
||
1283 | // if we need to keep it with the next one, pull one line. Next frame layouting |
||
1284 | // will pull more from us if it proves necessary. |
||
1285 | if (keepWithNext && (!pull)) pull = 1; |
||
1286 | |||
1287 | if (pull) { |
||
22108 | jghali | 1288 | qDebug() << "pulling" << pull << "lines"; |
16840 | jghali | 1289 | // push this paragraph to the next frame |
1290 | for (int i = 0; i < pull; ++i) |
||
18987 | avox | 1291 | textLayout.removeLastLine(); |
21197 | jghali | 1292 | MaxChars = incompletePositions[incompleteLines-pull]; |
16840 | jghali | 1293 | incompleteLines = 0; |
1294 | incompletePositions.clear(); |
||
1295 | } |
||
1296 | } |
||
1297 | } |
||
22112 | jghali | 1298 | |
18524 | avox | 1299 | void PageItem_TextFrame::layout() |
3614 | cbradney | 1300 | { |
21563 | jghali | 1301 | // qDebug()<<"==Layout==" << itemName() ; |
13034 | pierre | 1302 | // printBacktrace(24); |
21440 | jghali | 1303 | if (BackBox != NULL) { |
5732 | avox | 1304 | // qDebug("textframe: len=%d, going back", itemText.length()); |
21440 | jghali | 1305 | PageItem_TextFrame* firstInvalid = NULL; |
1306 | PageItem_TextFrame* prevInChain = dynamic_cast<PageItem_TextFrame*>(BackBox); |
||
1307 | while (prevInChain) |
||
16543 | jghali | 1308 | { |
21440 | jghali | 1309 | if (prevInChain->invalid) |
1310 | firstInvalid = prevInChain; |
||
16543 | jghali | 1311 | prevInChain = dynamic_cast<PageItem_TextFrame*>(prevInChain->BackBox); |
1312 | } |
||
21440 | jghali | 1313 | PageItem_TextFrame* nextInChain = firstInvalid; |
1314 | while (nextInChain && (nextInChain != this)) |
||
1315 | { |
||
1316 | nextInChain->layout(); |
||
1317 | nextInChain = dynamic_cast<PageItem_TextFrame*>(nextInChain->NextBox); |
||
1318 | } |
||
16106 | jghali | 1319 | // #9592 : warning, BackBox->layout() may not layout BackBox next box |
1320 | if (!invalid) |
||
1321 | return; |
||
5362 | avox | 1322 | } |
6370 | avox | 1323 | else if (!invalid && OnMasterPage.isEmpty()) { |
13085 | jghali | 1324 | // qDebug() << QString("textframe: len=%1, invalid=%2 OnMasterPage=%3: no relayout").arg(itemText.length()).arg(invalid).arg(OnMasterPage); |
5732 | avox | 1325 | return; |
1326 | } |
||
8334 | avox | 1327 | if (invalid && BackBox == NULL) |
1328 | firstChar = 0; |
||
18524 | avox | 1329 | |
22108 | jghali | 1330 | // qDebug() << QString("textframe(%1,%2): len=%3, start relayout at %4").arg(m_xPos).arg(m_yPos).arg(itemText.length()).arg(firstInFrame()); |
5362 | avox | 1331 | QPoint pt1, pt2; |
16868 | jghali | 1332 | QRect pt; |
7969 | avox | 1333 | double chs, chsd = 0; |
21107 | craig | 1334 | double EndX, OFs; |
6805 | avox | 1335 | ParagraphStyle style; |
17742 | jghali | 1336 | int opticalMargins = ParagraphStyle::OM_None; |
18524 | avox | 1337 | |
5362 | avox | 1338 | bool outs = false; |
1339 | bool goNoRoom = false; |
||
1340 | bool goNextColumn = false; |
||
18524 | avox | 1341 | |
7905 | avox | 1342 | TabControl tabs; |
1343 | tabs.active = false; // RTab |
||
1344 | tabs.status = TabNONE; // TabCode |
||
21107 | craig | 1345 | tabs.tabGlyph = 0; // was int charIndex ~ StartRT |
7905 | avox | 1346 | tabs.xPos = 0; // RTabX |
1347 | |||
9803 | fschmid | 1348 | QList<ParagraphStyle::TabRecord> tTabValues; |
7905 | avox | 1349 | tTabValues.clear(); |
18524 | avox | 1350 | |
18047 | craig | 1351 | bool BulNumMode = false; //when bullet or counter should be inserted |
17760 | jghali | 1352 | bool DropCmode = false, FlopBaseline = false; |
17599 | craig | 1353 | double desc=0, asce=0, realAsce=0, realDesc = 0, offset = 0; |
16868 | jghali | 1354 | double maxDY=0, maxDX=0; |
6370 | avox | 1355 | double DropCapDrop = 0; |
11776 | jghali | 1356 | int DropLines = 0; |
1357 | int DropLinesCount = 0; |
||
18524 | avox | 1358 | |
18987 | avox | 1359 | textLayout.clear(); |
16840 | jghali | 1360 | incompleteLines = 0; |
1361 | incompletePositions.clear(); |
||
8095 | avox | 1362 | |
1363 | double lineCorr = 0; |
||
1364 | if (lineColor() != CommonStrings::None) |
||
1365 | lineCorr = m_lineWidth / 2.0; |
||
18524 | avox | 1366 | |
16868 | jghali | 1367 | //hold Y position of last computed line of text (with glyphs descent) |
1368 | //for moving next line if glyphs are higher than that |
||
1369 | double lastLineY = 0; |
||
18524 | avox | 1370 | |
17826 | craig | 1371 | QMap<int, Mark*> noteMarksPosMap; //maping notes marks and its position in text |
1372 | |||
5778 | avox | 1373 | // dump styles |
18524 | avox | 1374 | /* |
5778 | avox | 1375 | for (int i=0; i < itemText.nrOfParagraphs(); ++i) { |
1376 | const ParagraphStyle& pstyle(itemText.paragraphStyle(itemText.endOfParagraph(i))); |
||
13085 | jghali | 1377 | qDebug("par %d:", i); |
5778 | avox | 1378 | dumpIt(pstyle); |
1379 | } |
||
13085 | jghali | 1380 | qDebug() << "default:"; |
5778 | avox | 1381 | dumpIt(itemText.defaultStyle()); |
16989 | jghali | 1382 | */ |
18524 | avox | 1383 | |
6144 | avox | 1384 | setShadow(); |
17826 | craig | 1385 | int itLen = itemText.length(); |
1386 | //fast validate empty frames |
||
1387 | if (itLen == 0 || firstInFrame() == itLen) |
||
8095 | avox | 1388 | { |
21495 | jghali | 1389 | PageItem_TextFrame * next = this; |
17826 | craig | 1390 | while (next != NULL) |
1391 | { |
||
1392 | next->invalid = false; |
||
21495 | jghali | 1393 | next->firstChar = itLen; |
1394 | next->MaxChars = itLen; |
||
1395 | next->textLayout.clear(); |
||
1396 | next = dynamic_cast<PageItem_TextFrame*>(next->nextInChain()); |
||
17826 | craig | 1397 | } |
21195 | fschmid | 1398 | // TODO layout() shouldn't delete any frame here, as it breaks any loop |
1399 | // over the doc->Items or doc->MasterItems lists when done with indexes |
||
1400 | // see Bug #12685 for an example |
||
17826 | craig | 1401 | if (!isNoteFrame() && m_Doc->notesChanged() && !m_notesFramesMap.isEmpty()) |
1402 | { //if notes are used |
||
1403 | UndoManager::instance()->setUndoEnabled(false); |
||
1404 | QList<PageItem_NoteFrame*> delList; |
||
1405 | foreach (PageItem_NoteFrame* nF, m_notesFramesMap.keys()) |
||
1406 | { |
||
1407 | if (nF->notesList().isEmpty() && !nF->isAutoNoteFrame()) |
||
1408 | delList.append(nF); |
||
1409 | } |
||
1410 | while (!delList.isEmpty()) |
||
1411 | m_Doc->delNoteFrame(delList.takeFirst(), false); |
||
1412 | UndoManager::instance()->setUndoEnabled(true); |
||
1413 | } |
||
1414 | return; |
||
1415 | } |
||
1416 | |||
1417 | if ((itLen != 0)) // || (NextBox != 0)) |
||
1418 | { |
||
5408 | avox | 1419 | // determine layout area |
17578 | craig | 1420 | m_availableRegion = calcAvailableRegion(); |
1421 | if (m_availableRegion.isEmpty()) |
||
14384 | jghali | 1422 | { |
1423 | MaxChars = firstInFrame(); |
||
1424 | goto NoRoom; |
||
1425 | } |
||
18524 | avox | 1426 | |
16989 | jghali | 1427 | if (imageFlippedH() || imageFlippedV()) |
5362 | avox | 1428 | { |
16989 | jghali | 1429 | QTransform matrix; |
1430 | if (imageFlippedH()) |
||
1431 | { |
||
18095 | craig | 1432 | matrix.translate(m_width, 0); |
16989 | jghali | 1433 | matrix.scale(-1, 1); |
1434 | } |
||
1435 | if (imageFlippedV()) |
||
1436 | { |
||
18095 | craig | 1437 | matrix.translate(0, m_height); |
16989 | jghali | 1438 | matrix.scale(1, -1); |
1439 | } |
||
17578 | craig | 1440 | m_availableRegion = matrix.map(m_availableRegion); |
5362 | avox | 1441 | } |
18524 | avox | 1442 | |
21563 | jghali | 1443 | ITextContext* context = this; |
1444 | //TextShaper textShaper(this, itemText, firstInFrame()); |
||
1445 | ShapedTextFeed shapedText(&itemText, firstInFrame(), context); |
||
1446 | |||
1447 | QList<GlyphCluster> glyphClusters; // = textShaper.shape(); |
||
1448 | // std::sort(glyphClusters.begin(), glyphClusters.end(), logicalGlyphRunComp); |
||
21107 | craig | 1449 | |
21563 | jghali | 1450 | LineControl current(m_width, m_height, m_textDistanceMargins, lineCorr, m_Doc, context,columnWidth(), ColGap); |
21107 | craig | 1451 | current.nextColumn(textLayout); |
1452 | |||
19831 | craig | 1453 | lastLineY = m_textDistanceMargins.top(); |
8095 | avox | 1454 | |
17223 | jghali | 1455 | //automatic line spacing factor (calculated once) |
21563 | jghali | 1456 | double autoLS = static_cast<double>(context->typographicPrefs().autoLineSpacing) / 100.0; |
17223 | jghali | 1457 | |
5559 | avox | 1458 | // find start of first line |
17826 | craig | 1459 | if (firstInFrame() < itLen) |
5362 | avox | 1460 | { |
21563 | jghali | 1461 | const CharStyle& cstyle = itemText.charStyle(firstInFrame()); |
7887 | avox | 1462 | style = itemText.paragraphStyle(firstInFrame()); |
16840 | jghali | 1463 | style.setLineSpacing (calculateLineSpacing (style, this)); |
8956 | avox | 1464 | |
13085 | jghali | 1465 | // qDebug() << QString("style @0: %1 -- %2, %4/%5 char: %3").arg(style.leftMargin()).arg(style.rightMargin()) |
1466 | // .arg(style.charStyle().asString()).arg(style.name()).arg(style.parent()?style.parent()->name():""); |
||
6805 | avox | 1467 | if (style.hasDropCap()) |
3614 | cbradney | 1468 | { |
16840 | jghali | 1469 | chs = calculateLineSpacing (style, this) * style.dropCapLines() * 10; |
3614 | cbradney | 1470 | } |
18524 | avox | 1471 | else |
1472 | chs = cstyle.fontSize(); |
||
1473 | desc = -cstyle.font().descent(chs / 10.0); |
||
19831 | craig | 1474 | current.yPos = m_textDistanceMargins.top() + lineCorr; |
13085 | jghali | 1475 | // qDebug() << QString("first line at y=%1").arg(current.yPos); |
21563 | jghali | 1476 | |
5362 | avox | 1477 | } |
6370 | avox | 1478 | else // empty itemText: |
5362 | avox | 1479 | { |
16868 | jghali | 1480 | desc = -itemText.defaultStyle().charStyle().font().descent(itemText.defaultStyle().charStyle().fontSize() / 10.0); |
19831 | craig | 1481 | current.yPos = itemText.defaultStyle().lineSpacing() + m_textDistanceMargins.top() + lineCorr - desc; |
5362 | avox | 1482 | } |
22170 | jghali | 1483 | // update Bullet & number list if any. |
1484 | updateBulletsNum(); |
||
21563 | jghali | 1485 | |
21107 | craig | 1486 | current.startLine(0); |
16868 | jghali | 1487 | |
5362 | avox | 1488 | outs = false; |
1489 | OFs = 0; |
||
1490 | MaxChars = 0; |
||
16868 | jghali | 1491 | double realEnd = 0; |
21107 | craig | 1492 | current.restartIndex = current.restartRowIndex = 0; |
16868 | jghali | 1493 | current.afterOverflow = false; |
1494 | current.addLine = false; |
||
1495 | current.recalculateY = true; |
||
1496 | current.lastInRowLine = false; |
||
1497 | current.addLeftIndent = true; |
||
1498 | current.wasFirstInRow = false; |
||
1499 | current.leftIndent = 0; |
||
1500 | current.rightMargin = 0.0; |
||
1501 | current.mustLineEnd = current.colRight; |
||
1502 | current.restartX = 0; |
||
17826 | craig | 1503 | |
1504 | //why emit invalidating signals each time text is changed by appling styles? |
||
1505 | //this speed up layouting in case of using notes marks and drop caps |
||
1506 | itemText.blockSignals(true); |
||
17595 | craig | 1507 | setMaxY(-1); |
17599 | craig | 1508 | double maxYAsc = 0.0, maxYDesc = 0.0; |
18482 | jghali | 1509 | int regionMinY = 0, regionMaxY= 0; |
21563 | jghali | 1510 | |
21155 | jghali | 1511 | double autoLeftIndent = 0.0; |
21563 | jghali | 1512 | for (int i = 0; shapedText.haveMoreText(i, glyphClusters); ++i) |
21107 | craig | 1513 | { |
21715 | craig | 1514 | int currentIndex = i - current.lineData.firstCluster; |
21563 | jghali | 1515 | GlyphCluster newRun = glyphClusters[i]; |
1516 | if (currentIndex >= current.glyphs.count()) |
||
1517 | current.glyphs.append(newRun); |
||
21155 | jghali | 1518 | else |
21563 | jghali | 1519 | current.glyphs[currentIndex] = newRun; |
21155 | jghali | 1520 | |
21563 | jghali | 1521 | if (current.glyphs[currentIndex].glyphs().isEmpty()) |
21107 | craig | 1522 | continue; |
16868 | jghali | 1523 | |
21563 | jghali | 1524 | int a = current.glyphs[currentIndex].firstChar(); |
18524 | avox | 1525 | bool HasObject = itemText.hasObject(a); |
21563 | jghali | 1526 | PageItem* currentObject = itemText.object(a).getPageItem(m_Doc); |
21301 | fschmid | 1527 | QRectF currentObjectBox = QRectF(); |
1528 | if (HasObject) |
||
1529 | currentObjectBox = currentObject->getVisualBoundingRect(); |
||
21563 | jghali | 1530 | |
18524 | avox | 1531 | bool HasMark = itemText.hasMark(a); |
1532 | |||
17826 | craig | 1533 | if (HasMark) |
1534 | { |
||
21107 | craig | 1535 | Mark* mark = itemText.mark(a); |
17826 | craig | 1536 | //store mark pointer and position in text |
18524 | avox | 1537 | if (mark->isType(MARKNoteMasterType)) |
1538 | noteMarksPosMap.insert(a, mark); |
||
21107 | craig | 1539 | } |
18524 | avox | 1540 | |
18047 | craig | 1541 | BulNumMode = false; |
21563 | jghali | 1542 | if (itemText.isBlockStart(a)) |
18047 | craig | 1543 | { |
1544 | autoLeftIndent = 0.0; |
||
1545 | style = itemText.paragraphStyle(a); |
||
1546 | if (style.hasBullet() || style.hasNum()) |
||
1547 | { |
||
1548 | BulNumMode = true; |
||
1549 | } |
||
1550 | } |
||
21107 | craig | 1551 | |
1552 | if (current.isEmpty) |
||
18047 | craig | 1553 | opticalMargins = style.opticalMargins(); |
20550 | craig | 1554 | //-->#13490 |
21107 | craig | 1555 | if (isNoteFrame() && !HasMark) |
20550 | craig | 1556 | { |
1557 | StyleFlag s(itemText.charStyle(a).effects()); |
||
1558 | s &= ~ScStyle_Superscript; |
||
1559 | CharStyle haveSuperscript; |
||
1560 | haveSuperscript.setFeatures(s.featureList()); |
||
1561 | itemText.applyCharStyle(a, 1, haveSuperscript); |
||
1562 | } |
||
1563 | //--<#13490 |
||
18524 | avox | 1564 | CharStyle charStyle = ((itemText.text(a) != SpecialChars::PARSEP) ? itemText.charStyle(a) : style.charStyle()); |
17826 | craig | 1565 | |
18047 | craig | 1566 | //set style for paragraph effects |
21563 | jghali | 1567 | if (itemText.isBlockStart(a)) |
18047 | craig | 1568 | { |
21922 | jghali | 1569 | // FIXME: we should avoid calling setCharStyle() in layout() |
18047 | craig | 1570 | if (style.hasDropCap() || style.hasBullet() || style.hasNum()) |
1571 | { |
||
1572 | const QString& curParent(style.hasParent() ? style.parent() : style.name()); |
||
20269 | jghali | 1573 | CharStyle newStyle(charStyle); |
19089 | jghali | 1574 | if (style.peCharStyleName().isEmpty()) |
18047 | craig | 1575 | newStyle.setParent(m_Doc->paragraphStyle(curParent).charStyle().name()); |
1576 | else if (charStyle.name() != style.peCharStyleName()) |
||
1577 | newStyle.setParent(m_Doc->charStyle(style.peCharStyleName()).name()); |
||
1578 | charStyle.setStyle(newStyle); |
||
1579 | itemText.setCharStyle(a, 1 , charStyle); |
||
1580 | } |
||
19089 | jghali | 1581 | else if (!style.peCharStyleName().isEmpty()) |
18047 | craig | 1582 | { |
21922 | jghali | 1583 | //par effect is cleared but is set dcCharStyleName = clear drop cap char style |
1584 | if (charStyle.parent() == style.peCharStyleName()) |
||
1585 | { |
||
1586 | const QString& curParent(style.hasParent() ? style.parent() : style.name()); |
||
1587 | if (m_Doc->charStyles().contains(style.peCharStyleName())) |
||
1588 | charStyle.eraseCharStyle(m_Doc->charStyle(style.peCharStyleName())); |
||
1589 | charStyle.setParent(m_Doc->paragraphStyle(curParent).charStyle().name()); |
||
1590 | itemText.setCharStyle(a, 1,charStyle); |
||
1591 | } |
||
18047 | craig | 1592 | } |
1593 | } |
||
1594 | |||
16868 | jghali | 1595 | double hlcsize10 = charStyle.fontSize() / 10.0; |
1596 | double scaleV = charStyle.scaleV() / 1000.0; |
||
1597 | double scaleH = charStyle.scaleH() / 1000.0; |
||
17826 | craig | 1598 | double offset = hlcsize10 * (charStyle.baselineOffset() / 1000.0); |
16840 | jghali | 1599 | style.setLineSpacing (calculateLineSpacing (style, this)); |
17760 | jghali | 1600 | FlopBaseline = (current.startOfCol && firstLineOffset() == FLOPBaselineGrid); |
1601 | |||
5408 | avox | 1602 | // find out about par gap and dropcap |
5721 | avox | 1603 | if (a == firstInFrame()) |
3614 | cbradney | 1604 | { |
21563 | jghali | 1605 | if (itemText.isBlockStart(a)) |
5362 | avox | 1606 | { |
21563 | jghali | 1607 | if (!itemText.isBlockStart(a+1)) |
5362 | avox | 1608 | { |
5721 | avox | 1609 | DropCmode = style.hasDropCap(); |
5362 | avox | 1610 | if (DropCmode) |
5721 | avox | 1611 | DropLines = style.dropCapLines(); |
5362 | avox | 1612 | } |
1613 | else |
||
1614 | DropCmode = false; |
||
21715 | craig | 1615 | current.lineData.isFirstLine = true; |
5362 | avox | 1616 | } |
3614 | cbradney | 1617 | } |
8814 | cbradney | 1618 | |
21563 | jghali | 1619 | const ScFace& font = charStyle.font(); |
17757 | jghali | 1620 | |
21563 | jghali | 1621 | current.glyphs[currentIndex].clearFlag(ScLayout_DropCap); |
1622 | current.glyphs[currentIndex].clearFlag(ScLayout_SoftHyphenVisible); |
||
8814 | cbradney | 1623 | |
18524 | avox | 1624 | // No space at begin of line, |
8150 | avox | 1625 | if (legacy) |
3614 | cbradney | 1626 | { |
8150 | avox | 1627 | // unless at begin of par (eeks) |
21107 | craig | 1628 | if ( (current.isEmpty) && (SpecialChars::isBreakingSpace(itemText.text(a))) |
8177 | avox | 1629 | && (a > 0 && ! SpecialChars::isBreak(itemText.text(a-1))) |
18524 | avox | 1630 | && ! (a > 0 && SpecialChars::isBreakingSpace(itemText.text(a-1)) |
21563 | jghali | 1631 | && (!glyphClusters[i - 1].hasFlag(ScLayout_SuppressSpace)))) |
8150 | avox | 1632 | { |
21563 | jghali | 1633 | current.glyphs[currentIndex].setFlag(ScLayout_SuppressSpace); |
8150 | avox | 1634 | continue; |
1635 | } |
||
1636 | else |
||
21563 | jghali | 1637 | current.glyphs[currentIndex].clearFlag(ScLayout_SuppressSpace); |
5362 | avox | 1638 | } |
8150 | avox | 1639 | else // from 134 on use NBSPACE for this effect |
1640 | { |
||
21107 | craig | 1641 | if ( current.isEmpty && (SpecialChars::isBreakingSpace(itemText.text(a)) || itemText.text(a).isSpace())) |
8150 | avox | 1642 | { |
21563 | jghali | 1643 | current.glyphs[currentIndex].setFlag(ScLayout_SuppressSpace); |
8150 | avox | 1644 | continue; |
1645 | } |
||
1646 | else |
||
21563 | jghali | 1647 | current.glyphs[currentIndex].clearFlag(ScLayout_SuppressSpace); |
8150 | avox | 1648 | } |
21107 | craig | 1649 | if (current.isEmpty) |
5362 | avox | 1650 | { |
18524 | avox | 1651 | if (style.rightMargin() == 0) |
3614 | cbradney | 1652 | { |
16868 | jghali | 1653 | //addLine = true; |
1654 | current.rightMargin = 0.0; |
||
1655 | } |
||
1656 | else |
||
1657 | { |
||
1658 | if (current.lastInRowLine) |
||
1659 | current.rightMargin = style.rightMargin(); |
||
1660 | else |
||
1661 | current.rightMargin = 0.0; |
||
1662 | } |
||
1663 | current.breakIndex = -1; |
||
1664 | if (current.startOfCol && !current.afterOverflow && current.recalculateY) |
||
19831 | craig | 1665 | current.yPos = qMax(current.yPos, m_textDistanceMargins.top()); |
16868 | jghali | 1666 | // more about par gap and dropcaps |
21563 | jghali | 1667 | if ((a > firstInFrame() && itemText.isBlockStart(a)) || (a == 0 && BackBox == 0 && current.startOfCol)) |
16868 | jghali | 1668 | { |
1669 | if (!current.afterOverflow && current.recalculateY && !current.startOfCol) |
||
1670 | current.yPos += style.gapBefore(); |
||
6370 | avox | 1671 | DropCapDrop = 0; |
21563 | jghali | 1672 | if (!itemText.isBlockStart(a+1)) |
5721 | avox | 1673 | DropCmode = style.hasDropCap(); |
5362 | avox | 1674 | else |
1675 | DropCmode = false; |
||
16868 | jghali | 1676 | if (DropCmode && !current.afterOverflow) |
5362 | avox | 1677 | { |
5721 | avox | 1678 | DropLines = style.dropCapLines(); |
21347 | jghali | 1679 | // DropCapDrop = calculateLineSpacing (style, this) * (DropLines - 1); |
13085 | jghali | 1680 | // qDebug() << QString("dropcapdrop: y=%1+%2").arg(current.yPos).arg(DropCapDrop); |
5362 | avox | 1681 | } |
21715 | craig | 1682 | current.lineData.isFirstLine = true; |
3614 | cbradney | 1683 | } |
5362 | avox | 1684 | } |
16868 | jghali | 1685 | // find charsize factors |
5362 | avox | 1686 | if (DropCmode) |
1687 | { |
||
21347 | jghali | 1688 | DropCapDrop = calculateLineSpacing (style, this) * (DropLines - 1); |
17140 | jghali | 1689 | |
18524 | avox | 1690 | //text height, width, ascent and descent should be calculated for whole text provided by ScText in current position |
17826 | craig | 1691 | //and that may be more than one char (variable text for example) |
1692 | double realCharHeight = 0.0, realCharAscent = 0.0; |
||
21563 | jghali | 1693 | foreach (const GlyphLayout& gl, current.glyphs[currentIndex].glyphs()) { |
21107 | craig | 1694 | GlyphMetrics gm = font.glyphBBox(gl.glyph); |
1695 | realCharHeight = qMax(realCharHeight, gm.ascent + gm.descent); |
||
1696 | realCharAscent = qMax(realCharAscent, gm.ascent); |
||
17826 | craig | 1697 | } |
18524 | avox | 1698 | double fontAscent = font.ascent(style.charStyle().fontSize() / 10.0); |
14929 | jghali | 1699 | if (realCharHeight == 0.0) |
17757 | jghali | 1700 | realCharHeight = font.height(style.charStyle().fontSize() / 10.0); |
14929 | jghali | 1701 | if (realCharAscent == 0.0) |
1702 | realCharAscent = fontAscent; |
||
16840 | jghali | 1703 | chsd = (10 * ((DropCapDrop + fontAscent) / realCharHeight)); |
1704 | chs = (10 * ((DropCapDrop + fontAscent) / realCharAscent)); |
||
21563 | jghali | 1705 | current.glyphs[currentIndex].setFlag(ScLayout_DropCap); |
17826 | craig | 1706 | if (HasObject) |
17199 | fschmid | 1707 | { |
21301 | fschmid | 1708 | // chs = qRound((currentObject->height() + currentObject->lineWidth()) * 10); |
1709 | // chsd = qRound((currentObject->height() + currentObject->lineWidth()) * 10); |
||
1710 | chs = currentObjectBox.height() * 10; |
||
1711 | chsd = currentObjectBox.height() * 10; |
||
17199 | fschmid | 1712 | } |
5362 | avox | 1713 | } |
5408 | avox | 1714 | else // ! dropCapMode |
5362 | avox | 1715 | { |
17826 | craig | 1716 | if (HasObject) |
21301 | fschmid | 1717 | chs = currentObjectBox.height() * 10; |
1718 | // chs = qRound((currentObject->height() + currentObject->lineWidth()) * 10); |
||
5362 | avox | 1719 | else |
6144 | avox | 1720 | chs = charStyle.fontSize(); |
5362 | avox | 1721 | } |
21563 | jghali | 1722 | // set StartOfLine |
1723 | if (current.isEmpty) |
||
1724 | current.glyphs[currentIndex].setFlag(ScLayout_StartOfLine); |
||
1725 | else |
||
1726 | current.glyphs[currentIndex].clearFlag(ScLayout_StartOfLine); |
||
1727 | |||
1728 | if (!current.glyphs[currentIndex].hasFlag(ScLayout_StartOfLine)) |
||
1729 | { |
||
1730 | double tracking = charStyle.fontSize() * charStyle.tracking() / 10000.0; |
||
1731 | current.glyphs[currentIndex].xoffset += tracking; |
||
1732 | if (current.glyphs[currentIndex].width() != 0) |
||
1733 | current.glyphs[currentIndex].extraWidth += tracking; |
||
1734 | } |
||
1735 | |||
21107 | craig | 1736 | // glyphs->yadvance = 0; |
18524 | avox | 1737 | |
21715 | craig | 1738 | if (i == current.lineData.firstCluster && current.glyphs[currentIndex].hasFlag(ScLayout_CJKFence)) |
14417 | jghali | 1739 | { |
21563 | jghali | 1740 | current.glyphs[currentIndex].extraWidth -= (charStyle.fontSize() / 10 / 2); |
1741 | current.glyphs[currentIndex].xoffset -= (charStyle.fontSize() / 10 / 2); |
||
5362 | avox | 1742 | } |
21107 | craig | 1743 | // find out width, ascent and descent of char |
21563 | jghali | 1744 | double wide = current.glyphs[currentIndex].width(); |
21107 | craig | 1745 | |
5362 | avox | 1746 | if (DropCmode) |
1747 | { |
||
5408 | avox | 1748 | // drop caps are wider... |
21638 | jghali | 1749 | GlyphCluster& glyphCluster = current.glyphs[currentIndex]; |
17826 | craig | 1750 | if (HasObject) |
5362 | avox | 1751 | { |
21301 | fschmid | 1752 | // double itemHeight = currentObject->height() + currentObject->lineWidth(); |
1753 | // if (itemHeight == 0) |
||
1754 | // itemHeight = font.height(style.charStyle().fontSize() / 10.0); |
||
1755 | // asce = currentObject->height() + currentObject->lineWidth(); |
||
1756 | // wide = currentObject->width() + currentObject->lineWidth(); |
||
1757 | double itemHeight = currentObjectBox.height(); |
||
14929 | jghali | 1758 | if (itemHeight == 0) |
17757 | jghali | 1759 | itemHeight = font.height(style.charStyle().fontSize() / 10.0); |
21301 | fschmid | 1760 | asce = currentObjectBox.height(); |
1761 | wide = currentObjectBox.width(); |
||
16868 | jghali | 1762 | realAsce = calculateLineSpacing (style, this) * DropLines; |
21638 | jghali | 1763 | glyphCluster.setScaleH(glyphCluster.scaleH() / glyphCluster.scaleV()); |
1764 | glyphCluster.setScaleV(realAsce / itemHeight); |
||
1765 | glyphCluster.setScaleH(glyphCluster.scaleH() * glyphCluster.scaleV()); |
||
1766 | wide *= glyphCluster.scaleH(); |
||
5362 | avox | 1767 | } |
1768 | else |
||
1769 | { |
||
17826 | craig | 1770 | double realCharHeight = 0.0; |
1771 | wide = 0.0; realAsce = 0.0; |
||
21638 | jghali | 1772 | foreach (const GlyphLayout& gl, glyphCluster.glyphs()) { |
21107 | craig | 1773 | GlyphMetrics gm; |
1774 | gm = font.glyphBBox(gl.glyph, charStyle.fontSize() / 10.0); |
||
1775 | realCharHeight = qMax(realCharHeight, gm.ascent + gm.descent); |
||
1776 | gm = font.glyphBBox(gl.glyph, chsd / 10.0); |
||
1777 | realAsce = qMax(realAsce, gm.ascent + gm.descent); |
||
21356 | jghali | 1778 | wide += gm.width; |
17826 | craig | 1779 | } |
21563 | jghali | 1780 | wide = (wide* scaleH) + (1 - scaleH); |
17826 | craig | 1781 | realAsce = realAsce * scaleV + offset; |
14929 | jghali | 1782 | if (realCharHeight == 0) |
17757 | jghali | 1783 | realCharHeight = font.height(style.charStyle().fontSize() / 10.0); |
1784 | asce = font.ascent(hlcsize10); |
||
21638 | jghali | 1785 | glyphCluster.setScaleH(glyphCluster.scaleH() / glyphCluster.scaleV()); |
1786 | glyphCluster.setScaleV(realAsce / realCharHeight); |
||
1787 | glyphCluster.setScaleH(glyphCluster.scaleH() * glyphCluster.scaleV()); |
||
1788 | glyphCluster.xoffset -= 0.5; //drop caps are always to far from column left edge |
||
5362 | avox | 1789 | } |
21194 | jghali | 1790 | // This is to mimic pre-boxes branches in case first character of paragraph is a space |
1791 | // If we don't do this, paragraph offset will not apply correctly to first line |
||
21638 | jghali | 1792 | if ((glyphCluster.scaleH() == 0.0) || (glyphCluster.scaleV() == 0.0)) |
21194 | jghali | 1793 | { |
21638 | jghali | 1794 | glyphCluster.setScaleH(1.0); |
1795 | glyphCluster.setScaleV(1.0); |
||
21194 | jghali | 1796 | wide = 0.0; |
1797 | } |
||
19338 | jghali | 1798 | desc = realDesc = 0; |
5362 | avox | 1799 | } |
8814 | cbradney | 1800 | else // !DropCMode |
5362 | avox | 1801 | { |
5408 | avox | 1802 | // find ascent / descent |
17826 | craig | 1803 | if (HasObject) |
16868 | jghali | 1804 | desc = realDesc = 0; |
5362 | avox | 1805 | else |
1806 | { |
||
21107 | craig | 1807 | if (itemText.text(a) != SpecialChars::OBJECT) |
17826 | craig | 1808 | { |
21563 | jghali | 1809 | foreach (const GlyphLayout& gl, current.glyphs[currentIndex].glyphs()) |
21301 | fschmid | 1810 | { |
21107 | craig | 1811 | GlyphMetrics gm = font.glyphBBox(gl.glyph, hlcsize10); |
21174 | fschmid | 1812 | realDesc = qMax(realDesc, gm.descent * scaleV - offset); |
21107 | craig | 1813 | realAsce = gm.ascent; |
1814 | } |
||
17826 | craig | 1815 | } |
17757 | jghali | 1816 | desc = -font.descent(hlcsize10); |
21107 | craig | 1817 | current.rememberShrinkStretch(itemText.text(a), wide, style); |
5362 | avox | 1818 | } |
17826 | craig | 1819 | if (HasObject) |
17199 | fschmid | 1820 | { |
21301 | fschmid | 1821 | // asce = currentObject->height() + currentObject->lineWidth(); |
1822 | asce = currentObjectBox.height(); |
||
17199 | fschmid | 1823 | realAsce = asce * scaleV + offset; |
21301 | fschmid | 1824 | wide = currentObjectBox.width() * scaleH; |
17199 | fschmid | 1825 | } |
1826 | else |
||
1827 | { |
||
17757 | jghali | 1828 | asce = font.ascent(hlcsize10); |
18047 | craig | 1829 | if (HasMark && !BulNumMode) |
17826 | craig | 1830 | realAsce = asce * scaleV + offset; |
1831 | else |
||
1832 | { |
||
21563 | jghali | 1833 | foreach (const GlyphLayout& gl, current.glyphs[currentIndex].glyphs()) |
21107 | craig | 1834 | realAsce = qMax(realAsce, font.glyphBBox(gl.glyph, hlcsize10).ascent * scaleV + offset); |
17826 | craig | 1835 | } |
17199 | fschmid | 1836 | } |
5362 | avox | 1837 | } |
18047 | craig | 1838 | // if (BulNumMode) |
1839 | // hl->glyph.last()->xadvance += style.parEffectOffset(); |
||
16868 | jghali | 1840 | //check for Y position at beginning of line |
21107 | craig | 1841 | if (current.isEmpty && !current.afterOverflow) |
5362 | avox | 1842 | { |
16868 | jghali | 1843 | if (current.recalculateY) |
5362 | avox | 1844 | { |
16868 | jghali | 1845 | //if top of column Y position depends on first line offset |
1846 | if (current.startOfCol) |
||
3614 | cbradney | 1847 | { |
19831 | craig | 1848 | lastLineY = qMax(lastLineY, m_textDistanceMargins.top() + lineCorr); |
17760 | jghali | 1849 | if (style.lineSpacingMode() == ParagraphStyle::BaselineGridLineSpacing || FlopBaseline) |
3614 | cbradney | 1850 | { |
17760 | jghali | 1851 | if (current.yPos <= lastLineY) |
1852 | current.yPos = lastLineY +1; |
||
18032 | craig | 1853 | double by = m_yPos; |
17760 | jghali | 1854 | if (OwnPage != -1) |
18032 | craig | 1855 | by = m_yPos - m_Doc->Pages->at(OwnPage)->yOffset(); |
17760 | jghali | 1856 | int ol1 = qRound((by + current.yPos - m_Doc->guidesPrefs().offsetBaselineGrid) * 10000.0); |
1857 | int ol2 = static_cast<int>(ol1 / m_Doc->guidesPrefs().valueBaselineGrid); |
||
1858 | current.yPos = ceil( ol2 / 10000.0 ) * m_Doc->guidesPrefs().valueBaselineGrid + m_Doc->guidesPrefs().offsetBaselineGrid - by; |
||
1859 | } |
||
1860 | else if (style.lineSpacingMode() != ParagraphStyle::BaselineGridLineSpacing) |
||
1861 | { |
||
16868 | jghali | 1862 | if (firstLineOffset() == FLOPRealGlyphHeight) |
1863 | { |
||
21422 | jghali | 1864 | if (DropCmode || (itemText.text(a) == SpecialChars::PARSEP)) |
16868 | jghali | 1865 | current.yPos += asce; |
1866 | else |
||
1867 | current.yPos += realAsce; |
||
1868 | } |
||
1869 | else if (firstLineOffset() == FLOPLineSpacing) |
||
1870 | current.yPos += style.lineSpacing(); |
||
1871 | else |
||
1872 | current.yPos += asce; |
||
3614 | cbradney | 1873 | } |
1874 | } |
||
16868 | jghali | 1875 | else |
1876 | { |
||
1877 | if (style.lineSpacingMode() == ParagraphStyle::BaselineGridLineSpacing) |
||
1878 | { |
||
1879 | current.yPos += m_Doc->guidesPrefs().valueBaselineGrid; |
||
18032 | craig | 1880 | double by = m_yPos; |
16868 | jghali | 1881 | if (OwnPage != -1) |
18032 | craig | 1882 | by = m_yPos - m_Doc->Pages->at(OwnPage)->yOffset(); |
16868 | jghali | 1883 | int ol1 = qRound((by + current.yPos - m_Doc->guidesPrefs().offsetBaselineGrid) * 10000.0); |
1884 | int ol2 = static_cast<int>(ol1 / m_Doc->guidesPrefs().valueBaselineGrid); |
||
1885 | current.yPos = ceil( ol2 / 10000.0 ) * m_Doc->guidesPrefs().valueBaselineGrid + m_Doc->guidesPrefs().offsetBaselineGrid - by; |
||
1886 | } |
||
1887 | else |
||
1888 | current.yPos += style.lineSpacing(); |
||
1889 | } |
||
1890 | if (DropCmode) |
||
1891 | current.yPos += DropCapDrop; |
||
5362 | avox | 1892 | } |
16868 | jghali | 1893 | //set left indentation |
1894 | current.leftIndent = 0.0; |
||
22135 | jghali | 1895 | if (current.addLeftIndent && ((maxDX == 0) || DropCmode || BulNumMode)) |
5362 | avox | 1896 | { |
18047 | craig | 1897 | current.leftIndent = style.leftMargin() + autoLeftIndent; |
21563 | jghali | 1898 | if (itemText.isBlockStart(a)) |
18047 | craig | 1899 | { |
16868 | jghali | 1900 | current.leftIndent += style.firstIndent(); |
18047 | craig | 1901 | if (BulNumMode || DropCmode) |
1902 | { |
||
19927 | jghali | 1903 | if (style.parEffectIndent()) |
18047 | craig | 1904 | { |
1905 | current.leftIndent -= style.parEffectOffset() + wide; |
||
1906 | if (current.leftIndent < 0.0) |
||
1907 | { |
||
1908 | autoLeftIndent = abs(current.leftIndent); |
||
1909 | current.leftIndent = 0.0; |
||
1910 | } |
||
1911 | } |
||
1912 | } |
||
1913 | } |
||
16868 | jghali | 1914 | current.addLeftIndent = false; |
5362 | avox | 1915 | } |
1916 | } |
||
16868 | jghali | 1917 | current.recalculateY = true; |
17599 | craig | 1918 | maxYAsc = 0.0, maxYDesc = 0.0; |
18047 | craig | 1919 | double addAsce = 0.0; |
16868 | jghali | 1920 | if (current.startOfCol) |
5362 | avox | 1921 | { |
17599 | craig | 1922 | if (DropCmode) |
1923 | addAsce = qMax(realAsce, asce + offset); |
||
1924 | else |
||
1925 | addAsce = asce + offset; |
||
16868 | jghali | 1926 | if (style.lineSpacingMode() != ParagraphStyle::BaselineGridLineSpacing) |
5362 | avox | 1927 | { |
16868 | jghali | 1928 | if (firstLineOffset() == FLOPRealGlyphHeight) |
1929 | addAsce = realAsce; |
||
1930 | else if (firstLineOffset() == FLOPLineSpacing) |
||
1931 | addAsce = style.lineSpacing(); |
||
5362 | avox | 1932 | } |
17595 | craig | 1933 | maxYAsc = current.yPos - addAsce; |
16868 | jghali | 1934 | } |
1935 | else |
||
17599 | craig | 1936 | maxYAsc = current.yPos - realAsce; |
16913 | jghali | 1937 | //fix for glyphs with negative realAsce value |
17595 | craig | 1938 | maxYAsc = qMax(maxYAsc, 0.0); |
17599 | craig | 1939 | maxYDesc = current.yPos + realDesc; |
16868 | jghali | 1940 | |
21301 | fschmid | 1941 | if (style.lineSpacingMode() == ParagraphStyle::AutomaticLineSpacing) |
18524 | avox | 1942 | { |
1943 | regionMinY = static_cast<int>(floor(maxYAsc)); |
||
1944 | regionMaxY = static_cast<int>(floor(maxYDesc)); |
||
1945 | } |
||
1946 | else // #11727, #11628, etc. |
||
1947 | { |
||
1948 | regionMinY = static_cast<int>(qMax(0.0, floor(current.yPos - (asce + offset)))); |
||
1949 | regionMaxY = static_cast<int>(floor(current.yPos + desc)); |
||
1950 | } |
||
18482 | jghali | 1951 | |
21107 | craig | 1952 | if (current.isEmpty && !current.afterOverflow) |
16868 | jghali | 1953 | { |
1954 | //start a new line |
||
1955 | goNoRoom = false; |
||
1956 | |||
1957 | // find line`s start |
||
18482 | jghali | 1958 | pt1 = QPoint(static_cast<int>(floor(current.xPos)), regionMinY); |
1959 | pt2 = QPoint(static_cast<int>(floor(current.xPos + (style.minGlyphExtension() * wide))), regionMaxY -1); |
||
16868 | jghali | 1960 | pt = QRect(pt1, pt2); |
1961 | realEnd = 0; |
||
1962 | //check if there is overflow at start of line, if so jump behind it and check again |
||
1963 | double Xpos, Xend; |
||
1964 | bool done = false; |
||
1965 | bool newColumn = false; |
||
18524 | avox | 1966 | |
17215 | jghali | 1967 | //FIX ME - that should be paragraph style`s properties |
1968 | //if set then indent is add to possible line start point (after overflow) |
||
1969 | //if not then indent is calculated from column left edge |
||
21017 | craig | 1970 | //if you don't agree that adding indent to overflow should be default behaviour |
17215 | jghali | 1971 | //then change it to false |
1972 | bool addIndent2overflow = false; // should be addIndent2Overflow = style.addIndent2Overlow(); |
||
1973 | bool addFirstIndent2overflow = true; // should be addFirstIndent2Overflow = style.addFirstIndent2Overlow(); |
||
1974 | //if first line indent is negative and left indent should not be added to overflow |
||
21017 | craig | 1975 | //then don't add first line ident either |
17215 | jghali | 1976 | if ((style.firstIndent() < 0) && !addIndent2overflow) |
1977 | addFirstIndent2overflow = false; |
||
1978 | |||
16868 | jghali | 1979 | while (!done) |
8814 | cbradney | 1980 | { |
17215 | jghali | 1981 | Xpos = current.xPos + (addIndent2overflow ? 0 : current.leftIndent); |
16868 | jghali | 1982 | Xend = current.xPos + current.leftIndent; |
1983 | //check if in indent any overflow occurs |
||
1984 | while (Xpos <= Xend && Xpos < current.colRight) |
||
3614 | cbradney | 1985 | { |
18482 | jghali | 1986 | pt.moveTopLeft(QPoint(static_cast<int>(floor(Xpos)), regionMinY)); |
17578 | craig | 1987 | if (!regionContainsRect(m_availableRegion, pt)) |
16868 | jghali | 1988 | { |
17578 | craig | 1989 | Xpos = current.xPos = realEnd = findRealOverflowEnd(m_availableRegion, pt, current.colRight); |
17215 | jghali | 1990 | Xend = current.xPos + (addIndent2overflow ? current.leftIndent : 0); |
1991 | //for first paragraph`s line - if first line offset should be added |
||
21563 | jghali | 1992 | if ( addFirstIndent2overflow && itemText.isBlockStart(a)) |
17215 | jghali | 1993 | Xend += style.firstIndent(); |
16868 | jghali | 1994 | } |
1995 | else |
||
1996 | Xpos++; |
||
1997 | } |
||
1998 | current.xPos = Xend; |
||
1999 | done = true; |
||
17215 | jghali | 2000 | if (current.isEndOfLine((style.minGlyphExtension() * wide) + current.rightMargin)) |
16868 | jghali | 2001 | { |
5408 | avox | 2002 | // new line |
16868 | jghali | 2003 | current.xPos = qMax(current.colLeft, maxDX); |
17760 | jghali | 2004 | if (style.lineSpacingMode() == ParagraphStyle::BaselineGridLineSpacing || FlopBaseline) |
5362 | avox | 2005 | { |
17085 | jghali | 2006 | current.yPos++; |
18032 | craig | 2007 | double by = m_yPos; |
16868 | jghali | 2008 | if (OwnPage != -1) |
18032 | craig | 2009 | by = m_yPos - m_Doc->Pages->at(OwnPage)->yOffset(); |
16868 | jghali | 2010 | int ol1 = qRound((by + current.yPos - m_Doc->guidesPrefs().offsetBaselineGrid) * 10000.0); |
2011 | int ol2 = static_cast<int>(ol1 / m_Doc->guidesPrefs().valueBaselineGrid); |
||
2012 | current.yPos = ceil( ol2 / 10000.0 ) * m_Doc->guidesPrefs().valueBaselineGrid + m_Doc->guidesPrefs().offsetBaselineGrid - by; |
||
2013 | } |
||
17085 | jghali | 2014 | else if (style.lineSpacingMode() == ParagraphStyle::FixedLineSpacing) |
2015 | current.yPos += (current.startOfCol ? 1 : style.lineSpacing()); |
||
2016 | else |
||
21188 | fschmid | 2017 | current.yPos += (current.startOfCol ? 1 : style.lineSpacing()); |
17595 | craig | 2018 | lastLineY = maxYAsc; |
16868 | jghali | 2019 | if (current.startOfCol) |
2020 | { |
||
18047 | craig | 2021 | // double addAsce; |
2022 | // if (DropCmode) |
||
2023 | // addAsce = qMax(realAsce, asce + offset); |
||
2024 | // else |
||
2025 | // addAsce = asce + offset; |
||
2026 | // if (style.lineSpacingMode() != ParagraphStyle::BaselineGridLineSpacing) |
||
2027 | // { |
||
2028 | // if (firstLineOffset() == FLOPRealGlyphHeight) |
||
2029 | // addAsce = realAsce; |
||
2030 | // &nb |