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.h"
9 #include "sk_tool_utils.h"
10 
11 #include "SkBlurMask.h"
12 #include "SkBlurMaskFilter.h"
13 #include "SkReadBuffer.h"
14 #include "SkTextBlob.h"
15 #include "SkTo.h"
16 #include "SkWriteBuffer.h"
17 
18 #include "Sk2DPathEffect.h"
19 
create_underline(const SkTDArray<SkScalar> & intersections,SkScalar last,SkScalar finalPos,SkScalar uPos,SkScalar uWidth,SkScalar textSize)20 static 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) {
26         SkScalar start = intersections[index] - uWidth;
27         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 
41 namespace {
42 
MakeFancyBlob(const SkPaint & paint,const SkFont & font,const char * text)43 sk_sp<SkTextBlob> MakeFancyBlob(const SkPaint& paint, const SkFont& font, const char* text) {
44     const size_t textLen = strlen(text);
45     const int glyphCount = font.countText(text, textLen, kUTF8_SkTextEncoding);
46     SkAutoTArray<SkGlyphID> glyphs(glyphCount);
47     font.textToGlyphs(text, textLen, kUTF8_SkTextEncoding, glyphs.get(), glyphCount);
48     SkAutoTArray<SkScalar> widths(glyphCount);
49     font.getWidths(glyphs.get(), glyphCount, widths.get());
50 
51     SkTextBlobBuilder blobBuilder;
52     int glyphIndex = 0;
53     SkScalar advance = 0;
54 
55     // Default-positioned run.
56     {
57         const int defaultRunLen = glyphCount / 3;
58         const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRun(font,
59                                                                        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;
71         const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRunPosH(font,
72                                                                            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;
85         const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRunPos(font, fullRunLen);
86         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 
95     return blobBuilder.make();
96 }
97 
98 } // anonymous ns
99 
100 DEF_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) {
108         for (SkScalar textSize = 100; textSize > 10; textSize -= 20) {
109             SkFont skFont(
110                     sk_tool_utils::create_portable_typeface(fam[font], SkFontStyle()), textSize);
111             const SkScalar uWidth = textSize / 15;
112             paint.setStrokeWidth(uWidth);
113             paint.setStyle(SkPaint::kFill_Style);
114 
115             sk_sp<SkTextBlob> blob = MakeFancyBlob(paint, skFont, test);
116             canvas->drawTextBlob(blob, blobOffset.x(), blobOffset.y(), paint);
117 
118             const SkScalar uPos = uWidth;
119             const SkScalar bounds[2] = { uPos - uWidth / 2, uPos + uWidth / 2 };
120             const int interceptCount = blob->getIntercepts(bounds, nullptr, &paint);
121             SkASSERT(!(interceptCount % 2));
122 
123             SkTDArray<SkScalar> intercepts;
124             intercepts.setCount(interceptCount);
125             blob->getIntercepts(bounds, intercepts.begin(), &paint);
126 
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 
141 ///////////////////////////////////////////////////////////////////////////////////////////////////
142 
make_text(const SkFont & font,const SkGlyphID glyphs[],int count)143 static 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 
make_posh(const SkFont & font,const SkGlyphID glyphs[],int count,SkScalar spacing)148 static 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 
make_pos(const SkFont & font,const SkGlyphID glyphs[],int count,SkScalar spacing)159 static 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
trim_with_halo(SkScalar intervals[],int count,SkScalar margin)171 static 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 
draw_blob_adorned(SkCanvas * canvas,sk_sp<SkTextBlob> blob)196 static 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 
229 DEF_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 }
253