зеркало из https://github.com/mozilla/gecko-dev.git
Bug 380757 - switch svg filters to thebes. r=jwatt, sr=roc
This commit is contained in:
Родитель
218ecc7055
Коммит
09e9701b1e
|
@ -59,7 +59,7 @@
|
|||
#include "nsIDocument.h"
|
||||
#include "nsIFrame.h"
|
||||
#include "nsSVGAnimatedInteger.h"
|
||||
#include "gfxColor.h"
|
||||
#include "gfxContext.h"
|
||||
|
||||
nsSVGElement::LengthInfo nsSVGFE::sLengthInfo[4] =
|
||||
{
|
||||
|
@ -235,7 +235,7 @@ public:
|
|||
*/
|
||||
nsresult AcquireSourceImage(nsIDOMSVGAnimatedString* aIn,
|
||||
nsSVGFE* aFilter, PRUint8** aSourceData,
|
||||
cairo_surface_t** aSurface = 0);
|
||||
gfxImageSurface** aSurface = 0);
|
||||
/*
|
||||
* Acquiring a target image will create a new surface to be used as the
|
||||
* target image.
|
||||
|
@ -247,7 +247,7 @@ public:
|
|||
*/
|
||||
nsresult AcquireTargetImage(nsIDOMSVGAnimatedString* aResult,
|
||||
PRUint8** aTargetData,
|
||||
cairo_surface_t** aSurface = 0);
|
||||
gfxImageSurface** aSurface = 0);
|
||||
const nsRect& GetRect() {
|
||||
return mRect;
|
||||
}
|
||||
|
@ -282,7 +282,7 @@ private:
|
|||
|
||||
nsAutoString mInput, mResult;
|
||||
nsRect mRect;
|
||||
cairo_surface_t *mTargetImage;
|
||||
nsRefPtr<gfxImageSurface> mTargetImage;
|
||||
nsSVGFilterInstance* mInstance;
|
||||
PRUint8 *mSourceData, *mTargetData;
|
||||
PRUint32 mWidth, mHeight;
|
||||
|
@ -311,24 +311,26 @@ nsSVGFilterResource::~nsSVGFilterResource()
|
|||
nsresult
|
||||
nsSVGFilterResource::AcquireSourceImage(nsIDOMSVGAnimatedString* aIn,
|
||||
nsSVGFE* aFilter, PRUint8** aSourceData,
|
||||
cairo_surface_t** aSurface)
|
||||
gfxImageSurface** aSurface)
|
||||
{
|
||||
aIn->GetAnimVal(mInput);
|
||||
|
||||
nsRect defaultRect;
|
||||
cairo_surface_t *surface;
|
||||
mInstance->LookupImage(mInput, &surface, &defaultRect, mColorModel);
|
||||
nsRefPtr<gfxImageSurface> surface;
|
||||
mInstance->LookupImage(mInput, getter_AddRefs(surface),
|
||||
&defaultRect, mColorModel);
|
||||
if (!surface) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (aSurface) {
|
||||
*aSurface = surface;
|
||||
*aSurface = nsnull;
|
||||
surface.swap(*aSurface);
|
||||
}
|
||||
mInstance->GetFilterSubregion(aFilter, defaultRect, &mRect);
|
||||
|
||||
mSourceData = cairo_image_surface_get_data(surface);
|
||||
mStride = cairo_image_surface_get_stride(surface);
|
||||
mSourceData = surface->Data();
|
||||
mStride = surface->Stride();
|
||||
|
||||
*aSourceData = mSourceData;
|
||||
return NS_OK;
|
||||
|
@ -337,7 +339,7 @@ nsSVGFilterResource::AcquireSourceImage(nsIDOMSVGAnimatedString* aIn,
|
|||
nsresult
|
||||
nsSVGFilterResource::AcquireTargetImage(nsIDOMSVGAnimatedString* aResult,
|
||||
PRUint8** aTargetData,
|
||||
cairo_surface_t** aSurface)
|
||||
gfxImageSurface** aSurface)
|
||||
{
|
||||
aResult->GetAnimVal(mResult);
|
||||
mTargetImage = mInstance->GetImage();
|
||||
|
@ -346,12 +348,14 @@ nsSVGFilterResource::AcquireTargetImage(nsIDOMSVGAnimatedString* aResult,
|
|||
}
|
||||
|
||||
if (aSurface) {
|
||||
NS_ADDREF(mTargetImage);
|
||||
*aSurface = mTargetImage;
|
||||
}
|
||||
mTargetData = cairo_image_surface_get_data(mTargetImage);
|
||||
mStride = cairo_image_surface_get_stride(mTargetImage);
|
||||
mWidth = cairo_image_surface_get_width(mTargetImage);
|
||||
mHeight = cairo_image_surface_get_height(mTargetImage);
|
||||
mTargetData = mTargetImage->Data();
|
||||
mStride = mTargetImage->Stride();
|
||||
gfxIntSize size = mTargetImage->GetSize();
|
||||
mWidth = size.width;
|
||||
mHeight = size.height;
|
||||
|
||||
*aTargetData = mTargetData;
|
||||
return NS_OK;
|
||||
|
@ -365,8 +369,6 @@ nsSVGFilterResource::ReleaseTarget()
|
|||
}
|
||||
mInstance->DefineImage(mResult, mTargetImage, mRect, mColorModel);
|
||||
|
||||
// filter instance now owns the image
|
||||
cairo_surface_destroy(mTargetImage);
|
||||
mTargetImage = nsnull;
|
||||
}
|
||||
|
||||
|
@ -1510,13 +1512,15 @@ nsSVGFECompositeElement::Filter(nsSVGFilterInstance *instance)
|
|||
{
|
||||
nsresult rv;
|
||||
PRUint8 *sourceData, *targetData;
|
||||
cairo_surface_t *sourceSurface, *targetSurface;
|
||||
nsRefPtr<gfxImageSurface> sourceSurface, targetSurface;
|
||||
|
||||
nsSVGFilterResource fr(instance,
|
||||
GetColorModel(nsSVGFilterInstance::ColorModel::PREMULTIPLIED));
|
||||
rv = fr.AcquireSourceImage(mIn2, this, &sourceData, &sourceSurface);
|
||||
rv = fr.AcquireSourceImage(mIn2, this, &sourceData,
|
||||
getter_AddRefs(sourceSurface));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = fr.AcquireTargetImage(mResult, &targetData, &targetSurface);
|
||||
rv = fr.AcquireTargetImage(mResult, &targetData,
|
||||
getter_AddRefs(targetSurface));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
PRUint16 op;
|
||||
|
@ -1568,29 +1572,27 @@ nsSVGFECompositeElement::Filter(nsSVGFilterInstance *instance)
|
|||
}
|
||||
|
||||
// Cairo supports the operation we are trying to perform
|
||||
cairo_t *cr = cairo_create(targetSurface);
|
||||
if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) {
|
||||
cairo_destroy(cr);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
gfxContext ctx(targetSurface);
|
||||
|
||||
cairo_set_source_surface(cr, sourceSurface, 0, 0);
|
||||
cairo_paint(cr);
|
||||
ctx.SetSource(sourceSurface);
|
||||
ctx.Paint();
|
||||
|
||||
if (op < SVG_OPERATOR_OVER || op > SVG_OPERATOR_XOR) {
|
||||
cairo_destroy(cr);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
cairo_operator_t opMap[] = { CAIRO_OPERATOR_DEST, CAIRO_OPERATOR_OVER,
|
||||
CAIRO_OPERATOR_IN, CAIRO_OPERATOR_OUT,
|
||||
CAIRO_OPERATOR_ATOP, CAIRO_OPERATOR_XOR };
|
||||
cairo_set_operator(cr, opMap[op]);
|
||||
gfxContext::GraphicsOperator opMap[] = { gfxContext::OPERATOR_DEST,
|
||||
gfxContext::OPERATOR_OVER,
|
||||
gfxContext::OPERATOR_IN,
|
||||
gfxContext::OPERATOR_OUT,
|
||||
gfxContext::OPERATOR_ATOP,
|
||||
gfxContext::OPERATOR_XOR };
|
||||
ctx.SetOperator(opMap[op]);
|
||||
|
||||
rv = fr.AcquireSourceImage(mIn1, this, &sourceData, &sourceSurface);
|
||||
rv = fr.AcquireSourceImage(mIn1, this, &sourceData,
|
||||
getter_AddRefs(sourceSurface));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
cairo_set_source_surface(cr, sourceSurface, 0, 0);
|
||||
cairo_paint(cr);
|
||||
cairo_destroy(cr);
|
||||
ctx.SetSource(sourceSurface);
|
||||
ctx.Paint();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -2266,18 +2268,15 @@ nsSVGFEMergeElement::Filter(nsSVGFilterInstance *instance)
|
|||
{
|
||||
nsresult rv;
|
||||
PRUint8 *sourceData, *targetData;
|
||||
cairo_surface_t *sourceSurface, *targetSurface;
|
||||
nsRefPtr<gfxImageSurface> sourceSurface, targetSurface;
|
||||
|
||||
nsSVGFilterResource fr(instance,
|
||||
GetColorModel(nsSVGFilterInstance::ColorModel::PREMULTIPLIED));
|
||||
rv = fr.AcquireTargetImage(mResult, &targetData, &targetSurface);
|
||||
rv = fr.AcquireTargetImage(mResult, &targetData,
|
||||
getter_AddRefs(targetSurface));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
cairo_t *cr = cairo_create(targetSurface);
|
||||
if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) {
|
||||
cairo_destroy(cr);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
gfxContext ctx(targetSurface);
|
||||
|
||||
PRUint32 count = GetChildCount();
|
||||
for (PRUint32 i = 0; i < count; i++) {
|
||||
|
@ -2288,13 +2287,13 @@ nsSVGFEMergeElement::Filter(nsSVGFilterInstance *instance)
|
|||
nsCOMPtr<nsIDOMSVGAnimatedString> str;
|
||||
node->GetIn1(getter_AddRefs(str));
|
||||
|
||||
rv = fr.AcquireSourceImage(str, this, &sourceData, &sourceSurface);
|
||||
rv = fr.AcquireSourceImage(str, this, &sourceData,
|
||||
getter_AddRefs(sourceSurface));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
cairo_set_source_surface(cr, sourceSurface, 0, 0);
|
||||
cairo_paint(cr);
|
||||
ctx.SetSource(sourceSurface);
|
||||
ctx.Paint();
|
||||
}
|
||||
cairo_destroy(cr);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -2705,7 +2704,7 @@ nsSVGFEFloodElement::Filter(nsSVGFilterInstance *instance)
|
|||
{
|
||||
nsresult rv;
|
||||
PRUint8 *sourceData, *targetData;
|
||||
cairo_surface_t *targetSurface;
|
||||
nsRefPtr<gfxImageSurface> targetSurface;
|
||||
// flood colour is sRGB
|
||||
nsSVGFilterInstance::ColorModel
|
||||
colorModel(nsSVGFilterInstance::ColorModel::SRGB,
|
||||
|
@ -2714,7 +2713,8 @@ nsSVGFEFloodElement::Filter(nsSVGFilterInstance *instance)
|
|||
|
||||
rv = fr.AcquireSourceImage(mIn1, this, &sourceData);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = fr.AcquireTargetImage(mResult, &targetData, &targetSurface);
|
||||
rv = fr.AcquireTargetImage(mResult, &targetData,
|
||||
getter_AddRefs(targetSurface));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsRect rect = fr.GetRect();
|
||||
|
||||
|
@ -2725,19 +2725,13 @@ nsSVGFEFloodElement::Filter(nsSVGFilterInstance *instance)
|
|||
nscolor floodColor = style->GetStyleSVGReset()->mFloodColor;
|
||||
float floodOpacity = style->GetStyleSVGReset()->mFloodOpacity;
|
||||
|
||||
cairo_t *cr = cairo_create(targetSurface);
|
||||
if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) {
|
||||
cairo_destroy(cr);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
cairo_set_source_rgba(cr,
|
||||
NS_GET_R(floodColor) / 255.0,
|
||||
NS_GET_G(floodColor) / 255.0,
|
||||
NS_GET_B(floodColor) / 255.0,
|
||||
NS_GET_A(floodColor) / 255.0 * floodOpacity);
|
||||
cairo_rectangle(cr, rect.x, rect.y, rect.width, rect.height);
|
||||
cairo_fill(cr);
|
||||
cairo_destroy(cr);
|
||||
gfxContext ctx(targetSurface);
|
||||
ctx.SetColor(gfxRGBA(NS_GET_R(floodColor) / 255.0,
|
||||
NS_GET_G(floodColor) / 255.0,
|
||||
NS_GET_B(floodColor) / 255.0,
|
||||
NS_GET_A(floodColor) / 255.0 * floodOpacity));
|
||||
ctx.Rectangle(gfxRect(rect.x, rect.y, rect.width, rect.height));
|
||||
ctx.Fill();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -218,7 +218,8 @@ nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
|
|||
|
||||
// paint the target geometry
|
||||
nsRefPtr<gfxImageSurface> tmpSurface =
|
||||
new gfxImageSurface(gfxIntSize(filterResX, filterResY), gfxASurface::ImageFormatARGB32);
|
||||
new gfxImageSurface(gfxIntSize(filterResX, filterResY),
|
||||
gfxASurface::ImageFormatARGB32);
|
||||
if (!tmpSurface || !tmpSurface->Data()) {
|
||||
FilterFailCleanup(aContext, aTarget);
|
||||
return NS_OK;
|
||||
|
@ -227,7 +228,10 @@ nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
|
|||
gfxContext tmpContext(tmpSurface);
|
||||
nsSVGRenderState tmpState(&tmpContext);
|
||||
|
||||
memset(tmpSurface->Data(), 0, tmpSurface->GetSize().height * tmpSurface->Stride());
|
||||
tmpContext.SetOperator(gfxContext::OPERATOR_CLEAR);
|
||||
tmpContext.Paint();
|
||||
tmpContext.SetOperator(gfxContext::OPERATOR_OVER);
|
||||
|
||||
aTarget->PaintSVG(&tmpState, nsnull);
|
||||
|
||||
PRUint16 primitiveUnits;
|
||||
|
@ -241,19 +245,17 @@ nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
|
|||
nsSVGFilterInstance::ColorModel::PREMULTIPLIED);
|
||||
|
||||
if (requirements & NS_FE_SOURCEALPHA) {
|
||||
cairo_surface_t *alpha =
|
||||
cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
|
||||
filterResX, filterResY);
|
||||
nsRefPtr<gfxImageSurface> alpha =
|
||||
new gfxImageSurface(gfxIntSize(filterResX, filterResY),
|
||||
gfxASurface::ImageFormatARGB32);
|
||||
|
||||
if (!alpha || cairo_surface_status(alpha)) {
|
||||
if (alpha)
|
||||
cairo_surface_destroy(alpha);
|
||||
if (!alpha || !alpha->Data()) {
|
||||
FilterFailCleanup(aContext, aTarget);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
PRUint8 *data = tmpSurface->Data();
|
||||
PRUint8 *alphaData = cairo_image_surface_get_data(alpha);
|
||||
PRUint8 *alphaData = alpha->Data();
|
||||
PRUint32 stride = tmpSurface->Stride();
|
||||
|
||||
for (PRUint32 yy = 0; yy < PRUint32(filterResY); yy++)
|
||||
|
@ -262,7 +264,7 @@ nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
|
|||
alphaData[stride*yy + 4*xx + GFX_ARGB32_OFFSET_G] = 0;
|
||||
alphaData[stride*yy + 4*xx + GFX_ARGB32_OFFSET_R] = 0;
|
||||
alphaData[stride*yy + 4*xx + GFX_ARGB32_OFFSET_A] =
|
||||
data[stride*yy + 4*xx + 3];
|
||||
data[stride*yy + 4*xx + GFX_ARGB32_OFFSET_A];
|
||||
}
|
||||
|
||||
instance.DefineImage(NS_LITERAL_STRING("SourceAlpha"), alpha,
|
||||
|
@ -271,8 +273,7 @@ nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
|
|||
|
||||
// this always needs to be defined last because the default image
|
||||
// for the first filter element is supposed to be SourceGraphic
|
||||
instance.DefineImage(NS_LITERAL_STRING("SourceGraphic"),
|
||||
tmpSurface->CairoSurface(),
|
||||
instance.DefineImage(NS_LITERAL_STRING("SourceGraphic"), tmpSurface,
|
||||
nsRect(0, 0, filterResX, filterResY), colorModel);
|
||||
|
||||
for (PRUint32 k=0; k<count; ++k) {
|
||||
|
@ -285,16 +286,13 @@ nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
|
|||
}
|
||||
}
|
||||
|
||||
cairo_surface_t *filterResult = nsnull;
|
||||
nsRect filterRect;
|
||||
nsRefPtr<gfxASurface> resultSurface;
|
||||
nsRefPtr<gfxImageSurface> filterSurface;
|
||||
|
||||
instance.LookupImage(NS_LITERAL_STRING(""),
|
||||
&filterResult, &filterRect, colorModel);
|
||||
getter_AddRefs(filterSurface), &filterRect, colorModel);
|
||||
|
||||
if (filterResult)
|
||||
resultSurface = gfxASurface::Wrap(filterResult);
|
||||
if (!resultSurface) {
|
||||
if (!filterSurface) {
|
||||
FilterFailCleanup(aContext, aTarget);
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -308,7 +306,7 @@ nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
|
|||
ctm->Multiply(scale, getter_AddRefs(fini));
|
||||
|
||||
nsSVGUtils::CompositeSurfaceMatrix(aContext->GetGfxContext(),
|
||||
resultSurface, fini, 1.0);
|
||||
filterSurface, fini, 1.0);
|
||||
|
||||
aTarget->SetOverrideCTM(nsnull);
|
||||
aTarget->SetMatrixPropagation(PR_TRUE);
|
||||
|
@ -505,23 +503,29 @@ nsSVGFilterInstance::GetFilterSubregion(
|
|||
#endif
|
||||
}
|
||||
|
||||
cairo_surface_t *
|
||||
already_AddRefed<gfxImageSurface>
|
||||
nsSVGFilterInstance::GetImage()
|
||||
{
|
||||
cairo_surface_t *surface =
|
||||
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, mFilterResX, mFilterResY);
|
||||
nsRefPtr<gfxImageSurface> surface =
|
||||
new gfxImageSurface(gfxIntSize(mFilterResX, mFilterResY),
|
||||
gfxASurface::ImageFormatARGB32);
|
||||
|
||||
if (surface && cairo_surface_status(surface)) {
|
||||
cairo_surface_destroy(surface);
|
||||
surface = nsnull;
|
||||
if (!(surface && surface->Data())) {
|
||||
return nsnull;
|
||||
}
|
||||
|
||||
return surface;
|
||||
gfxContext ctx(surface);
|
||||
ctx.SetOperator(gfxContext::OPERATOR_CLEAR);
|
||||
ctx.Paint();
|
||||
|
||||
gfxImageSurface *retval = nsnull;
|
||||
surface.swap(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
void
|
||||
nsSVGFilterInstance::LookupImage(const nsAString &aName,
|
||||
cairo_surface_t **aImage,
|
||||
gfxImageSurface **aImage,
|
||||
nsRect *aRegion,
|
||||
const ColorModel &aRequiredColorModel)
|
||||
{
|
||||
|
@ -534,14 +538,15 @@ nsSVGFilterInstance::LookupImage(const nsAString &aName,
|
|||
|
||||
if (entry) {
|
||||
*aImage = entry->mImage;
|
||||
NS_ADDREF(*aImage);
|
||||
*aRegion = entry->mRegion;
|
||||
|
||||
if (aRequiredColorModel == entry->mColorModel)
|
||||
return;
|
||||
|
||||
// convert image to desired format
|
||||
PRUint8 *data = cairo_image_surface_get_data(entry->mImage);
|
||||
PRInt32 stride = cairo_image_surface_get_stride(entry->mImage);
|
||||
PRUint8 *data = (*aImage)->Data();
|
||||
PRInt32 stride = (*aImage)->Stride();
|
||||
|
||||
if (entry->mColorModel.mAlphaChannel == ColorModel::PREMULTIPLIED)
|
||||
nsSVGUtils::UnPremultiplyImageDataAlpha(data, stride, entry->mRegion);
|
||||
|
@ -566,7 +571,7 @@ nsSVGFilterInstance::LookupImage(const nsAString &aName,
|
|||
|
||||
void
|
||||
nsSVGFilterInstance::DefineImage(const nsAString &aName,
|
||||
cairo_surface_t *aImage,
|
||||
gfxImageSurface *aImage,
|
||||
const nsRect &aRegion,
|
||||
const ColorModel &aColorModel)
|
||||
{
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
#include "nsIContent.h"
|
||||
#include "nsAutoPtr.h"
|
||||
|
||||
#include "cairo.h"
|
||||
#include "gfxImageSurface.h"
|
||||
|
||||
class nsSVGLength2;
|
||||
class nsSVGElement;
|
||||
|
@ -75,13 +75,13 @@ public:
|
|||
nsRect defaultRegion,
|
||||
nsRect *result);
|
||||
|
||||
cairo_surface_t *GetImage();
|
||||
already_AddRefed<gfxImageSurface> GetImage();
|
||||
void LookupImage(const nsAString &aName,
|
||||
cairo_surface_t **aImage,
|
||||
gfxImageSurface **aImage,
|
||||
nsRect *aRegion,
|
||||
const ColorModel &aColorModel);
|
||||
void DefineImage(const nsAString &aName,
|
||||
cairo_surface_t *aImage,
|
||||
gfxImageSurface *aImage,
|
||||
const nsRect &aRegion,
|
||||
const ColorModel &aColorModel);
|
||||
void GetFilterBox(float *x, float *y, float *width, float *height) {
|
||||
|
@ -110,17 +110,13 @@ public:
|
|||
private:
|
||||
class ImageEntry {
|
||||
public:
|
||||
ImageEntry(cairo_surface_t *aImage,
|
||||
ImageEntry(gfxImageSurface *aImage,
|
||||
const nsRect &aRegion,
|
||||
const ColorModel &aColorModel) :
|
||||
mImage(aImage), mRegion(aRegion), mColorModel(aColorModel) {
|
||||
cairo_surface_reference(aImage);
|
||||
}
|
||||
~ImageEntry() {
|
||||
cairo_surface_destroy(mImage);
|
||||
}
|
||||
|
||||
cairo_surface_t *mImage;
|
||||
nsRefPtr<gfxImageSurface> mImage;
|
||||
nsRect mRegion;
|
||||
ColorModel mColorModel;
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче