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 |