1 /*
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 */
7
8 #include "gm/gm.h"
9 #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"
25 #include "tools/ToolUtils.h"
26
27 #include <string.h>
28
create_underline(const SkTDArray<SkScalar> & intersections,SkScalar last,SkScalar finalPos,SkScalar uPos,SkScalar uWidth,SkScalar textSize)29 static 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) {
35 SkScalar start = intersections[index] - uWidth;
36 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
50 namespace {
51
MakeFancyBlob(const SkPaint & paint,const SkFont & font,const char * text)52 sk_sp<SkTextBlob> MakeFancyBlob(const SkPaint& paint, const SkFont& font, const char* text) {
53 const size_t textLen = strlen(text);
54 const int glyphCount = font.countText(text, textLen, SkTextEncoding::kUTF8);
55 SkAutoTArray<SkGlyphID> glyphs(glyphCount);
56 font.textToGlyphs(text, textLen, SkTextEncoding::kUTF8, glyphs.get(), glyphCount);
57 SkAutoTArray<SkScalar> widths(glyphCount);
58 font.getWidths(glyphs.get(), glyphCount, widths.get());
59
60 SkTextBlobBuilder blobBuilder;
61 int glyphIndex = 0;
62 SkScalar advance = 0;
63
64 // Default-positioned run.
65 {
66 const int defaultRunLen = glyphCount / 3;
67 const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRun(font,
68 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;
80 const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRunPosH(font,
81 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;
94 const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRunPos(font, fullRunLen);
95 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
104 return blobBuilder.make();
105 }
106
107 } // anonymous ns
108
109 DEF_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) {
117 for (SkScalar textSize = 100; textSize > 10; textSize -= 20) {
118 SkFont skFont(ToolUtils::create_portable_typeface(fam[font], SkFontStyle()), textSize);
119 const SkScalar uWidth = textSize / 15;
120 paint.setStrokeWidth(uWidth);
121 paint.setStyle(SkPaint::kFill_Style);
122
123 sk_sp<SkTextBlob> blob = MakeFancyBlob(paint, skFont, test);
124 canvas->drawTextBlob(blob, blobOffset.x(), blobOffset.y(), paint);
125
126 const SkScalar uPos = uWidth;
127 const SkScalar bounds[2] = { uPos - uWidth / 2, uPos + uWidth / 2 };
128 const int interceptCount = blob->getIntercepts(bounds, nullptr, &paint);
129 SkASSERT(!(interceptCount % 2));
130
131 SkTDArray<SkScalar> intercepts;
132 intercepts.setCount(interceptCount);
133 blob->getIntercepts(bounds, intercepts.begin(), &paint);
134
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
149 ///////////////////////////////////////////////////////////////////////////////////////////////////
150
make_text(const SkFont & font,const SkGlyphID glyphs[],int count)151 static sk_sp<SkTextBlob> make_text(const SkFont& font, const SkGlyphID glyphs[], int count) {
152 return SkTextBlob::MakeFromText(glyphs, count * sizeof(SkGlyphID), font,
153 SkTextEncoding::kGlyphID);
154 }
155
make_posh(const SkFont & font,const SkGlyphID glyphs[],int count,SkScalar spacing)156 static 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,
164 SkTextEncoding::kGlyphID);
165 }
166
make_pos(const SkFont & font,const SkGlyphID glyphs[],int count,SkScalar spacing)167 static 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,
175 SkTextEncoding::kGlyphID);
176 }
177
178 // widen the gaps with a margin (on each side of the gap), elimnating segments that go away
trim_with_halo(SkScalar intervals[],int count,SkScalar margin)179 static 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
draw_blob_adorned(SkCanvas * canvas,sk_sp<SkTextBlob> blob)204 static 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
237 DEF_SIMPLE_GM(textblob_intercepts, canvas, 940, 800) {
238 const char text[] = "Hyjay {worlp}.";
239 const size_t length = strlen(text);
240 SkFont font;
241 font.setTypeface(ToolUtils::create_portable_typeface());
242 font.setSize(100);
243 font.setEdging(SkFont::Edging::kAntiAlias);
244 const int count = font.countText(text, length, SkTextEncoding::kUTF8);
245 SkAutoTArray<SkGlyphID> glyphs(count);
246 font.textToGlyphs(text, length, SkTextEncoding::kUTF8, glyphs.get(), count);
247
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 }
261