blob: 1490a672c7472f2d0d6f76380f7a40c0f56f388a [file] [log] [blame]
schenney@chromium.org4da06ab2011-12-20 15:14:18 +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 */
bungemand3ebb482015-08-05 13:57:49 -07007
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "gm/gm.h"
9#include "include/core/SkCanvas.h"
Ben Wagner7fde8e12019-05-01 17:28:53 -040010#include "include/core/SkColor.h"
11#include "include/core/SkFont.h"
12#include "include/core/SkMatrix.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050013#include "include/core/SkPaint.h"
14#include "include/core/SkPath.h"
Ben Wagner7fde8e12019-05-01 17:28:53 -040015#include "include/core/SkPoint.h"
16#include "include/core/SkRect.h"
17#include "include/core/SkScalar.h"
18#include "include/core/SkSize.h"
19#include "include/core/SkString.h"
20#include "include/core/SkTypeface.h"
21#include "include/core/SkTypes.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050022#include "include/utils/SkRandom.h"
23#include "tools/ToolUtils.h"
schenney@chromium.org4da06ab2011-12-20 15:14:18 +000024
halcanary6950de62015-11-07 05:29:00 -080025// https://bug.skia.org/1316 shows that this cubic, when slightly clipped, creates big
reed@google.com22999c62013-05-23 19:30:48 +000026// (incorrect) changes to its control points.
27class ClippedCubicGM : public skiagm::GM {
Hal Canaryfa3305a2019-07-18 12:36:54 -040028 SkString onShortName() override { return SkString("clippedcubic"); }
skia.committer@gmail.com3e2345a2013-05-24 07:01:26 +000029
Hal Canaryfa3305a2019-07-18 12:36:54 -040030 SkISize onISize() override { return {1240, 390}; }
commit-bot@chromium.orga90c6802014-04-30 13:20:45 +000031
John Stiles1cf2c8d2020-08-13 22:58:04 -040032 void onDraw(SkCanvas* canvas) override {
reed@google.com22999c62013-05-23 19:30:48 +000033 SkPath path;
34 path.moveTo(0, 0);
35 path.cubicTo(140, 150, 40, 10, 170, 150);
skia.committer@gmail.com3e2345a2013-05-24 07:01:26 +000036
reed@google.com22999c62013-05-23 19:30:48 +000037 SkPaint paint;
38 SkRect bounds = path.getBounds();
skia.committer@gmail.com3e2345a2013-05-24 07:01:26 +000039
reed@google.com0f8990c2013-05-23 19:47:05 +000040 for (SkScalar dy = -1; dy <= 1; dy += 1) {
reed@google.com22999c62013-05-23 19:30:48 +000041 canvas->save();
reed@google.com0f8990c2013-05-23 19:47:05 +000042 for (SkScalar dx = -1; dx <= 1; dx += 1) {
reed@google.com22999c62013-05-23 19:30:48 +000043 canvas->save();
44 canvas->clipRect(bounds);
45 canvas->translate(dx, dy);
46 canvas->drawPath(path, paint);
47 canvas->restore();
skia.committer@gmail.com3e2345a2013-05-24 07:01:26 +000048
reed@google.com22999c62013-05-23 19:30:48 +000049 canvas->translate(bounds.width(), 0);
50 }
51 canvas->restore();
52 canvas->translate(0, bounds.height());
53 }
54 }
reed@google.com22999c62013-05-23 19:30:48 +000055};
56
caryclark7d173402015-08-20 08:23:52 -070057
58class ClippedCubic2GM : public skiagm::GM {
Hal Canaryfa3305a2019-07-18 12:36:54 -040059 SkString onShortName() override { return SkString("clippedcubic2"); }
caryclark7d173402015-08-20 08:23:52 -070060
Hal Canaryfa3305a2019-07-18 12:36:54 -040061 SkISize onISize() override { return {1240, 390}; }
caryclark7d173402015-08-20 08:23:52 -070062
63 void onDraw(SkCanvas* canvas) override {
64 canvas->save();
caryclark05424f72015-08-20 10:35:43 -070065 canvas->translate(-2, 120);
caryclark7d173402015-08-20 08:23:52 -070066 drawOne(canvas, fPath, SkRect::MakeLTRB(0, 0, 80, 150));
67 canvas->translate(0, 170);
68 drawOne(canvas, fPath, SkRect::MakeLTRB(0, 0, 80, 100));
69 canvas->translate(0, 170);
70 drawOne(canvas, fPath, SkRect::MakeLTRB(0, 0, 30, 150));
71 canvas->translate(0, 170);
72 drawOne(canvas, fPath, SkRect::MakeLTRB(0, 0, 10, 150));
73 canvas->restore();
caryclark05424f72015-08-20 10:35:43 -070074 canvas->save();
75 canvas->translate(20, -2);
76 drawOne(canvas, fFlipped, SkRect::MakeLTRB(0, 0, 150, 80));
77 canvas->translate(170, 0);
78 drawOne(canvas, fFlipped, SkRect::MakeLTRB(0, 0, 100, 80));
79 canvas->translate(170, 0);
80 drawOne(canvas, fFlipped, SkRect::MakeLTRB(0, 0, 150, 30));
81 canvas->translate(170, 0);
82 drawOne(canvas, fFlipped, SkRect::MakeLTRB(0, 0, 150, 10));
83 canvas->restore();
caryclark7d173402015-08-20 08:23:52 -070084 }
85
86 void drawOne(SkCanvas* canvas, const SkPath& path, const SkRect& clip) {
87 SkPaint framePaint, fillPaint;
88 framePaint.setStyle(SkPaint::kStroke_Style);
89 canvas->drawRect(clip, framePaint);
90 canvas->drawPath(path, framePaint);
91 canvas->save();
92 canvas->clipRect(clip);
93 canvas->drawPath(path, fillPaint);
94 canvas->restore();
95 }
96
97 void onOnceBeforeDraw() override {
98 fPath.moveTo(69.7030518991886f, 0);
99 fPath.cubicTo( 69.7030518991886f, 21.831149999999997f,
100 58.08369508178456f, 43.66448333333333f, 34.8449814469765f, 65.5f);
101 fPath.cubicTo( 11.608591683531916f, 87.33115f, -0.010765133872116195f, 109.16448333333332f,
102 -0.013089005235602302f, 131);
103 fPath.close();
caryclark05424f72015-08-20 10:35:43 -0700104 fFlipped = fPath;
105 SkMatrix matrix;
106 matrix.reset();
107 matrix.setScaleX(0);
108 matrix.setScaleY(0);
109 matrix.setSkewX(1);
110 matrix.setSkewY(1);
111 fFlipped.transform(matrix);
caryclark7d173402015-08-20 08:23:52 -0700112 }
113
114 SkPath fPath;
caryclark05424f72015-08-20 10:35:43 -0700115 SkPath fFlipped;
caryclark7d173402015-08-20 08:23:52 -0700116private:
John Stiles7571f9e2020-09-02 22:42:33 -0400117 using INHERITED = skiagm::GM;
caryclark7d173402015-08-20 08:23:52 -0700118};
119
120
reed@google.com27b90fa2013-03-08 18:44:01 +0000121class CubicPathGM : public skiagm::GM {
Hal Canaryfa3305a2019-07-18 12:36:54 -0400122 SkString onShortName() override { return SkString("cubicpath"); }
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000123
Hal Canaryfa3305a2019-07-18 12:36:54 -0400124 SkISize onISize() override { return {1240, 390}; }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000125
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000126 void drawPath(SkPath& path,SkCanvas* canvas,SkColor color,
127 const SkRect& clip,SkPaint::Cap cap, SkPaint::Join join,
Mike Reed7d34dc72019-11-26 12:17:17 -0500128 SkPaint::Style style, SkPathFillType fill,
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000129 SkScalar strokeWidth) {
130 path.setFillType(fill);
131 SkPaint paint;
132 paint.setStrokeCap(cap);
133 paint.setStrokeWidth(strokeWidth);
134 paint.setStrokeJoin(join);
135 paint.setColor(color);
136 paint.setStyle(style);
137 canvas->save();
138 canvas->clipRect(clip);
139 canvas->drawPath(path, paint);
140 canvas->restore();
141 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000142
Hal Canaryfa3305a2019-07-18 12:36:54 -0400143 void onDraw(SkCanvas* canvas) override {
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000144 struct FillAndName {
Mike Reed7d34dc72019-11-26 12:17:17 -0500145 SkPathFillType fFill;
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000146 const char* fName;
147 };
mtkleindbfd7ab2016-09-01 11:24:54 -0700148 constexpr FillAndName gFills[] = {
Mike Reed7d34dc72019-11-26 12:17:17 -0500149 {SkPathFillType::kWinding, "Winding"},
150 {SkPathFillType::kEvenOdd, "Even / Odd"},
151 {SkPathFillType::kInverseWinding, "Inverse Winding"},
152 {SkPathFillType::kInverseEvenOdd, "Inverse Even / Odd"},
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000153 };
154 struct StyleAndName {
155 SkPaint::Style fStyle;
156 const char* fName;
157 };
mtkleindbfd7ab2016-09-01 11:24:54 -0700158 constexpr StyleAndName gStyles[] = {
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000159 {SkPaint::kFill_Style, "Fill"},
160 {SkPaint::kStroke_Style, "Stroke"},
161 {SkPaint::kStrokeAndFill_Style, "Stroke And Fill"},
162 };
163 struct CapAndName {
164 SkPaint::Cap fCap;
165 SkPaint::Join fJoin;
166 const char* fName;
167 };
mtkleindbfd7ab2016-09-01 11:24:54 -0700168 constexpr CapAndName gCaps[] = {
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000169 {SkPaint::kButt_Cap, SkPaint::kBevel_Join, "Butt"},
170 {SkPaint::kRound_Cap, SkPaint::kRound_Join, "Round"},
171 {SkPaint::kSquare_Cap, SkPaint::kBevel_Join, "Square"}
172 };
173 struct PathAndName {
174 SkPath fPath;
175 const char* fName;
176 };
177 PathAndName path;
178 path.fPath.moveTo(25*SK_Scalar1, 10*SK_Scalar1);
179 path.fPath.cubicTo(40*SK_Scalar1, 20*SK_Scalar1,
180 60*SK_Scalar1, 20*SK_Scalar1,
181 75*SK_Scalar1, 10*SK_Scalar1);
182 path.fName = "moveTo-cubic";
183
184 SkPaint titlePaint;
185 titlePaint.setColor(SK_ColorBLACK);
186 titlePaint.setAntiAlias(true);
Mike Kleinea3f0142019-03-20 11:12:10 -0500187 SkFont font(ToolUtils::create_portable_typeface(), 15);
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000188 const char title[] = "Cubic Drawn Into Rectangle Clips With "
189 "Indicated Style, Fill and Linecaps, with stroke width 10";
Mike Reed1af9b482019-01-07 11:01:57 -0500190 canvas->drawString(title, 20, 20, font, titlePaint);
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000191
scroggof9d61012014-12-15 12:54:51 -0800192 SkRandom rand;
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000193 SkRect rect = SkRect::MakeWH(100*SK_Scalar1, 30*SK_Scalar1);
194 canvas->save();
195 canvas->translate(10 * SK_Scalar1, 30 * SK_Scalar1);
196 canvas->save();
197 for (size_t cap = 0; cap < SK_ARRAY_COUNT(gCaps); ++cap) {
198 if (0 < cap) {
199 canvas->translate((rect.width() + 40 * SK_Scalar1) * SK_ARRAY_COUNT(gStyles), 0);
200 }
201 canvas->save();
202 for (size_t fill = 0; fill < SK_ARRAY_COUNT(gFills); ++fill) {
203 if (0 < fill) {
204 canvas->translate(0, rect.height() + 40 * SK_Scalar1);
205 }
206 canvas->save();
207 for (size_t style = 0; style < SK_ARRAY_COUNT(gStyles); ++style) {
208 if (0 < style) {
209 canvas->translate(rect.width() + 40 * SK_Scalar1, 0);
210 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000211
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000212 SkColor color = 0xff007000;
213 this->drawPath(path.fPath, canvas, color, rect,
214 gCaps[cap].fCap, gCaps[cap].fJoin, gStyles[style].fStyle,
215 gFills[fill].fFill, SK_Scalar1*10);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000216
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000217 SkPaint rectPaint;
218 rectPaint.setColor(SK_ColorBLACK);
219 rectPaint.setStyle(SkPaint::kStroke_Style);
220 rectPaint.setStrokeWidth(-1);
221 rectPaint.setAntiAlias(true);
222 canvas->drawRect(rect, rectPaint);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000223
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000224 SkPaint labelPaint;
225 labelPaint.setColor(color);
Mike Reed1af9b482019-01-07 11:01:57 -0500226 font.setSize(10);
227 canvas->drawString(gStyles[style].fName, 0, rect.height() + 12, font, labelPaint);
228 canvas->drawString(gFills[fill].fName, 0, rect.height() + 24, font, labelPaint);
229 canvas->drawString(gCaps[cap].fName, 0, rect.height() + 36, font, labelPaint);
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000230 }
231 canvas->restore();
232 }
233 canvas->restore();
234 }
235 canvas->restore();
236 canvas->restore();
237 }
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000238};
239
reed@google.com27b90fa2013-03-08 18:44:01 +0000240class CubicClosePathGM : public skiagm::GM {
Hal Canaryfa3305a2019-07-18 12:36:54 -0400241 SkString onShortName() override { return SkString("cubicclosepath"); }
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000242
Hal Canaryfa3305a2019-07-18 12:36:54 -0400243 SkISize onISize() override { return {1240, 390}; }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000244
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000245 void drawPath(SkPath& path,SkCanvas* canvas,SkColor color,
246 const SkRect& clip,SkPaint::Cap cap, SkPaint::Join join,
Mike Reed7d34dc72019-11-26 12:17:17 -0500247 SkPaint::Style style, SkPathFillType fill,
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000248 SkScalar strokeWidth) {
249 path.setFillType(fill);
250 SkPaint paint;
251 paint.setStrokeCap(cap);
252 paint.setStrokeWidth(strokeWidth);
253 paint.setStrokeJoin(join);
254 paint.setColor(color);
255 paint.setStyle(style);
256 canvas->save();
257 canvas->clipRect(clip);
258 canvas->drawPath(path, paint);
259 canvas->restore();
260 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000261
John Stiles1cf2c8d2020-08-13 22:58:04 -0400262 void onDraw(SkCanvas* canvas) override {
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000263 struct FillAndName {
Mike Reed7d34dc72019-11-26 12:17:17 -0500264 SkPathFillType fFill;
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000265 const char* fName;
266 };
mtkleindbfd7ab2016-09-01 11:24:54 -0700267 constexpr FillAndName gFills[] = {
Mike Reed7d34dc72019-11-26 12:17:17 -0500268 {SkPathFillType::kWinding, "Winding"},
269 {SkPathFillType::kEvenOdd, "Even / Odd"},
270 {SkPathFillType::kInverseWinding, "Inverse Winding"},
271 {SkPathFillType::kInverseEvenOdd, "Inverse Even / Odd"},
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000272 };
273 struct StyleAndName {
274 SkPaint::Style fStyle;
275 const char* fName;
276 };
mtkleindbfd7ab2016-09-01 11:24:54 -0700277 constexpr StyleAndName gStyles[] = {
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000278 {SkPaint::kFill_Style, "Fill"},
279 {SkPaint::kStroke_Style, "Stroke"},
280 {SkPaint::kStrokeAndFill_Style, "Stroke And Fill"},
281 };
282 struct CapAndName {
283 SkPaint::Cap fCap;
284 SkPaint::Join fJoin;
285 const char* fName;
286 };
mtkleindbfd7ab2016-09-01 11:24:54 -0700287 constexpr CapAndName gCaps[] = {
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000288 {SkPaint::kButt_Cap, SkPaint::kBevel_Join, "Butt"},
289 {SkPaint::kRound_Cap, SkPaint::kRound_Join, "Round"},
290 {SkPaint::kSquare_Cap, SkPaint::kBevel_Join, "Square"}
291 };
292 struct PathAndName {
293 SkPath fPath;
294 const char* fName;
295 };
296 PathAndName path;
297 path.fPath.moveTo(25*SK_Scalar1, 10*SK_Scalar1);
298 path.fPath.cubicTo(40*SK_Scalar1, 20*SK_Scalar1,
299 60*SK_Scalar1, 20*SK_Scalar1,
300 75*SK_Scalar1, 10*SK_Scalar1);
301 path.fPath.close();
302 path.fName = "moveTo-cubic-close";
303
304 SkPaint titlePaint;
305 titlePaint.setColor(SK_ColorBLACK);
306 titlePaint.setAntiAlias(true);
Mike Kleinea3f0142019-03-20 11:12:10 -0500307 SkFont font(ToolUtils::create_portable_typeface(), 15);
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000308 const char title[] = "Cubic Closed Drawn Into Rectangle Clips With "
309 "Indicated Style, Fill and Linecaps, with stroke width 10";
Mike Reed1af9b482019-01-07 11:01:57 -0500310 canvas->drawString(title, 20, 20, font, titlePaint);
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000311
scroggof9d61012014-12-15 12:54:51 -0800312 SkRandom rand;
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000313 SkRect rect = SkRect::MakeWH(100*SK_Scalar1, 30*SK_Scalar1);
314 canvas->save();
315 canvas->translate(10 * SK_Scalar1, 30 * SK_Scalar1);
316 canvas->save();
317 for (size_t cap = 0; cap < SK_ARRAY_COUNT(gCaps); ++cap) {
318 if (0 < cap) {
319 canvas->translate((rect.width() + 40 * SK_Scalar1) * SK_ARRAY_COUNT(gStyles), 0);
320 }
321 canvas->save();
322 for (size_t fill = 0; fill < SK_ARRAY_COUNT(gFills); ++fill) {
323 if (0 < fill) {
324 canvas->translate(0, rect.height() + 40 * SK_Scalar1);
325 }
326 canvas->save();
327 for (size_t style = 0; style < SK_ARRAY_COUNT(gStyles); ++style) {
328 if (0 < style) {
329 canvas->translate(rect.width() + 40 * SK_Scalar1, 0);
330 }
rmistry@google.comd6176b02012-08-23 18:14:13 +0000331
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000332 SkColor color = 0xff007000;
333 this->drawPath(path.fPath, canvas, color, rect,
334 gCaps[cap].fCap, gCaps[cap].fJoin, gStyles[style].fStyle,
335 gFills[fill].fFill, SK_Scalar1*10);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000336
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000337 SkPaint rectPaint;
338 rectPaint.setColor(SK_ColorBLACK);
339 rectPaint.setStyle(SkPaint::kStroke_Style);
340 rectPaint.setStrokeWidth(-1);
341 rectPaint.setAntiAlias(true);
342 canvas->drawRect(rect, rectPaint);
rmistry@google.comd6176b02012-08-23 18:14:13 +0000343
schenney@chromium.org45cbfdd2011-12-20 21:48:14 +0000344 SkPaint labelPaint;
345 labelPaint.setColor(color);
346 labelPaint.setAntiAlias(true);
Mike Reed1af9b482019-01-07 11:01:57 -0500347 font.setSize(10);
348 canvas->drawString(gStyles[style].fName, 0, rect.height() + 12, font, labelPaint);
349 canvas->drawString(gFills[fill].fName, 0, rect.height() + 24, font, labelPaint);
350 canvas->drawString(gCaps[cap].fName, 0, rect.height() + 36, font, labelPaint);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000351 }
352 canvas->restore();
353 }
354 canvas->restore();
355 }
356 canvas->restore();
357 canvas->restore();
358 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000359};
360
caryclark4693a172016-04-06 08:54:06 -0700361DEF_SIMPLE_GM(bug5099, canvas, 50, 50) {
362 SkPaint p;
363 p.setColor(SK_ColorRED);
364 p.setAntiAlias(true);
365 p.setStyle(SkPaint::kStroke_Style);
366 p.setStrokeWidth(10);
367
368 SkPath path;
369 path.moveTo(6, 27);
370 path.cubicTo(31.5f, 1.5f, 3.5f, 4.5f, 29, 29);
371 canvas->drawPath(path, p);
372}
373
Cary Clarkd4631982017-01-05 12:08:38 -0500374DEF_SIMPLE_GM(bug6083, canvas, 100, 50) {
Ben Wagner29380bd2017-10-09 14:43:00 -0400375 SkPaint p;
376 p.setColor(SK_ColorRED);
377 p.setAntiAlias(true);
378 p.setStyle(SkPaint::kStroke_Style);
379 p.setStrokeWidth(15);
380 canvas->translate(-500, -130);
381 SkPath path;
382 path.moveTo(500.988f, 155.200f);
383 path.lineTo(526.109f, 155.200f);
384 SkPoint p1 = { 526.109f, 155.200f };
385 SkPoint p2 = { 525.968f, 212.968f };
386 SkPoint p3 = { 526.109f, 241.840f };
387 path.cubicTo(p1, p2, p3);
388 canvas->drawPath(path, p);
389 canvas->translate(50, 0);
390 path.reset();
391 p2.set(525.968f, 213.172f);
392 path.moveTo(500.988f, 155.200f);
393 path.lineTo(526.109f, 155.200f);
394 path.cubicTo(p1, p2, p3);
395 canvas->drawPath(path, p);
Cary Clarkd4631982017-01-05 12:08:38 -0500396}
397
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000398//////////////////////////////////////////////////////////////////////////////
399
reed@google.com27b90fa2013-03-08 18:44:01 +0000400DEF_GM( return new CubicPathGM; )
401DEF_GM( return new CubicClosePathGM; )
reed@google.com22999c62013-05-23 19:30:48 +0000402DEF_GM( return new ClippedCubicGM; )
caryclark7d173402015-08-20 08:23:52 -0700403DEF_GM( return new ClippedCubic2GM; )