gfx/2d/HelpersD2D.h

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #ifndef MOZILLA_GFX_HELPERSD2D_H_
     7 #define MOZILLA_GFX_HELPERSD2D_H_
     9 #ifndef USE_D2D1_1
    10 #include "moz-d2d1-1.h"
    11 #else
    12 #include <d2d1_1.h>
    13 #endif
    15 #include <vector>
    17 #include <dwrite.h>
    18 #include "2D.h"
    19 #include "Logging.h"
    20 #include "Tools.h"
    21 #include "ImageScaling.h"
    23 #include "ScaledFontDWrite.h"
    25 #undef min
    26 #undef max
    28 namespace mozilla {
    29 namespace gfx {
    31 ID2D1Factory* D2DFactory();
    33 #ifdef USE_D2D1_1
    34 ID2D1Factory1* D2DFactory1();
    35 #endif
    37 static inline D2D1_POINT_2F D2DPoint(const Point &aPoint)
    38 {
    39   return D2D1::Point2F(aPoint.x, aPoint.y);
    40 }
    42 static inline D2D1_SIZE_U D2DIntSize(const IntSize &aSize)
    43 {
    44   return D2D1::SizeU(aSize.width, aSize.height);
    45 }
    47 static inline D2D1_RECT_F D2DRect(const Rect &aRect)
    48 {
    49   return D2D1::RectF(aRect.x, aRect.y, aRect.XMost(), aRect.YMost());
    50 }
    52 static inline D2D1_EXTEND_MODE D2DExtend(ExtendMode aExtendMode)
    53 {
    54   D2D1_EXTEND_MODE extend;
    55   switch (aExtendMode) {
    56   case ExtendMode::REPEAT:
    57     extend = D2D1_EXTEND_MODE_WRAP;
    58     break;
    59   case ExtendMode::REFLECT:
    60     extend = D2D1_EXTEND_MODE_MIRROR;
    61     break;
    62   default:
    63     extend = D2D1_EXTEND_MODE_CLAMP;
    64   }
    66   return extend;
    67 }
    69 static inline D2D1_BITMAP_INTERPOLATION_MODE D2DFilter(const Filter &aFilter)
    70 {
    71   switch (aFilter) {
    72   case Filter::POINT:
    73     return D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR;
    74   default:
    75     return D2D1_BITMAP_INTERPOLATION_MODE_LINEAR;
    76   }
    77 }
    79 #ifdef USE_D2D1_1
    80 static inline D2D1_INTERPOLATION_MODE D2DInterpolationMode(const Filter &aFilter)
    81 {
    82   switch (aFilter) {
    83   case Filter::POINT:
    84     return D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR;
    85   default:
    86     return D2D1_INTERPOLATION_MODE_LINEAR;
    87   }
    88 }
    90 static inline D2D1_MATRIX_5X4_F D2DMatrix5x4(const Matrix5x4 &aMatrix)
    91 {
    92   return D2D1::Matrix5x4F(aMatrix._11, aMatrix._12, aMatrix._13, aMatrix._14,
    93                           aMatrix._21, aMatrix._22, aMatrix._23, aMatrix._24,
    94                           aMatrix._31, aMatrix._32, aMatrix._33, aMatrix._34,
    95                           aMatrix._41, aMatrix._42, aMatrix._43, aMatrix._44,
    96                           aMatrix._51, aMatrix._52, aMatrix._53, aMatrix._54);
    97 }
    99 static inline D2D1_VECTOR_3F D2DVector3D(const Point3D &aPoint)
   100 {
   101   return D2D1::Vector3F(aPoint.x, aPoint.y, aPoint.z);
   102 }
   104 #endif
   106 static inline D2D1_ANTIALIAS_MODE D2DAAMode(AntialiasMode aMode)
   107 {
   108   switch (aMode) {
   109   case AntialiasMode::NONE:
   110     return D2D1_ANTIALIAS_MODE_ALIASED;
   111   default:
   112     return D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
   113   }
   114 }
   116 static inline D2D1_MATRIX_3X2_F D2DMatrix(const Matrix &aTransform)
   117 {
   118   return D2D1::Matrix3x2F(aTransform._11, aTransform._12,
   119                           aTransform._21, aTransform._22,
   120                           aTransform._31, aTransform._32);
   121 }
   123 static inline D2D1_COLOR_F D2DColor(const Color &aColor)
   124 {
   125   return D2D1::ColorF(aColor.r, aColor.g, aColor.b, aColor.a);
   126 }
   128 static inline IntSize ToIntSize(const D2D1_SIZE_U &aSize)
   129 {
   130   return IntSize(aSize.width, aSize.height);
   131 }
   133 static inline SurfaceFormat ToPixelFormat(const D2D1_PIXEL_FORMAT &aFormat)
   134 {
   135   switch(aFormat.format) {
   136   case DXGI_FORMAT_A8_UNORM:
   137     return SurfaceFormat::A8;
   138   case DXGI_FORMAT_B8G8R8A8_UNORM:
   139     if (aFormat.alphaMode == D2D1_ALPHA_MODE_IGNORE) {
   140       return SurfaceFormat::B8G8R8X8;
   141     } else {
   142       return SurfaceFormat::B8G8R8A8;
   143     }
   144   default:
   145     return SurfaceFormat::B8G8R8A8;
   146   }
   147 }
   149 static inline Rect ToRect(const D2D1_RECT_F &aRect)
   150 {
   151   return Rect(aRect.left, aRect.top, aRect.right - aRect.left, aRect.bottom - aRect.top);
   152 }
   154 static inline Matrix ToMatrix(const D2D1_MATRIX_3X2_F &aTransform)
   155 {
   156   return Matrix(aTransform._11, aTransform._12,
   157                 aTransform._21, aTransform._22,
   158                 aTransform._31, aTransform._32);
   159 }
   161 static inline Point ToPoint(const D2D1_POINT_2F &aPoint)
   162 {
   163   return Point(aPoint.x, aPoint.y);
   164 }
   166 static inline DXGI_FORMAT DXGIFormat(SurfaceFormat aFormat)
   167 {
   168   switch (aFormat) {
   169   case SurfaceFormat::B8G8R8A8:
   170     return DXGI_FORMAT_B8G8R8A8_UNORM;
   171   case SurfaceFormat::B8G8R8X8:
   172     return DXGI_FORMAT_B8G8R8A8_UNORM;
   173   case SurfaceFormat::A8:
   174     return DXGI_FORMAT_A8_UNORM;
   175   default:
   176     return DXGI_FORMAT_UNKNOWN;
   177   }
   178 }
   180 static inline D2D1_ALPHA_MODE D2DAlphaModeForFormat(SurfaceFormat aFormat)
   181 {
   182   switch (aFormat) {
   183   case SurfaceFormat::B8G8R8X8:
   184     return D2D1_ALPHA_MODE_IGNORE;
   185   default:
   186     return D2D1_ALPHA_MODE_PREMULTIPLIED;
   187   }
   188 }
   190 static inline D2D1_PIXEL_FORMAT D2DPixelFormat(SurfaceFormat aFormat)
   191 {
   192   return D2D1::PixelFormat(DXGIFormat(aFormat), D2DAlphaModeForFormat(aFormat));
   193 }
   195 #ifdef USE_D2D1_1
   196 static inline D2D1_COMPOSITE_MODE D2DCompositionMode(CompositionOp aOp)
   197 {
   198   switch(aOp) {
   199   case CompositionOp::OP_OVER:
   200     return D2D1_COMPOSITE_MODE_SOURCE_OVER;
   201   case CompositionOp::OP_ADD:
   202     return D2D1_COMPOSITE_MODE_PLUS;
   203   case CompositionOp::OP_ATOP:
   204     return D2D1_COMPOSITE_MODE_SOURCE_ATOP;
   205   case CompositionOp::OP_OUT:
   206     return D2D1_COMPOSITE_MODE_SOURCE_OUT;
   207   case CompositionOp::OP_IN:
   208     return D2D1_COMPOSITE_MODE_SOURCE_IN;
   209   case CompositionOp::OP_SOURCE:
   210     return D2D1_COMPOSITE_MODE_SOURCE_COPY;
   211   case CompositionOp::OP_DEST_IN:
   212     return D2D1_COMPOSITE_MODE_DESTINATION_IN;
   213   case CompositionOp::OP_DEST_OUT:
   214     return D2D1_COMPOSITE_MODE_DESTINATION_OUT;
   215   case CompositionOp::OP_DEST_OVER:
   216     return D2D1_COMPOSITE_MODE_DESTINATION_OVER;
   217   case CompositionOp::OP_DEST_ATOP:
   218     return D2D1_COMPOSITE_MODE_DESTINATION_ATOP;
   219   case CompositionOp::OP_XOR:
   220     return D2D1_COMPOSITE_MODE_XOR;
   221   default:
   222     return D2D1_COMPOSITE_MODE_SOURCE_OVER;
   223   }
   224 }
   225 #endif
   227 static inline bool IsPatternSupportedByD2D(const Pattern &aPattern)
   228 {
   229   if (aPattern.GetType() != PatternType::RADIAL_GRADIENT) {
   230     return true;
   231   }
   233   const RadialGradientPattern *pat =
   234     static_cast<const RadialGradientPattern*>(&aPattern);
   236   if (pat->mRadius1 != 0) {
   237     return false;
   238   }
   240   Point diff = pat->mCenter2 - pat->mCenter1;
   242   if (sqrt(diff.x * diff.x + diff.y * diff.y) >= pat->mRadius2) {
   243     // Inner point lies outside the circle.
   244     return false;
   245   }
   247   return true;
   248 }
   250 /**
   251  * This structure is used to pass rectangles to our shader constant. We can use
   252  * this for passing rectangular areas to SetVertexShaderConstant. In the format
   253  * of a 4 component float(x,y,width,height). Our vertex shader can then use
   254  * this to construct rectangular positions from the 0,0-1,1 quad that we source
   255  * it with.
   256  */
   257 struct ShaderConstantRectD3D10
   258 {
   259   float mX, mY, mWidth, mHeight;
   260   ShaderConstantRectD3D10(float aX, float aY, float aWidth, float aHeight)
   261     : mX(aX), mY(aY), mWidth(aWidth), mHeight(aHeight)
   262   { }
   264   // For easy passing to SetVertexShaderConstantF.
   265   operator float* () { return &mX; }
   266 };
   268 static inline DWRITE_MATRIX
   269 DWriteMatrixFromMatrix(Matrix &aMatrix)
   270 {
   271   DWRITE_MATRIX mat;
   272   mat.m11 = aMatrix._11;
   273   mat.m12 = aMatrix._12;
   274   mat.m21 = aMatrix._21;
   275   mat.m22 = aMatrix._22;
   276   mat.dx = aMatrix._31;
   277   mat.dy = aMatrix._32;
   278   return mat;
   279 }
   281 class AutoDWriteGlyphRun : public DWRITE_GLYPH_RUN
   282 {
   283     static const unsigned kNumAutoGlyphs = 256;
   285 public:
   286     AutoDWriteGlyphRun() {
   287         glyphCount = 0;
   288     }
   290     ~AutoDWriteGlyphRun() {
   291         if (glyphCount > kNumAutoGlyphs) {
   292             delete[] glyphIndices;
   293             delete[] glyphAdvances;
   294             delete[] glyphOffsets;
   295         }
   296     }
   298     void allocate(unsigned aNumGlyphs) {
   299         glyphCount = aNumGlyphs;
   300         if (aNumGlyphs <= kNumAutoGlyphs) {
   301             glyphIndices = &mAutoIndices[0];
   302             glyphAdvances = &mAutoAdvances[0];
   303             glyphOffsets = &mAutoOffsets[0];
   304         } else {
   305             glyphIndices = new UINT16[aNumGlyphs];
   306             glyphAdvances = new FLOAT[aNumGlyphs];
   307             glyphOffsets = new DWRITE_GLYPH_OFFSET[aNumGlyphs];
   308         }
   309     }
   311 private:
   312     DWRITE_GLYPH_OFFSET mAutoOffsets[kNumAutoGlyphs];
   313     FLOAT               mAutoAdvances[kNumAutoGlyphs];
   314     UINT16              mAutoIndices[kNumAutoGlyphs];
   315 };
   317 static inline void
   318 DWriteGlyphRunFromGlyphs(const GlyphBuffer &aGlyphs, ScaledFontDWrite *aFont, AutoDWriteGlyphRun *run)
   319 {
   320   run->allocate(aGlyphs.mNumGlyphs);
   322   FLOAT *advances = const_cast<FLOAT*>(run->glyphAdvances);
   323   UINT16 *indices = const_cast<UINT16*>(run->glyphIndices);
   324   DWRITE_GLYPH_OFFSET *offsets = const_cast<DWRITE_GLYPH_OFFSET*>(run->glyphOffsets);
   326   memset(advances, 0, sizeof(FLOAT) * aGlyphs.mNumGlyphs);
   327   for (unsigned int i = 0; i < aGlyphs.mNumGlyphs; i++) {
   328     indices[i] = aGlyphs.mGlyphs[i].mIndex;
   329     offsets[i].advanceOffset = aGlyphs.mGlyphs[i].mPosition.x;
   330     offsets[i].ascenderOffset = -aGlyphs.mGlyphs[i].mPosition.y;
   331   }
   333   run->bidiLevel = 0;
   334   run->fontFace = aFont->mFontFace;
   335   run->fontEmSize = aFont->GetSize();
   336   run->glyphCount = aGlyphs.mNumGlyphs;
   337   run->isSideways = FALSE;
   338 }
   340 static TemporaryRef<ID2D1Geometry>
   341 ConvertRectToGeometry(const D2D1_RECT_F& aRect)
   342 {
   343   RefPtr<ID2D1RectangleGeometry> rectGeom;
   344   D2DFactory()->CreateRectangleGeometry(&aRect, byRef(rectGeom));
   345   return rectGeom.forget();
   346 }
   348 static TemporaryRef<ID2D1Geometry>
   349 GetTransformedGeometry(ID2D1Geometry *aGeometry, const D2D1_MATRIX_3X2_F &aTransform)
   350 {
   351   RefPtr<ID2D1PathGeometry> tmpGeometry;
   352   D2DFactory()->CreatePathGeometry(byRef(tmpGeometry));
   353   RefPtr<ID2D1GeometrySink> currentSink;
   354   tmpGeometry->Open(byRef(currentSink));
   355   aGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES,
   356                       aTransform, currentSink);
   357   currentSink->Close();
   358   return tmpGeometry;
   359 }
   361 static TemporaryRef<ID2D1Geometry>
   362 IntersectGeometry(ID2D1Geometry *aGeometryA, ID2D1Geometry *aGeometryB)
   363 {
   364   RefPtr<ID2D1PathGeometry> pathGeom;
   365   D2DFactory()->CreatePathGeometry(byRef(pathGeom));
   366   RefPtr<ID2D1GeometrySink> sink;
   367   pathGeom->Open(byRef(sink));
   368   aGeometryA->CombineWithGeometry(aGeometryB, D2D1_COMBINE_MODE_INTERSECT, nullptr, sink);
   369   sink->Close();
   371   return pathGeom;
   372 }
   374 static TemporaryRef<ID2D1StrokeStyle>
   375 CreateStrokeStyleForOptions(const StrokeOptions &aStrokeOptions)
   376 {
   377   RefPtr<ID2D1StrokeStyle> style;
   379   D2D1_CAP_STYLE capStyle;
   380   D2D1_LINE_JOIN joinStyle;
   382   switch (aStrokeOptions.mLineCap) {
   383   case CapStyle::BUTT:
   384     capStyle = D2D1_CAP_STYLE_FLAT;
   385     break;
   386   case CapStyle::ROUND:
   387     capStyle = D2D1_CAP_STYLE_ROUND;
   388     break;
   389   case CapStyle::SQUARE:
   390     capStyle = D2D1_CAP_STYLE_SQUARE;
   391     break;
   392   }
   394   switch (aStrokeOptions.mLineJoin) {
   395   case JoinStyle::MITER:
   396     joinStyle = D2D1_LINE_JOIN_MITER;
   397     break;
   398   case JoinStyle::MITER_OR_BEVEL:
   399     joinStyle = D2D1_LINE_JOIN_MITER_OR_BEVEL;
   400     break;
   401   case JoinStyle::ROUND:
   402     joinStyle = D2D1_LINE_JOIN_ROUND;
   403     break;
   404   case JoinStyle::BEVEL:
   405     joinStyle = D2D1_LINE_JOIN_BEVEL;
   406     break;
   407   }
   410   HRESULT hr;
   411   if (aStrokeOptions.mDashPattern) {
   412     typedef std::vector<Float> FloatVector;
   413     // D2D "helpfully" multiplies the dash pattern by the line width.
   414     // That's not what cairo does, or is what <canvas>'s dash wants.
   415     // So fix the multiplication in advance.
   416     Float lineWidth = aStrokeOptions.mLineWidth;
   417     FloatVector dash(aStrokeOptions.mDashPattern,
   418                      aStrokeOptions.mDashPattern + aStrokeOptions.mDashLength);
   419     for (FloatVector::iterator it = dash.begin(); it != dash.end(); ++it) {
   420       *it /= lineWidth;
   421     }
   423     hr = D2DFactory()->CreateStrokeStyle(
   424       D2D1::StrokeStyleProperties(capStyle, capStyle,
   425                                   capStyle, joinStyle,
   426                                   aStrokeOptions.mMiterLimit,
   427                                   D2D1_DASH_STYLE_CUSTOM,
   428                                   aStrokeOptions.mDashOffset / lineWidth),
   429       &dash[0], // data() is not C++98, although it's in recent gcc
   430                 // and VC10's STL
   431       dash.size(),
   432       byRef(style));
   433   } else {
   434     hr = D2DFactory()->CreateStrokeStyle(
   435       D2D1::StrokeStyleProperties(capStyle, capStyle,
   436                                   capStyle, joinStyle,
   437                                   aStrokeOptions.mMiterLimit),
   438       nullptr, 0, byRef(style));
   439   }
   441   if (FAILED(hr)) {
   442     gfxWarning() << "Failed to create Direct2D stroke style.";
   443   }
   445   return style;
   446 }
   448 // This creates a (partially) uploaded bitmap for a DataSourceSurface. It
   449 // uploads the minimum requirement and possibly downscales. It adjusts the
   450 // input Matrix to compensate.
   451 static TemporaryRef<ID2D1Bitmap>
   452 CreatePartialBitmapForSurface(DataSourceSurface *aSurface, const Matrix &aDestinationTransform,
   453                               const IntSize &aDestinationSize, ExtendMode aExtendMode,
   454                               Matrix &aSourceTransform, ID2D1RenderTarget *aRT)
   455 {
   456   RefPtr<ID2D1Bitmap> bitmap;
   458   // This is where things get complicated. The source surface was
   459   // created for a surface that was too large to fit in a texture.
   460   // We'll need to figure out if we can work with a partial upload
   461   // or downsample in software.
   463   Matrix transform = aDestinationTransform;
   464   Matrix invTransform = transform = aSourceTransform * transform;
   465   if (!invTransform.Invert()) {
   466     // Singular transform, nothing to be drawn.
   467     return nullptr;
   468   }
   470   Rect rect(0, 0, Float(aDestinationSize.width), Float(aDestinationSize.height));
   472   // Calculate the rectangle of the source mapped to our surface.
   473   rect = invTransform.TransformBounds(rect);
   474   rect.RoundOut();
   476   IntSize size = aSurface->GetSize();
   478   Rect uploadRect(0, 0, Float(size.width), Float(size.height));
   480   // Limit the uploadRect as much as possible without supporting discontiguous uploads 
   481   //
   482   //                               region we will paint from
   483   //   uploadRect
   484   //   .---------------.              .---------------.         resulting uploadRect
   485   //   |               |rect          |               |
   486   //   |          .---------.         .----.     .----.          .---------------.
   487   //   |          |         |  ---->  |    |     |    |   ---->  |               |
   488   //   |          '---------'         '----'     '----'          '---------------'
   489   //   '---------------'              '---------------'
   490   //
   491   //
   493   if (uploadRect.Contains(rect)) {
   494     // Extend mode is irrelevant, the displayed rect is completely contained
   495     // by the source bitmap.
   496     uploadRect = rect;
   497   } else if (aExtendMode == ExtendMode::CLAMP && uploadRect.Intersects(rect)) {
   498     // Calculate the rectangle on the source bitmap that touches our
   499     // surface, and upload that, for ExtendMode::CLAMP we can actually guarantee
   500     // correct behaviour in this case.
   501     uploadRect = uploadRect.Intersect(rect);
   503     // We now proceed to check if we can limit at least one dimension of the
   504     // upload rect safely without looking at extend mode.
   505   } else if (rect.x >= 0 && rect.XMost() < size.width) {
   506     uploadRect.x = rect.x;
   507     uploadRect.width = rect.width;
   508   } else if (rect.y >= 0 && rect.YMost() < size.height) {
   509     uploadRect.y = rect.y;
   510     uploadRect.height = rect.height;
   511   }
   514   int stride = aSurface->Stride();
   516   if (uploadRect.width <= aRT->GetMaximumBitmapSize() &&
   517       uploadRect.height <= aRT->GetMaximumBitmapSize()) {
   519     // A partial upload will suffice.
   520     aRT->CreateBitmap(D2D1::SizeU(uint32_t(uploadRect.width), uint32_t(uploadRect.height)),
   521                       aSurface->GetData() + int(uploadRect.x) * 4 + int(uploadRect.y) * stride,
   522                       stride,
   523                       D2D1::BitmapProperties(D2DPixelFormat(aSurface->GetFormat())),
   524                       byRef(bitmap));
   526     aSourceTransform.Translate(uploadRect.x, uploadRect.y);
   528     return bitmap;
   529   } else {
   530     int Bpp = BytesPerPixel(aSurface->GetFormat());
   532     if (Bpp != 4) {
   533       // This shouldn't actually happen in practice!
   534       MOZ_ASSERT(false);
   535       return nullptr;
   536     }
   538     ImageHalfScaler scaler(aSurface->GetData(), stride, size);
   540     // Calculate the maximum width/height of the image post transform.
   541     Point topRight = transform * Point(Float(size.width), 0);
   542     Point topLeft = transform * Point(0, 0);
   543     Point bottomRight = transform * Point(Float(size.width), Float(size.height));
   544     Point bottomLeft = transform * Point(0, Float(size.height));
   546     IntSize scaleSize;
   548     scaleSize.width = int32_t(std::max(Distance(topRight, topLeft),
   549                                        Distance(bottomRight, bottomLeft)));
   550     scaleSize.height = int32_t(std::max(Distance(topRight, bottomRight),
   551                                         Distance(topLeft, bottomLeft)));
   553     if (unsigned(scaleSize.width) > aRT->GetMaximumBitmapSize()) {
   554       // Ok, in this case we'd really want a downscale of a part of the bitmap,
   555       // perhaps we can do this later but for simplicity let's do something
   556       // different here and assume it's good enough, this should be rare!
   557       scaleSize.width = 4095;
   558     }
   559     if (unsigned(scaleSize.height) > aRT->GetMaximumBitmapSize()) {
   560       scaleSize.height = 4095;
   561     }
   563     scaler.ScaleForSize(scaleSize);
   565     IntSize newSize = scaler.GetSize();
   567     aRT->CreateBitmap(D2D1::SizeU(newSize.width, newSize.height),
   568                       scaler.GetScaledData(), scaler.GetStride(),
   569                       D2D1::BitmapProperties(D2DPixelFormat(aSurface->GetFormat())),
   570                       byRef(bitmap));
   572     aSourceTransform.Scale(Float(size.width / newSize.width),
   573                            Float(size.height / newSize.height));
   574     return bitmap;
   575   }
   576 }
   578 }
   579 }
   581 #endif /* MOZILLA_GFX_HELPERSD2D_H_ */

mercurial