From dabd4f0b799318cb6e90b69ae1ec0ed0b6d32f60 Mon Sep 17 00:00:00 2001 From: halcanary Date: Wed, 3 Aug 2016 11:16:56 -0700 Subject: [PATCH] SkPDF: PDFShader code modernized. Motivation: reduce code complexity. SkCanon stores SkPDFShader::State next to SkDFObject, not inside. many places use sk_sp rather than T* to represent ownership. SkPDFShader::State no longer holds bitmap. SkPDFShader::State gets move constructor, no longer heap-allocated. Classes removed: SkPDFFunctionShader SkPDFAlphaFunctionShader SkPDFImageShader BUG=skia: GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2193973002 Review-Url: https://codereview.chromium.org/2193973002 --- src/pdf/SkPDFCanon.cpp | 64 ++++--- src/pdf/SkPDFCanon.h | 26 +-- src/pdf/SkPDFDevice.cpp | 55 +++--- src/pdf/SkPDFDevice.h | 6 +- src/pdf/SkPDFGraphicState.cpp | 4 +- src/pdf/SkPDFGraphicState.h | 2 +- src/pdf/SkPDFShader.cpp | 319 ++++++++++++++-------------------- src/pdf/SkPDFShader.h | 84 ++++----- src/pdf/SkPDFTypes.h | 2 +- tests/CPlusPlusEleven.cpp | 20 +++ 10 files changed, 265 insertions(+), 317 deletions(-) diff --git a/src/pdf/SkPDFCanon.cpp b/src/pdf/SkPDFCanon.cpp index 11bfb892d1..3dcf4e9f0f 100644 --- a/src/pdf/SkPDFCanon.cpp +++ b/src/pdf/SkPDFCanon.cpp @@ -17,12 +17,13 @@ void SkPDFCanon::reset() { fFontRecords[i].fFont->unref(); } fFontRecords.reset(); - fFunctionShaderRecords.unrefAll(); + fFunctionShaderRecords.reset(); - fAlphaShaderRecords.unrefAll(); fAlphaShaderRecords.reset(); - fImageShaderRecords.unrefAll(); fImageShaderRecords.reset(); + + // TODO(halcanary): make SkTHashSet work nicely with sk_sp<>, + // or use std::unordered_set<> fGraphicStateRecords.foreach ([](WrapGS w) { w.fPtr->unref(); }); fGraphicStateRecords.reset(); @@ -32,21 +33,6 @@ void SkPDFCanon::reset() { //////////////////////////////////////////////////////////////////////////////// -template T* assert_ptr(T* p) { SkASSERT(p); return p; } - -// requires `bool T::equals(const U&) const` -template -T* find_item(const SkTDArray& ptrArray, const U& object) { - for (int i = 0; i < ptrArray.count(); ++i) { - if (ptrArray[i]->equals(object)) { - return ptrArray[i]; - } - } - return nullptr; -} - -//////////////////////////////////////////////////////////////////////////////// - SkPDFFont* SkPDFCanon::findFont(uint32_t fontID, uint16_t glyphID, SkPDFFont** relatedFontPtr) const { @@ -76,33 +62,43 @@ void SkPDFCanon::addFont(SkPDFFont* font, uint32_t fontID, uint16_t fGlyphID) { //////////////////////////////////////////////////////////////////////////////// -SkPDFFunctionShader* SkPDFCanon::findFunctionShader( +template +sk_sp find_shader(const SkTArray& records, + const SkPDFShader::State& state) { + for (const T& record : records) { + if (record.fShaderState == state) { + return record.fShaderObject; + } + } + return nullptr; +} + +sk_sp SkPDFCanon::findFunctionShader( const SkPDFShader::State& state) const { - return find_item(fFunctionShaderRecords, state); + return find_shader(fFunctionShaderRecords, state); } -void SkPDFCanon::addFunctionShader(SkPDFFunctionShader* pdfShader) { - fFunctionShaderRecords.push(SkRef(pdfShader)); +void SkPDFCanon::addFunctionShader(sk_sp pdfShader, + SkPDFShader::State state) { + fFunctionShaderRecords.emplace_back(std::move(state), std::move(pdfShader)); } -//////////////////////////////////////////////////////////////////////////////// - -SkPDFAlphaFunctionShader* SkPDFCanon::findAlphaShader( +sk_sp SkPDFCanon::findAlphaShader( const SkPDFShader::State& state) const { - return find_item(fAlphaShaderRecords, state); + return find_shader(fAlphaShaderRecords, state); } -void SkPDFCanon::addAlphaShader(SkPDFAlphaFunctionShader* pdfShader) { - fAlphaShaderRecords.push(SkRef(pdfShader)); +void SkPDFCanon::addAlphaShader(sk_sp pdfShader, + SkPDFShader::State state) { + fAlphaShaderRecords.emplace_back(std::move(state), std::move(pdfShader)); } -//////////////////////////////////////////////////////////////////////////////// - -SkPDFImageShader* SkPDFCanon::findImageShader( +sk_sp SkPDFCanon::findImageShader( const SkPDFShader::State& state) const { - return find_item(fImageShaderRecords, state); + return find_shader(fImageShaderRecords, state); } -void SkPDFCanon::addImageShader(SkPDFImageShader* pdfShader) { - fImageShaderRecords.push(SkRef(pdfShader)); +void SkPDFCanon::addImageShader(sk_sp pdfShader, + SkPDFShader::State state) { + fImageShaderRecords.emplace_back(std::move(state), std::move(pdfShader)); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/pdf/SkPDFCanon.h b/src/pdf/SkPDFCanon.h index cae93c97c9..a0241e308f 100644 --- a/src/pdf/SkPDFCanon.h +++ b/src/pdf/SkPDFCanon.h @@ -48,14 +48,14 @@ public: SkPDFFont** relatedFont) const; void addFont(SkPDFFont* font, uint32_t fontID, uint16_t fGlyphID); - SkPDFFunctionShader* findFunctionShader(const SkPDFShader::State&) const; - void addFunctionShader(SkPDFFunctionShader*); + sk_sp findFunctionShader(const SkPDFShader::State&) const; + void addFunctionShader(sk_sp, SkPDFShader::State); - SkPDFAlphaFunctionShader* findAlphaShader(const SkPDFShader::State&) const; - void addAlphaShader(SkPDFAlphaFunctionShader*); + sk_sp findAlphaShader(const SkPDFShader::State&) const; + void addAlphaShader(sk_sp, SkPDFShader::State); - SkPDFImageShader* findImageShader(const SkPDFShader::State&) const; - void addImageShader(SkPDFImageShader*); + sk_sp findImageShader(const SkPDFShader::State&) const; + void addImageShader(sk_sp, SkPDFShader::State); const SkPDFGraphicState* findGraphicState(const SkPDFGraphicState&) const; void addGraphicState(const SkPDFGraphicState*); @@ -82,11 +82,15 @@ private: }; SkTDArray fFontRecords; - SkTDArray fFunctionShaderRecords; - - SkTDArray fAlphaShaderRecords; - - SkTDArray fImageShaderRecords; + struct ShaderRec { + SkPDFShader::State fShaderState; + sk_sp fShaderObject; + ShaderRec(SkPDFShader::State s, sk_sp o) + : fShaderState(std::move(s)), fShaderObject(std::move(o)) {} + }; + SkTArray fFunctionShaderRecords; + SkTArray fAlphaShaderRecords; + SkTArray fImageShaderRecords; struct WrapGS { explicit WrapGS(const SkPDFGraphicState* ptr = nullptr) : fPtr(ptr) {} diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp index 211ce50b3d..7d025780a6 100644 --- a/src/pdf/SkPDFDevice.cpp +++ b/src/pdf/SkPDFDevice.cpp @@ -564,9 +564,8 @@ public: if (shape->isEmpty()) { shape = nullptr; } - fDevice->finishContentEntry(fXfermode, fDstFormXObject, shape); + fDevice->finishContentEntry(fXfermode, std::move(fDstFormXObject), shape); } - SkSafeUnref(fDstFormXObject); } SkPDFDevice::ContentEntry* entry() { return fContentEntry; } @@ -609,7 +608,7 @@ private: SkPDFDevice* fDevice; SkPDFDevice::ContentEntry* fContentEntry; SkXfermode::Mode fXfermode; - SkPDFObject* fDstFormXObject; + sk_sp fDstFormXObject; SkPath fShape; void init(const SkClipStack* clipStack, const SkRegion& clipRegion, @@ -1606,7 +1605,7 @@ sk_sp SkPDFDevice::makeFormXObjectFromDevice() { } void SkPDFDevice::drawFormXObjectWithMask(int xObjectIndex, - SkPDFObject* mask, + sk_sp mask, const SkClipStack* clipStack, const SkRegion& clipRegion, SkXfermode::Mode mode, @@ -1616,7 +1615,8 @@ void SkPDFDevice::drawFormXObjectWithMask(int xObjectIndex, } sk_sp sMaskGS = SkPDFGraphicState::GetSMaskGraphicState( - mask, invertClip, SkPDFGraphicState::kAlpha_SMaskMode, fDocument->canon()); + std::move(mask), invertClip, + SkPDFGraphicState::kAlpha_SMaskMode, fDocument->canon()); SkMatrix identity; identity.reset(); @@ -1643,7 +1643,7 @@ SkPDFDevice::ContentEntry* SkPDFDevice::setUpContentEntry(const SkClipStack* cli const SkMatrix& matrix, const SkPaint& paint, bool hasText, - SkPDFObject** dst) { + sk_sp* dst) { *dst = nullptr; if (clipRegion.isEmpty()) { return nullptr; @@ -1684,8 +1684,7 @@ SkPDFDevice::ContentEntry* SkPDFDevice::setUpContentEntry(const SkClipStack* cli xfermode == SkXfermode::kDstATop_Mode || xfermode == SkXfermode::kModulate_Mode) { if (!isContentEmpty()) { - // TODO(halcanary): make this safer. - *dst = this->makeFormXObjectFromDevice().release(); + *dst = this->makeFormXObjectFromDevice(); SkASSERT(isContentEmpty()); } else if (xfermode != SkXfermode::kSrc_Mode && xfermode != SkXfermode::kSrcOut_Mode) { @@ -1716,7 +1715,7 @@ SkPDFDevice::ContentEntry* SkPDFDevice::setUpContentEntry(const SkClipStack* cli } void SkPDFDevice::finishContentEntry(SkXfermode::Mode xfermode, - SkPDFObject* dst, + sk_sp dst, SkPath* shape) { if (xfermode != SkXfermode::kClear_Mode && xfermode != SkXfermode::kSrc_Mode && @@ -1773,7 +1772,8 @@ void SkPDFDevice::finishContentEntry(SkXfermode::Mode xfermode, ScopedContentEntry content(this, &fExistingClipStack, fExistingClipRegion, identity, stockPaint); - SkPDFUtils::DrawFormXObject(this->addXObjectResource(dst), + // TODO: addXObjectResource take sk_sp + SkPDFUtils::DrawFormXObject(this->addXObjectResource(dst.get()), &content.entry()->fContent); return; } else { @@ -1795,8 +1795,6 @@ void SkPDFDevice::finishContentEntry(SkXfermode::Mode xfermode, &fExistingClipStack, fExistingClipRegion, SkXfermode::kSrcOver_Mode, true); } else { - sk_sp dstMaskStorage; - SkPDFObject* dstMask = srcFormXObject.get(); if (shape != nullptr) { // Draw shape into a form-xobject. SkRasterClip rc(clipRegion); @@ -1808,13 +1806,16 @@ void SkPDFDevice::finishContentEntry(SkXfermode::Mode xfermode, filledPaint.setColor(SK_ColorBLACK); filledPaint.setStyle(SkPaint::kFill_Style); this->drawPath(d, *shape, filledPaint, nullptr, true); + drawFormXObjectWithMask(addXObjectResource(dst.get()), + this->makeFormXObjectFromDevice(), + &fExistingClipStack, fExistingClipRegion, + SkXfermode::kSrcOver_Mode, true); - dstMaskStorage = this->makeFormXObjectFromDevice(); - dstMask = dstMaskStorage.get(); + } else { + drawFormXObjectWithMask(addXObjectResource(dst.get()), srcFormXObject, + &fExistingClipStack, fExistingClipRegion, + SkXfermode::kSrcOver_Mode, true); } - drawFormXObjectWithMask(addXObjectResource(dst), dstMask, - &fExistingClipStack, fExistingClipRegion, - SkXfermode::kSrcOver_Mode, true); } if (xfermode == SkXfermode::kClear_Mode) { @@ -1835,7 +1836,7 @@ void SkPDFDevice::finishContentEntry(SkXfermode::Mode xfermode, ScopedContentEntry content(this, &fExistingClipStack, fExistingClipRegion, identity, stockPaint); if (content.entry()) { - SkPDFUtils::DrawFormXObject(this->addXObjectResource(dst), + SkPDFUtils::DrawFormXObject(this->addXObjectResource(dst.get()), &content.entry()->fContent); } } @@ -1851,22 +1852,26 @@ void SkPDFDevice::finishContentEntry(SkXfermode::Mode xfermode, if (xfermode == SkXfermode::kSrcIn_Mode || xfermode == SkXfermode::kSrcOut_Mode || xfermode == SkXfermode::kSrcATop_Mode) { - drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()), dst, + drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()), + std::move(dst), &fExistingClipStack, fExistingClipRegion, SkXfermode::kSrcOver_Mode, xfermode == SkXfermode::kSrcOut_Mode); + return; } else { SkXfermode::Mode mode = SkXfermode::kSrcOver_Mode; + int resourceID = addXObjectResource(dst.get()); if (xfermode == SkXfermode::kModulate_Mode) { drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()), - dst, &fExistingClipStack, + std::move(dst), &fExistingClipStack, fExistingClipRegion, SkXfermode::kSrcOver_Mode, false); mode = SkXfermode::kMultiply_Mode; } - drawFormXObjectWithMask(addXObjectResource(dst), srcFormXObject.get(), + drawFormXObjectWithMask(resourceID, std::move(srcFormXObject), &fExistingClipStack, fExistingClipRegion, mode, xfermode == SkXfermode::kDstOut_Mode); + return; } } @@ -1918,8 +1923,8 @@ void SkPDFDevice::populateGraphicStateEntryFromPaint( SkScalar rasterScale = SkIntToScalar(fRasterDpi) / DPI_FOR_RASTER_SCALE_ONE; - pdfShader.reset(SkPDFShader::GetPDFShader( - fDocument, fRasterDpi, shader, transform, bounds, rasterScale)); + pdfShader = SkPDFShader::GetPDFShader( + fDocument, fRasterDpi, shader, transform, bounds, rasterScale); if (pdfShader.get()) { // pdfShader has been canonicalized so we can directly compare @@ -1981,13 +1986,13 @@ int SkPDFDevice::addGraphicStateResource(SkPDFObject* gs) { } int SkPDFDevice::addXObjectResource(SkPDFObject* xObject) { + // TODO(halcanary): make this take a sk_sp // Assumes that xobject has been canonicalized (so we can directly compare // pointers). int result = fXObjectResources.find(xObject); if (result < 0) { result = fXObjectResources.count(); - fXObjectResources.push(xObject); - xObject->ref(); + fXObjectResources.push(SkRef(xObject)); } return result; } diff --git a/src/pdf/SkPDFDevice.h b/src/pdf/SkPDFDevice.h index d5d52f98e3..5fedd0eb4c 100644 --- a/src/pdf/SkPDFDevice.h +++ b/src/pdf/SkPDFDevice.h @@ -271,7 +271,7 @@ private: sk_sp makeFormXObjectFromDevice(); void drawFormXObjectWithMask(int xObjectIndex, - SkPDFObject* mask, + sk_sp mask, const SkClipStack* clipStack, const SkRegion& clipRegion, SkXfermode::Mode mode, @@ -286,9 +286,9 @@ private: const SkMatrix& matrix, const SkPaint& paint, bool hasText, - SkPDFObject** dst); + sk_sp* dst); void finishContentEntry(SkXfermode::Mode xfermode, - SkPDFObject* dst, + sk_sp dst, SkPath* shape); bool isContentEmpty(); diff --git a/src/pdf/SkPDFGraphicState.cpp b/src/pdf/SkPDFGraphicState.cpp index 7b793e6ea8..5a603ce8da 100644 --- a/src/pdf/SkPDFGraphicState.cpp +++ b/src/pdf/SkPDFGraphicState.cpp @@ -144,7 +144,7 @@ sk_sp SkPDFGraphicState::MakeInvertFunction() { } sk_sp SkPDFGraphicState::GetSMaskGraphicState( - SkPDFObject* sMask, + sk_sp sMask, bool invert, SkPDFSMaskMode sMaskMode, SkPDFCanon* canon) { @@ -156,7 +156,7 @@ sk_sp SkPDFGraphicState::GetSMaskGraphicState( } else if (sMaskMode == kLuminosity_SMaskMode) { sMaskDict->insertName("S", "Luminosity"); } - sMaskDict->insertObjRef("G", sk_ref_sp(sMask)); + sMaskDict->insertObjRef("G", std::move(sMask)); if (invert) { // Instead of calling SkPDFGraphicState::MakeInvertFunction, // let the canon deduplicate this object. diff --git a/src/pdf/SkPDFGraphicState.h b/src/pdf/SkPDFGraphicState.h index 6b3f7cccf0..84491ba0d3 100644 --- a/src/pdf/SkPDFGraphicState.h +++ b/src/pdf/SkPDFGraphicState.h @@ -51,7 +51,7 @@ public: * * These are not de-duped. */ - static sk_sp GetSMaskGraphicState(SkPDFObject* sMask, + static sk_sp GetSMaskGraphicState(sk_sp sMask, bool invert, SkPDFSMaskMode sMaskMode, SkPDFCanon* canon); diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp index 885b7e6049..f7aef6304a 100644 --- a/src/pdf/SkPDFShader.cpp +++ b/src/pdf/SkPDFShader.cpp @@ -523,112 +523,65 @@ static void drawBitmapMatrix(SkCanvas* canvas, const SkBitmap& bm, const SkMatri canvas->drawBitmap(bm, 0, 0); } -class SkPDFShader::State { -public: - SkShader::GradientType fType; - SkShader::GradientInfo fInfo; - SkAutoFree fColorData; // This provides storage for arrays in fInfo. - SkMatrix fCanvasTransform; - SkMatrix fShaderTransform; - SkIRect fBBox; - - SkBitmap fImage; - SkBitmapKey fBitmapKey; - SkShader::TileMode fImageTileModes[2]; - - State(SkShader* shader, const SkMatrix& canvasTransform, - const SkIRect& bbox, SkScalar rasterScale); - - bool operator==(const State& b) const; - - SkPDFShader::State* CreateAlphaToLuminosityState() const; - SkPDFShader::State* CreateOpaqueState() const; - - bool GradientHasAlpha() const; - -private: - State(const State& other); - State operator=(const State& rhs); - void AllocateGradientInfoStorage(); -}; - //////////////////////////////////////////////////////////////////////////////// -SkPDFFunctionShader::SkPDFFunctionShader(SkPDFShader::State* state) - : SkPDFDict("Pattern"), fShaderState(state) { - state->fImage.reset(); -} +static sk_sp make_alpha_function_shader(SkPDFDocument* doc, + SkScalar dpi, + const SkPDFShader::State& state); +static sk_sp make_function_shader(SkPDFCanon* canon, + const SkPDFShader::State& state); -SkPDFFunctionShader::~SkPDFFunctionShader() {} +static sk_sp make_image_shader(SkPDFDocument* doc, + SkScalar dpi, + const SkPDFShader::State& state, + SkBitmap image); -bool SkPDFFunctionShader::equals(const SkPDFShader::State& state) const { - return state == *fShaderState; -} - -//////////////////////////////////////////////////////////////////////////////// - -SkPDFAlphaFunctionShader::SkPDFAlphaFunctionShader(SkPDFShader::State* state) - : fShaderState(state) { - state->fImage.reset(); -} - -bool SkPDFAlphaFunctionShader::equals(const SkPDFShader::State& state) const { - return state == *fShaderState; -} - -SkPDFAlphaFunctionShader::~SkPDFAlphaFunctionShader() {} - -//////////////////////////////////////////////////////////////////////////////// - -SkPDFImageShader::SkPDFImageShader(SkPDFShader::State* state) - : fShaderState(state) { - state->fImage.reset(); -} - -bool SkPDFImageShader::equals(const SkPDFShader::State& state) const { - return state == *fShaderState; -} - -SkPDFImageShader::~SkPDFImageShader() {} - -//////////////////////////////////////////////////////////////////////////////// - -static SkPDFObject* get_pdf_shader_by_state( +static sk_sp get_pdf_shader_by_state( SkPDFDocument* doc, SkScalar dpi, - std::unique_ptr* autoState) { - const SkPDFShader::State& state = **autoState; + SkPDFShader::State state, + SkBitmap image) { SkPDFCanon* canon = doc->canon(); - if (state.fType == SkShader::kNone_GradientType && state.fImage.isNull()) { + if (state.fType == SkShader::kNone_GradientType && image.isNull()) { // TODO(vandebo) This drops SKComposeShader on the floor. We could // handle compose shader by pulling things up to a layer, drawing with // the first shader, applying the xfer mode and drawing again with the // second shader, then applying the layer to the original drawing. return nullptr; } else if (state.fType == SkShader::kNone_GradientType) { - SkPDFObject* shader = canon->findImageShader(state); - return shader ? SkRef(shader) - : SkPDFImageShader::Create(doc, dpi, autoState); + sk_sp shader = canon->findImageShader(state); + if (!shader) { + shader = make_image_shader(doc, dpi, state, std::move(image)); + canon->addImageShader(shader, std::move(state)); + } + return shader; } else if (state.GradientHasAlpha()) { - SkPDFObject* shader = canon->findAlphaShader(state); - return shader ? SkRef(shader) - : SkPDFAlphaFunctionShader::Create(doc, dpi, autoState); + sk_sp shader = canon->findAlphaShader(state); + if (!shader) { + shader = make_alpha_function_shader(doc, dpi, state); + canon->addAlphaShader(shader, std::move(state)); + } + return shader; } else { - SkPDFObject* shader = canon->findFunctionShader(state); - return shader ? SkRef(shader) - : SkPDFFunctionShader::Create(canon, autoState); + sk_sp shader = canon->findFunctionShader(state); + if (!shader) { + shader = make_function_shader(canon, state); + canon->addFunctionShader(shader, std::move(state)); + } + return shader; } } -// static -SkPDFObject* SkPDFShader::GetPDFShader(SkPDFDocument* doc, - SkScalar dpi, - SkShader* shader, - const SkMatrix& matrix, - const SkIRect& surfaceBBox, - SkScalar rasterScale) { - std::unique_ptr state(new State(shader, matrix, surfaceBBox, rasterScale)); - return get_pdf_shader_by_state(doc, dpi, &state); +sk_sp SkPDFShader::GetPDFShader(SkPDFDocument* doc, + SkScalar dpi, + SkShader* shader, + const SkMatrix& matrix, + const SkIRect& surfaceBBox, + SkScalar rasterScale) { + SkBitmap image; + State state(shader, matrix, surfaceBBox, rasterScale, &image); + return get_pdf_shader_by_state( + doc, dpi, std::move(state), std::move(image)); } static sk_sp get_gradient_resource_dict( @@ -647,7 +600,7 @@ static sk_sp get_gradient_resource_dict( static void populate_tiling_pattern_dict(SkPDFDict* pattern, SkRect& bbox, - SkPDFDict* resources, + sk_sp resources, const SkMatrix& matrix) { const int kTiling_PatternType = 1; const int kColoredTilingPattern_PaintType = 1; @@ -660,7 +613,7 @@ static void populate_tiling_pattern_dict(SkPDFDict* pattern, pattern->insertObject("BBox", SkPDFUtils::RectToArray(bbox)); pattern->insertScalar("XStep", bbox.width()); pattern->insertScalar("YStep", bbox.height()); - pattern->insertObject("Resources", sk_ref_sp(resources)); + pattern->insertObject("Resources", std::move(resources)); if (!matrix.isIdentity()) { pattern->insertObject("Matrix", SkPDFUtils::MatrixToArray(matrix)); } @@ -671,7 +624,8 @@ static void populate_tiling_pattern_dict(SkPDFDict* pattern, * @param gsIndex A graphics state resource index to apply, or <0 if no * graphics state to apply. */ -static SkStreamAsset* create_pattern_fill_content(int gsIndex, SkRect& bounds) { +static std::unique_ptr create_pattern_fill_content( + int gsIndex, SkRect& bounds) { SkDynamicMemoryWStream content; if (gsIndex >= 0) { SkPDFUtils::ApplyGraphicState(gsIndex, &content); @@ -681,7 +635,7 @@ static SkStreamAsset* create_pattern_fill_content(int gsIndex, SkRect& bounds) { SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPath::kEvenOdd_FillType, &content); - return content.detachAsStream(); + return std::unique_ptr(content.detachAsStream()); } /** @@ -693,10 +647,9 @@ static sk_sp create_smask_graphic_state( SkRect bbox; bbox.set(state.fBBox); - std::unique_ptr alphaToLuminosityState( - state.CreateAlphaToLuminosityState()); sk_sp luminosityShader( - get_pdf_shader_by_state(doc, dpi, &alphaToLuminosityState)); + get_pdf_shader_by_state(doc, dpi, state.MakeAlphaToLuminosityState(), + SkBitmap())); std::unique_ptr alphaStream(create_pattern_fill_content(-1, bbox)); @@ -709,22 +662,20 @@ static sk_sp create_smask_graphic_state( SkMatrix::I(), "DeviceRGB"); return SkPDFGraphicState::GetSMaskGraphicState( - alphaMask.get(), false, + std::move(alphaMask), false, SkPDFGraphicState::kLuminosity_SMaskMode, doc->canon()); } -SkPDFAlphaFunctionShader* SkPDFAlphaFunctionShader::Create( - SkPDFDocument* doc, - SkScalar dpi, - std::unique_ptr* autoState) { - const SkPDFShader::State& state = **autoState; +static sk_sp make_alpha_function_shader(SkPDFDocument* doc, + SkScalar dpi, + const SkPDFShader::State& state) { SkRect bbox; bbox.set(state.fBBox); - std::unique_ptr opaqueState(state.CreateOpaqueState()); + SkPDFShader::State opaqueState(state.MakeOpaqueState()); sk_sp colorShader( - get_pdf_shader_by_state(doc, dpi, &opaqueState)); + get_pdf_shader_by_state(doc, dpi, std::move(opaqueState), SkBitmap())); if (!colorShader) { return nullptr; } @@ -733,20 +684,15 @@ SkPDFAlphaFunctionShader* SkPDFAlphaFunctionShader::Create( // pattern shader as P0, then write content stream. auto alphaGs = create_smask_graphic_state(doc, dpi, state); - SkPDFAlphaFunctionShader* alphaFunctionShader = - new SkPDFAlphaFunctionShader(autoState->release()); - auto resourceDict = get_gradient_resource_dict(colorShader.get(), alphaGs.get()); std::unique_ptr colorStream( create_pattern_fill_content(0, bbox)); - alphaFunctionShader->setData(std::move(colorStream)); + auto alphaFunctionShader = sk_make_sp(std::move(colorStream)); - populate_tiling_pattern_dict( - alphaFunctionShader->dict(), bbox, resourceDict.get(), - SkMatrix::I()); - doc->canon()->addAlphaShader(alphaFunctionShader); + populate_tiling_pattern_dict(alphaFunctionShader->dict(), bbox, + std::move(resourceDict), SkMatrix::I()); return alphaFunctionShader; } @@ -802,11 +748,11 @@ sk_sp SkPDFShader::MakeRangeObject() { static sk_sp make_ps_function( std::unique_ptr psCode, - SkPDFArray* domain, + sk_sp domain, sk_sp range) { auto result = sk_make_sp(std::move(psCode)); result->dict()->insertInt("FunctionType", 4); - result->dict()->insertObject("Domain", sk_ref_sp(domain)); + result->dict()->insertObject("Domain", std::move(domain)); result->dict()->insertObject("Range", std::move(range)); return result; } @@ -826,10 +772,8 @@ static void FixUpRadius(const SkPoint& p1, SkScalar& r1, const SkPoint& p2, SkSc } } -SkPDFFunctionShader* SkPDFFunctionShader::Create( - SkPDFCanon* canon, std::unique_ptr* autoState) { - const SkPDFShader::State& state = **autoState; - +static sk_sp make_function_shader(SkPDFCanon* canon, + const SkPDFShader::State& state) { void (*codeFunction)(const SkShader::GradientInfo& info, const SkMatrix& perspectiveRemover, SkDynamicMemoryWStream* function) = nullptr; @@ -839,10 +783,10 @@ SkPDFFunctionShader* SkPDFFunctionShader::Create( finalMatrix.preConcat(state.fShaderTransform); bool doStitchFunctions = (state.fType == SkShader::kLinear_GradientType || - state.fType == SkShader::kRadial_GradientType || - state.fType == SkShader::kConical_GradientType) && - info->fTileMode == SkShader::kClamp_TileMode && - !finalMatrix.hasPerspective(); + state.fType == SkShader::kRadial_GradientType || + state.fType == SkShader::kConical_GradientType) && + info->fTileMode == SkShader::kClamp_TileMode && + !finalMatrix.hasPerspective(); auto domain = sk_make_sp(); @@ -984,39 +928,37 @@ SkPDFFunctionShader* SkPDFFunctionShader::Create( codeFunction(*info, perspectiveInverseOnly, &functionCode); } - pdfShader->insertObject("Domain", sk_ref_sp(domain.get())); + pdfShader->insertObject("Domain", domain); // Call canon->makeRangeObject() instead of // SkPDFShader::MakeRangeObject() so that the canon can // deduplicate. std::unique_ptr functionStream( functionCode.detachAsStream()); - auto function = make_ps_function(std::move(functionStream), domain.get(), - canon->makeRangeObject()); + sk_sp function = make_ps_function(std::move(functionStream), + std::move(domain), + canon->makeRangeObject()); pdfShader->insertObjRef("Function", std::move(function)); } pdfShader->insertInt("ShadingType", shadingType); pdfShader->insertName("ColorSpace", "DeviceRGB"); - sk_sp pdfFunctionShader( - new SkPDFFunctionShader(autoState->release())); + auto pdfFunctionShader = sk_make_sp("Pattern"); pdfFunctionShader->insertInt("PatternType", 2); pdfFunctionShader->insertObject("Matrix", SkPDFUtils::MatrixToArray(finalMatrix)); pdfFunctionShader->insertObject("Shading", std::move(pdfShader)); - canon->addFunctionShader(pdfFunctionShader.get()); - return pdfFunctionShader.release(); + return pdfFunctionShader; } -SkPDFImageShader* SkPDFImageShader::Create( - SkPDFDocument* doc, - SkScalar dpi, - std::unique_ptr* autoState) { - const SkPDFShader::State& state = **autoState; - - state.fImage.lockPixels(); +static sk_sp make_image_shader(SkPDFDocument* doc, + SkScalar dpi, + const SkPDFShader::State& state, + SkBitmap image) { + SkASSERT(state.fBitmapKey == SkBitmapKey(image)); + SkAutoLockPixels SkAutoLockPixels(image); // The image shader pattern cell will be drawn into a separate device // in pattern cell space (no scaling on the bitmap, though there may be @@ -1032,9 +974,8 @@ SkPDFImageShader* SkPDFImageShader::Create( return nullptr; } - const SkBitmap* image = &state.fImage; SkRect bitmapBounds; - image->getBounds(&bitmapBounds); + image.getBounds(&bitmapBounds); // For tiling modes, the bounds should be extended to include the bitmap, // otherwise the bitmap gets clipped out and the shader is empty and awful. @@ -1055,7 +996,7 @@ SkPDFImageShader* SkPDFImageShader::Create( SkCanvas canvas(patternDevice.get()); SkRect patternBBox; - image->getBounds(&patternBBox); + image.getBounds(&patternBBox); // Translate the canvas so that the bitmap origin is at (0, 0). canvas.translate(-deviceBounds.left(), -deviceBounds.top()); @@ -1066,24 +1007,24 @@ SkPDFImageShader* SkPDFImageShader::Create( // If the bitmap is out of bounds (i.e. clamp mode where we only see the // stretched sides), canvas will clip this out and the extraneous data // won't be saved to the PDF. - canvas.drawBitmap(*image, 0, 0); + canvas.drawBitmap(image, 0, 0); - SkScalar width = SkIntToScalar(image->width()); - SkScalar height = SkIntToScalar(image->height()); + SkScalar width = SkIntToScalar(image.width()); + SkScalar height = SkIntToScalar(image.height()); // Tiling is implied. First we handle mirroring. if (tileModes[0] == SkShader::kMirror_TileMode) { SkMatrix xMirror; xMirror.setScale(-1, 1); xMirror.postTranslate(2 * width, 0); - drawBitmapMatrix(&canvas, *image, xMirror); + drawBitmapMatrix(&canvas, image, xMirror); patternBBox.fRight += width; } if (tileModes[1] == SkShader::kMirror_TileMode) { SkMatrix yMirror; yMirror.setScale(SK_Scalar1, -SK_Scalar1); yMirror.postTranslate(0, 2 * height); - drawBitmapMatrix(&canvas, *image, yMirror); + drawBitmapMatrix(&canvas, image, yMirror); patternBBox.fBottom += height; } if (tileModes[0] == SkShader::kMirror_TileMode && @@ -1091,7 +1032,7 @@ SkPDFImageShader* SkPDFImageShader::Create( SkMatrix mirror; mirror.setScale(-1, -1); mirror.postTranslate(2 * width, 2 * height); - drawBitmapMatrix(&canvas, *image, mirror); + drawBitmapMatrix(&canvas, image, mirror); } // Then handle Clamping, which requires expanding the pattern canvas to @@ -1105,39 +1046,39 @@ SkPDFImageShader* SkPDFImageShader::Create( SkRect rect; rect = SkRect::MakeLTRB(deviceBounds.left(), deviceBounds.top(), 0, 0); if (!rect.isEmpty()) { - paint.setColor(image->getColor(0, 0)); + paint.setColor(image.getColor(0, 0)); canvas.drawRect(rect, paint); } rect = SkRect::MakeLTRB(width, deviceBounds.top(), deviceBounds.right(), 0); if (!rect.isEmpty()) { - paint.setColor(image->getColor(image->width() - 1, 0)); + paint.setColor(image.getColor(image.width() - 1, 0)); canvas.drawRect(rect, paint); } rect = SkRect::MakeLTRB(width, height, deviceBounds.right(), deviceBounds.bottom()); if (!rect.isEmpty()) { - paint.setColor(image->getColor(image->width() - 1, - image->height() - 1)); + paint.setColor(image.getColor(image.width() - 1, + image.height() - 1)); canvas.drawRect(rect, paint); } rect = SkRect::MakeLTRB(deviceBounds.left(), height, 0, deviceBounds.bottom()); if (!rect.isEmpty()) { - paint.setColor(image->getColor(0, image->height() - 1)); + paint.setColor(image.getColor(0, image.height() - 1)); canvas.drawRect(rect, paint); } } // Then expand the left, right, top, then bottom. if (tileModes[0] == SkShader::kClamp_TileMode) { - SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, image->height()); + SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, image.height()); if (deviceBounds.left() < 0) { SkBitmap left; - SkAssertResult(image->extractSubset(&left, subset)); + SkAssertResult(image.extractSubset(&left, subset)); SkMatrix leftMatrix; leftMatrix.setScale(-deviceBounds.left(), 1); @@ -1154,8 +1095,8 @@ SkPDFImageShader* SkPDFImageShader::Create( if (deviceBounds.right() > width) { SkBitmap right; - subset.offset(image->width() - 1, 0); - SkAssertResult(image->extractSubset(&right, subset)); + subset.offset(image.width() - 1, 0); + SkAssertResult(image.extractSubset(&right, subset)); SkMatrix rightMatrix; rightMatrix.setScale(deviceBounds.right() - width, 1); @@ -1172,10 +1113,10 @@ SkPDFImageShader* SkPDFImageShader::Create( } if (tileModes[1] == SkShader::kClamp_TileMode) { - SkIRect subset = SkIRect::MakeXYWH(0, 0, image->width(), 1); + SkIRect subset = SkIRect::MakeXYWH(0, 0, image.width(), 1); if (deviceBounds.top() < 0) { SkBitmap top; - SkAssertResult(image->extractSubset(&top, subset)); + SkAssertResult(image.extractSubset(&top, subset)); SkMatrix topMatrix; topMatrix.setScale(SK_Scalar1, -deviceBounds.top()); @@ -1192,8 +1133,8 @@ SkPDFImageShader* SkPDFImageShader::Create( if (deviceBounds.bottom() > height) { SkBitmap bottom; - subset.offset(0, image->height() - 1); - SkAssertResult(image->extractSubset(&bottom, subset)); + subset.offset(0, image.height() - 1); + SkAssertResult(image.extractSubset(&bottom, subset)); SkMatrix bottomMatrix; bottomMatrix.setScale(SK_Scalar1, deviceBounds.bottom() - height); @@ -1209,17 +1150,9 @@ SkPDFImageShader* SkPDFImageShader::Create( } } - // Put the canvas into the pattern stream (fContent). - SkPDFImageShader* imageShader = new SkPDFImageShader(autoState->release()); - imageShader->setData(patternDevice->content()); - - auto resourceDict = patternDevice->makeResourceDict(); + auto imageShader = sk_make_sp(patternDevice->content()); populate_tiling_pattern_dict(imageShader->dict(), patternBBox, - resourceDict.get(), finalMatrix); - - imageShader->fShaderState->fImage.unlockPixels(); - - doc->canon()->addImageShader(imageShader); + patternDevice->makeResourceDict(), finalMatrix); return imageShader; } @@ -1277,9 +1210,11 @@ bool SkPDFShader::State::operator==(const SkPDFShader::State& b) const { } SkPDFShader::State::State(SkShader* shader, const SkMatrix& canvasTransform, - const SkIRect& bbox, SkScalar rasterScale) + const SkIRect& bbox, SkScalar rasterScale, + SkBitmap* imageDst) : fCanvasTransform(canvasTransform), fBBox(bbox) { + SkASSERT(imageDst); fInfo.fColorCount = 0; fInfo.fColors = nullptr; fInfo.fColorOffsets = nullptr; @@ -1289,18 +1224,19 @@ SkPDFShader::State::State(SkShader* shader, const SkMatrix& canvasTransform, fType = shader->asAGradient(&fInfo); if (fType == SkShader::kNone_GradientType) { - if (!shader->isABitmap(&fImage, nullptr, fImageTileModes)) { + if (!shader->isABitmap(imageDst, nullptr, fImageTileModes)) { // Generic fallback for unsupported shaders: // * allocate a bbox-sized bitmap // * shade the whole area // * use the result as a bitmap shader - // bbox is in device space. While that's exactly what we want for sizing our bitmap, - // we need to map it into shader space for adjustments (to match - // SkPDFImageShader::Create's behavior). + // bbox is in device space. While that's exactly what we + // want for sizing our bitmap, we need to map it into + // shader space for adjustments (to match + // MakeImageShader's behavior). SkRect shaderRect = SkRect::Make(bbox); if (!inverse_transform_bbox(canvasTransform, &shaderRect)) { - fImage.reset(); + imageDst->reset(); return; } @@ -1316,13 +1252,13 @@ SkPDFShader::State::State(SkShader* shader, const SkMatrix& canvasTransform, SkSize scale = SkSize::Make(SkIntToScalar(size.width()) / shaderRect.width(), SkIntToScalar(size.height()) / shaderRect.height()); - fImage.allocN32Pixels(size.width(), size.height()); - fImage.eraseColor(SK_ColorTRANSPARENT); + imageDst->allocN32Pixels(size.width(), size.height()); + imageDst->eraseColor(SK_ColorTRANSPARENT); SkPaint p; p.setShader(sk_ref_sp(shader)); - SkCanvas canvas(fImage); + SkCanvas canvas(*imageDst); canvas.scale(scale.width(), scale.height()); canvas.translate(-shaderRect.x(), -shaderRect.y()); canvas.drawPaint(p); @@ -1330,9 +1266,9 @@ SkPDFShader::State::State(SkShader* shader, const SkMatrix& canvasTransform, fShaderTransform.setTranslate(shaderRect.x(), shaderRect.y()); fShaderTransform.preScale(1 / scale.width(), 1 / scale.height()); } - fBitmapKey = SkBitmapKey(fImage); + fBitmapKey = SkBitmapKey(*imageDst); } else { - AllocateGradientInfoStorage(); + this->allocateGradientInfoStorage(); shader->asAGradient(&fInfo); } } @@ -1350,7 +1286,7 @@ SkPDFShader::State::State(const SkPDFShader::State& other) if (fType != SkShader::kNone_GradientType) { fInfo = other.fInfo; - AllocateGradientInfoStorage(); + this->allocateGradientInfoStorage(); for (int i = 0; i < fInfo.fColorCount; i++) { fInfo.fColors[i] = other.fInfo.fColors[i]; fInfo.fColorOffsets[i] = other.fInfo.fColorOffsets[i]; @@ -1362,14 +1298,15 @@ SkPDFShader::State::State(const SkPDFShader::State& other) * Create a copy of this gradient state with alpha assigned to RGB luminousity. * Only valid for gradient states. */ -SkPDFShader::State* SkPDFShader::State::CreateAlphaToLuminosityState() const { +SkPDFShader::State SkPDFShader::State::MakeAlphaToLuminosityState() const { + SkASSERT(fBitmapKey == SkBitmapKey()); SkASSERT(fType != SkShader::kNone_GradientType); - SkPDFShader::State* newState = new SkPDFShader::State(*this); + SkPDFShader::State newState(*this); for (int i = 0; i < fInfo.fColorCount; i++) { SkAlpha alpha = SkColorGetA(fInfo.fColors[i]); - newState->fInfo.fColors[i] = SkColorSetARGB(255, alpha, alpha, alpha); + newState.fInfo.fColors[i] = SkColorSetARGB(255, alpha, alpha, alpha); } return newState; @@ -1379,12 +1316,13 @@ SkPDFShader::State* SkPDFShader::State::CreateAlphaToLuminosityState() const { * Create a copy of this gradient state with alpha set to fully opaque * Only valid for gradient states. */ -SkPDFShader::State* SkPDFShader::State::CreateOpaqueState() const { +SkPDFShader::State SkPDFShader::State::MakeOpaqueState() const { + SkASSERT(fBitmapKey == SkBitmapKey()); SkASSERT(fType != SkShader::kNone_GradientType); - SkPDFShader::State* newState = new SkPDFShader::State(*this); + SkPDFShader::State newState(*this); for (int i = 0; i < fInfo.fColorCount; i++) { - newState->fInfo.fColors[i] = SkColorSetA(fInfo.fColors[i], + newState.fInfo.fColors[i] = SkColorSetA(fInfo.fColors[i], SK_AlphaOPAQUE); } @@ -1408,10 +1346,9 @@ bool SkPDFShader::State::GradientHasAlpha() const { return false; } -void SkPDFShader::State::AllocateGradientInfoStorage() { - fColorData.set(sk_malloc_throw( - fInfo.fColorCount * (sizeof(SkColor) + sizeof(SkScalar)))); - fInfo.fColors = reinterpret_cast(fColorData.get()); - fInfo.fColorOffsets = - reinterpret_cast(fInfo.fColors + fInfo.fColorCount); +void SkPDFShader::State::allocateGradientInfoStorage() { + fColors.reset(new SkColor[fInfo.fColorCount]); + fStops.reset(new SkScalar[fInfo.fColorCount]); + fInfo.fColors = fColors.get(); + fInfo.fColorOffsets = fStops.get(); } diff --git a/src/pdf/SkPDFShader.h b/src/pdf/SkPDFShader.h index a6d36b6f02..db13cd50b3 100644 --- a/src/pdf/SkPDFShader.h +++ b/src/pdf/SkPDFShader.h @@ -9,12 +9,13 @@ #ifndef SkPDFShader_DEFINED #define SkPDFShader_DEFINED +#include "SkBitmapKey.h" #include "SkPDFTypes.h" +#include "SkShader.h" class SkPDFCanon; class SkPDFDocument; class SkMatrix; -class SkShader; struct SkIRect; /** \class SkPDFShader @@ -25,8 +26,6 @@ struct SkIRect; class SkPDFShader { public: - class State; - /** Get the PDF shader for the passed SkShader. If the SkShader is * invalid in some way, returns nullptr. The reference count of * the object is incremented and it is the caller's responsibility to @@ -41,60 +40,47 @@ public: * @param rasterScale Additional scale to be applied for early * rasterization. */ - static SkPDFObject* GetPDFShader(SkPDFDocument* doc, - SkScalar dpi, - SkShader* shader, - const SkMatrix& matrix, - const SkIRect& surfaceBBox, - SkScalar rasterScale); + static sk_sp GetPDFShader(SkPDFDocument* doc, + SkScalar dpi, + SkShader* shader, + const SkMatrix& matrix, + const SkIRect& surfaceBBox, + SkScalar rasterScale); static sk_sp MakeRangeObject(); -}; -class SkPDFFunctionShader final : public SkPDFDict { -public: - static SkPDFFunctionShader* Create(SkPDFCanon*, - std::unique_ptr*); - virtual ~SkPDFFunctionShader(); - bool equals(const SkPDFShader::State&) const; + class State { + public: + SkShader::GradientType fType; + SkShader::GradientInfo fInfo; + std::unique_ptr fColors; + std::unique_ptr fStops; + SkMatrix fCanvasTransform; + SkMatrix fShaderTransform; + SkIRect fBBox; -private: - std::unique_ptr fShaderState; - SkPDFFunctionShader(SkPDFShader::State*); - typedef SkPDFDict INHERITED; -}; + SkBitmapKey fBitmapKey; + SkShader::TileMode fImageTileModes[2]; -/** - * A shader for PDF gradients. This encapsulates the function shader - * inside a tiling pattern while providing a common pattern interface. - * The encapsulation allows the use of a SMask for transparency gradients. - */ -class SkPDFAlphaFunctionShader final : public SkPDFStream { -public: - static SkPDFAlphaFunctionShader* Create(SkPDFDocument*, - SkScalar dpi, - std::unique_ptr*); - virtual ~SkPDFAlphaFunctionShader(); - bool equals(const SkPDFShader::State&) const; + State(SkShader* shader, const SkMatrix& canvasTransform, + const SkIRect& bbox, SkScalar rasterScale, + SkBitmap* dstImage); -private: - std::unique_ptr fShaderState; - SkPDFAlphaFunctionShader(SkPDFShader::State*); - typedef SkPDFStream INHERITED; -}; + bool operator==(const State& b) const; -class SkPDFImageShader final : public SkPDFStream { -public: - static SkPDFImageShader* Create(SkPDFDocument*, - SkScalar dpi, - std::unique_ptr*); - virtual ~SkPDFImageShader(); - bool equals(const SkPDFShader::State&) const; + State MakeAlphaToLuminosityState() const; + State MakeOpaqueState() const; -private: - std::unique_ptr fShaderState; - SkPDFImageShader(SkPDFShader::State*); - typedef SkPDFStream INHERITED; + bool GradientHasAlpha() const; + + State(State&&) = default; + State& operator=(State&&) = default; + + private: + State(const State& other); + State& operator=(const State& rhs); + void allocateGradientInfoStorage(); + }; }; #endif diff --git a/src/pdf/SkPDFTypes.h b/src/pdf/SkPDFTypes.h index 6ee9162139..52ce221725 100644 --- a/src/pdf/SkPDFTypes.h +++ b/src/pdf/SkPDFTypes.h @@ -331,7 +331,7 @@ private: memory. */ -class SkPDFStream : public SkPDFObject { +class SkPDFStream final : public SkPDFObject { public: /** Create a PDF stream. A Length entry is automatically added to the diff --git a/tests/CPlusPlusEleven.cpp b/tests/CPlusPlusEleven.cpp index b5a34b2264..3130e6f95b 100644 --- a/tests/CPlusPlusEleven.cpp +++ b/tests/CPlusPlusEleven.cpp @@ -35,3 +35,23 @@ DEF_TEST(CPlusPlusEleven_constexpr, r) { static constexpr int y = SkTPin(100, 0, 10); REPORTER_ASSERT(r, y == 10); } + +namespace { +struct MoveableCopyable { + bool fCopied; + MoveableCopyable() : fCopied(false) {} + MoveableCopyable(const MoveableCopyable &o) : fCopied(true) {} + MoveableCopyable(MoveableCopyable &&o) : fCopied(o.fCopied) {} +}; +struct TestClass { + MoveableCopyable fFoo; +}; +} // namespace + +DEF_TEST(CPlusPlusEleven_default_move, r) { + TestClass a; + TestClass b(a); + TestClass c(std::move(a)); + REPORTER_ASSERT(r, b.fFoo.fCopied); + REPORTER_ASSERT(r, !c.fFoo.fCopied); +}