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