blob: ed11db183e33c7f3b719afb622a80a933a546616 [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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "gm/gm.h"
Ben Wagner7fde8e12019-05-01 17:28:53 -04009#include "include/core/SkCanvas.h"
10#include "include/core/SkFont.h"
11#include "include/core/SkFontStyle.h"
12#include "include/core/SkFontTypes.h"
13#include "include/core/SkPaint.h"
14#include "include/core/SkPath.h"
15#include "include/core/SkPoint.h"
16#include "include/core/SkRect.h"
17#include "include/core/SkRefCnt.h"
18#include "include/core/SkScalar.h"
19#include "include/core/SkTextBlob.h"
20#include "include/core/SkTypeface.h"
21#include "include/core/SkTypes.h"
22#include "include/private/SkTDArray.h"
23#include "include/private/SkTemplates.h"
24#include "include/private/SkTo.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050025#include "tools/ToolUtils.h"
Hal Canaryc640d0d2018-06-13 09:59:02 -040026
Ben Wagner7fde8e12019-05-01 17:28:53 -040027#include <string.h>
reed@android.com8a1c16f2008-12-17 15:59:43 +000028
caryclark0449bcf2016-02-09 13:25:45 -080029static SkPath create_underline(const SkTDArray<SkScalar>& intersections,
30 SkScalar last, SkScalar finalPos,
31 SkScalar uPos, SkScalar uWidth, SkScalar textSize) {
32 SkPath underline;
33 SkScalar end = last;
34 for (int index = 0; index < intersections.count(); index += 2) {
Kevin Lubick48ce5432019-01-04 08:54:32 -050035 SkScalar start = intersections[index] - uWidth;
caryclark0449bcf2016-02-09 13:25:45 -080036 end = intersections[index + 1] + uWidth;
37 if (start > last && last + textSize / 12 < start) {
38 underline.moveTo(last, uPos);
39 underline.lineTo(start, uPos);
40 }
41 last = end;
42 }
43 if (end < finalPos) {
44 underline.moveTo(end, uPos);
45 underline.lineTo(finalPos, uPos);
46 }
47 return underline;
48}
49
fmalitaeae6a912016-07-28 09:47:24 -070050namespace {
51
Hal Canarydf2d27e2019-01-08 09:38:02 -050052sk_sp<SkTextBlob> MakeFancyBlob(const SkPaint& paint, const SkFont& font, const char* text) {
fmalitaeae6a912016-07-28 09:47:24 -070053 const size_t textLen = strlen(text);
Ben Wagner51e15a62019-05-07 15:38:46 -040054 const int glyphCount = font.countText(text, textLen, SkTextEncoding::kUTF8);
fmalitaeae6a912016-07-28 09:47:24 -070055 SkAutoTArray<SkGlyphID> glyphs(glyphCount);
Ben Wagner51e15a62019-05-07 15:38:46 -040056 font.textToGlyphs(text, textLen, SkTextEncoding::kUTF8, glyphs.get(), glyphCount);
fmalitaeae6a912016-07-28 09:47:24 -070057 SkAutoTArray<SkScalar> widths(glyphCount);
Mike Reed158df432018-12-06 22:41:30 -050058 font.getWidths(glyphs.get(), glyphCount, widths.get());
fmalitaeae6a912016-07-28 09:47:24 -070059
60 SkTextBlobBuilder blobBuilder;
61 int glyphIndex = 0;
62 SkScalar advance = 0;
63
64 // Default-positioned run.
65 {
66 const int defaultRunLen = glyphCount / 3;
Mike Reed158df432018-12-06 22:41:30 -050067 const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRun(font,
fmalitaeae6a912016-07-28 09:47:24 -070068 defaultRunLen,
69 advance, 0);
70 memcpy(buf.glyphs, glyphs.get(), SkTo<uint32_t>(defaultRunLen) * sizeof(SkGlyphID));
71
72 for (int i = 0; i < defaultRunLen; ++i) {
73 advance += widths[glyphIndex++];
74 }
75 }
76
77 // Horizontal-positioned run.
78 {
79 const int horizontalRunLen = glyphCount / 3;
Mike Reed158df432018-12-06 22:41:30 -050080 const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRunPosH(font,
fmalitaeae6a912016-07-28 09:47:24 -070081 horizontalRunLen,
82 0);
83 memcpy(buf.glyphs, glyphs.get() + glyphIndex,
84 SkTo<uint32_t>(horizontalRunLen) * sizeof(SkGlyphID));
85 for (int i = 0; i < horizontalRunLen; ++i) {
86 buf.pos[i] = advance;
87 advance += widths[glyphIndex++];
88 }
89 }
90
91 // Full-positioned run.
92 {
93 const int fullRunLen = glyphCount - glyphIndex;
Mike Reed158df432018-12-06 22:41:30 -050094 const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRunPos(font, fullRunLen);
fmalitaeae6a912016-07-28 09:47:24 -070095 memcpy(buf.glyphs, glyphs.get() + glyphIndex,
96 SkTo<uint32_t>(fullRunLen) * sizeof(SkGlyphID));
97 for (int i = 0; i < fullRunLen; ++i) {
98 buf.pos[i * 2 + 0] = advance; // x offset
99 buf.pos[i * 2 + 1] = 0; // y offset
100 advance += widths[glyphIndex++];
101 }
102 }
103
fmalita37283c22016-09-13 10:00:23 -0700104 return blobBuilder.make();
fmalitaeae6a912016-07-28 09:47:24 -0700105}
106
107} // anonymous ns
108
109DEF_SIMPLE_GM(fancyblobunderline, canvas, 1480, 1380) {
110 SkPaint paint;
111 paint.setAntiAlias(true);
112 const char* fam[] = { "sans-serif", "serif", "monospace" };
113 const char test[] = "aAjJgGyY_|{-(~[,]qQ}pP}zZ";
114 const SkPoint blobOffset = { 10, 80 };
115
116 for (size_t font = 0; font < SK_ARRAY_COUNT(fam); ++font) {
fmalitaeae6a912016-07-28 09:47:24 -0700117 for (SkScalar textSize = 100; textSize > 10; textSize -= 20) {
Mike Kleinea3f0142019-03-20 11:12:10 -0500118 SkFont skFont(ToolUtils::create_portable_typeface(fam[font], SkFontStyle()), textSize);
fmalitaeae6a912016-07-28 09:47:24 -0700119 const SkScalar uWidth = textSize / 15;
120 paint.setStrokeWidth(uWidth);
121 paint.setStyle(SkPaint::kFill_Style);
122
Hal Canarydf2d27e2019-01-08 09:38:02 -0500123 sk_sp<SkTextBlob> blob = MakeFancyBlob(paint, skFont, test);
fmalita37283c22016-09-13 10:00:23 -0700124 canvas->drawTextBlob(blob, blobOffset.x(), blobOffset.y(), paint);
fmalitaeae6a912016-07-28 09:47:24 -0700125
126 const SkScalar uPos = uWidth;
127 const SkScalar bounds[2] = { uPos - uWidth / 2, uPos + uWidth / 2 };
Mike Reedae0d8602018-12-10 22:02:17 -0500128 const int interceptCount = blob->getIntercepts(bounds, nullptr, &paint);
fmalitaeae6a912016-07-28 09:47:24 -0700129 SkASSERT(!(interceptCount % 2));
130
131 SkTDArray<SkScalar> intercepts;
132 intercepts.setCount(interceptCount);
Mike Reedae0d8602018-12-10 22:02:17 -0500133 blob->getIntercepts(bounds, intercepts.begin(), &paint);
fmalitaeae6a912016-07-28 09:47:24 -0700134
135 const SkScalar start = blob->bounds().left();
136 const SkScalar end = blob->bounds().right();
137 SkPath underline = create_underline(intercepts, start, end, uPos, uWidth, textSize);
138 underline.offset(blobOffset.x(), blobOffset.y());
139 paint.setStyle(SkPaint::kStroke_Style);
140 canvas->drawPath(underline, paint);
141
142 canvas->translate(0, textSize * 1.3f);
143 }
144
145 canvas->translate(0, 60);
146 }
147}
148
Mike Reed5f6e20d2018-12-12 13:01:42 -0500149///////////////////////////////////////////////////////////////////////////////////////////////////
150
151static sk_sp<SkTextBlob> make_text(const SkFont& font, const SkGlyphID glyphs[], int count) {
152 return SkTextBlob::MakeFromText(glyphs, count * sizeof(SkGlyphID), font,
Ben Wagner51e15a62019-05-07 15:38:46 -0400153 SkTextEncoding::kGlyphID);
Mike Reed5f6e20d2018-12-12 13:01:42 -0500154}
155
156static sk_sp<SkTextBlob> make_posh(const SkFont& font, const SkGlyphID glyphs[], int count,
157 SkScalar spacing) {
158 SkAutoTArray<SkScalar> xpos(count);
159 font.getXPos(glyphs, count, xpos.get());
160 for (int i = 1; i < count; ++i) {
161 xpos[i] += spacing * i;
162 }
163 return SkTextBlob::MakeFromPosTextH(glyphs, count * sizeof(SkGlyphID), xpos.get(), 0, font,
Ben Wagner51e15a62019-05-07 15:38:46 -0400164 SkTextEncoding::kGlyphID);
Mike Reed5f6e20d2018-12-12 13:01:42 -0500165}
166
167static sk_sp<SkTextBlob> make_pos(const SkFont& font, const SkGlyphID glyphs[], int count,
168 SkScalar spacing) {
169 SkAutoTArray<SkPoint> pos(count);
170 font.getPos(glyphs, count, pos.get());
171 for (int i = 1; i < count; ++i) {
172 pos[i].fX += spacing * i;
173 }
174 return SkTextBlob::MakeFromPosText(glyphs, count * sizeof(SkGlyphID), pos.get(), font,
Ben Wagner51e15a62019-05-07 15:38:46 -0400175 SkTextEncoding::kGlyphID);
Mike Reed5f6e20d2018-12-12 13:01:42 -0500176}
177
178// widen the gaps with a margin (on each side of the gap), elimnating segments that go away
179static int trim_with_halo(SkScalar intervals[], int count, SkScalar margin) {
180 SkASSERT(count > 0 && (count & 1) == 0);
181
182 int n = count;
183 SkScalar* stop = intervals + count;
184 *intervals++ -= margin;
185 while (intervals < stop - 1) {
186 intervals[0] += margin;
187 intervals[1] -= margin;
188 if (intervals[0] >= intervals[1]) { // went away
189 int remaining = stop - intervals - 2;
190 SkASSERT(remaining >= 0 && (remaining & 1) == 1);
191 if (remaining > 0) {
192 memmove(intervals, intervals + 2, remaining * sizeof(SkScalar));
193 }
194 stop -= 2;
195 n -= 2;
196 } else {
197 intervals += 2;
198 }
199 }
200 *intervals += margin;
201 return n;
202}
203
204static void draw_blob_adorned(SkCanvas* canvas, sk_sp<SkTextBlob> blob) {
205 SkPaint paint;
206
207 canvas->drawTextBlob(blob.get(), 0, 0, paint);
208
209 const SkScalar yminmax[] = { 8, 16 };
210 int count = blob->getIntercepts(yminmax, nullptr);
211 if (!count) {
212 return;
213 }
214
215 SkAutoTArray<SkScalar> intervals(count);
216 blob->getIntercepts(yminmax, intervals.get());
217 count = trim_with_halo(intervals.get(), count, SkScalarHalf(yminmax[1] - yminmax[0]) * 1.5f);
218 SkASSERT(count >= 2);
219
220 const SkScalar y = SkScalarAve(yminmax[0], yminmax[1]);
221 SkScalar end = 900;
222 SkPath path;
223 path.moveTo({0, y});
224 for (int i = 0; i < count; i += 2) {
225 path.lineTo(intervals[i], y).moveTo(intervals[i+1], y);
226 }
227 if (intervals[count - 1] < end) {
228 path.lineTo(end, y);
229 }
230
231 paint.setAntiAlias(true);
232 paint.setStyle(SkPaint::kStroke_Style);
233 paint.setStrokeWidth(yminmax[1] - yminmax[0]);
234 canvas->drawPath(path, paint);
235}
236
237DEF_SIMPLE_GM(textblob_intercepts, canvas, 940, 800) {
238 const char text[] = "Hyjay {worlp}.";
239 const size_t length = strlen(text);
240 SkFont font;
Mike Kleinea3f0142019-03-20 11:12:10 -0500241 font.setTypeface(ToolUtils::create_portable_typeface());
Mike Reed5f6e20d2018-12-12 13:01:42 -0500242 font.setSize(100);
243 font.setEdging(SkFont::Edging::kAntiAlias);
Ben Wagner51e15a62019-05-07 15:38:46 -0400244 const int count = font.countText(text, length, SkTextEncoding::kUTF8);
Mike Reed5f6e20d2018-12-12 13:01:42 -0500245 SkAutoTArray<SkGlyphID> glyphs(count);
Ben Wagner51e15a62019-05-07 15:38:46 -0400246 font.textToGlyphs(text, length, SkTextEncoding::kUTF8, glyphs.get(), count);
Mike Reed5f6e20d2018-12-12 13:01:42 -0500247
248 auto b0 = make_text(font, glyphs.get(), count);
249
250 canvas->translate(20, 120);
251 draw_blob_adorned(canvas, b0);
252 for (SkScalar spacing = 0; spacing < 30; spacing += 20) {
253 auto b1 = make_posh(font, glyphs.get(), count, spacing);
254 auto b2 = make_pos( font, glyphs.get(), count, spacing);
255 canvas->translate(0, 150);
256 draw_blob_adorned(canvas, b1);
257 canvas->translate(0, 150);
258 draw_blob_adorned(canvas, b2);
259 }
260}