blob: 7ac7890f3da1c6e4d1ed02aa49c559df29fea0a6 [file] [log] [blame]
/*
* Copyright (C) 2014 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "SimpleLineLayoutResolver.h"
#include "InlineTextBoxStyle.h"
#include "RenderBlockFlow.h"
#include "RenderObject.h"
#include "SimpleLineLayoutFunctions.h"
namespace WebCore {
namespace SimpleLineLayout {
static FloatPoint linePosition(float logicalLeft, float logicalTop)
{
return FloatPoint(logicalLeft, roundf(logicalTop));
}
static FloatSize lineSize(float logicalLeft, float logicalRight, float height)
{
return FloatSize(logicalRight - logicalLeft, height);
}
RunResolver::Run::Run(const Iterator& iterator)
: m_iterator(iterator)
{
}
String RunResolver::Run::textWithHyphen() const
{
auto& run = m_iterator.simpleRun();
ASSERT(run.hasHyphen);
// Empty runs should not have hyphen.
ASSERT(run.start < run.end);
auto& segment = m_iterator.resolver().m_flowContents.segmentForRun(run.start, run.end);
auto text = StringView(segment.text).substring(segment.toSegmentPosition(run.start), run.end - run.start);
return makeString(text, m_iterator.resolver().flow().style().hyphenString());
}
FloatRect RunResolver::Run::rect() const
{
auto& run = m_iterator.simpleRun();
auto& resolver = m_iterator.resolver();
float baseline = computeBaselinePosition();
FloatPoint position = linePosition(run.logicalLeft, baseline - resolver.m_ascent);
FloatSize size = lineSize(run.logicalLeft, run.logicalRight, resolver.m_ascent + resolver.m_descent + resolver.m_visualOverflowOffset);
bool moveLineBreakToBaseline = false;
if (run.start == run.end && m_iterator != resolver.begin() && resolver.m_inQuirksMode) {
auto previousRun = m_iterator;
--previousRun;
moveLineBreakToBaseline = !previousRun.simpleRun().isEndOfLine;
}
if (moveLineBreakToBaseline)
return FloatRect(FloatPoint(position.x(), baseline), FloatSize(size.width(), std::max<float>(0, resolver.m_ascent - resolver.m_baseline.toFloat())));
return FloatRect(position, size);
}
StringView RunResolver::Run::text() const
{
auto& run = m_iterator.simpleRun();
ASSERT(run.start < run.end);
auto& segment = m_iterator.resolver().m_flowContents.segmentForRun(run.start, run.end);
// We currently split runs on segment boundaries (different RenderObject).
ASSERT(run.end <= segment.end);
return StringView(segment.text).substring(segment.toSegmentPosition(run.start), run.end - run.start);
}
const RenderObject& RunResolver::Run::renderer() const
{
auto& run = m_iterator.simpleRun();
// FlowContents cannot differentiate empty runs.
ASSERT(run.start != run.end);
return m_iterator.resolver().m_flowContents.segmentForRun(run.start, run.end).renderer;
}
unsigned RunResolver::Run::localStart() const
{
auto& run = m_iterator.simpleRun();
// FlowContents cannot differentiate empty runs.
ASSERT(run.start != run.end);
return m_iterator.resolver().m_flowContents.segmentForRun(run.start, run.end).toSegmentPosition(run.start);
}
unsigned RunResolver::Run::localEnd() const
{
auto& run = m_iterator.simpleRun();
// FlowContents cannot differentiate empty runs.
ASSERT(run.start != run.end);
return m_iterator.resolver().m_flowContents.segmentForRun(run.start, run.end).toSegmentPosition(run.end);
}
RunResolver::Iterator::Iterator(const RunResolver& resolver, unsigned runIndex, unsigned lineIndex)
: m_layout(&resolver.m_layout)
, m_resolver(&resolver)
, m_runIndex(runIndex)
, m_lineIndex(lineIndex)
{
ASSERT(&resolver == &m_layout->runResolver());
}
RunResolver::Iterator& RunResolver::Iterator::advance()
{
if (simpleRun().isEndOfLine)
++m_lineIndex;
++m_runIndex;
return *this;
}
RunResolver::Iterator& RunResolver::Iterator::advanceLines(unsigned lineCount)
{
unsigned runCount = layout().runCount();
if (runCount == layout().lineCount()) {
m_runIndex = std::min(runCount, m_runIndex + lineCount);
m_lineIndex = m_runIndex;
return *this;
}
unsigned target = m_lineIndex + lineCount;
while (m_lineIndex < target && m_runIndex < runCount)
advance();
return *this;
}
RunResolver::RunResolver(const RenderBlockFlow& flow, const Layout& layout)
: m_flowRenderer(flow)
, m_layout(layout)
, m_flowContents(flow)
, m_lineHeight(lineHeightFromFlow(flow))
, m_baseline(baselineFromFlow(flow))
, m_borderAndPaddingBefore(flow.borderAndPaddingBefore())
, m_ascent(flow.style().fontCascade().fontMetrics().ascent())
, m_descent(flow.style().fontCascade().fontMetrics().descent())
, m_visualOverflowOffset(visualOverflowForDecorations(flow.style(), nullptr).bottom)
, m_inQuirksMode(flow.document().inQuirksMode())
{
}
unsigned RunResolver::adjustLineIndexForStruts(LayoutUnit y, IndexType type, unsigned lineIndexCandidate) const
{
auto& struts = m_layout.struts();
// We need to offset the lineIndex with line struts when there's an actual strut before the candidate.
auto& strut = struts.first();
if (strut.lineBreak >= lineIndexCandidate)
return lineIndexCandidate;
unsigned strutIndex = 0;
Optional<unsigned> lastIndexCandidate;
auto top = strut.lineBreak * m_lineHeight;
auto lineHeightWithOverflow = m_lineHeight;
// If font is larger than the line height (glyphs overflow), use the font size when checking line boundaries.
if (m_ascent + m_descent > m_lineHeight) {
lineHeightWithOverflow = m_ascent + m_descent;
top += m_baseline - m_ascent;
}
auto bottom = top + lineHeightWithOverflow;
for (auto lineIndex = strut.lineBreak; lineIndex < m_layout.lineCount(); ++lineIndex) {
float strutOffset = 0;
if (strutIndex < struts.size() && struts.at(strutIndex).lineBreak == lineIndex)
strutOffset = struts.at(strutIndex++).offset;
bottom = top + strutOffset + lineHeightWithOverflow;
if (y >= top && y < bottom) {
if (type == IndexType::First)
return lineIndex;
lastIndexCandidate = lineIndex;
} else if (lastIndexCandidate)
return *lastIndexCandidate;
top += m_lineHeight + strutOffset;
}
if (lastIndexCandidate || y >= bottom)
return m_layout.lineCount() - 1;
// We missed the line.
ASSERT_NOT_REACHED();
return lineIndexCandidate;
}
unsigned RunResolver::lineIndexForHeight(LayoutUnit height, IndexType type) const
{
ASSERT(m_lineHeight);
float y = height - m_borderAndPaddingBefore;
// Lines may overlap, adjust to get the first or last line at this height.
auto adjustedY = y;
if (type == IndexType::First)
adjustedY += m_lineHeight - (m_baseline + m_descent);
else
adjustedY -= m_baseline - m_ascent;
adjustedY = std::max<float>(adjustedY, 0);
auto lineIndexCandidate = std::min<unsigned>(adjustedY / m_lineHeight, m_layout.lineCount() - 1);
if (m_layout.hasLineStruts())
return adjustLineIndexForStruts(LayoutUnit(y), type, lineIndexCandidate);
return lineIndexCandidate;
}
WTF::IteratorRange<RunResolver::Iterator> RunResolver::rangeForRect(const LayoutRect& rect) const
{
if (!m_lineHeight)
return { begin(), end() };
unsigned firstLine = lineIndexForHeight(rect.y(), IndexType::First);
unsigned lastLine = std::max(firstLine, lineIndexForHeight(rect.maxY(), IndexType::Last));
auto rangeBegin = begin().advanceLines(firstLine);
if (rangeBegin == end())
return { end(), end() };
auto rangeEnd = rangeBegin;
ASSERT(lastLine >= firstLine);
rangeEnd.advanceLines(lastLine - firstLine + 1);
return { rangeBegin, rangeEnd };
}
WTF::IteratorRange<RunResolver::Iterator> RunResolver::rangeForLine(unsigned lineIndex) const
{
auto rangeBegin = begin().advanceLines(lineIndex);
if (rangeBegin == end())
return { end(), end() };
auto rangeEnd = rangeBegin;
rangeEnd.advanceLines(1);
return { rangeBegin, rangeEnd };
}
WTF::IteratorRange<RunResolver::Iterator> RunResolver::rangeForRenderer(const RenderObject& renderer) const
{
if (begin() == end())
return { end(), end() };
FlowContents::Iterator segment = m_flowContents.begin();
auto run = begin();
ASSERT(segment->start <= (*run).start());
// Move run to the beginning of the segment.
while (&segment->renderer != &renderer && run != end()) {
if ((*run).start() == segment->start && (*run).end() == segment->end) {
++run;
++segment;
} else if ((*run).start() < segment->end)
++run;
else
++segment;
ASSERT(segment != m_flowContents.end());
}
// Do we actually have a run for this renderer?
// Collapsed whitespace with dedicated renderer could end up with no run at all.
if (run == end() || (segment->start != segment->end && segment->end <= (*run).start()))
return { end(), end() };
auto rangeBegin = run;
// Move beyond the end of the segment.
while (run != end() && (*run).start() < segment->end)
++run;
// Special case when segment == run.
if (run == rangeBegin)
++run;
return { rangeBegin, run };
}
RunResolver::Iterator RunResolver::runForPoint(const LayoutPoint& point) const
{
if (!m_lineHeight)
return end();
if (begin() == end())
return end();
unsigned lineIndex = lineIndexForHeight(point.y(), IndexType::Last);
auto x = point.x() - m_borderAndPaddingBefore;
auto it = begin();
it.advanceLines(lineIndex);
// Point is at the left side of the first run on this line.
if ((*it).logicalLeft() > x)
return it;
// Advance to the first candidate run on this line.
while (it != end() && (*it).logicalRight() < x && lineIndex == it.lineIndex())
++it;
// We jumped to the next line so the point is at the right side of the previous line.
if (it.lineIndex() > lineIndex)
return --it;
// Now we have a candidate run.
// Find the last run that still contains this point (taking overlapping runs with odd word spacing values into account).
while (it != end() && (*it).logicalLeft() <= x && lineIndex == it.lineIndex())
++it;
return --it;
}
WTF::IteratorRange<RunResolver::Iterator> RunResolver::rangeForRendererWithOffsets(const RenderObject& renderer, unsigned startOffset, unsigned endOffset) const
{
ASSERT(startOffset <= endOffset);
auto range = rangeForRenderer(renderer);
if (range.begin() == range.end())
return { end(), end() };
auto it = range.begin();
auto localEnd = (*it).start() + endOffset;
// Advance to the first run with the start offset inside. Only the first node in a range can have a startOffset.
while (it != range.end() && (*it).end() <= startOffset)
++it;
if (it == range.end())
return { end(), end() };
auto rangeBegin = it;
// Special case empty ranges that start at the edge of the run. Apparently normal line layout include those.
if (localEnd == startOffset && (*it).start() == localEnd)
return { rangeBegin, ++it };
// Advance beyond the last run with the end offset.
while (it != range.end() && (*it).start() < localEnd)
++it;
return { rangeBegin, it };
}
LineResolver::Iterator::Iterator(RunResolver::Iterator runIterator)
: m_runIterator(runIterator)
{
}
FloatRect LineResolver::Iterator::operator*() const
{
unsigned currentLine = m_runIterator.lineIndex();
auto it = m_runIterator;
FloatRect rect = (*it).rect();
while (it.advance().lineIndex() == currentLine)
rect.unite((*it).rect());
return rect;
}
const RenderObject& LineResolver::Iterator::renderer() const
{
// FIXME: This works as long as we've got only one renderer per line.
auto run = *m_runIterator;
return m_runIterator.resolver().flowContents().segmentForRun(run.start(), run.end()).renderer;
}
LineResolver::LineResolver(const RunResolver& runResolver)
: m_runResolver(runResolver)
{
}
}
}