summaryrefslogtreecommitdiff
path: root/Source/WebCore/rendering/SimpleLineLayoutResolver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/rendering/SimpleLineLayoutResolver.cpp')
-rw-r--r--Source/WebCore/rendering/SimpleLineLayoutResolver.cpp293
1 files changed, 293 insertions, 0 deletions
diff --git a/Source/WebCore/rendering/SimpleLineLayoutResolver.cpp b/Source/WebCore/rendering/SimpleLineLayoutResolver.cpp
new file mode 100644
index 000000000..44bd762a2
--- /dev/null
+++ b/Source/WebCore/rendering/SimpleLineLayoutResolver.cpp
@@ -0,0 +1,293 @@
+/*
+ * 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() && m_iterator.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);
+}
+
+RunResolver::Iterator::Iterator(const RunResolver& resolver, unsigned runIndex, unsigned lineIndex)
+ : m_resolver(resolver)
+ , m_runIndex(runIndex)
+ , m_lineIndex(lineIndex)
+{
+}
+
+RunResolver::Iterator& RunResolver::Iterator::advance()
+{
+ if (simpleRun().isEndOfLine)
+ ++m_lineIndex;
+ ++m_runIndex;
+ return *this;
+}
+
+RunResolver::Iterator& RunResolver::Iterator::advanceLines(unsigned lineCount)
+{
+ unsigned runCount = m_resolver.m_layout.runCount();
+ if (runCount == m_resolver.m_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, 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;
+ // Jump over the first strut since we know that the line we are looking for is beyond the strut.
+ unsigned strutIndex = 1;
+ float topPosition = strut.lineBreak * m_lineHeight + strut.offset;
+ for (auto lineIndex = strut.lineBreak; lineIndex < m_layout.lineCount(); ++lineIndex) {
+ if (strutIndex < struts.size() && struts.at(strutIndex).lineBreak == lineIndex)
+ topPosition += struts.at(strutIndex++).offset;
+ if (y >= topPosition && y < (topPosition + m_lineHeight))
+ return lineIndex;
+ topPosition += m_lineHeight;
+ }
+ return m_layout.lineCount() - 1;
+}
+
+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.
+ if (type == IndexType::First)
+ y += m_lineHeight - (m_baseline + m_descent);
+ else
+ y -= m_baseline - m_ascent;
+ y = std::max<float>(y, 0);
+ auto lineIndexCandidate = std::min<unsigned>(y / m_lineHeight, m_layout.lineCount() - 1);
+ if (m_layout.hasLineStruts())
+ return adjustLineIndexForStruts(y, 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::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();
+ 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);
+ auto it = range.begin();
+ // Advance to the firt run with the start offset inside.
+ 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 (endOffset == startOffset && (*it).start() == endOffset)
+ return { rangeBegin, ++it };
+ // Advance beyond the last run with the end offset.
+ while (it != range.end() && (*it).start() < endOffset)
+ ++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 RenderBlockFlow& flow, const Layout& layout)
+ : m_runResolver(flow, layout)
+{
+}
+
+}
+}