1/*
2 * Copyright 2019 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "src/core/SkStrikeSpec.h"
9
10#include "include/core/SkGraphics.h"
11#include "src/core/SkDraw.h"
12#include "src/core/SkFontPriv.h"
13#include "src/core/SkStrikeCache.h"
14#include "src/core/SkTLazy.h"
15
16#if SK_SUPPORT_GPU
17#include "src/gpu/text/GrSDFMaskFilter.h"
18#include "src/gpu/text/GrSDFTOptions.h"
19#include "src/gpu/text/GrStrikeCache.h"
20#endif
21
22SkStrikeSpec SkStrikeSpec::MakeMask(const SkFont& font, const SkPaint& paint,
23 const SkSurfaceProps& surfaceProps,
24 SkScalerContextFlags scalerContextFlags,
25 const SkMatrix& deviceMatrix) {
26 SkStrikeSpec storage;
27
28 storage.commonSetup(font, paint, surfaceProps, scalerContextFlags, deviceMatrix);
29
30 return storage;
31}
32
33SkStrikeSpec SkStrikeSpec::MakePath(const SkFont& font, const SkPaint& paint,
34 const SkSurfaceProps& surfaceProps,
35 SkScalerContextFlags scalerContextFlags) {
36 SkStrikeSpec storage;
37
38 // setup our std runPaint, in hopes of getting hits in the cache
39 SkPaint pathPaint{paint};
40 SkFont pathFont{font};
41
42 // The factor to get from the size stored in the strike to the size needed for
43 // the source.
44 storage.fStrikeToSourceRatio = pathFont.setupForAsPaths(&pathPaint);
45
46 // The sub-pixel position will always happen when transforming to the screen.
47 pathFont.setSubpixel(false);
48
49 storage.commonSetup(pathFont, pathPaint, surfaceProps, scalerContextFlags, SkMatrix::I());
50
51 return storage;
52}
53
54SkStrikeSpec SkStrikeSpec::MakeSourceFallback(
55 const SkFont& font,
56 const SkPaint& paint,
57 const SkSurfaceProps& surfaceProps,
58 SkScalerContextFlags scalerContextFlags,
59 SkScalar maxSourceGlyphDimension) {
60 SkStrikeSpec storage;
61
62 // Subtract 2 to account for the bilerp pad around the glyph
63 SkScalar maxAtlasDimension = SkStrikeCommon::kSkSideTooBigForAtlas - 2;
64
65 SkScalar runFontTextSize = font.getSize();
66 SkScalar fallbackTextSize = runFontTextSize;
67 if (maxSourceGlyphDimension > maxAtlasDimension) {
68 // Scale the text size down so the long side of all the glyphs will fit in the atlas.
69 fallbackTextSize = SkScalarFloorToScalar(
70 (maxAtlasDimension / maxSourceGlyphDimension) * runFontTextSize);
71 }
72
73 SkFont fallbackFont{font};
74 fallbackFont.setSize(fallbackTextSize);
75
76 // No sub-pixel needed. The transform to the screen will take care of sub-pixel positioning.
77 fallbackFont.setSubpixel(false);
78
79 // The scale factor to go from strike size to the source size for glyphs.
80 storage.fStrikeToSourceRatio = runFontTextSize / fallbackTextSize;
81
82 storage.commonSetup(fallbackFont, paint, surfaceProps, scalerContextFlags, SkMatrix::I());
83
84 return storage;
85}
86
87SkStrikeSpec SkStrikeSpec::MakeCanonicalized(const SkFont& font, const SkPaint* paint) {
88 SkStrikeSpec storage;
89
90 SkPaint canonicalizedPaint;
91 if (paint != nullptr) {
92 canonicalizedPaint = *paint;
93 }
94
95 const SkFont* canonicalizedFont = &font;
96 SkTLazy<SkFont> pathFont;
97 if (ShouldDrawAsPath(canonicalizedPaint, font, SkMatrix::I())) {
98 canonicalizedFont = pathFont.set(font);
99 storage.fStrikeToSourceRatio = pathFont->setupForAsPaths(nullptr);
100 canonicalizedPaint.reset();
101 }
102
103 storage.commonSetup(*canonicalizedFont,
104 canonicalizedPaint,
105 SkSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType),
106 kFakeGammaAndBoostContrast,
107 SkMatrix::I());
108 return storage;
109}
110
111SkStrikeSpec SkStrikeSpec::MakeWithNoDevice(const SkFont& font, const SkPaint* paint) {
112 SkStrikeSpec storage;
113
114 SkPaint setupPaint;
115 if (paint != nullptr) {
116 setupPaint = *paint;
117 }
118
119 storage.commonSetup(font,
120 setupPaint,
121 SkSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType),
122 kFakeGammaAndBoostContrast,
123 SkMatrix::I());
124
125 return storage;
126
127}
128
129SkStrikeSpec SkStrikeSpec::MakeDefault() {
130 SkFont defaultFont;
131 return MakeCanonicalized(defaultFont);
132}
133
134bool SkStrikeSpec::ShouldDrawAsPath(
135 const SkPaint& paint, const SkFont& font, const SkMatrix& viewMatrix) {
136
137 // hairline glyphs are fast enough so we don't need to cache them
138 if (SkPaint::kStroke_Style == paint.getStyle() && 0 == paint.getStrokeWidth()) {
139 return true;
140 }
141
142 // we don't cache perspective
143 if (viewMatrix.hasPerspective()) {
144 return true;
145 }
146
147 SkMatrix textMatrix = SkFontPriv::MakeTextMatrix(font);
148 textMatrix.postConcat(viewMatrix);
149
150 // we have a self-imposed maximum, just to limit memory-usage
151 SkScalar limit = std::min(SkGraphics::GetFontCachePointSizeLimit(), 1024);
152 SkScalar maxSizeSquared = limit * limit;
153
154 auto distance = [&textMatrix](int XIndex, int YIndex) {
155 return textMatrix[XIndex] * textMatrix[XIndex] + textMatrix[YIndex] * textMatrix[YIndex];
156 };
157
158 return distance(SkMatrix::kMScaleX, SkMatrix::kMSkewY ) > maxSizeSquared
159 || distance(SkMatrix::kMSkewX, SkMatrix::kMScaleY) > maxSizeSquared;
160}
161
162SkStrikeSpec SkStrikeSpec::MakePDFVector(const SkTypeface& typeface, int* size) {
163 SkFont font;
164 font.setHinting(SkFontHinting::kNone);
165 font.setEdging(SkFont::Edging::kAlias);
166 font.setTypeface(sk_ref_sp(&typeface));
167 int unitsPerEm = typeface.getUnitsPerEm();
168 if (unitsPerEm <= 0) {
169 unitsPerEm = 1024;
170 }
171 if (size) {
172 *size = unitsPerEm;
173 }
174 font.setSize((SkScalar)unitsPerEm);
175
176 SkStrikeSpec storage;
177 storage.commonSetup(font,
178 SkPaint(),
179 SkSurfaceProps(0, kUnknown_SkPixelGeometry),
180 kFakeGammaAndBoostContrast,
181 SkMatrix::I());
182
183 return storage;
184}
185
186#if SK_SUPPORT_GPU
187std::tuple<SkStrikeSpec, SkScalar, SkScalar>
188SkStrikeSpec::MakeSDFT(const SkFont& font, const SkPaint& paint,
189 const SkSurfaceProps& surfaceProps, const SkMatrix& deviceMatrix,
190 const GrSDFTOptions& options) {
191 SkStrikeSpec storage;
192
193 SkPaint dfPaint{paint};
194 dfPaint.setMaskFilter(GrSDFMaskFilter::Make());
195 SkFont dfFont = options.getSDFFont(font, deviceMatrix, &storage.fStrikeToSourceRatio);
196
197 // Fake-gamma and subpixel antialiasing are applied in the shader, so we ignore the
198 // passed-in scaler context flags. (It's only used when we fall-back to bitmap text).
199 SkScalerContextFlags flags = SkScalerContextFlags::kNone;
200
201 SkScalar minScale, maxScale;
202 std::tie(minScale, maxScale) = options.computeSDFMinMaxScale(font.getSize(), deviceMatrix);
203
204 storage.commonSetup(dfFont, dfPaint, surfaceProps, flags, SkMatrix::I());
205
206 return std::tie(storage, minScale, maxScale);
207}
208
209sk_sp<GrTextStrike> SkStrikeSpec::findOrCreateGrStrike(GrStrikeCache* cache) const {
210 return cache->findOrCreateStrike(*fAutoDescriptor.getDesc());
211}
212#endif
213
214void SkStrikeSpec::commonSetup(const SkFont& font, const SkPaint& paint,
215 const SkSurfaceProps& surfaceProps,
216 SkScalerContextFlags scalerContextFlags,
217 const SkMatrix& deviceMatrix) {
218 SkScalerContextEffects effects;
219
220 SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
221 font, paint, surfaceProps, scalerContextFlags, deviceMatrix,
222 &fAutoDescriptor, &effects);
223
224 fMaskFilter = sk_ref_sp(effects.fMaskFilter);
225 fPathEffect = sk_ref_sp(effects.fPathEffect);
226 fTypeface = font.refTypefaceOrDefault();
227}
228
229SkScopedStrikeForGPU SkStrikeSpec::findOrCreateScopedStrike(SkStrikeForGPUCacheInterface* cache) const {
230 SkScalerContextEffects effects{fPathEffect.get(), fMaskFilter.get()};
231 return cache->findOrCreateScopedStrike(*fAutoDescriptor.getDesc(), effects, *fTypeface);
232}
233
234sk_sp<SkStrike> SkStrikeSpec::findOrCreateStrike(SkStrikeCache* cache) const {
235 SkScalerContextEffects effects{fPathEffect.get(), fMaskFilter.get()};
236 return cache->findOrCreateStrike(*fAutoDescriptor.getDesc(), effects, *fTypeface);
237}
238
239SkBulkGlyphMetrics::SkBulkGlyphMetrics(const SkStrikeSpec& spec)
240 : fStrike{spec.findOrCreateStrike()} { }
241
242SkSpan<const SkGlyph*> SkBulkGlyphMetrics::glyphs(SkSpan<const SkGlyphID> glyphIDs) {
243 fGlyphs.reset(glyphIDs.size());
244 return fStrike->metrics(glyphIDs, fGlyphs.get());
245}
246
247const SkGlyph* SkBulkGlyphMetrics::glyph(SkGlyphID glyphID) {
248 return this->glyphs(SkSpan<const SkGlyphID>{&glyphID, 1})[0];
249}
250
251SkBulkGlyphMetricsAndPaths::SkBulkGlyphMetricsAndPaths(const SkStrikeSpec& spec)
252 : fStrike{spec.findOrCreateStrike()} { }
253
254SkBulkGlyphMetricsAndPaths::SkBulkGlyphMetricsAndPaths(sk_sp<SkStrike>&& strike)
255 : fStrike{std::move(strike)} { }
256
257SkSpan<const SkGlyph*> SkBulkGlyphMetricsAndPaths::glyphs(SkSpan<const SkGlyphID> glyphIDs) {
258 fGlyphs.reset(glyphIDs.size());
259 return fStrike->preparePaths(glyphIDs, fGlyphs.get());
260}
261
262const SkGlyph* SkBulkGlyphMetricsAndPaths::glyph(SkGlyphID glyphID) {
263 return this->glyphs(SkSpan<const SkGlyphID>{&glyphID, 1})[0];
264}
265
266void SkBulkGlyphMetricsAndPaths::findIntercepts(
267 const SkScalar* bounds, SkScalar scale, SkScalar xPos,
268 const SkGlyph* glyph, SkScalar* array, int* count) {
269 // TODO(herb): remove this abominable const_cast. Do the intercepts really need to be on the
270 // glyph?
271 fStrike->findIntercepts(bounds, scale, xPos, const_cast<SkGlyph*>(glyph), array, count);
272}
273
274SkBulkGlyphMetricsAndImages::SkBulkGlyphMetricsAndImages(const SkStrikeSpec& spec)
275 : fStrike{spec.findOrCreateStrike()} { }
276
277SkBulkGlyphMetricsAndImages::SkBulkGlyphMetricsAndImages(sk_sp<SkStrike>&& strike)
278 : fStrike{std::move(strike)} { }
279
280SkSpan<const SkGlyph*> SkBulkGlyphMetricsAndImages::glyphs(SkSpan<const SkPackedGlyphID> glyphIDs) {
281 fGlyphs.reset(glyphIDs.size());
282 return fStrike->prepareImages(glyphIDs, fGlyphs.get());
283}
284
285const SkGlyph* SkBulkGlyphMetricsAndImages::glyph(SkPackedGlyphID packedID) {
286 return this->glyphs(SkSpan<const SkPackedGlyphID>{&packedID, 1})[0];
287}
288
289const SkDescriptor& SkBulkGlyphMetricsAndImages::descriptor() const {
290 return fStrike->getDescriptor();
291}
292