/* * 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(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(y, 0); auto lineIndexCandidate = std::min(y / m_lineHeight, m_layout.lineCount() - 1); if (m_layout.hasLineStruts()) return adjustLineIndexForStruts(y, lineIndexCandidate); return lineIndexCandidate; } WTF::IteratorRange 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::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::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) { } } }