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 setReplaced(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
88RenderBlock* RenderRubyRun::firstLineBlock() const
89{
90 return nullptr;
91}
92
93bool RenderRubyRun::isChildAllowed(const RenderObject& child, const RenderStyle&) const
94{
95 return child.isInline() || child.isRubyText();
96}
97
98RenderPtr<RenderRubyBase> RenderRubyRun::createRubyBase() const
99{
100 auto newStyle = RenderStyle::createAnonymousStyleWithDisplay(style(), DisplayType::Block);
101 newStyle.setTextAlign(TextAlignMode::Center); // FIXME: use TextAlignMode::WebKitCenter?
102 auto renderer = createRenderer<RenderRubyBase>(document(), WTFMove(newStyle));
103 renderer->initializeStyle();
104 return renderer;
105}
106
107RenderPtr<RenderRubyRun> RenderRubyRun::staticCreateRubyRun(const RenderObject* parentRuby)
108{
109 ASSERT(isRuby(parentRuby));
110 auto renderer = createRenderer<RenderRubyRun>(parentRuby->document(), RenderStyle::createAnonymousStyleWithDisplay(parentRuby->style(), DisplayType::InlineBlock));
111 renderer->initializeStyle();
112 return renderer;
113}
114
115void RenderRubyRun::layoutExcludedChildren(bool relayoutChildren)
116{
117 RenderBlockFlow::layoutExcludedChildren(relayoutChildren);
118
119 StackStats::LayoutCheckPoint layoutCheckPoint;
120 // Don't bother positioning the RenderRubyRun yet.
121 RenderRubyText* rt = rubyText();
122 if (!rt)
123 return;
124 rt->setIsExcludedFromNormalLayout(true);
125 if (relayoutChildren)
126 rt->setChildNeedsLayout(MarkOnlyThis);
127 rt->layoutIfNeeded();
128}
129
130void RenderRubyRun::layout()
131{
132 if (RenderRubyBase* base = rubyBase())
133 base->reset();
134 RenderBlockFlow::layout();
135}
136
137void RenderRubyRun::layoutBlock(bool relayoutChildren, LayoutUnit pageHeight)
138{
139 if (!relayoutChildren) {
140 // Since the extra relayout in RenderBlockFlow::updateRubyForJustifiedText() causes the size of the RenderRubyText/RenderRubyBase
141 // dependent on the line's current expansion, whenever we relayout the RenderRubyRun, we need to relayout the RenderRubyBase/RenderRubyText as well.
142 // FIXME: We should take the expansion opportunities into account if possible.
143 relayoutChildren = style().textAlign() == TextAlignMode::Justify;
144 }
145
146 RenderBlockFlow::layoutBlock(relayoutChildren, pageHeight);
147
148 RenderRubyText* rt = rubyText();
149 if (!rt)
150 return;
151
152 rt->setLogicalLeft(0);
153
154 // Place the RenderRubyText such that its bottom is flush with the lineTop of the first line of the RenderRubyBase.
155 LayoutUnit lastLineRubyTextBottom = rt->logicalHeight();
156 LayoutUnit firstLineRubyTextTop;
157 RootInlineBox* rootBox = rt->lastRootBox();
158 if (rootBox) {
159 // In order to align, we have to ignore negative leading.
160 firstLineRubyTextTop = rt->firstRootBox()->logicalTopLayoutOverflow();
161 lastLineRubyTextBottom = rootBox->logicalBottomLayoutOverflow();
162 }
163
164 if (isHorizontalWritingMode() && rt->style().rubyPosition() == RubyPosition::InterCharacter) {
165 // Bopomofo. We need to move the RenderRubyText over to the right side and center it
166 // vertically relative to the base.
167 const FontCascade& font = style().fontCascade();
168 float distanceBetweenBase = std::max(font.letterSpacing(), 2.0f * rt->style().fontCascade().fontMetrics().height());
169 setWidth(width() + distanceBetweenBase - font.letterSpacing());
170 if (RenderRubyBase* rb = rubyBase()) {
171 LayoutUnit firstLineTop;
172 LayoutUnit lastLineBottom = logicalHeight();
173 RootInlineBox* rootBox = rb->firstRootBox();
174 if (rootBox)
175 firstLineTop = rootBox->logicalTopLayoutOverflow();
176 firstLineTop += rb->logicalTop();
177 if (rootBox)
178 lastLineBottom = rootBox->logicalBottomLayoutOverflow();
179 lastLineBottom += rb->logicalTop();
180 rt->setX(rb->x() + rb->width() - font.letterSpacing());
181 LayoutUnit extent = lastLineBottom - firstLineTop;
182 rt->setY(firstLineTop + (extent - rt->height()) / 2);
183 }
184 } else if (style().isFlippedLinesWritingMode() == (style().rubyPosition() == RubyPosition::After)) {
185 LayoutUnit firstLineTop;
186 if (RenderRubyBase* rb = rubyBase()) {
187 RootInlineBox* rootBox = rb->firstRootBox();
188 if (rootBox)
189 firstLineTop = rootBox->logicalTopLayoutOverflow();
190 firstLineTop += rb->logicalTop();
191 }
192
193 rt->setLogicalTop(-lastLineRubyTextBottom + firstLineTop);
194 } else {
195 LayoutUnit lastLineBottom = logicalHeight();
196 if (RenderRubyBase* rb = rubyBase()) {
197 RootInlineBox* rootBox = rb->lastRootBox();
198 if (rootBox)
199 lastLineBottom = rootBox->logicalBottomLayoutOverflow();
200 lastLineBottom += rb->logicalTop();
201 }
202
203 rt->setLogicalTop(-firstLineRubyTextTop + lastLineBottom);
204 }
205
206 // Update our overflow to account for the new RenderRubyText position.
207 computeOverflow(clientLogicalBottom());
208}
209
210static bool shouldOverhang(bool firstLine, const RenderObject* renderer, const RenderRubyBase& rubyBase)
211{
212 if (!renderer || !renderer->isText())
213 return false;
214 const RenderStyle& rubyBaseStyle = firstLine ? rubyBase.firstLineStyle() : rubyBase.style();
215 const RenderStyle& style = firstLine ? renderer->firstLineStyle() : renderer->style();
216 return style.computedFontPixelSize() <= rubyBaseStyle.computedFontPixelSize();
217}
218
219void RenderRubyRun::getOverhang(bool firstLine, RenderObject* startRenderer, RenderObject* endRenderer, float& startOverhang, float& endOverhang) const
220{
221 ASSERT(!needsLayout());
222
223 startOverhang = 0;
224 endOverhang = 0;
225
226 RenderRubyBase* rubyBase = this->rubyBase();
227 RenderRubyText* rubyText = this->rubyText();
228
229 if (!rubyBase || !rubyText)
230 return;
231
232 if (!rubyBase->firstRootBox())
233 return;
234
235 LayoutUnit logicalWidth = this->logicalWidth();
236 float logicalLeftOverhang = std::numeric_limits<float>::max();
237 float logicalRightOverhang = std::numeric_limits<float>::max();
238 for (RootInlineBox* rootInlineBox = rubyBase->firstRootBox(); rootInlineBox; rootInlineBox = rootInlineBox->nextRootBox()) {
239 logicalLeftOverhang = std::min<float>(logicalLeftOverhang, rootInlineBox->logicalLeft());
240 logicalRightOverhang = std::min<float>(logicalRightOverhang, logicalWidth - rootInlineBox->logicalRight());
241 }
242
243 startOverhang = style().isLeftToRightDirection() ? logicalLeftOverhang : logicalRightOverhang;
244 endOverhang = style().isLeftToRightDirection() ? logicalRightOverhang : logicalLeftOverhang;
245
246 if (!shouldOverhang(firstLine, startRenderer, *rubyBase))
247 startOverhang = 0;
248 if (!shouldOverhang(firstLine, endRenderer, *rubyBase))
249 endOverhang = 0;
250
251 // We overhang a ruby only if the neighboring render object is a text.
252 // We can overhang the ruby by no more than half the width of the neighboring text
253 // and no more than half the font size.
254 const RenderStyle& rubyTextStyle = firstLine ? rubyText->firstLineStyle() : rubyText->style();
255 float halfWidthOfFontSize = rubyTextStyle.computedFontPixelSize() / 2.;
256 if (startOverhang)
257 startOverhang = std::min(startOverhang, std::min(downcast<RenderText>(*startRenderer).minLogicalWidth(), halfWidthOfFontSize));
258 if (endOverhang)
259 endOverhang = std::min(endOverhang, std::min(downcast<RenderText>(*endRenderer).minLogicalWidth(), halfWidthOfFontSize));
260}
261
262void RenderRubyRun::updatePriorContextFromCachedBreakIterator(LazyLineBreakIterator& iterator) const
263{
264 iterator.setPriorContext(m_lastCharacter, m_secondToLastCharacter);
265}
266
267bool RenderRubyRun::canBreakBefore(const LazyLineBreakIterator& iterator) const
268{
269 RenderRubyText* rubyText = this->rubyText();
270 if (!rubyText)
271 return true;
272 return rubyText->canBreakBefore(iterator);
273}
274
275} // namespace WebCore
276