зеркало из https://github.com/mozilla/moz-skia.git
rename public SkDataRef to SkData
rename animator's internal SkData to SkDataInput git-svn-id: http://skia.googlecode.com/svn/trunk@1697 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
Родитель
c134f39401
Коммит
8d0b5770f8
|
@ -55,7 +55,7 @@
|
|||
'../src/core/SkCoreBlitters.h',
|
||||
'../src/core/SkCubicClipper.cpp',
|
||||
'../src/core/SkCubicClipper.h',
|
||||
'../src/core/SkDataRef.cpp',
|
||||
'../src/core/SkData.cpp',
|
||||
'../src/core/SkDebug.cpp',
|
||||
'../src/core/SkDeque.cpp',
|
||||
'../src/core/SkDevice.cpp',
|
||||
|
@ -176,6 +176,7 @@
|
|||
'../include/core/SkColorPriv.h',
|
||||
'../include/core/SkColorShader.h',
|
||||
'../include/core/SkComposeShader.h',
|
||||
'../include/core/SkData.h',
|
||||
'../include/core/SkDeque.h',
|
||||
'../include/core/SkDescriptor.h',
|
||||
'../include/core/SkDevice.h',
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
Copyright 2011 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef SkDataRef_DEFINED
|
||||
#define SkDataRef_DEFINED
|
||||
|
||||
#include "SkRefCnt.h"
|
||||
|
||||
/**
|
||||
* SkDataRef holds an immutable data buffer. Not only is the data immutable,
|
||||
* but the actual ptr that is returned (by data() or bytes()) is guaranteed
|
||||
* to always be the same for the life of this instance.
|
||||
*/
|
||||
class SkDataRef : public SkRefCnt {
|
||||
public:
|
||||
/**
|
||||
* Returns the number of bytes stored.
|
||||
*/
|
||||
size_t size() const { return fSize; }
|
||||
|
||||
/**
|
||||
* Returns the ptr to the data.
|
||||
*/
|
||||
const void* data() const { return fPtr; }
|
||||
|
||||
/**
|
||||
* Like data(), returns a read-only ptr into the data, but in this case
|
||||
* it is cast to uint8_t*, to make it easy to add an offset to it.
|
||||
*/
|
||||
const uint8_t* bytes() const {
|
||||
return reinterpret_cast<const uint8_t*>(fPtr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to copy a range of the data into a caller-provided buffer.
|
||||
* Returns the actual number of bytes copied, after clamping offset and
|
||||
* length to the size of the data. If buffer is NULL, it is ignored, and
|
||||
* only the computed number of bytes is returned.
|
||||
*/
|
||||
size_t copyRange(size_t offset, size_t length, void* buffer) const;
|
||||
|
||||
/**
|
||||
* Function that, if provided, will be called when the SkDataRef goes out
|
||||
* of scope, allowing for custom allocation/freeing of the data.
|
||||
*/
|
||||
typedef void (*ReleaseProc)(const void* ptr, size_t length, void* context);
|
||||
|
||||
/**
|
||||
* Create a new dataref by copying the specified data
|
||||
*/
|
||||
static SkDataRef* NewWithCopy(const void* data, size_t length);
|
||||
|
||||
/**
|
||||
* Create a new dataref, taking the data ptr as is, and using the
|
||||
* releaseproc to free it. The proc may be NULL.
|
||||
*/
|
||||
static SkDataRef* NewWithProc(const void* data, size_t length,
|
||||
ReleaseProc proc, void* context);
|
||||
|
||||
/**
|
||||
* Create a new dataref using a subset of the data in the specified
|
||||
* src dataref.
|
||||
*/
|
||||
static SkDataRef* NewSubset(const SkDataRef* src, size_t offset, size_t length);
|
||||
|
||||
/**
|
||||
* Returns a new empty dataref (or a reference to a shared empty dataref).
|
||||
* New or shared, the caller must see that unref() is eventually called.
|
||||
*/
|
||||
static SkDataRef* NewEmpty();
|
||||
|
||||
private:
|
||||
ReleaseProc fReleaseProc;
|
||||
void* fReleaseProcContext;
|
||||
|
||||
const void* fPtr;
|
||||
size_t fSize;
|
||||
|
||||
SkDataRef(const void* ptr, size_t size, ReleaseProc, void* context);
|
||||
~SkDataRef();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -20,7 +20,7 @@
|
|||
#include "SkRefCnt.h"
|
||||
#include "SkScalar.h"
|
||||
|
||||
class SkDataRef;
|
||||
class SkData;
|
||||
|
||||
class SK_API SkStream : public SkRefCnt {
|
||||
public:
|
||||
|
@ -297,7 +297,7 @@ public:
|
|||
/**
|
||||
* Return a copy of the data written so far
|
||||
*/
|
||||
SkDataRef* copyToData() const;
|
||||
SkData* copyToData() const;
|
||||
|
||||
// reset the stream to its original state
|
||||
void reset();
|
||||
|
|
|
@ -51,13 +51,13 @@ SkPost::SkPost() : delay(0), /*initialized(SkBool(-1)), */ mode(kImmediate), fMa
|
|||
}
|
||||
|
||||
SkPost::~SkPost() {
|
||||
for (SkData** part = fParts.begin(); part < fParts.end(); part++)
|
||||
for (SkDataInput** part = fParts.begin(); part < fParts.end(); part++)
|
||||
delete *part;
|
||||
}
|
||||
|
||||
bool SkPost::add(SkAnimateMaker& , SkDisplayable* child) {
|
||||
SkASSERT(child && child->isData());
|
||||
SkData* part = (SkData*) child;
|
||||
SkASSERT(child && child->isDataInput());
|
||||
SkDataInput* part = (SkDataInput*) child;
|
||||
*fParts.append() = part;
|
||||
return true;
|
||||
}
|
||||
|
@ -113,8 +113,8 @@ void SkPost::dump(SkAnimateMaker* maker) {
|
|||
//for some reason the last part is id, which i don't want
|
||||
//and the parts seem to be in the reverse order from the one in which we find the
|
||||
//data itself
|
||||
//SkData** ptr = fParts.end();
|
||||
//SkData* data;
|
||||
//SkDataInput** ptr = fParts.end();
|
||||
//SkDataInput* data;
|
||||
//const char* ID;
|
||||
while ((name = iter.next(&type, &number)) != NULL) {
|
||||
//ptr--;
|
||||
|
@ -190,7 +190,7 @@ bool SkPost::enable(SkAnimateMaker& maker ) {
|
|||
fEvent.getMetaData().reset();
|
||||
if (preserveID.size() > 0)
|
||||
fEvent.setString("id", preserveID);
|
||||
for (SkData** part = fParts.begin(); part < fParts.end(); part++) {
|
||||
for (SkDataInput** part = fParts.begin(); part < fParts.end(); part++) {
|
||||
if ((*part)->add())
|
||||
maker.setErrorCode(SkDisplayXMLParserError::kErrorAddingDataToPost);
|
||||
}
|
||||
|
@ -284,7 +284,7 @@ bool SkPost::hasEnable() const {
|
|||
void SkPost::onEndElement(SkAnimateMaker& maker) {
|
||||
fTargetMaker = fMaker = &maker;
|
||||
if (fChildHasID == false) {
|
||||
for (SkData** part = fParts.begin(); part < fParts.end(); part++)
|
||||
for (SkDataInput** part = fParts.begin(); part < fParts.end(); part++)
|
||||
delete *part;
|
||||
fParts.reset();
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#include "SkMemberInfo.h"
|
||||
#include "SkIntArray.h"
|
||||
|
||||
class SkData;
|
||||
class SkDataInput;
|
||||
class SkAnimateMaker;
|
||||
|
||||
class SkPost : public SkDisplayable {
|
||||
|
@ -60,7 +60,7 @@ protected:
|
|||
SkBool8 fDirty;
|
||||
private:
|
||||
void findSinkID();
|
||||
friend class SkData;
|
||||
friend class SkDataInput;
|
||||
typedef SkDisplayable INHERITED;
|
||||
};
|
||||
|
||||
|
|
|
@ -119,7 +119,7 @@ SkDisplayable* SkDisplayType::CreateInstance(SkAnimateMaker* maker, SkDisplayTyp
|
|||
CASE_DRAW_NEW(Color);
|
||||
CASE_NEW(CubicTo);
|
||||
CASE_NEW(Dash);
|
||||
CASE_NEW(Data);
|
||||
CASE_NEW(DataInput);
|
||||
CASE_NEW(Discrete);
|
||||
// displayable
|
||||
// drawable
|
||||
|
@ -274,7 +274,7 @@ const SkMemberInfo* SkDisplayType::GetMembers(SkAnimateMaker* maker,
|
|||
CASE_GET_DRAW_INFO(Color);
|
||||
CASE_GET_INFO(CubicTo);
|
||||
CASE_GET_INFO(Dash);
|
||||
CASE_GET_INFO(Data);
|
||||
CASE_GET_INFO(DataInput);
|
||||
CASE_GET_INFO(Discrete);
|
||||
// displayable
|
||||
// drawable
|
||||
|
@ -441,7 +441,7 @@ const TypeNames gTypeNames[] = {
|
|||
DRAW_NAME("color", SkType_Color),
|
||||
{ "cubicTo", SkType_CubicTo INIT_BOOL_FIELDS },
|
||||
{ "dash", SkType_Dash INIT_BOOL_FIELDS },
|
||||
{ "data", SkType_Data INIT_BOOL_FIELDS },
|
||||
{ "data", SkType_DataInput INIT_BOOL_FIELDS },
|
||||
{ "discrete", SkType_Discrete INIT_BOOL_FIELDS },
|
||||
// displayable
|
||||
// drawable
|
||||
|
@ -634,7 +634,7 @@ bool SkDisplayType::IsDisplayable(SkAnimateMaker* , SkDisplayTypes type) {
|
|||
case SkType_Color:
|
||||
case SkType_CubicTo:
|
||||
case SkType_Dash:
|
||||
case SkType_Data:
|
||||
case SkType_DataInput:
|
||||
case SkType_Discrete:
|
||||
case SkType_Displayable:
|
||||
case SkType_Drawable:
|
||||
|
|
|
@ -75,7 +75,7 @@ enum SkDisplayTypes {
|
|||
SkType_Color,
|
||||
SkType_CubicTo,
|
||||
SkType_Dash,
|
||||
SkType_Data,
|
||||
SkType_DataInput,
|
||||
SkType_Discrete,
|
||||
SkType_Displayable,
|
||||
SkType_Drawable,
|
||||
|
|
|
@ -96,7 +96,7 @@ public:
|
|||
virtual bool setProperty(int index, SkScriptValue& );
|
||||
void setReference(const SkMemberInfo* info, SkDisplayable* ref);
|
||||
#ifdef SK_DEBUG
|
||||
bool isData() const { return getType() == SkType_Data; };
|
||||
bool isDataInput() const { return getType() == SkType_DataInput; };
|
||||
bool isEvent() const { return getType() == SkType_Event; }
|
||||
virtual bool isMatrixPart() const { return false; }
|
||||
bool isPatch() const { return getType() == SkType_3D_Patch; }
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
class SkActive;
|
||||
class SkAnimateBase;
|
||||
class SkData;
|
||||
class SkDataInput;
|
||||
class SkDisplayable;
|
||||
class SkDisplayEvent;
|
||||
class SkDrawable;
|
||||
|
@ -46,7 +46,7 @@ typedef SkIntArray(SkScalar) SkTDScalarArray;
|
|||
|
||||
typedef SkLongArray(SkActive*) SkTDActiveArray;
|
||||
typedef SkLongArray(SkAnimateBase*) SkTDAnimateArray;
|
||||
typedef SkLongArray(SkData*) SkTDDataArray;
|
||||
typedef SkLongArray(SkDataInput*) SkTDDataArray;
|
||||
typedef SkLongArray(SkDisplayable*) SkTDDisplayableArray;
|
||||
typedef SkLongArray(SkDisplayEvent*) SkTDDisplayEventArray;
|
||||
typedef SkLongArray(SkDrawable*) SkTDDrawableArray;
|
||||
|
|
|
@ -20,17 +20,17 @@
|
|||
|
||||
#if SK_USE_CONDENSED_INFO == 0
|
||||
|
||||
const SkMemberInfo SkData::fInfo[] = {
|
||||
const SkMemberInfo SkDataInput::fInfo[] = {
|
||||
SK_MEMBER_INHERITED
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
DEFINE_GET_MEMBER(SkData);
|
||||
DEFINE_GET_MEMBER(SkDataInput);
|
||||
|
||||
SkData::SkData() : fParent(NULL) {}
|
||||
SkDataInput::SkDataInput() : fParent(NULL) {}
|
||||
|
||||
bool SkData::add() {
|
||||
bool SkDataInput::add() {
|
||||
SkASSERT(name.size() > 0);
|
||||
const char* dataName = name.c_str();
|
||||
if (fInt != (int) SK_NaN32)
|
||||
|
@ -44,22 +44,22 @@ bool SkData::add() {
|
|||
return false;
|
||||
}
|
||||
|
||||
void SkData::dirty() {
|
||||
void SkDataInput::dirty() {
|
||||
fParent->dirty();
|
||||
}
|
||||
|
||||
SkDisplayable* SkData::getParent() const {
|
||||
SkDisplayable* SkDataInput::getParent() const {
|
||||
return fParent;
|
||||
}
|
||||
|
||||
bool SkData::setParent(SkDisplayable* displayable) {
|
||||
bool SkDataInput::setParent(SkDisplayable* displayable) {
|
||||
if (displayable->isPost() == false)
|
||||
return true;
|
||||
fParent = (SkPost*) displayable;
|
||||
return false;
|
||||
}
|
||||
|
||||
void SkData::onEndElement(SkAnimateMaker&) {
|
||||
void SkDataInput::onEndElement(SkAnimateMaker&) {
|
||||
add();
|
||||
}
|
||||
|
||||
|
|
|
@ -22,9 +22,9 @@
|
|||
|
||||
class SkPost;
|
||||
|
||||
class SkData: public SkInput {
|
||||
DECLARE_MEMBER_INFO(Data);
|
||||
SkData();
|
||||
class SkDataInput: public SkInput {
|
||||
DECLARE_MEMBER_INFO(DataInput);
|
||||
SkDataInput();
|
||||
bool add();
|
||||
virtual void dirty();
|
||||
virtual SkDisplayable* getParent() const;
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
Copyright 2011 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#include "SkDataRef.h"
|
||||
|
||||
SkDataRef::SkDataRef(const void* ptr, size_t size, ReleaseProc proc, void* context) {
|
||||
fPtr = ptr;
|
||||
fSize = size;
|
||||
fReleaseProc = proc;
|
||||
fReleaseProcContext = context;
|
||||
}
|
||||
|
||||
SkDataRef::~SkDataRef() {
|
||||
if (fReleaseProc) {
|
||||
fReleaseProc(fPtr, fSize, fReleaseProcContext);
|
||||
}
|
||||
}
|
||||
|
||||
size_t SkDataRef::copyRange(size_t offset, size_t length, void* buffer) const {
|
||||
size_t available = fSize;
|
||||
if (offset >= available || 0 == length) {
|
||||
return 0;
|
||||
}
|
||||
available -= offset;
|
||||
if (length > available) {
|
||||
length = available;
|
||||
}
|
||||
SkASSERT(length > 0);
|
||||
|
||||
memcpy(buffer, this->bytes() + offset, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SkDataRef* SkDataRef::NewEmpty() {
|
||||
static SkDataRef* gEmptyRef;
|
||||
if (NULL == gEmptyRef) {
|
||||
gEmptyRef = new SkDataRef(NULL, 0, NULL, NULL);
|
||||
}
|
||||
gEmptyRef->ref();
|
||||
return gEmptyRef;
|
||||
}
|
||||
|
||||
// assumes fPtr was allocated via sk_malloc
|
||||
static void sk_malloc_releaseproc(const void* ptr, size_t, void*) {
|
||||
sk_free((void*)ptr);
|
||||
}
|
||||
|
||||
SkDataRef* SkDataRef::NewWithCopy(const void* data, size_t length) {
|
||||
if (0 == length) {
|
||||
return SkDataRef::NewEmpty();
|
||||
}
|
||||
|
||||
void* copy = sk_malloc_throw(length); // balanced in sk_malloc_releaseproc
|
||||
memcpy(copy, data, length);
|
||||
return new SkDataRef(copy, length, sk_malloc_releaseproc, NULL);
|
||||
}
|
||||
|
||||
SkDataRef* SkDataRef::NewWithProc(const void* data, size_t length,
|
||||
ReleaseProc proc, void* context) {
|
||||
return new SkDataRef(data, length, proc, context);
|
||||
}
|
||||
|
||||
// assumes context is a SkDataRef
|
||||
static void sk_dataref_releaseproc(const void*, size_t, void* context) {
|
||||
SkDataRef* src = reinterpret_cast<SkDataRef*>(context);
|
||||
src->unref();
|
||||
}
|
||||
|
||||
SkDataRef* SkDataRef::NewSubset(const SkDataRef* src, size_t offset, size_t length) {
|
||||
/*
|
||||
We could, if we wanted/need to, just make a deep copy of src's data,
|
||||
rather than referencing it. This would duplicate the storage (of the
|
||||
subset amount) but would possibly allow src to go out of scope sooner.
|
||||
*/
|
||||
|
||||
size_t available = src->size();
|
||||
if (offset >= available || 0 == length) {
|
||||
return SkDataRef::NewEmpty();
|
||||
}
|
||||
available -= offset;
|
||||
if (length > available) {
|
||||
length = available;
|
||||
}
|
||||
SkASSERT(length > 0);
|
||||
|
||||
src->ref(); // this will be balanced in sk_dataref_releaseproc
|
||||
return new SkDataRef(src->bytes() + offset, length, sk_dataref_releaseproc,
|
||||
const_cast<SkDataRef*>(src));
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
|
||||
#include "SkStream.h"
|
||||
#include "SkDataRef.h"
|
||||
#include "SkData.h"
|
||||
#include "SkFixed.h"
|
||||
#include "SkString.h"
|
||||
#include "SkOSFile.h"
|
||||
|
@ -729,9 +729,10 @@ static void sk_free_release_proc(const void* ptr, size_t length, void*) {
|
|||
sk_free((void*)ptr);
|
||||
}
|
||||
|
||||
SkDataRef* SkDynamicMemoryWStream::copyToData() const {
|
||||
return SkDataRef::NewWithProc(this->detach(), fBytesWritten,
|
||||
sk_free_release_proc, NULL);
|
||||
SkData* SkDynamicMemoryWStream::copyToData() const {
|
||||
// should rewrite when we remove detach()
|
||||
return SkData::NewWithProc(this->detach(), fBytesWritten,
|
||||
sk_free_release_proc, NULL);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#include "Test.h"
|
||||
#include "SkDataRef.h"
|
||||
#include "SkData.h"
|
||||
|
||||
static void* gGlobal;
|
||||
|
||||
|
@ -9,11 +9,11 @@ static void delete_int_proc(const void* ptr, size_t len, void* context) {
|
|||
delete[] data;
|
||||
}
|
||||
|
||||
static void assert_len(skiatest::Reporter* reporter, SkDataRef* ref, size_t len) {
|
||||
static void assert_len(skiatest::Reporter* reporter, SkData* ref, size_t len) {
|
||||
REPORTER_ASSERT(reporter, ref->size() == len);
|
||||
}
|
||||
|
||||
static void assert_data(skiatest::Reporter* reporter, SkDataRef* ref,
|
||||
static void assert_data(skiatest::Reporter* reporter, SkData* ref,
|
||||
const void* data, size_t len) {
|
||||
REPORTER_ASSERT(reporter, ref->size() == len);
|
||||
REPORTER_ASSERT(reporter, !memcmp(ref->data(), data, len));
|
||||
|
@ -23,11 +23,11 @@ void TestDataRef(skiatest::Reporter* reporter) {
|
|||
const char* str = "We the people, in order to form a more perfect union.";
|
||||
const int N = 10;
|
||||
|
||||
SkDataRef* r0 = SkDataRef::NewEmpty();
|
||||
SkDataRef* r1 = SkDataRef::NewWithCopy(str, strlen(str));
|
||||
SkDataRef* r2 = SkDataRef::NewWithProc(new int[N], N*sizeof(int),
|
||||
SkData* r0 = SkData::NewEmpty();
|
||||
SkData* r1 = SkData::NewWithCopy(str, strlen(str));
|
||||
SkData* r2 = SkData::NewWithProc(new int[N], N*sizeof(int),
|
||||
delete_int_proc, gGlobal);
|
||||
SkDataRef* r3 = SkDataRef::NewSubset(r1, 7, 6);
|
||||
SkData* r3 = SkData::NewSubset(r1, 7, 6);
|
||||
|
||||
SkAutoUnref aur0(r0);
|
||||
SkAutoUnref aur1(r1);
|
||||
|
@ -42,10 +42,10 @@ void TestDataRef(skiatest::Reporter* reporter) {
|
|||
assert_data(reporter, r1, str, strlen(str));
|
||||
assert_data(reporter, r3, "people", 6);
|
||||
|
||||
SkDataRef* tmp = SkDataRef::NewSubset(r1, strlen(str), 10);
|
||||
SkData* tmp = SkData::NewSubset(r1, strlen(str), 10);
|
||||
assert_len(reporter, tmp, 0);
|
||||
tmp->unref();
|
||||
tmp = SkDataRef::NewSubset(r1, 0, 0);
|
||||
tmp = SkData::NewSubset(r1, 0, 0);
|
||||
assert_len(reporter, tmp, 0);
|
||||
tmp->unref();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "Test.h"
|
||||
#include "SkRandom.h"
|
||||
#include "SkStream.h"
|
||||
#include "SkDataRef.h"
|
||||
#include "SkData.h"
|
||||
|
||||
#define MAX_SIZE (256 * 1024)
|
||||
|
||||
|
@ -85,7 +85,7 @@ static void TestWStream(skiatest::Reporter* reporter) {
|
|||
REPORTER_ASSERT(reporter, memcmp(dst, ds.getStream(), 100*26) == 0);
|
||||
|
||||
{
|
||||
SkDataRef* data = ds.copyToData();
|
||||
SkData* data = ds.copyToData();
|
||||
REPORTER_ASSERT(reporter, 100 * 26 == data->size());
|
||||
REPORTER_ASSERT(reporter, memcmp(dst, data->data(), data->size()) == 0);
|
||||
data->unref();
|
||||
|
|
Загрузка…
Ссылка в новой задаче