| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "core/paint/InlineTextBoxPainter.h" | 5 #include "core/paint/InlineTextBoxPainter.h" |
| 6 | 6 |
| 7 #include "core/editing/CompositionUnderline.h" | 7 #include "core/editing/CompositionUnderline.h" |
| 8 #include "core/editing/Editor.h" | 8 #include "core/editing/Editor.h" |
| 9 #include "core/editing/markers/DocumentMarkerController.h" | 9 #include "core/editing/markers/DocumentMarkerController.h" |
| 10 #include "core/frame/LocalFrame.h" | 10 #include "core/frame/LocalFrame.h" |
| 11 #include "core/layout/LayoutTextCombine.h" | 11 #include "core/layout/LayoutTextCombine.h" |
| 12 #include "core/layout/LayoutTheme.h" | 12 #include "core/layout/LayoutTheme.h" |
| 13 #include "core/layout/api/LineLayoutAPIShim.h" | 13 #include "core/layout/api/LineLayoutAPIShim.h" |
| 14 #include "core/layout/api/LineLayoutBox.h" | 14 #include "core/layout/api/LineLayoutBox.h" |
| 15 #include "core/layout/line/InlineTextBox.h" | 15 #include "core/layout/line/InlineTextBox.h" |
| 16 #include "core/paint/PaintInfo.h" | 16 #include "core/paint/PaintInfo.h" |
| 17 #include "core/paint/TextPainter.h" | 17 #include "core/paint/TextPainter.h" |
| 18 #include "core/style/AppliedTextDecoration.h" | 18 #include "core/style/AppliedTextDecoration.h" |
| 19 #include "platform/graphics/GraphicsContextStateSaver.h" | 19 #include "platform/graphics/GraphicsContextStateSaver.h" |
| 20 #include "platform/graphics/paint/DrawingRecorder.h" | 20 #include "platform/graphics/paint/DrawingRecorder.h" |
| 21 #include "wtf/Optional.h" | 21 #include "wtf/Optional.h" |
| 22 | 22 |
| 23 namespace blink { | 23 namespace blink { |
| 24 | 24 |
| 25 static int computeUnderlineOffset(const TextUnderlinePosition underlinePosition, |
| 26 const FontMetrics& fontMetrics, |
| 27 const InlineTextBox* inlineTextBox, |
| 28 const float textDecorationThickness) { |
| 29 // Compute the gap between the font and the underline. Use at least one |
| 30 // pixel gap, if underline is thick then use a bigger gap. |
| 31 int gap = 0; |
| 32 |
| 33 // Underline position of zero means draw underline on Baseline Position, |
| 34 // in Blink we need at least 1-pixel gap to adding following check. |
| 35 // Positive underline Position means underline should be drawn above baselin e |
| 36 // and negative value means drawing below baseline, negating the value as in |
| 37 // Blink |
| 38 // downward Y-increases. |
| 39 |
| 40 if (fontMetrics.underlinePosition()) |
| 41 gap = -fontMetrics.underlinePosition(); |
| 42 else |
| 43 gap = std::max<int>(1, ceilf(textDecorationThickness / 2.f)); |
| 44 |
| 45 // FIXME: We support only horizontal text for now. |
| 46 switch (underlinePosition) { |
| 47 case TextUnderlinePositionAuto: |
| 48 return fontMetrics.ascent() + |
| 49 gap; // Position underline near the alphabetic baseline. |
| 50 case TextUnderlinePositionUnder: { |
| 51 // Position underline relative to the under edge of the lowest element's |
| 52 // content box. |
| 53 const LayoutUnit offset = |
| 54 inlineTextBox->root().maxLogicalTop() - inlineTextBox->logicalTop(); |
| 55 if (offset > 0) |
| 56 return (inlineTextBox->logicalHeight() + gap + offset).toInt(); |
| 57 return (inlineTextBox->logicalHeight() + gap).toInt(); |
| 58 } |
| 59 } |
| 60 |
| 61 NOTREACHED(); |
| 62 return fontMetrics.ascent() + gap; |
| 63 } |
| 64 |
| 65 static bool shouldSetDecorationAntialias( |
| 66 const Vector<AppliedTextDecoration>& decorations) { |
| 67 for (const AppliedTextDecoration& decoration : decorations) { |
| 68 TextDecorationStyle decorationStyle = decoration.style(); |
| 69 if (decorationStyle == TextDecorationStyleDotted || |
| 70 decorationStyle == TextDecorationStyleDashed) |
| 71 return true; |
| 72 } |
| 73 return false; |
| 74 } |
| 75 |
| 76 static StrokeStyle textDecorationStyleToStrokeStyle( |
| 77 TextDecorationStyle decorationStyle) { |
| 78 StrokeStyle strokeStyle = SolidStroke; |
| 79 switch (decorationStyle) { |
| 80 case TextDecorationStyleSolid: |
| 81 strokeStyle = SolidStroke; |
| 82 break; |
| 83 case TextDecorationStyleDouble: |
| 84 strokeStyle = DoubleStroke; |
| 85 break; |
| 86 case TextDecorationStyleDotted: |
| 87 strokeStyle = DottedStroke; |
| 88 break; |
| 89 case TextDecorationStyleDashed: |
| 90 strokeStyle = DashedStroke; |
| 91 break; |
| 92 case TextDecorationStyleWavy: |
| 93 strokeStyle = WavyStroke; |
| 94 break; |
| 95 } |
| 96 |
| 97 return strokeStyle; |
| 98 } |
| 99 |
| 100 static void adjustStepToDecorationLength(float& step, |
| 101 float& controlPointDistance, |
| 102 float length) { |
| 103 DCHECK_GT(step, 0); |
| 104 |
| 105 if (length <= 0) |
| 106 return; |
| 107 |
| 108 unsigned stepCount = static_cast<unsigned>(length / step); |
| 109 |
| 110 // Each Bezier curve starts at the same pixel that the previous one |
| 111 // ended. We need to subtract (stepCount - 1) pixels when calculating the |
| 112 // length covered to account for that. |
| 113 float uncoveredLength = length - (stepCount * step - (stepCount - 1)); |
| 114 float adjustment = uncoveredLength / stepCount; |
| 115 step += adjustment; |
| 116 controlPointDistance += adjustment; |
| 117 } |
| 118 |
| 119 class AppliedDecorationPainter final { |
| 120 STACK_ALLOCATED(); |
| 121 |
| 122 public: |
| 123 AppliedDecorationPainter(GraphicsContext& context, |
| 124 FloatPoint startPoint, |
| 125 float width, |
| 126 const AppliedTextDecoration& decoration, |
| 127 float thickness, |
| 128 float doubleOffset, |
| 129 int wavyOffsetFactor, |
| 130 bool antialiasDecoration) |
| 131 : m_context(context), |
| 132 m_startPoint(startPoint), |
| 133 m_width(width), |
| 134 m_decoration(decoration), |
| 135 m_thickness(thickness), |
| 136 m_doubleOffset(doubleOffset), |
| 137 m_wavyOffsetFactor(wavyOffsetFactor), |
| 138 m_shouldAntialias(antialiasDecoration){}; |
| 139 |
| 140 void paint(); |
| 141 FloatRect decorationBounds(); |
| 142 |
| 143 private: |
| 144 void strokeWavyTextDecoration(); |
| 145 |
| 146 Path prepareWavyStrokePath(); |
| 147 Path prepareDottedDashedStrokePath(); |
| 148 |
| 149 GraphicsContext& m_context; |
| 150 FloatPoint m_startPoint; |
| 151 float m_width; |
| 152 const AppliedTextDecoration& m_decoration; |
| 153 float m_thickness; |
| 154 const float m_doubleOffset; |
| 155 const int m_wavyOffsetFactor; |
| 156 bool m_shouldAntialias; |
| 157 }; |
| 158 |
| 159 Path AppliedDecorationPainter::prepareDottedDashedStrokePath() { |
| 160 // These coordinate transforms need to match what's happening in |
| 161 // GraphicsContext's drawLineForText and drawLine. |
| 162 int y = floorf(m_startPoint.y() + std::max<float>(m_thickness / 2.0f, 0.5f)); |
| 163 Path strokePath; |
| 164 FloatPoint roundedStartPoint(m_startPoint.x(), y); |
| 165 FloatPoint roundedEndPoint(roundedStartPoint + FloatPoint(m_width, 0)); |
| 166 m_context.adjustLineToPixelBoundaries(roundedStartPoint, roundedEndPoint, |
| 167 roundf(m_thickness), |
| 168 m_context.getStrokeStyle()); |
| 169 strokePath.moveTo(roundedStartPoint); |
| 170 strokePath.addLineTo(roundedEndPoint); |
| 171 return strokePath; |
| 172 } |
| 173 |
| 174 FloatRect AppliedDecorationPainter::decorationBounds() { |
| 175 StrokeData strokeData; |
| 176 strokeData.setThickness(m_thickness); |
| 177 |
| 178 switch (m_decoration.style()) { |
| 179 case TextDecorationStyleDotted: |
| 180 case TextDecorationStyleDashed: { |
| 181 strokeData.setStyle( |
| 182 textDecorationStyleToStrokeStyle(m_decoration.style())); |
| 183 return prepareDottedDashedStrokePath().strokeBoundingRect( |
| 184 strokeData, Path::BoundsType::Exact); |
| 185 } |
| 186 case TextDecorationStyleWavy: |
| 187 return prepareWavyStrokePath().strokeBoundingRect( |
| 188 strokeData, Path::BoundsType::Exact); |
| 189 break; |
| 190 case TextDecorationStyleDouble: |
| 191 if (m_doubleOffset > 0) { |
| 192 return FloatRect(m_startPoint.x(), m_startPoint.y(), m_width, |
| 193 m_doubleOffset + m_thickness); |
| 194 } |
| 195 return FloatRect(m_startPoint.x(), m_startPoint.y() + m_doubleOffset, |
| 196 m_width, -m_doubleOffset + m_thickness); |
| 197 break; |
| 198 case TextDecorationStyleSolid: |
| 199 return FloatRect(m_startPoint.x(), m_startPoint.y(), m_width, |
| 200 m_thickness); |
| 201 default: |
| 202 break; |
| 203 } |
| 204 NOTREACHED(); |
| 205 return FloatRect(); |
| 206 } |
| 207 |
| 208 void AppliedDecorationPainter::paint() { |
| 209 m_context.setStrokeStyle( |
| 210 textDecorationStyleToStrokeStyle(m_decoration.style())); |
| 211 m_context.setStrokeColor(m_decoration.color()); |
| 212 |
| 213 switch (m_decoration.style()) { |
| 214 case TextDecorationStyleWavy: |
| 215 strokeWavyTextDecoration(); |
| 216 break; |
| 217 case TextDecorationStyleDotted: |
| 218 case TextDecorationStyleDashed: |
| 219 m_context.setShouldAntialias(m_shouldAntialias); |
| 220 // Fall through |
| 221 default: |
| 222 m_context.drawLineForText(m_startPoint, m_width); |
| 223 |
| 224 if (m_decoration.style() == TextDecorationStyleDouble) { |
| 225 m_context.drawLineForText(m_startPoint + FloatPoint(0, m_doubleOffset), |
| 226 m_width); |
| 227 } |
| 228 } |
| 229 } |
| 230 |
| 231 void AppliedDecorationPainter::strokeWavyTextDecoration() { |
| 232 m_context.setShouldAntialias(true); |
| 233 m_context.strokePath(prepareWavyStrokePath()); |
| 234 } |
| 235 |
| 236 /* |
| 237 * Prepare a path for a cubic Bezier curve and repeat the same pattern long the |
| 238 * the decoration's axis. The start point (p1), controlPoint1, controlPoint2 |
| 239 * and end point (p2) of the Bezier curve form a diamond shape: |
| 240 * |
| 241 * step |
| 242 * |-----------| |
| 243 * |
| 244 * controlPoint1 |
| 245 * + |
| 246 * |
| 247 * |
| 248 * . . |
| 249 * . . |
| 250 * . . |
| 251 * (x1, y1) p1 + . + p2 (x2, y2) - <--- Decoration's axis |
| 252 * . . | |
| 253 * . . | |
| 254 * . . | controlPointDistance |
| 255 * | |
| 256 * | |
| 257 * + - |
| 258 * controlPoint2 |
| 259 * |
| 260 * |-----------| |
| 261 * step |
| 262 */ |
| 263 Path AppliedDecorationPainter::prepareWavyStrokePath() { |
| 264 FloatPoint p1(m_startPoint + |
| 265 FloatPoint(0, m_doubleOffset * m_wavyOffsetFactor)); |
| 266 FloatPoint p2(m_startPoint + |
| 267 FloatPoint(m_width, m_doubleOffset * m_wavyOffsetFactor)); |
| 268 |
| 269 m_context.adjustLineToPixelBoundaries(p1, p2, m_thickness, |
| 270 m_context.getStrokeStyle()); |
| 271 |
| 272 Path path; |
| 273 path.moveTo(p1); |
| 274 |
| 275 // Distance between decoration's axis and Bezier curve's control points. |
| 276 // The height of the curve is based on this distance. Use a minimum of 6 |
| 277 // pixels distance since |
| 278 // the actual curve passes approximately at half of that distance, that is 3 |
| 279 // pixels. |
| 280 // The minimum height of the curve is also approximately 3 pixels. Increases |
| 281 // the curve's height |
| 282 // as strockThickness increases to make the curve looks better. |
| 283 float controlPointDistance = 3 * std::max<float>(2, m_thickness); |
| 284 |
| 285 // Increment used to form the diamond shape between start point (p1), control |
| 286 // points and end point (p2) along the axis of the decoration. Makes the |
| 287 // curve wider as strockThickness increases to make the curve looks better. |
| 288 float step = 2 * std::max<float>(2, m_thickness); |
| 289 |
| 290 bool isVerticalLine = (p1.x() == p2.x()); |
| 291 |
| 292 if (isVerticalLine) { |
| 293 DCHECK(p1.x() == p2.x()); |
| 294 |
| 295 float xAxis = p1.x(); |
| 296 float y1; |
| 297 float y2; |
| 298 |
| 299 if (p1.y() < p2.y()) { |
| 300 y1 = p1.y(); |
| 301 y2 = p2.y(); |
| 302 } else { |
| 303 y1 = p2.y(); |
| 304 y2 = p1.y(); |
| 305 } |
| 306 |
| 307 adjustStepToDecorationLength(step, controlPointDistance, y2 - y1); |
| 308 FloatPoint controlPoint1(xAxis + controlPointDistance, 0); |
| 309 FloatPoint controlPoint2(xAxis - controlPointDistance, 0); |
| 310 |
| 311 for (float y = y1; y + 2 * step <= y2;) { |
| 312 controlPoint1.setY(y + step); |
| 313 controlPoint2.setY(y + step); |
| 314 y += 2 * step; |
| 315 path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(xAxis, y)); |
| 316 } |
| 317 } else { |
| 318 DCHECK(p1.y() == p2.y()); |
| 319 |
| 320 float yAxis = p1.y(); |
| 321 float x1; |
| 322 float x2; |
| 323 |
| 324 if (p1.x() < p2.x()) { |
| 325 x1 = p1.x(); |
| 326 x2 = p2.x(); |
| 327 } else { |
| 328 x1 = p2.x(); |
| 329 x2 = p1.x(); |
| 330 } |
| 331 |
| 332 adjustStepToDecorationLength(step, controlPointDistance, x2 - x1); |
| 333 FloatPoint controlPoint1(0, yAxis + controlPointDistance); |
| 334 FloatPoint controlPoint2(0, yAxis - controlPointDistance); |
| 335 |
| 336 for (float x = x1; x + 2 * step <= x2;) { |
| 337 controlPoint1.setX(x + step); |
| 338 controlPoint2.setX(x + step); |
| 339 x += 2 * step; |
| 340 path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(x, yAxis)); |
| 341 } |
| 342 } |
| 343 return path; |
| 344 } |
| 345 |
| 25 typedef WTF::HashMap<const InlineTextBox*, TextBlobPtr> | 346 typedef WTF::HashMap<const InlineTextBox*, TextBlobPtr> |
| 26 InlineTextBoxBlobCacheMap; | 347 InlineTextBoxBlobCacheMap; |
| 27 static InlineTextBoxBlobCacheMap* gTextBlobCache; | 348 static InlineTextBoxBlobCacheMap* gTextBlobCache; |
| 28 | 349 |
| 29 static const int misspellingLineThickness = 3; | 350 static const int misspellingLineThickness = 3; |
| 30 | 351 |
| 31 void InlineTextBoxPainter::removeFromTextBlobCache( | 352 void InlineTextBoxPainter::removeFromTextBlobCache( |
| 32 const InlineTextBox& inlineTextBox) { | 353 const InlineTextBox& inlineTextBox) { |
| 33 if (gTextBlobCache) | 354 if (gTextBlobCache) |
| 34 gTextBlobCache->remove(&inlineTextBox); | 355 gTextBlobCache->remove(&inlineTextBox); |
| (...skipping 246 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 281 if (textBlobIsCacheable) | 602 if (textBlobIsCacheable) |
| 282 cachedTextBlob = addToTextBlobCache(m_inlineTextBox); | 603 cachedTextBlob = addToTextBlobCache(m_inlineTextBox); |
| 283 textPainter.paint(selectionStart, selectionEnd, length, selectionStyle, | 604 textPainter.paint(selectionStart, selectionEnd, length, selectionStyle, |
| 284 cachedTextBlob); | 605 cachedTextBlob); |
| 285 } | 606 } |
| 286 | 607 |
| 287 // Paint decorations | 608 // Paint decorations |
| 288 if (styleToUse.textDecorationsInEffect() != TextDecorationNone && | 609 if (styleToUse.textDecorationsInEffect() != TextDecorationNone && |
| 289 !paintSelectedTextOnly) { | 610 !paintSelectedTextOnly) { |
| 290 GraphicsContextStateSaver stateSaver(context, false); | 611 GraphicsContextStateSaver stateSaver(context, false); |
| 612 |
| 291 TextPainter::updateGraphicsContext( | 613 TextPainter::updateGraphicsContext( |
| 292 context, textStyle, m_inlineTextBox.isHorizontal(), stateSaver); | 614 context, textStyle, m_inlineTextBox.isHorizontal(), stateSaver); |
| 615 |
| 293 if (combinedText) | 616 if (combinedText) |
| 294 context.concatCTM(TextPainter::rotation(boxRect, TextPainter::Clockwise)); | 617 context.concatCTM(TextPainter::rotation(boxRect, TextPainter::Clockwise)); |
| 295 paintDecorations(paintInfo, boxOrigin, styleToUse.appliedTextDecorations()); | 618 paintDecorations(textPainter, paintInfo, boxOrigin, |
| 619 styleToUse.appliedTextDecorations()); |
| 296 if (combinedText) | 620 if (combinedText) |
| 297 context.concatCTM( | 621 context.concatCTM( |
| 298 TextPainter::rotation(boxRect, TextPainter::Counterclockwise)); | 622 TextPainter::rotation(boxRect, TextPainter::Counterclockwise)); |
| 299 } | 623 } |
| 300 | 624 |
| 301 if (paintInfo.phase == PaintPhaseForeground) | 625 if (paintInfo.phase == PaintPhaseForeground) |
| 302 paintDocumentMarkers(paintInfo, boxOrigin, styleToUse, font, | 626 paintDocumentMarkers(paintInfo, boxOrigin, styleToUse, font, |
| 303 DocumentMarkerPaintPhase::Foreground); | 627 DocumentMarkerPaintPhase::Foreground); |
| 304 | 628 |
| 305 if (shouldRotate) | 629 if (shouldRotate) |
| (...skipping 370 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 676 LayoutRect& rect) { | 1000 LayoutRect& rect) { |
| 677 FloatRectOutsets outsets = FloatRectOutsets(); | 1001 FloatRectOutsets outsets = FloatRectOutsets(); |
| 678 float spaceWidth = m_inlineTextBox.newlineSpaceWidth(); | 1002 float spaceWidth = m_inlineTextBox.newlineSpaceWidth(); |
| 679 if (m_inlineTextBox.isLeftToRightDirection()) | 1003 if (m_inlineTextBox.isLeftToRightDirection()) |
| 680 outsets.setRight(spaceWidth); | 1004 outsets.setRight(spaceWidth); |
| 681 else | 1005 else |
| 682 outsets.setLeft(spaceWidth); | 1006 outsets.setLeft(spaceWidth); |
| 683 rect.expand(outsets); | 1007 rect.expand(outsets); |
| 684 } | 1008 } |
| 685 | 1009 |
| 686 static int computeUnderlineOffset(const TextUnderlinePosition underlinePosition, | |
| 687 const FontMetrics& fontMetrics, | |
| 688 const InlineTextBox* inlineTextBox, | |
| 689 const float textDecorationThickness) { | |
| 690 // Compute the gap between the font and the underline. Use at least one | |
| 691 // pixel gap, if underline is thick then use a bigger gap. | |
| 692 int gap = 0; | |
| 693 | |
| 694 // Underline position of zero means draw underline on Baseline Position, | |
| 695 // in Blink we need at least 1-pixel gap to adding following check. | |
| 696 // Positive underline Position means underline should be drawn above baselin e | |
| 697 // and negative value means drawing below baseline, negating the value as in | |
| 698 // Blink downward Y-increases. | |
| 699 | |
| 700 if (fontMetrics.underlinePosition()) | |
| 701 gap = -fontMetrics.underlinePosition(); | |
| 702 else | |
| 703 gap = std::max<int>(1, ceilf(textDecorationThickness / 2.f)); | |
| 704 | |
| 705 // FIXME: We support only horizontal text for now. | |
| 706 switch (underlinePosition) { | |
| 707 case TextUnderlinePositionAuto: | |
| 708 return fontMetrics.ascent() + | |
| 709 gap; // Position underline near the alphabetic baseline. | |
| 710 case TextUnderlinePositionUnder: { | |
| 711 // Position underline relative to the under edge of the lowest element's | |
| 712 // content box. | |
| 713 const LayoutUnit offset = | |
| 714 inlineTextBox->root().maxLogicalTop() - inlineTextBox->logicalTop(); | |
| 715 if (offset > 0) | |
| 716 return (inlineTextBox->logicalHeight() + gap + offset).toInt(); | |
| 717 return (inlineTextBox->logicalHeight() + gap).toInt(); | |
| 718 } | |
| 719 } | |
| 720 | |
| 721 ASSERT_NOT_REACHED(); | |
| 722 return fontMetrics.ascent() + gap; | |
| 723 } | |
| 724 | |
| 725 static bool shouldSetDecorationAntialias( | |
| 726 const Vector<AppliedTextDecoration>& decorations) { | |
| 727 for (const AppliedTextDecoration& decoration : decorations) { | |
| 728 TextDecorationStyle decorationStyle = decoration.style(); | |
| 729 if (decorationStyle == TextDecorationStyleDotted || | |
| 730 decorationStyle == TextDecorationStyleDashed) | |
| 731 return true; | |
| 732 } | |
| 733 return false; | |
| 734 } | |
| 735 | |
| 736 static StrokeStyle textDecorationStyleToStrokeStyle( | |
| 737 TextDecorationStyle decorationStyle) { | |
| 738 StrokeStyle strokeStyle = SolidStroke; | |
| 739 switch (decorationStyle) { | |
| 740 case TextDecorationStyleSolid: | |
| 741 strokeStyle = SolidStroke; | |
| 742 break; | |
| 743 case TextDecorationStyleDouble: | |
| 744 strokeStyle = DoubleStroke; | |
| 745 break; | |
| 746 case TextDecorationStyleDotted: | |
| 747 strokeStyle = DottedStroke; | |
| 748 break; | |
| 749 case TextDecorationStyleDashed: | |
| 750 strokeStyle = DashedStroke; | |
| 751 break; | |
| 752 case TextDecorationStyleWavy: | |
| 753 strokeStyle = WavyStroke; | |
| 754 break; | |
| 755 } | |
| 756 | |
| 757 return strokeStyle; | |
| 758 } | |
| 759 | |
| 760 static void adjustStepToDecorationLength(float& step, | |
| 761 float& controlPointDistance, | |
| 762 float length) { | |
| 763 DCHECK_GT(step, 0); | |
| 764 | |
| 765 if (length <= 0) | |
| 766 return; | |
| 767 | |
| 768 unsigned stepCount = static_cast<unsigned>(length / step); | |
| 769 | |
| 770 // Each Bezier curve starts at the same pixel that the previous one | |
| 771 // ended. We need to subtract (stepCount - 1) pixels when calculating the | |
| 772 // length covered to account for that. | |
| 773 float uncoveredLength = length - (stepCount * step - (stepCount - 1)); | |
| 774 float adjustment = uncoveredLength / stepCount; | |
| 775 step += adjustment; | |
| 776 controlPointDistance += adjustment; | |
| 777 } | |
| 778 | |
| 779 /* | |
| 780 * Draw one cubic Bezier curve and repeat the same pattern long the the | |
| 781 * decoration's axis. The start point (p1), controlPoint1, controlPoint2 and | |
| 782 * end point (p2) of the Bezier curve form a diamond shape: | |
| 783 * | |
| 784 * step | |
| 785 * |-----------| | |
| 786 * | |
| 787 * controlPoint1 | |
| 788 * + | |
| 789 * | |
| 790 * | |
| 791 * . . | |
| 792 * . . | |
| 793 * . . | |
| 794 * (x1, y1) p1 + . + p2 (x2, y2) - <--- Decoration's axis | |
| 795 * . . | | |
| 796 * . . | | |
| 797 * . . | controlPointDistance | |
| 798 * | | |
| 799 * | | |
| 800 * + - | |
| 801 * controlPoint2 | |
| 802 * | |
| 803 * |-----------| | |
| 804 * step | |
| 805 */ | |
| 806 static void strokeWavyTextDecoration(GraphicsContext& context, | |
| 807 FloatPoint p1, | |
| 808 FloatPoint p2, | |
| 809 float strokeThickness) { | |
| 810 context.adjustLineToPixelBoundaries(p1, p2, strokeThickness, | |
| 811 context.getStrokeStyle()); | |
| 812 | |
| 813 Path path; | |
| 814 path.moveTo(p1); | |
| 815 | |
| 816 // Distance between decoration's axis and Bezier curve's control points. | |
| 817 // The height of the curve is based on this distance. Use a minimum of 6 | |
| 818 // pixels distance since the actual curve passes approximately at half of that | |
| 819 // distance, that is 3 pixels. The minimum height of the curve is also | |
| 820 // approximately 3 pixels. Increases the curve's height | |
| 821 // as strockThickness increases to make the curve looks better. | |
| 822 float controlPointDistance = 3 * std::max<float>(2, strokeThickness); | |
| 823 | |
| 824 // Increment used to form the diamond shape between start point (p1), control | |
| 825 // points and end point (p2) along the axis of the decoration. Makes the | |
| 826 // curve wider as strockThickness increases to make the curve looks better. | |
| 827 float step = 2 * std::max<float>(2, strokeThickness); | |
| 828 | |
| 829 bool isVerticalLine = (p1.x() == p2.x()); | |
| 830 | |
| 831 if (isVerticalLine) { | |
| 832 DCHECK(p1.x() == p2.x()); | |
| 833 | |
| 834 float xAxis = p1.x(); | |
| 835 float y1; | |
| 836 float y2; | |
| 837 | |
| 838 if (p1.y() < p2.y()) { | |
| 839 y1 = p1.y(); | |
| 840 y2 = p2.y(); | |
| 841 } else { | |
| 842 y1 = p2.y(); | |
| 843 y2 = p1.y(); | |
| 844 } | |
| 845 | |
| 846 adjustStepToDecorationLength(step, controlPointDistance, y2 - y1); | |
| 847 FloatPoint controlPoint1(xAxis + controlPointDistance, 0); | |
| 848 FloatPoint controlPoint2(xAxis - controlPointDistance, 0); | |
| 849 | |
| 850 for (float y = y1; y + 2 * step <= y2;) { | |
| 851 controlPoint1.setY(y + step); | |
| 852 controlPoint2.setY(y + step); | |
| 853 y += 2 * step; | |
| 854 path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(xAxis, y)); | |
| 855 } | |
| 856 } else { | |
| 857 DCHECK(p1.y() == p2.y()); | |
| 858 | |
| 859 float yAxis = p1.y(); | |
| 860 float x1; | |
| 861 float x2; | |
| 862 | |
| 863 if (p1.x() < p2.x()) { | |
| 864 x1 = p1.x(); | |
| 865 x2 = p2.x(); | |
| 866 } else { | |
| 867 x1 = p2.x(); | |
| 868 x2 = p1.x(); | |
| 869 } | |
| 870 | |
| 871 adjustStepToDecorationLength(step, controlPointDistance, x2 - x1); | |
| 872 FloatPoint controlPoint1(0, yAxis + controlPointDistance); | |
| 873 FloatPoint controlPoint2(0, yAxis - controlPointDistance); | |
| 874 | |
| 875 for (float x = x1; x + 2 * step <= x2;) { | |
| 876 controlPoint1.setX(x + step); | |
| 877 controlPoint2.setX(x + step); | |
| 878 x += 2 * step; | |
| 879 path.addBezierCurveTo(controlPoint1, controlPoint2, FloatPoint(x, yAxis)); | |
| 880 } | |
| 881 } | |
| 882 | |
| 883 context.setShouldAntialias(true); | |
| 884 context.strokePath(path); | |
| 885 } | |
| 886 | |
| 887 static void paintAppliedDecoration(GraphicsContext& context, | |
| 888 FloatPoint start, | |
| 889 float width, | |
| 890 float doubleOffset, | |
| 891 int wavyOffsetFactor, | |
| 892 AppliedTextDecoration decoration, | |
| 893 float thickness, | |
| 894 bool antialiasDecoration, | |
| 895 bool isPrinting) { | |
| 896 context.setStrokeStyle(textDecorationStyleToStrokeStyle(decoration.style())); | |
| 897 context.setStrokeColor(decoration.color()); | |
| 898 | |
| 899 switch (decoration.style()) { | |
| 900 case TextDecorationStyleWavy: | |
| 901 strokeWavyTextDecoration( | |
| 902 context, start + FloatPoint(0, doubleOffset * wavyOffsetFactor), | |
| 903 start + FloatPoint(width, doubleOffset * wavyOffsetFactor), | |
| 904 thickness); | |
| 905 break; | |
| 906 case TextDecorationStyleDotted: | |
| 907 case TextDecorationStyleDashed: | |
| 908 context.setShouldAntialias(antialiasDecoration); | |
| 909 // Fall through | |
| 910 default: | |
| 911 context.drawLineForText(FloatPoint(start), width, isPrinting); | |
| 912 | |
| 913 if (decoration.style() == TextDecorationStyleDouble) | |
| 914 context.drawLineForText(start + FloatPoint(0, doubleOffset), width, | |
| 915 isPrinting); | |
| 916 } | |
| 917 } | |
| 918 | |
| 919 void InlineTextBoxPainter::paintDecorations( | 1010 void InlineTextBoxPainter::paintDecorations( |
| 1011 TextPainter& textPainter, |
| 920 const PaintInfo& paintInfo, | 1012 const PaintInfo& paintInfo, |
| 921 const LayoutPoint& boxOrigin, | 1013 const LayoutPoint& boxOrigin, |
| 922 const Vector<AppliedTextDecoration>& decorations) { | 1014 const Vector<AppliedTextDecoration>& decorations) { |
| 923 if (m_inlineTextBox.truncation() == cFullTruncation) | 1015 if (m_inlineTextBox.truncation() == cFullTruncation) |
| 924 return; | 1016 return; |
| 925 | 1017 |
| 926 GraphicsContext& context = paintInfo.context; | 1018 GraphicsContext& context = paintInfo.context; |
| 927 GraphicsContextStateSaver stateSaver(context); | 1019 GraphicsContextStateSaver stateSaver(context); |
| 928 | 1020 |
| 929 LayoutPoint localOrigin(boxOrigin); | 1021 LayoutPoint localOrigin(boxOrigin); |
| 930 | 1022 |
| 931 LayoutUnit width = m_inlineTextBox.logicalWidth(); | 1023 LayoutUnit width = m_inlineTextBox.logicalWidth(); |
| 932 if (m_inlineTextBox.truncation() != cNoTruncation) { | 1024 if (m_inlineTextBox.truncation() != cNoTruncation) { |
| 933 bool ltr = m_inlineTextBox.isLeftToRightDirection(); | 1025 bool ltr = m_inlineTextBox.isLeftToRightDirection(); |
| 934 bool flowIsLTR = | 1026 bool flowIsLTR = |
| 935 m_inlineTextBox.getLineLayoutItem().style()->isLeftToRightDirection(); | 1027 m_inlineTextBox.getLineLayoutItem().style()->isLeftToRightDirection(); |
| 936 width = LayoutUnit(m_inlineTextBox.getLineLayoutItem().width( | 1028 width = LayoutUnit(m_inlineTextBox.getLineLayoutItem().width( |
| 937 ltr == flowIsLTR ? m_inlineTextBox.start() | 1029 ltr == flowIsLTR ? m_inlineTextBox.start() |
| 938 : m_inlineTextBox.truncation(), | 1030 : m_inlineTextBox.truncation(), |
| 939 ltr == flowIsLTR ? m_inlineTextBox.truncation() | 1031 ltr == flowIsLTR ? m_inlineTextBox.truncation() |
| 940 : m_inlineTextBox.len() - m_inlineTextBox.truncation(), | 1032 : m_inlineTextBox.len() - m_inlineTextBox.truncation(), |
| 941 m_inlineTextBox.textPos(), flowIsLTR ? LTR : RTL, | 1033 m_inlineTextBox.textPos(), flowIsLTR ? LTR : RTL, |
| 942 m_inlineTextBox.isFirstLineStyle())); | 1034 m_inlineTextBox.isFirstLineStyle())); |
| 943 if (!flowIsLTR) | 1035 if (!flowIsLTR) |
| 944 localOrigin.move(m_inlineTextBox.logicalWidth() - width, LayoutUnit()); | 1036 localOrigin.move(m_inlineTextBox.logicalWidth() - width, LayoutUnit()); |
| 945 } | 1037 } |
| 946 | 1038 |
| 947 // Use a special function for underlines to get the positioning exactly right. | 1039 LayoutObject& textBoxLayoutObject = inlineLayoutObject(); |
| 948 bool isPrinting = paintInfo.isPrinting(); | |
| 949 | 1040 |
| 950 LayoutObject& textBoxLayoutObject = inlineLayoutObject(); | |
| 951 const ComputedStyle& styleToUse = | 1041 const ComputedStyle& styleToUse = |
| 952 textBoxLayoutObject.styleRef(m_inlineTextBox.isFirstLineStyle()); | 1042 textBoxLayoutObject.styleRef(m_inlineTextBox.isFirstLineStyle()); |
| 953 const SimpleFontData* fontData = styleToUse.font().primaryFont(); | 1043 const SimpleFontData* fontData = styleToUse.font().primaryFont(); |
| 954 DCHECK(fontData); | 1044 DCHECK(fontData); |
| 955 float baseline = fontData ? fontData->getFontMetrics().ascent() : 0; | 1045 float baseline = fontData ? fontData->getFontMetrics().floatAscent() : 0; |
| 956 | 1046 |
| 957 // Set the thick of the line to be 10% (or something else ?)of the computed | 1047 // Set the thick of the line to be 10% (or something else ?)of the computed |
| 958 // font size and not less than 1px. Using computedFontSize should take care | 1048 // font size and not less than 1px. Using computedFontSize should take care |
| 959 // of zoom as well. | 1049 // of zoom as well. |
| 960 | 1050 |
| 961 // Update Underline thickness, in case we have Faulty Font Metrics calculating | 1051 // Update Underline thickness, in case we have Faulty Font Metrics calculating |
| 962 // underline thickness by old method. | 1052 // underline thickness by old method. |
| 963 float textDecorationThickness = 0.0; | 1053 float textDecorationThickness = 0.0; |
| 964 int fontHeightInt = 0; | 1054 int fontHeightInt = 0; |
| 965 if (fontData) { | 1055 if (fontData) { |
| 966 textDecorationThickness = fontData->getFontMetrics().underlineThickness(); | 1056 textDecorationThickness = fontData->getFontMetrics().underlineThickness(); |
| 967 fontHeightInt = (int)(fontData->getFontMetrics().floatHeight() + 0.5); | 1057 fontHeightInt = (int)(fontData->getFontMetrics().floatHeight() + 0.5); |
| 968 } | 1058 } |
| 969 if ((textDecorationThickness == 0.f) || | 1059 if ((textDecorationThickness == 0.f) || |
| 970 (textDecorationThickness >= (fontHeightInt >> 1))) | 1060 (textDecorationThickness >= (fontHeightInt >> 1))) |
| 971 textDecorationThickness = | 1061 textDecorationThickness = |
| 972 std::max(1.f, styleToUse.computedFontSize() / 10.f); | 1062 std::max(1.f, styleToUse.computedFontSize() / 10.f); |
| 973 | 1063 |
| 974 context.setStrokeThickness(textDecorationThickness); | 1064 context.setStrokeThickness(textDecorationThickness); |
| 975 | 1065 |
| 976 bool antialiasDecoration = shouldSetDecorationAntialias(decorations); | 1066 bool antialiasDecoration = shouldSetDecorationAntialias(decorations); |
| 977 | 1067 |
| 978 // Offset between lines - always non-zero, so lines never cross each other. | 1068 // Offset between lines - always non-zero, so lines never cross each other. |
| 979 float doubleOffset = textDecorationThickness + 1.f; | 1069 float doubleOffset = textDecorationThickness + 1.f; |
| 1070 bool skipIntercepts = |
| 1071 styleToUse.getTextDecorationSkip() & TextDecorationSkipInk; |
| 980 | 1072 |
| 981 for (const AppliedTextDecoration& decoration : decorations) { | 1073 for (const AppliedTextDecoration& decoration : decorations) { |
| 982 TextDecoration lines = decoration.lines(); | 1074 TextDecoration lines = decoration.lines(); |
| 983 if (lines & TextDecorationUnderline && fontData) { | 1075 if ((lines & TextDecorationUnderline) && fontData) { |
| 984 const int underlineOffset = computeUnderlineOffset( | 1076 const int underlineOffset = computeUnderlineOffset( |
| 985 styleToUse.getTextUnderlinePosition(), fontData->getFontMetrics(), | 1077 styleToUse.getTextUnderlinePosition(), fontData->getFontMetrics(), |
| 986 &m_inlineTextBox, textDecorationThickness); | 1078 &m_inlineTextBox, textDecorationThickness); |
| 987 paintAppliedDecoration( | 1079 AppliedDecorationPainter decorationPainter( |
| 988 context, FloatPoint(localOrigin) + FloatPoint(0, underlineOffset), | 1080 context, FloatPoint(localOrigin) + FloatPoint(0, underlineOffset), |
| 989 width.toFloat(), doubleOffset, 1, decoration, textDecorationThickness, | 1081 width.toFloat(), decoration, textDecorationThickness, doubleOffset, 1, |
| 990 antialiasDecoration, isPrinting); | 1082 antialiasDecoration); |
| 1083 if (skipIntercepts) { |
| 1084 textPainter.clipDecorationsStripe( |
| 1085 -baseline + decorationPainter.decorationBounds().y() - |
| 1086 FloatPoint(localOrigin).y(), |
| 1087 decorationPainter.decorationBounds().height(), |
| 1088 textDecorationThickness); |
| 1089 } |
| 1090 decorationPainter.paint(); |
| 991 } | 1091 } |
| 992 if (lines & TextDecorationOverline) { | 1092 if (lines & TextDecorationOverline) { |
| 993 paintAppliedDecoration( | 1093 AppliedDecorationPainter decorationPainter( |
| 994 context, FloatPoint(localOrigin), width.toFloat(), -doubleOffset, 1, | 1094 context, FloatPoint(localOrigin), width.toFloat(), decoration, |
| 995 decoration, textDecorationThickness, antialiasDecoration, isPrinting); | 1095 textDecorationThickness, -doubleOffset, 1, antialiasDecoration); |
| 1096 if (skipIntercepts) { |
| 1097 textPainter.clipDecorationsStripe( |
| 1098 -baseline + decorationPainter.decorationBounds().y() - |
| 1099 FloatPoint(localOrigin).y(), |
| 1100 decorationPainter.decorationBounds().height(), |
| 1101 textDecorationThickness); |
| 1102 } |
| 1103 decorationPainter.paint(); |
| 996 } | 1104 } |
| 997 if (lines & TextDecorationLineThrough) { | 1105 if (lines & TextDecorationLineThrough) { |
| 998 const float lineThroughOffset = 2 * baseline / 3; | 1106 const float lineThroughOffset = 2 * baseline / 3; |
| 999 paintAppliedDecoration( | 1107 AppliedDecorationPainter decorationPainter( |
| 1000 context, FloatPoint(localOrigin) + FloatPoint(0, lineThroughOffset), | 1108 context, FloatPoint(localOrigin) + FloatPoint(0, lineThroughOffset), |
| 1001 width.toFloat(), doubleOffset, 0, decoration, textDecorationThickness, | 1109 width.toFloat(), decoration, textDecorationThickness, doubleOffset, 0, |
| 1002 antialiasDecoration, isPrinting); | 1110 antialiasDecoration); |
| 1111 if (skipIntercepts) { |
| 1112 textPainter.clipDecorationsStripe( |
| 1113 -baseline + decorationPainter.decorationBounds().y() - |
| 1114 FloatPoint(localOrigin).y(), |
| 1115 decorationPainter.decorationBounds().height(), |
| 1116 textDecorationThickness); |
| 1117 } |
| 1118 decorationPainter.paint(); |
| 1003 } | 1119 } |
| 1004 } | 1120 } |
| 1005 } | 1121 } |
| 1006 | 1122 |
| 1007 void InlineTextBoxPainter::paintCompositionUnderline( | 1123 void InlineTextBoxPainter::paintCompositionUnderline( |
| 1008 GraphicsContext& context, | 1124 GraphicsContext& context, |
| 1009 const LayoutPoint& boxOrigin, | 1125 const LayoutPoint& boxOrigin, |
| 1010 const CompositionUnderline& underline) { | 1126 const CompositionUnderline& underline) { |
| 1011 if (underline.color() == Color::transparent) | 1127 if (underline.color() == Color::transparent) |
| 1012 return; | 1128 return; |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1074 start += 1; | 1190 start += 1; |
| 1075 width -= 2; | 1191 width -= 2; |
| 1076 | 1192 |
| 1077 context.setStrokeColor(underline.color()); | 1193 context.setStrokeColor(underline.color()); |
| 1078 context.setStrokeThickness(lineThickness); | 1194 context.setStrokeThickness(lineThickness); |
| 1079 context.drawLineForText( | 1195 context.drawLineForText( |
| 1080 FloatPoint( | 1196 FloatPoint( |
| 1081 boxOrigin.x() + start, | 1197 boxOrigin.x() + start, |
| 1082 (boxOrigin.y() + m_inlineTextBox.logicalHeight() - lineThickness) | 1198 (boxOrigin.y() + m_inlineTextBox.logicalHeight() - lineThickness) |
| 1083 .toFloat()), | 1199 .toFloat()), |
| 1084 width, m_inlineTextBox.getLineLayoutItem().document().printing()); | 1200 width); |
| 1085 } | 1201 } |
| 1086 | 1202 |
| 1087 void InlineTextBoxPainter::paintTextMatchMarkerForeground( | 1203 void InlineTextBoxPainter::paintTextMatchMarkerForeground( |
| 1088 const PaintInfo& paintInfo, | 1204 const PaintInfo& paintInfo, |
| 1089 const LayoutPoint& boxOrigin, | 1205 const LayoutPoint& boxOrigin, |
| 1090 DocumentMarker* marker, | 1206 DocumentMarker* marker, |
| 1091 const ComputedStyle& style, | 1207 const ComputedStyle& style, |
| 1092 const Font& font) { | 1208 const Font& font) { |
| 1093 if (!inlineLayoutObject().frame()->editor().markedTextMatchesAreHighlighted()) | 1209 if (!inlineLayoutObject().frame()->editor().markedTextMatchesAreHighlighted()) |
| 1094 return; | 1210 return; |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1151 GraphicsContextStateSaver stateSaver(context); | 1267 GraphicsContextStateSaver stateSaver(context); |
| 1152 | 1268 |
| 1153 LayoutRect boxRect(boxOrigin, LayoutSize(m_inlineTextBox.logicalWidth(), | 1269 LayoutRect boxRect(boxOrigin, LayoutSize(m_inlineTextBox.logicalWidth(), |
| 1154 m_inlineTextBox.logicalHeight())); | 1270 m_inlineTextBox.logicalHeight())); |
| 1155 context.clip(FloatRect(boxRect)); | 1271 context.clip(FloatRect(boxRect)); |
| 1156 context.drawHighlightForText(font, run, FloatPoint(boxOrigin), | 1272 context.drawHighlightForText(font, run, FloatPoint(boxOrigin), |
| 1157 boxRect.height().toInt(), color, sPos, ePos); | 1273 boxRect.height().toInt(), color, sPos, ePos); |
| 1158 } | 1274 } |
| 1159 | 1275 |
| 1160 } // namespace blink | 1276 } // namespace blink |
| OLD | NEW |