|
1 |
|
2 /* |
|
3 * Copyright 2006 The Android Open Source Project |
|
4 * |
|
5 * Use of this source code is governed by a BSD-style license that can be |
|
6 * found in the LICENSE file. |
|
7 */ |
|
8 |
|
9 |
|
10 #include "SkDrawExtraPathEffect.h" |
|
11 #include "SkDrawPath.h" |
|
12 #include "Sk1DPathEffect.h" |
|
13 #include "Sk2DPathEffect.h" |
|
14 #include "SkMemberInfo.h" |
|
15 #include "SkPaintParts.h" |
|
16 #include "SkPathEffect.h" |
|
17 #include "SkCornerPathEffect.h" |
|
18 |
|
19 #include "SkDashPathEffect.h" |
|
20 |
|
21 class SkDrawShapePathEffect : public SkDrawPathEffect { |
|
22 DECLARE_PRIVATE_MEMBER_INFO(DrawShapePathEffect); |
|
23 SkDrawShapePathEffect(); |
|
24 virtual ~SkDrawShapePathEffect(); |
|
25 virtual bool addChild(SkAnimateMaker& , SkDisplayable* ) SK_OVERRIDE; |
|
26 virtual SkPathEffect* getPathEffect(); |
|
27 protected: |
|
28 SkDrawable* addPath; |
|
29 SkDrawable* addMatrix; |
|
30 SkDrawPath* path; |
|
31 SkPathEffect* fPathEffect; |
|
32 friend class SkShape1DPathEffect; |
|
33 friend class SkShape2DPathEffect; |
|
34 }; |
|
35 |
|
36 class SkDrawShape1DPathEffect : public SkDrawShapePathEffect { |
|
37 DECLARE_EXTRAS_MEMBER_INFO(SkDrawShape1DPathEffect); |
|
38 SkDrawShape1DPathEffect(SkDisplayTypes ); |
|
39 virtual ~SkDrawShape1DPathEffect(); |
|
40 virtual void onEndElement(SkAnimateMaker& ); |
|
41 private: |
|
42 SkString phase; |
|
43 SkString spacing; |
|
44 friend class SkShape1DPathEffect; |
|
45 typedef SkDrawShapePathEffect INHERITED; |
|
46 }; |
|
47 |
|
48 class SkDrawShape2DPathEffect : public SkDrawShapePathEffect { |
|
49 DECLARE_EXTRAS_MEMBER_INFO(SkDrawShape2DPathEffect); |
|
50 SkDrawShape2DPathEffect(SkDisplayTypes ); |
|
51 virtual ~SkDrawShape2DPathEffect(); |
|
52 virtual void onEndElement(SkAnimateMaker& ); |
|
53 private: |
|
54 SkDrawMatrix* matrix; |
|
55 friend class SkShape2DPathEffect; |
|
56 typedef SkDrawShapePathEffect INHERITED; |
|
57 }; |
|
58 |
|
59 class SkDrawComposePathEffect : public SkDrawPathEffect { |
|
60 DECLARE_EXTRAS_MEMBER_INFO(SkDrawComposePathEffect); |
|
61 SkDrawComposePathEffect(SkDisplayTypes ); |
|
62 virtual ~SkDrawComposePathEffect(); |
|
63 virtual bool addChild(SkAnimateMaker& , SkDisplayable* ) SK_OVERRIDE; |
|
64 virtual SkPathEffect* getPathEffect(); |
|
65 virtual bool isPaint() const; |
|
66 private: |
|
67 SkDrawPathEffect* effect1; |
|
68 SkDrawPathEffect* effect2; |
|
69 }; |
|
70 |
|
71 class SkDrawCornerPathEffect : public SkDrawPathEffect { |
|
72 DECLARE_EXTRAS_MEMBER_INFO(SkDrawCornerPathEffect); |
|
73 SkDrawCornerPathEffect(SkDisplayTypes ); |
|
74 virtual ~SkDrawCornerPathEffect(); |
|
75 virtual SkPathEffect* getPathEffect(); |
|
76 private: |
|
77 SkScalar radius; |
|
78 }; |
|
79 |
|
80 //////////// SkShape1DPathEffect |
|
81 |
|
82 #include "SkAnimateMaker.h" |
|
83 #include "SkAnimatorScript.h" |
|
84 #include "SkDisplayApply.h" |
|
85 #include "SkDrawMatrix.h" |
|
86 #include "SkPaint.h" |
|
87 |
|
88 class SkShape1DPathEffect : public Sk1DPathEffect { |
|
89 public: |
|
90 SkShape1DPathEffect(SkDrawShape1DPathEffect* draw, SkAnimateMaker* maker) : |
|
91 fDraw(draw), fMaker(maker) { |
|
92 } |
|
93 |
|
94 SK_DECLARE_UNFLATTENABLE_OBJECT() |
|
95 |
|
96 protected: |
|
97 virtual SkScalar begin(SkScalar contourLength) const { |
|
98 SkScriptValue value; |
|
99 SkAnimatorScript engine(*fMaker, NULL, SkType_Float); |
|
100 engine.propertyCallBack(GetContourLength, &contourLength); |
|
101 value.fOperand.fScalar = 0; |
|
102 engine.evaluate(fDraw->phase.c_str(), &value, SkType_Float); |
|
103 return value.fOperand.fScalar; |
|
104 } |
|
105 |
|
106 virtual SkScalar next(SkPath* dst, SkScalar distance, SkPathMeasure&) const { |
|
107 fMaker->setExtraPropertyCallBack(fDraw->fType, GetDistance, &distance); |
|
108 SkDrawPath* drawPath = NULL; |
|
109 if (fDraw->addPath->isPath()) { |
|
110 drawPath = (SkDrawPath*) fDraw->addPath; |
|
111 } else { |
|
112 SkApply* apply = (SkApply*) fDraw->addPath; |
|
113 apply->refresh(*fMaker); |
|
114 apply->activate(*fMaker); |
|
115 apply->interpolate(*fMaker, SkScalarRoundToInt(distance * 1000)); |
|
116 drawPath = (SkDrawPath*) apply->getScope(); |
|
117 } |
|
118 SkMatrix m; |
|
119 m.reset(); |
|
120 if (fDraw->addMatrix) { |
|
121 SkDrawMatrix* matrix; |
|
122 if (fDraw->addMatrix->getType() == SkType_Matrix) |
|
123 matrix = (SkDrawMatrix*) fDraw->addMatrix; |
|
124 else { |
|
125 SkApply* apply = (SkApply*) fDraw->addMatrix; |
|
126 apply->refresh(*fMaker); |
|
127 apply->activate(*fMaker); |
|
128 apply->interpolate(*fMaker, SkScalarRoundToInt(distance * 1000)); |
|
129 matrix = (SkDrawMatrix*) apply->getScope(); |
|
130 } |
|
131 if (matrix) { |
|
132 m = matrix->getMatrix(); |
|
133 } |
|
134 } |
|
135 SkScalar result = 0; |
|
136 SkAnimatorScript::EvaluateFloat(*fMaker, NULL, fDraw->spacing.c_str(), &result); |
|
137 if (drawPath) |
|
138 dst->addPath(drawPath->getPath(), m); |
|
139 fMaker->clearExtraPropertyCallBack(fDraw->fType); |
|
140 return result; |
|
141 } |
|
142 |
|
143 private: |
|
144 static bool GetContourLength(const char* token, size_t len, void* clen, SkScriptValue* value) { |
|
145 if (SK_LITERAL_STR_EQUAL("contourLength", token, len)) { |
|
146 value->fOperand.fScalar = *(SkScalar*) clen; |
|
147 value->fType = SkType_Float; |
|
148 return true; |
|
149 } |
|
150 return false; |
|
151 } |
|
152 |
|
153 static bool GetDistance(const char* token, size_t len, void* dist, SkScriptValue* value) { |
|
154 if (SK_LITERAL_STR_EQUAL("distance", token, len)) { |
|
155 value->fOperand.fScalar = *(SkScalar*) dist; |
|
156 value->fType = SkType_Float; |
|
157 return true; |
|
158 } |
|
159 return false; |
|
160 } |
|
161 |
|
162 SkDrawShape1DPathEffect* fDraw; |
|
163 SkAnimateMaker* fMaker; |
|
164 }; |
|
165 |
|
166 //////////// SkDrawShapePathEffect |
|
167 |
|
168 #if SK_USE_CONDENSED_INFO == 0 |
|
169 |
|
170 const SkMemberInfo SkDrawShapePathEffect::fInfo[] = { |
|
171 SK_MEMBER(addMatrix, Drawable), // either matrix or apply |
|
172 SK_MEMBER(addPath, Drawable), // either path or apply |
|
173 SK_MEMBER(path, Path), |
|
174 }; |
|
175 |
|
176 #endif |
|
177 |
|
178 DEFINE_GET_MEMBER(SkDrawShapePathEffect); |
|
179 |
|
180 SkDrawShapePathEffect::SkDrawShapePathEffect() : |
|
181 addPath(NULL), addMatrix(NULL), path(NULL), fPathEffect(NULL) { |
|
182 } |
|
183 |
|
184 SkDrawShapePathEffect::~SkDrawShapePathEffect() { |
|
185 SkSafeUnref(fPathEffect); |
|
186 } |
|
187 |
|
188 bool SkDrawShapePathEffect::addChild(SkAnimateMaker& , SkDisplayable* child) { |
|
189 path = (SkDrawPath*) child; |
|
190 return true; |
|
191 } |
|
192 |
|
193 SkPathEffect* SkDrawShapePathEffect::getPathEffect() { |
|
194 fPathEffect->ref(); |
|
195 return fPathEffect; |
|
196 } |
|
197 |
|
198 //////////// SkDrawShape1DPathEffect |
|
199 |
|
200 #if SK_USE_CONDENSED_INFO == 0 |
|
201 |
|
202 const SkMemberInfo SkDrawShape1DPathEffect::fInfo[] = { |
|
203 SK_MEMBER_INHERITED, |
|
204 SK_MEMBER(phase, String), |
|
205 SK_MEMBER(spacing, String), |
|
206 }; |
|
207 |
|
208 #endif |
|
209 |
|
210 DEFINE_GET_MEMBER(SkDrawShape1DPathEffect); |
|
211 |
|
212 SkDrawShape1DPathEffect::SkDrawShape1DPathEffect(SkDisplayTypes type) : fType(type) { |
|
213 } |
|
214 |
|
215 SkDrawShape1DPathEffect::~SkDrawShape1DPathEffect() { |
|
216 } |
|
217 |
|
218 void SkDrawShape1DPathEffect::onEndElement(SkAnimateMaker& maker) { |
|
219 if (addPath == NULL || (addPath->isPath() == false && addPath->isApply() == false)) |
|
220 maker.setErrorCode(SkDisplayXMLParserError::kUnknownError); // !!! add error |
|
221 else |
|
222 fPathEffect = new SkShape1DPathEffect(this, &maker); |
|
223 } |
|
224 |
|
225 ////////// SkShape2DPathEffect |
|
226 |
|
227 class SkShape2DPathEffect : public Sk2DPathEffect { |
|
228 public: |
|
229 SkShape2DPathEffect(SkDrawShape2DPathEffect* draw, SkAnimateMaker* maker, |
|
230 const SkMatrix& matrix) : Sk2DPathEffect(matrix), fDraw(draw), fMaker(maker) { |
|
231 } |
|
232 |
|
233 protected: |
|
234 virtual void begin(const SkIRect& uvBounds, SkPath*) const SK_OVERRIDE { |
|
235 const_cast<SkShape2DPathEffect*>(this)->setUVBounds(uvBounds); |
|
236 } |
|
237 |
|
238 virtual void next(const SkPoint& loc, int u, int v, SkPath* dst) const SK_OVERRIDE { |
|
239 const_cast<SkShape2DPathEffect*>(this)->addPath(loc, u, v, dst); |
|
240 } |
|
241 |
|
242 private: |
|
243 void setUVBounds(const SkIRect& uvBounds) { |
|
244 fUVBounds.set(SkIntToScalar(uvBounds.fLeft), SkIntToScalar(uvBounds.fTop), |
|
245 SkIntToScalar(uvBounds.fRight), SkIntToScalar(uvBounds.fBottom)); |
|
246 } |
|
247 |
|
248 void addPath(const SkPoint& loc, int u, int v, SkPath* dst) { |
|
249 fLoc = loc; |
|
250 fU = u; |
|
251 fV = v; |
|
252 SkDrawPath* drawPath; |
|
253 fMaker->setExtraPropertyCallBack(fDraw->fType, Get2D, this); |
|
254 if (fDraw->addPath->isPath()) { |
|
255 drawPath = (SkDrawPath*) fDraw->addPath; |
|
256 } else { |
|
257 SkApply* apply = (SkApply*) fDraw->addPath; |
|
258 apply->refresh(*fMaker); |
|
259 apply->activate(*fMaker); |
|
260 apply->interpolate(*fMaker, v); |
|
261 drawPath = (SkDrawPath*) apply->getScope(); |
|
262 } |
|
263 if (drawPath == NULL) |
|
264 goto clearCallBack; |
|
265 if (fDraw->matrix) { |
|
266 SkDrawMatrix* matrix; |
|
267 if (fDraw->matrix->getType() == SkType_Matrix) |
|
268 matrix = (SkDrawMatrix*) fDraw->matrix; |
|
269 else { |
|
270 SkApply* apply = (SkApply*) fDraw->matrix; |
|
271 apply->activate(*fMaker); |
|
272 apply->interpolate(*fMaker, v); |
|
273 matrix = (SkDrawMatrix*) apply->getScope(); |
|
274 } |
|
275 if (matrix) { |
|
276 dst->addPath(drawPath->getPath(), matrix->getMatrix()); |
|
277 goto clearCallBack; |
|
278 } |
|
279 } |
|
280 dst->addPath(drawPath->getPath()); |
|
281 clearCallBack: |
|
282 fMaker->clearExtraPropertyCallBack(fDraw->fType); |
|
283 } |
|
284 |
|
285 static bool Get2D(const char* token, size_t len, void* s2D, SkScriptValue* value) { |
|
286 static const char match[] = "locX|locY|left|top|right|bottom|u|v" ; |
|
287 SkShape2DPathEffect* shape2D = (SkShape2DPathEffect*) s2D; |
|
288 int index; |
|
289 if (SkAnimatorScript::MapEnums(match, token, len, &index) == false) |
|
290 return false; |
|
291 SkASSERT((sizeof(SkPoint) + sizeof(SkRect)) / sizeof(SkScalar) == 6); |
|
292 if (index < 6) { |
|
293 value->fType = SkType_Float; |
|
294 value->fOperand.fScalar = (&shape2D->fLoc.fX)[index]; |
|
295 } else { |
|
296 value->fType = SkType_Int; |
|
297 value->fOperand.fS32 = (&shape2D->fU)[index - 6]; |
|
298 } |
|
299 return true; |
|
300 } |
|
301 |
|
302 SkPoint fLoc; |
|
303 SkRect fUVBounds; |
|
304 int32_t fU; |
|
305 int32_t fV; |
|
306 SkDrawShape2DPathEffect* fDraw; |
|
307 SkAnimateMaker* fMaker; |
|
308 |
|
309 // illegal |
|
310 SkShape2DPathEffect(const SkShape2DPathEffect&); |
|
311 SkShape2DPathEffect& operator=(const SkShape2DPathEffect&); |
|
312 }; |
|
313 |
|
314 ////////// SkDrawShape2DPathEffect |
|
315 |
|
316 #if SK_USE_CONDENSED_INFO == 0 |
|
317 |
|
318 const SkMemberInfo SkDrawShape2DPathEffect::fInfo[] = { |
|
319 SK_MEMBER_INHERITED, |
|
320 SK_MEMBER(matrix, Matrix) |
|
321 }; |
|
322 |
|
323 #endif |
|
324 |
|
325 DEFINE_GET_MEMBER(SkDrawShape2DPathEffect); |
|
326 |
|
327 SkDrawShape2DPathEffect::SkDrawShape2DPathEffect(SkDisplayTypes type) : fType(type) { |
|
328 } |
|
329 |
|
330 SkDrawShape2DPathEffect::~SkDrawShape2DPathEffect() { |
|
331 } |
|
332 |
|
333 void SkDrawShape2DPathEffect::onEndElement(SkAnimateMaker& maker) { |
|
334 if (addPath == NULL || (addPath->isPath() == false && addPath->isApply() == false) || |
|
335 matrix == NULL) |
|
336 maker.setErrorCode(SkDisplayXMLParserError::kUnknownError); // !!! add error |
|
337 else |
|
338 fPathEffect = new SkShape2DPathEffect(this, &maker, matrix->getMatrix()); |
|
339 } |
|
340 |
|
341 ////////// SkDrawComposePathEffect |
|
342 |
|
343 #if SK_USE_CONDENSED_INFO == 0 |
|
344 |
|
345 const SkMemberInfo SkDrawComposePathEffect::fInfo[] = { |
|
346 SK_MEMBER(effect1, PathEffect), |
|
347 SK_MEMBER(effect2, PathEffect) |
|
348 }; |
|
349 |
|
350 #endif |
|
351 |
|
352 DEFINE_GET_MEMBER(SkDrawComposePathEffect); |
|
353 |
|
354 SkDrawComposePathEffect::SkDrawComposePathEffect(SkDisplayTypes type) : fType(type), |
|
355 effect1(NULL), effect2(NULL) { |
|
356 } |
|
357 |
|
358 SkDrawComposePathEffect::~SkDrawComposePathEffect() { |
|
359 delete effect1; |
|
360 delete effect2; |
|
361 } |
|
362 |
|
363 bool SkDrawComposePathEffect::addChild(SkAnimateMaker& , SkDisplayable* child) { |
|
364 if (effect1 == NULL) |
|
365 effect1 = (SkDrawPathEffect*) child; |
|
366 else |
|
367 effect2 = (SkDrawPathEffect*) child; |
|
368 return true; |
|
369 } |
|
370 |
|
371 SkPathEffect* SkDrawComposePathEffect::getPathEffect() { |
|
372 SkPathEffect* e1 = effect1->getPathEffect(); |
|
373 SkPathEffect* e2 = effect2->getPathEffect(); |
|
374 SkPathEffect* composite = SkComposePathEffect::Create(e1, e2); |
|
375 e1->unref(); |
|
376 e2->unref(); |
|
377 return composite; |
|
378 } |
|
379 |
|
380 bool SkDrawComposePathEffect::isPaint() const { |
|
381 return true; |
|
382 } |
|
383 |
|
384 //////////// SkDrawCornerPathEffect |
|
385 |
|
386 #if SK_USE_CONDENSED_INFO == 0 |
|
387 |
|
388 const SkMemberInfo SkDrawCornerPathEffect::fInfo[] = { |
|
389 SK_MEMBER(radius, Float) |
|
390 }; |
|
391 |
|
392 #endif |
|
393 |
|
394 DEFINE_GET_MEMBER(SkDrawCornerPathEffect); |
|
395 |
|
396 SkDrawCornerPathEffect::SkDrawCornerPathEffect(SkDisplayTypes type): |
|
397 fType(type), radius(0) { |
|
398 } |
|
399 |
|
400 SkDrawCornerPathEffect::~SkDrawCornerPathEffect() { |
|
401 } |
|
402 |
|
403 SkPathEffect* SkDrawCornerPathEffect::getPathEffect() { |
|
404 return SkCornerPathEffect::Create(radius); |
|
405 } |
|
406 |
|
407 ///////// |
|
408 |
|
409 #include "SkExtras.h" |
|
410 |
|
411 const char kDrawShape1DPathEffectName[] = "pathEffect:shape1D"; |
|
412 const char kDrawShape2DPathEffectName[] = "pathEffect:shape2D"; |
|
413 const char kDrawComposePathEffectName[] = "pathEffect:compose"; |
|
414 const char kDrawCornerPathEffectName[] = "pathEffect:corner"; |
|
415 |
|
416 class SkExtraPathEffects : public SkExtras { |
|
417 public: |
|
418 SkExtraPathEffects() : |
|
419 skDrawShape1DPathEffectType(SkType_Unknown), |
|
420 skDrawShape2DPathEffectType(SkType_Unknown), |
|
421 skDrawComposePathEffectType(SkType_Unknown), |
|
422 skDrawCornerPathEffectType(SkType_Unknown) { |
|
423 } |
|
424 |
|
425 virtual SkDisplayable* createInstance(SkDisplayTypes type) { |
|
426 SkDisplayable* result = NULL; |
|
427 if (skDrawShape1DPathEffectType == type) |
|
428 result = new SkDrawShape1DPathEffect(type); |
|
429 else if (skDrawShape2DPathEffectType == type) |
|
430 result = new SkDrawShape2DPathEffect(type); |
|
431 else if (skDrawComposePathEffectType == type) |
|
432 result = new SkDrawComposePathEffect(type); |
|
433 else if (skDrawCornerPathEffectType == type) |
|
434 result = new SkDrawCornerPathEffect(type); |
|
435 return result; |
|
436 } |
|
437 |
|
438 virtual bool definesType(SkDisplayTypes type) { |
|
439 return type == skDrawShape1DPathEffectType || |
|
440 type == skDrawShape2DPathEffectType || |
|
441 type == skDrawComposePathEffectType || |
|
442 type == skDrawCornerPathEffectType; |
|
443 } |
|
444 |
|
445 #if SK_USE_CONDENSED_INFO == 0 |
|
446 virtual const SkMemberInfo* getMembers(SkDisplayTypes type, int* infoCountPtr) { |
|
447 const SkMemberInfo* info = NULL; |
|
448 int infoCount = 0; |
|
449 if (skDrawShape1DPathEffectType == type) { |
|
450 info = SkDrawShape1DPathEffect::fInfo; |
|
451 infoCount = SkDrawShape1DPathEffect::fInfoCount; |
|
452 } else if (skDrawShape2DPathEffectType == type) { |
|
453 info = SkDrawShape2DPathEffect::fInfo; |
|
454 infoCount = SkDrawShape2DPathEffect::fInfoCount; |
|
455 } else if (skDrawComposePathEffectType == type) { |
|
456 info = SkDrawComposePathEffect::fInfo; |
|
457 infoCount = SkDrawShape1DPathEffect::fInfoCount; |
|
458 } else if (skDrawCornerPathEffectType == type) { |
|
459 info = SkDrawCornerPathEffect::fInfo; |
|
460 infoCount = SkDrawCornerPathEffect::fInfoCount; |
|
461 } |
|
462 if (infoCountPtr) |
|
463 *infoCountPtr = infoCount; |
|
464 return info; |
|
465 } |
|
466 #endif |
|
467 |
|
468 #ifdef SK_DEBUG |
|
469 virtual const char* getName(SkDisplayTypes type) { |
|
470 if (skDrawShape1DPathEffectType == type) |
|
471 return kDrawShape1DPathEffectName; |
|
472 else if (skDrawShape2DPathEffectType == type) |
|
473 return kDrawShape2DPathEffectName; |
|
474 else if (skDrawComposePathEffectType == type) |
|
475 return kDrawComposePathEffectName; |
|
476 else if (skDrawCornerPathEffectType == type) |
|
477 return kDrawCornerPathEffectName; |
|
478 return NULL; |
|
479 } |
|
480 #endif |
|
481 |
|
482 virtual SkDisplayTypes getType(const char name[], size_t len ) { |
|
483 SkDisplayTypes* type = NULL; |
|
484 if (SK_LITERAL_STR_EQUAL(kDrawShape1DPathEffectName, name, len)) |
|
485 type = &skDrawShape1DPathEffectType; |
|
486 else if (SK_LITERAL_STR_EQUAL(kDrawShape2DPathEffectName, name, len)) |
|
487 type = &skDrawShape2DPathEffectType; |
|
488 else if (SK_LITERAL_STR_EQUAL(kDrawComposePathEffectName, name, len)) |
|
489 type = &skDrawComposePathEffectType; |
|
490 else if (SK_LITERAL_STR_EQUAL(kDrawCornerPathEffectName, name, len)) |
|
491 type = &skDrawCornerPathEffectType; |
|
492 if (type) { |
|
493 if (*type == SkType_Unknown) |
|
494 *type = SkDisplayType::RegisterNewType(); |
|
495 return *type; |
|
496 } |
|
497 return SkType_Unknown; |
|
498 } |
|
499 |
|
500 private: |
|
501 SkDisplayTypes skDrawShape1DPathEffectType; |
|
502 SkDisplayTypes skDrawShape2DPathEffectType; |
|
503 SkDisplayTypes skDrawComposePathEffectType; |
|
504 SkDisplayTypes skDrawCornerPathEffectType; |
|
505 }; |
|
506 |
|
507 void InitializeSkExtraPathEffects(SkAnimator* animator) { |
|
508 animator->addExtras(new SkExtraPathEffects()); |
|
509 } |
|
510 |
|
511 //////////////// |
|
512 |
|
513 |
|
514 SkExtras::SkExtras() : fExtraCallBack(NULL), fExtraStorage(NULL) { |
|
515 } |