source: webkit/trunk/Source/WebCore/rendering/RenderRubyRun.cpp

Last change on this file was 292226, checked in by Alan Bujtas, 3 years ago

[Ruby] Statically positioned out-of-flow block boxes are mispositioned
https://bugs.webkit.org/show_bug.cgi?id=238653

Reviewed by Antti Koivisto.

Source/WebCore:

A statically positioned out-of-flow ruby base e.g.

<div>A<ruby><rb style="position: absolute">B</rb></ruby></div>

and

<div>A<span style="position: absolute">B</span></div>

should match layout where <rb> box's top is aligned with current line box's top position
(as if <rb> was inflow, which then puts "A" and "B" (somewhat) next to each other vertically).

However since we wrap such content (<rb>) inside an inline-block type anonymous box (RenderRubyBase), in practice
we match instead this:

<div>A

<span style="display: inline-block">

<span style="position: absolute">B</span>

</span>

</div>

where <rb>'s top is now aligned with the top of the wrapper inline-block box's top.
But this inline-block box sits on the line box's baseline with the height of 0 (it's inflow empty)
which makes the <rb> aligned with the line box's baseline instead of its top (and now "B" in below "A" under the baseline).

This patch fixes this by making sure that the (inflow empty) inline-block box's baseline is computed as if it had inflow content.

Test: fast/ruby/ruby-with-out-of-flow-base-content.html

  • rendering/RenderRubyBase.cpp:

(WebCore::RenderRubyBase::isEmptyOrHasInFlowContent const):

  • rendering/RenderRubyBase.h:
  • rendering/RenderRubyRun.cpp:

(WebCore::RenderRubyRun::baselinePosition const):

  • rendering/RenderRubyRun.h:

LayoutTests:

  • TestExpectations:
  • fast/ruby/ruby-with-out-of-flow-base-content-expected.html: Added.
  • fast/ruby/ruby-with-out-of-flow-base-content.html: Added.
  • Property svn:eol-style set to native
File size: 11.5 KB
Line 
1/*
2 * Copyright (C) 2009 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32
33#include "RenderRubyRun.h"
34
35#include "RenderRuby.h"
36#include "RenderRubyBase.h"
37#include "RenderRubyText.h"
38#include "RenderText.h"
39#include "RenderView.h"
40#include "StyleInheritedData.h"
41#include <wtf/IsoMallocInlines.h>
42#include <wtf/StackStats.h>
43
44namespace WebCore {
45
46WTF_MAKE_ISO_ALLOCATED_IMPL(RenderRubyRun);
47
48RenderRubyRun::RenderRubyRun(Document& document, RenderStyle&& style)
49 : RenderBlockFlow(document, WTFMove(style))
50 , m_lastCharacter(0)
51 , m_secondToLastCharacter(0)
52{
53 setReplacedOrInlineBlock(true);
54 setInline(true);
55}
56
57RenderRubyRun::~RenderRubyRun() = default;
58
59bool RenderRubyRun::hasRubyText() const
60{
61 // The only place where a ruby text can be is in the first position
62 // Note: As anonymous blocks, ruby runs do not have ':before' or ':after' content themselves.
63 return firstChild() && firstChild()->isRubyText();
64}
65
66bool RenderRubyRun::hasRubyBase() const
67{
68 // The only place where a ruby base can be is in the last position
69 // Note: As anonymous blocks, ruby runs do not have ':before' or ':after' content themselves.
70 return lastChild() && lastChild()->isRubyBase();
71}
72
73RenderRubyText* RenderRubyRun::rubyText() const
74{
75 RenderObject* child = firstChild();
76 // If in future it becomes necessary to support floating or positioned ruby text,
77 // layout will have to be changed to handle them properly.
78 ASSERT(!child || !child->isRubyText() || !child->isFloatingOrOutOfFlowPositioned());
79 return child && child->isRubyText() ? static_cast<RenderRubyText*>(child) : nullptr;
80}
81
82RenderRubyBase* RenderRubyRun::rubyBase() const
83{
84 RenderObject* child = lastChild();
85 return child && child->isRubyBase() ? static_cast<RenderRubyBase*>(child) : nullptr;
86}
87
88bool RenderRubyRun::isChildAllowed(const RenderObject& child, const RenderStyle&) const
89{
90 return child.isInline() || child.isRubyText();
91}
92
93RenderPtr<RenderRubyBase> RenderRubyRun::createRubyBase() const
94{
95 auto newStyle = RenderStyle::createAnonymousStyleWithDisplay(style(), DisplayType::Block);
96 newStyle.setTextAlign(TextAlignMode::Center); // FIXME: use TextAlignMode::WebKitCenter?
97 auto renderer = createRenderer<RenderRubyBase>(document(), WTFMove(newStyle));
98 renderer->initializeStyle();
99 return renderer;
100}
101
102RenderPtr<RenderRubyRun> RenderRubyRun::staticCreateRubyRun(const RenderObject* parentRuby)
103{
104 ASSERT(isRuby(parentRuby));
105 auto renderer = createRenderer<RenderRubyRun>(parentRuby->document(), RenderStyle::createAnonymousStyleWithDisplay(parentRuby->style(), DisplayType::InlineBlock));
106 renderer->initializeStyle();
107 return renderer;
108}
109
110void RenderRubyRun::layoutExcludedChildren(bool relayoutChildren)
111{
112 RenderBlockFlow::layoutExcludedChildren(relayoutChildren);
113
114 StackStats::LayoutCheckPoint layoutCheckPoint;
115 // Don't bother positioning the RenderRubyRun yet.
116 RenderRubyText* rt = rubyText();
117 if (!rt)
118 return;
119 rt->setIsExcludedFromNormalLayout(true);
120 if (relayoutChildren)
121 rt->setChildNeedsLayout(MarkOnlyThis);
122 rt->layoutIfNeeded();
123}
124
125void RenderRubyRun::layout()
126{
127 if (RenderRubyBase* base = rubyBase())
128 base->reset();
129 RenderBlockFlow::layout();
130}
131
132void RenderRubyRun::layoutBlock(bool relayoutChildren, LayoutUnit pageHeight)
133{
134 if (!relayoutChildren) {
135 // Since the extra relayout in RenderBlockFlow::updateRubyForJustifiedText() causes the size of the RenderRubyText/RenderRubyBase
136 // dependent on the line's current expansion, whenever we relayout the RenderRubyRun, we need to relayout the RenderRubyBase/RenderRubyText as well.
137 // FIXME: We should take the expansion opportunities into account if possible.
138 relayoutChildren = style().textAlign() == TextAlignMode::Justify;
139 }
140
141 RenderBlockFlow::layoutBlock(relayoutChildren, pageHeight);
142
143 RenderRubyText* rt = rubyText();
144 if (!rt)
145 return;
146
147 rt->setLogicalLeft(0);
148
149 // Place the RenderRubyText such that its bottom is flush with the lineTop of the first line of the RenderRubyBase.
150 LayoutUnit lastLineRubyTextBottom = rt->logicalHeight();
151 LayoutUnit firstLineRubyTextTop;
152 LegacyRootInlineBox* rootBox = rt->lastRootBox();
153 if (rootBox) {
154 // In order to align, we have to ignore negative leading.
155 firstLineRubyTextTop = rt->firstRootBox()->logicalTopLayoutOverflow();
156 lastLineRubyTextBottom = rootBox->logicalBottomLayoutOverflow();
157 }
158
159 if (isHorizontalWritingMode() && rt->style().rubyPosition() == RubyPosition::InterCharacter) {
160 // Bopomofo. We need to move the RenderRubyText over to the right side and center it
161 // vertically relative to the base.
162 const FontCascade& font = style().fontCascade();
163 float distanceBetweenBase = std::max(font.letterSpacing(), 2.0f * rt->style().fontCascade().metricsOfPrimaryFont().height());
164 setWidth(width() + distanceBetweenBase - font.letterSpacing());
165 if (RenderRubyBase* rb = rubyBase()) {
166 LayoutUnit firstLineTop;
167 LayoutUnit lastLineBottom = logicalHeight();
168 LegacyRootInlineBox* rootBox = rb->firstRootBox();
169 if (rootBox)
170 firstLineTop = rootBox->logicalTopLayoutOverflow();
171 firstLineTop += rb->logicalTop();
172 if (rootBox)
173 lastLineBottom = rootBox->logicalBottomLayoutOverflow();
174 lastLineBottom += rb->logicalTop();
175 rt->setX(rb->x() + rb->width() - font.letterSpacing());
176 LayoutUnit extent = lastLineBottom - firstLineTop;
177 rt->setY(firstLineTop + (extent - rt->height()) / 2);
178 }
179 } else if (style().isFlippedLinesWritingMode() == (style().rubyPosition() == RubyPosition::After)) {
180 LayoutUnit firstLineTop;
181 if (RenderRubyBase* rb = rubyBase()) {
182 LegacyRootInlineBox* rootBox = rb->firstRootBox();
183 if (rootBox)
184 firstLineTop = rootBox->logicalTopLayoutOverflow();
185 firstLineTop += rb->logicalTop();
186 }
187
188 rt->setLogicalTop(-lastLineRubyTextBottom + firstLineTop);
189 } else {
190 LayoutUnit lastLineBottom = logicalHeight();
191 if (RenderRubyBase* rb = rubyBase()) {
192 LegacyRootInlineBox* rootBox = rb->lastRootBox();
193 if (rootBox)
194 lastLineBottom = rootBox->logicalBottomLayoutOverflow();
195 lastLineBottom += rb->logicalTop();
196 }
197
198 rt->setLogicalTop(-firstLineRubyTextTop + lastLineBottom);
199 }
200
201 // Update our overflow to account for the new RenderRubyText position.
202 computeOverflow(clientLogicalBottom());
203}
204
205LayoutUnit RenderRubyRun::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode lineDirectionMode, LinePositionMode linePositionMode) const
206{
207 // The (inline-block type) ruby base wrapper box fails to produce the correct
208 // baseline when the base is, or has out-of-flow content only.
209 if (!rubyBase() || rubyBase()->isEmptyOrHasInFlowContent())
210 return RenderBlockFlow::baselinePosition(baselineType, firstLine, lineDirectionMode, linePositionMode);
211 auto& style = firstLine ? firstLineStyle() : this->style();
212 return LayoutUnit { style.metricsOfPrimaryFont().ascent(baselineType) };
213}
214
215static bool shouldOverhang(bool firstLine, const RenderObject* renderer, const RenderRubyBase& rubyBase)
216{
217 if (!renderer || !renderer->isText())
218 return false;
219 const RenderStyle& rubyBaseStyle = firstLine ? rubyBase.firstLineStyle() : rubyBase.style();
220 const RenderStyle& style = firstLine ? renderer->firstLineStyle() : renderer->style();
221 return style.computedFontPixelSize() <= rubyBaseStyle.computedFontPixelSize();
222}
223
224void RenderRubyRun::getOverhang(bool firstLine, RenderObject* startRenderer, RenderObject* endRenderer, float& startOverhang, float& endOverhang) const
225{
226 ASSERT(!needsLayout());
227
228 startOverhang = 0;
229 endOverhang = 0;
230
231 RenderRubyBase* rubyBase = this->rubyBase();
232 RenderRubyText* rubyText = this->rubyText();
233
234 if (!rubyBase || !rubyText)
235 return;
236
237 if (!rubyBase->firstRootBox())
238 return;
239
240 LayoutUnit logicalWidth = this->logicalWidth();
241 float logicalLeftOverhang = std::numeric_limits<float>::max();
242 float logicalRightOverhang = std::numeric_limits<float>::max();
243 for (auto* rootInlineBox = rubyBase->firstRootBox(); rootInlineBox; rootInlineBox = rootInlineBox->nextRootBox()) {
244 logicalLeftOverhang = std::min<float>(logicalLeftOverhang, rootInlineBox->logicalLeft());
245 logicalRightOverhang = std::min<float>(logicalRightOverhang, logicalWidth - rootInlineBox->logicalRight());
246 }
247
248 startOverhang = style().isLeftToRightDirection() ? logicalLeftOverhang : logicalRightOverhang;
249 endOverhang = style().isLeftToRightDirection() ? logicalRightOverhang : logicalLeftOverhang;
250
251 if (!shouldOverhang(firstLine, startRenderer, *rubyBase))
252 startOverhang = 0;
253 if (!shouldOverhang(firstLine, endRenderer, *rubyBase))
254 endOverhang = 0;
255
256 // We overhang a ruby only if the neighboring render object is a text.
257 // We can overhang the ruby by no more than half the width of the neighboring text
258 // and no more than half the font size.
259 const RenderStyle& rubyTextStyle = firstLine ? rubyText->firstLineStyle() : rubyText->style();
260 float halfWidthOfFontSize = rubyTextStyle.computedFontPixelSize() / 2.;
261 if (startOverhang)
262 startOverhang = std::min(startOverhang, std::min(downcast<RenderText>(*startRenderer).minLogicalWidth(), halfWidthOfFontSize));
263 if (endOverhang)
264 endOverhang = std::min(endOverhang, std::min(downcast<RenderText>(*endRenderer).minLogicalWidth(), halfWidthOfFontSize));
265}
266
267void RenderRubyRun::updatePriorContextFromCachedBreakIterator(LazyLineBreakIterator& iterator) const
268{
269 iterator.setPriorContext(m_lastCharacter, m_secondToLastCharacter);
270}
271
272bool RenderRubyRun::canBreakBefore(const LazyLineBreakIterator& iterator) const
273{
274 RenderRubyText* rubyText = this->rubyText();
275 if (!rubyText)
276 return true;
277 return rubyText->canBreakBefore(iterator);
278}
279
280} // namespace WebCore
Note: See TracBrowser for help on using the repository browser.