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#include "SkBlurMask.h"
11#include "SkBlurMaskFilter.h"
12#include "SkReadBuffer.h"
13#include "SkTextBlob.h"
14#include "SkWriteBuffer.h"
15
16#include "Sk2DPathEffect.h"
17
18static SkPath create_underline(const SkTDArray<SkScalar>& intersections,
19        SkScalar last, SkScalar finalPos,
20        SkScalar uPos, SkScalar uWidth, SkScalar textSize) {
21    SkPath underline;
22    SkScalar end = last;
23    for (int index = 0; index < intersections.count(); index += 2) {
24        SkScalar start = intersections[index] - uWidth;;
25        end = intersections[index + 1] + uWidth;
26        if (start > last && last + textSize / 12 < start) {
27            underline.moveTo(last, uPos);
28            underline.lineTo(start, uPos);
29        }
30        last = end;
31    }
32    if (end < finalPos) {
33        underline.moveTo(end, uPos);
34        underline.lineTo(finalPos, uPos);
35    }
36    return underline;
37}
38
39static void find_intercepts(const char* test, size_t len, SkScalar x, SkScalar y,
40        const SkPaint& paint, SkScalar uWidth, SkTDArray<SkScalar>* intersections) {
41    SkScalar uPos = y + uWidth;
42    SkScalar bounds[2] = { uPos - uWidth / 2, uPos + uWidth / 2 };
43    int count = paint.getTextIntercepts(test, len, x, y, bounds, nullptr);
44    SkASSERT(!(count % 2));
45    if (count) {
46        intersections->setCount(count);
47        paint.getTextIntercepts(test, len, x, y, bounds, intersections->begin());
48    }
49}
50
51DEF_SIMPLE_GM(fancyunderline, canvas, 900, 1350) {
52    SkPaint paint;
53    paint.setAntiAlias(true);
54    const char* fam[] = { "sans-serif", "serif", "monospace" };
55    const char test[] = "aAjJgGyY_|{-(~[,]qQ}pP}zZ";
56    SkPoint textPt = { 10, 80 };
57    for (size_t font = 0; font < SK_ARRAY_COUNT(fam); ++font) {
58        sk_tool_utils::set_portable_typeface(&paint, fam[font]);
59        for (SkScalar textSize = 100; textSize > 10; textSize -= 20) {
60            paint.setTextSize(textSize);
61            const SkScalar uWidth = textSize / 15;
62            paint.setStrokeWidth(uWidth);
63            paint.setStyle(SkPaint::kFill_Style);
64            canvas->drawText(test, sizeof(test) - 1, textPt.fX, textPt.fY, paint);
65
66            SkTDArray<SkScalar> intersections;
67            find_intercepts(test, sizeof(test) - 1, textPt.fX, textPt.fY, paint, uWidth,
68                &intersections);
69
70            SkScalar start = textPt.fX;
71            SkScalar end = paint.measureText(test, sizeof(test) - 1) + textPt.fX;
72            SkScalar uPos = textPt.fY + uWidth;
73            SkPath underline = create_underline(intersections, start, end, uPos, uWidth, textSize);
74            paint.setStyle(SkPaint::kStroke_Style);
75            canvas->drawPath(underline, paint);
76
77            canvas->translate(0, textSize * 1.3f);
78        }
79        canvas->translate(0, 60);
80    }
81}
82
83static void find_intercepts(const char* test, size_t len, const SkPoint* pos, const SkPaint& paint,
84        SkScalar uWidth, SkTDArray<SkScalar>* intersections) {
85    SkScalar uPos = pos[0].fY + uWidth;
86    SkScalar bounds[2] = { uPos - uWidth / 2, uPos + uWidth / 2 };
87    int count = paint.getPosTextIntercepts(test, len, pos, bounds, nullptr);
88    SkASSERT(!(count % 2));
89    if (count) {
90        intersections->setCount(count);
91        paint.getPosTextIntercepts(test, len, pos, bounds, intersections->begin());
92    }
93}
94
95DEF_SIMPLE_GM(fancyposunderline, canvas, 900, 1350) {
96    SkPaint paint;
97    paint.setAntiAlias(true);
98    const char* fam[] = { "sans-serif", "serif", "monospace" };
99    const char test[] = "aAjJgGyY_|{-(~[,]qQ}pP}zZ";
100    SkPoint textPt = { 10, 80 };
101    for (size_t font = 0; font < SK_ARRAY_COUNT(fam); ++font) {
102        sk_tool_utils::set_portable_typeface(&paint, fam[font]);
103        for (SkScalar textSize = 100; textSize > 10; textSize -= 20) {
104            paint.setTextSize(textSize);
105            const SkScalar uWidth = textSize / 15;
106            paint.setStrokeWidth(uWidth);
107            paint.setStyle(SkPaint::kFill_Style);
108            int widthCount = paint.getTextWidths(test, sizeof(test) - 1, nullptr);
109            SkTDArray<SkScalar> widths;
110            widths.setCount(widthCount);
111            (void) paint.getTextWidths(test, sizeof(test) - 1, widths.begin());
112            SkTDArray<SkPoint> pos;
113            pos.setCount(widthCount);
114            SkScalar posX = textPt.fX;
115            for (int index = 0; index < widthCount; ++index) {
116                pos[index].fX = posX;
117                posX += widths[index];
118                pos[index].fY = textPt.fY + (textSize / 25) * (index % 4);
119            }
120            canvas->drawPosText(test, sizeof(test) - 1, pos.begin(), paint);
121
122            SkTDArray<SkScalar> intersections;
123            find_intercepts(test, sizeof(test) - 1, pos.begin(), paint, uWidth, &intersections);
124
125            SkScalar start = textPt.fX;
126            SkScalar end = posX;
127            SkScalar uPos = textPt.fY + uWidth;
128            SkPath underline = create_underline(intersections, start, end, uPos, uWidth, textSize);
129            paint.setStyle(SkPaint::kStroke_Style);
130            canvas->drawPath(underline, paint);
131
132            canvas->translate(0, textSize * 1.3f);
133        }
134        canvas->translate(0, 60);
135    }
136}
137
138namespace {
139
140sk_sp<SkTextBlob> MakeFancyBlob(const SkPaint& paint, const char* text) {
141    SkPaint blobPaint(paint);
142
143    const size_t textLen = strlen(text);
144    const int glyphCount = blobPaint.textToGlyphs(text, textLen, nullptr);
145    SkAutoTArray<SkGlyphID> glyphs(glyphCount);
146    blobPaint.textToGlyphs(text, textLen, glyphs.get());
147
148    blobPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
149    const size_t glyphTextBytes = SkTo<uint32_t>(glyphCount) * sizeof(SkGlyphID);
150    const int widthCount = blobPaint.getTextWidths(glyphs.get(), glyphTextBytes, nullptr);
151    SkAssertResult(widthCount == glyphCount);
152
153    SkAutoTArray<SkScalar> widths(glyphCount);
154    blobPaint.getTextWidths(glyphs.get(), glyphTextBytes, widths.get());
155
156    SkTextBlobBuilder blobBuilder;
157    int glyphIndex = 0;
158    SkScalar advance = 0;
159
160    // Default-positioned run.
161    {
162        const int defaultRunLen = glyphCount / 3;
163        const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRun(blobPaint,
164                                                                       defaultRunLen,
165                                                                       advance, 0);
166        memcpy(buf.glyphs, glyphs.get(), SkTo<uint32_t>(defaultRunLen) * sizeof(SkGlyphID));
167
168        for (int i = 0; i < defaultRunLen; ++i) {
169            advance += widths[glyphIndex++];
170        }
171    }
172
173    // Horizontal-positioned run.
174    {
175        const int horizontalRunLen = glyphCount / 3;
176        const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRunPosH(blobPaint,
177                                                                           horizontalRunLen,
178                                                                           0);
179        memcpy(buf.glyphs, glyphs.get() + glyphIndex,
180               SkTo<uint32_t>(horizontalRunLen) * sizeof(SkGlyphID));
181        for (int i = 0; i < horizontalRunLen; ++i) {
182            buf.pos[i] = advance;
183            advance += widths[glyphIndex++];
184        }
185    }
186
187    // Full-positioned run.
188    {
189        const int fullRunLen = glyphCount - glyphIndex;
190        const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRunPos(blobPaint, fullRunLen);
191        memcpy(buf.glyphs, glyphs.get() + glyphIndex,
192               SkTo<uint32_t>(fullRunLen) * sizeof(SkGlyphID));
193        for (int i = 0; i < fullRunLen; ++i) {
194            buf.pos[i * 2 + 0] = advance; // x offset
195            buf.pos[i * 2 + 1] = 0;       // y offset
196            advance += widths[glyphIndex++];
197        }
198    }
199
200    return blobBuilder.make();
201}
202
203} // anonymous ns
204
205DEF_SIMPLE_GM(fancyblobunderline, canvas, 1480, 1380) {
206    SkPaint paint;
207    paint.setAntiAlias(true);
208    const char* fam[] = { "sans-serif", "serif", "monospace" };
209    const char test[] = "aAjJgGyY_|{-(~[,]qQ}pP}zZ";
210    const SkPoint blobOffset = { 10, 80 };
211
212    for (size_t font = 0; font < SK_ARRAY_COUNT(fam); ++font) {
213        sk_tool_utils::set_portable_typeface(&paint, fam[font]);
214        for (SkScalar textSize = 100; textSize > 10; textSize -= 20) {
215            paint.setTextSize(textSize);
216            const SkScalar uWidth = textSize / 15;
217            paint.setStrokeWidth(uWidth);
218            paint.setStyle(SkPaint::kFill_Style);
219
220            sk_sp<SkTextBlob> blob = MakeFancyBlob(paint, test);
221            canvas->drawTextBlob(blob, blobOffset.x(), blobOffset.y(), paint);
222
223            const SkScalar uPos = uWidth;
224            const SkScalar bounds[2] = { uPos - uWidth / 2, uPos + uWidth / 2 };
225            const int interceptCount = paint.getTextBlobIntercepts(blob.get(), bounds, nullptr);
226            SkASSERT(!(interceptCount % 2));
227
228            SkTDArray<SkScalar> intercepts;
229            intercepts.setCount(interceptCount);
230            paint.getTextBlobIntercepts(blob.get(), bounds, intercepts.begin());
231
232            const SkScalar start = blob->bounds().left();
233            const SkScalar end = blob->bounds().right();
234            SkPath underline = create_underline(intercepts, start, end, uPos, uWidth, textSize);
235            underline.offset(blobOffset.x(), blobOffset.y());
236            paint.setStyle(SkPaint::kStroke_Style);
237            canvas->drawPath(underline, paint);
238
239            canvas->translate(0, textSize * 1.3f);
240        }
241
242        canvas->translate(0, 60);
243    }
244}
245
246DEF_SIMPLE_GM(fancyunderlinebars, canvas, 1500, 460) {
247    SkPaint paint;
248    paint.setAntiAlias(true);
249    const char test[] = " .}]_ .}]_ .}]_ .}]_ .}]_ .}]_ .}]_ .}]_ .}]_ .}]_ .}]_ .}]_ .}]_";
250    SkPoint textPt = { 10, 80 };
251    sk_tool_utils::set_portable_typeface(&paint, "serif");
252    for (SkScalar textSize = 100; textSize > 10; textSize -= 20) {
253        paint.setTextSize(textSize);
254        SkScalar uWidth = textSize / 15;
255        paint.setStrokeWidth(uWidth);
256        paint.setStyle(SkPaint::kFill_Style);
257        int widthCount = paint.getTextWidths(test, sizeof(test) - 1, nullptr);
258        SkTDArray<SkScalar> widths;
259        widths.setCount(widthCount);
260        (void) paint.getTextWidths(test, sizeof(test) - 1, widths.begin());
261        SkTDArray<SkPoint> pos;
262        pos.setCount(widthCount);
263        SkScalar posX = textPt.fX;
264        pos[0] = textPt;
265        posX += widths[0];
266        for (int index = 1; index < widthCount; ++index) {
267            pos[index].fX = posX;
268            posX += widths[index];
269            pos[index].fY = textPt.fY - (textSize / 50) * (index / 5) + textSize / 50 * 4;
270        }
271        canvas->drawPosText(test, sizeof(test) - 1, pos.begin(), paint);
272
273        SkTDArray<SkScalar> intersections;
274        find_intercepts(test, sizeof(test) - 1, pos.begin(), paint, uWidth, &intersections);
275
276        SkScalar start = textPt.fX;
277        SkScalar end = posX;
278        SkScalar uPos = pos[0].fY + uWidth;
279        SkPath underline = create_underline(intersections, start, end, uPos, uWidth, textSize);
280        paint.setStyle(SkPaint::kStroke_Style);
281        canvas->drawPath(underline, paint);
282        canvas->translate(0, textSize * 1.3f);
283    }
284}
285