1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <SkFontMetrics.h>
18 #include <SkRRect.h>
19 #include <SkTextBlob.h>
20 #include <com_android_graphics_hwui_flags.h>
21
22 #include "../utils/Color.h"
23 #include "Canvas.h"
24 #include "FeatureFlags.h"
25 #include "MinikinUtils.h"
26 #include "Paint.h"
27 #include "Properties.h"
28 #include "RenderNode.h"
29 #include "Typeface.h"
30 #include "hwui/PaintFilter.h"
31 #include "pipeline/skia/SkiaRecordingCanvas.h"
32
33 namespace flags = com::android::graphics::hwui::flags;
34
35 namespace android {
36
37 // These should match the constants in framework/base/core/java/android/text/Layout.java
38 inline constexpr float kHighContrastTextBorderWidth = 4.0f;
39 inline constexpr float kHighContrastTextBorderWidthFactor = 0.2f;
40
drawStroke(SkScalar left,SkScalar right,SkScalar top,SkScalar thickness,const Paint & paint,Canvas * canvas)41 static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
42 const Paint& paint, Canvas* canvas) {
43 const SkScalar strokeWidth = fmax(thickness, 1.0f);
44 const SkScalar bottom = top + strokeWidth;
45 canvas->drawRect(left, top, right, bottom, paint);
46 }
47
simplifyPaint(int color,Paint * paint)48 static void simplifyPaint(int color, Paint* paint) {
49 paint->setColor(color);
50 paint->setShader(nullptr);
51 paint->setColorFilter(nullptr);
52 paint->setLooper(nullptr);
53
54 if (flags::high_contrast_text_small_text_rect()) {
55 paint->setStrokeWidth(
56 std::max(kHighContrastTextBorderWidth,
57 kHighContrastTextBorderWidthFactor * paint->getSkFont().getSize()));
58 } else {
59 auto borderWidthFactor = 0.04f;
60 paint->setStrokeWidth(kHighContrastTextBorderWidth +
61 borderWidthFactor * paint->getSkFont().getSize());
62 }
63 paint->setStrokeJoin(SkPaint::kRound_Join);
64 paint->setLooper(nullptr);
65 }
66
67 class DrawTextFunctor {
68 public:
69 /**
70 * Creates a Functor to draw the given text layout.
71 *
72 * @param layout
73 * @param canvas
74 * @param paint
75 * @param x
76 * @param y
77 * @param totalAdvance
78 * @param bounds bounds of the text. Only required if high contrast text mode is enabled.
79 */
DrawTextFunctor(const minikin::Layout & layout,Canvas * canvas,const Paint & paint,float x,float y,float totalAdvance)80 DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
81 float y, float totalAdvance)
82 : layout(layout)
83 , canvas(canvas)
84 , paint(paint)
85 , x(x)
86 , y(y)
87 , totalAdvance(totalAdvance)
88 , underlinePosition(0)
89 , underlineThickness(0) {}
90
operator()91 void operator()(size_t start, size_t end) {
92 auto glyphFunc = [&](uint16_t* text, float* positions) {
93 for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
94 text[textIndex++] = layout.getGlyphId(i);
95 positions[posIndex++] = x + layout.getX(i);
96 positions[posIndex++] = y + layout.getY(i);
97 }
98 };
99
100 size_t glyphCount = end - start;
101
102 if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
103 // high contrast draw path
104 int color = paint.getColor();
105 bool darken;
106 // This equation should match the one in core/java/android/text/Layout.java
107 if (flags::high_contrast_text_luminance()) {
108 uirenderer::Lab lab = uirenderer::sRGBToLab(color);
109 darken = lab.L <= 50;
110 } else {
111 int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
112 darken = channelSum < (128 * 3);
113 }
114
115 // outline
116 gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
117 Paint outlinePaint(paint);
118 simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
119 outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
120 canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
121
122 // inner
123 gDrawTextBlobMode = DrawTextBlobMode::HctInner;
124 Paint innerPaint(paint);
125 simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
126 innerPaint.setStyle(SkPaint::kFill_Style);
127 canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
128 gDrawTextBlobMode = DrawTextBlobMode::Normal;
129 } else {
130 // standard draw path
131 canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
132 }
133
134 if (text_feature::fix_double_underline()) {
135 // Extract underline position and thickness.
136 if (paint.isUnderline()) {
137 SkFontMetrics metrics;
138 paint.getSkFont().getMetrics(&metrics);
139 const float textSize = paint.getSkFont().getSize();
140 SkScalar position;
141 if (!metrics.hasUnderlinePosition(&position)) {
142 position = textSize * Paint::kStdUnderline_Top;
143 }
144 SkScalar thickness;
145 if (!metrics.hasUnderlineThickness(&thickness)) {
146 thickness = textSize * Paint::kStdUnderline_Thickness;
147 }
148
149 // If multiple fonts are used, use the most bottom position and most thick stroke
150 // width as the underline position. This follows the CSS standard:
151 // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property
152 // <quote>
153 // The exact position and thickness of line decorations is UA-defined in this level.
154 // However, for underlines and overlines the UA must use a single thickness and
155 // position on each line for the decorations deriving from a single decorating box.
156 // </quote>
157 underlinePosition = std::max(underlinePosition, position);
158 underlineThickness = std::max(underlineThickness, thickness);
159 }
160 }
161 }
162
getUnderlinePosition()163 float getUnderlinePosition() const { return underlinePosition; }
getUnderlineThickness()164 float getUnderlineThickness() const { return underlineThickness; }
165
166 private:
167 const minikin::Layout& layout;
168 Canvas* canvas;
169 const Paint& paint;
170 float x;
171 float y;
172 float totalAdvance;
173 float underlinePosition;
174 float underlineThickness;
175 };
176
177 } // namespace android
178