OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2014 Google Inc. | 2 * Copyright 2014 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "GrStencilAndCoverTextContext.h" | 8 #include "GrStencilAndCoverTextContext.h" |
9 #include "GrAtlasTextContext.h" | 9 #include "GrAtlasTextContext.h" |
10 #include "GrContext.h" | 10 #include "GrContext.h" |
(...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
228 GrPipelineBuilder pipelineBuilder(paint, dc->accessRenderTarget(), clip); | 228 GrPipelineBuilder pipelineBuilder(paint, dc->accessRenderTarget(), clip); |
229 | 229 |
230 TextBlob::Iter iter(blob); | 230 TextBlob::Iter iter(blob); |
231 for (TextRun* run = iter.get(); run; run = iter.next()) { | 231 for (TextRun* run = iter.get(); run; run = iter.next()) { |
232 run->draw(context, dc, &pipelineBuilder, paint.getColor(), viewMatrix, p
rops, x, y, | 232 run->draw(context, dc, &pipelineBuilder, paint.getColor(), viewMatrix, p
rops, x, y, |
233 clipBounds, fFallbackTextContext, skPaint); | 233 clipBounds, fFallbackTextContext, skPaint); |
234 run->releaseGlyphCache(); | 234 run->releaseGlyphCache(); |
235 } | 235 } |
236 } | 236 } |
237 | 237 |
238 static inline int style_key_cnt(const GrStyle& style) { | |
239 int cnt = GrStyle::KeySize(style, GrStyle::Apply::kPathEffectAndStrokeRec); | |
240 // We should be able to make a key because we filtered out arbitrary path ef
fects. | |
241 SkASSERT(cnt > 0); | |
242 return cnt; | |
243 } | |
244 | |
245 static inline void write_style_key(uint32_t* dst, const GrStyle& style) { | |
246 // Pass 1 for the scale since the GPU will apply the style not GrStyle::appl
yToPath(). | |
247 GrStyle::WriteKey(dst, style, GrStyle::Apply::kPathEffectAndStrokeRec, SK_Sc
alar1); | |
248 } | |
249 | |
250 const GrStencilAndCoverTextContext::TextBlob& | 238 const GrStencilAndCoverTextContext::TextBlob& |
251 GrStencilAndCoverTextContext::findOrCreateTextBlob(const SkTextBlob* skBlob, | 239 GrStencilAndCoverTextContext::findOrCreateTextBlob(const SkTextBlob* skBlob, |
252 const SkPaint& skPaint) { | 240 const SkPaint& skPaint) { |
253 // The font-related parameters are baked into the text blob and will overrid
e this skPaint, so | 241 // The font-related parameters are baked into the text blob and will overrid
e this skPaint, so |
254 // the only remaining properties that can affect a TextBlob are the ones rel
ated to stroke. | 242 // the only remaining properties that can affect a TextBlob are the ones rel
ated to stroke. |
255 if (SkPaint::kFill_Style == skPaint.getStyle()) { // Fast path. | 243 if (SkPaint::kFill_Style == skPaint.getStyle()) { // Fast path. |
256 if (TextBlob** found = fBlobIdCache.find(skBlob->uniqueID())) { | 244 if (TextBlob** found = fBlobIdCache.find(skBlob->uniqueID())) { |
257 fLRUList.remove(*found); | 245 fLRUList.remove(*found); |
258 fLRUList.addToTail(*found); | 246 fLRUList.addToTail(*found); |
259 return **found; | 247 return **found; |
260 } | 248 } |
261 TextBlob* blob = new TextBlob(skBlob->uniqueID(), skBlob, skPaint); | 249 TextBlob* blob = new TextBlob(skBlob->uniqueID(), skBlob, skPaint); |
262 this->purgeToFit(*blob); | 250 this->purgeToFit(*blob); |
263 fBlobIdCache.set(skBlob->uniqueID(), blob); | 251 fBlobIdCache.set(skBlob->uniqueID(), blob); |
264 fLRUList.addToTail(blob); | 252 fLRUList.addToTail(blob); |
265 fCacheSize += blob->cpuMemorySize(); | 253 fCacheSize += blob->cpuMemorySize(); |
266 return *blob; | 254 return *blob; |
267 } else { | 255 } else { |
268 GrStyle style(skPaint); | 256 GrStrokeInfo stroke(skPaint); |
269 SkSTArray<4, uint32_t, true> key; | 257 SkSTArray<4, uint32_t, true> key; |
270 key.reset(1 + style_key_cnt(style)); | 258 key.reset(1 + stroke.computeUniqueKeyFragmentData32Cnt()); |
271 key[0] = skBlob->uniqueID(); | 259 key[0] = skBlob->uniqueID(); |
272 write_style_key(&key[1], style); | 260 stroke.asUniqueKeyFragment(&key[1]); |
273 if (TextBlob** found = fBlobKeyCache.find(key)) { | 261 if (TextBlob** found = fBlobKeyCache.find(key)) { |
274 fLRUList.remove(*found); | 262 fLRUList.remove(*found); |
275 fLRUList.addToTail(*found); | 263 fLRUList.addToTail(*found); |
276 return **found; | 264 return **found; |
277 } | 265 } |
278 TextBlob* blob = new TextBlob(key, skBlob, skPaint); | 266 TextBlob* blob = new TextBlob(key, skBlob, skPaint); |
279 this->purgeToFit(*blob); | 267 this->purgeToFit(*blob); |
280 fBlobKeyCache.set(blob); | 268 fBlobKeyCache.set(blob); |
281 fLRUList.addToTail(blob); | 269 fLRUList.addToTail(blob); |
282 fCacheSize += blob->cpuMemorySize(); | 270 fCacheSize += blob->cpuMemorySize(); |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
358 SkPaint fFont; | 346 SkPaint fFont; |
359 int fBuffIdx; | 347 int fBuffIdx; |
360 int fCount; | 348 int fCount; |
361 uint16_t fGlyphIds[kWriteBufferSize]; | 349 uint16_t fGlyphIds[kWriteBufferSize]; |
362 SkPoint fPositions[kWriteBufferSize]; | 350 SkPoint fPositions[kWriteBufferSize]; |
363 }; | 351 }; |
364 | 352 |
365 ////////////////////////////////////////////////////////////////////////////////
//////////////////// | 353 ////////////////////////////////////////////////////////////////////////////////
//////////////////// |
366 | 354 |
367 GrStencilAndCoverTextContext::TextRun::TextRun(const SkPaint& fontAndStroke) | 355 GrStencilAndCoverTextContext::TextRun::TextRun(const SkPaint& fontAndStroke) |
368 : fStyle(fontAndStroke), | 356 : fStroke(fontAndStroke), |
369 fFont(fontAndStroke), | 357 fFont(fontAndStroke), |
370 fTotalGlyphCount(0), | 358 fTotalGlyphCount(0), |
371 fFallbackGlyphCount(0), | 359 fFallbackGlyphCount(0), |
372 fDetachedGlyphCache(nullptr), | 360 fDetachedGlyphCache(nullptr), |
373 fLastDrawnGlyphsID(SK_InvalidUniqueID) { | 361 fLastDrawnGlyphsID(SK_InvalidUniqueID) { |
374 SkASSERT(fFont.getTextSize() > 0); | 362 SkASSERT(fFont.getTextSize() > 0); |
375 SkASSERT(!fStyle.hasNonDashPathEffect()); // Arbitrary path effects not supp
orted. | 363 SkASSERT(!fStroke.isHairlineStyle()); // Hairlines are not supported. |
376 SkASSERT(!fStyle.isSimpleHairline()); // Hairlines are not supported. | |
377 | 364 |
378 // Setting to "fill" ensures that no strokes get baked into font outlines. (
We use the GPU path | 365 // Setting to "fill" ensures that no strokes get baked into font outlines. (
We use the GPU path |
379 // rendering API for stroking). | 366 // rendering API for stroking). |
380 fFont.setStyle(SkPaint::kFill_Style); | 367 fFont.setStyle(SkPaint::kFill_Style); |
381 | 368 |
382 if (fFont.isFakeBoldText() && fStyle.isSimpleFill()) { | 369 if (fFont.isFakeBoldText() && SkStrokeRec::kStroke_Style != fStroke.getStyle
()) { |
383 const SkStrokeRec& stroke = fStyle.strokeRec(); | |
384 // Instead of letting fake bold get baked into the glyph outlines, do it
with GPU stroke. | 370 // Instead of letting fake bold get baked into the glyph outlines, do it
with GPU stroke. |
385 SkScalar fakeBoldScale = SkScalarInterpFunc(fFont.getTextSize(), | 371 SkScalar fakeBoldScale = SkScalarInterpFunc(fFont.getTextSize(), |
386 kStdFakeBoldInterpKeys, | 372 kStdFakeBoldInterpKeys, |
387 kStdFakeBoldInterpValues, | 373 kStdFakeBoldInterpValues, |
388 kStdFakeBoldInterpLength); | 374 kStdFakeBoldInterpLength); |
389 SkScalar extra = SkScalarMul(fFont.getTextSize(), fakeBoldScale); | 375 SkScalar extra = SkScalarMul(fFont.getTextSize(), fakeBoldScale); |
| 376 fStroke.setStrokeStyle(fStroke.needToApply() ? fStroke.getWidth() + extr
a : extra, |
| 377 true /*strokeAndFill*/); |
390 | 378 |
391 SkStrokeRec strokeRec(SkStrokeRec::kFill_InitStyle); | |
392 strokeRec.setStrokeStyle(stroke.needToApply() ? stroke.getWidth() + extr
a : extra, | |
393 true /*strokeAndFill*/); | |
394 fStyle = GrStyle(strokeRec, fStyle.pathEffect()); | |
395 fFont.setFakeBoldText(false); | 379 fFont.setFakeBoldText(false); |
396 } | 380 } |
397 | 381 |
398 if (!fFont.getPathEffect() && !fStyle.isDashed()) { | 382 if (!fFont.getPathEffect() && !fStroke.isDashed()) { |
399 const SkStrokeRec& stroke = fStyle.strokeRec(); | |
400 // We can draw the glyphs from canonically sized paths. | 383 // We can draw the glyphs from canonically sized paths. |
401 fTextRatio = fFont.getTextSize() / SkPaint::kCanonicalTextSizeForPaths; | 384 fTextRatio = fFont.getTextSize() / SkPaint::kCanonicalTextSizeForPaths; |
402 fTextInverseRatio = SkPaint::kCanonicalTextSizeForPaths / fFont.getTextS
ize(); | 385 fTextInverseRatio = SkPaint::kCanonicalTextSizeForPaths / fFont.getTextS
ize(); |
403 | 386 |
404 // Compensate for the glyphs being scaled by fTextRatio. | 387 // Compensate for the glyphs being scaled by fTextRatio. |
405 if (!fStyle.isSimpleFill()) { | 388 if (!fStroke.isFillStyle()) { |
406 SkStrokeRec strokeRec(SkStrokeRec::kFill_InitStyle); | 389 fStroke.setStrokeStyle(fStroke.getWidth() / fTextRatio, |
407 strokeRec.setStrokeStyle(stroke.getWidth() / fTextRatio, | 390 SkStrokeRec::kStrokeAndFill_Style == fStroke.
getStyle()); |
408 SkStrokeRec::kStrokeAndFill_Style == stroke
.getStyle()); | |
409 fStyle = GrStyle(strokeRec, fStyle.pathEffect()); | |
410 } | 391 } |
411 | 392 |
412 fFont.setLinearText(true); | 393 fFont.setLinearText(true); |
413 fFont.setLCDRenderText(false); | 394 fFont.setLCDRenderText(false); |
414 fFont.setAutohinted(false); | 395 fFont.setAutohinted(false); |
415 fFont.setHinting(SkPaint::kNo_Hinting); | 396 fFont.setHinting(SkPaint::kNo_Hinting); |
416 fFont.setSubpixelText(true); | 397 fFont.setSubpixelText(true); |
417 fFont.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths)); | 398 fFont.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths)); |
418 | 399 |
419 fUsingRawGlyphPaths = SK_Scalar1 == fFont.getTextScaleX() && | 400 fUsingRawGlyphPaths = SK_Scalar1 == fFont.getTextScaleX() && |
420 0 == fFont.getTextSkewX() && | 401 0 == fFont.getTextSkewX() && |
421 !fFont.isFakeBoldText() && | 402 !fFont.isFakeBoldText() && |
422 !fFont.isVerticalText(); | 403 !fFont.isVerticalText(); |
423 } else { | 404 } else { |
424 fTextRatio = fTextInverseRatio = 1.0f; | 405 fTextRatio = fTextInverseRatio = 1.0f; |
425 fUsingRawGlyphPaths = false; | 406 fUsingRawGlyphPaths = false; |
426 } | 407 } |
427 | 408 |
428 // Generate the key that will be used to cache the GPU glyph path objects. | 409 // Generate the key that will be used to cache the GPU glyph path objects. |
429 if (fUsingRawGlyphPaths && fStyle.isSimpleFill()) { | 410 if (fUsingRawGlyphPaths && fStroke.isFillStyle()) { |
430 static const GrUniqueKey::Domain kRawFillPathGlyphDomain = GrUniqueKey::
GenerateDomain(); | 411 static const GrUniqueKey::Domain kRawFillPathGlyphDomain = GrUniqueKey::
GenerateDomain(); |
431 | 412 |
432 const SkTypeface* typeface = fFont.getTypeface(); | 413 const SkTypeface* typeface = fFont.getTypeface(); |
433 GrUniqueKey::Builder builder(&fGlyphPathsKey, kRawFillPathGlyphDomain, 1
); | 414 GrUniqueKey::Builder builder(&fGlyphPathsKey, kRawFillPathGlyphDomain, 1
); |
434 reinterpret_cast<uint32_t&>(builder[0]) = typeface ? typeface->uniqueID(
) : 0; | 415 reinterpret_cast<uint32_t&>(builder[0]) = typeface ? typeface->uniqueID(
) : 0; |
435 } else { | 416 } else { |
436 static const GrUniqueKey::Domain kPathGlyphDomain = GrUniqueKey::Generat
eDomain(); | 417 static const GrUniqueKey::Domain kPathGlyphDomain = GrUniqueKey::Generat
eDomain(); |
437 | 418 |
438 int styleDataCount = GrStyle::KeySize(fStyle, GrStyle::Apply::kPathEffec
tAndStrokeRec); | 419 int strokeDataCount = fStroke.computeUniqueKeyFragmentData32Cnt(); |
439 // Key should be valid since we opted out of drawing arbitrary path effe
cts. | |
440 SkASSERT(styleDataCount >= 0); | |
441 if (fUsingRawGlyphPaths) { | 420 if (fUsingRawGlyphPaths) { |
442 const SkTypeface* typeface = fFont.getTypeface(); | 421 const SkTypeface* typeface = fFont.getTypeface(); |
443 GrUniqueKey::Builder builder(&fGlyphPathsKey, kPathGlyphDomain, 2 +
styleDataCount); | 422 GrUniqueKey::Builder builder(&fGlyphPathsKey, kPathGlyphDomain, 2 +
strokeDataCount); |
444 reinterpret_cast<uint32_t&>(builder[0]) = typeface ? typeface->uniqu
eID() : 0; | 423 reinterpret_cast<uint32_t&>(builder[0]) = typeface ? typeface->uniqu
eID() : 0; |
445 reinterpret_cast<uint32_t&>(builder[1]) = styleDataCount; | 424 reinterpret_cast<uint32_t&>(builder[1]) = strokeDataCount; |
446 if (styleDataCount) { | 425 fStroke.asUniqueKeyFragment(&builder[2]); |
447 write_style_key(&builder[2], fStyle); | |
448 } | |
449 } else { | 426 } else { |
450 SkGlyphCache* glyphCache = this->getGlyphCache(); | 427 SkGlyphCache* glyphCache = this->getGlyphCache(); |
451 const SkTypeface* typeface = glyphCache->getScalerContext()->getType
face(); | 428 const SkTypeface* typeface = glyphCache->getScalerContext()->getType
face(); |
452 const SkDescriptor* desc = &glyphCache->getDescriptor(); | 429 const SkDescriptor* desc = &glyphCache->getDescriptor(); |
453 int descDataCount = (desc->getLength() + 3) / 4; | 430 int descDataCount = (desc->getLength() + 3) / 4; |
454 GrUniqueKey::Builder builder(&fGlyphPathsKey, kPathGlyphDomain, | 431 GrUniqueKey::Builder builder(&fGlyphPathsKey, kPathGlyphDomain, |
455 2 + styleDataCount + descDataCount); | 432 2 + strokeDataCount + descDataCount); |
456 reinterpret_cast<uint32_t&>(builder[0]) = typeface ? typeface->uniqu
eID() : 0; | 433 reinterpret_cast<uint32_t&>(builder[0]) = typeface ? typeface->uniqu
eID() : 0; |
457 reinterpret_cast<uint32_t&>(builder[1]) = styleDataCount | (descData
Count << 16); | 434 reinterpret_cast<uint32_t&>(builder[1]) = strokeDataCount | (descDat
aCount << 16); |
458 if (styleDataCount) { | 435 fStroke.asUniqueKeyFragment(&builder[2]); |
459 write_style_key(&builder[2], fStyle); | 436 memcpy(&builder[2 + strokeDataCount], desc, desc->getLength()); |
460 } | |
461 memcpy(&builder[2 + styleDataCount], desc, desc->getLength()); | |
462 } | 437 } |
463 } | 438 } |
464 } | 439 } |
465 | 440 |
466 GrStencilAndCoverTextContext::TextRun::~TextRun() { | 441 GrStencilAndCoverTextContext::TextRun::~TextRun() { |
467 this->releaseGlyphCache(); | 442 this->releaseGlyphCache(); |
468 } | 443 } |
469 | 444 |
470 void GrStencilAndCoverTextContext::TextRun::setText(const char text[], size_t by
teLength, | 445 void GrStencilAndCoverTextContext::TextRun::setText(const char text[], size_t by
teLength, |
471 SkScalar x, SkScalar y) { | 446 SkScalar x, SkScalar y) { |
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
559 fFallbackTextBlob.reset(fallback.buildIfNeeded(&fFallbackGlyphCount)); | 534 fFallbackTextBlob.reset(fallback.buildIfNeeded(&fFallbackGlyphCount)); |
560 } | 535 } |
561 | 536 |
562 GrPathRange* GrStencilAndCoverTextContext::TextRun::createGlyphs(GrContext* ctx)
const { | 537 GrPathRange* GrStencilAndCoverTextContext::TextRun::createGlyphs(GrContext* ctx)
const { |
563 GrPathRange* glyphs = static_cast<GrPathRange*>( | 538 GrPathRange* glyphs = static_cast<GrPathRange*>( |
564 ctx->resourceProvider()->findAndRefResourceByUniqueKey(fGlyphPathsKe
y)); | 539 ctx->resourceProvider()->findAndRefResourceByUniqueKey(fGlyphPathsKe
y)); |
565 if (nullptr == glyphs) { | 540 if (nullptr == glyphs) { |
566 if (fUsingRawGlyphPaths) { | 541 if (fUsingRawGlyphPaths) { |
567 SkScalerContextEffects noeffects; | 542 SkScalerContextEffects noeffects; |
568 glyphs = ctx->resourceProvider()->createGlyphs(fFont.getTypeface(),
noeffects, | 543 glyphs = ctx->resourceProvider()->createGlyphs(fFont.getTypeface(),
noeffects, |
569 nullptr, fStyle); | 544 nullptr, fStroke); |
570 } else { | 545 } else { |
571 SkGlyphCache* cache = this->getGlyphCache(); | 546 SkGlyphCache* cache = this->getGlyphCache(); |
572 glyphs = ctx->resourceProvider()->createGlyphs(cache->getScalerConte
xt()->getTypeface(), | 547 glyphs = ctx->resourceProvider()->createGlyphs(cache->getScalerConte
xt()->getTypeface(), |
573 cache->getScalerConte
xt()->getEffects(), | 548 cache->getScalerConte
xt()->getEffects(), |
574 &cache->getDescriptor
(), | 549 &cache->getDescriptor
(), |
575 fStyle); | 550 fStroke); |
576 } | 551 } |
577 ctx->resourceProvider()->assignUniqueKeyToResource(fGlyphPathsKey, glyph
s); | 552 ctx->resourceProvider()->assignUniqueKeyToResource(fGlyphPathsKey, glyph
s); |
578 } | 553 } |
579 return glyphs; | 554 return glyphs; |
580 } | 555 } |
581 | 556 |
582 inline void GrStencilAndCoverTextContext::TextRun::appendGlyph(const SkGlyph& gl
yph, | 557 inline void GrStencilAndCoverTextContext::TextRun::appendGlyph(const SkGlyph& gl
yph, |
583 const SkPoint& po
s, | 558 const SkPoint& po
s, |
584 FallbackBlobBuild
er* fallback) { | 559 FallbackBlobBuild
er* fallback) { |
585 // Stick the glyphs we can't draw into the fallback text blob. | 560 // Stick the glyphs we can't draw into the fallback text blob. |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
639 GrDrawPathRangeBatch::Create(viewMatrix, fTextRatio, fTextInverseRat
io * x, | 614 GrDrawPathRangeBatch::Create(viewMatrix, fTextRatio, fTextInverseRat
io * x, |
640 fTextInverseRatio * y, color, | 615 fTextInverseRatio * y, color, |
641 GrPathRendering::kWinding_FillType, gly
phs, fInstanceData, | 616 GrPathRendering::kWinding_FillType, gly
phs, fInstanceData, |
642 bounds)); | 617 bounds)); |
643 | 618 |
644 dc->drawPathBatch(*pipelineBuilder, batch); | 619 dc->drawPathBatch(*pipelineBuilder, batch); |
645 } | 620 } |
646 | 621 |
647 if (fFallbackTextBlob) { | 622 if (fFallbackTextBlob) { |
648 SkPaint fallbackSkPaint(originalSkPaint); | 623 SkPaint fallbackSkPaint(originalSkPaint); |
649 fStyle.strokeRec().applyToPaint(&fallbackSkPaint); | 624 fStroke.applyToPaint(&fallbackSkPaint); |
650 if (!fStyle.isSimpleFill()) { | 625 if (!fStroke.isFillStyle()) { |
651 fallbackSkPaint.setStrokeWidth(fStyle.strokeRec().getWidth() * fText
Ratio); | 626 fallbackSkPaint.setStrokeWidth(fStroke.getWidth() * fTextRatio); |
652 } | 627 } |
653 | 628 |
654 fallbackTextContext->drawTextBlob(ctx, dc, pipelineBuilder->clip(), fall
backSkPaint, | 629 fallbackTextContext->drawTextBlob(ctx, dc, pipelineBuilder->clip(), fall
backSkPaint, |
655 viewMatrix, props, fFallbackTextBlob,
x, y, nullptr, | 630 viewMatrix, props, fFallbackTextBlob,
x, y, nullptr, |
656 clipBounds); | 631 clipBounds); |
657 } | 632 } |
658 } | 633 } |
659 | 634 |
660 SkGlyphCache* GrStencilAndCoverTextContext::TextRun::getGlyphCache() const { | 635 SkGlyphCache* GrStencilAndCoverTextContext::TextRun::getGlyphCache() const { |
661 if (!fDetachedGlyphCache) { | 636 if (!fDetachedGlyphCache) { |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
728 } | 703 } |
729 | 704 |
730 const SkTextBlob* GrStencilAndCoverTextContext::FallbackBlobBuilder::buildIfNeed
ed(int *count) { | 705 const SkTextBlob* GrStencilAndCoverTextContext::FallbackBlobBuilder::buildIfNeed
ed(int *count) { |
731 *count = fCount; | 706 *count = fCount; |
732 if (fCount) { | 707 if (fCount) { |
733 this->flush(); | 708 this->flush(); |
734 return fBuilder->build(); | 709 return fBuilder->build(); |
735 } | 710 } |
736 return nullptr; | 711 return nullptr; |
737 } | 712 } |
OLD | NEW |