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/GrStrikeCache.h"
18#include "src/gpu/text/GrTextContext.h"
19#endif
20
21SkStrikeSpec SkStrikeSpec::MakeMask(const SkFont& font, const SkPaint& paint,
22 const SkSurfaceProps& surfaceProps,
23 SkScalerContextFlags scalerContextFlags,
24 const SkMatrix& deviceMatrix) {
25 SkStrikeSpec storage;
26
27 storage.commonSetup(font, paint, surfaceProps, scalerContextFlags, deviceMatrix);
28
29 return storage;
30}
31
32SkStrikeSpec SkStrikeSpec::MakePath(const SkFont& font, const SkPaint& paint,
33 const SkSurfaceProps& surfaceProps,
34 SkScalerContextFlags scalerContextFlags) {
35 SkStrikeSpec storage;
36
37 // setup our std runPaint, in hopes of getting hits in the cache
38 SkPaint pathPaint{paint};
39 SkFont pathFont{font};
40
41 // The factor to get from the size stored in the strike to the size needed for
42 // the source.
43 storage.fStrikeToSourceRatio = pathFont.setupForAsPaths(&pathPaint);
44
45 // The sub-pixel position will always happen when transforming to the screen.
46 pathFont.setSubpixel(false);
47
48 storage.commonSetup(pathFont, pathPaint, surfaceProps, scalerContextFlags, SkMatrix::I());
49
50 return storage;
51}
52
53SkStrikeSpec SkStrikeSpec::MakeSourceFallback(
54 const SkFont& font,
55 const SkPaint& paint,
56 const SkSurfaceProps& surfaceProps,
57 SkScalerContextFlags scalerContextFlags,
58 SkScalar maxSourceGlyphDimension) {
59 SkStrikeSpec storage;
60
61 // Subtract 2 to account for the bilerp pad around the glyph
62 SkScalar maxAtlasDimension = SkStrikeCommon::kSkSideTooBigForAtlas - 2;
63
64 SkScalar runFontTextSize = font.getSize();
65 SkScalar fallbackTextSize = runFontTextSize;
66 if (maxSourceGlyphDimension > maxAtlasDimension) {
67 // Scale the text size down so the long side of all the glyphs will fit in the atlas.
68 fallbackTextSize = SkScalarFloorToScalar(
69 (maxAtlasDimension / maxSourceGlyphDimension) * runFontTextSize);
70 }
71
72 SkFont fallbackFont{font};
73 fallbackFont.setSize(fallbackTextSize);
74
75 // No sub-pixel needed. The transform to the screen will take care of sub-pixel positioning.
76 fallbackFont.setSubpixel(false);
77
78 // The scale factor to go from strike size to the source size for glyphs.
79 storage.fStrikeToSourceRatio = runFontTextSize / fallbackTextSize;
80
81 storage.commonSetup(fallbackFont, paint, surfaceProps, scalerContextFlags, SkMatrix::I());
82
83 return storage;
84}
85
86SkStrikeSpec SkStrikeSpec::MakeCanonicalized(const SkFont& font, const SkPaint* paint) {
87 SkStrikeSpec storage;
88
89 SkPaint canonicalizedPaint;
90 if (paint != nullptr) {
91 canonicalizedPaint = *paint;
92 }
93
94 const SkFont* canonicalizedFont = &font;
95 SkTLazy<SkFont> pathFont;
96 if (ShouldDrawAsPath(canonicalizedPaint, font, SkMatrix::I())) {
97 canonicalizedFont = pathFont.set(font);
98 storage.fStrikeToSourceRatio = pathFont->setupForAsPaths(nullptr);
99 canonicalizedPaint.reset();
100 }
101
102 storage.commonSetup(*canonicalizedFont,
103 canonicalizedPaint,
104 SkSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType),
105 kFakeGammaAndBoostContrast,
106 SkMatrix::I());
107 return storage;
108}
109
110SkStrikeSpec SkStrikeSpec::MakeWithNoDevice(const SkFont& font, const SkPaint* paint) {
111 SkStrikeSpec storage;
112
113 SkPaint setupPaint;
114 if (paint != nullptr) {
115 setupPaint = *paint;
116 }
117
118 storage.commonSetup(font,
119 setupPaint,
120 SkSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType),
121 kFakeGammaAndBoostContrast,
122 SkMatrix::I());
123
124 return storage;
125
126}
127
128SkStrikeSpec SkStrikeSpec::MakeDefault() {
129 SkFont defaultFont;
130 return MakeCanonicalized(defaultFont);
131}
132
133bool SkStrikeSpec::ShouldDrawAsPath(
134 const SkPaint& paint, const SkFont& font, const SkMatrix& viewMatrix) {
135
136 // hairline glyphs are fast enough so we don't need to cache them
137 if (SkPaint::kStroke_Style == paint.getStyle() && 0 == paint.getStrokeWidth()) {
138 return true;
139 }
140
141 // we don't cache perspective
142 if (viewMatrix.hasPerspective()) {
143 return true;
144 }
145
146 SkMatrix textMatrix = SkFontPriv::MakeTextMatrix(font);
147 textMatrix.postConcat(viewMatrix);
148
149 // we have a self-imposed maximum, just for memory-usage sanity
150 SkScalar limit = std::min(SkGraphics::GetFontCachePointSizeLimit(), 1024);
151 SkScalar maxSizeSquared = limit * limit;
152
153 auto distance = [&textMatrix](int XIndex, int YIndex) {
154 return textMatrix[XIndex] * textMatrix[XIndex] + textMatrix[YIndex] * textMatrix[YIndex];
155 };
156
157 return distance(SkMatrix::kMScaleX, SkMatrix::kMSkewY ) > maxSizeSquared
158 || distance(SkMatrix::kMSkewX, SkMatrix::kMScaleY) > maxSizeSquared;
159}
160
161SkStrikeSpec SkStrikeSpec::MakePDFVector(const SkTypeface& typeface, int* size) {
162 SkFont font;
163 font.setHinting(SkFontHinting::kNone);
164 font.setEdging(SkFont::Edging::kAlias);
165 font.setTypeface(sk_ref_sp(&typeface));
166 int unitsPerEm = typeface.getUnitsPerEm();
167 if (unitsPerEm <= 0) {
168 unitsPerEm = 1024;
169 }
170 if (size) {
171 *size = unitsPerEm;
172 }
173 font.setSize((SkScalar)unitsPerEm);
174
175 SkStrikeSpec storage;
176 storage.commonSetup(font,
177 SkPaint(),
178 SkSurfaceProps(0, kUnknown_SkPixelGeometry),
179 kFakeGammaAndBoostContrast,
180 SkMatrix::I());
181
182 return storage;
183}
184
185#if SK_SUPPORT_GPU
186std::tuple<SkStrikeSpec, SkScalar, SkScalar>
187SkStrikeSpec::MakeSDFT(const SkFont& font, const SkPaint& paint,
188 const SkSurfaceProps& surfaceProps, const SkMatrix& deviceMatrix,
189 const GrTextContext::Options& options) {
190 SkStrikeSpec storage;
191
192 SkPaint dfPaint = GrTextContext::InitDistanceFieldPaint(paint);
193 SkFont dfFont = GrTextContext::InitDistanceFieldFont(
194 font, deviceMatrix, options, &storage.fStrikeToSourceRatio);
195
196 // Fake-gamma and subpixel antialiasing are applied in the shader, so we ignore the
197 // passed-in scaler context flags. (It's only used when we fall-back to bitmap text).
198 SkScalerContextFlags flags = SkScalerContextFlags::kNone;
199
200 SkScalar minScale, maxScale;
201 std::tie(minScale, maxScale) = GrTextContext::InitDistanceFieldMinMaxScale(
202 font.getSize(), deviceMatrix, options);
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->getStrike(*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