diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/rendering/InlineTextBox.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/rendering/InlineTextBox.cpp')
-rw-r--r-- | Source/WebCore/rendering/InlineTextBox.cpp | 1125 |
1 files changed, 293 insertions, 832 deletions
diff --git a/Source/WebCore/rendering/InlineTextBox.cpp b/Source/WebCore/rendering/InlineTextBox.cpp index 7b8efc2af..f6f248af3 100644 --- a/Source/WebCore/rendering/InlineTextBox.cpp +++ b/Source/WebCore/rendering/InlineTextBox.cpp @@ -1,7 +1,7 @@ /* * (C) 1999 Lars Knoll (knoll@kde.org) * (C) 2000 Dirk Mueller (mueller@kde.org) - * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. + * Copyright (C) 2004-2014 Apple Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -23,18 +23,17 @@ #include "config.h" #include "InlineTextBox.h" -#include "Chrome.h" -#include "ChromeClient.h" +#include "BreakLines.h" #include "DashArray.h" #include "Document.h" #include "DocumentMarkerController.h" #include "Editor.h" #include "EllipsisBox.h" -#include "FontCache.h" #include "Frame.h" #include "GraphicsContext.h" #include "HitTestResult.h" #include "ImageBuffer.h" +#include "InlineTextBoxStyle.h" #include "Page.h" #include "PaintInfo.h" #include "RenderedDocumentMarker.h" @@ -45,12 +44,11 @@ #include "RenderRubyText.h" #include "RenderTheme.h" #include "RenderView.h" -#include "Settings.h" -#include "SVGTextRunRenderingContext.h" #include "Text.h" +#include "TextDecorationPainter.h" #include "TextPaintStyle.h" #include "TextPainter.h" -#include "break_lines.h" +#include <stdio.h> #include <wtf/text/CString.h> namespace WebCore { @@ -66,71 +64,6 @@ COMPILE_ASSERT(sizeof(InlineTextBox) == sizeof(SameSizeAsInlineTextBox), InlineT typedef WTF::HashMap<const InlineTextBox*, LayoutRect> InlineTextBoxOverflowMap; static InlineTextBoxOverflowMap* gTextBoxesWithOverflow; -#if ENABLE(CSS3_TEXT_DECORATION_SKIP_INK) -static bool compareTuples(std::pair<float, float> l, std::pair<float, float> r) -{ - return l.first < r.first; -} - -static DashArray translateIntersectionPointsToSkipInkBoundaries(const DashArray& intersections, float dilationAmount, float totalWidth) -{ - ASSERT(!(intersections.size() % 2)); - - // Step 1: Make pairs so we can sort based on range starting-point. We dilate the ranges in this step as well. - Vector<std::pair<float, float>> tuples; - for (auto i = intersections.begin(); i != intersections.end(); i++, i++) - tuples.append(std::make_pair(*i - dilationAmount, *(i + 1) + dilationAmount)); - std::sort(tuples.begin(), tuples.end(), &compareTuples); - - // Step 2: Deal with intersecting ranges. - Vector<std::pair<float, float>> intermediateTuples; - if (tuples.size() >= 2) { - intermediateTuples.append(*tuples.begin()); - for (auto i = tuples.begin() + 1; i != tuples.end(); i++) { - float& firstEnd = intermediateTuples.last().second; - float secondStart = i->first; - float secondEnd = i->second; - if (secondStart <= firstEnd && secondEnd <= firstEnd) { - // Ignore this range completely - } else if (secondStart <= firstEnd) - firstEnd = secondEnd; - else - intermediateTuples.append(*i); - } - } else - intermediateTuples = tuples; - - // Step 3: Output the space between the ranges, but only if the space warrants an underline. - float previous = 0; - DashArray result; - for (auto i = intermediateTuples.begin(); i != intermediateTuples.end(); i++) { - if (i->first - previous > dilationAmount) { - result.append(previous); - result.append(i->first); - } - previous = i->second; - } - if (totalWidth - previous > dilationAmount) { - result.append(previous); - result.append(totalWidth); - } - - return result; -} - -static void drawSkipInkUnderline(TextPainter& textPainter, GraphicsContext& context, FloatPoint localOrigin, float underlineOffset, float width, bool isPrinting) -{ - FloatPoint adjustedLocalOrigin = localOrigin; - adjustedLocalOrigin.move(0, underlineOffset); - FloatRect underlineBoundingBox = context.computeLineBoundsForText(adjustedLocalOrigin, width, isPrinting); - DashArray intersections = textPainter.dashesForIntersectionsWithRect(underlineBoundingBox); - DashArray a = translateIntersectionPointsToSkipInkBoundaries(intersections, underlineBoundingBox.height(), width); - - ASSERT(!(a.size() % 2)); - context.drawLinesForText(adjustedLocalOrigin, a, isPrinting); -} -#endif - InlineTextBox::~InlineTextBox() { if (!knownToHaveNoOverflow() && gTextBoxesWithOverflow) @@ -167,7 +100,7 @@ int InlineTextBox::baselinePosition(FontBaseline baselineType) const return 0; if (&parent()->renderer() == renderer().parent()) return parent()->baselinePosition(baselineType); - return toRenderBoxModelObject(renderer().parent())->baselinePosition(baselineType, isFirstLine(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine); + return downcast<RenderBoxModelObject>(*renderer().parent()).baselinePosition(baselineType, isFirstLine(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine); } LayoutUnit InlineTextBox::lineHeight() const @@ -176,7 +109,7 @@ LayoutUnit InlineTextBox::lineHeight() const return 0; if (&parent()->renderer() == renderer().parent()) return parent()->lineHeight(); - return toRenderBoxModelObject(renderer().parent())->lineHeight(isFirstLine(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine); + return downcast<RenderBoxModelObject>(*renderer().parent()).lineHeight(isFirstLine(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine); } LayoutUnit InlineTextBox::selectionTop() const @@ -194,21 +127,26 @@ LayoutUnit InlineTextBox::selectionHeight() const return root().selectionHeight(); } -bool InlineTextBox::isSelected(int startPos, int endPos) const +bool InlineTextBox::isSelected(unsigned startPos, unsigned endPos) const { - LayoutUnit sPos = std::max<LayoutUnit>(startPos - m_start, 0); - LayoutUnit ePos = std::min<LayoutUnit>(endPos - m_start, m_len); - return (sPos < ePos); + int sPos = clampedOffset(startPos); + int ePos = clampedOffset(endPos); + // FIXME: https://bugs.webkit.org/show_bug.cgi?id=160786 + // We should only be checking if sPos >= ePos here, because those are the + // indices used to actually generate the selection rect. Allowing us past this guard + // on any other condition creates zero-width selection rects. + return sPos < ePos || (startPos == endPos && startPos >= start() && startPos <= (start() + len())); } RenderObject::SelectionState InlineTextBox::selectionState() { RenderObject::SelectionState state = renderer().selectionState(); if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) { - int startPos, endPos; + unsigned startPos, endPos; renderer().selectionStartEnd(startPos, endPos); // The position after a hard line break is considered to be past its end. - int lastSelectable = start() + len() - (isLineBreak() ? 1 : 0); + ASSERT(start() + len() >= (isLineBreak() ? 1 : 0)); + unsigned lastSelectable = start() + len() - (isLineBreak() ? 1 : 0); bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len); bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= lastSelectable); @@ -229,13 +167,14 @@ RenderObject::SelectionState InlineTextBox::selectionState() if (m_truncation != cNoTruncation && root().ellipsisBox()) { EllipsisBox* ellipsis = root().ellipsisBox(); if (state != RenderObject::SelectionNone) { - int start, end; - selectionStartEnd(start, end); + unsigned selectionStart; + unsigned selectionEnd; + std::tie(selectionStart, selectionEnd) = selectionStartEnd(); // The ellipsis should be considered to be selected if the end of // the selection is past the beginning of the truncation and the // beginning of the selection is before or at the beginning of the // truncation. - ellipsis->setSelectionState(end >= m_truncation && start <= m_truncation ? + ellipsis->setSelectionState(selectionEnd >= m_truncation && selectionStart <= m_truncation ? RenderObject::SelectionInside : RenderObject::SelectionNone); } else ellipsis->setSelectionState(RenderObject::SelectionNone); @@ -244,63 +183,53 @@ RenderObject::SelectionState InlineTextBox::selectionState() return state; } -static void adjustCharactersAndLengthForHyphen(BufferForAppendingHyphen& charactersWithHyphen, const RenderStyle& style, String& string, int& length) +static const FontCascade& fontToUse(const RenderStyle& style, const RenderText& renderer) { - const AtomicString& hyphenString = style.hyphenString(); - charactersWithHyphen.reserveCapacity(length + hyphenString.length()); - charactersWithHyphen.append(string); - charactersWithHyphen.append(hyphenString); - string = charactersWithHyphen.toString(); - length += hyphenString.length(); -} - -static const Font& fontToUse(const RenderStyle& style, const RenderText& renderer) -{ - if (style.hasTextCombine() && renderer.isCombineText()) { - const RenderCombineText& textCombineRenderer = toRenderCombineText(renderer); + if (style.hasTextCombine() && is<RenderCombineText>(renderer)) { + const auto& textCombineRenderer = downcast<RenderCombineText>(renderer); if (textCombineRenderer.isCombined()) return textCombineRenderer.textCombineFont(); } - return style.font(); + return style.fontCascade(); } -LayoutRect InlineTextBox::localSelectionRect(int startPos, int endPos) const +LayoutRect InlineTextBox::localSelectionRect(unsigned startPos, unsigned endPos) const { - int sPos = std::max(startPos - m_start, 0); - int ePos = std::min(endPos - m_start, (int)m_len); - - if (sPos > ePos) + unsigned sPos = clampedOffset(startPos); + unsigned ePos = clampedOffset(endPos); + + // FIXME: https://bugs.webkit.org/show_bug.cgi?id=160786 + // We should only be checking if sPos >= ePos here, because those are the + // indices used to actually generate the selection rect. Allowing us past this guard + // on any other condition creates zero-width selection rects. + if (sPos >= ePos && !(startPos == endPos && startPos >= start() && startPos <= (start() + len()))) return LayoutRect(); - FontCachePurgePreventer fontCachePurgePreventer; - - LayoutUnit selTop = selectionTop(); - LayoutUnit selHeight = selectionHeight(); + LayoutUnit selectionTop = this->selectionTop(); + LayoutUnit selectionHeight = this->selectionHeight(); const RenderStyle& lineStyle = this->lineStyle(); - const Font& font = fontToUse(lineStyle, renderer()); + const FontCascade& font = fontToUse(lineStyle, renderer()); - BufferForAppendingHyphen charactersWithHyphen; + String hyphenatedString; bool respectHyphen = ePos == m_len && hasHyphen(); - TextRun textRun = constructTextRun(lineStyle, font, respectHyphen ? &charactersWithHyphen : 0); if (respectHyphen) - endPos = textRun.length(); - - FloatPoint startingPoint = FloatPoint(logicalLeft(), selTop); - LayoutRect r; - if (sPos || ePos != static_cast<int>(m_len)) - r = enclosingIntRect(font.selectionRectForText(textRun, startingPoint, selHeight, sPos, ePos)); - else // Avoid computing the font width when the entire line box is selected as an optimization. - r = enclosingIntRect(FloatRect(startingPoint, FloatSize(m_logicalWidth, selHeight))); - - LayoutUnit logicalWidth = r.width(); - if (r.x() > logicalRight()) + hyphenatedString = hyphenatedStringForTextRun(lineStyle); + TextRun textRun = constructTextRun(lineStyle, hyphenatedString); + + LayoutRect selectionRect = LayoutRect(LayoutPoint(logicalLeft(), selectionTop), LayoutSize(m_logicalWidth, selectionHeight)); + // Avoid computing the font width when the entire line box is selected as an optimization. + if (sPos || ePos != m_len) + font.adjustSelectionRectForText(textRun, selectionRect, sPos, ePos); + IntRect snappedSelectionRect = enclosingIntRect(selectionRect); + LayoutUnit logicalWidth = snappedSelectionRect.width(); + if (snappedSelectionRect.x() > logicalRight()) logicalWidth = 0; - else if (r.maxX() > logicalRight()) - logicalWidth = logicalRight() - r.x(); + else if (snappedSelectionRect.maxX() > logicalRight()) + logicalWidth = logicalRight() - snappedSelectionRect.x(); - LayoutPoint topPoint = isHorizontal() ? LayoutPoint(r.x(), selTop) : LayoutPoint(selTop, r.x()); - LayoutUnit width = isHorizontal() ? logicalWidth : selHeight; - LayoutUnit height = isHorizontal() ? selHeight : logicalWidth; + LayoutPoint topPoint = isHorizontal() ? LayoutPoint(snappedSelectionRect.x(), selectionTop) : LayoutPoint(selectionTop, snappedSelectionRect.x()); + LayoutUnit width = isHorizontal() ? logicalWidth : selectionHeight; + LayoutUnit height = isHorizontal() ? selectionHeight : logicalWidth; return LayoutRect(topPoint, LayoutSize(width, height)); } @@ -402,7 +331,8 @@ bool InlineTextBox::isLineBreak() const return renderer().style().preserveNewline() && len() == 1 && (*renderer().text())[start()] == '\n'; } -bool InlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/) +bool InlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/, + HitTestAction /*hitTestAction*/) { if (!visibleToHitTesting()) return false; @@ -434,33 +364,6 @@ bool InlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& re return false; } -FloatSize InlineTextBox::applyShadowToGraphicsContext(GraphicsContext* context, const ShadowData* shadow, const FloatRect& textRect, bool stroked, bool opaque, bool horizontal) -{ - if (!shadow) - return FloatSize(); - - FloatSize extraOffset; - int shadowX = horizontal ? shadow->x() : shadow->y(); - int shadowY = horizontal ? shadow->y() : -shadow->x(); - FloatSize shadowOffset(shadowX, shadowY); - int shadowRadius = shadow->radius(); - const Color& shadowColor = shadow->color(); - - if (shadow->next() || stroked || !opaque) { - FloatRect shadowRect(textRect); - shadowRect.inflate(shadow->paintingExtent()); - shadowRect.move(shadowOffset); - context->save(); - context->clip(shadowRect); - - extraOffset = FloatSize(0, 2 * textRect.height() + std::max(0.0f, shadowOffset.height()) + shadowRadius); - shadowOffset -= extraOffset; - } - - context->setShadow(shadowOffset, shadowRadius, shadowColor, context->fillColorSpace()); - return extraOffset; -} - static inline bool emphasisPositionHasNeitherLeftNorRight(TextEmphasisPosition emphasisPosition) { return !(emphasisPosition & TextEmphasisPositionLeft) && !(emphasisPosition & TextEmphasisPositionRight); @@ -491,10 +394,10 @@ bool InlineTextBox::emphasisMarkExistsAndIsAbove(const RenderStyle& style, bool& if (!containingBlock->isRubyBase()) return true; // This text is not inside a ruby base, so it does not have ruby text over it. - if (!containingBlock->parent()->isRubyRun()) + if (!is<RenderRubyRun>(*containingBlock->parent())) return true; // Cannot get the ruby text. - RenderRubyText* rubyText = toRenderRubyRun(containingBlock->parent())->rubyText(); + RenderRubyText* rubyText = downcast<RenderRubyRun>(*containingBlock->parent()).rubyText(); // The emphasis marks over are suppressed only if there is a ruby text box and it not empty. return !rubyText || !rubyText->hasLines(); @@ -516,7 +419,7 @@ void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit paintEnd = isHorizontal() ? paintInfo.rect.maxX() : paintInfo.rect.maxY(); LayoutUnit paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y(); - LayoutPoint adjustedPaintOffset = roundedIntPoint(paintOffset); + FloatPoint localPaintOffset(paintOffset); if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart) return; @@ -542,59 +445,45 @@ void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit widthOfVisibleText = renderer().width(m_start, m_truncation, textPos(), isFirstLine()); LayoutUnit widthOfHiddenText = m_logicalWidth - widthOfVisibleText; LayoutSize truncationOffset(isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText, 0); - adjustedPaintOffset.move(isHorizontal() ? truncationOffset : truncationOffset.transposedSize()); + localPaintOffset.move(isHorizontal() ? truncationOffset : truncationOffset.transposedSize()); } } - GraphicsContext* context = paintInfo.context; + GraphicsContext& context = paintInfo.context(); const RenderStyle& lineStyle = this->lineStyle(); - adjustedPaintOffset.move(0, lineStyle.isHorizontalWritingMode() ? 0 : -logicalHeight()); + localPaintOffset.move(0, lineStyle.isHorizontalWritingMode() ? 0 : -logicalHeight()); FloatPoint boxOrigin = locationIncludingFlipping(); - boxOrigin.move(adjustedPaintOffset.x(), adjustedPaintOffset.y()); + boxOrigin.moveBy(localPaintOffset); FloatRect boxRect(boxOrigin, FloatSize(logicalWidth(), logicalHeight())); - RenderCombineText* combinedText = lineStyle.hasTextCombine() && renderer().isCombineText() && toRenderCombineText(renderer()).isCombined() ? &toRenderCombineText(renderer()) : 0; + RenderCombineText* combinedText = lineStyle.hasTextCombine() && is<RenderCombineText>(renderer()) && downcast<RenderCombineText>(renderer()).isCombined() ? &downcast<RenderCombineText>(renderer()) : nullptr; bool shouldRotate = !isHorizontal() && !combinedText; if (shouldRotate) - context->concatCTM(rotation(boxRect, Clockwise)); + context.concatCTM(rotation(boxRect, Clockwise)); // Determine whether or not we have composition underlines to draw. bool containsComposition = renderer().textNode() && renderer().frame().editor().compositionNode() == renderer().textNode(); bool useCustomUnderlines = containsComposition && renderer().frame().editor().compositionUsesCustomUnderlines(); // Determine the text colors and selection colors. - TextPaintStyle textPaintStyle = computeTextPaintStyle(renderer(), lineStyle, paintInfo); - - bool paintSelectedTextOnly; - bool paintSelectedTextSeparately; - const ShadowData* selectionShadow; - TextPaintStyle selectionPaintStyle = computeTextSelectionPaintStyle(textPaintStyle, renderer(), lineStyle, paintInfo, paintSelectedTextOnly, paintSelectedTextSeparately, selectionShadow); + TextPaintStyle textPaintStyle = computeTextPaintStyle(renderer().frame(), lineStyle, paintInfo); + bool paintSelectedTextOnly = false; + bool paintSelectedTextSeparately = false; + const ShadowData* selectionShadow = nullptr; + // Text with custom underlines does not have selection background painted, so selection paint style is not appropriate for it. - if (useCustomUnderlines) - selectionPaintStyle = textPaintStyle; + TextPaintStyle selectionPaintStyle = haveSelection && !useCustomUnderlines ? computeTextSelectionPaintStyle(textPaintStyle, renderer(), lineStyle, paintInfo, paintSelectedTextOnly, paintSelectedTextSeparately, selectionShadow) : textPaintStyle; // Set our font. - const Font& font = fontToUse(lineStyle, renderer()); - - FloatPoint textOrigin = FloatPoint(boxOrigin.x(), boxOrigin.y() + font.fontMetrics().ascent()); - - if (combinedText) - combinedText->adjustTextOrigin(textOrigin, boxRect); - + const FontCascade& font = fontToUse(lineStyle, renderer()); // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection // and composition underlines. if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) { -#if PLATFORM(MAC) - // Custom highlighters go behind everything else. - if (lineStyle.highlight() != nullAtom && !context->paintingDisabled()) - paintCustomHighlight(adjustedPaintOffset, lineStyle.highlight()); -#endif - if (containsComposition && !useCustomUnderlines) paintCompositionBackground(context, boxOrigin, lineStyle, font, renderer().frame().editor().compositionStart(), @@ -606,69 +495,68 @@ void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, paintSelection(context, boxOrigin, lineStyle, font, selectionPaintStyle.fillColor); } - if (Page* page = renderer().frame().page()) { - // FIXME: Right now, InlineTextBoxes never call addRelevantUnpaintedObject() even though they might - // legitimately be unpainted if they are waiting on a slow-loading web font. We should fix that, and - // when we do, we will have to account for the fact the InlineTextBoxes do not always have unique - // renderers and Page currently relies on each unpainted object having a unique renderer. - if (paintInfo.phase == PaintPhaseForeground) - page->addRelevantRepaintedObject(&renderer(), IntRect(boxOrigin.x(), boxOrigin.y(), logicalWidth(), logicalHeight())); - } + // FIXME: Right now, InlineTextBoxes never call addRelevantUnpaintedObject() even though they might + // legitimately be unpainted if they are waiting on a slow-loading web font. We should fix that, and + // when we do, we will have to account for the fact the InlineTextBoxes do not always have unique + // renderers and Page currently relies on each unpainted object having a unique renderer. + if (paintInfo.phase == PaintPhaseForeground) + renderer().page().addRelevantRepaintedObject(&renderer(), IntRect(boxOrigin.x(), boxOrigin.y(), logicalWidth(), logicalHeight())); // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only). - int length = m_len; - int maximumLength; - String string; - if (!combinedText) { - string = renderer().text(); - if (static_cast<unsigned>(length) != string.length() || m_start) { - ASSERT_WITH_SECURITY_IMPLICATION(static_cast<unsigned>(m_start + length) <= string.length()); - string = string.substringSharingImpl(m_start, length); - } - maximumLength = renderer().textLength() - m_start; - } else { - combinedText->getStringToRender(m_start, string, length); - maximumLength = length; - } + String alternateStringToRender; + if (combinedText) + alternateStringToRender = combinedText->combinedStringForRendering(); + else if (hasHyphen()) + alternateStringToRender = hyphenatedStringForTextRun(lineStyle); - BufferForAppendingHyphen charactersWithHyphen; - TextRun textRun = constructTextRun(lineStyle, font, string, maximumLength, hasHyphen() ? &charactersWithHyphen : 0); - if (hasHyphen()) - length = textRun.length(); + TextRun textRun = constructTextRun(lineStyle, alternateStringToRender); + unsigned length = textRun.length(); - int sPos = 0; - int ePos = 0; + unsigned selectionStart = 0; + unsigned selectionEnd = 0; if (haveSelection && (paintSelectedTextOnly || paintSelectedTextSeparately)) - selectionStartEnd(sPos, ePos); + std::tie(selectionStart, selectionEnd) = selectionStartEnd(); if (m_truncation != cNoTruncation) { - sPos = std::min<int>(sPos, m_truncation); - ePos = std::min<int>(ePos, m_truncation); + selectionStart = std::min(selectionStart, static_cast<unsigned>(m_truncation)); + selectionEnd = std::min(selectionEnd, static_cast<unsigned>(m_truncation)); length = m_truncation; } - int emphasisMarkOffset = 0; + float emphasisMarkOffset = 0; bool emphasisMarkAbove; bool hasTextEmphasis = emphasisMarkExistsAndIsAbove(lineStyle, emphasisMarkAbove); const AtomicString& emphasisMark = hasTextEmphasis ? lineStyle.textEmphasisMarkString() : nullAtom; if (!emphasisMark.isEmpty()) emphasisMarkOffset = emphasisMarkAbove ? -font.fontMetrics().ascent() - font.emphasisMarkDescent(emphasisMark) : font.fontMetrics().descent() + font.emphasisMarkAscent(emphasisMark); - const ShadowData* textShadow = paintInfo.forceBlackText() ? 0 : lineStyle.textShadow(); + const ShadowData* textShadow = (paintInfo.forceTextColor()) ? nullptr : lineStyle.textShadow(); - TextPainter textPainter(*context, paintSelectedTextOnly, paintSelectedTextSeparately, font, sPos, ePos, length, emphasisMark, combinedText, textRun, boxRect, textOrigin, emphasisMarkOffset, textShadow, selectionShadow, isHorizontal(), textPaintStyle, selectionPaintStyle); - textPainter.paintText(); + FloatPoint textOrigin(boxOrigin.x(), boxOrigin.y() + font.fontMetrics().ascent()); + if (combinedText) { + if (auto newOrigin = combinedText->computeTextOrigin(boxRect)) + textOrigin = newOrigin.value(); + } + + if (isHorizontal()) + textOrigin.setY(roundToDevicePixel(LayoutUnit(textOrigin.y()), renderer().document().deviceScaleFactor())); + else + textOrigin.setX(roundToDevicePixel(LayoutUnit(textOrigin.x()), renderer().document().deviceScaleFactor())); + + TextPainter textPainter(context); + textPainter.setFont(font); + textPainter.setTextPaintStyle(textPaintStyle); + textPainter.setSelectionPaintStyle(selectionPaintStyle); + textPainter.setIsHorizontal(isHorizontal()); + textPainter.addTextShadow(textShadow, selectionShadow); + textPainter.addEmphasis(emphasisMark, emphasisMarkOffset, combinedText); + + textPainter.paintText(textRun, length, boxRect, textOrigin, selectionStart, selectionEnd, paintSelectedTextOnly, paintSelectedTextSeparately); // Paint decorations TextDecoration textDecorations = lineStyle.textDecorationsInEffect(); - if (textDecorations != TextDecorationNone && paintInfo.phase != PaintPhaseSelection) { - updateGraphicsContext(*context, textPaintStyle); - if (combinedText) - context->concatCTM(rotation(boxRect, Clockwise)); - paintDecoration(*context, boxOrigin, textDecorations, lineStyle.textDecorationStyle(), textShadow, textPainter); - if (combinedText) - context->concatCTM(rotation(boxRect, Counterclockwise)); - } + if (textDecorations != TextDecorationNone && paintInfo.phase != PaintPhaseSelection) + paintDecoration(context, font, combinedText, textRun, textOrigin, boxRect, textDecorations, textPaintStyle, textShadow); if (paintInfo.phase == PaintPhaseForeground) { paintDocumentMarkers(context, boxOrigin, lineStyle, font, false); @@ -700,44 +588,41 @@ void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, } if (shouldRotate) - context->concatCTM(rotation(boxRect, Counterclockwise)); + context.concatCTM(rotation(boxRect, Counterclockwise)); } -void InlineTextBox::selectionStartEnd(int& sPos, int& ePos) +unsigned InlineTextBox::clampedOffset(unsigned x) const { - int startPos, endPos; - if (renderer().selectionState() == RenderObject::SelectionInside) { - startPos = 0; - endPos = renderer().textLength(); - } else { - renderer().selectionStartEnd(startPos, endPos); - if (renderer().selectionState() == RenderObject::SelectionStart) - endPos = renderer().textLength(); - else if (renderer().selectionState() == RenderObject::SelectionEnd) - startPos = 0; - } - - sPos = std::max(startPos - m_start, 0); - ePos = std::min(endPos - m_start, (int)m_len); + return std::max(std::min(x, start() + len()), start()) - start(); } -void alignSelectionRectToDevicePixels(FloatRect& rect) +std::pair<unsigned, unsigned> InlineTextBox::selectionStartEnd() const { - float maxX = floorf(rect.maxX()); - rect.setX(floorf(rect.x())); - rect.setWidth(roundf(maxX - rect.x())); + auto selectionState = renderer().selectionState(); + if (selectionState == RenderObject::SelectionInside) + return { 0, m_len }; + + unsigned start; + unsigned end; + renderer().selectionStartEnd(start, end); + if (selectionState == RenderObject::SelectionStart) + end = renderer().textLength(); + else if (selectionState == RenderObject::SelectionEnd) + start = 0; + return { clampedOffset(start), clampedOffset(end) }; } -void InlineTextBox::paintSelection(GraphicsContext* context, const FloatPoint& boxOrigin, const RenderStyle& style, const Font& font, Color textColor) +void InlineTextBox::paintSelection(GraphicsContext& context, const FloatPoint& boxOrigin, const RenderStyle& style, const FontCascade& font, const Color& textColor) { #if ENABLE(TEXT_SELECTION) - if (context->paintingDisabled()) + if (context.paintingDisabled()) return; // See if we have a selection to paint at all. - int sPos, ePos; - selectionStartEnd(sPos, ePos); - if (sPos >= ePos) + unsigned selectionStart; + unsigned selectionEnd; + std::tie(selectionStart, selectionEnd) = selectionStartEnd(); + if (selectionStart >= selectionEnd) return; Color c = renderer().selectionBackgroundColor(); @@ -749,39 +634,32 @@ void InlineTextBox::paintSelection(GraphicsContext* context, const FloatPoint& b if (textColor == c) c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue()); - GraphicsContextStateSaver stateSaver(*context); - updateGraphicsContext(*context, TextPaintStyle(c, style.colorSpace())); // Don't draw text at all! - + GraphicsContextStateSaver stateSaver(context); + updateGraphicsContext(context, TextPaintStyle(c)); // Don't draw text at all! + // If the text is truncated, let the thing being painted in the truncation // draw its own highlight. - int length = m_truncation != cNoTruncation ? m_truncation : m_len; - String string = renderer().text(); - if (string.length() != static_cast<unsigned>(length) || m_start) { - ASSERT_WITH_SECURITY_IMPLICATION(static_cast<unsigned>(m_start + length) <= string.length()); - string = string.substringSharingImpl(m_start, length); - } + unsigned length = m_truncation != cNoTruncation ? m_truncation : len(); - BufferForAppendingHyphen charactersWithHyphen; - bool respectHyphen = ePos == length && hasHyphen(); - TextRun textRun = constructTextRun(style, font, string, renderer().textLength() - m_start, respectHyphen ? &charactersWithHyphen : 0); + String hyphenatedString; + bool respectHyphen = selectionEnd == length && hasHyphen(); + if (respectHyphen) + hyphenatedString = hyphenatedStringForTextRun(style, length); + TextRun textRun = constructTextRun(style, hyphenatedString, std::optional<unsigned>(length)); if (respectHyphen) - ePos = textRun.length(); + selectionEnd = textRun.length(); const RootInlineBox& rootBox = root(); LayoutUnit selectionBottom = rootBox.selectionBottom(); LayoutUnit selectionTop = rootBox.selectionTopAdjustedForPrecedingBlock(); - int deltaY = roundToInt(renderer().style().isFlippedLinesWritingMode() ? selectionBottom - logicalBottom() : logicalTop() - selectionTop); - int selHeight = std::max(0, roundToInt(selectionBottom - selectionTop)); - - FloatPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY); - FloatRect clipRect(localOrigin, FloatSize(m_logicalWidth, selHeight)); - alignSelectionRectToDevicePixels(clipRect); - - context->clip(clipRect); + LayoutUnit deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom - logicalBottom() : logicalTop() - selectionTop; + LayoutUnit selectionHeight = std::max<LayoutUnit>(0, selectionBottom - selectionTop); - context->drawHighlightForText(font, textRun, localOrigin, selHeight, c, style.colorSpace(), sPos, ePos); + LayoutRect selectionRect = LayoutRect(boxOrigin.x(), boxOrigin.y() - deltaY, m_logicalWidth, selectionHeight); + font.adjustSelectionRectForText(textRun, selectionRect, selectionStart, selectionEnd); + context.fillRect(snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr()), c); #else UNUSED_PARAM(context); UNUSED_PARAM(boxOrigin); @@ -791,476 +669,62 @@ void InlineTextBox::paintSelection(GraphicsContext* context, const FloatPoint& b #endif } -void InlineTextBox::paintCompositionBackground(GraphicsContext* context, const FloatPoint& boxOrigin, const RenderStyle& style, const Font& font, int startPos, int endPos) +void InlineTextBox::paintCompositionBackground(GraphicsContext& context, const FloatPoint& boxOrigin, const RenderStyle& style, const FontCascade& font, unsigned startPos, unsigned endPos) { - int offset = m_start; - int sPos = std::max(startPos - offset, 0); - int ePos = std::min(endPos - offset, (int)m_len); - - if (sPos >= ePos) + unsigned selectionStart = clampedOffset(startPos); + unsigned selectionEnd = clampedOffset(endPos); + if (selectionStart >= selectionEnd) return; - GraphicsContextStateSaver stateSaver(*context); - -#if !PLATFORM(IOS) - // FIXME: Is this color still applicable as of Mavericks? for non-Mac ports? We should - // be able to move this color information to RenderStyle. - Color c = Color(225, 221, 85); -#else - Color c = style.compositionFillColor(); -#endif - - updateGraphicsContext(*context, TextPaintStyle(c, style.colorSpace())); // Don't draw text at all! - - int deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop(); - int selHeight = selectionHeight(); - FloatPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY); - context->drawHighlightForText(font, constructTextRun(style, font), localOrigin, selHeight, c, style.colorSpace(), sPos, ePos); -} - -#if PLATFORM(MAC) - -void InlineTextBox::paintCustomHighlight(const LayoutPoint& paintOffset, const AtomicString& type) -{ - Page* page = renderer().frame().page(); - if (!page) - return; - - const RootInlineBox& rootBox = root(); - FloatRect rootRect(paintOffset.x() + rootBox.x(), paintOffset.y() + selectionTop(), rootBox.logicalWidth(), selectionHeight()); - FloatRect textRect(paintOffset.x() + x(), rootRect.y(), logicalWidth(), rootRect.height()); - - page->chrome().client().paintCustomHighlight(renderer().textNode(), type, textRect, rootRect, true, false); -} - -#endif - -static StrokeStyle textDecorationStyleToStrokeStyle(TextDecorationStyle decorationStyle) -{ - StrokeStyle strokeStyle = SolidStroke; - switch (decorationStyle) { - case TextDecorationStyleSolid: - strokeStyle = SolidStroke; - break; - case TextDecorationStyleDouble: - strokeStyle = DoubleStroke; - break; - case TextDecorationStyleDotted: - strokeStyle = DottedStroke; - break; - case TextDecorationStyleDashed: - strokeStyle = DashedStroke; - break; - case TextDecorationStyleWavy: - strokeStyle = WavyStroke; - break; - } - - return strokeStyle; -} - -static int computeUnderlineOffset(const TextUnderlinePosition underlinePosition, const FontMetrics& fontMetrics, const InlineTextBox* inlineTextBox, const int textDecorationThickness) -{ - // Compute the gap between the font and the underline. Use at least one - // pixel gap, if underline is thick then use a bigger gap. - const int gap = std::max<int>(1, ceilf(textDecorationThickness / 2.0)); - - // According to the specification TextUnderlinePositionAuto should default to 'alphabetic' for horizontal text - // and to 'under Left' for vertical text (e.g. japanese). We support only horizontal text for now. - switch (underlinePosition) { - case TextUnderlinePositionAlphabetic: - case TextUnderlinePositionAuto: - return fontMetrics.ascent() + gap; // Position underline near the alphabetic baseline. - case TextUnderlinePositionUnder: { - // Position underline relative to the under edge of the lowest element's content box. - const float offset = inlineTextBox->root().maxLogicalTop() - inlineTextBox->logicalTop(); - return inlineTextBox->logicalHeight() + gap + std::max<float>(offset, 0); - } - } + GraphicsContextStateSaver stateSaver(context); + Color compositionColor = Color::compositionFill; + updateGraphicsContext(context, TextPaintStyle(compositionColor)); // Don't draw text at all! - ASSERT_NOT_REACHED(); - return fontMetrics.ascent() + gap; + LayoutUnit deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop(); + LayoutRect selectionRect = LayoutRect(boxOrigin.x(), boxOrigin.y() - deltaY, 0, selectionHeight()); + TextRun textRun = constructTextRun(style); + font.adjustSelectionRectForText(textRun, selectionRect, selectionStart, selectionEnd); + context.fillRect(snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), textRun.ltr()), compositionColor); } -static void adjustStepToDecorationLength(float& step, float& controlPointDistance, float length) +static inline void mirrorRTLSegment(float logicalWidth, TextDirection direction, float& start, float width) { - ASSERT(step > 0); - - if (length <= 0) + if (direction == LTR) return; - - unsigned stepCount = static_cast<unsigned>(length / step); - - // Each Bezier curve starts at the same pixel that the previous one - // ended. We need to subtract (stepCount - 1) pixels when calculating the - // length covered to account for that. - float uncoveredLength = length - (stepCount * step - (stepCount - 1)); - float adjustment = uncoveredLength / stepCount; - step += adjustment; - controlPointDistance += adjustment; + start = logicalWidth - width - start; } -static void getWavyStrokeParameters(float strokeThickness, float& controlPointDistance, float& step) +void InlineTextBox::paintDecoration(GraphicsContext& context, const FontCascade& font, RenderCombineText* combinedText, const TextRun& textRun, const FloatPoint& textOrigin, + const FloatRect& boxRect, TextDecoration decoration, TextPaintStyle textPaintStyle, const ShadowData* shadow) { - // Distance between decoration's axis and Bezier curve's control points. - // The height of the curve is based on this distance. Use a minimum of 6 pixels distance since - // the actual curve passes approximately at half of that distance, that is 3 pixels. - // The minimum height of the curve is also approximately 3 pixels. Increases the curve's height - // as strockThickness increases to make the curve looks better. - controlPointDistance = 3 * std::max<float>(2, strokeThickness); - - // Increment used to form the diamond shape between start point (p1), control - // points and end point (p2) along the axis of the decoration. Makes the - // curve wider as strockThickness increases to make the curve looks better. - step = 2 * std::max<float>(2, strokeThickness); -} - -/* - * Draw one cubic Bezier curve and repeat the same pattern long the the decoration's axis. - * The start point (p1), controlPoint1, controlPoint2 and end point (p2) of the Bezier curve - * form a diamond shape: - * - * step - * |-----------| - * - * controlPoint1 - * + - * - * - * . . - * . . - * . . - * (x1, y1) p1 + . + p2 (x2, y2) - <--- Decoration's axis - * . . | - * . . | - * . . | controlPointDistance - * | - * | - * + - - * controlPoint2 - * - * |-----------| - * step - */ -static void strokeWavyTextDecoration(GraphicsContext& context, FloatPoint& p1, FloatPoint& p2, float strokeThickness) -{ - context.adjustLineToPixelBoundaries(p1, p2, strokeThickness, context.strokeStyle()); - - Path path; - path.moveTo(p1); - - float controlPointDistance; - float step; - getWavyStrokeParameters(strokeThickness, controlPointDistance, step); - - bool isVerticalLine = (p1.x() == p2.x()); - - if (isVerticalLine) { - ASSERT(p1.x() == p2.x()); - - float xAxis = p1.x(); - float y1; - float y2; - - if (p1.y() < p2.y()) { - y1 = p1.y(); - y2 = p2.y(); - } else { - y1 = p2.y(); - y2 = p1.y(); - } - - adjustStepToDecorationLength(step, controlPointDistance, y2 - y1); - FloatPoint controlPoint1(xAxis + controlPointDistance, 0); - FloatPoint controlPoint2(xAxis - controlPointDistance, 0); - - for (float y = y1; y + 2 * step <= y2;) { - controlPoint1.setY(y + step); - controlPoint2.setY(y + step); - y += 2 * step; - path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(xAxis, y)); - } - } else { - ASSERT(p1.y() == p2.y()); - - float yAxis = p1.y(); - float x1; - float x2; - - if (p1.x() < p2.x()) { - x1 = p1.x(); - x2 = p2.x(); - } else { - x1 = p2.x(); - x2 = p1.x(); - } - - adjustStepToDecorationLength(step, controlPointDistance, x2 - x1); - FloatPoint controlPoint1(0, yAxis + controlPointDistance); - FloatPoint controlPoint2(0, yAxis - controlPointDistance); - - for (float x = x1; x + 2 * step <= x2;) { - controlPoint1.setX(x + step); - controlPoint2.setX(x + step); - x += 2 * step; - path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(x, yAxis)); - } - } - - context.setShouldAntialias(true); - context.strokePath(path); -} - -// Because finding the bounding box of an underline is structurally similar to finding -// the bounding box of a strikethrough, we can pull out the computation and parameterize -// by the location of the decoration (yOffset, underlineOffset, and doubleOffset). -static FloatRect boundingBoxForSingleDecoration(GraphicsContext& context, float textDecorationThickness, - float width, FloatPoint localOrigin, TextDecorationStyle decorationStyle, - bool isPrinting, float yOffset, float underlineOffset, float doubleOffset) -{ - FloatRect boundingBox; - - switch (decorationStyle) { - case TextDecorationStyleWavy: { - FloatPoint start(localOrigin.x(), localOrigin.y() + yOffset); - FloatPoint end = start + FloatSize(width, 0); - context.adjustLineToPixelBoundaries(start, end, textDecorationThickness, context.strokeStyle()); - - float controlPointDistance; - float step; - getWavyStrokeParameters(textDecorationThickness, controlPointDistance, step); - - adjustStepToDecorationLength(step, controlPointDistance, width); - - controlPointDistance += textDecorationThickness; - FloatPoint boundingBoxOrigin = start - FloatSize(0, controlPointDistance); - FloatSize boundingBoxSize = FloatSize(width, 2 * controlPointDistance); - boundingBox = FloatRect(boundingBoxOrigin, boundingBoxSize); - break; - } - default: - boundingBox = context.computeLineBoundsForText(localOrigin + FloatSize(0, underlineOffset), width, isPrinting); - if (decorationStyle == TextDecorationStyleDouble) - boundingBox.unite(context.computeLineBoundsForText(localOrigin + FloatSize(0, doubleOffset), width, isPrinting)); - } - return boundingBox; -} - -static FloatRect boundingBoxForAllActiveDecorations(InlineTextBox& inlineTextBox, GraphicsContext& context, TextDecoration decoration, float textDecorationThickness, float width, float doubleOffset, TextDecorationStyle decorationStyle, const FloatPoint localOrigin, const RenderStyle& lineStyle, bool isPrinting, int baseline) -{ - FloatRect boundingBox; - if (decoration & TextDecorationUnderline) { - int underlineOffset = computeUnderlineOffset(lineStyle.textUnderlinePosition(), lineStyle.fontMetrics(), &inlineTextBox, textDecorationThickness); - - boundingBox.unite(boundingBoxForSingleDecoration(context, textDecorationThickness, width, localOrigin, decorationStyle, isPrinting, underlineOffset + doubleOffset, underlineOffset, baseline + 1)); - } - if (decoration & TextDecorationOverline) - boundingBox.unite(boundingBoxForSingleDecoration(context, textDecorationThickness, width, localOrigin, decorationStyle, isPrinting, -doubleOffset, 0, -doubleOffset)); - if (decoration & TextDecorationLineThrough) - boundingBox.unite(boundingBoxForSingleDecoration(context, textDecorationThickness, width, localOrigin, decorationStyle, isPrinting, 2 * baseline / 3, 2 * baseline / 3, doubleOffset + 2 * baseline / 3)); - return boundingBox; -} - -void InlineTextBox::paintDecoration(GraphicsContext& context, const FloatPoint& boxOrigin, TextDecoration decoration, TextDecorationStyle decorationStyle, const ShadowData* shadow, TextPainter& textPainter) -{ - // FIXME: We should improve this rule and not always just assume 1. - const float textDecorationThickness = 1.f; - if (m_truncation == cFullTruncation) return; - FloatPoint localOrigin = boxOrigin; + updateGraphicsContext(context, textPaintStyle); + if (combinedText) + context.concatCTM(rotation(boxRect, Clockwise)); + float start = 0; float width = m_logicalWidth; if (m_truncation != cNoTruncation) { width = renderer().width(m_start, m_truncation, textPos(), isFirstLine()); - if (!isLeftToRightDirection()) - localOrigin.move(m_logicalWidth - width, 0); - } - - // Get the text decoration colors. - Color underline, overline, linethrough; - renderer().getTextDecorationColors(decoration, underline, overline, linethrough, true); - if (isFirstLine()) - renderer().getTextDecorationColors(decoration, underline, overline, linethrough, true, true); - - // Use a special function for underlines to get the positioning exactly right. - bool isPrinting = renderer().document().printing(); -#if !PLATFORM(IOS) - context.setStrokeThickness(textDecorationThickness); -#else - // On iOS we want to draw crisp decorations. The function drawLineForText takes the context's - // strokeThickness and renders that at device pixel scale (i.e. a strokeThickness of 1 will - // produce a 1 device pixel line, so thinner on retina than non-retina). We will also scale - // our thickness based on the size of the font. Since our default size is 16px we'll use that - // as a scale reference. - float pageScale = 1; - if (Page* page = renderer().frame().page()) - pageScale = page->pageScaleFactor(); - - const float textDecorationBaseFontSize = 16; - float fontSizeScaling = renderer().style().fontSize() / textDecorationBaseFontSize; - float strokeThickness = roundf(textDecorationThickness * fontSizeScaling * pageScale); - context.setStrokeThickness(strokeThickness); -#endif - - bool linesAreOpaque = !isPrinting && (!(decoration & TextDecorationUnderline) || underline.alpha() == 255) && (!(decoration & TextDecorationOverline) || overline.alpha() == 255) && (!(decoration & TextDecorationLineThrough) || linethrough.alpha() == 255); - - const RenderStyle& lineStyle = this->lineStyle(); - int baseline = lineStyle.fontMetrics().ascent(); - - bool setClip = false; - int extraOffset = 0; - if (!linesAreOpaque && shadow && shadow->next()) { - FloatRect clipRect(localOrigin, FloatSize(width, baseline + 2)); - for (const ShadowData* s = shadow; s; s = s->next()) { - int shadowExtent = s->paintingExtent(); - FloatRect shadowRect(localOrigin, FloatSize(width, baseline + 2)); - shadowRect.inflate(shadowExtent); - int shadowX = isHorizontal() ? s->x() : s->y(); - int shadowY = isHorizontal() ? s->y() : -s->x(); - shadowRect.move(shadowX, shadowY); - clipRect.unite(shadowRect); - extraOffset = std::max(extraOffset, std::max(0, shadowY) + shadowExtent); - } - context.save(); - context.clip(clipRect); - extraOffset += baseline + 2; - localOrigin.move(0, extraOffset); - setClip = true; + mirrorRTLSegment(m_logicalWidth, direction(), start, width); } - ColorSpace colorSpace = renderer().style().colorSpace(); - bool setShadow = false; + TextDecorationPainter decorationPainter(context, decoration, renderer(), isFirstLine()); + decorationPainter.setInlineTextBox(this); + decorationPainter.setFont(font); + decorationPainter.setWidth(width); + decorationPainter.setBaseline(lineStyle().fontMetrics().ascent()); + decorationPainter.setIsHorizontal(isHorizontal()); + decorationPainter.addTextShadow(shadow); - do { - if (shadow) { - if (!shadow->next()) { - // The last set of lines paints normally inside the clip. - localOrigin.move(0, -extraOffset); - extraOffset = 0; - } - int shadowX = isHorizontal() ? shadow->x() : shadow->y(); - int shadowY = isHorizontal() ? shadow->y() : -shadow->x(); - context.setShadow(FloatSize(shadowX, shadowY - extraOffset), shadow->radius(), shadow->color(), colorSpace); - setShadow = true; - shadow = shadow->next(); - } + FloatPoint localOrigin = boxRect.location(); + localOrigin.move(start, 0); + decorationPainter.paintTextDecoration(textRun, textOrigin, localOrigin); - // Offset between lines - always non-zero, so lines never cross each other. - float doubleOffset = textDecorationThickness + 1; - - bool clipDecorationToMask = false; - - GraphicsContextStateSaver stateSaver(context, false); - - if (clipDecorationToMask) { - const float skipInkGapWidth = 1; - - stateSaver.save(); - - FloatRect underlineRect = boundingBoxForAllActiveDecorations(*this, context, decoration, textDecorationThickness, width, doubleOffset, decorationStyle, localOrigin, lineStyle, isPrinting, baseline); - IntRect enclosingDeviceRect = enclosingIntRect(underlineRect); - std::unique_ptr<ImageBuffer> imageBuffer = context.createCompatibleBuffer(enclosingDeviceRect.size()); - - if (imageBuffer.get()) { - GraphicsContext& maskContext = *imageBuffer->context(); - maskContext.setFillColor(Color::black, ColorSpaceDeviceRGB); - maskContext.setLineJoin(RoundJoin); - maskContext.translate(FloatPoint() - enclosingDeviceRect.location()); - - maskContext.fillRect(enclosingDeviceRect); - maskContext.setCompositeOperation(CompositeClear); - - textPainter.paintTextInContext(maskContext, skipInkGapWidth); - - context.clipToImageBuffer(imageBuffer.get(), enclosingDeviceRect); - } - } - context.setStrokeStyle(textDecorationStyleToStrokeStyle(decorationStyle)); - if (decoration & TextDecorationUnderline) { - context.setStrokeColor(underline, colorSpace); - TextUnderlinePosition underlinePosition = lineStyle.textUnderlinePosition(); - const int underlineOffset = computeUnderlineOffset(underlinePosition, lineStyle.fontMetrics(), this, textDecorationThickness); - - switch (decorationStyle) { - case TextDecorationStyleWavy: { - FloatPoint start(localOrigin.x(), localOrigin.y() + underlineOffset + doubleOffset); - FloatPoint end(localOrigin.x() + width, localOrigin.y() + underlineOffset + doubleOffset); - strokeWavyTextDecoration(context, start, end, textDecorationThickness); - break; - } - default: -#if ENABLE(CSS3_TEXT_DECORATION_SKIP_INK) - if (lineStyle.textDecorationSkip() == TextDecorationSkipInk) { - if (!context.paintingDisabled()) { - drawSkipInkUnderline(textPainter, context, localOrigin, underlineOffset, width, isPrinting); - - if (decorationStyle == TextDecorationStyleDouble) - drawSkipInkUnderline(textPainter, context, localOrigin, underlineOffset + doubleOffset, width, isPrinting); - } - } else { -#endif // CSS3_TEXT_DECORATION_SKIP_INK - context.drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + underlineOffset), width, isPrinting); - - if (decorationStyle == TextDecorationStyleDouble) - context.drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + underlineOffset + doubleOffset), width, isPrinting); -#if ENABLE(CSS3_TEXT_DECORATION_SKIP_INK) - } -#endif - } - } - if (decoration & TextDecorationOverline) { - context.setStrokeColor(overline, colorSpace); - switch (decorationStyle) { - case TextDecorationStyleWavy: { - FloatPoint start(localOrigin.x(), localOrigin.y() - doubleOffset); - FloatPoint end(localOrigin.x() + width, localOrigin.y() - doubleOffset); - strokeWavyTextDecoration(context, start, end, textDecorationThickness); - break; - } - default: -#if ENABLE(CSS3_TEXT_DECORATION_SKIP_INK) - if (lineStyle.textDecorationSkip() == TextDecorationSkipInk) { - if (!context.paintingDisabled()) { - drawSkipInkUnderline(textPainter, context, localOrigin, 0, width, isPrinting); - - if (decorationStyle == TextDecorationStyleDouble) - drawSkipInkUnderline(textPainter, context, localOrigin, -doubleOffset, width, isPrinting); - } - } else { -#endif // CSS3_TEXT_DECORATION_SKIP_INK - context.drawLineForText(localOrigin, width, isPrinting); - if (decorationStyle == TextDecorationStyleDouble) - context.drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() - doubleOffset), width, isPrinting); -#if ENABLE(CSS3_TEXT_DECORATION_SKIP_INK) - } -#endif - } - } - if (clipDecorationToMask) - stateSaver.restore(); - if (decoration & TextDecorationLineThrough) { - context.setStrokeColor(linethrough, colorSpace); - switch (decorationStyle) { - case TextDecorationStyleWavy: { - FloatPoint start(localOrigin.x(), localOrigin.y() + 2 * baseline / 3); - FloatPoint end(localOrigin.x() + width, localOrigin.y() + 2 * baseline / 3); - strokeWavyTextDecoration(context, start, end, textDecorationThickness); - break; - } - default: - context.drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + 2 * baseline / 3), width, isPrinting); - if (decorationStyle == TextDecorationStyleDouble) - context.drawLineForText(FloatPoint(localOrigin.x(), localOrigin.y() + doubleOffset + 2 * baseline / 3), width, isPrinting); - } - } - } while (shadow); - - if (setClip) - context.restore(); - else if (setShadow) - context.clearShadow(); + if (combinedText) + context.concatCTM(rotation(boxRect, Counterclockwise)); } static GraphicsContext::DocumentMarkerLineStyle lineStyleForMarkerType(DocumentMarker::MarkerType markerType) @@ -1285,7 +749,7 @@ static GraphicsContext::DocumentMarkerLineStyle lineStyleForMarkerType(DocumentM } } -void InlineTextBox::paintDocumentMarker(GraphicsContext* pt, const FloatPoint& boxOrigin, DocumentMarker* marker, const RenderStyle& style, const Font& font, bool grammar) +void InlineTextBox::paintDocumentMarker(GraphicsContext& context, const FloatPoint& boxOrigin, RenderedDocumentMarker& marker, const RenderStyle& style, const FontCascade& font, bool grammar) { // Never print spelling/grammar markers (5327887) if (renderer().document().printing()) @@ -1299,39 +763,32 @@ void InlineTextBox::paintDocumentMarker(GraphicsContext* pt, const FloatPoint& b // Determine whether we need to measure text bool markerSpansWholeBox = true; - if (m_start <= (int)marker->startOffset()) + if (m_start <= marker.startOffset()) markerSpansWholeBox = false; - if ((end() + 1) != marker->endOffset()) // end points at the last char, not past it + if ((end() + 1) != marker.endOffset()) // end points at the last char, not past it markerSpansWholeBox = false; if (m_truncation != cNoTruncation) markerSpansWholeBox = false; - bool isDictationMarker = marker->type() == DocumentMarker::DictationAlternatives; + bool isDictationMarker = marker.type() == DocumentMarker::DictationAlternatives; if (!markerSpansWholeBox || grammar || isDictationMarker) { - int startPosition = std::max<int>(marker->startOffset() - m_start, 0); - int endPosition = std::min<int>(marker->endOffset() - m_start, m_len); + unsigned startPosition = clampedOffset(marker.startOffset()); + unsigned endPosition = clampedOffset(marker.endOffset()); if (m_truncation != cNoTruncation) - endPosition = std::min<int>(endPosition, m_truncation); + endPosition = std::min(endPosition, static_cast<unsigned>(m_truncation)); // Calculate start & width int deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop(); int selHeight = selectionHeight(); FloatPoint startPoint(boxOrigin.x(), boxOrigin.y() - deltaY); - TextRun run = constructTextRun(style, font); + TextRun run = constructTextRun(style); - // FIXME: Convert the document markers to float rects. - IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, startPoint, selHeight, startPosition, endPosition)); + LayoutRect selectionRect = LayoutRect(startPoint, FloatSize(0, selHeight)); + font.adjustSelectionRectForText(run, selectionRect, startPosition, endPosition); + IntRect markerRect = enclosingIntRect(selectionRect); start = markerRect.x() - startPoint.x(); width = markerRect.width(); - - // Store rendered rects for bad grammar markers, so we can hit-test against it elsewhere in order to - // display a toolTip. We don't do this for misspelling markers. - if (grammar || isDictationMarker) { - markerRect.move(-boxOrigin.x(), -boxOrigin.y()); - markerRect = renderer().localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox(); - toRenderedDocumentMarker(marker)->setRenderedRect(markerRect); - } } // IMPORTANT: The misspelling underline is not considered when calculating the text bounds, so we have to @@ -1351,65 +808,44 @@ void InlineTextBox::paintDocumentMarker(GraphicsContext* pt, const FloatPoint& b // In larger fonts, though, place the underline up near the baseline to prevent a big gap. underlineOffset = baseline + 2; } - pt->drawLineForDocumentMarker(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + underlineOffset), width, lineStyleForMarkerType(marker->type())); + context.drawLineForDocumentMarker(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + underlineOffset), width, lineStyleForMarkerType(marker.type())); } -void InlineTextBox::paintTextMatchMarker(GraphicsContext* pt, const FloatPoint& boxOrigin, DocumentMarker* marker, const RenderStyle& style, const Font& font) +void InlineTextBox::paintTextMatchMarker(GraphicsContext& context, const FloatPoint& boxOrigin, RenderedDocumentMarker& marker, const RenderStyle& style, const FontCascade& font) { + if (!renderer().frame().editor().markedTextMatchesAreHighlighted()) + return; + + Color color = marker.isActiveMatch() ? renderer().theme().platformActiveTextSearchHighlightColor() : renderer().theme().platformInactiveTextSearchHighlightColor(); + GraphicsContextStateSaver stateSaver(context); + updateGraphicsContext(context, TextPaintStyle(color)); // Don't draw text at all! + // Use same y positioning and height as for selection, so that when the selection and this highlight are on // the same word there are no pieces sticking out. - int deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop(); - int selHeight = selectionHeight(); + LayoutUnit deltaY = renderer().style().isFlippedLinesWritingMode() ? selectionBottom() - logicalBottom() : logicalTop() - selectionTop(); + LayoutRect selectionRect = LayoutRect(boxOrigin.x(), boxOrigin.y() - deltaY, 0, this->selectionHeight()); - int sPos = std::max(marker->startOffset() - m_start, (unsigned)0); - int ePos = std::min(marker->endOffset() - m_start, (unsigned)m_len); - TextRun run = constructTextRun(style, font); + unsigned sPos = clampedOffset(marker.startOffset()); + unsigned ePos = clampedOffset(marker.endOffset()); + TextRun run = constructTextRun(style); + font.adjustSelectionRectForText(run, selectionRect, sPos, ePos); - // Always compute and store the rect associated with this marker. The computed rect is in absolute coordinates. - IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, IntPoint(x(), selectionTop()), selHeight, sPos, ePos)); - markerRect = renderer().localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox(); - toRenderedDocumentMarker(marker)->setRenderedRect(markerRect); - - // Optionally highlight the text - if (renderer().frame().editor().markedTextMatchesAreHighlighted()) { - Color color = marker->activeMatch() ? renderer().theme().platformActiveTextSearchHighlightColor() : renderer().theme().platformInactiveTextSearchHighlightColor(); - GraphicsContextStateSaver stateSaver(*pt); - updateGraphicsContext(*pt, TextPaintStyle(color, style.colorSpace())); // Don't draw text at all! - pt->clip(FloatRect(boxOrigin.x(), boxOrigin.y() - deltaY, m_logicalWidth, selHeight)); - pt->drawHighlightForText(font, run, FloatPoint(boxOrigin.x(), boxOrigin.y() - deltaY), selHeight, color, style.colorSpace(), sPos, ePos); - } -} + if (selectionRect.isEmpty()) + return; -void InlineTextBox::computeRectForReplacementMarker(DocumentMarker* marker, const RenderStyle& style, const Font& font) -{ - // Replacement markers are not actually drawn, but their rects need to be computed for hit testing. - int top = selectionTop(); - int h = selectionHeight(); - - int sPos = std::max(marker->startOffset() - m_start, (unsigned)0); - int ePos = std::min(marker->endOffset() - m_start, (unsigned)m_len); - TextRun run = constructTextRun(style, font); - IntPoint startPoint = IntPoint(x(), top); - - // Compute and store the rect associated with this marker. - IntRect markerRect = enclosingIntRect(font.selectionRectForText(run, startPoint, h, sPos, ePos)); - markerRect = renderer().localToAbsoluteQuad(FloatRect(markerRect)).enclosingBoundingBox(); - toRenderedDocumentMarker(marker)->setRenderedRect(markerRect); + context.fillRect(snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), run.ltr()), color); } -void InlineTextBox::paintDocumentMarkers(GraphicsContext* pt, const FloatPoint& boxOrigin, const RenderStyle& style, const Font& font, bool background) +void InlineTextBox::paintDocumentMarkers(GraphicsContext& context, const FloatPoint& boxOrigin, const RenderStyle& style, const FontCascade& font, bool background) { if (!renderer().textNode()) return; - Vector<DocumentMarker*> markers = renderer().document().markers().markersFor(renderer().textNode()); - Vector<DocumentMarker*>::const_iterator markerIt = markers.begin(); + Vector<RenderedDocumentMarker*> markers = renderer().document().markers().markersFor(renderer().textNode()); // Give any document markers that touch this run a chance to draw before the text has been drawn. // Note end() points at the last char, not one past it like endOffset and ranges do. - for ( ; markerIt != markers.end(); ++markerIt) { - DocumentMarker* marker = *markerIt; - + for (auto* marker : markers) { // Paint either the background markers or the foreground markers, but not both switch (marker->type()) { case DocumentMarker::Grammar: @@ -1425,6 +861,9 @@ void InlineTextBox::paintDocumentMarkers(GraphicsContext* pt, const FloatPoint& continue; break; case DocumentMarker::TextMatch: +#if ENABLE(TELEPHONE_NUMBER_DETECTION) + case DocumentMarker::TelephoneNumber: +#endif if (!background) continue; break; @@ -1446,23 +885,26 @@ void InlineTextBox::paintDocumentMarkers(GraphicsContext* pt, const FloatPoint& case DocumentMarker::Spelling: case DocumentMarker::CorrectionIndicator: case DocumentMarker::DictationAlternatives: - paintDocumentMarker(pt, boxOrigin, marker, style, font, false); + paintDocumentMarker(context, boxOrigin, *marker, style, font, false); break; case DocumentMarker::Grammar: - paintDocumentMarker(pt, boxOrigin, marker, style, font, true); + paintDocumentMarker(context, boxOrigin, *marker, style, font, true); break; #if PLATFORM(IOS) // FIXME: See <rdar://problem/8933352>. Also, remove the PLATFORM(IOS)-guard. case DocumentMarker::DictationPhraseWithAlternatives: - paintDocumentMarker(pt, boxOrigin, marker, style, font, true); + paintDocumentMarker(context, boxOrigin, *marker, style, font, true); break; #endif case DocumentMarker::TextMatch: - paintTextMatchMarker(pt, boxOrigin, marker, style, font); + paintTextMatchMarker(context, boxOrigin, *marker, style, font); break; case DocumentMarker::Replacement: - computeRectForReplacementMarker(marker, style, font); break; +#if ENABLE(TELEPHONE_NUMBER_DETECTION) + case DocumentMarker::TelephoneNumber: + break; +#endif default: ASSERT_NOT_REACHED(); } @@ -1470,7 +912,7 @@ void InlineTextBox::paintDocumentMarkers(GraphicsContext* pt, const FloatPoint& } } -void InlineTextBox::paintCompositionUnderline(GraphicsContext* ctx, const FloatPoint& boxOrigin, const CompositionUnderline& underline) +void InlineTextBox::paintCompositionUnderline(GraphicsContext& context, const FloatPoint& boxOrigin, const CompositionUnderline& underline) { if (m_truncation == cFullTruncation) return; @@ -1495,6 +937,7 @@ void InlineTextBox::paintCompositionUnderline(GraphicsContext* ctx, const FloatP } if (!useWholeWidth) { width = renderer().width(paintStart, paintEnd - paintStart, textPos() + start, isFirstLine()); + mirrorRTLSegment(m_logicalWidth, direction(), start, width); } // Thick marked text underlines are 2px thick as long as there is room for the 2px line under the baseline. @@ -1510,9 +953,9 @@ void InlineTextBox::paintCompositionUnderline(GraphicsContext* ctx, const FloatP start += 1; width -= 2; - ctx->setStrokeColor(underline.color, renderer().style().colorSpace()); - ctx->setStrokeThickness(lineThickness); - ctx->drawLineForText(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + logicalHeight() - lineThickness), width, renderer().document().printing()); + context.setStrokeColor(underline.color); + context.setStrokeThickness(lineThickness); + context.drawLineForText(FloatPoint(boxOrigin.x() + start, boxOrigin.y() + logicalHeight() - lineThickness), width, renderer().document().printing()); } int InlineTextBox::caretMinOffset() const @@ -1544,60 +987,54 @@ int InlineTextBox::offsetForPosition(float lineOffset, bool includePartialGlyphs if (lineOffset - logicalLeft() < 0) return isLeftToRightDirection() ? 0 : len(); - FontCachePurgePreventer fontCachePurgePreventer; - const RenderStyle& lineStyle = this->lineStyle(); - const Font& font = fontToUse(lineStyle, renderer()); - return font.offsetForPosition(constructTextRun(lineStyle, font), lineOffset - logicalLeft(), includePartialGlyphs); + const FontCascade& font = fontToUse(lineStyle, renderer()); + return font.offsetForPosition(constructTextRun(lineStyle), lineOffset - logicalLeft(), includePartialGlyphs); } -float InlineTextBox::positionForOffset(int offset) const +float InlineTextBox::positionForOffset(unsigned offset) const { ASSERT(offset >= m_start); - ASSERT(offset <= m_start + m_len); + ASSERT(offset <= m_start + len()); if (isLineBreak()) return logicalLeft(); - FontCachePurgePreventer fontCachePurgePreventer; - const RenderStyle& lineStyle = this->lineStyle(); - const Font& font = fontToUse(lineStyle, renderer()); - int from = !isLeftToRightDirection() ? offset - m_start : 0; - int to = !isLeftToRightDirection() ? m_len : offset - m_start; + const FontCascade& font = fontToUse(lineStyle, renderer()); + unsigned from = !isLeftToRightDirection() ? clampedOffset(offset) : 0; + unsigned to = !isLeftToRightDirection() ? m_len : clampedOffset(offset); // FIXME: Do we need to add rightBearing here? - return font.selectionRectForText(constructTextRun(lineStyle, font), IntPoint(logicalLeft(), 0), 0, from, to).maxX(); + LayoutRect selectionRect = LayoutRect(logicalLeft(), 0, 0, 0); + TextRun run = constructTextRun(lineStyle); + font.adjustSelectionRectForText(run, selectionRect, from, to); + return snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), run.ltr()).maxX(); } -TextRun InlineTextBox::constructTextRun(const RenderStyle& style, const Font& font, BufferForAppendingHyphen* charactersWithHyphen) const +StringView InlineTextBox::substringToRender(std::optional<unsigned> overridingLength) const { - ASSERT(renderer().text()); - - String string = renderer().text(); - unsigned startPos = start(); - unsigned length = len(); - - if (string.length() != length || startPos) - string = string.substringSharingImpl(startPos, length); - - return constructTextRun(style, font, string, renderer().textLength() - startPos, charactersWithHyphen); + return StringView(renderer().text()).substring(start(), overridingLength.value_or(len())); } -TextRun InlineTextBox::constructTextRun(const RenderStyle& style, const Font& font, String string, int maximumLength, BufferForAppendingHyphen* charactersWithHyphen) const +String InlineTextBox::hyphenatedStringForTextRun(const RenderStyle& style, std::optional<unsigned> alternateLength) const { - int length = string.length(); + ASSERT(hasHyphen()); + return makeString(substringToRender(alternateLength), style.hyphenString()); +} - if (charactersWithHyphen) { - adjustCharactersAndLengthForHyphen(*charactersWithHyphen, style, string, length); - maximumLength = length; - } +TextRun InlineTextBox::constructTextRun(const RenderStyle& style, StringView alternateStringToRender, std::optional<unsigned> alternateLength) const +{ + if (alternateStringToRender.isNull()) + return constructTextRun(style, substringToRender(alternateLength), renderer().textLength() - start()); + return constructTextRun(style, alternateStringToRender, alternateStringToRender.length()); +} - ASSERT(maximumLength >= length); +TextRun InlineTextBox::constructTextRun(const RenderStyle& style, StringView string, unsigned maximumLength) const +{ + ASSERT(maximumLength >= string.length()); TextRun run(string, textPos(), expansion(), expansionBehavior(), direction(), dirOverride() || style.rtlOrdering() == VisualOrder, !renderer().canUseSimpleFontCodePath()); run.setTabSize(!style.collapseWhiteSpace(), style.tabSize()); - if (font.isSVGFont()) - run.setRenderingContext(SVGTextRunRenderingContext::create(renderer())); // Propagate the maximum length of the characters buffer to the TextRun, even when we're only processing a substring. run.setCharactersLength(maximumLength); @@ -1605,27 +1042,51 @@ TextRun InlineTextBox::constructTextRun(const RenderStyle& style, const Font& fo return run; } -#ifndef NDEBUG +ExpansionBehavior InlineTextBox::expansionBehavior() const +{ + ExpansionBehavior leadingBehavior; + if (forceLeadingExpansion()) + leadingBehavior = ForceLeadingExpansion; + else if (canHaveLeadingExpansion()) + leadingBehavior = AllowLeadingExpansion; + else + leadingBehavior = ForbidLeadingExpansion; + + ExpansionBehavior trailingBehavior; + if (forceTrailingExpansion()) + trailingBehavior = ForceTrailingExpansion; + else if (expansion() && nextLeafChild() && !nextLeafChild()->isLineBreak()) + trailingBehavior = AllowTrailingExpansion; + else + trailingBehavior = ForbidTrailingExpansion; + + return leadingBehavior | trailingBehavior; +} + +#if ENABLE(TREE_DEBUGGING) const char* InlineTextBox::boxName() const { return "InlineTextBox"; } -void InlineTextBox::showBox(int printedCharacters) const +void InlineTextBox::showLineBox(bool mark, int depth) const { + fprintf(stderr, "-------- %c-", isDirty() ? 'D' : '-'); + + int printedCharacters = 0; + if (mark) { + fprintf(stderr, "*"); + ++printedCharacters; + } + while (++printedCharacters <= depth * 2) + fputc(' ', stderr); + String value = renderer().text(); value = value.substring(start(), len()); value.replaceWithLiteral('\\', "\\\\"); value.replaceWithLiteral('\n', "\\n"); - printedCharacters += fprintf(stderr, "%s\t%p", boxName(), this); - for (; printedCharacters < showTreeCharacterOffset; printedCharacters++) - fputc(' ', stderr); - printedCharacters = fprintf(stderr, "\t%s %p", renderer().renderName(), &renderer()); - const int rendererCharacterOffset = 24; - for (; printedCharacters < rendererCharacterOffset; printedCharacters++) - fputc(' ', stderr); - fprintf(stderr, "(%d,%d) \"%s\"\n", start(), start() + len(), value.utf8().data()); + fprintf(stderr, "%s (%.2f, %.2f) (%.2f, %.2f) (%p) renderer->(%p) run(%d, %d) \"%s\"\n", boxName(), x(), y(), width(), height(), this, &renderer(), start(), start() + len(), value.utf8().data()); } #endif |