|
1 /* |
|
2 * Copyright (C) 2003, 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. |
|
3 * |
|
4 * This library is free software; you can redistribute it and/or |
|
5 * modify it under the terms of the GNU Library General Public |
|
6 * License as published by the Free Software Foundation; either |
|
7 * version 2 of the License, or (at your option) any later version. |
|
8 * |
|
9 * This library is distributed in the hope that it will be useful, |
|
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
12 * Library General Public License for more details. |
|
13 * |
|
14 * You should have received a copy of the GNU Library General Public License |
|
15 * along with this library; see the file COPYING.LIB. If not, write to |
|
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
|
17 * Boston, MA 02110-1301, USA. |
|
18 */ |
|
19 |
|
20 #include "config.h" |
|
21 #include "InlineFlowBox.h" |
|
22 |
|
23 #include "CachedImage.h" |
|
24 #include "Document.h" |
|
25 #include "EllipsisBox.h" |
|
26 #include "GraphicsContext.h" |
|
27 #include "InlineTextBox.h" |
|
28 #include "HitTestResult.h" |
|
29 #include "RootInlineBox.h" |
|
30 #include "RenderBlock.h" |
|
31 #include "RenderFlow.h" |
|
32 #include "RenderListMarker.h" |
|
33 #include "RenderTableCell.h" |
|
34 #include "RootInlineBox.h" |
|
35 #include "Text.h" |
|
36 |
|
37 #include <math.h> |
|
38 |
|
39 using namespace std; |
|
40 |
|
41 namespace WebCore { |
|
42 |
|
43 #ifndef NDEBUG |
|
44 |
|
45 InlineFlowBox::~InlineFlowBox() |
|
46 { |
|
47 if (!m_hasBadChildList) |
|
48 for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) |
|
49 child->setHasBadParent(); |
|
50 } |
|
51 |
|
52 #endif |
|
53 |
|
54 RenderFlow* InlineFlowBox::flowObject() |
|
55 { |
|
56 return static_cast<RenderFlow*>(m_object); |
|
57 } |
|
58 |
|
59 int InlineFlowBox::marginLeft() |
|
60 { |
|
61 if (!includeLeftEdge()) |
|
62 return 0; |
|
63 |
|
64 Length margin = object()->style()->marginLeft(); |
|
65 if (margin.isAuto()) |
|
66 return 0; |
|
67 if (margin.isFixed()) |
|
68 return margin.value(); |
|
69 return object()->marginLeft(); |
|
70 } |
|
71 |
|
72 int InlineFlowBox::marginRight() |
|
73 { |
|
74 if (!includeRightEdge()) |
|
75 return 0; |
|
76 |
|
77 Length margin = object()->style()->marginRight(); |
|
78 if (margin.isAuto()) |
|
79 return 0; |
|
80 if (margin.isFixed()) |
|
81 return margin.value(); |
|
82 return object()->marginRight(); |
|
83 } |
|
84 |
|
85 int InlineFlowBox::marginBorderPaddingLeft() |
|
86 { |
|
87 return marginLeft() + borderLeft() + paddingLeft(); |
|
88 } |
|
89 |
|
90 int InlineFlowBox::marginBorderPaddingRight() |
|
91 { |
|
92 return marginRight() + borderRight() + paddingRight(); |
|
93 } |
|
94 |
|
95 int InlineFlowBox::getFlowSpacingWidth() |
|
96 { |
|
97 int totWidth = marginBorderPaddingLeft() + marginBorderPaddingRight(); |
|
98 for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { |
|
99 if (curr->isInlineFlowBox()) |
|
100 totWidth += static_cast<InlineFlowBox*>(curr)->getFlowSpacingWidth(); |
|
101 } |
|
102 return totWidth; |
|
103 } |
|
104 |
|
105 void InlineFlowBox::addToLine(InlineBox* child) |
|
106 { |
|
107 ASSERT(!child->parent()); |
|
108 ASSERT(!child->nextOnLine()); |
|
109 ASSERT(!child->prevOnLine()); |
|
110 checkConsistency(); |
|
111 |
|
112 child->setParent(this); |
|
113 if (!m_firstChild) { |
|
114 m_firstChild = child; |
|
115 m_lastChild = child; |
|
116 } else { |
|
117 m_lastChild->setNextOnLine(child); |
|
118 child->setPrevOnLine(m_lastChild); |
|
119 m_lastChild = child; |
|
120 } |
|
121 child->setFirstLineStyleBit(m_firstLine); |
|
122 if (child->isText()) |
|
123 m_hasTextChildren = true; |
|
124 if (child->object()->selectionState() != RenderObject::SelectionNone) |
|
125 root()->setHasSelectedChildren(true); |
|
126 |
|
127 checkConsistency(); |
|
128 } |
|
129 |
|
130 void InlineFlowBox::removeChild(InlineBox* child) |
|
131 { |
|
132 checkConsistency(); |
|
133 |
|
134 if (!m_dirty) |
|
135 dirtyLineBoxes(); |
|
136 |
|
137 root()->childRemoved(child); |
|
138 |
|
139 if (child == m_firstChild) |
|
140 m_firstChild = child->nextOnLine(); |
|
141 if (child == m_lastChild) |
|
142 m_lastChild = child->prevOnLine(); |
|
143 if (child->nextOnLine()) |
|
144 child->nextOnLine()->setPrevOnLine(child->prevOnLine()); |
|
145 if (child->prevOnLine()) |
|
146 child->prevOnLine()->setNextOnLine(child->nextOnLine()); |
|
147 |
|
148 child->setParent(0); |
|
149 |
|
150 checkConsistency(); |
|
151 } |
|
152 |
|
153 void InlineFlowBox::deleteLine(RenderArena* arena) |
|
154 { |
|
155 InlineBox* child = firstChild(); |
|
156 InlineBox* next = 0; |
|
157 while (child) { |
|
158 ASSERT(this == child->parent()); |
|
159 next = child->nextOnLine(); |
|
160 #ifndef NDEBUG |
|
161 child->setParent(0); |
|
162 #endif |
|
163 child->deleteLine(arena); |
|
164 child = next; |
|
165 } |
|
166 #ifndef NDEBUG |
|
167 m_firstChild = 0; |
|
168 m_lastChild = 0; |
|
169 #endif |
|
170 |
|
171 static_cast<RenderFlow*>(m_object)->removeLineBox(this); |
|
172 destroy(arena); |
|
173 } |
|
174 |
|
175 void InlineFlowBox::extractLine() |
|
176 { |
|
177 if (!m_extracted) |
|
178 static_cast<RenderFlow*>(m_object)->extractLineBox(this); |
|
179 for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) |
|
180 child->extractLine(); |
|
181 } |
|
182 |
|
183 void InlineFlowBox::attachLine() |
|
184 { |
|
185 if (m_extracted) |
|
186 static_cast<RenderFlow*>(m_object)->attachLineBox(this); |
|
187 for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) |
|
188 child->attachLine(); |
|
189 } |
|
190 |
|
191 void InlineFlowBox::adjustPosition(int dx, int dy) |
|
192 { |
|
193 InlineRunBox::adjustPosition(dx, dy); |
|
194 for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) |
|
195 child->adjustPosition(dx, dy); |
|
196 } |
|
197 |
|
198 bool InlineFlowBox::onEndChain(RenderObject* endObject) |
|
199 { |
|
200 if (!endObject) |
|
201 return false; |
|
202 |
|
203 if (endObject == object()) |
|
204 return true; |
|
205 |
|
206 RenderObject* curr = endObject; |
|
207 RenderObject* parent = curr->parent(); |
|
208 while (parent && !parent->isRenderBlock()) { |
|
209 if (parent->lastChild() != curr || parent == object()) |
|
210 return false; |
|
211 |
|
212 curr = parent; |
|
213 parent = curr->parent(); |
|
214 } |
|
215 |
|
216 return true; |
|
217 } |
|
218 |
|
219 void InlineFlowBox::determineSpacingForFlowBoxes(bool lastLine, RenderObject* endObject) |
|
220 { |
|
221 // All boxes start off open. They will not apply any margins/border/padding on |
|
222 // any side. |
|
223 bool includeLeftEdge = false; |
|
224 bool includeRightEdge = false; |
|
225 |
|
226 RenderFlow* flow = static_cast<RenderFlow*>(object()); |
|
227 |
|
228 if (!flow->firstChild()) |
|
229 includeLeftEdge = includeRightEdge = true; // Empty inlines never split across lines. |
|
230 else if (parent()) { // The root inline box never has borders/margins/padding. |
|
231 bool ltr = flow->style()->direction() == LTR; |
|
232 |
|
233 // Check to see if all initial lines are unconstructed. If so, then |
|
234 // we know the inline began on this line. |
|
235 if (!flow->firstLineBox()->isConstructed()) { |
|
236 if (ltr && flow->firstLineBox() == this) |
|
237 includeLeftEdge = true; |
|
238 else if (!ltr && flow->lastLineBox() == this) |
|
239 includeRightEdge = true; |
|
240 } |
|
241 |
|
242 // In order to determine if the inline ends on this line, we check three things: |
|
243 // (1) If we are the last line and we don't have a continuation(), then we can |
|
244 // close up. |
|
245 // (2) If the last line box for the flow has an object following it on the line (ltr, |
|
246 // reverse for rtl), then the inline has closed. |
|
247 // (3) The line may end on the inline. If we are the last child (climbing up |
|
248 // the end object's chain), then we just closed as well. |
|
249 if (!flow->lastLineBox()->isConstructed()) { |
|
250 if (ltr) { |
|
251 if (!nextLineBox() && |
|
252 ((lastLine && !object()->continuation()) || nextOnLineExists() || onEndChain(endObject))) |
|
253 includeRightEdge = true; |
|
254 } else { |
|
255 if ((!prevLineBox() || prevLineBox()->isConstructed()) && |
|
256 ((lastLine && !object()->continuation()) || prevOnLineExists() || onEndChain(endObject))) |
|
257 includeLeftEdge = true; |
|
258 } |
|
259 } |
|
260 } |
|
261 |
|
262 setEdges(includeLeftEdge, includeRightEdge); |
|
263 |
|
264 // Recur into our children. |
|
265 for (InlineBox* currChild = firstChild(); currChild; currChild = currChild->nextOnLine()) { |
|
266 if (currChild->isInlineFlowBox()) { |
|
267 InlineFlowBox* currFlow = static_cast<InlineFlowBox*>(currChild); |
|
268 currFlow->determineSpacingForFlowBoxes(lastLine, endObject); |
|
269 } |
|
270 } |
|
271 } |
|
272 |
|
273 int InlineFlowBox::placeBoxesHorizontally(int x, int& leftPosition, int& rightPosition, bool& needsWordSpacing) |
|
274 { |
|
275 // Set our x position. |
|
276 setXPos(x); |
|
277 |
|
278 int boxShadowLeft = 0; |
|
279 int boxShadowRight = 0; |
|
280 if (ShadowData* boxShadow = object()->style(m_firstLine)->boxShadow()) { |
|
281 boxShadowLeft = min(boxShadow->x - boxShadow->blur, 0); |
|
282 boxShadowRight = max(boxShadow->x + boxShadow->blur, 0); |
|
283 } |
|
284 leftPosition = min(x + boxShadowLeft, leftPosition); |
|
285 |
|
286 int startX = x; |
|
287 x += borderLeft() + paddingLeft(); |
|
288 |
|
289 for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { |
|
290 if (curr->object()->isText()) { |
|
291 InlineTextBox* text = static_cast<InlineTextBox*>(curr); |
|
292 RenderText* rt = static_cast<RenderText*>(text->object()); |
|
293 if (rt->textLength()) { |
|
294 if (needsWordSpacing && DeprecatedChar(rt->characters()[text->start()]).isSpace()) |
|
295 x += rt->style(m_firstLine)->font().wordSpacing(); |
|
296 needsWordSpacing = !DeprecatedChar(rt->characters()[text->end()]).isSpace(); |
|
297 } |
|
298 text->setXPos(x); |
|
299 |
|
300 int strokeOverflow = static_cast<int>(ceilf(rt->style()->textStrokeWidth() / 2.0f)); |
|
301 |
|
302 // If letter-spacing is negative, we should factor that into right overflow. (Even in RTL, letter-spacing is |
|
303 // applied to the right, so this is not an issue with left overflow. |
|
304 int letterSpacing = min(0, (int)rt->style(m_firstLine)->font().letterSpacing()); |
|
305 |
|
306 int leftGlyphOverflow = -strokeOverflow; |
|
307 int rightGlyphOverflow = strokeOverflow - letterSpacing; |
|
308 |
|
309 int visualOverflowLeft = leftGlyphOverflow; |
|
310 int visualOverflowRight = rightGlyphOverflow; |
|
311 for (ShadowData* shadow = rt->style()->textShadow(); shadow; shadow = shadow->next) { |
|
312 visualOverflowLeft = min(visualOverflowLeft, shadow->x - shadow->blur + leftGlyphOverflow); |
|
313 visualOverflowRight = max(visualOverflowRight, shadow->x + shadow->blur + rightGlyphOverflow); |
|
314 } |
|
315 |
|
316 leftPosition = min(x + visualOverflowLeft, leftPosition); |
|
317 rightPosition = max(x + text->width() + visualOverflowRight, rightPosition); |
|
318 m_maxHorizontalVisualOverflow = max(max(visualOverflowRight, -visualOverflowLeft), m_maxHorizontalVisualOverflow); |
|
319 x += text->width(); |
|
320 } else { |
|
321 if (curr->object()->isPositioned()) { |
|
322 if (curr->object()->parent()->style()->direction() == LTR) |
|
323 curr->setXPos(x); |
|
324 else |
|
325 // Our offset that we cache needs to be from the edge of the right border box and |
|
326 // not the left border box. We have to subtract |x| from the width of the block |
|
327 // (which can be obtained from the root line box). |
|
328 curr->setXPos(root()->object()->width()-x); |
|
329 continue; // The positioned object has no effect on the width. |
|
330 } |
|
331 if (curr->object()->isInlineFlow()) { |
|
332 InlineFlowBox* flow = static_cast<InlineFlowBox*>(curr); |
|
333 if (curr->object()->isCompact()) { |
|
334 int ignoredX = x; |
|
335 flow->placeBoxesHorizontally(ignoredX, leftPosition, rightPosition, needsWordSpacing); |
|
336 } else { |
|
337 x += flow->marginLeft(); |
|
338 x = flow->placeBoxesHorizontally(x, leftPosition, rightPosition, needsWordSpacing); |
|
339 x += flow->marginRight(); |
|
340 } |
|
341 } else if (!curr->object()->isCompact() && (!curr->object()->isListMarker() || static_cast<RenderListMarker*>(curr->object())->isInside())) { |
|
342 x += curr->object()->marginLeft(); |
|
343 curr->setXPos(x); |
|
344 leftPosition = min(x + curr->object()->overflowLeft(false), leftPosition); |
|
345 rightPosition = max(x + curr->object()->overflowWidth(false), rightPosition); |
|
346 x += curr->width() + curr->object()->marginRight(); |
|
347 } |
|
348 } |
|
349 } |
|
350 |
|
351 x += borderRight() + paddingRight(); |
|
352 setWidth(x - startX); |
|
353 rightPosition = max(xPos() + width() + boxShadowRight, rightPosition); |
|
354 |
|
355 return x; |
|
356 } |
|
357 |
|
358 void InlineFlowBox::verticallyAlignBoxes(int& heightOfBlock) |
|
359 { |
|
360 int maxPositionTop = 0; |
|
361 int maxPositionBottom = 0; |
|
362 int maxAscent = 0; |
|
363 int maxDescent = 0; |
|
364 |
|
365 // Figure out if we're in strict mode. Note that we can't simply use !style()->htmlHacks(), |
|
366 // because that would match almost strict mode as well. |
|
367 RenderObject* curr = object(); |
|
368 while (curr && !curr->element()) |
|
369 curr = curr->container(); |
|
370 bool strictMode = (curr && curr->document()->inStrictMode()); |
|
371 |
|
372 computeLogicalBoxHeights(maxPositionTop, maxPositionBottom, maxAscent, maxDescent, strictMode); |
|
373 |
|
374 if (maxAscent + maxDescent < max(maxPositionTop, maxPositionBottom)) |
|
375 adjustMaxAscentAndDescent(maxAscent, maxDescent, maxPositionTop, maxPositionBottom); |
|
376 |
|
377 int maxHeight = maxAscent + maxDescent; |
|
378 int topPosition = heightOfBlock; |
|
379 int bottomPosition = heightOfBlock; |
|
380 int selectionTop = heightOfBlock; |
|
381 int selectionBottom = heightOfBlock; |
|
382 placeBoxesVertically(heightOfBlock, maxHeight, maxAscent, strictMode, topPosition, bottomPosition, selectionTop, selectionBottom); |
|
383 |
|
384 setVerticalOverflowPositions(topPosition, bottomPosition); |
|
385 setVerticalSelectionPositions(selectionTop, selectionBottom); |
|
386 |
|
387 // Shrink boxes with no text children in quirks and almost strict mode. |
|
388 if (!strictMode) |
|
389 shrinkBoxesWithNoTextChildren(topPosition, bottomPosition); |
|
390 |
|
391 heightOfBlock += maxHeight; |
|
392 } |
|
393 |
|
394 void InlineFlowBox::adjustMaxAscentAndDescent(int& maxAscent, int& maxDescent, |
|
395 int maxPositionTop, int maxPositionBottom) |
|
396 { |
|
397 for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { |
|
398 // The computed lineheight needs to be extended for the |
|
399 // positioned elements |
|
400 if (curr->object()->isPositioned()) |
|
401 continue; // Positioned placeholders don't affect calculations. |
|
402 if (curr->yPos() == PositionTop || curr->yPos() == PositionBottom) { |
|
403 if (curr->yPos() == PositionTop) { |
|
404 if (maxAscent + maxDescent < curr->height()) |
|
405 maxDescent = curr->height() - maxAscent; |
|
406 } |
|
407 else { |
|
408 if (maxAscent + maxDescent < curr->height()) |
|
409 maxAscent = curr->height() - maxDescent; |
|
410 } |
|
411 |
|
412 if (maxAscent + maxDescent >= max(maxPositionTop, maxPositionBottom)) |
|
413 break; |
|
414 } |
|
415 |
|
416 if (curr->isInlineFlowBox()) |
|
417 static_cast<InlineFlowBox*>(curr)->adjustMaxAscentAndDescent(maxAscent, maxDescent, maxPositionTop, maxPositionBottom); |
|
418 } |
|
419 } |
|
420 |
|
421 void InlineFlowBox::computeLogicalBoxHeights(int& maxPositionTop, int& maxPositionBottom, |
|
422 int& maxAscent, int& maxDescent, bool strictMode) |
|
423 { |
|
424 if (isRootInlineBox()) { |
|
425 // Examine our root box. |
|
426 setHeight(object()->lineHeight(m_firstLine, true)); |
|
427 bool isTableCell = object()->isTableCell(); |
|
428 if (isTableCell) { |
|
429 RenderTableCell* tableCell = static_cast<RenderTableCell*>(object()); |
|
430 setBaseline(tableCell->RenderBlock::baselinePosition(m_firstLine, true)); |
|
431 } |
|
432 else |
|
433 setBaseline(object()->baselinePosition(m_firstLine, true)); |
|
434 if (hasTextChildren() || strictMode) { |
|
435 int ascent = baseline(); |
|
436 int descent = height() - ascent; |
|
437 if (maxAscent < ascent) |
|
438 maxAscent = ascent; |
|
439 if (maxDescent < descent) |
|
440 maxDescent = descent; |
|
441 } |
|
442 } |
|
443 |
|
444 for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { |
|
445 if (curr->object()->isPositioned()) |
|
446 continue; // Positioned placeholders don't affect calculations. |
|
447 |
|
448 curr->setHeight(curr->object()->lineHeight(m_firstLine)); |
|
449 curr->setBaseline(curr->object()->baselinePosition(m_firstLine)); |
|
450 curr->setYPos(curr->object()->verticalPositionHint(m_firstLine)); |
|
451 if (curr->yPos() == PositionTop) { |
|
452 if (maxPositionTop < curr->height()) |
|
453 maxPositionTop = curr->height(); |
|
454 } |
|
455 else if (curr->yPos() == PositionBottom) { |
|
456 if (maxPositionBottom < curr->height()) |
|
457 maxPositionBottom = curr->height(); |
|
458 } |
|
459 else if (curr->hasTextChildren() || strictMode) { |
|
460 int ascent = curr->baseline() - curr->yPos(); |
|
461 int descent = curr->height() - ascent; |
|
462 if (maxAscent < ascent) |
|
463 maxAscent = ascent; |
|
464 if (maxDescent < descent) |
|
465 maxDescent = descent; |
|
466 } |
|
467 |
|
468 if (curr->isInlineFlowBox()) |
|
469 static_cast<InlineFlowBox*>(curr)->computeLogicalBoxHeights(maxPositionTop, maxPositionBottom, maxAscent, maxDescent, strictMode); |
|
470 } |
|
471 } |
|
472 |
|
473 void InlineFlowBox::placeBoxesVertically(int y, int maxHeight, int maxAscent, bool strictMode, |
|
474 int& topPosition, int& bottomPosition, int& selectionTop, int& selectionBottom) |
|
475 { |
|
476 if (isRootInlineBox()) |
|
477 setYPos(y + maxAscent - baseline());// Place our root box. |
|
478 |
|
479 for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { |
|
480 if (curr->object()->isPositioned()) |
|
481 continue; // Positioned placeholders don't affect calculations. |
|
482 |
|
483 // Adjust boxes to use their real box y/height and not the logical height (as dictated by |
|
484 // line-height). |
|
485 if (curr->isInlineFlowBox()) |
|
486 static_cast<InlineFlowBox*>(curr)->placeBoxesVertically(y, maxHeight, maxAscent, strictMode, topPosition, bottomPosition, selectionTop, selectionBottom); |
|
487 |
|
488 bool childAffectsTopBottomPos = true; |
|
489 if (curr->yPos() == PositionTop) |
|
490 curr->setYPos(y); |
|
491 else if (curr->yPos() == PositionBottom) |
|
492 curr->setYPos(y + maxHeight - curr->height()); |
|
493 else { |
|
494 if (!curr->hasTextChildren() && !strictMode) |
|
495 childAffectsTopBottomPos = false; |
|
496 curr->setYPos(curr->yPos() + y + maxAscent - curr->baseline()); |
|
497 } |
|
498 |
|
499 int newY = curr->yPos(); |
|
500 int newHeight = curr->height(); |
|
501 int newBaseline = curr->baseline(); |
|
502 int overflowTop = 0; |
|
503 int overflowBottom = 0; |
|
504 if (curr->isText() || curr->isInlineFlowBox()) { |
|
505 const Font& font = curr->object()->style(m_firstLine)->font(); |
|
506 newBaseline = font.ascent(); |
|
507 newY += curr->baseline() - newBaseline; |
|
508 newHeight = newBaseline + font.descent(); |
|
509 for (ShadowData* shadow = curr->object()->style()->textShadow(); shadow; shadow = shadow->next) { |
|
510 overflowTop = min(overflowTop, shadow->y - shadow->blur); |
|
511 overflowBottom = max(overflowBottom, shadow->y + shadow->blur); |
|
512 } |
|
513 |
|
514 if (ShadowData* boxShadow = curr->object()->style(m_firstLine)->boxShadow()) { |
|
515 overflowTop = min(overflowTop, boxShadow->y - boxShadow->blur); |
|
516 overflowBottom = max(overflowBottom, boxShadow->y + boxShadow->blur); |
|
517 } |
|
518 |
|
519 if (curr->isInlineFlowBox()) { |
|
520 newHeight += curr->object()->borderTop() + curr->object()->paddingTop() + |
|
521 curr->object()->borderBottom() + curr->object()->paddingBottom(); |
|
522 newY -= curr->object()->borderTop() + curr->object()->paddingTop(); |
|
523 newBaseline += curr->object()->borderTop() + curr->object()->paddingTop(); |
|
524 } |
|
525 } else if (!curr->object()->isBR()) { |
|
526 newY += curr->object()->marginTop(); |
|
527 newHeight = curr->height() - (curr->object()->marginTop() + curr->object()->marginBottom()); |
|
528 overflowTop = curr->object()->overflowTop(false); |
|
529 overflowBottom = curr->object()->overflowHeight(false) - newHeight; |
|
530 } |
|
531 |
|
532 curr->setYPos(newY); |
|
533 curr->setHeight(newHeight); |
|
534 curr->setBaseline(newBaseline); |
|
535 |
|
536 if (childAffectsTopBottomPos) { |
|
537 selectionTop = min(selectionTop, newY); |
|
538 selectionBottom = max(selectionBottom, newY + newHeight); |
|
539 topPosition = min(topPosition, newY + overflowTop); |
|
540 bottomPosition = max(bottomPosition, newY + newHeight + overflowBottom); |
|
541 } |
|
542 } |
|
543 |
|
544 if (isRootInlineBox()) { |
|
545 const Font& font = object()->style(m_firstLine)->font(); |
|
546 setHeight(font.ascent() + font.descent()); |
|
547 setYPos(yPos() + baseline() - font.ascent()); |
|
548 setBaseline(font.ascent()); |
|
549 if (hasTextChildren() || strictMode) { |
|
550 selectionTop = min(selectionTop, yPos()); |
|
551 selectionBottom = max(selectionBottom, yPos() + height()); |
|
552 } |
|
553 } |
|
554 } |
|
555 |
|
556 void InlineFlowBox::shrinkBoxesWithNoTextChildren(int topPos, int bottomPos) |
|
557 { |
|
558 // First shrink our kids. |
|
559 for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { |
|
560 if (curr->object()->isPositioned()) |
|
561 continue; // Positioned placeholders don't affect calculations. |
|
562 |
|
563 if (curr->isInlineFlowBox()) |
|
564 static_cast<InlineFlowBox*>(curr)->shrinkBoxesWithNoTextChildren(topPos, bottomPos); |
|
565 } |
|
566 |
|
567 // See if we have text children. If not, then we need to shrink ourselves to fit on the line. |
|
568 if (!hasTextChildren()) { |
|
569 if (yPos() < topPos) |
|
570 setYPos(topPos); |
|
571 if (yPos() + height() > bottomPos) |
|
572 setHeight(bottomPos - yPos()); |
|
573 if (baseline() > height()) |
|
574 setBaseline(height()); |
|
575 } |
|
576 } |
|
577 |
|
578 bool InlineFlowBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty) |
|
579 { |
|
580 // Check children first. |
|
581 for (InlineBox* curr = lastChild(); curr; curr = curr->prevOnLine()) { |
|
582 if (!curr->object()->hasLayer() && curr->nodeAtPoint(request, result, x, y, tx, ty)) { |
|
583 object()->updateHitTestResult(result, IntPoint(x - tx, y - ty)); |
|
584 return true; |
|
585 } |
|
586 } |
|
587 |
|
588 // Now check ourselves. |
|
589 IntRect rect(tx + m_x, ty + m_y, m_width, m_height); |
|
590 if (object()->style()->visibility() == VISIBLE && rect.contains(x, y)) { |
|
591 object()->updateHitTestResult(result, IntPoint(x - tx, y - ty)); // Don't add in m_x or m_y here, we want coords in the containing block's space. |
|
592 return true; |
|
593 } |
|
594 |
|
595 return false; |
|
596 } |
|
597 |
|
598 void InlineFlowBox::paint(RenderObject::PaintInfo& paintInfo, int tx, int ty) |
|
599 { |
|
600 int xPos = tx + m_x - object()->maximalOutlineSize(paintInfo.phase); |
|
601 int w = width() + 2 * object()->maximalOutlineSize(paintInfo.phase); |
|
602 if (ShadowData* boxShadow = object()->style(m_firstLine)->boxShadow()) { |
|
603 int shadowLeft = min(boxShadow->x - boxShadow->blur, 0); |
|
604 xPos += shadowLeft; |
|
605 w += -shadowLeft + max(boxShadow->x + boxShadow->blur, 0); |
|
606 } |
|
607 bool intersectsDamageRect = xPos < paintInfo.rect.right() && xPos + w > paintInfo.rect.x(); |
|
608 |
|
609 if (intersectsDamageRect && paintInfo.phase != PaintPhaseChildOutlines) { |
|
610 if (paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) { |
|
611 // Add ourselves to the paint info struct's list of inlines that need to paint their |
|
612 // outlines. |
|
613 if (object()->style()->visibility() == VISIBLE && object()->hasOutline() && !isRootInlineBox()) { |
|
614 if ((object()->continuation() || object()->isInlineContinuation()) && !object()->hasLayer()) { |
|
615 // Add ourselves to the containing block of the entire continuation so that it can |
|
616 // paint us atomically. |
|
617 RenderBlock* block = object()->containingBlock()->containingBlock(); |
|
618 block->addContinuationWithOutline(static_cast<RenderFlow*>(object()->element()->renderer())); |
|
619 } else if (!object()->isInlineContinuation()) |
|
620 paintInfo.outlineObjects->add(flowObject()); |
|
621 } |
|
622 } else { |
|
623 // 1. Paint our background, border and box-shadow. |
|
624 paintBoxDecorations(paintInfo, tx, ty); |
|
625 |
|
626 // 2. Paint our underline and overline. |
|
627 paintTextDecorations(paintInfo, tx, ty, false); |
|
628 } |
|
629 } |
|
630 |
|
631 PaintPhase paintPhase = paintInfo.phase == PaintPhaseChildOutlines ? PaintPhaseOutline : paintInfo.phase; |
|
632 RenderObject::PaintInfo childInfo(paintInfo); |
|
633 childInfo.phase = paintPhase; |
|
634 childInfo.paintingRoot = object()->paintingRootForChildren(paintInfo); |
|
635 |
|
636 // 3. Paint our children. |
|
637 if (paintPhase != PaintPhaseSelfOutline) { |
|
638 for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { |
|
639 if (!curr->object()->hasLayer()) |
|
640 curr->paint(childInfo, tx, ty); |
|
641 } |
|
642 } |
|
643 |
|
644 // 4. Paint our strike-through |
|
645 if (intersectsDamageRect && (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection)) |
|
646 paintTextDecorations(paintInfo, tx, ty, true); |
|
647 } |
|
648 |
|
649 void InlineFlowBox::paintBackgrounds(GraphicsContext* p, const Color& c, const BackgroundLayer* bgLayer, |
|
650 int my, int mh, int _tx, int _ty, int w, int h) |
|
651 { |
|
652 if (!bgLayer) |
|
653 return; |
|
654 paintBackgrounds(p, c, bgLayer->next(), my, mh, _tx, _ty, w, h); |
|
655 paintBackground(p, c, bgLayer, my, mh, _tx, _ty, w, h); |
|
656 } |
|
657 |
|
658 void InlineFlowBox::paintBackground(GraphicsContext* context, const Color& c, const BackgroundLayer* bgLayer, |
|
659 int my, int mh, int tx, int ty, int w, int h) |
|
660 { |
|
661 CachedImage* bg = bgLayer->backgroundImage(); |
|
662 bool hasBackgroundImage = bg && bg->canRender(); |
|
663 if ((!hasBackgroundImage && !object()->style()->hasBorderRadius()) || (!prevLineBox() && !nextLineBox()) || !parent()) |
|
664 object()->paintBackgroundExtended(context, c, bgLayer, my, mh, tx, ty, w, h); |
|
665 else { |
|
666 // We have a background image that spans multiple lines. |
|
667 // We need to adjust _tx and _ty by the width of all previous lines. |
|
668 // Think of background painting on inlines as though you had one long line, a single continuous |
|
669 // strip. Even though that strip has been broken up across multiple lines, you still paint it |
|
670 // as though you had one single line. This means each line has to pick up the background where |
|
671 // the previous line left off. |
|
672 // FIXME: What the heck do we do with RTL here? The math we're using is obviously not right, |
|
673 // but it isn't even clear how this should work at all. |
|
674 int xOffsetOnLine = 0; |
|
675 for (InlineRunBox* curr = prevLineBox(); curr; curr = curr->prevLineBox()) |
|
676 xOffsetOnLine += curr->width(); |
|
677 int startX = tx - xOffsetOnLine; |
|
678 int totalWidth = xOffsetOnLine; |
|
679 for (InlineRunBox* curr = this; curr; curr = curr->nextLineBox()) |
|
680 totalWidth += curr->width(); |
|
681 context->save(); |
|
682 context->clip(IntRect(tx, ty, width(), height())); |
|
683 object()->paintBackgroundExtended(context, c, bgLayer, my, mh, startX, ty, |
|
684 totalWidth, h, includeLeftEdge(), includeRightEdge()); |
|
685 context->restore(); |
|
686 } |
|
687 } |
|
688 |
|
689 void InlineFlowBox::paintBoxShadow(GraphicsContext* context, RenderStyle* s, int tx, int ty, int w, int h) |
|
690 { |
|
691 if ((!prevLineBox() && !nextLineBox()) || !parent()) |
|
692 object()->paintBoxShadow(context, tx, ty, w, h, s); |
|
693 else { |
|
694 // FIXME: We can do better here in the multi-line case. We want to push a clip so that the shadow doesn't |
|
695 // protrude incorrectly at the edges, and we want to possibly include shadows cast from the previous/following lines |
|
696 object()->paintBoxShadow(context, tx, ty, w, h, s, includeLeftEdge(), includeRightEdge()); |
|
697 } |
|
698 } |
|
699 |
|
700 void InlineFlowBox::paintBoxDecorations(RenderObject::PaintInfo& paintInfo, int tx, int ty) |
|
701 { |
|
702 if (!object()->shouldPaintWithinRoot(paintInfo) || object()->style()->visibility() != VISIBLE || paintInfo.phase != PaintPhaseForeground) |
|
703 return; |
|
704 |
|
705 // Move x/y to our coordinates. |
|
706 tx += m_x; |
|
707 ty += m_y; |
|
708 |
|
709 int w = width(); |
|
710 int h = height(); |
|
711 |
|
712 int my = max(ty, paintInfo.rect.y()); |
|
713 int mh; |
|
714 if (ty < paintInfo.rect.y()) |
|
715 mh = max(0, h - (paintInfo.rect.y() - ty)); |
|
716 else |
|
717 mh = min(paintInfo.rect.height(), h); |
|
718 |
|
719 GraphicsContext* context = paintInfo.context; |
|
720 |
|
721 // You can use p::first-line to specify a background. If so, the root line boxes for |
|
722 // a line may actually have to paint a background. |
|
723 RenderStyle* styleToUse = object()->style(m_firstLine); |
|
724 if ((!parent() && m_firstLine && styleToUse != object()->style()) || (parent() && object()->hasBoxDecorations())) { |
|
725 // Shadow comes first and is behind the background and border. |
|
726 if (styleToUse->boxShadow()) |
|
727 paintBoxShadow(context, styleToUse, tx, ty, w, h); |
|
728 |
|
729 Color c = styleToUse->backgroundColor(); |
|
730 paintBackgrounds(context, c, styleToUse->backgroundLayers(), my, mh, tx, ty, w, h); |
|
731 |
|
732 // :first-line cannot be used to put borders on a line. Always paint borders with our |
|
733 // non-first-line style. |
|
734 if (parent() && object()->style()->hasBorder()) { |
|
735 CachedImage* borderImage = object()->style()->borderImage().image(); |
|
736 bool hasBorderImage = borderImage && borderImage->canRender(); |
|
737 if (hasBorderImage && !borderImage->isLoaded()) |
|
738 return; // Don't paint anything while we wait for the image to load. |
|
739 |
|
740 // The simple case is where we either have no border image or we are the only box for this object. In those |
|
741 // cases only a single call to draw is required. |
|
742 if (!hasBorderImage || (!prevLineBox() && !nextLineBox())) |
|
743 object()->paintBorder(context, tx, ty, w, h, object()->style(), includeLeftEdge(), includeRightEdge()); |
|
744 else { |
|
745 // We have a border image that spans multiple lines. |
|
746 // We need to adjust _tx and _ty by the width of all previous lines. |
|
747 // Think of border image painting on inlines as though you had one long line, a single continuous |
|
748 // strip. Even though that strip has been broken up across multiple lines, you still paint it |
|
749 // as though you had one single line. This means each line has to pick up the image where |
|
750 // the previous line left off. |
|
751 // FIXME: What the heck do we do with RTL here? The math we're using is obviously not right, |
|
752 // but it isn't even clear how this should work at all. |
|
753 int xOffsetOnLine = 0; |
|
754 for (InlineRunBox* curr = prevLineBox(); curr; curr = curr->prevLineBox()) |
|
755 xOffsetOnLine += curr->width(); |
|
756 int startX = tx - xOffsetOnLine; |
|
757 int totalWidth = xOffsetOnLine; |
|
758 for (InlineRunBox* curr = this; curr; curr = curr->nextLineBox()) |
|
759 totalWidth += curr->width(); |
|
760 context->save(); |
|
761 context->clip(IntRect(tx, ty, width(), height())); |
|
762 object()->paintBorder(context, startX, ty, totalWidth, h, object()->style()); |
|
763 context->restore(); |
|
764 } |
|
765 } |
|
766 } |
|
767 } |
|
768 |
|
769 static bool shouldDrawTextDecoration(RenderObject* obj) |
|
770 { |
|
771 for (RenderObject* curr = obj->firstChild(); curr; curr = curr->nextSibling()) { |
|
772 if (curr->isInlineFlow()) |
|
773 return true; |
|
774 if (curr->isText() && !curr->isBR()) { |
|
775 if (!curr->style()->collapseWhiteSpace()) |
|
776 return true; |
|
777 Node* currElement = curr->element(); |
|
778 if (!currElement) |
|
779 return true; |
|
780 if (!currElement->isTextNode()) |
|
781 return true; |
|
782 if (!static_cast<Text*>(currElement)->containsOnlyWhitespace()) |
|
783 return true; |
|
784 } |
|
785 } |
|
786 return false; |
|
787 } |
|
788 |
|
789 void InlineFlowBox::paintTextDecorations(RenderObject::PaintInfo& paintInfo, int tx, int ty, bool paintedChildren) |
|
790 { |
|
791 // Paint text decorations like underlines/overlines. We only do this if we aren't in quirks mode (i.e., in |
|
792 // almost-strict mode or strict mode). |
|
793 if (object()->style()->htmlHacks() || !object()->shouldPaintWithinRoot(paintInfo) || |
|
794 object()->style()->visibility() != VISIBLE) |
|
795 return; |
|
796 |
|
797 // We don't want underlines or other decorations when we're trying to draw nothing but the selection as white text. |
|
798 if (paintInfo.phase == PaintPhaseSelection && paintInfo.forceBlackText) |
|
799 return; |
|
800 |
|
801 GraphicsContext* context = paintInfo.context; |
|
802 tx += m_x; |
|
803 ty += m_y; |
|
804 RenderStyle* styleToUse = object()->style(m_firstLine); |
|
805 int deco = parent() ? styleToUse->textDecoration() : styleToUse->textDecorationsInEffect(); |
|
806 if (deco != TDNONE && |
|
807 ((!paintedChildren && ((deco & UNDERLINE) || (deco & OVERLINE))) || (paintedChildren && (deco & LINE_THROUGH))) && |
|
808 shouldDrawTextDecoration(object())) { |
|
809 int x = m_x + borderLeft() + paddingLeft(); |
|
810 int w = m_width - (borderLeft() + paddingLeft() + borderRight() + paddingRight()); |
|
811 RootInlineBox* rootLine = root(); |
|
812 if (rootLine->ellipsisBox()) { |
|
813 int ellipsisX = rootLine->ellipsisBox()->xPos(); |
|
814 int ellipsisWidth = rootLine->ellipsisBox()->width(); |
|
815 |
|
816 // FIXME: Will need to work with RTL |
|
817 if (rootLine == this) { |
|
818 if (x + w >= ellipsisX + ellipsisWidth) |
|
819 w -= (x + w - ellipsisX - ellipsisWidth); |
|
820 } else { |
|
821 if (x >= ellipsisX) |
|
822 return; |
|
823 if (x + w >= ellipsisX) |
|
824 w -= (x + w - ellipsisX); |
|
825 } |
|
826 } |
|
827 |
|
828 // Set up the appropriate text-shadow effect for the decoration. |
|
829 // FIXME: Support multiple shadow effects. Need more from the CG API before we can do this. |
|
830 bool setShadow = false; |
|
831 if (styleToUse->textShadow()) { |
|
832 context->setShadow(IntSize(styleToUse->textShadow()->x, styleToUse->textShadow()->y), |
|
833 styleToUse->textShadow()->blur, styleToUse->textShadow()->color); |
|
834 setShadow = true; |
|
835 } |
|
836 |
|
837 // We must have child boxes and have decorations defined. |
|
838 tx += borderLeft() + paddingLeft(); |
|
839 |
|
840 Color underline, overline, linethrough; |
|
841 underline = overline = linethrough = styleToUse->color(); |
|
842 if (!parent()) |
|
843 object()->getTextDecorationColors(deco, underline, overline, linethrough); |
|
844 |
|
845 if (styleToUse->font() != context->font()) |
|
846 context->setFont(styleToUse->font()); |
|
847 |
|
848 bool isPrinting = object()->document()->printing(); |
|
849 context->setStrokeThickness(1.0f); // FIXME: We should improve this rule and not always just assume 1. |
|
850 if (deco & UNDERLINE && !paintedChildren) { |
|
851 context->setStrokeColor(underline); |
|
852 // Leave one pixel of white between the baseline and the underline. |
|
853 context->drawLineForText(IntPoint(tx, ty + m_baseline + 1), w, isPrinting); |
|
854 } |
|
855 if (deco & OVERLINE && !paintedChildren) { |
|
856 context->setStrokeColor(overline); |
|
857 context->drawLineForText(IntPoint(tx, ty), w, isPrinting); |
|
858 } |
|
859 if (deco & LINE_THROUGH && paintedChildren) { |
|
860 context->setStrokeColor(linethrough); |
|
861 context->drawLineForText(IntPoint(tx, ty + 2 * m_baseline / 3), w, isPrinting); |
|
862 } |
|
863 |
|
864 if (setShadow) |
|
865 context->clearShadow(); |
|
866 } |
|
867 } |
|
868 |
|
869 InlineBox* InlineFlowBox::firstLeafChild() |
|
870 { |
|
871 return firstLeafChildAfterBox(); |
|
872 } |
|
873 |
|
874 InlineBox* InlineFlowBox::lastLeafChild() |
|
875 { |
|
876 return lastLeafChildBeforeBox(); |
|
877 } |
|
878 |
|
879 InlineBox* InlineFlowBox::firstLeafChildAfterBox(InlineBox* start) |
|
880 { |
|
881 InlineBox* leaf = 0; |
|
882 for (InlineBox* box = start ? start->nextOnLine() : firstChild(); box && !leaf; box = box->nextOnLine()) |
|
883 leaf = box->firstLeafChild(); |
|
884 if (start && !leaf && parent()) |
|
885 return parent()->firstLeafChildAfterBox(this); |
|
886 return leaf; |
|
887 } |
|
888 |
|
889 InlineBox* InlineFlowBox::lastLeafChildBeforeBox(InlineBox* start) |
|
890 { |
|
891 InlineBox* leaf = 0; |
|
892 for (InlineBox* box = start ? start->prevOnLine() : lastChild(); box && !leaf; box = box->prevOnLine()) |
|
893 leaf = box->lastLeafChild(); |
|
894 if (start && !leaf && parent()) |
|
895 return parent()->lastLeafChildBeforeBox(this); |
|
896 return leaf; |
|
897 } |
|
898 |
|
899 RenderObject::SelectionState InlineFlowBox::selectionState() |
|
900 { |
|
901 return RenderObject::SelectionNone; |
|
902 } |
|
903 |
|
904 bool InlineFlowBox::canAccommodateEllipsis(bool ltr, int blockEdge, int ellipsisWidth) |
|
905 { |
|
906 for (InlineBox *box = firstChild(); box; box = box->nextOnLine()) { |
|
907 if (!box->canAccommodateEllipsis(ltr, blockEdge, ellipsisWidth)) |
|
908 return false; |
|
909 } |
|
910 return true; |
|
911 } |
|
912 |
|
913 int InlineFlowBox::placeEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth, bool& foundBox) |
|
914 { |
|
915 int result = -1; |
|
916 for (InlineBox *box = firstChild(); box; box = box->nextOnLine()) { |
|
917 int currResult = box->placeEllipsisBox(ltr, blockEdge, ellipsisWidth, foundBox); |
|
918 if (currResult != -1 && result == -1) |
|
919 result = currResult; |
|
920 } |
|
921 return result; |
|
922 } |
|
923 |
|
924 void InlineFlowBox::clearTruncation() |
|
925 { |
|
926 for (InlineBox *box = firstChild(); box; box = box->nextOnLine()) |
|
927 box->clearTruncation(); |
|
928 } |
|
929 |
|
930 #ifndef NDEBUG |
|
931 |
|
932 void InlineFlowBox::checkConsistency() const |
|
933 { |
|
934 #ifdef CHECK_CONSISTENCY |
|
935 ASSERT(!m_hasBadChildList); |
|
936 const InlineBox* prev = 0; |
|
937 for (const InlineBox* child = m_firstChild; child; child = child->nextOnLine()) { |
|
938 ASSERT(child->parent() == this); |
|
939 ASSERT(child->prevOnLine() == prev); |
|
940 prev = child; |
|
941 } |
|
942 ASSERT(prev == m_lastChild); |
|
943 #endif |
|
944 } |
|
945 |
|
946 #endif |
|
947 |
|
948 } // namespace WebCore |