|
1 /* |
|
2 * Copyright (C) 2000 Lars Knoll (knoll@kde.org) |
|
3 * Copyright (C) 2003, 2004, 2006, 2007, 2008, 2009, 2010 Apple Inc. All right reserved. |
|
4 * Copyright (C) 2010 Google Inc. All rights reserved. |
|
5 * |
|
6 * This library is free software; you can redistribute it and/or |
|
7 * modify it under the terms of the GNU Library General Public |
|
8 * License as published by the Free Software Foundation; either |
|
9 * version 2 of the License, or (at your option) any later version. |
|
10 * |
|
11 * This library is distributed in the hope that it will be useful, |
|
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
14 * Library General Public License for more details. |
|
15 * |
|
16 * You should have received a copy of the GNU Library General Public License |
|
17 * along with this library; see the file COPYING.LIB. If not, write to |
|
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
|
19 * Boston, MA 02110-1301, USA. |
|
20 * |
|
21 */ |
|
22 |
|
23 #include "config.h" |
|
24 |
|
25 #include "BidiResolver.h" |
|
26 #include "CharacterNames.h" |
|
27 #include "Hyphenation.h" |
|
28 #include "InlineIterator.h" |
|
29 #include "InlineTextBox.h" |
|
30 #include "Logging.h" |
|
31 #include "RenderArena.h" |
|
32 #include "RenderInline.h" |
|
33 #include "RenderLayer.h" |
|
34 #include "RenderListMarker.h" |
|
35 #include "RenderView.h" |
|
36 #include "TrailingFloatsRootInlineBox.h" |
|
37 #include "break_lines.h" |
|
38 #include <wtf/AlwaysInline.h> |
|
39 #include <wtf/RefCountedLeakCounter.h> |
|
40 #include <wtf/StdLibExtras.h> |
|
41 #include <wtf/Vector.h> |
|
42 |
|
43 #if ENABLE(SVG) |
|
44 #include "SVGRootInlineBox.h" |
|
45 #endif |
|
46 |
|
47 using namespace std; |
|
48 using namespace WTF; |
|
49 using namespace Unicode; |
|
50 |
|
51 namespace WebCore { |
|
52 |
|
53 // We don't let our line box tree for a single line get any deeper than this. |
|
54 const unsigned cMaxLineDepth = 200; |
|
55 |
|
56 static int getBorderPaddingMargin(RenderBoxModelObject* child, bool endOfInline) |
|
57 { |
|
58 bool leftSide = (child->style()->direction() == LTR) ? !endOfInline : endOfInline; |
|
59 if (leftSide) |
|
60 return child->marginLeft() + child->paddingLeft() + child->borderLeft(); |
|
61 return child->marginRight() + child->paddingRight() + child->borderRight(); |
|
62 } |
|
63 |
|
64 static int inlineWidth(RenderObject* child, bool start = true, bool end = true) |
|
65 { |
|
66 unsigned lineDepth = 1; |
|
67 int extraWidth = 0; |
|
68 RenderObject* parent = child->parent(); |
|
69 while (parent->isInline() && !parent->isInlineBlockOrInlineTable() && lineDepth++ < cMaxLineDepth) { |
|
70 if (start && !child->previousSibling()) |
|
71 extraWidth += getBorderPaddingMargin(toRenderBoxModelObject(parent), false); |
|
72 if (end && !child->nextSibling()) |
|
73 extraWidth += getBorderPaddingMargin(toRenderBoxModelObject(parent), true); |
|
74 child = parent; |
|
75 parent = child->parent(); |
|
76 } |
|
77 return extraWidth; |
|
78 } |
|
79 |
|
80 static void chopMidpointsAt(LineMidpointState& lineMidpointState, RenderObject* obj, unsigned pos) |
|
81 { |
|
82 if (!lineMidpointState.numMidpoints) |
|
83 return; |
|
84 InlineIterator* midpoints = lineMidpointState.midpoints.data(); |
|
85 for (int i = lineMidpointState.numMidpoints - 1; i >= 0; i--) { |
|
86 const InlineIterator& point = midpoints[i]; |
|
87 if (point.obj == obj && point.pos == pos) { |
|
88 lineMidpointState.numMidpoints = i; |
|
89 break; |
|
90 } |
|
91 } |
|
92 } |
|
93 |
|
94 static void checkMidpoints(LineMidpointState& lineMidpointState, InlineIterator& lBreak) |
|
95 { |
|
96 // Check to see if our last midpoint is a start point beyond the line break. If so, |
|
97 // shave it off the list, and shave off a trailing space if the previous end point doesn't |
|
98 // preserve whitespace. |
|
99 if (lBreak.obj && lineMidpointState.numMidpoints && !(lineMidpointState.numMidpoints % 2)) { |
|
100 InlineIterator* midpoints = lineMidpointState.midpoints.data(); |
|
101 InlineIterator& endpoint = midpoints[lineMidpointState.numMidpoints - 2]; |
|
102 const InlineIterator& startpoint = midpoints[lineMidpointState.numMidpoints - 1]; |
|
103 InlineIterator currpoint = endpoint; |
|
104 while (!currpoint.atEnd() && currpoint != startpoint && currpoint != lBreak) |
|
105 currpoint.increment(); |
|
106 if (currpoint == lBreak) { |
|
107 // We hit the line break before the start point. Shave off the start point. |
|
108 lineMidpointState.numMidpoints--; |
|
109 if (endpoint.obj->style()->collapseWhiteSpace()) { |
|
110 if (endpoint.obj->isText()) { |
|
111 // Don't shave a character off the endpoint if it was from a soft hyphen. |
|
112 RenderText* textObj = toRenderText(endpoint.obj); |
|
113 if (endpoint.pos + 1 < textObj->textLength()) { |
|
114 if (textObj->characters()[endpoint.pos+1] == softHyphen) |
|
115 return; |
|
116 } else if (startpoint.obj->isText()) { |
|
117 RenderText *startText = toRenderText(startpoint.obj); |
|
118 if (startText->textLength() && startText->characters()[0] == softHyphen) |
|
119 return; |
|
120 } |
|
121 } |
|
122 endpoint.pos--; |
|
123 } |
|
124 } |
|
125 } |
|
126 } |
|
127 |
|
128 static void addMidpoint(LineMidpointState& lineMidpointState, const InlineIterator& midpoint) |
|
129 { |
|
130 if (lineMidpointState.midpoints.size() <= lineMidpointState.numMidpoints) |
|
131 lineMidpointState.midpoints.grow(lineMidpointState.numMidpoints + 10); |
|
132 |
|
133 InlineIterator* midpoints = lineMidpointState.midpoints.data(); |
|
134 midpoints[lineMidpointState.numMidpoints++] = midpoint; |
|
135 } |
|
136 |
|
137 void RenderBlock::appendRunsForObject(int start, int end, RenderObject* obj, InlineBidiResolver& resolver) |
|
138 { |
|
139 if (start > end || obj->isFloating() || |
|
140 (obj->isPositioned() && !obj->style()->hasStaticX() && !obj->style()->hasStaticY() && !obj->container()->isRenderInline())) |
|
141 return; |
|
142 |
|
143 LineMidpointState& lineMidpointState = resolver.midpointState(); |
|
144 bool haveNextMidpoint = (lineMidpointState.currentMidpoint < lineMidpointState.numMidpoints); |
|
145 InlineIterator nextMidpoint; |
|
146 if (haveNextMidpoint) |
|
147 nextMidpoint = lineMidpointState.midpoints[lineMidpointState.currentMidpoint]; |
|
148 if (lineMidpointState.betweenMidpoints) { |
|
149 if (!(haveNextMidpoint && nextMidpoint.obj == obj)) |
|
150 return; |
|
151 // This is a new start point. Stop ignoring objects and |
|
152 // adjust our start. |
|
153 lineMidpointState.betweenMidpoints = false; |
|
154 start = nextMidpoint.pos; |
|
155 lineMidpointState.currentMidpoint++; |
|
156 if (start < end) |
|
157 return appendRunsForObject(start, end, obj, resolver); |
|
158 } else { |
|
159 if (!haveNextMidpoint || (obj != nextMidpoint.obj)) { |
|
160 resolver.addRun(new (obj->renderArena()) BidiRun(start, end, obj, resolver.context(), resolver.dir())); |
|
161 return; |
|
162 } |
|
163 |
|
164 // An end midpoint has been encountered within our object. We |
|
165 // need to go ahead and append a run with our endpoint. |
|
166 if (static_cast<int>(nextMidpoint.pos + 1) <= end) { |
|
167 lineMidpointState.betweenMidpoints = true; |
|
168 lineMidpointState.currentMidpoint++; |
|
169 if (nextMidpoint.pos != UINT_MAX) { // UINT_MAX means stop at the object and don't include any of it. |
|
170 if (static_cast<int>(nextMidpoint.pos + 1) > start) |
|
171 resolver.addRun(new (obj->renderArena()) |
|
172 BidiRun(start, nextMidpoint.pos + 1, obj, resolver.context(), resolver.dir())); |
|
173 return appendRunsForObject(nextMidpoint.pos + 1, end, obj, resolver); |
|
174 } |
|
175 } else |
|
176 resolver.addRun(new (obj->renderArena()) BidiRun(start, end, obj, resolver.context(), resolver.dir())); |
|
177 } |
|
178 } |
|
179 |
|
180 static inline InlineBox* createInlineBoxForRenderer(RenderObject* obj, bool isRootLineBox, bool isOnlyRun = false) |
|
181 { |
|
182 if (isRootLineBox) |
|
183 return toRenderBlock(obj)->createAndAppendRootInlineBox(); |
|
184 |
|
185 if (obj->isText()) { |
|
186 InlineTextBox* textBox = toRenderText(obj)->createInlineTextBox(); |
|
187 // We only treat a box as text for a <br> if we are on a line by ourself or in strict mode |
|
188 // (Note the use of strict mode. In "almost strict" mode, we don't treat the box for <br> as text.) |
|
189 if (obj->isBR()) |
|
190 textBox->setIsText(isOnlyRun || obj->document()->inStrictMode()); |
|
191 return textBox; |
|
192 } |
|
193 |
|
194 if (obj->isBox()) |
|
195 return toRenderBox(obj)->createInlineBox(); |
|
196 |
|
197 return toRenderInline(obj)->createAndAppendInlineFlowBox(); |
|
198 } |
|
199 |
|
200 static inline void dirtyLineBoxesForRenderer(RenderObject* o, bool fullLayout) |
|
201 { |
|
202 if (o->isText()) { |
|
203 if (o->prefWidthsDirty() && o->isCounter()) |
|
204 toRenderText(o)->calcPrefWidths(0); // FIXME: Counters depend on this hack. No clue why. Should be investigated and removed. |
|
205 toRenderText(o)->dirtyLineBoxes(fullLayout); |
|
206 } else |
|
207 toRenderInline(o)->dirtyLineBoxes(fullLayout); |
|
208 } |
|
209 |
|
210 InlineFlowBox* RenderBlock::createLineBoxes(RenderObject* obj, bool firstLine) |
|
211 { |
|
212 // See if we have an unconstructed line box for this object that is also |
|
213 // the last item on the line. |
|
214 unsigned lineDepth = 1; |
|
215 InlineFlowBox* childBox = 0; |
|
216 InlineFlowBox* parentBox = 0; |
|
217 InlineFlowBox* result = 0; |
|
218 do { |
|
219 ASSERT(obj->isRenderInline() || obj == this); |
|
220 |
|
221 // Get the last box we made for this render object. |
|
222 parentBox = obj->isRenderInline() ? toRenderInline(obj)->lastLineBox() : toRenderBlock(obj)->lastLineBox(); |
|
223 |
|
224 // If this box is constructed then it is from a previous line, and we need |
|
225 // to make a new box for our line. If this box is unconstructed but it has |
|
226 // something following it on the line, then we know we have to make a new box |
|
227 // as well. In this situation our inline has actually been split in two on |
|
228 // the same line (this can happen with very fancy language mixtures). |
|
229 bool constructedNewBox = false; |
|
230 if (!parentBox || parentBox->isConstructed() || parentBox->nextOnLine()) { |
|
231 // We need to make a new box for this render object. Once |
|
232 // made, we need to place it at the end of the current line. |
|
233 InlineBox* newBox = createInlineBoxForRenderer(obj, obj == this); |
|
234 ASSERT(newBox->isInlineFlowBox()); |
|
235 parentBox = static_cast<InlineFlowBox*>(newBox); |
|
236 parentBox->setFirstLineStyleBit(firstLine); |
|
237 constructedNewBox = true; |
|
238 } |
|
239 |
|
240 if (!result) |
|
241 result = parentBox; |
|
242 |
|
243 // If we have hit the block itself, then |box| represents the root |
|
244 // inline box for the line, and it doesn't have to be appended to any parent |
|
245 // inline. |
|
246 if (childBox) |
|
247 parentBox->addToLine(childBox); |
|
248 |
|
249 if (!constructedNewBox || obj == this) |
|
250 break; |
|
251 |
|
252 childBox = parentBox; |
|
253 |
|
254 // If we've exceeded our line depth, then jump straight to the root and skip all the remaining |
|
255 // intermediate inline flows. |
|
256 obj = (++lineDepth >= cMaxLineDepth) ? this : obj->parent(); |
|
257 |
|
258 } while (true); |
|
259 |
|
260 return result; |
|
261 } |
|
262 |
|
263 RootInlineBox* RenderBlock::constructLine(unsigned runCount, BidiRun* firstRun, BidiRun* lastRun, bool firstLine, bool lastLine, RenderObject* endObject) |
|
264 { |
|
265 ASSERT(firstRun); |
|
266 |
|
267 InlineFlowBox* parentBox = 0; |
|
268 for (BidiRun* r = firstRun; r; r = r->next()) { |
|
269 // Create a box for our object. |
|
270 bool isOnlyRun = (runCount == 1); |
|
271 if (runCount == 2 && !r->m_object->isListMarker()) |
|
272 isOnlyRun = ((style()->direction() == RTL) ? lastRun : firstRun)->m_object->isListMarker(); |
|
273 |
|
274 InlineBox* box = createInlineBoxForRenderer(r->m_object, false, isOnlyRun); |
|
275 r->m_box = box; |
|
276 |
|
277 ASSERT(box); |
|
278 if (!box) |
|
279 continue; |
|
280 |
|
281 // If we have no parent box yet, or if the run is not simply a sibling, |
|
282 // then we need to construct inline boxes as necessary to properly enclose the |
|
283 // run's inline box. |
|
284 if (!parentBox || parentBox->renderer() != r->m_object->parent()) |
|
285 // Create new inline boxes all the way back to the appropriate insertion point. |
|
286 parentBox = createLineBoxes(r->m_object->parent(), firstLine); |
|
287 |
|
288 // Append the inline box to this line. |
|
289 parentBox->addToLine(box); |
|
290 |
|
291 bool visuallyOrdered = r->m_object->style()->visuallyOrdered(); |
|
292 box->setBidiLevel(visuallyOrdered ? 0 : r->level()); |
|
293 |
|
294 if (box->isInlineTextBox()) { |
|
295 InlineTextBox* text = static_cast<InlineTextBox*>(box); |
|
296 text->setStart(r->m_start); |
|
297 text->setLen(r->m_stop - r->m_start); |
|
298 text->m_dirOverride = r->dirOverride(visuallyOrdered); |
|
299 if (r->m_hasHyphen) |
|
300 text->setHasHyphen(true); |
|
301 } |
|
302 } |
|
303 |
|
304 // We should have a root inline box. It should be unconstructed and |
|
305 // be the last continuation of our line list. |
|
306 ASSERT(lastLineBox() && !lastLineBox()->isConstructed()); |
|
307 |
|
308 // Set bits on our inline flow boxes that indicate which sides should |
|
309 // paint borders/margins/padding. This knowledge will ultimately be used when |
|
310 // we determine the horizontal positions and widths of all the inline boxes on |
|
311 // the line. |
|
312 lastLineBox()->determineSpacingForFlowBoxes(lastLine, endObject); |
|
313 |
|
314 // Now mark the line boxes as being constructed. |
|
315 lastLineBox()->setConstructed(); |
|
316 |
|
317 // Return the last line. |
|
318 return lastRootBox(); |
|
319 } |
|
320 |
|
321 void RenderBlock::computeHorizontalPositionsForLine(RootInlineBox* lineBox, bool firstLine, BidiRun* firstRun, BidiRun* trailingSpaceRun, bool reachedEnd, GlyphOverflowAndFallbackFontsMap& textBoxDataMap) |
|
322 { |
|
323 // First determine our total width. |
|
324 int availableWidth = lineWidth(height(), firstLine); |
|
325 int totWidth = lineBox->getFlowSpacingWidth(); |
|
326 bool needsWordSpacing = false; |
|
327 unsigned numSpaces = 0; |
|
328 ETextAlign textAlign = style()->textAlign(); |
|
329 |
|
330 for (BidiRun* r = firstRun; r; r = r->next()) { |
|
331 if (!r->m_box || r->m_object->isPositioned() || r->m_box->isLineBreak()) |
|
332 continue; // Positioned objects are only participating to figure out their |
|
333 // correct static x position. They have no effect on the width. |
|
334 // Similarly, line break boxes have no effect on the width. |
|
335 if (r->m_object->isText()) { |
|
336 RenderText* rt = toRenderText(r->m_object); |
|
337 |
|
338 if (textAlign == JUSTIFY && r != trailingSpaceRun) { |
|
339 const UChar* characters = rt->characters(); |
|
340 for (int i = r->m_start; i < r->m_stop; i++) { |
|
341 UChar c = characters[i]; |
|
342 if (c == ' ' || c == '\n' || c == '\t') |
|
343 numSpaces++; |
|
344 } |
|
345 } |
|
346 |
|
347 if (int length = rt->textLength()) { |
|
348 if (!r->m_start && needsWordSpacing && isSpaceOrNewline(rt->characters()[r->m_start])) |
|
349 totWidth += rt->style(firstLine)->font().wordSpacing(); |
|
350 needsWordSpacing = !isSpaceOrNewline(rt->characters()[r->m_stop - 1]) && r->m_stop == length; |
|
351 } |
|
352 HashSet<const SimpleFontData*> fallbackFonts; |
|
353 GlyphOverflow glyphOverflow; |
|
354 int hyphenWidth = 0; |
|
355 if (static_cast<InlineTextBox*>(r->m_box)->hasHyphen()) { |
|
356 const AtomicString& hyphenString = rt->style()->hyphenString(); |
|
357 hyphenWidth = rt->style(firstLine)->font().width(TextRun(hyphenString.characters(), hyphenString.length())); |
|
358 } |
|
359 r->m_box->setWidth(rt->width(r->m_start, r->m_stop - r->m_start, totWidth, firstLine, &fallbackFonts, &glyphOverflow) + hyphenWidth); |
|
360 if (!fallbackFonts.isEmpty()) { |
|
361 ASSERT(r->m_box->isText()); |
|
362 GlyphOverflowAndFallbackFontsMap::iterator it = textBoxDataMap.add(static_cast<InlineTextBox*>(r->m_box), make_pair(Vector<const SimpleFontData*>(), GlyphOverflow())).first; |
|
363 ASSERT(it->second.first.isEmpty()); |
|
364 copyToVector(fallbackFonts, it->second.first); |
|
365 } |
|
366 if ((glyphOverflow.top || glyphOverflow.bottom || glyphOverflow.left || glyphOverflow.right)) { |
|
367 ASSERT(r->m_box->isText()); |
|
368 GlyphOverflowAndFallbackFontsMap::iterator it = textBoxDataMap.add(static_cast<InlineTextBox*>(r->m_box), make_pair(Vector<const SimpleFontData*>(), GlyphOverflow())).first; |
|
369 it->second.second = glyphOverflow; |
|
370 } |
|
371 } else if (!r->m_object->isRenderInline()) { |
|
372 RenderBox* renderBox = toRenderBox(r->m_object); |
|
373 renderBox->calcWidth(); |
|
374 r->m_box->setWidth(renderBox->width()); |
|
375 totWidth += renderBox->marginLeft() + renderBox->marginRight(); |
|
376 } |
|
377 |
|
378 totWidth += r->m_box->width(); |
|
379 } |
|
380 |
|
381 // Armed with the total width of the line (without justification), |
|
382 // we now examine our text-align property in order to determine where to position the |
|
383 // objects horizontally. The total width of the line can be increased if we end up |
|
384 // justifying text. |
|
385 int x = leftOffset(height(), firstLine); |
|
386 switch (textAlign) { |
|
387 case LEFT: |
|
388 case WEBKIT_LEFT: |
|
389 // The direction of the block should determine what happens with wide lines. In |
|
390 // particular with RTL blocks, wide lines should still spill out to the left. |
|
391 if (style()->direction() == LTR) { |
|
392 if (totWidth > availableWidth && trailingSpaceRun) |
|
393 trailingSpaceRun->m_box->setWidth(max(0, trailingSpaceRun->m_box->width() - totWidth + availableWidth)); |
|
394 } else { |
|
395 if (trailingSpaceRun) |
|
396 trailingSpaceRun->m_box->setWidth(0); |
|
397 else if (totWidth > availableWidth) |
|
398 x -= (totWidth - availableWidth); |
|
399 } |
|
400 break; |
|
401 case JUSTIFY: |
|
402 if (numSpaces && !reachedEnd && !lineBox->endsWithBreak()) { |
|
403 if (trailingSpaceRun) { |
|
404 totWidth -= trailingSpaceRun->m_box->width(); |
|
405 trailingSpaceRun->m_box->setWidth(0); |
|
406 } |
|
407 break; |
|
408 } |
|
409 // fall through |
|
410 case TAAUTO: |
|
411 numSpaces = 0; |
|
412 // for right to left fall through to right aligned |
|
413 if (style()->direction() == LTR) { |
|
414 if (totWidth > availableWidth && trailingSpaceRun) |
|
415 trailingSpaceRun->m_box->setWidth(max(0, trailingSpaceRun->m_box->width() - totWidth + availableWidth)); |
|
416 break; |
|
417 } |
|
418 case RIGHT: |
|
419 case WEBKIT_RIGHT: |
|
420 // Wide lines spill out of the block based off direction. |
|
421 // So even if text-align is right, if direction is LTR, wide lines should overflow out of the right |
|
422 // side of the block. |
|
423 if (style()->direction() == LTR) { |
|
424 if (trailingSpaceRun) { |
|
425 totWidth -= trailingSpaceRun->m_box->width(); |
|
426 trailingSpaceRun->m_box->setWidth(0); |
|
427 } |
|
428 if (totWidth < availableWidth) |
|
429 x += availableWidth - totWidth; |
|
430 } else { |
|
431 if (totWidth > availableWidth && trailingSpaceRun) { |
|
432 trailingSpaceRun->m_box->setWidth(max(0, trailingSpaceRun->m_box->width() - totWidth + availableWidth)); |
|
433 totWidth -= trailingSpaceRun->m_box->width(); |
|
434 } else |
|
435 x += availableWidth - totWidth; |
|
436 } |
|
437 break; |
|
438 case CENTER: |
|
439 case WEBKIT_CENTER: |
|
440 int trailingSpaceWidth = 0; |
|
441 if (trailingSpaceRun) { |
|
442 totWidth -= trailingSpaceRun->m_box->width(); |
|
443 trailingSpaceWidth = min(trailingSpaceRun->m_box->width(), (availableWidth - totWidth + 1) / 2); |
|
444 trailingSpaceRun->m_box->setWidth(max(0, trailingSpaceWidth)); |
|
445 } |
|
446 if (style()->direction() == LTR) |
|
447 x += max((availableWidth - totWidth) / 2, 0); |
|
448 else |
|
449 x += totWidth > availableWidth ? (availableWidth - totWidth) : (availableWidth - totWidth) / 2 - trailingSpaceWidth; |
|
450 break; |
|
451 } |
|
452 |
|
453 if (numSpaces) { |
|
454 for (BidiRun* r = firstRun; r; r = r->next()) { |
|
455 if (!r->m_box || r == trailingSpaceRun) |
|
456 continue; |
|
457 |
|
458 int spaceAdd = 0; |
|
459 if (r->m_object->isText()) { |
|
460 unsigned spaces = 0; |
|
461 const UChar* characters = toRenderText(r->m_object)->characters(); |
|
462 for (int i = r->m_start; i < r->m_stop; i++) { |
|
463 UChar c = characters[i]; |
|
464 if (c == ' ' || c == '\n' || c == '\t') |
|
465 spaces++; |
|
466 } |
|
467 |
|
468 ASSERT(spaces <= numSpaces); |
|
469 |
|
470 // Only justify text if whitespace is collapsed. |
|
471 if (r->m_object->style()->collapseWhiteSpace()) { |
|
472 spaceAdd = (availableWidth - totWidth) * spaces / numSpaces; |
|
473 static_cast<InlineTextBox*>(r->m_box)->setSpaceAdd(spaceAdd); |
|
474 totWidth += spaceAdd; |
|
475 } |
|
476 numSpaces -= spaces; |
|
477 if (!numSpaces) |
|
478 break; |
|
479 } |
|
480 } |
|
481 } |
|
482 |
|
483 // The widths of all runs are now known. We can now place every inline box (and |
|
484 // compute accurate widths for the inline flow boxes). |
|
485 needsWordSpacing = false; |
|
486 lineBox->placeBoxesHorizontally(x, needsWordSpacing, textBoxDataMap); |
|
487 } |
|
488 |
|
489 void RenderBlock::computeVerticalPositionsForLine(RootInlineBox* lineBox, BidiRun* firstRun, GlyphOverflowAndFallbackFontsMap& textBoxDataMap) |
|
490 { |
|
491 setHeight(lineBox->verticallyAlignBoxes(height(), textBoxDataMap)); |
|
492 lineBox->setBlockHeight(height()); |
|
493 |
|
494 // Now make sure we place replaced render objects correctly. |
|
495 for (BidiRun* r = firstRun; r; r = r->next()) { |
|
496 ASSERT(r->m_box); |
|
497 if (!r->m_box) |
|
498 continue; // Skip runs with no line boxes. |
|
499 |
|
500 // Align positioned boxes with the top of the line box. This is |
|
501 // a reasonable approximation of an appropriate y position. |
|
502 if (r->m_object->isPositioned()) |
|
503 r->m_box->setY(height()); |
|
504 |
|
505 // Position is used to properly position both replaced elements and |
|
506 // to update the static normal flow x/y of positioned elements. |
|
507 if (r->m_object->isText()) |
|
508 toRenderText(r->m_object)->positionLineBox(r->m_box); |
|
509 else if (r->m_object->isBox()) |
|
510 toRenderBox(r->m_object)->positionLineBox(r->m_box); |
|
511 } |
|
512 // Positioned objects and zero-length text nodes destroy their boxes in |
|
513 // position(), which unnecessarily dirties the line. |
|
514 lineBox->markDirty(false); |
|
515 } |
|
516 |
|
517 static inline bool isCollapsibleSpace(UChar character, RenderText* renderer) |
|
518 { |
|
519 if (character == ' ' || character == '\t' || character == softHyphen) |
|
520 return true; |
|
521 if (character == '\n') |
|
522 return !renderer->style()->preserveNewline(); |
|
523 if (character == noBreakSpace) |
|
524 return renderer->style()->nbspMode() == SPACE; |
|
525 return false; |
|
526 } |
|
527 |
|
528 void RenderBlock::layoutInlineChildren(bool relayoutChildren, int& repaintTop, int& repaintBottom) |
|
529 { |
|
530 bool useRepaintBounds = false; |
|
531 |
|
532 m_overflow.clear(); |
|
533 |
|
534 setHeight(borderTop() + paddingTop()); |
|
535 int toAdd = borderBottom() + paddingBottom() + horizontalScrollbarHeight(); |
|
536 |
|
537 // Figure out if we should clear out our line boxes. |
|
538 // FIXME: Handle resize eventually! |
|
539 bool fullLayout = !firstLineBox() || selfNeedsLayout() || relayoutChildren; |
|
540 if (fullLayout) |
|
541 lineBoxes()->deleteLineBoxes(renderArena()); |
|
542 |
|
543 // Text truncation only kicks in if your overflow isn't visible and your text-overflow-mode isn't |
|
544 // clip. |
|
545 // FIXME: CSS3 says that descendants that are clipped must also know how to truncate. This is insanely |
|
546 // difficult to figure out (especially in the middle of doing layout), and is really an esoteric pile of nonsense |
|
547 // anyway, so we won't worry about following the draft here. |
|
548 bool hasTextOverflow = style()->textOverflow() && hasOverflowClip(); |
|
549 |
|
550 // Walk all the lines and delete our ellipsis line boxes if they exist. |
|
551 if (hasTextOverflow) |
|
552 deleteEllipsisLineBoxes(); |
|
553 |
|
554 if (firstChild()) { |
|
555 // layout replaced elements |
|
556 bool endOfInline = false; |
|
557 RenderObject* o = bidiFirst(this, 0, false); |
|
558 Vector<FloatWithRect> floats; |
|
559 bool hasInlineChild = false; |
|
560 while (o) { |
|
561 if (o->isReplaced() || o->isFloating() || o->isPositioned()) { |
|
562 RenderBox* box = toRenderBox(o); |
|
563 |
|
564 if (relayoutChildren || o->style()->width().isPercent() || o->style()->height().isPercent()) |
|
565 o->setChildNeedsLayout(true, false); |
|
566 |
|
567 // If relayoutChildren is set and we have percentage padding, we also need to invalidate the child's pref widths. |
|
568 if (relayoutChildren && (o->style()->paddingLeft().isPercent() || o->style()->paddingRight().isPercent())) |
|
569 o->setPrefWidthsDirty(true, false); |
|
570 |
|
571 if (o->isPositioned()) |
|
572 o->containingBlock()->insertPositionedObject(box); |
|
573 else { |
|
574 if (o->isFloating()) |
|
575 floats.append(FloatWithRect(box)); |
|
576 else if (fullLayout || o->needsLayout()) // Replaced elements |
|
577 toRenderBox(o)->dirtyLineBoxes(fullLayout); |
|
578 |
|
579 o->layoutIfNeeded(); |
|
580 } |
|
581 } else if (o->isText() || (o->isRenderInline() && !endOfInline)) { |
|
582 hasInlineChild = true; |
|
583 if (fullLayout || o->selfNeedsLayout()) |
|
584 dirtyLineBoxesForRenderer(o, fullLayout); |
|
585 o->setNeedsLayout(false); |
|
586 if (!o->isText()) |
|
587 toRenderInline(o)->invalidateVerticalPosition(); // FIXME: Should do better here and not always invalidate everything. |
|
588 } |
|
589 o = bidiNext(this, o, 0, false, &endOfInline); |
|
590 } |
|
591 |
|
592 // We want to skip ahead to the first dirty line |
|
593 InlineBidiResolver resolver; |
|
594 unsigned floatIndex; |
|
595 bool firstLine = true; |
|
596 bool previousLineBrokeCleanly = true; |
|
597 RootInlineBox* startLine = determineStartPosition(firstLine, fullLayout, previousLineBrokeCleanly, resolver, floats, floatIndex); |
|
598 |
|
599 if (fullLayout && hasInlineChild && !selfNeedsLayout()) { |
|
600 setNeedsLayout(true, false); // Mark ourselves as needing a full layout. This way we'll repaint like |
|
601 // we're supposed to. |
|
602 RenderView* v = view(); |
|
603 if (v && !v->doingFullRepaint() && hasLayer()) { |
|
604 // Because we waited until we were already inside layout to discover |
|
605 // that the block really needed a full layout, we missed our chance to repaint the layer |
|
606 // before layout started. Luckily the layer has cached the repaint rect for its original |
|
607 // position and size, and so we can use that to make a repaint happen now. |
|
608 repaintUsingContainer(containerForRepaint(), layer()->repaintRect()); |
|
609 } |
|
610 } |
|
611 |
|
612 FloatingObject* lastFloat = m_floatingObjects ? m_floatingObjects->last() : 0; |
|
613 |
|
614 LineMidpointState& lineMidpointState = resolver.midpointState(); |
|
615 |
|
616 // We also find the first clean line and extract these lines. We will add them back |
|
617 // if we determine that we're able to synchronize after handling all our dirty lines. |
|
618 InlineIterator cleanLineStart; |
|
619 BidiStatus cleanLineBidiStatus; |
|
620 int endLineYPos = 0; |
|
621 RootInlineBox* endLine = (fullLayout || !startLine) ? |
|
622 0 : determineEndPosition(startLine, cleanLineStart, cleanLineBidiStatus, endLineYPos); |
|
623 |
|
624 if (startLine) { |
|
625 useRepaintBounds = true; |
|
626 repaintTop = height(); |
|
627 repaintBottom = height(); |
|
628 RenderArena* arena = renderArena(); |
|
629 RootInlineBox* box = startLine; |
|
630 while (box) { |
|
631 repaintTop = min(repaintTop, box->topVisibleOverflow()); |
|
632 repaintBottom = max(repaintBottom, box->bottomVisibleOverflow()); |
|
633 RootInlineBox* next = box->nextRootBox(); |
|
634 box->deleteLine(arena); |
|
635 box = next; |
|
636 } |
|
637 } |
|
638 |
|
639 InlineIterator end = resolver.position(); |
|
640 |
|
641 if (!fullLayout && lastRootBox() && lastRootBox()->endsWithBreak()) { |
|
642 // If the last line before the start line ends with a line break that clear floats, |
|
643 // adjust the height accordingly. |
|
644 // A line break can be either the first or the last object on a line, depending on its direction. |
|
645 if (InlineBox* lastLeafChild = lastRootBox()->lastLeafChild()) { |
|
646 RenderObject* lastObject = lastLeafChild->renderer(); |
|
647 if (!lastObject->isBR()) |
|
648 lastObject = lastRootBox()->firstLeafChild()->renderer(); |
|
649 if (lastObject->isBR()) { |
|
650 EClear clear = lastObject->style()->clear(); |
|
651 if (clear != CNONE) |
|
652 newLine(clear); |
|
653 } |
|
654 } |
|
655 } |
|
656 |
|
657 bool endLineMatched = false; |
|
658 bool checkForEndLineMatch = endLine; |
|
659 bool checkForFloatsFromLastLine = false; |
|
660 |
|
661 bool isLineEmpty = true; |
|
662 |
|
663 while (!end.atEnd()) { |
|
664 // FIXME: Is this check necessary before the first iteration or can it be moved to the end? |
|
665 if (checkForEndLineMatch && (endLineMatched = matchedEndLine(resolver, cleanLineStart, cleanLineBidiStatus, endLine, endLineYPos, repaintBottom, repaintTop))) |
|
666 break; |
|
667 |
|
668 lineMidpointState.reset(); |
|
669 |
|
670 isLineEmpty = true; |
|
671 |
|
672 EClear clear = CNONE; |
|
673 bool hyphenated; |
|
674 end = findNextLineBreak(resolver, firstLine, isLineEmpty, previousLineBrokeCleanly, hyphenated, &clear); |
|
675 if (resolver.position().atEnd()) { |
|
676 resolver.deleteRuns(); |
|
677 checkForFloatsFromLastLine = true; |
|
678 break; |
|
679 } |
|
680 ASSERT(end != resolver.position()); |
|
681 |
|
682 if (!isLineEmpty) { |
|
683 resolver.createBidiRunsForLine(end, style()->visuallyOrdered(), previousLineBrokeCleanly); |
|
684 ASSERT(resolver.position() == end); |
|
685 |
|
686 BidiRun* trailingSpaceRun = 0; |
|
687 if (!previousLineBrokeCleanly && resolver.runCount() && resolver.logicallyLastRun()->m_object->style()->breakOnlyAfterWhiteSpace() |
|
688 && resolver.logicallyLastRun()->m_object->style()->autoWrap()) { |
|
689 trailingSpaceRun = resolver.logicallyLastRun(); |
|
690 RenderObject* lastObject = trailingSpaceRun->m_object; |
|
691 if (lastObject->isText()) { |
|
692 RenderText* lastText = toRenderText(lastObject); |
|
693 const UChar* characters = lastText->characters(); |
|
694 int firstSpace = trailingSpaceRun->stop(); |
|
695 while (firstSpace > trailingSpaceRun->start()) { |
|
696 UChar current = characters[firstSpace - 1]; |
|
697 if (!isCollapsibleSpace(current, lastText)) |
|
698 break; |
|
699 firstSpace--; |
|
700 } |
|
701 if (firstSpace == trailingSpaceRun->stop()) |
|
702 trailingSpaceRun = 0; |
|
703 else { |
|
704 TextDirection direction = style()->direction(); |
|
705 bool shouldReorder = trailingSpaceRun != (direction == LTR ? resolver.lastRun() : resolver.firstRun()); |
|
706 if (firstSpace != trailingSpaceRun->start()) { |
|
707 BidiContext* baseContext = resolver.context(); |
|
708 while (BidiContext* parent = baseContext->parent()) |
|
709 baseContext = parent; |
|
710 |
|
711 BidiRun* newTrailingRun = new (renderArena()) BidiRun(firstSpace, trailingSpaceRun->m_stop, trailingSpaceRun->m_object, baseContext, OtherNeutral); |
|
712 trailingSpaceRun->m_stop = firstSpace; |
|
713 if (direction == LTR) |
|
714 resolver.addRun(newTrailingRun); |
|
715 else |
|
716 resolver.prependRun(newTrailingRun); |
|
717 trailingSpaceRun = newTrailingRun; |
|
718 shouldReorder = false; |
|
719 } |
|
720 if (shouldReorder) { |
|
721 if (direction == LTR) { |
|
722 resolver.moveRunToEnd(trailingSpaceRun); |
|
723 trailingSpaceRun->m_level = 0; |
|
724 } else { |
|
725 resolver.moveRunToBeginning(trailingSpaceRun); |
|
726 trailingSpaceRun->m_level = 1; |
|
727 } |
|
728 } |
|
729 } |
|
730 } else |
|
731 trailingSpaceRun = 0; |
|
732 } |
|
733 |
|
734 // Now that the runs have been ordered, we create the line boxes. |
|
735 // At the same time we figure out where border/padding/margin should be applied for |
|
736 // inline flow boxes. |
|
737 |
|
738 RootInlineBox* lineBox = 0; |
|
739 if (resolver.runCount()) { |
|
740 if (hyphenated) |
|
741 resolver.logicallyLastRun()->m_hasHyphen = true; |
|
742 lineBox = constructLine(resolver.runCount(), resolver.firstRun(), resolver.lastRun(), firstLine, !end.obj, end.obj && !end.pos ? end.obj : 0); |
|
743 if (lineBox) { |
|
744 lineBox->setEndsWithBreak(previousLineBrokeCleanly); |
|
745 |
|
746 #if ENABLE(SVG) |
|
747 bool isSVGRootInlineBox = lineBox->isSVGRootInlineBox(); |
|
748 #else |
|
749 bool isSVGRootInlineBox = false; |
|
750 #endif |
|
751 |
|
752 GlyphOverflowAndFallbackFontsMap textBoxDataMap; |
|
753 |
|
754 // Now we position all of our text runs horizontally. |
|
755 if (!isSVGRootInlineBox) |
|
756 computeHorizontalPositionsForLine(lineBox, firstLine, resolver.firstRun(), trailingSpaceRun, end.atEnd(), textBoxDataMap); |
|
757 |
|
758 // Now position our text runs vertically. |
|
759 computeVerticalPositionsForLine(lineBox, resolver.firstRun(), textBoxDataMap); |
|
760 |
|
761 #if ENABLE(SVG) |
|
762 // SVG text layout code computes vertical & horizontal positions on its own. |
|
763 // Note that we still need to execute computeVerticalPositionsForLine() as |
|
764 // it calls InlineTextBox::positionLineBox(), which tracks whether the box |
|
765 // contains reversed text or not. If we wouldn't do that editing and thus |
|
766 // text selection in RTL boxes would not work as expected. |
|
767 if (isSVGRootInlineBox) { |
|
768 ASSERT(isSVGText()); |
|
769 static_cast<SVGRootInlineBox*>(lineBox)->computePerCharacterLayoutInformation(); |
|
770 } |
|
771 #endif |
|
772 |
|
773 #if PLATFORM(MAC) |
|
774 // Highlight acts as an overflow inflation. |
|
775 if (style()->highlight() != nullAtom) |
|
776 lineBox->addHighlightOverflow(); |
|
777 #endif |
|
778 } |
|
779 } |
|
780 |
|
781 resolver.deleteRuns(); |
|
782 |
|
783 if (lineBox) { |
|
784 lineBox->setLineBreakInfo(end.obj, end.pos, resolver.status()); |
|
785 if (useRepaintBounds) { |
|
786 repaintTop = min(repaintTop, lineBox->topVisibleOverflow()); |
|
787 repaintBottom = max(repaintBottom, lineBox->bottomVisibleOverflow()); |
|
788 } |
|
789 } |
|
790 |
|
791 firstLine = false; |
|
792 newLine(clear); |
|
793 } |
|
794 |
|
795 if (m_floatingObjects && lastRootBox()) { |
|
796 if (lastFloat) { |
|
797 for (FloatingObject* f = m_floatingObjects->last(); f != lastFloat; f = m_floatingObjects->prev()) { |
|
798 } |
|
799 m_floatingObjects->next(); |
|
800 } else |
|
801 m_floatingObjects->first(); |
|
802 for (FloatingObject* f = m_floatingObjects->current(); f; f = m_floatingObjects->next()) { |
|
803 lastRootBox()->floats().append(f->m_renderer); |
|
804 ASSERT(f->m_renderer == floats[floatIndex].object); |
|
805 // If a float's geometry has changed, give up on syncing with clean lines. |
|
806 if (floats[floatIndex].rect != IntRect(f->m_left, f->m_top, f->m_width, f->m_bottom - f->m_top)) |
|
807 checkForEndLineMatch = false; |
|
808 floatIndex++; |
|
809 } |
|
810 lastFloat = m_floatingObjects->last(); |
|
811 } |
|
812 |
|
813 lineMidpointState.reset(); |
|
814 resolver.setPosition(end); |
|
815 } |
|
816 |
|
817 if (endLine) { |
|
818 if (endLineMatched) { |
|
819 // Attach all the remaining lines, and then adjust their y-positions as needed. |
|
820 int delta = height() - endLineYPos; |
|
821 for (RootInlineBox* line = endLine; line; line = line->nextRootBox()) { |
|
822 line->attachLine(); |
|
823 if (delta) { |
|
824 repaintTop = min(repaintTop, line->topVisibleOverflow() + min(delta, 0)); |
|
825 repaintBottom = max(repaintBottom, line->bottomVisibleOverflow() + max(delta, 0)); |
|
826 line->adjustPosition(0, delta); |
|
827 } |
|
828 if (Vector<RenderBox*>* cleanLineFloats = line->floatsPtr()) { |
|
829 Vector<RenderBox*>::iterator end = cleanLineFloats->end(); |
|
830 for (Vector<RenderBox*>::iterator f = cleanLineFloats->begin(); f != end; ++f) { |
|
831 int floatTop = (*f)->y() - (*f)->marginTop(); |
|
832 insertFloatingObject(*f); |
|
833 setHeight(floatTop + delta); |
|
834 positionNewFloats(); |
|
835 } |
|
836 } |
|
837 } |
|
838 setHeight(lastRootBox()->blockHeight()); |
|
839 } else { |
|
840 // Delete all the remaining lines. |
|
841 RootInlineBox* line = endLine; |
|
842 RenderArena* arena = renderArena(); |
|
843 while (line) { |
|
844 repaintTop = min(repaintTop, line->topVisibleOverflow()); |
|
845 repaintBottom = max(repaintBottom, line->bottomVisibleOverflow()); |
|
846 RootInlineBox* next = line->nextRootBox(); |
|
847 line->deleteLine(arena); |
|
848 line = next; |
|
849 } |
|
850 } |
|
851 } |
|
852 if (m_floatingObjects && (checkForFloatsFromLastLine || positionNewFloats()) && lastRootBox()) { |
|
853 // In case we have a float on the last line, it might not be positioned up to now. |
|
854 // This has to be done before adding in the bottom border/padding, or the float will |
|
855 // include the padding incorrectly. -dwh |
|
856 if (checkForFloatsFromLastLine) { |
|
857 int bottomVisualOverflow = lastRootBox()->bottomVisualOverflow(); |
|
858 int bottomLayoutOverflow = lastRootBox()->bottomLayoutOverflow(); |
|
859 TrailingFloatsRootInlineBox* trailingFloatsLineBox = new (renderArena()) TrailingFloatsRootInlineBox(this); |
|
860 m_lineBoxes.appendLineBox(trailingFloatsLineBox); |
|
861 trailingFloatsLineBox->setConstructed(); |
|
862 GlyphOverflowAndFallbackFontsMap textBoxDataMap; |
|
863 trailingFloatsLineBox->verticallyAlignBoxes(height(), textBoxDataMap); |
|
864 trailingFloatsLineBox->setVerticalOverflowPositions(height(), bottomLayoutOverflow, height(), bottomVisualOverflow, 0); |
|
865 trailingFloatsLineBox->setBlockHeight(height()); |
|
866 } |
|
867 if (lastFloat) { |
|
868 for (FloatingObject* f = m_floatingObjects->last(); f != lastFloat; f = m_floatingObjects->prev()) { |
|
869 } |
|
870 m_floatingObjects->next(); |
|
871 } else |
|
872 m_floatingObjects->first(); |
|
873 for (FloatingObject* f = m_floatingObjects->current(); f; f = m_floatingObjects->next()) |
|
874 lastRootBox()->floats().append(f->m_renderer); |
|
875 lastFloat = m_floatingObjects->last(); |
|
876 } |
|
877 size_t floatCount = floats.size(); |
|
878 // Floats that did not have layout did not repaint when we laid them out. They would have |
|
879 // painted by now if they had moved, but if they stayed at (0, 0), they still need to be |
|
880 // painted. |
|
881 for (size_t i = 0; i < floatCount; ++i) { |
|
882 if (!floats[i].everHadLayout) { |
|
883 RenderBox* f = floats[i].object; |
|
884 if (!f->x() && !f->y() && f->checkForRepaintDuringLayout()) |
|
885 f->repaint(); |
|
886 } |
|
887 } |
|
888 } |
|
889 |
|
890 // Now add in the bottom border/padding. |
|
891 setHeight(height() + toAdd); |
|
892 |
|
893 if (!firstLineBox() && hasLineIfEmpty()) |
|
894 setHeight(height() + lineHeight(true, true)); |
|
895 |
|
896 // See if we have any lines that spill out of our block. If we do, then we will possibly need to |
|
897 // truncate text. |
|
898 if (hasTextOverflow) |
|
899 checkLinesForTextOverflow(); |
|
900 } |
|
901 |
|
902 RootInlineBox* RenderBlock::determineStartPosition(bool& firstLine, bool& fullLayout, bool& previousLineBrokeCleanly, |
|
903 InlineBidiResolver& resolver, Vector<FloatWithRect>& floats, unsigned& numCleanFloats) |
|
904 { |
|
905 RootInlineBox* curr = 0; |
|
906 RootInlineBox* last = 0; |
|
907 |
|
908 bool dirtiedByFloat = false; |
|
909 if (!fullLayout) { |
|
910 size_t floatIndex = 0; |
|
911 for (curr = firstRootBox(); curr && !curr->isDirty(); curr = curr->nextRootBox()) { |
|
912 if (Vector<RenderBox*>* cleanLineFloats = curr->floatsPtr()) { |
|
913 Vector<RenderBox*>::iterator end = cleanLineFloats->end(); |
|
914 for (Vector<RenderBox*>::iterator o = cleanLineFloats->begin(); o != end; ++o) { |
|
915 RenderBox* f = *o; |
|
916 IntSize newSize(f->width() + f->marginLeft() +f->marginRight(), f->height() + f->marginTop() + f->marginBottom()); |
|
917 ASSERT(floatIndex < floats.size()); |
|
918 if (floats[floatIndex].object != f) { |
|
919 // A new float has been inserted before this line or before its last known float. |
|
920 // Just do a full layout. |
|
921 fullLayout = true; |
|
922 break; |
|
923 } |
|
924 if (floats[floatIndex].rect.size() != newSize) { |
|
925 int floatTop = floats[floatIndex].rect.y(); |
|
926 curr->markDirty(); |
|
927 markLinesDirtyInVerticalRange(curr->blockHeight(), floatTop + max(floats[floatIndex].rect.height(), newSize.height()), curr); |
|
928 floats[floatIndex].rect.setSize(newSize); |
|
929 dirtiedByFloat = true; |
|
930 } |
|
931 floatIndex++; |
|
932 } |
|
933 } |
|
934 if (dirtiedByFloat || fullLayout) |
|
935 break; |
|
936 } |
|
937 // Check if a new float has been inserted after the last known float. |
|
938 if (!curr && floatIndex < floats.size()) |
|
939 fullLayout = true; |
|
940 } |
|
941 |
|
942 if (fullLayout) { |
|
943 // Nuke all our lines. |
|
944 if (firstRootBox()) { |
|
945 RenderArena* arena = renderArena(); |
|
946 curr = firstRootBox(); |
|
947 while (curr) { |
|
948 RootInlineBox* next = curr->nextRootBox(); |
|
949 curr->deleteLine(arena); |
|
950 curr = next; |
|
951 } |
|
952 ASSERT(!firstLineBox() && !lastLineBox()); |
|
953 } |
|
954 } else { |
|
955 if (curr) { |
|
956 // We have a dirty line. |
|
957 if (RootInlineBox* prevRootBox = curr->prevRootBox()) { |
|
958 // We have a previous line. |
|
959 if (!dirtiedByFloat && (!prevRootBox->endsWithBreak() || (prevRootBox->lineBreakObj()->isText() && prevRootBox->lineBreakPos() >= toRenderText(prevRootBox->lineBreakObj())->textLength()))) |
|
960 // The previous line didn't break cleanly or broke at a newline |
|
961 // that has been deleted, so treat it as dirty too. |
|
962 curr = prevRootBox; |
|
963 } |
|
964 } else { |
|
965 // No dirty lines were found. |
|
966 // If the last line didn't break cleanly, treat it as dirty. |
|
967 if (lastRootBox() && !lastRootBox()->endsWithBreak()) |
|
968 curr = lastRootBox(); |
|
969 } |
|
970 |
|
971 // If we have no dirty lines, then last is just the last root box. |
|
972 last = curr ? curr->prevRootBox() : lastRootBox(); |
|
973 } |
|
974 |
|
975 numCleanFloats = 0; |
|
976 if (!floats.isEmpty()) { |
|
977 int savedHeight = height(); |
|
978 // Restore floats from clean lines. |
|
979 RootInlineBox* line = firstRootBox(); |
|
980 while (line != curr) { |
|
981 if (Vector<RenderBox*>* cleanLineFloats = line->floatsPtr()) { |
|
982 Vector<RenderBox*>::iterator end = cleanLineFloats->end(); |
|
983 for (Vector<RenderBox*>::iterator f = cleanLineFloats->begin(); f != end; ++f) { |
|
984 insertFloatingObject(*f); |
|
985 setHeight((*f)->y() - (*f)->marginTop()); |
|
986 positionNewFloats(); |
|
987 ASSERT(floats[numCleanFloats].object == *f); |
|
988 numCleanFloats++; |
|
989 } |
|
990 } |
|
991 line = line->nextRootBox(); |
|
992 } |
|
993 setHeight(savedHeight); |
|
994 } |
|
995 |
|
996 firstLine = !last; |
|
997 previousLineBrokeCleanly = !last || last->endsWithBreak(); |
|
998 |
|
999 RenderObject* startObj; |
|
1000 int pos = 0; |
|
1001 if (last) { |
|
1002 setHeight(last->blockHeight()); |
|
1003 startObj = last->lineBreakObj(); |
|
1004 pos = last->lineBreakPos(); |
|
1005 resolver.setStatus(last->lineBreakBidiStatus()); |
|
1006 } else { |
|
1007 bool ltr = style()->direction() == LTR |
|
1008 #if ENABLE(SVG) |
|
1009 || (style()->unicodeBidi() == UBNormal && isSVGText()) |
|
1010 #endif |
|
1011 ; |
|
1012 |
|
1013 Direction direction = ltr ? LeftToRight : RightToLeft; |
|
1014 resolver.setLastStrongDir(direction); |
|
1015 resolver.setLastDir(direction); |
|
1016 resolver.setEorDir(direction); |
|
1017 resolver.setContext(BidiContext::create(ltr ? 0 : 1, direction, style()->unicodeBidi() == Override)); |
|
1018 |
|
1019 startObj = bidiFirst(this, &resolver); |
|
1020 } |
|
1021 |
|
1022 resolver.setPosition(InlineIterator(this, startObj, pos)); |
|
1023 |
|
1024 return curr; |
|
1025 } |
|
1026 |
|
1027 RootInlineBox* RenderBlock::determineEndPosition(RootInlineBox* startLine, InlineIterator& cleanLineStart, BidiStatus& cleanLineBidiStatus, int& yPos) |
|
1028 { |
|
1029 RootInlineBox* last = 0; |
|
1030 if (!startLine) |
|
1031 last = 0; |
|
1032 else { |
|
1033 for (RootInlineBox* curr = startLine->nextRootBox(); curr; curr = curr->nextRootBox()) { |
|
1034 if (curr->isDirty()) |
|
1035 last = 0; |
|
1036 else if (!last) |
|
1037 last = curr; |
|
1038 } |
|
1039 } |
|
1040 |
|
1041 if (!last) |
|
1042 return 0; |
|
1043 |
|
1044 RootInlineBox* prev = last->prevRootBox(); |
|
1045 cleanLineStart = InlineIterator(this, prev->lineBreakObj(), prev->lineBreakPos()); |
|
1046 cleanLineBidiStatus = prev->lineBreakBidiStatus(); |
|
1047 yPos = prev->blockHeight(); |
|
1048 |
|
1049 for (RootInlineBox* line = last; line; line = line->nextRootBox()) |
|
1050 line->extractLine(); // Disconnect all line boxes from their render objects while preserving |
|
1051 // their connections to one another. |
|
1052 |
|
1053 return last; |
|
1054 } |
|
1055 |
|
1056 bool RenderBlock::matchedEndLine(const InlineBidiResolver& resolver, const InlineIterator& endLineStart, const BidiStatus& endLineStatus, RootInlineBox*& endLine, int& endYPos, int& repaintBottom, int& repaintTop) |
|
1057 { |
|
1058 if (resolver.position() == endLineStart) { |
|
1059 if (resolver.status() != endLineStatus) |
|
1060 return false; |
|
1061 |
|
1062 int delta = height() - endYPos; |
|
1063 if (!delta || !m_floatingObjects) |
|
1064 return true; |
|
1065 |
|
1066 // See if any floats end in the range along which we want to shift the lines vertically. |
|
1067 int top = min(height(), endYPos); |
|
1068 |
|
1069 RootInlineBox* lastLine = endLine; |
|
1070 while (RootInlineBox* nextLine = lastLine->nextRootBox()) |
|
1071 lastLine = nextLine; |
|
1072 |
|
1073 int bottom = lastLine->blockHeight() + abs(delta); |
|
1074 |
|
1075 for (FloatingObject* f = m_floatingObjects->first(); f; f = m_floatingObjects->next()) { |
|
1076 if (f->m_bottom >= top && f->m_bottom < bottom) |
|
1077 return false; |
|
1078 } |
|
1079 |
|
1080 return true; |
|
1081 } |
|
1082 |
|
1083 // The first clean line doesn't match, but we can check a handful of following lines to try |
|
1084 // to match back up. |
|
1085 static int numLines = 8; // The # of lines we're willing to match against. |
|
1086 RootInlineBox* line = endLine; |
|
1087 for (int i = 0; i < numLines && line; i++, line = line->nextRootBox()) { |
|
1088 if (line->lineBreakObj() == resolver.position().obj && line->lineBreakPos() == resolver.position().pos) { |
|
1089 // We have a match. |
|
1090 if (line->lineBreakBidiStatus() != resolver.status()) |
|
1091 return false; // ...but the bidi state doesn't match. |
|
1092 RootInlineBox* result = line->nextRootBox(); |
|
1093 |
|
1094 // Set our yPos to be the block height of endLine. |
|
1095 if (result) |
|
1096 endYPos = line->blockHeight(); |
|
1097 |
|
1098 int delta = height() - endYPos; |
|
1099 if (delta && m_floatingObjects) { |
|
1100 // See if any floats end in the range along which we want to shift the lines vertically. |
|
1101 int top = min(height(), endYPos); |
|
1102 |
|
1103 RootInlineBox* lastLine = endLine; |
|
1104 while (RootInlineBox* nextLine = lastLine->nextRootBox()) |
|
1105 lastLine = nextLine; |
|
1106 |
|
1107 int bottom = lastLine->blockHeight() + abs(delta); |
|
1108 |
|
1109 for (FloatingObject* f = m_floatingObjects->first(); f; f = m_floatingObjects->next()) { |
|
1110 if (f->m_bottom >= top && f->m_bottom < bottom) |
|
1111 return false; |
|
1112 } |
|
1113 } |
|
1114 |
|
1115 // Now delete the lines that we failed to sync. |
|
1116 RootInlineBox* boxToDelete = endLine; |
|
1117 RenderArena* arena = renderArena(); |
|
1118 while (boxToDelete && boxToDelete != result) { |
|
1119 repaintTop = min(repaintTop, boxToDelete->topVisibleOverflow()); |
|
1120 repaintBottom = max(repaintBottom, boxToDelete->bottomVisibleOverflow()); |
|
1121 RootInlineBox* next = boxToDelete->nextRootBox(); |
|
1122 boxToDelete->deleteLine(arena); |
|
1123 boxToDelete = next; |
|
1124 } |
|
1125 |
|
1126 endLine = result; |
|
1127 return result; |
|
1128 } |
|
1129 } |
|
1130 |
|
1131 return false; |
|
1132 } |
|
1133 |
|
1134 static inline bool skipNonBreakingSpace(const InlineIterator& it, bool isLineEmpty, bool previousLineBrokeCleanly) |
|
1135 { |
|
1136 if (it.obj->style()->nbspMode() != SPACE || it.current() != noBreakSpace) |
|
1137 return false; |
|
1138 |
|
1139 // FIXME: This is bad. It makes nbsp inconsistent with space and won't work correctly |
|
1140 // with m_minWidth/m_maxWidth. |
|
1141 // Do not skip a non-breaking space if it is the first character |
|
1142 // on a line after a clean line break (or on the first line, since previousLineBrokeCleanly starts off |
|
1143 // |true|). |
|
1144 if (isLineEmpty && previousLineBrokeCleanly) |
|
1145 return false; |
|
1146 |
|
1147 return true; |
|
1148 } |
|
1149 |
|
1150 static inline bool shouldCollapseWhiteSpace(const RenderStyle* style, bool isLineEmpty, bool previousLineBrokeCleanly) |
|
1151 { |
|
1152 return style->collapseWhiteSpace() || (style->whiteSpace() == PRE_WRAP && (!isLineEmpty || !previousLineBrokeCleanly)); |
|
1153 } |
|
1154 |
|
1155 static inline bool shouldPreserveNewline(RenderObject* object) |
|
1156 { |
|
1157 #if ENABLE(SVG) |
|
1158 if (object->isSVGInlineText()) |
|
1159 return false; |
|
1160 #endif |
|
1161 |
|
1162 return object->style()->preserveNewline(); |
|
1163 } |
|
1164 |
|
1165 static bool inlineFlowRequiresLineBox(RenderInline* flow) |
|
1166 { |
|
1167 // FIXME: Right now, we only allow line boxes for inlines that are truly empty. |
|
1168 // We need to fix this, though, because at the very least, inlines containing only |
|
1169 // ignorable whitespace should should also have line boxes. |
|
1170 return !flow->firstChild() && flow->hasHorizontalBordersPaddingOrMargin(); |
|
1171 } |
|
1172 |
|
1173 bool RenderBlock::requiresLineBox(const InlineIterator& it, bool isLineEmpty, bool previousLineBrokeCleanly) |
|
1174 { |
|
1175 if (it.obj->isFloatingOrPositioned()) |
|
1176 return false; |
|
1177 |
|
1178 if (it.obj->isRenderInline() && !inlineFlowRequiresLineBox(toRenderInline(it.obj))) |
|
1179 return false; |
|
1180 |
|
1181 if (!shouldCollapseWhiteSpace(it.obj->style(), isLineEmpty, previousLineBrokeCleanly) || it.obj->isBR()) |
|
1182 return true; |
|
1183 |
|
1184 UChar current = it.current(); |
|
1185 return current != ' ' && current != '\t' && current != softHyphen && (current != '\n' || shouldPreserveNewline(it.obj)) |
|
1186 && !skipNonBreakingSpace(it, isLineEmpty, previousLineBrokeCleanly); |
|
1187 } |
|
1188 |
|
1189 bool RenderBlock::generatesLineBoxesForInlineChild(RenderObject* inlineObj, bool isLineEmpty, bool previousLineBrokeCleanly) |
|
1190 { |
|
1191 ASSERT(inlineObj->parent() == this); |
|
1192 |
|
1193 InlineIterator it(this, inlineObj, 0); |
|
1194 while (!it.atEnd() && !requiresLineBox(it, isLineEmpty, previousLineBrokeCleanly)) |
|
1195 it.increment(); |
|
1196 |
|
1197 return !it.atEnd(); |
|
1198 } |
|
1199 |
|
1200 // FIXME: The entire concept of the skipTrailingWhitespace function is flawed, since we really need to be building |
|
1201 // line boxes even for containers that may ultimately collapse away. Otherwise we'll never get positioned |
|
1202 // elements quite right. In other words, we need to build this function's work into the normal line |
|
1203 // object iteration process. |
|
1204 // NB. this function will insert any floating elements that would otherwise |
|
1205 // be skipped but it will not position them. |
|
1206 void RenderBlock::skipTrailingWhitespace(InlineIterator& iterator, bool isLineEmpty, bool previousLineBrokeCleanly) |
|
1207 { |
|
1208 while (!iterator.atEnd() && !requiresLineBox(iterator, isLineEmpty, previousLineBrokeCleanly)) { |
|
1209 RenderObject* object = iterator.obj; |
|
1210 if (object->isFloating()) { |
|
1211 insertFloatingObject(toRenderBox(object)); |
|
1212 } else if (object->isPositioned()) { |
|
1213 // FIXME: The math here is actually not really right. It's a best-guess approximation that |
|
1214 // will work for the common cases |
|
1215 RenderObject* c = object->container(); |
|
1216 if (c->isRenderInline()) { |
|
1217 // A relative positioned inline encloses us. In this case, we also have to determine our |
|
1218 // position as though we were an inline. Set |staticX| and |staticY| on the relative positioned |
|
1219 // inline so that we can obtain the value later. |
|
1220 toRenderInline(c)->layer()->setStaticX(style()->direction() == LTR ? leftOffset(height(), false) : rightOffset(height(), false)); |
|
1221 toRenderInline(c)->layer()->setStaticY(height()); |
|
1222 } |
|
1223 |
|
1224 RenderBox* box = toRenderBox(object); |
|
1225 if (box->style()->hasStaticX()) { |
|
1226 if (box->style()->isOriginalDisplayInlineType()) |
|
1227 box->layer()->setStaticX(style()->direction() == LTR ? leftOffset(height(), false) : width() - rightOffset(height(), false)); |
|
1228 else |
|
1229 box->layer()->setStaticX(style()->direction() == LTR ? borderLeft() + paddingLeft() : borderRight() + paddingRight()); |
|
1230 } |
|
1231 |
|
1232 if (box->style()->hasStaticY()) |
|
1233 box->layer()->setStaticY(height()); |
|
1234 } |
|
1235 iterator.increment(); |
|
1236 } |
|
1237 } |
|
1238 |
|
1239 int RenderBlock::skipLeadingWhitespace(InlineBidiResolver& resolver, bool firstLine, bool isLineEmpty, bool previousLineBrokeCleanly) |
|
1240 { |
|
1241 int availableWidth = lineWidth(height(), firstLine); |
|
1242 while (!resolver.position().atEnd() && !requiresLineBox(resolver.position(), isLineEmpty, previousLineBrokeCleanly)) { |
|
1243 RenderObject* object = resolver.position().obj; |
|
1244 if (object->isFloating()) { |
|
1245 insertFloatingObject(toRenderBox(object)); |
|
1246 positionNewFloats(); |
|
1247 availableWidth = lineWidth(height(), firstLine); |
|
1248 } else if (object->isPositioned()) { |
|
1249 // FIXME: The math here is actually not really right. It's a best-guess approximation that |
|
1250 // will work for the common cases |
|
1251 RenderObject* c = object->container(); |
|
1252 if (c->isRenderInline()) { |
|
1253 // A relative positioned inline encloses us. In this case, we also have to determine our |
|
1254 // position as though we were an inline. Set |staticX| and |staticY| on the relative positioned |
|
1255 // inline so that we can obtain the value later. |
|
1256 toRenderInline(c)->layer()->setStaticX(style()->direction() == LTR ? leftOffset(height(), firstLine) : rightOffset(height(), firstLine)); |
|
1257 toRenderInline(c)->layer()->setStaticY(height()); |
|
1258 } |
|
1259 |
|
1260 RenderBox* box = toRenderBox(object); |
|
1261 if (box->style()->hasStaticX()) { |
|
1262 if (box->style()->isOriginalDisplayInlineType()) |
|
1263 box->layer()->setStaticX(style()->direction() == LTR ? leftOffset(height(), firstLine) : width() - rightOffset(height(), firstLine)); |
|
1264 else |
|
1265 box->layer()->setStaticX(style()->direction() == LTR ? borderLeft() + paddingLeft() : borderRight() + paddingRight()); |
|
1266 } |
|
1267 |
|
1268 if (box->style()->hasStaticY()) |
|
1269 box->layer()->setStaticY(height()); |
|
1270 } |
|
1271 resolver.increment(); |
|
1272 } |
|
1273 resolver.commitExplicitEmbedding(); |
|
1274 return availableWidth; |
|
1275 } |
|
1276 |
|
1277 // This is currently just used for list markers and inline flows that have line boxes. Neither should |
|
1278 // have an effect on whitespace at the start of the line. |
|
1279 static bool shouldSkipWhitespaceAfterStartObject(RenderBlock* block, RenderObject* o, LineMidpointState& lineMidpointState) |
|
1280 { |
|
1281 RenderObject* next = bidiNext(block, o); |
|
1282 if (next && !next->isBR() && next->isText() && toRenderText(next)->textLength() > 0) { |
|
1283 RenderText* nextText = toRenderText(next); |
|
1284 UChar nextChar = nextText->characters()[0]; |
|
1285 if (nextText->style()->isCollapsibleWhiteSpace(nextChar)) { |
|
1286 addMidpoint(lineMidpointState, InlineIterator(0, o, 0)); |
|
1287 return true; |
|
1288 } |
|
1289 } |
|
1290 |
|
1291 return false; |
|
1292 } |
|
1293 |
|
1294 void RenderBlock::fitBelowFloats(int widthToFit, bool firstLine, int& availableWidth) |
|
1295 { |
|
1296 ASSERT(widthToFit > availableWidth); |
|
1297 |
|
1298 int floatBottom; |
|
1299 int lastFloatBottom = height(); |
|
1300 int newLineWidth = availableWidth; |
|
1301 while (true) { |
|
1302 floatBottom = nextFloatBottomBelow(lastFloatBottom); |
|
1303 if (!floatBottom) |
|
1304 break; |
|
1305 |
|
1306 newLineWidth = lineWidth(floatBottom, firstLine); |
|
1307 lastFloatBottom = floatBottom; |
|
1308 if (newLineWidth >= widthToFit) |
|
1309 break; |
|
1310 } |
|
1311 |
|
1312 if (newLineWidth > availableWidth) { |
|
1313 setHeight(lastFloatBottom); |
|
1314 availableWidth = newLineWidth; |
|
1315 } |
|
1316 } |
|
1317 |
|
1318 static inline unsigned textWidth(RenderText* text, unsigned from, unsigned len, const Font& font, int xPos, bool isFixedPitch, bool collapseWhiteSpace) |
|
1319 { |
|
1320 if (isFixedPitch || (!from && len == text->textLength())) |
|
1321 return text->width(from, len, font, xPos); |
|
1322 return font.width(TextRun(text->characters() + from, len, !collapseWhiteSpace, xPos)); |
|
1323 } |
|
1324 |
|
1325 static void tryHyphenating(RenderText* text, const Font& font, int lastSpace, int pos, int xPos, int availableWidth, bool isFixedPitch, bool collapseWhiteSpace, int lastSpaceWordSpacing, InlineIterator& lineBreak, int nextBreakable, bool& hyphenated) |
|
1326 { |
|
1327 const AtomicString& hyphenString = text->style()->hyphenString(); |
|
1328 int hyphenWidth = font.width(TextRun(hyphenString.characters(), hyphenString.length())); |
|
1329 |
|
1330 unsigned prefixLength = font.offsetForPosition(TextRun(text->characters() + lastSpace, pos - lastSpace, !collapseWhiteSpace, xPos + lastSpaceWordSpacing), availableWidth - xPos - hyphenWidth - lastSpaceWordSpacing, false); |
|
1331 if (!prefixLength) |
|
1332 return; |
|
1333 |
|
1334 prefixLength = 1 + lastHyphenLocation(text->characters() + lastSpace + 1, pos - lastSpace - 1, prefixLength - 1); |
|
1335 if (prefixLength <= 1) |
|
1336 return; |
|
1337 |
|
1338 #if !ASSERT_DISABLED |
|
1339 int prefixWidth = hyphenWidth + textWidth(text, lastSpace, prefixLength, font, xPos, isFixedPitch, collapseWhiteSpace) + lastSpaceWordSpacing; |
|
1340 ASSERT(xPos + prefixWidth <= availableWidth); |
|
1341 #else |
|
1342 UNUSED_PARAM(isFixedPitch); |
|
1343 #endif |
|
1344 |
|
1345 lineBreak.obj = text; |
|
1346 lineBreak.pos = lastSpace + prefixLength; |
|
1347 lineBreak.nextBreakablePosition = nextBreakable; |
|
1348 hyphenated = true; |
|
1349 } |
|
1350 |
|
1351 InlineIterator RenderBlock::findNextLineBreak(InlineBidiResolver& resolver, bool firstLine, bool& isLineEmpty, bool& previousLineBrokeCleanly, |
|
1352 bool& hyphenated, EClear* clear) |
|
1353 { |
|
1354 ASSERT(resolver.position().block == this); |
|
1355 |
|
1356 bool appliedStartWidth = resolver.position().pos > 0; |
|
1357 LineMidpointState& lineMidpointState = resolver.midpointState(); |
|
1358 |
|
1359 int width = skipLeadingWhitespace(resolver, firstLine, isLineEmpty, previousLineBrokeCleanly); |
|
1360 |
|
1361 int w = 0; |
|
1362 int tmpW = 0; |
|
1363 |
|
1364 if (resolver.position().atEnd()) |
|
1365 return resolver.position(); |
|
1366 |
|
1367 // This variable is used only if whitespace isn't set to PRE, and it tells us whether |
|
1368 // or not we are currently ignoring whitespace. |
|
1369 bool ignoringSpaces = false; |
|
1370 InlineIterator ignoreStart; |
|
1371 |
|
1372 // This variable tracks whether the very last character we saw was a space. We use |
|
1373 // this to detect when we encounter a second space so we know we have to terminate |
|
1374 // a run. |
|
1375 bool currentCharacterIsSpace = false; |
|
1376 bool currentCharacterIsWS = false; |
|
1377 RenderObject* trailingSpaceObject = 0; |
|
1378 |
|
1379 InlineIterator lBreak = resolver.position(); |
|
1380 |
|
1381 RenderObject *o = resolver.position().obj; |
|
1382 RenderObject *last = o; |
|
1383 unsigned pos = resolver.position().pos; |
|
1384 int nextBreakable = resolver.position().nextBreakablePosition; |
|
1385 bool atStart = true; |
|
1386 |
|
1387 bool prevLineBrokeCleanly = previousLineBrokeCleanly; |
|
1388 previousLineBrokeCleanly = false; |
|
1389 |
|
1390 hyphenated = false; |
|
1391 |
|
1392 bool autoWrapWasEverTrueOnLine = false; |
|
1393 bool floatsFitOnLine = true; |
|
1394 |
|
1395 // Firefox and Opera will allow a table cell to grow to fit an image inside it under |
|
1396 // very specific circumstances (in order to match common WinIE renderings). |
|
1397 // Not supporting the quirk has caused us to mis-render some real sites. (See Bugzilla 10517.) |
|
1398 bool allowImagesToBreak = !style()->htmlHacks() || !isTableCell() || !style()->width().isIntrinsicOrAuto(); |
|
1399 |
|
1400 EWhiteSpace currWS = style()->whiteSpace(); |
|
1401 EWhiteSpace lastWS = currWS; |
|
1402 while (o) { |
|
1403 currWS = o->isReplaced() ? o->parent()->style()->whiteSpace() : o->style()->whiteSpace(); |
|
1404 lastWS = last->isReplaced() ? last->parent()->style()->whiteSpace() : last->style()->whiteSpace(); |
|
1405 |
|
1406 bool autoWrap = RenderStyle::autoWrap(currWS); |
|
1407 autoWrapWasEverTrueOnLine = autoWrapWasEverTrueOnLine || autoWrap; |
|
1408 |
|
1409 #if ENABLE(SVG) |
|
1410 bool preserveNewline = o->isSVGInlineText() ? false : RenderStyle::preserveNewline(currWS); |
|
1411 #else |
|
1412 bool preserveNewline = RenderStyle::preserveNewline(currWS); |
|
1413 #endif |
|
1414 |
|
1415 bool collapseWhiteSpace = RenderStyle::collapseWhiteSpace(currWS); |
|
1416 |
|
1417 if (o->isBR()) { |
|
1418 if (w + tmpW <= width) { |
|
1419 lBreak.obj = o; |
|
1420 lBreak.pos = 0; |
|
1421 lBreak.nextBreakablePosition = -1; |
|
1422 lBreak.increment(); |
|
1423 |
|
1424 // A <br> always breaks a line, so don't let the line be collapsed |
|
1425 // away. Also, the space at the end of a line with a <br> does not |
|
1426 // get collapsed away. It only does this if the previous line broke |
|
1427 // cleanly. Otherwise the <br> has no effect on whether the line is |
|
1428 // empty or not. |
|
1429 if (prevLineBrokeCleanly) |
|
1430 isLineEmpty = false; |
|
1431 trailingSpaceObject = 0; |
|
1432 previousLineBrokeCleanly = true; |
|
1433 |
|
1434 if (!isLineEmpty && clear) |
|
1435 *clear = o->style()->clear(); |
|
1436 } |
|
1437 goto end; |
|
1438 } |
|
1439 |
|
1440 if (o->isFloatingOrPositioned()) { |
|
1441 // add to special objects... |
|
1442 if (o->isFloating()) { |
|
1443 RenderBox* floatBox = toRenderBox(o); |
|
1444 insertFloatingObject(floatBox); |
|
1445 // check if it fits in the current line. |
|
1446 // If it does, position it now, otherwise, position |
|
1447 // it after moving to next line (in newLine() func) |
|
1448 if (floatsFitOnLine && floatBox->width() + floatBox->marginLeft() + floatBox->marginRight() + w + tmpW <= width) { |
|
1449 positionNewFloats(); |
|
1450 width = lineWidth(height(), firstLine); |
|
1451 } else |
|
1452 floatsFitOnLine = false; |
|
1453 } else if (o->isPositioned()) { |
|
1454 // If our original display wasn't an inline type, then we can |
|
1455 // go ahead and determine our static x position now. |
|
1456 RenderBox* box = toRenderBox(o); |
|
1457 bool isInlineType = box->style()->isOriginalDisplayInlineType(); |
|
1458 bool needToSetStaticX = box->style()->hasStaticX(); |
|
1459 if (box->style()->hasStaticX() && !isInlineType) { |
|
1460 box->layer()->setStaticX(o->parent()->style()->direction() == LTR ? |
|
1461 borderLeft() + paddingLeft() : |
|
1462 borderRight() + paddingRight()); |
|
1463 needToSetStaticX = false; |
|
1464 } |
|
1465 |
|
1466 // If our original display was an INLINE type, then we can go ahead |
|
1467 // and determine our static y position now. |
|
1468 bool needToSetStaticY = box->style()->hasStaticY(); |
|
1469 if (box->style()->hasStaticY() && isInlineType) { |
|
1470 box->layer()->setStaticY(height()); |
|
1471 needToSetStaticY = false; |
|
1472 } |
|
1473 |
|
1474 bool needToCreateLineBox = needToSetStaticX || needToSetStaticY; |
|
1475 RenderObject* c = o->container(); |
|
1476 if (c->isRenderInline() && (!needToSetStaticX || !needToSetStaticY)) |
|
1477 needToCreateLineBox = true; |
|
1478 |
|
1479 // If we're ignoring spaces, we have to stop and include this object and |
|
1480 // then start ignoring spaces again. |
|
1481 if (needToCreateLineBox) { |
|
1482 trailingSpaceObject = 0; |
|
1483 ignoreStart.obj = o; |
|
1484 ignoreStart.pos = 0; |
|
1485 if (ignoringSpaces) { |
|
1486 addMidpoint(lineMidpointState, ignoreStart); // Stop ignoring spaces. |
|
1487 addMidpoint(lineMidpointState, ignoreStart); // Start ignoring again. |
|
1488 } |
|
1489 |
|
1490 } |
|
1491 } |
|
1492 } else if (o->isRenderInline()) { |
|
1493 // Right now, we should only encounter empty inlines here. |
|
1494 ASSERT(!o->firstChild()); |
|
1495 |
|
1496 RenderInline* flowBox = toRenderInline(o); |
|
1497 |
|
1498 // Now that some inline flows have line boxes, if we are already ignoring spaces, we need |
|
1499 // to make sure that we stop to include this object and then start ignoring spaces again. |
|
1500 // If this object is at the start of the line, we need to behave like list markers and |
|
1501 // start ignoring spaces. |
|
1502 if (inlineFlowRequiresLineBox(flowBox)) { |
|
1503 isLineEmpty = false; |
|
1504 if (ignoringSpaces) { |
|
1505 trailingSpaceObject = 0; |
|
1506 addMidpoint(lineMidpointState, InlineIterator(0, o, 0)); // Stop ignoring spaces. |
|
1507 addMidpoint(lineMidpointState, InlineIterator(0, o, 0)); // Start ignoring again. |
|
1508 } else if (style()->collapseWhiteSpace() && resolver.position().obj == o |
|
1509 && shouldSkipWhitespaceAfterStartObject(this, o, lineMidpointState)) { |
|
1510 // Like with list markers, we start ignoring spaces to make sure that any |
|
1511 // additional spaces we see will be discarded. |
|
1512 currentCharacterIsSpace = true; |
|
1513 currentCharacterIsWS = true; |
|
1514 ignoringSpaces = true; |
|
1515 } |
|
1516 } |
|
1517 |
|
1518 tmpW += flowBox->marginLeft() + flowBox->borderLeft() + flowBox->paddingLeft() + |
|
1519 flowBox->marginRight() + flowBox->borderRight() + flowBox->paddingRight(); |
|
1520 } else if (o->isReplaced()) { |
|
1521 RenderBox* replacedBox = toRenderBox(o); |
|
1522 |
|
1523 // Break on replaced elements if either has normal white-space. |
|
1524 if ((autoWrap || RenderStyle::autoWrap(lastWS)) && (!o->isImage() || allowImagesToBreak)) { |
|
1525 w += tmpW; |
|
1526 tmpW = 0; |
|
1527 lBreak.obj = o; |
|
1528 lBreak.pos = 0; |
|
1529 lBreak.nextBreakablePosition = -1; |
|
1530 } |
|
1531 |
|
1532 if (ignoringSpaces) |
|
1533 addMidpoint(lineMidpointState, InlineIterator(0, o, 0)); |
|
1534 |
|
1535 isLineEmpty = false; |
|
1536 ignoringSpaces = false; |
|
1537 currentCharacterIsSpace = false; |
|
1538 currentCharacterIsWS = false; |
|
1539 trailingSpaceObject = 0; |
|
1540 |
|
1541 // Optimize for a common case. If we can't find whitespace after the list |
|
1542 // item, then this is all moot. -dwh |
|
1543 if (o->isListMarker()) { |
|
1544 if (style()->collapseWhiteSpace() && shouldSkipWhitespaceAfterStartObject(this, o, lineMidpointState)) { |
|
1545 // Like with inline flows, we start ignoring spaces to make sure that any |
|
1546 // additional spaces we see will be discarded. |
|
1547 currentCharacterIsSpace = true; |
|
1548 currentCharacterIsWS = true; |
|
1549 ignoringSpaces = true; |
|
1550 } |
|
1551 if (toRenderListMarker(o)->isInside()) |
|
1552 tmpW += replacedBox->width() + replacedBox->marginLeft() + replacedBox->marginRight() + inlineWidth(o); |
|
1553 } else |
|
1554 tmpW += replacedBox->width() + replacedBox->marginLeft() + replacedBox->marginRight() + inlineWidth(o); |
|
1555 } else if (o->isText()) { |
|
1556 if (!pos) |
|
1557 appliedStartWidth = false; |
|
1558 |
|
1559 RenderText* t = toRenderText(o); |
|
1560 |
|
1561 int strlen = t->textLength(); |
|
1562 int len = strlen - pos; |
|
1563 const UChar* str = t->characters(); |
|
1564 |
|
1565 RenderStyle* style = t->style(firstLine); |
|
1566 const Font& f = style->font(); |
|
1567 bool isFixedPitch = f.isFixedPitch(); |
|
1568 bool canHyphenate = style->hyphens() == HyphensAuto; |
|
1569 |
|
1570 int lastSpace = pos; |
|
1571 int wordSpacing = o->style()->wordSpacing(); |
|
1572 int lastSpaceWordSpacing = 0; |
|
1573 |
|
1574 // Non-zero only when kerning is enabled, in which case we measure words with their trailing |
|
1575 // space, then subtract its width. |
|
1576 int wordTrailingSpaceWidth = f.typesettingFeatures() & Kerning ? f.width(TextRun(&space, 1)) + wordSpacing : 0; |
|
1577 |
|
1578 int wrapW = tmpW + inlineWidth(o, !appliedStartWidth, true); |
|
1579 int charWidth = 0; |
|
1580 bool breakNBSP = autoWrap && o->style()->nbspMode() == SPACE; |
|
1581 // Auto-wrapping text should wrap in the middle of a word only if it could not wrap before the word, |
|
1582 // which is only possible if the word is the first thing on the line, that is, if |w| is zero. |
|
1583 bool breakWords = o->style()->breakWords() && ((autoWrap && !w) || currWS == PRE); |
|
1584 bool midWordBreak = false; |
|
1585 bool breakAll = o->style()->wordBreak() == BreakAllWordBreak && autoWrap; |
|
1586 |
|
1587 if (t->isWordBreak()) { |
|
1588 w += tmpW; |
|
1589 tmpW = 0; |
|
1590 lBreak.obj = o; |
|
1591 lBreak.pos = 0; |
|
1592 lBreak.nextBreakablePosition = -1; |
|
1593 ASSERT(!len); |
|
1594 } |
|
1595 |
|
1596 while (len) { |
|
1597 bool previousCharacterIsSpace = currentCharacterIsSpace; |
|
1598 bool previousCharacterIsWS = currentCharacterIsWS; |
|
1599 UChar c = str[pos]; |
|
1600 currentCharacterIsSpace = c == ' ' || c == '\t' || (!preserveNewline && (c == '\n')); |
|
1601 |
|
1602 if (!collapseWhiteSpace || !currentCharacterIsSpace) |
|
1603 isLineEmpty = false; |
|
1604 |
|
1605 // Check for soft hyphens. Go ahead and ignore them. |
|
1606 if (c == softHyphen) { |
|
1607 if (!ignoringSpaces) { |
|
1608 // Ignore soft hyphens |
|
1609 InlineIterator beforeSoftHyphen; |
|
1610 if (pos) |
|
1611 beforeSoftHyphen = InlineIterator(0, o, pos - 1); |
|
1612 else |
|
1613 beforeSoftHyphen = InlineIterator(0, last, last->isText() ? toRenderText(last)->textLength() - 1 : 0); |
|
1614 // Two consecutive soft hyphens. Avoid overlapping midpoints. |
|
1615 if (lineMidpointState.numMidpoints && lineMidpointState.midpoints[lineMidpointState.numMidpoints - 1].obj == o && |
|
1616 lineMidpointState.midpoints[lineMidpointState.numMidpoints - 1].pos == pos) |
|
1617 lineMidpointState.numMidpoints--; |
|
1618 else |
|
1619 addMidpoint(lineMidpointState, beforeSoftHyphen); |
|
1620 |
|
1621 // Add the width up to but not including the hyphen. |
|
1622 tmpW += textWidth(t, lastSpace, pos - lastSpace, f, w + tmpW, isFixedPitch, collapseWhiteSpace) + lastSpaceWordSpacing; |
|
1623 |
|
1624 // For wrapping text only, include the hyphen. We need to ensure it will fit |
|
1625 // on the line if it shows when we break. |
|
1626 if (autoWrap) |
|
1627 tmpW += textWidth(t, pos, 1, f, w + tmpW, isFixedPitch, collapseWhiteSpace); |
|
1628 |
|
1629 InlineIterator afterSoftHyphen(0, o, pos); |
|
1630 afterSoftHyphen.increment(); |
|
1631 addMidpoint(lineMidpointState, afterSoftHyphen); |
|
1632 } |
|
1633 |
|
1634 pos++; |
|
1635 len--; |
|
1636 lastSpaceWordSpacing = 0; |
|
1637 lastSpace = pos; // Cheesy hack to prevent adding in widths of the run twice. |
|
1638 if (style->hyphens() == HyphensNone) { |
|
1639 // Prevent a line break at the soft hyphen by ensuring that betweenWords is false |
|
1640 // in the next iteration. |
|
1641 atStart = true; |
|
1642 } |
|
1643 continue; |
|
1644 } |
|
1645 |
|
1646 bool applyWordSpacing = false; |
|
1647 |
|
1648 currentCharacterIsWS = currentCharacterIsSpace || (breakNBSP && c == noBreakSpace); |
|
1649 |
|
1650 if ((breakAll || breakWords) && !midWordBreak) { |
|
1651 wrapW += charWidth; |
|
1652 charWidth = textWidth(t, pos, 1, f, w + wrapW, isFixedPitch, collapseWhiteSpace); |
|
1653 midWordBreak = w + wrapW + charWidth > width; |
|
1654 } |
|
1655 |
|
1656 bool betweenWords = c == '\n' || (currWS != PRE && !atStart && isBreakable(str, pos, strlen, nextBreakable, breakNBSP)); |
|
1657 |
|
1658 if (betweenWords || midWordBreak) { |
|
1659 bool stoppedIgnoringSpaces = false; |
|
1660 if (ignoringSpaces) { |
|
1661 if (!currentCharacterIsSpace) { |
|
1662 // Stop ignoring spaces and begin at this |
|
1663 // new point. |
|
1664 ignoringSpaces = false; |
|
1665 lastSpaceWordSpacing = 0; |
|
1666 lastSpace = pos; // e.g., "Foo goo", don't add in any of the ignored spaces. |
|
1667 addMidpoint(lineMidpointState, InlineIterator(0, o, pos)); |
|
1668 stoppedIgnoringSpaces = true; |
|
1669 } else { |
|
1670 // Just keep ignoring these spaces. |
|
1671 pos++; |
|
1672 len--; |
|
1673 continue; |
|
1674 } |
|
1675 } |
|
1676 |
|
1677 int additionalTmpW; |
|
1678 if (wordTrailingSpaceWidth && currentCharacterIsSpace) |
|
1679 additionalTmpW = textWidth(t, lastSpace, pos + 1 - lastSpace, f, w + tmpW, isFixedPitch, collapseWhiteSpace) - wordTrailingSpaceWidth + lastSpaceWordSpacing; |
|
1680 else |
|
1681 additionalTmpW = textWidth(t, lastSpace, pos - lastSpace, f, w + tmpW, isFixedPitch, collapseWhiteSpace) + lastSpaceWordSpacing; |
|
1682 tmpW += additionalTmpW; |
|
1683 if (!appliedStartWidth) { |
|
1684 tmpW += inlineWidth(o, true, false); |
|
1685 appliedStartWidth = true; |
|
1686 } |
|
1687 |
|
1688 applyWordSpacing = wordSpacing && currentCharacterIsSpace && !previousCharacterIsSpace; |
|
1689 |
|
1690 if (!w && autoWrap && tmpW > width) |
|
1691 fitBelowFloats(tmpW, firstLine, width); |
|
1692 |
|
1693 if (autoWrap || breakWords) { |
|
1694 // If we break only after white-space, consider the current character |
|
1695 // as candidate width for this line. |
|
1696 bool lineWasTooWide = false; |
|
1697 if (w + tmpW <= width && currentCharacterIsWS && o->style()->breakOnlyAfterWhiteSpace() && !midWordBreak) { |
|
1698 int charWidth = textWidth(t, pos, 1, f, w + tmpW, isFixedPitch, collapseWhiteSpace) + (applyWordSpacing ? wordSpacing : 0); |
|
1699 // Check if line is too big even without the extra space |
|
1700 // at the end of the line. If it is not, do nothing. |
|
1701 // If the line needs the extra whitespace to be too long, |
|
1702 // then move the line break to the space and skip all |
|
1703 // additional whitespace. |
|
1704 if (w + tmpW + charWidth > width) { |
|
1705 lineWasTooWide = true; |
|
1706 lBreak.obj = o; |
|
1707 lBreak.pos = pos; |
|
1708 lBreak.nextBreakablePosition = nextBreakable; |
|
1709 skipTrailingWhitespace(lBreak, isLineEmpty, previousLineBrokeCleanly); |
|
1710 } |
|
1711 } |
|
1712 if (lineWasTooWide || w + tmpW > width) { |
|
1713 if (canHyphenate && w + tmpW > width) { |
|
1714 tryHyphenating(t, f, lastSpace, pos, w + tmpW - additionalTmpW, width, isFixedPitch, collapseWhiteSpace, lastSpaceWordSpacing, lBreak, nextBreakable, hyphenated); |
|
1715 if (hyphenated) |
|
1716 goto end; |
|
1717 } |
|
1718 if (lBreak.obj && shouldPreserveNewline(lBreak.obj) && lBreak.obj->isText() && toRenderText(lBreak.obj)->textLength() && !toRenderText(lBreak.obj)->isWordBreak() && toRenderText(lBreak.obj)->characters()[lBreak.pos] == '\n') { |
|
1719 if (!stoppedIgnoringSpaces && pos > 0) { |
|
1720 // We need to stop right before the newline and then start up again. |
|
1721 addMidpoint(lineMidpointState, InlineIterator(0, o, pos - 1)); // Stop |
|
1722 addMidpoint(lineMidpointState, InlineIterator(0, o, pos)); // Start |
|
1723 } |
|
1724 lBreak.increment(); |
|
1725 previousLineBrokeCleanly = true; |
|
1726 } |
|
1727 goto end; // Didn't fit. Jump to the end. |
|
1728 } else { |
|
1729 if (!betweenWords || (midWordBreak && !autoWrap)) |
|
1730 tmpW -= additionalTmpW; |
|
1731 if (pos > 0 && str[pos-1] == softHyphen) |
|
1732 // Subtract the width of the soft hyphen out since we fit on a line. |
|
1733 tmpW -= textWidth(t, pos - 1, 1, f, w + tmpW, isFixedPitch, collapseWhiteSpace); |
|
1734 } |
|
1735 } |
|
1736 |
|
1737 if (c == '\n' && preserveNewline) { |
|
1738 if (!stoppedIgnoringSpaces && pos > 0) { |
|
1739 // We need to stop right before the newline and then start up again. |
|
1740 addMidpoint(lineMidpointState, InlineIterator(0, o, pos - 1)); // Stop |
|
1741 addMidpoint(lineMidpointState, InlineIterator(0, o, pos)); // Start |
|
1742 } |
|
1743 lBreak.obj = o; |
|
1744 lBreak.pos = pos; |
|
1745 lBreak.nextBreakablePosition = nextBreakable; |
|
1746 lBreak.increment(); |
|
1747 previousLineBrokeCleanly = true; |
|
1748 return lBreak; |
|
1749 } |
|
1750 |
|
1751 if (autoWrap && betweenWords) { |
|
1752 w += tmpW; |
|
1753 wrapW = 0; |
|
1754 tmpW = 0; |
|
1755 lBreak.obj = o; |
|
1756 lBreak.pos = pos; |
|
1757 lBreak.nextBreakablePosition = nextBreakable; |
|
1758 // Auto-wrapping text should not wrap in the middle of a word once it has had an |
|
1759 // opportunity to break after a word. |
|
1760 breakWords = false; |
|
1761 } |
|
1762 |
|
1763 if (midWordBreak) { |
|
1764 // Remember this as a breakable position in case |
|
1765 // adding the end width forces a break. |
|
1766 lBreak.obj = o; |
|
1767 lBreak.pos = pos; |
|
1768 lBreak.nextBreakablePosition = nextBreakable; |
|
1769 midWordBreak &= (breakWords || breakAll); |
|
1770 } |
|
1771 |
|
1772 if (betweenWords) { |
|
1773 lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0; |
|
1774 lastSpace = pos; |
|
1775 } |
|
1776 |
|
1777 if (!ignoringSpaces && o->style()->collapseWhiteSpace()) { |
|
1778 // If we encounter a newline, or if we encounter a |
|
1779 // second space, we need to go ahead and break up this |
|
1780 // run and enter a mode where we start collapsing spaces. |
|
1781 if (currentCharacterIsSpace && previousCharacterIsSpace) { |
|
1782 ignoringSpaces = true; |
|
1783 |
|
1784 // We just entered a mode where we are ignoring |
|
1785 // spaces. Create a midpoint to terminate the run |
|
1786 // before the second space. |
|
1787 addMidpoint(lineMidpointState, ignoreStart); |
|
1788 } |
|
1789 } |
|
1790 } else if (ignoringSpaces) { |
|
1791 // Stop ignoring spaces and begin at this |
|
1792 // new point. |
|
1793 ignoringSpaces = false; |
|
1794 lastSpaceWordSpacing = applyWordSpacing ? wordSpacing : 0; |
|
1795 lastSpace = pos; // e.g., "Foo goo", don't add in any of the ignored spaces. |
|
1796 addMidpoint(lineMidpointState, InlineIterator(0, o, pos)); |
|
1797 } |
|
1798 |
|
1799 if (currentCharacterIsSpace && !previousCharacterIsSpace) { |
|
1800 ignoreStart.obj = o; |
|
1801 ignoreStart.pos = pos; |
|
1802 } |
|
1803 |
|
1804 if (!currentCharacterIsWS && previousCharacterIsWS) { |
|
1805 if (autoWrap && o->style()->breakOnlyAfterWhiteSpace()) { |
|
1806 lBreak.obj = o; |
|
1807 lBreak.pos = pos; |
|
1808 lBreak.nextBreakablePosition = nextBreakable; |
|
1809 } |
|
1810 } |
|
1811 |
|
1812 if (collapseWhiteSpace && currentCharacterIsSpace && !ignoringSpaces) |
|
1813 trailingSpaceObject = o; |
|
1814 else if (!o->style()->collapseWhiteSpace() || !currentCharacterIsSpace) |
|
1815 trailingSpaceObject = 0; |
|
1816 |
|
1817 pos++; |
|
1818 len--; |
|
1819 atStart = false; |
|
1820 } |
|
1821 |
|
1822 // IMPORTANT: pos is > length here! |
|
1823 int additionalTmpW = ignoringSpaces ? 0 : textWidth(t, lastSpace, pos - lastSpace, f, w + tmpW, isFixedPitch, collapseWhiteSpace) + lastSpaceWordSpacing; |
|
1824 tmpW += additionalTmpW; |
|
1825 tmpW += inlineWidth(o, !appliedStartWidth, true); |
|
1826 |
|
1827 if (canHyphenate && w + tmpW > width) { |
|
1828 tryHyphenating(t, f, lastSpace, pos, w + tmpW - additionalTmpW, width, isFixedPitch, collapseWhiteSpace, lastSpaceWordSpacing, lBreak, nextBreakable, hyphenated); |
|
1829 if (hyphenated) |
|
1830 goto end; |
|
1831 } |
|
1832 } else |
|
1833 ASSERT_NOT_REACHED(); |
|
1834 |
|
1835 RenderObject* next = bidiNext(this, o); |
|
1836 bool checkForBreak = autoWrap; |
|
1837 if (w && w + tmpW > width && lBreak.obj && currWS == NOWRAP) |
|
1838 checkForBreak = true; |
|
1839 else if (next && o->isText() && next->isText() && !next->isBR()) { |
|
1840 if (autoWrap || (next->style()->autoWrap())) { |
|
1841 if (currentCharacterIsSpace) |
|
1842 checkForBreak = true; |
|
1843 else { |
|
1844 checkForBreak = false; |
|
1845 RenderText* nextText = toRenderText(next); |
|
1846 if (nextText->textLength()) { |
|
1847 UChar c = nextText->characters()[0]; |
|
1848 if (c == ' ' || c == '\t' || (c == '\n' && !shouldPreserveNewline(next))) |
|
1849 // If the next item on the line is text, and if we did not end with |
|
1850 // a space, then the next text run continues our word (and so it needs to |
|
1851 // keep adding to |tmpW|. Just update and continue. |
|
1852 checkForBreak = true; |
|
1853 } else if (nextText->isWordBreak()) |
|
1854 checkForBreak = true; |
|
1855 bool willFitOnLine = w + tmpW <= width; |
|
1856 if (!willFitOnLine && !w) { |
|
1857 fitBelowFloats(tmpW, firstLine, width); |
|
1858 willFitOnLine = tmpW <= width; |
|
1859 } |
|
1860 bool canPlaceOnLine = willFitOnLine || !autoWrapWasEverTrueOnLine; |
|
1861 if (canPlaceOnLine && checkForBreak) { |
|
1862 w += tmpW; |
|
1863 tmpW = 0; |
|
1864 lBreak.obj = next; |
|
1865 lBreak.pos = 0; |
|
1866 lBreak.nextBreakablePosition = -1; |
|
1867 } |
|
1868 } |
|
1869 } |
|
1870 } |
|
1871 |
|
1872 if (checkForBreak && (w + tmpW > width)) { |
|
1873 // if we have floats, try to get below them. |
|
1874 if (currentCharacterIsSpace && !ignoringSpaces && o->style()->collapseWhiteSpace()) |
|
1875 trailingSpaceObject = 0; |
|
1876 |
|
1877 if (w) |
|
1878 goto end; |
|
1879 |
|
1880 fitBelowFloats(tmpW, firstLine, width); |
|
1881 |
|
1882 // |width| may have been adjusted because we got shoved down past a float (thus |
|
1883 // giving us more room), so we need to retest, and only jump to |
|
1884 // the end label if we still don't fit on the line. -dwh |
|
1885 if (w + tmpW > width) |
|
1886 goto end; |
|
1887 } |
|
1888 |
|
1889 if (!o->isFloatingOrPositioned()) { |
|
1890 last = o; |
|
1891 if (last->isReplaced() && autoWrap && (!last->isImage() || allowImagesToBreak) && (!last->isListMarker() || toRenderListMarker(last)->isInside())) { |
|
1892 w += tmpW; |
|
1893 tmpW = 0; |
|
1894 lBreak.obj = next; |
|
1895 lBreak.pos = 0; |
|
1896 lBreak.nextBreakablePosition = -1; |
|
1897 } |
|
1898 } |
|
1899 |
|
1900 o = next; |
|
1901 nextBreakable = -1; |
|
1902 |
|
1903 // Clear out our character space bool, since inline <pre>s don't collapse whitespace |
|
1904 // with adjacent inline normal/nowrap spans. |
|
1905 if (!collapseWhiteSpace) |
|
1906 currentCharacterIsSpace = false; |
|
1907 |
|
1908 pos = 0; |
|
1909 atStart = false; |
|
1910 } |
|
1911 |
|
1912 |
|
1913 if (w + tmpW <= width || lastWS == NOWRAP) { |
|
1914 lBreak.obj = 0; |
|
1915 lBreak.pos = 0; |
|
1916 lBreak.nextBreakablePosition = -1; |
|
1917 } |
|
1918 |
|
1919 end: |
|
1920 if (lBreak == resolver.position() && (!lBreak.obj || !lBreak.obj->isBR())) { |
|
1921 // we just add as much as possible |
|
1922 if (style()->whiteSpace() == PRE) { |
|
1923 // FIXME: Don't really understand this case. |
|
1924 if (pos != 0) { |
|
1925 lBreak.obj = o; |
|
1926 lBreak.pos = pos - 1; |
|
1927 } else { |
|
1928 lBreak.obj = last; |
|
1929 lBreak.pos = last->isText() ? last->length() : 0; |
|
1930 lBreak.nextBreakablePosition = -1; |
|
1931 } |
|
1932 } else if (lBreak.obj) { |
|
1933 // Don't ever break in the middle of a word if we can help it. |
|
1934 // There's no room at all. We just have to be on this line, |
|
1935 // even though we'll spill out. |
|
1936 lBreak.obj = o; |
|
1937 lBreak.pos = pos; |
|
1938 lBreak.nextBreakablePosition = -1; |
|
1939 } |
|
1940 } |
|
1941 |
|
1942 // make sure we consume at least one char/object. |
|
1943 if (lBreak == resolver.position()) |
|
1944 lBreak.increment(); |
|
1945 |
|
1946 // Sanity check our midpoints. |
|
1947 checkMidpoints(lineMidpointState, lBreak); |
|
1948 |
|
1949 if (trailingSpaceObject) { |
|
1950 // This object is either going to be part of the last midpoint, or it is going |
|
1951 // to be the actual endpoint. In both cases we just decrease our pos by 1 level to |
|
1952 // exclude the space, allowing it to - in effect - collapse into the newline. |
|
1953 if (lineMidpointState.numMidpoints % 2) { |
|
1954 InlineIterator* midpoints = lineMidpointState.midpoints.data(); |
|
1955 midpoints[lineMidpointState.numMidpoints - 1].pos--; |
|
1956 } |
|
1957 //else if (lBreak.pos > 0) |
|
1958 // lBreak.pos--; |
|
1959 else if (lBreak.obj == 0 && trailingSpaceObject->isText()) { |
|
1960 // Add a new end midpoint that stops right at the very end. |
|
1961 RenderText* text = toRenderText(trailingSpaceObject); |
|
1962 unsigned length = text->textLength(); |
|
1963 unsigned pos = length >= 2 ? length - 2 : UINT_MAX; |
|
1964 InlineIterator endMid(0, trailingSpaceObject, pos); |
|
1965 addMidpoint(lineMidpointState, endMid); |
|
1966 } |
|
1967 } |
|
1968 |
|
1969 // We might have made lBreak an iterator that points past the end |
|
1970 // of the object. Do this adjustment to make it point to the start |
|
1971 // of the next object instead to avoid confusing the rest of the |
|
1972 // code. |
|
1973 if (lBreak.pos > 0) { |
|
1974 lBreak.pos--; |
|
1975 lBreak.increment(); |
|
1976 } |
|
1977 |
|
1978 if (lBreak.obj && lBreak.pos >= 2 && lBreak.obj->isText()) { |
|
1979 // For soft hyphens on line breaks, we have to chop out the midpoints that made us |
|
1980 // ignore the hyphen so that it will render at the end of the line. |
|
1981 UChar c = toRenderText(lBreak.obj)->characters()[lBreak.pos - 1]; |
|
1982 if (c == softHyphen) |
|
1983 chopMidpointsAt(lineMidpointState, lBreak.obj, lBreak.pos - 2); |
|
1984 } |
|
1985 |
|
1986 return lBreak; |
|
1987 } |
|
1988 |
|
1989 void RenderBlock::addOverflowFromInlineChildren() |
|
1990 { |
|
1991 for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) { |
|
1992 addLayoutOverflow(curr->layoutOverflowRect()); |
|
1993 if (!hasOverflowClip()) |
|
1994 addVisualOverflow(curr->visualOverflowRect()); |
|
1995 } |
|
1996 } |
|
1997 |
|
1998 void RenderBlock::deleteEllipsisLineBoxes() |
|
1999 { |
|
2000 for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) |
|
2001 curr->clearTruncation(); |
|
2002 } |
|
2003 |
|
2004 void RenderBlock::checkLinesForTextOverflow() |
|
2005 { |
|
2006 // Determine the width of the ellipsis using the current font. |
|
2007 // FIXME: CSS3 says this is configurable, also need to use 0x002E (FULL STOP) if horizontal ellipsis is "not renderable" |
|
2008 TextRun ellipsisRun(&horizontalEllipsis, 1); |
|
2009 DEFINE_STATIC_LOCAL(AtomicString, ellipsisStr, (&horizontalEllipsis, 1)); |
|
2010 const Font& firstLineFont = firstLineStyle()->font(); |
|
2011 const Font& font = style()->font(); |
|
2012 int firstLineEllipsisWidth = firstLineFont.width(ellipsisRun); |
|
2013 int ellipsisWidth = (font == firstLineFont) ? firstLineEllipsisWidth : font.width(ellipsisRun); |
|
2014 |
|
2015 // For LTR text truncation, we want to get the right edge of our padding box, and then we want to see |
|
2016 // if the right edge of a line box exceeds that. For RTL, we use the left edge of the padding box and |
|
2017 // check the left edge of the line box to see if it is less |
|
2018 // Include the scrollbar for overflow blocks, which means we want to use "contentWidth()" |
|
2019 bool ltr = style()->direction() == LTR; |
|
2020 for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) { |
|
2021 int blockRightEdge = rightOffset(curr->y(), curr == firstRootBox()); |
|
2022 int blockLeftEdge = leftOffset(curr->y(), curr == firstRootBox()); |
|
2023 int lineBoxEdge = ltr ? curr->x() + curr->width() : curr->x(); |
|
2024 if ((ltr && lineBoxEdge > blockRightEdge) || (!ltr && lineBoxEdge < blockLeftEdge)) { |
|
2025 // This line spills out of our box in the appropriate direction. Now we need to see if the line |
|
2026 // can be truncated. In order for truncation to be possible, the line must have sufficient space to |
|
2027 // accommodate our truncation string, and no replaced elements (images, tables) can overlap the ellipsis |
|
2028 // space. |
|
2029 int width = curr == firstRootBox() ? firstLineEllipsisWidth : ellipsisWidth; |
|
2030 int blockEdge = ltr ? blockRightEdge : blockLeftEdge; |
|
2031 if (curr->canAccommodateEllipsis(ltr, blockEdge, lineBoxEdge, width)) |
|
2032 curr->placeEllipsis(ellipsisStr, ltr, blockLeftEdge, blockRightEdge, width); |
|
2033 } |
|
2034 } |
|
2035 } |
|
2036 |
|
2037 } |