blob: 8fdaf8e62d07529fad1546171f7c6519025214ca [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2011 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
mike@reedtribe.org3d1cb972012-08-13 00:52:07 +00007
8#include "gm.h"
Mike Klein33d20552017-03-22 13:47:51 -04009#include "sk_tool_utils.h"
Hal Canaryc640d0d2018-06-13 09:59:02 -040010
robertphillips@google.comb7061172013-09-06 14:16:12 +000011#include "SkBlurMask.h"
12#include "SkBlurMaskFilter.h"
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +000013#include "SkReadBuffer.h"
fmalitaeae6a912016-07-28 09:47:24 -070014#include "SkTextBlob.h"
Hal Canaryc640d0d2018-06-13 09:59:02 -040015#include "SkTo.h"
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +000016#include "SkWriteBuffer.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
18#include "Sk2DPathEffect.h"
19
caryclark0449bcf2016-02-09 13:25:45 -080020static SkPath create_underline(const SkTDArray<SkScalar>& intersections,
21 SkScalar last, SkScalar finalPos,
22 SkScalar uPos, SkScalar uWidth, SkScalar textSize) {
23 SkPath underline;
24 SkScalar end = last;
25 for (int index = 0; index < intersections.count(); index += 2) {
Kevin Lubick48ce5432019-01-04 08:54:32 -050026 SkScalar start = intersections[index] - uWidth;
caryclark0449bcf2016-02-09 13:25:45 -080027 end = intersections[index + 1] + uWidth;
28 if (start > last && last + textSize / 12 < start) {
29 underline.moveTo(last, uPos);
30 underline.lineTo(start, uPos);
31 }
32 last = end;
33 }
34 if (end < finalPos) {
35 underline.moveTo(end, uPos);
36 underline.lineTo(finalPos, uPos);
37 }
38 return underline;
39}
40
fmalitaeae6a912016-07-28 09:47:24 -070041namespace {
42
Hal Canarydf2d27e2019-01-08 09:38:02 -050043sk_sp<SkTextBlob> MakeFancyBlob(const SkPaint& paint, const SkFont& font, const char* text) {
fmalitaeae6a912016-07-28 09:47:24 -070044 const size_t textLen = strlen(text);
Mike Reed158df432018-12-06 22:41:30 -050045 const int glyphCount = font.countText(text, textLen, kUTF8_SkTextEncoding);
fmalitaeae6a912016-07-28 09:47:24 -070046 SkAutoTArray<SkGlyphID> glyphs(glyphCount);
Mike Reed158df432018-12-06 22:41:30 -050047 font.textToGlyphs(text, textLen, kUTF8_SkTextEncoding, glyphs.get(), glyphCount);
fmalitaeae6a912016-07-28 09:47:24 -070048 SkAutoTArray<SkScalar> widths(glyphCount);
Mike Reed158df432018-12-06 22:41:30 -050049 font.getWidths(glyphs.get(), glyphCount, widths.get());
fmalitaeae6a912016-07-28 09:47:24 -070050
51 SkTextBlobBuilder blobBuilder;
52 int glyphIndex = 0;
53 SkScalar advance = 0;
54
55 // Default-positioned run.
56 {
57 const int defaultRunLen = glyphCount / 3;
Mike Reed158df432018-12-06 22:41:30 -050058 const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRun(font,
fmalitaeae6a912016-07-28 09:47:24 -070059 defaultRunLen,
60 advance, 0);
61 memcpy(buf.glyphs, glyphs.get(), SkTo<uint32_t>(defaultRunLen) * sizeof(SkGlyphID));
62
63 for (int i = 0; i < defaultRunLen; ++i) {
64 advance += widths[glyphIndex++];
65 }
66 }
67
68 // Horizontal-positioned run.
69 {
70 const int horizontalRunLen = glyphCount / 3;
Mike Reed158df432018-12-06 22:41:30 -050071 const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRunPosH(font,
fmalitaeae6a912016-07-28 09:47:24 -070072 horizontalRunLen,
73 0);
74 memcpy(buf.glyphs, glyphs.get() + glyphIndex,
75 SkTo<uint32_t>(horizontalRunLen) * sizeof(SkGlyphID));
76 for (int i = 0; i < horizontalRunLen; ++i) {
77 buf.pos[i] = advance;
78 advance += widths[glyphIndex++];
79 }
80 }
81
82 // Full-positioned run.
83 {
84 const int fullRunLen = glyphCount - glyphIndex;
Mike Reed158df432018-12-06 22:41:30 -050085 const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRunPos(font, fullRunLen);
fmalitaeae6a912016-07-28 09:47:24 -070086 memcpy(buf.glyphs, glyphs.get() + glyphIndex,
87 SkTo<uint32_t>(fullRunLen) * sizeof(SkGlyphID));
88 for (int i = 0; i < fullRunLen; ++i) {
89 buf.pos[i * 2 + 0] = advance; // x offset
90 buf.pos[i * 2 + 1] = 0; // y offset
91 advance += widths[glyphIndex++];
92 }
93 }
94
fmalita37283c22016-09-13 10:00:23 -070095 return blobBuilder.make();
fmalitaeae6a912016-07-28 09:47:24 -070096}
97
98} // anonymous ns
99
100DEF_SIMPLE_GM(fancyblobunderline, canvas, 1480, 1380) {
101 SkPaint paint;
102 paint.setAntiAlias(true);
103 const char* fam[] = { "sans-serif", "serif", "monospace" };
104 const char test[] = "aAjJgGyY_|{-(~[,]qQ}pP}zZ";
105 const SkPoint blobOffset = { 10, 80 };
106
107 for (size_t font = 0; font < SK_ARRAY_COUNT(fam); ++font) {
fmalitaeae6a912016-07-28 09:47:24 -0700108 for (SkScalar textSize = 100; textSize > 10; textSize -= 20) {
Hal Canarydf2d27e2019-01-08 09:38:02 -0500109 SkFont skFont(
110 sk_tool_utils::create_portable_typeface(fam[font], SkFontStyle()), textSize);
fmalitaeae6a912016-07-28 09:47:24 -0700111 const SkScalar uWidth = textSize / 15;
112 paint.setStrokeWidth(uWidth);
113 paint.setStyle(SkPaint::kFill_Style);
114
Hal Canarydf2d27e2019-01-08 09:38:02 -0500115 sk_sp<SkTextBlob> blob = MakeFancyBlob(paint, skFont, test);
fmalita37283c22016-09-13 10:00:23 -0700116 canvas->drawTextBlob(blob, blobOffset.x(), blobOffset.y(), paint);
fmalitaeae6a912016-07-28 09:47:24 -0700117
118 const SkScalar uPos = uWidth;
119 const SkScalar bounds[2] = { uPos - uWidth / 2, uPos + uWidth / 2 };
Mike Reedae0d8602018-12-10 22:02:17 -0500120 const int interceptCount = blob->getIntercepts(bounds, nullptr, &paint);
fmalitaeae6a912016-07-28 09:47:24 -0700121 SkASSERT(!(interceptCount % 2));
122
123 SkTDArray<SkScalar> intercepts;
124 intercepts.setCount(interceptCount);
Mike Reedae0d8602018-12-10 22:02:17 -0500125 blob->getIntercepts(bounds, intercepts.begin(), &paint);
fmalitaeae6a912016-07-28 09:47:24 -0700126
127 const SkScalar start = blob->bounds().left();
128 const SkScalar end = blob->bounds().right();
129 SkPath underline = create_underline(intercepts, start, end, uPos, uWidth, textSize);
130 underline.offset(blobOffset.x(), blobOffset.y());
131 paint.setStyle(SkPaint::kStroke_Style);
132 canvas->drawPath(underline, paint);
133
134 canvas->translate(0, textSize * 1.3f);
135 }
136
137 canvas->translate(0, 60);
138 }
139}
140
Mike Reed5f6e20d2018-12-12 13:01:42 -0500141///////////////////////////////////////////////////////////////////////////////////////////////////
142
143static sk_sp<SkTextBlob> make_text(const SkFont& font, const SkGlyphID glyphs[], int count) {
144 return SkTextBlob::MakeFromText(glyphs, count * sizeof(SkGlyphID), font,
145 kGlyphID_SkTextEncoding);
146}
147
148static sk_sp<SkTextBlob> make_posh(const SkFont& font, const SkGlyphID glyphs[], int count,
149 SkScalar spacing) {
150 SkAutoTArray<SkScalar> xpos(count);
151 font.getXPos(glyphs, count, xpos.get());
152 for (int i = 1; i < count; ++i) {
153 xpos[i] += spacing * i;
154 }
155 return SkTextBlob::MakeFromPosTextH(glyphs, count * sizeof(SkGlyphID), xpos.get(), 0, font,
156 kGlyphID_SkTextEncoding);
157}
158
159static sk_sp<SkTextBlob> make_pos(const SkFont& font, const SkGlyphID glyphs[], int count,
160 SkScalar spacing) {
161 SkAutoTArray<SkPoint> pos(count);
162 font.getPos(glyphs, count, pos.get());
163 for (int i = 1; i < count; ++i) {
164 pos[i].fX += spacing * i;
165 }
166 return SkTextBlob::MakeFromPosText(glyphs, count * sizeof(SkGlyphID), pos.get(), font,
167 kGlyphID_SkTextEncoding);
168}
169
170// widen the gaps with a margin (on each side of the gap), elimnating segments that go away
171static int trim_with_halo(SkScalar intervals[], int count, SkScalar margin) {
172 SkASSERT(count > 0 && (count & 1) == 0);
173
174 int n = count;
175 SkScalar* stop = intervals + count;
176 *intervals++ -= margin;
177 while (intervals < stop - 1) {
178 intervals[0] += margin;
179 intervals[1] -= margin;
180 if (intervals[0] >= intervals[1]) { // went away
181 int remaining = stop - intervals - 2;
182 SkASSERT(remaining >= 0 && (remaining & 1) == 1);
183 if (remaining > 0) {
184 memmove(intervals, intervals + 2, remaining * sizeof(SkScalar));
185 }
186 stop -= 2;
187 n -= 2;
188 } else {
189 intervals += 2;
190 }
191 }
192 *intervals += margin;
193 return n;
194}
195
196static void draw_blob_adorned(SkCanvas* canvas, sk_sp<SkTextBlob> blob) {
197 SkPaint paint;
198
199 canvas->drawTextBlob(blob.get(), 0, 0, paint);
200
201 const SkScalar yminmax[] = { 8, 16 };
202 int count = blob->getIntercepts(yminmax, nullptr);
203 if (!count) {
204 return;
205 }
206
207 SkAutoTArray<SkScalar> intervals(count);
208 blob->getIntercepts(yminmax, intervals.get());
209 count = trim_with_halo(intervals.get(), count, SkScalarHalf(yminmax[1] - yminmax[0]) * 1.5f);
210 SkASSERT(count >= 2);
211
212 const SkScalar y = SkScalarAve(yminmax[0], yminmax[1]);
213 SkScalar end = 900;
214 SkPath path;
215 path.moveTo({0, y});
216 for (int i = 0; i < count; i += 2) {
217 path.lineTo(intervals[i], y).moveTo(intervals[i+1], y);
218 }
219 if (intervals[count - 1] < end) {
220 path.lineTo(end, y);
221 }
222
223 paint.setAntiAlias(true);
224 paint.setStyle(SkPaint::kStroke_Style);
225 paint.setStrokeWidth(yminmax[1] - yminmax[0]);
226 canvas->drawPath(path, paint);
227}
228
229DEF_SIMPLE_GM(textblob_intercepts, canvas, 940, 800) {
230 const char text[] = "Hyjay {worlp}.";
231 const size_t length = strlen(text);
232 SkFont font;
233 font.setTypeface(sk_tool_utils::create_portable_typeface());
234 font.setSize(100);
235 font.setEdging(SkFont::Edging::kAntiAlias);
236 const int count = font.countText(text, length, kUTF8_SkTextEncoding);
237 SkAutoTArray<SkGlyphID> glyphs(count);
238 font.textToGlyphs(text, length, kUTF8_SkTextEncoding, glyphs.get(), count);
239
240 auto b0 = make_text(font, glyphs.get(), count);
241
242 canvas->translate(20, 120);
243 draw_blob_adorned(canvas, b0);
244 for (SkScalar spacing = 0; spacing < 30; spacing += 20) {
245 auto b1 = make_posh(font, glyphs.get(), count, spacing);
246 auto b2 = make_pos( font, glyphs.get(), count, spacing);
247 canvas->translate(0, 150);
248 draw_blob_adorned(canvas, b1);
249 canvas->translate(0, 150);
250 draw_blob_adorned(canvas, b2);
251 }
252}