-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathTextDrawing.cpp
More file actions
190 lines (163 loc) · 8.08 KB
/
Copy pathTextDrawing.cpp
File metadata and controls
190 lines (163 loc) · 8.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "TextDrawing.h"
#include <AutoDraw.h>
#include <Utils/ValueUtils.h>
#include <unicode.h>
#include <windows.ui.composition.interop.h>
#include <winrt/Microsoft.ReactNative.Composition.h>
#include "CompositionHelpers.h"
namespace winrt::Microsoft::ReactNative::Composition {
void RenderText(
ID2D1RenderTarget &deviceContext,
::IDWriteTextLayout &textLayout,
const facebook::react::AttributedString &attributedString,
const facebook::react::TextAttributes &textAttributes,
facebook::react::Point offset,
float pointScaleFactor,
winrt::Microsoft::ReactNative::Composition::implementation::Theme &theme) noexcept {
float offsetX = offset.x / pointScaleFactor;
float offsetY = offset.y / pointScaleFactor;
const auto dpi = pointScaleFactor * 96.0f;
float oldDpiX, oldDpiY;
deviceContext.GetDpi(&oldDpiX, &oldDpiY);
deviceContext.SetDpi(dpi, dpi);
// Create a solid color brush for the text. A more sophisticated application might want
// to cache and reuse a brush across all text elements instead, taking care to recreate
// it in the event of device removed.
winrt::com_ptr<ID2D1SolidColorBrush> brush;
if (textAttributes.foregroundColor) {
auto color = theme.D2DColor(*textAttributes.foregroundColor);
winrt::check_hresult(deviceContext.CreateSolidColorBrush(color, brush.put()));
} else {
winrt::check_hresult(deviceContext.CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black, 1.0f), brush.put()));
}
if (textAttributes.textDecorationLineType) {
DWRITE_TEXT_RANGE range = {0, std::numeric_limits<uint32_t>::max()};
if (*(textAttributes.textDecorationLineType) == facebook::react::TextDecorationLineType::Underline ||
*(textAttributes.textDecorationLineType) == facebook::react::TextDecorationLineType::UnderlineStrikethrough) {
textLayout.SetUnderline(true, range);
} else {
textLayout.SetUnderline(false, range);
}
}
if (textAttributes.textDecorationLineType) {
DWRITE_TEXT_RANGE range = {0, std::numeric_limits<uint32_t>::max()};
if (*(textAttributes.textDecorationLineType) == facebook::react::TextDecorationLineType::Strikethrough ||
*(textAttributes.textDecorationLineType) == facebook::react::TextDecorationLineType::UnderlineStrikethrough) {
textLayout.SetStrikethrough(true, range);
} else {
textLayout.SetStrikethrough(false, range);
}
}
// Create color effects for individual text fragments.
unsigned int position = 0;
unsigned int length = 0;
for (auto fragment : attributedString.getFragments()) {
length = static_cast<UINT32>(::Microsoft::Common::Unicode::Utf8ToUtf16Length(fragment.string));
DWRITE_TEXT_RANGE range = {position, length};
if (fragment.textAttributes.foregroundColor &&
(fragment.textAttributes.foregroundColor != textAttributes.foregroundColor) ||
!isnan(fragment.textAttributes.opacity)) {
winrt::com_ptr<ID2D1SolidColorBrush> fragmentBrush;
if (fragment.textAttributes.foregroundColor) {
auto color = theme.D2DColor(*fragment.textAttributes.foregroundColor);
winrt::check_hresult(deviceContext.CreateSolidColorBrush(color, fragmentBrush.put()));
} else {
winrt::check_hresult(
deviceContext.CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black, 1.0f), fragmentBrush.put()));
}
if (fragment.textAttributes.textDecorationLineType) {
if (*(fragment.textAttributes.textDecorationLineType) == facebook::react::TextDecorationLineType::Underline ||
*(fragment.textAttributes.textDecorationLineType) ==
facebook::react::TextDecorationLineType::UnderlineStrikethrough) {
textLayout.SetUnderline(true, range);
} else {
textLayout.SetUnderline(false, range);
}
}
if (fragment.textAttributes.textDecorationLineType) {
if (*(fragment.textAttributes.textDecorationLineType) ==
facebook::react::TextDecorationLineType::Strikethrough ||
*(fragment.textAttributes.textDecorationLineType) ==
facebook::react::TextDecorationLineType::UnderlineStrikethrough) {
textLayout.SetStrikethrough(true, range);
} else {
textLayout.SetStrikethrough(false, range);
}
}
if (!isnan(fragment.textAttributes.opacity)) {
fragmentBrush->SetOpacity(fragment.textAttributes.opacity);
}
textLayout.SetDrawingEffect(fragmentBrush.get(), range);
// DWrite doesn't handle background highlight colors, so we manually draw the background color for ranges
if (facebook::react::isColorMeaningful(fragment.textAttributes.backgroundColor)) {
UINT32 actualHitTestCount = 0;
if (range.length > 0) {
textLayout.HitTestTextRange(
range.startPosition,
range.length,
0, // x
0, // y
NULL,
0, // metrics count
&actualHitTestCount);
}
// Allocate enough room to return all hit-test metrics.
std::vector<DWRITE_HIT_TEST_METRICS> hitTestMetrics(actualHitTestCount);
if (range.length > 0) {
textLayout.HitTestTextRange(
range.startPosition,
range.length,
0, // x
0, // y
&hitTestMetrics[0],
static_cast<UINT32>(hitTestMetrics.size()),
&actualHitTestCount);
}
// Draw the selection ranges behind the text.
if (actualHitTestCount > 0) {
// Note that an ideal layout will return fractional values,
// so you may see slivers between the selection ranges due
// to the per-primitive anti-aliasing of the edges unless
// it is disabled (better for performance anyway).
auto oldAliasMode = deviceContext.GetAntialiasMode();
deviceContext.SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
winrt::com_ptr<ID2D1SolidColorBrush> textHighlightBrush;
winrt::check_hresult(deviceContext.CreateSolidColorBrush(
theme.D2DColor(*fragment.textAttributes.backgroundColor), textHighlightBrush.put()));
for (size_t i = 0; i < actualHitTestCount; ++i) {
const DWRITE_HIT_TEST_METRICS &htm = hitTestMetrics[i];
const D2D1_RECT_F rect = {
std::round(htm.left + offsetX),
std::round(htm.top + offsetY),
std::round(htm.left + htm.width + offsetX),
std::round(htm.top + htm.height + offsetY)};
deviceContext.FillRectangle(rect, textHighlightBrush.get());
}
deviceContext.SetAntialiasMode(oldAliasMode);
}
}
}
position += length;
}
// The composition drawing surface this text lands on is premultiplied-alpha and is transparent
// wherever the view has no opaque background; the surface is then composited by the visual tree,
// possibly under an opacity or a transform. ClearType subpixel antialiasing has no valid opaque
// background to filter against in that situation, and its per-channel coverage survives into the
// surface's color channels as dark/colored fringes along thin glyph stems. Grayscale antialiasing
// is the correct mode for transparent render targets, so ask for it explicitly rather than
// inheriting whatever the device context defaults to.
auto oldTextAntialiasMode = deviceContext.GetTextAntialiasMode();
deviceContext.SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
// Draw the line of text at the specified offset, which corresponds to the top-left
// corner of our drawing surface. Notice we don't call BeginDraw on the D2D device
// context; this has already been done for us by the composition API.
deviceContext.DrawTextLayout(
D2D1::Point2F(offsetX, offsetY), &textLayout, brush.get(), D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
deviceContext.SetTextAntialiasMode(oldTextAntialiasMode);
// restore dpi to old state
deviceContext.SetDpi(oldDpiX, oldDpiY);
}
} // namespace winrt::Microsoft::ReactNative::Composition